From 29c7a904a37c7bd0899d1fb436ae616fc82f7633 Mon Sep 17 00:00:00 2001 From: Andreas Tille Date: Mon, 30 Mar 2020 14:02:01 +0100 Subject: [PATCH] Import igraph_0.8.1+ds.orig.tar.xz [dgit import orig igraph_0.8.1+ds.orig.tar.xz] --- .astylerc | 37 + .editorconfig | 14 + .travis.yml | 58 + .zenodo.json | 13 + ACKNOWLEDGEMENTS.md | 272 + AUTHORS | 4 + CHANGELOG.md | 107 + CONTRIBUTING.md | 235 + COPYING | 340 + ChangeLog | 1 + INSTALL | 1 + Makefile.am | 90 + NEWS | 6 + ONEWS | 1435 + README.md | 20 + appveyor.yml | 96 + bootstrap.sh | 36 + configure.ac | 410 + doc/Makefile.am | 286 + doc/abstracts/wien08.txt | 24 + doc/adjlist.xxml | 53 + doc/arpack.xxml | 51 + doc/attributes.xxml | 110 + doc/basicigraph.xxml | 125 + doc/bibdatabase.xml | 51 + doc/bipartite.xxml | 34 + doc/c-docbook.re | 655 + doc/cliques.xxml | 40 + doc/coloring.xxml | 13 + doc/community.xxml | 59 + doc/devhelp.xsl | 131 + doc/docbook.outlang | 36 + doc/doxrox.py | 271 + doc/dqueue.xxml | 23 + doc/embedding.xxml | 16 + doc/error.xxml | 72 + doc/fdl.xml | 420 + doc/flows.xxml | 48 + doc/foreign.xxml | 50 + doc/frplots-small.png | Bin 0 -> 2013 bytes doc/frplots.png | Bin 0 -> 13137 bytes doc/generators.xxml | 71 + doc/gpl.xml | 444 + doc/graphlets.xxml | 20 + doc/gtk-doc.xsl | 368 + doc/heap.xxml | 20 + doc/hrg.xxml | 41 + doc/html/home.png | Bin 0 -> 654 bytes doc/html/left.png | Bin 0 -> 459 bytes doc/html/right.png | Bin 0 -> 472 bytes doc/html/style.css | 237 + doc/html/toggle.js | 18 + doc/html/up.png | Bin 0 -> 406 bytes doc/igraph-docs.xml | 133 + doc/igraph.3 | 46 + doc/igraph.info.diff | 20 + doc/igraphlogo/igraph-white.svg.gz | Bin 0 -> 6138 bytes doc/igraphlogo/igraph.svg.gz | Bin 0 -> 6101 bytes doc/igraphlogo/igraph2.svg.gz | Bin 0 -> 1952 bytes doc/installation.xml | 19 + doc/introduction.xml | 105 + doc/isomorphism.xxml | 57 + doc/iterators.xxml | 99 + doc/kkplots-small.png | Bin 0 -> 2029 bytes doc/kkplots.png | Bin 0 -> 13225 bytes doc/layout.xxml | 49 + doc/licenses.xml | 14 + doc/licenses/Licence_CeCILL-B_V1-en.txt | 515 + doc/licenses/Licence_CeCILL-B_V1-fr.txt | 519 + doc/licenses/gpl-2.0.txt | 281 + doc/licenses/gpl-3.0.txt | 621 + doc/licenses/lgpl-2.1.txt | 459 + doc/licenses/lgpl-3.0.txt | 165 + doc/matrix.xxml | 119 + doc/memory.xxml | 13 + doc/motifs.xxml | 32 + doc/nongraph.xxml | 37 + doc/operators.xxml | 25 + doc/papers/iccs06/ICCS.bst | 1 + doc/papers/iccs06/ICCSsty.tex | 1 + doc/papers/iccs06/Makefile | 29 + doc/papers/iccs06/arch.fig | 25 + doc/papers/iccs06/csardi2.tex | 373 + doc/papers/iccs06/grid2.fig | 100 + doc/papers/iccs06/karate.fig | 241 + doc/papers/iccs06/net.bib | 1559 + doc/pmt.xml | 120 + doc/presentations/iccs06/Makefile | 31 + doc/presentations/iccs06/arch.fig | 25 + doc/presentations/iccs06/demo1.R | 18 + doc/presentations/iccs06/demo2.R | 18 + doc/presentations/iccs06/demo3.R | 18 + doc/presentations/iccs06/iccs06.tex | 182 + doc/presentations/images/CCSS.svg.gz | Bin 0 -> 93900 bytes doc/presentations/images/RMKI-BME.svg.gz | Bin 0 -> 30406 bytes doc/presentations/images/arch.svg.gz | Bin 0 -> 2485 bytes doc/presentations/images/cblocks.pdf | Bin 0 -> 149219 bytes doc/presentations/images/clustering.png | Bin 0 -> 80490 bytes doc/presentations/images/degreedist.png | Bin 0 -> 44763 bytes doc/presentations/images/diameter.png | Bin 0 -> 49265 bytes doc/presentations/images/diameter2.png | Bin 0 -> 51772 bytes doc/presentations/images/groups.pdf | Bin 0 -> 156108 bytes doc/presentations/images/igraph.svg | 2783 ++ doc/presentations/images/karate3d.png | Bin 0 -> 126895 bytes doc/presentations/images/python3d.png | Bin 0 -> 89058 bytes doc/presentations/images/small3.pdf | Bin 0 -> 26223 bytes doc/presentations/images/small4.pdf | Bin 0 -> 27030 bytes doc/presentations/images/tkplot.png | Bin 0 -> 20531 bytes doc/presentations/images/vicsek.pdf | Bin 0 -> 120902 bytes doc/presentations/infovis07/BME.svg.gz | Bin 0 -> 27372 bytes doc/presentations/infovis07/Makefile | 54 + doc/presentations/infovis07/RMKI.svg.gz | Bin 0 -> 3813 bytes doc/presentations/infovis07/dendrogram.svg.gz | Bin 0 -> 4222 bytes .../infovis07/infovis07-igraph.tex | 124 + doc/presentations/infovis07/plots.R | 132 + .../infovis07/screenshots.svg.gz | Bin 0 -> 5355 bytes doc/presentations/largescale06/abstract.tex | 66 + doc/presentations/netsci06/arch.fig | 24 + doc/presentations/netsci06/demo.R | 68 + doc/presentations/netsci06/grid1.fig | 33 + doc/presentations/netsci06/grid2.fig | 90 + .../netsci06/igraph-netsci06.tex | 130 + doc/presentations/netsci07/Makefile | 68 + doc/presentations/netsci07/netsci07.tex | 269 + doc/presentations/netsci07/python3d-2.png | Bin 0 -> 124519 bytes doc/presentations/netsci07/sna_screenshot.jpg | Bin 0 -> 44510 bytes doc/presentations/netsci10/igraph_poster.pdf | Bin 0 -> 1027619 bytes .../netsci10/igraph_poster.svg.gz | Bin 0 -> 664080 bytes doc/presentations/nips08/Makefile | 8 + doc/presentations/nips08/NIPS2008.R | 639 + doc/presentations/nips08/boxplot.pdf | Bin 0 -> 64996 bytes .../nips08/centnet-betweenness.pdf | Bin 0 -> 29733 bytes .../nips08/centnet-closeness.pdf | Bin 0 -> 29595 bytes .../nips08/centnet-constraint.pdf | Bin 0 -> 29692 bytes doc/presentations/nips08/centnet-degree.pdf | Bin 0 -> 29487 bytes doc/presentations/nips08/centnet-evcent.pdf | Bin 0 -> 29992 bytes doc/presentations/nips08/centnet-infocent.pdf | Bin 0 -> 30041 bytes doc/presentations/nips08/centnet-trans.pdf | Bin 0 -> 29652 bytes doc/presentations/nips08/centnet.pdf | Bin 0 -> 28570 bytes doc/presentations/nips08/communities.pdf | Bin 0 -> 9372 bytes doc/presentations/nips08/dend.pdf | 802 + doc/presentations/nips08/igraph.tex | 973 + doc/presentations/nips08/spinglass1.pdf | Bin 0 -> 48568 bytes doc/presentations/nips08/spinglass2.pdf | Bin 0 -> 48569 bytes doc/presentations/tex/fltfonts.def | 291 + doc/presentations/tex/foil17.clo | 94 + doc/presentations/tex/foil20.clo | 93 + doc/presentations/tex/foil25.clo | 99 + doc/presentations/tex/foil30.clo | 104 + doc/presentations/tex/foils.cls | 837 + doc/presentations/tex/foils.sty | 56 + doc/presentations/tex/foilshrt.clo | 80 + doc/presentations/unil2009/abstract.txt | 28 + .../user2008/Libxml2-Logo-180x168.png | Bin 0 -> 6826 bytes doc/presentations/user2008/Makefile | 38 + doc/presentations/user2008/Rlogo-2.png | Bin 0 -> 212779 bytes doc/presentations/user2008/SQLite.png | Bin 0 -> 3246 bytes doc/presentations/user2008/arch.svg.gz | Bin 0 -> 3435 bytes doc/presentations/user2008/attr.svg.gz | Bin 0 -> 3547 bytes doc/presentations/user2008/ba.svg.gz | Bin 0 -> 14143 bytes doc/presentations/user2008/bipartite.svg.gz | Bin 0 -> 1544 bytes doc/presentations/user2008/csardi.tex | 842 + doc/presentations/user2008/er.svg.gz | Bin 0 -> 5776 bytes doc/presentations/user2008/g1.svg.gz | Bin 0 -> 2783 bytes doc/presentations/user2008/g2.svg.gz | Bin 0 -> 4653 bytes doc/presentations/user2008/g3.svg.gz | Bin 0 -> 3863 bytes doc/presentations/user2008/g4.svg.gz | Bin 0 -> 3871 bytes doc/presentations/user2008/g5.svg.gz | Bin 0 -> 6043 bytes doc/presentations/user2008/g6.svg.gz | Bin 0 -> 3321 bytes doc/presentations/user2008/g7.svg.gz | Bin 0 -> 2031 bytes doc/presentations/user2008/g8.svg.gz | Bin 0 -> 2533 bytes doc/presentations/user2008/gmplogo2.png | Bin 0 -> 3817 bytes doc/presentations/user2008/hash_small.png | Bin 0 -> 23169 bytes doc/presentations/user2008/hist3d_2lights.jpg | Bin 0 -> 15293 bytes doc/presentations/user2008/hyper.svg.gz | Bin 0 -> 1549 bytes doc/presentations/user2008/igraph0.svg.gz | Bin 0 -> 7724 bytes doc/presentations/user2008/igraph1.svg.gz | Bin 0 -> 7741 bytes doc/presentations/user2008/igraph11.svg.gz | Bin 0 -> 8438 bytes doc/presentations/user2008/igraph2.svg.gz | Bin 0 -> 8518 bytes doc/presentations/user2008/igraph3.svg.gz | Bin 0 -> 9370 bytes doc/presentations/user2008/indd.svg.gz | Bin 0 -> 9157 bytes doc/presentations/user2008/jgplot.png | Bin 0 -> 122899 bytes doc/presentations/user2008/logo125.png | Bin 0 -> 1627 bytes doc/presentations/user2008/matrix.jpg | Bin 0 -> 41619 bytes doc/presentations/user2008/mixed.svg.gz | Bin 0 -> 1436 bytes doc/presentations/user2008/orgnet.svg.gz | Bin 0 -> 9715 bytes doc/presentations/user2008/outdd.svg.gz | Bin 0 -> 8837 bytes doc/presentations/user2008/pause.sty | 185 + doc/presentations/user2008/plot.svg.gz | Bin 0 -> 20190 bytes doc/presentations/user2008/rglplot.png | Bin 0 -> 37054 bytes doc/presentations/user2008/schema.svg.gz | Bin 0 -> 2141 bytes doc/presentations/user2008/source_c.png | Bin 0 -> 10696 bytes doc/presentations/user2008/source_cpp.png | Bin 0 -> 10930 bytes doc/presentations/user2008/tkplot.png | Bin 0 -> 14302 bytes .../user2008/transitivity.svg.gz | Bin 0 -> 1434 bytes doc/presentations/wien08/3dplot.png | Bin 0 -> 289931 bytes doc/presentations/wien08/adjacency.pdf | Bin 0 -> 30705 bytes doc/presentations/wien08/adjlist.pdf | Bin 0 -> 28504 bytes doc/presentations/wien08/cb.Rdata | Bin 0 -> 672 bytes doc/presentations/wien08/code.R | 268 + doc/presentations/wien08/commstr.jpg | Bin 0 -> 92619 bytes doc/presentations/wien08/edgelist.pdf | Bin 0 -> 28169 bytes doc/presentations/wien08/edgelist.svg | 1531 + doc/presentations/wien08/ercomps.pdf | Bin 0 -> 23975 bytes doc/presentations/wien08/ex-betw.pdf | Bin 0 -> 31121 bytes doc/presentations/wien08/ex-close.pdf | Bin 0 -> 31192 bytes doc/presentations/wien08/ex-deg.pdf | Bin 0 -> 30605 bytes doc/presentations/wien08/ex-ev.pdf | Bin 0 -> 31123 bytes doc/presentations/wien08/ex-pagerank.pdf | Bin 0 -> 31124 bytes doc/presentations/wien08/example.graph.Rdata | Bin 0 -> 362 bytes doc/presentations/wien08/example.pdf | Bin 0 -> 29572 bytes doc/presentations/wien08/frplots.pdf | Bin 0 -> 17288 bytes doc/presentations/wien08/homepage.png | Bin 0 -> 315010 bytes doc/presentations/wien08/karate.net | 80 + doc/presentations/wien08/lesmis.txt | 254 + doc/presentations/wien08/presentation.tex | 649 + doc/presentations/wien08/relations.csv | 34 + doc/presentations/wien08/small1.pdf | Bin 0 -> 26230 bytes doc/presentations/wien08/small2.pdf | Bin 0 -> 26229 bytes doc/presentations/wien08/traits.csv | 10 + doc/progress.xxml | 38 + doc/random.xxml | 51 + doc/scg.xxml | 23 + doc/separators.xxml | 15 + doc/sitemap_gen.py | 2094 ++ doc/sitemap_gen_config.xml | 14 + doc/sna_screenshot-small.jpg | Bin 0 -> 14582 bytes doc/sna_screenshot.jpg | Bin 0 -> 44510 bytes doc/sparsemat.xxml | 93 + doc/sparsematrix.xxml | 62 + doc/spatialgames.xxml | 23 + doc/stack.xxml | 20 + doc/status.xxml | 27 + doc/structural.xxml | 208 + doc/strvector.xxml | 25 + doc/threading.xxml | 30 + doc/tutorial.xml | 314 + doc/vector.xxml | 136 + doc/version-greater-or-equal.xsl | 54 + doc/visitors.xxml | 25 + doc/zachary-small.png | Bin 0 -> 13018 bytes doc/zachary.png | Bin 0 -> 15227 bytes examples/benchmarks/bench.h | 53 + examples/benchmarks/igraph_cliques.c | 32 + examples/benchmarks/igraph_coloring.c | 27 + examples/benchmarks/igraph_maximal_cliques.c | 62 + examples/benchmarks/igraph_random_walk.c | 93 + examples/benchmarks/igraph_transitivity.c | 59 + examples/simple/2wheap.c | 163 + examples/simple/LINKS.NET | 15 + examples/simple/VF2-compat.c | 246 + examples/simple/adjlist.c | 70 + examples/simple/ak-4102.max | 24623 ++++++++++++++++ examples/simple/assortativity.c | 213 + examples/simple/assortativity.out | 10 + examples/simple/bellman_ford.c | 117 + examples/simple/bellman_ford.out | 16 + examples/simple/biguint.c | 97 + examples/simple/biguint.out | 2 + examples/simple/biguint_betweenness.c | 210 + examples/simple/bipartite.net | 26 + examples/simple/blas.c | 57 + examples/simple/blas.out | 1 + examples/simple/bug-1033045.c | 50 + examples/simple/bug-1033045.out | 9 + examples/simple/bug-1149658.c | 45 + examples/simple/cattr_bool_bug.c | 71 + examples/simple/cattr_bool_bug.graphml | 76 + examples/simple/cattributes.c | 437 + examples/simple/cattributes.out | 84 + examples/simple/cattributes2.c | 77 + examples/simple/cattributes2.out | 338 + examples/simple/cattributes3.c | 158 + examples/simple/cattributes3.out | 243 + examples/simple/cattributes4.c | 90 + examples/simple/cattributes4.out | 81 + examples/simple/cattributes5.c | 162 + examples/simple/cattributes5.out | 243 + examples/simple/celegansneural.gml | 15644 ++++++++++ examples/simple/centralization.c | 177 + examples/simple/cohesive_blocks.c | 186 + examples/simple/cohesive_blocks.out | 66 + examples/simple/d_indheap.c | 102 + examples/simple/d_indheap.out | 12 + examples/simple/dijkstra.c | 79 + examples/simple/dijkstra.out | 10 + examples/simple/dominator_tree.c | 165 + examples/simple/dominator_tree.out | 49 + examples/simple/dot.c | 52 + examples/simple/dot.out | 196 + examples/simple/dqueue.c | 118 + examples/simple/dqueue.out | 2 + examples/simple/edgelist1.dl | 10 + examples/simple/edgelist2.dl | 9 + examples/simple/edgelist3.dl | 11 + examples/simple/edgelist4.dl | 12 + examples/simple/edgelist5.dl | 9 + examples/simple/edgelist6.dl | 11 + examples/simple/edgelist7.dl | 6 + examples/simple/eigenvector_centrality.c | 85 + examples/simple/eigenvector_centrality.out | 3 + examples/simple/even_tarjan.c | 70 + examples/simple/flow.c | 106 + examples/simple/flow2.c | 257 + examples/simple/flow2.out | 10 + examples/simple/foreign.c | 46 + examples/simple/foreign.out | 11 + examples/simple/fullmatrix1.dl | 9 + examples/simple/fullmatrix2.dl | 10 + examples/simple/fullmatrix3.dl | 12 + examples/simple/fullmatrix4.dl | 10 + examples/simple/gml.c | 52 + examples/simple/gml.out | 612 + examples/simple/graphml-default-attrs.xml | 25 + examples/simple/graphml-hsa05010.xml | 379 + examples/simple/graphml-lenient.xml | 10 + examples/simple/graphml-malformed.xml | 30 + examples/simple/graphml-namespace.xml | 11 + examples/simple/graphml.c | 178 + examples/simple/graphml.out | 46 + examples/simple/heap.c | 31 + examples/simple/igraph_add_edges.c | 101 + examples/simple/igraph_add_edges.out | 3 + examples/simple/igraph_add_vertices.c | 67 + examples/simple/igraph_adjacency.c | 29 + .../igraph_adjacency_spectral_embedding.c | 70 + .../igraph_adjacency_spectral_embedding.out | 21 + examples/simple/igraph_all_st_cuts.c | 440 + examples/simple/igraph_all_st_cuts.out | 94 + examples/simple/igraph_all_st_mincuts.c | 166 + examples/simple/igraph_all_st_mincuts.out | 99 + examples/simple/igraph_are_connected.c | 109 + examples/simple/igraph_arpack_rnsolve.c | 213 + examples/simple/igraph_arpack_rnsolve.out | 11 + examples/simple/igraph_array.c | 87 + examples/simple/igraph_array.out | 37 + examples/simple/igraph_atlas.c | 58 + examples/simple/igraph_atlas.out | 31 + examples/simple/igraph_average_path_length.c | 40 + examples/simple/igraph_barabasi_game.c | 123 + examples/simple/igraph_barabasi_game2.c | 110 + examples/simple/igraph_betweenness.c | 204 + examples/simple/igraph_bfs.c | 61 + examples/simple/igraph_bfs.out | 6 + examples/simple/igraph_bfs2.c | 128 + examples/simple/igraph_bfs2.out | 12 + .../simple/igraph_biconnected_components.c | 85 + .../simple/igraph_biconnected_components.out | 6 + examples/simple/igraph_bipartite_create.c | 66 + examples/simple/igraph_bipartite_create.out | 8 + examples/simple/igraph_bipartite_projection.c | 181 + examples/simple/igraph_bridges.c | 35 + examples/simple/igraph_bridges.out | 2 + examples/simple/igraph_cliques.c | 167 + examples/simple/igraph_cliques.out | 50 + examples/simple/igraph_cocitation.c | 56 + examples/simple/igraph_cocitation.out | 8 + examples/simple/igraph_coloring.c | 30 + .../igraph_community_edge_betweenness.c | 193 + .../igraph_community_edge_betweenness.out | 4 + examples/simple/igraph_community_fastgreedy.c | 186 + .../simple/igraph_community_fastgreedy.out | 26 + examples/simple/igraph_community_infomap.c | 255 + examples/simple/igraph_community_infomap.out | 22 + .../igraph_community_label_propagation.c | 53 + .../igraph_community_leading_eigenvector.c | 116 + .../igraph_community_leading_eigenvector.out | 7 + .../igraph_community_leading_eigenvector2.c | 120 + .../igraph_community_leading_eigenvector2.out | 7 + examples/simple/igraph_community_leiden.c | 77 + examples/simple/igraph_community_multilevel.c | 103 + .../simple/igraph_community_multilevel.out | 10 + .../igraph_community_optimal_modularity.c | 111 + examples/simple/igraph_complementer.c | 109 + examples/simple/igraph_complementer.out | 87 + examples/simple/igraph_complex.c | 243 + examples/simple/igraph_compose.c | 117 + examples/simple/igraph_compose.out | 11 + examples/simple/igraph_convergence_degree.c | 53 + examples/simple/igraph_convergence_degree.out | 2 + examples/simple/igraph_convex_hull.c | 196 + examples/simple/igraph_convex_hull.out | 19 + examples/simple/igraph_copy.c | 56 + examples/simple/igraph_correlated_game.c | 38 + examples/simple/igraph_create.c | 88 + examples/simple/igraph_decompose.c | 104 + examples/simple/igraph_decompose.out | 18 + examples/simple/igraph_degree.c | 220 + examples/simple/igraph_degree.out | 13 + examples/simple/igraph_degree_sequence_game.c | 113 + .../simple/igraph_degree_sequence_game.out | 7 + examples/simple/igraph_delete_edges.c | 88 + examples/simple/igraph_delete_vertices.c | 78 + examples/simple/igraph_density.c | 151 + examples/simple/igraph_density.out | 29 + .../igraph_deterministic_optimal_imitation.c | 253 + examples/simple/igraph_diameter.c | 61 + examples/simple/igraph_diameter.out | 2 + examples/simple/igraph_difference.c | 121 + examples/simple/igraph_difference.out | 16 + examples/simple/igraph_disjoint_union.c | 95 + examples/simple/igraph_disjoint_union.out | 2 + examples/simple/igraph_eccentricity.c | 52 + examples/simple/igraph_eccentricity.out | 3 + examples/simple/igraph_edge_betweenness.c | 168 + examples/simple/igraph_edge_betweenness.out | 113 + examples/simple/igraph_eigen_matrix.c | 131 + examples/simple/igraph_eigen_matrix.out | 11 + examples/simple/igraph_eigen_matrix2.c | 113 + examples/simple/igraph_eigen_matrix2.out | 11 + examples/simple/igraph_eigen_matrix3.c | 95 + examples/simple/igraph_eigen_matrix3.out | 0 examples/simple/igraph_eigen_matrix4.c | 98 + examples/simple/igraph_eigen_matrix4.out | 2 + .../simple/igraph_eigen_matrix_symmetric.c | 123 + .../simple/igraph_eigen_matrix_symmetric.out | 4 + .../igraph_eigen_matrix_symmetric_arpack.c | 124 + .../igraph_eigen_matrix_symmetric_arpack.out | 4 + examples/simple/igraph_empty.c | 79 + examples/simple/igraph_erdos_renyi_game.c | 302 + examples/simple/igraph_es_adj.c | 126 + examples/simple/igraph_es_adj.out | 38 + examples/simple/igraph_es_fromto.c | 81 + examples/simple/igraph_es_fromto.out | 3 + examples/simple/igraph_es_pairs.c | 80 + examples/simple/igraph_es_path.c | 74 + examples/simple/igraph_feedback_arc_set.c | 77 + examples/simple/igraph_feedback_arc_set.out | 3 + examples/simple/igraph_feedback_arc_set_ip.c | 108 + .../simple/igraph_feedback_arc_set_ip.out | 5 + examples/simple/igraph_fisher_yates_shuffle.c | 105 + examples/simple/igraph_from_prufer.c | 47 + examples/simple/igraph_from_prufer.out | 16 + examples/simple/igraph_full.c | 29 + .../igraph_get_all_shortest_paths_dijkstra.c | 210 + ...igraph_get_all_shortest_paths_dijkstra.out | 19 + examples/simple/igraph_get_all_simple_paths.c | 61 + .../simple/igraph_get_all_simple_paths.out | 12 + examples/simple/igraph_get_eid.c | 160 + examples/simple/igraph_get_eid.out | 2 + examples/simple/igraph_get_eids.c | 224 + examples/simple/igraph_get_eids.out | 3 + examples/simple/igraph_get_shortest_paths.c | 131 + examples/simple/igraph_get_shortest_paths.out | 7 + examples/simple/igraph_get_shortest_paths2.c | 89 + .../simple/igraph_get_shortest_paths2.out | 16 + .../igraph_get_shortest_paths_dijkstra.c | 226 + .../igraph_get_shortest_paths_dijkstra.out | 18 + examples/simple/igraph_girth.c | 42 + examples/simple/igraph_gomory_hu_tree.c | 149 + examples/simple/igraph_grg_game.c | 72 + examples/simple/igraph_growing_random_game.c | 29 + examples/simple/igraph_has_multiple.c | 93 + examples/simple/igraph_hashtable.c | 133 + examples/simple/igraph_hashtable.out | 14 + examples/simple/igraph_hrg.c | 73 + examples/simple/igraph_hrg2.c | 77 + examples/simple/igraph_hrg2.out | 2 + examples/simple/igraph_hrg3.c | 77 + examples/simple/igraph_hrg3.out | 2 + examples/simple/igraph_i_cutheap.c | 64 + examples/simple/igraph_i_cutheap.out | 1 + examples/simple/igraph_i_layout_sphere.c | 85 + examples/simple/igraph_independent_sets.c | 87 + examples/simple/igraph_independent_sets.out | 36 + examples/simple/igraph_induced_subgraph_map.c | 62 + examples/simple/igraph_intersection.c | 124 + examples/simple/igraph_intersection.out | 7 + examples/simple/igraph_intersection2.c | 60 + examples/simple/igraph_intersection2.out | 21 + examples/simple/igraph_is_degree_sequence.c | 200 + examples/simple/igraph_is_directed.c | 43 + examples/simple/igraph_is_loop.c | 55 + examples/simple/igraph_is_loop.out | 2 + examples/simple/igraph_is_minimal_separator.c | 75 + examples/simple/igraph_is_multiple.c | 56 + examples/simple/igraph_is_multiple.out | 2 + examples/simple/igraph_is_separator.c | 88 + examples/simple/igraph_is_tree.c | 125 + examples/simple/igraph_isomorphic_bliss.c | 126 + examples/simple/igraph_isomorphic_vf2.c | 274 + examples/simple/igraph_k_regular_game.c | 197 + examples/simple/igraph_k_regular_game.out | 16 + examples/simple/igraph_knn.c | 70 + examples/simple/igraph_lapack_dgeev.c | 249 + examples/simple/igraph_lapack_dgeevx.c | 215 + examples/simple/igraph_lapack_dgehrd.c | 77 + examples/simple/igraph_lapack_dgehrd.out | 0 examples/simple/igraph_lapack_dgesv.c | 152 + examples/simple/igraph_lapack_dgesv.out | 1 + examples/simple/igraph_lapack_dsyevr.c | 221 + examples/simple/igraph_laplacian.c | 246 + examples/simple/igraph_laplacian.out | 80 + examples/simple/igraph_lattice.c | 181 + .../simple/igraph_layout_davidson_harel.c | 122 + examples/simple/igraph_layout_grid.c | 68 + examples/simple/igraph_layout_grid.out | 95 + examples/simple/igraph_layout_lgl.c | 49 + examples/simple/igraph_layout_mds.c | 89 + examples/simple/igraph_layout_mds.out | 10 + examples/simple/igraph_layout_merge.c | 82 + examples/simple/igraph_layout_merge2.c | 74 + examples/simple/igraph_layout_merge2.out | 13 + examples/simple/igraph_layout_merge3.c | 44 + .../simple/igraph_layout_reingold_tilford.c | 47 + .../simple/igraph_layout_reingold_tilford.in | 44 + examples/simple/igraph_layout_sugiyama.c | 97 + examples/simple/igraph_layout_sugiyama.out | 87 + examples/simple/igraph_lcf.c | 87 + examples/simple/igraph_local_transitivity.c | 59 + examples/simple/igraph_marked_queue.c | 62 + examples/simple/igraph_maximal_cliques.c | 161 + examples/simple/igraph_maximal_cliques.out | 22 + examples/simple/igraph_maximal_cliques2.c | 106 + examples/simple/igraph_maximal_cliques2.out | 12 + examples/simple/igraph_maximal_cliques3.c | 83 + examples/simple/igraph_maximal_cliques3.out | 28 + examples/simple/igraph_maximal_cliques4.c | 112 + examples/simple/igraph_maximal_cliques4.out | 30 + .../igraph_maximum_bipartite_matching.c | 329 + examples/simple/igraph_mincut.c | 111 + examples/simple/igraph_mincut.out | 12 + examples/simple/igraph_minimal_separators.c | 58 + .../simple/igraph_minimum_size_separators.c | 172 + .../simple/igraph_minimum_size_separators.out | 30 + .../simple/igraph_minimum_spanning_tree.c | 70 + .../simple/igraph_minimum_spanning_tree.out | 34 + examples/simple/igraph_moran_process.c | 226 + examples/simple/igraph_motifs_randesu.c | 71 + examples/simple/igraph_motifs_randesu.out | 9 + examples/simple/igraph_neighbors.c | 78 + examples/simple/igraph_neighbors.out | 3 + examples/simple/igraph_pagerank.c | 267 + examples/simple/igraph_pagerank.out | 16 + examples/simple/igraph_power_law_fit.c | 314 + examples/simple/igraph_power_law_fit.out | 42 + examples/simple/igraph_preference_game.c | 199 + examples/simple/igraph_psumtree.c | 198 + examples/simple/igraph_qsort.c | 58 + examples/simple/igraph_qsort.out | 1 + examples/simple/igraph_qsort_r.c | 68 + examples/simple/igraph_qsort_r.out | 1 + examples/simple/igraph_radius.c | 54 + examples/simple/igraph_random_sample.c | 224 + examples/simple/igraph_random_walk.c | 53 + examples/simple/igraph_read_graph_dl.c | 63 + examples/simple/igraph_read_graph_dl.out | 104 + examples/simple/igraph_read_graph_graphdb.c | 41 + examples/simple/igraph_read_graph_graphdb.out | 1500 + examples/simple/igraph_read_graph_lgl-1.lgl | 7 + examples/simple/igraph_read_graph_lgl-2.lgl | 7 + examples/simple/igraph_read_graph_lgl-3.lgl | 4 + examples/simple/igraph_read_graph_lgl.c | 80 + examples/simple/igraph_read_graph_lgl.out | 12 + .../simple/igraph_realize_degree_sequence.c | 143 + .../simple/igraph_realize_degree_sequence.out | 159 + examples/simple/igraph_reciprocity.c | 65 + examples/simple/igraph_rewire.c | 72 + examples/simple/igraph_ring.c | 174 + examples/simple/igraph_rng_get_exp.c | 36 + examples/simple/igraph_rng_get_exp.out | 1000 + .../simple/igraph_roulette_wheel_imitation.c | 284 + examples/simple/igraph_scg_grouping.c | 84 + examples/simple/igraph_scg_grouping.out | 1 + examples/simple/igraph_scg_grouping2.c | 79 + examples/simple/igraph_scg_grouping2.out | 4 + examples/simple/igraph_scg_grouping3.c | 115 + examples/simple/igraph_scg_grouping3.out | 5 + examples/simple/igraph_scg_grouping4.c | 94 + examples/simple/igraph_scg_grouping4.out | 4 + examples/simple/igraph_scg_semiprojectors.c | 116 + examples/simple/igraph_scg_semiprojectors.out | 34 + examples/simple/igraph_scg_semiprojectors2.c | 143 + .../simple/igraph_scg_semiprojectors2.out | 38 + examples/simple/igraph_scg_semiprojectors3.c | 126 + .../simple/igraph_scg_semiprojectors3.out | 34 + examples/simple/igraph_set.c | 86 + examples/simple/igraph_set.out | 1 + examples/simple/igraph_similarity.c | 218 + examples/simple/igraph_similarity.out | 48 + examples/simple/igraph_simplify.c | 92 + examples/simple/igraph_simplify.out | 10 + examples/simple/igraph_small.c | 35 + examples/simple/igraph_small.out | 5 + examples/simple/igraph_sparsemat.c | 171 + examples/simple/igraph_sparsemat.out | 295 + examples/simple/igraph_sparsemat2.c | 266 + examples/simple/igraph_sparsemat2.out | 0 examples/simple/igraph_sparsemat3.c | 322 + examples/simple/igraph_sparsemat3.out | 0 examples/simple/igraph_sparsemat4.c | 298 + examples/simple/igraph_sparsemat4.out | 0 examples/simple/igraph_sparsemat5.c | 377 + examples/simple/igraph_sparsemat5.out | 117 + examples/simple/igraph_sparsemat6.c | 66 + examples/simple/igraph_sparsemat7.c | 81 + examples/simple/igraph_sparsemat8.c | 209 + examples/simple/igraph_sparsemat9.c | 84 + .../simple/igraph_sparsemat_is_symmetric.c | 66 + examples/simple/igraph_sparsemat_minmax.c | 235 + examples/simple/igraph_sparsemat_minmax.out | 16 + .../simple/igraph_sparsemat_which_minmax.c | 274 + .../simple/igraph_sparsemat_which_minmax.out | 16 + examples/simple/igraph_star.c | 29 + examples/simple/igraph_stochastic_imitation.c | 244 + examples/simple/igraph_strvector.c | 192 + examples/simple/igraph_strvector.out | 64 + examples/simple/igraph_subisomorphic_lad.c | 324 + examples/simple/igraph_subisomorphic_lad.out | 31 + examples/simple/igraph_to_prufer.c | 160 + examples/simple/igraph_to_undirected.c | 52 + examples/simple/igraph_to_undirected.out | 48 + examples/simple/igraph_topological_sorting.c | 100 + .../simple/igraph_topological_sorting.out | 4 + .../simple/igraph_transitive_closure_dag.c | 54 + .../simple/igraph_transitive_closure_dag.out | 2 + examples/simple/igraph_transitivity.c | 94 + examples/simple/igraph_tree.c | 43 + examples/simple/igraph_tree.out | 2 + examples/simple/igraph_trie.c | 132 + examples/simple/igraph_trie.out | 38 + examples/simple/igraph_union.c | 154 + examples/simple/igraph_union.out | 69 + examples/simple/igraph_version.c | 41 + examples/simple/igraph_vs_nonadj.c | 65 + examples/simple/igraph_vs_nonadj.out | 2 + examples/simple/igraph_vs_seq.c | 50 + examples/simple/igraph_vs_seq.out | 1 + examples/simple/igraph_vs_vector.c | 85 + examples/simple/igraph_vs_vector.out | 1 + examples/simple/igraph_weighted_adjacency.c | 112 + examples/simple/igraph_weighted_adjacency.out | 30 + examples/simple/igraph_weighted_cliques.c | 174 + examples/simple/igraph_weighted_cliques.out | 136 + examples/simple/igraph_write_graph_leda.c | 104 + examples/simple/igraph_write_graph_leda.out | 133 + examples/simple/igraph_write_graph_lgl.c | 48 + examples/simple/igraph_write_graph_pajek.c | 75 + examples/simple/igraph_write_graph_pajek.out | 56 + examples/simple/indheap.c | 31 + examples/simple/input.dl | 104 + examples/simple/iso_b03_m1000.A00 | Bin 0 -> 5002 bytes examples/simple/isomorphism_test.c | 215 + examples/simple/isomorphism_test.out | 0 examples/simple/karate.gml | 530 + examples/simple/levc-stress.c | 66 + examples/simple/lineendings.c | 71 + examples/simple/lineendings.out | 44 + examples/simple/matrix.c | 175 + examples/simple/matrix.out | 37 + examples/simple/matrix2.c | 360 + examples/simple/matrix2.out | 167 + examples/simple/matrix3.c | 43 + examples/simple/mt.c | 39 + examples/simple/nodelist1.dl | 9 + examples/simple/nodelist2.dl | 11 + examples/simple/pajek.c | 55 + examples/simple/pajek1.net | 21 + examples/simple/pajek2.c | 54 + examples/simple/pajek2.net | 1 + examples/simple/pajek2.out | 1 + examples/simple/pajek3.net | 21 + examples/simple/pajek4.net | 22 + examples/simple/pajek5.net | 21 + examples/simple/pajek6.net | 21 + examples/simple/pajek_bip.net | 27 + examples/simple/pajek_bip2.net | 27 + examples/simple/pajek_bipartite.c | 43 + examples/simple/pajek_bipartite.out | 22 + examples/simple/pajek_bipartite2.c | 125 + examples/simple/pajek_bipartite2.out | 81 + examples/simple/pajek_signed.c | 102 + examples/simple/pajek_signed.net | 23 + examples/simple/pajek_signed.out | 55 + examples/simple/random_seed.c | 52 + examples/simple/scg.c | 162 + examples/simple/scg.out | 300 + examples/simple/scg2.c | 123 + examples/simple/scg2.out | 166 + examples/simple/scg3.c | 121 + examples/simple/scg3.out | 177 + examples/simple/single_target_shortest_path.c | 81 + .../simple/single_target_shortest_path.out | 10 + examples/simple/spinglass.c | 99 + examples/simple/spmatrix.c | 216 + examples/simple/spmatrix.out | 52 + examples/simple/stack.c | 94 + examples/simple/test.gxl | 54 + examples/simple/tls1.c | 53 + examples/simple/tls2.c | 237 + examples/simple/tls2.out | 23 + examples/simple/topology.c | 63 + examples/simple/topology.out | 4 + examples/simple/triad_census.c | 44 + examples/simple/triad_census.out | 2 + examples/simple/vector.c | 343 + examples/simple/vector.out | 26 + examples/simple/vector2.c | 129 + examples/simple/vector2.out | 16 + examples/simple/vector3.c | 46 + examples/simple/vector_ptr.c | 285 + examples/simple/walktrap.c | 70 + examples/simple/walktrap.out | 10 + examples/simple/watts_strogatz_game.c | 129 + examples/simple/wikti_en_V_syn.elist | 8293 ++++++ examples/tests/cattr_bool_bug2.c | 44 + examples/tests/cattr_bool_bug2.graphml | 7 + examples/tests/cattr_bool_bug2.out | 1 + examples/tests/igraph_closeness.c | 190 + examples/tests/igraph_closeness.out | 30 + .../igraph_community_fluid_communities.c | 69 + .../igraph_community_fluid_communities.out | 0 .../igraph_community_label_propagation.c | 100 + .../igraph_community_label_propagation.out | 0 examples/tests/igraph_community_leiden.c | 172 + examples/tests/igraph_community_leiden.out | 36 + examples/tests/igraph_decompose_strong.c | 73 + examples/tests/igraph_decompose_strong.out | 13 + .../igraph_layout_reingold_tilford_extended.c | 50 + ...igraph_layout_reingold_tilford_extended.in | 4 + examples/tests/maximal_cliques_callback.c | 96 + examples/tests/maximal_cliques_hist.c | 21 + examples/tests/maximal_cliques_hist.out | 1 + examples/tests/rng_reproducibility.c | 18 + examples/tests/rng_reproducibility.out | 32 + examples/tests/simplify_and_colorize.c | 58 + examples/tests/simplify_and_colorize.out | 70 + examples/tests/test_utilities.inc | 60 + examples/tests/tree.c | 38 + examples/tests/tree.out | 61 + igraph.pc.in | 12 + igraph_Info.plist.in | 22 + include/igraph.h | 100 + include/igraph_adjlist.h | 232 + include/igraph_arpack.h | 333 + include/igraph_array.h | 61 + include/igraph_array_pmt.h | 51 + include/igraph_attributes.h | 873 + include/igraph_bipartite.h | 97 + include/igraph_blas.h | 65 + include/igraph_centrality.h | 212 + include/igraph_cliques.h | 114 + include/igraph_cocitation.h | 66 + include/igraph_cohesive_blocks.h | 41 + include/igraph_coloring.h | 43 + include/igraph_community.h | 247 + include/igraph_complex.h | 104 + include/igraph_components.h | 61 + include/igraph_constants.h | 193 + include/igraph_constructors.h | 80 + include/igraph_conversion.h | 66 + include/igraph_datatype.h | 83 + include/igraph_decls.h | 26 + include/igraph_dqueue.h | 73 + include/igraph_dqueue_pmt.h | 49 + include/igraph_eigen.h | 112 + include/igraph_embedding.h | 69 + include/igraph_epidemics.h | 66 + include/igraph_error.h | 720 + include/igraph_flow.h | 169 + include/igraph_foreign.h | 85 + include/igraph_games.h | 227 + include/igraph_graphlets.h | 52 + include/igraph_heap.h | 83 + include/igraph_heap_pmt.h | 45 + include/igraph_hrg.h | 114 + include/igraph_interface.h | 86 + include/igraph_interrupt.h | 128 + include/igraph_iterators.h | 401 + include/igraph_lapack.h | 114 + include/igraph_layout.h | 250 + include/igraph_lsap.h | 16 + include/igraph_matching.h | 56 + include/igraph_matrix.h | 100 + include/igraph_matrix_pmt.h | 243 + include/igraph_memory.h | 47 + include/igraph_microscopic_update.h | 60 + include/igraph_mixing.h | 51 + include/igraph_motifs.h | 97 + include/igraph_neighborhood.h | 47 + include/igraph_nongraph.h | 93 + include/igraph_operators.h | 63 + include/igraph_paths.h | 146 + include/igraph_pmt.h | 150 + include/igraph_pmt_off.h | 158 + include/igraph_progress.h | 183 + include/igraph_psumtree.h | 58 + include/igraph_qsort.h | 40 + include/igraph_random.h | 133 + include/igraph_scan.h | 69 + include/igraph_scg.h | 142 + include/igraph_separators.h | 53 + include/igraph_sparsemat.h | 287 + include/igraph_spmatrix.h | 114 + include/igraph_stack.h | 79 + include/igraph_stack_pmt.h | 47 + include/igraph_statusbar.h | 126 + include/igraph_structural.h | 151 + include/igraph_strvector.h | 97 + include/igraph_threading.h.in | 43 + include/igraph_topology.h | 292 + include/igraph_transitivity.h | 64 + include/igraph_types.h | 91 + include/igraph_vector.h | 176 + include/igraph_vector_pmt.h | 265 + include/igraph_vector_ptr.h | 100 + include/igraph_vector_type.h | 34 + include/igraph_version.h.in | 46 + include/igraph_visitor.h | 132 + interfaces/R/README | 2 + interfaces/functions.def | 2100 ++ interfaces/java/COPYING | 340 + interfaces/java/README | 72 + interfaces/java/build.xml | 228 + .../java/etc/enums/Connectedness.properties | 3 + .../java/etc/enums/NeighborMode.properties | 3 + interfaces/java/etc/enums/StarMode.properties | 3 + interfaces/java/src/c/config.h.in | 1 + interfaces/java/src/c/conversion.c | 105 + interfaces/java/src/c/conversion.h | 59 + interfaces/java/src/c/jni_utils.c | 134 + interfaces/java/src/c/jni_utils.h | 68 + .../java/src/c/net_sf_igraph_Graph.c.in | 84 + .../java/src/c/net_sf_igraph_VertexSet.c | 130 + .../java/src/c/net_sf_igraph_VertexSet.h | 53 + interfaces/java/src/c/net_sf_igraph_enum.pmt | 44 + .../java/src/c/net_sf_igraph_enum_impl.pmt | 82 + interfaces/java/src/c/net_sf_igraph_enums.c | 60 + interfaces/java/src/c/net_sf_igraph_enums.h | 45 + interfaces/java/src/c/net_sf_igraph_pmt.h | 50 + interfaces/java/src/java/CoreException.java | 40 + interfaces/java/src/java/GenericEnum.java.in | 45 + interfaces/java/src/java/Graph.java.in | 59 + interfaces/java/src/java/VertexSet.java | 186 + .../java/src/java/util/LongRangeIterator.java | 95 + .../java/src/tests/BasicGraphTests.java | 158 + interfaces/java/types-C.def | 232 + interfaces/java/types-Java.def | 61 + interfaces/shell/Makefile.am | 23 + interfaces/shell/interface.c.in | 448 + interfaces/shell/types.def | 369 + m4/ax_check_compile_flag.m4 | 53 + m4/libtool.m4 | 8369 ++++++ optional/simpleraytracer/Color.cpp | 96 + optional/simpleraytracer/Color.h | 40 + optional/simpleraytracer/Light.cpp | 46 + optional/simpleraytracer/Light.h | 39 + optional/simpleraytracer/Point.cpp | 106 + optional/simpleraytracer/Point.h | 45 + optional/simpleraytracer/RIgraphRay.cpp | 94 + optional/simpleraytracer/Ray.cpp | 44 + optional/simpleraytracer/Ray.h | 33 + optional/simpleraytracer/RayTracer.cpp | 266 + optional/simpleraytracer/RayTracer.h | 63 + optional/simpleraytracer/RayVector.cpp | 128 + optional/simpleraytracer/RayVector.h | 49 + optional/simpleraytracer/Shape.cpp | 106 + optional/simpleraytracer/Shape.h | 65 + optional/simpleraytracer/Sphere.cpp | 71 + optional/simpleraytracer/Sphere.h | 31 + optional/simpleraytracer/Triangle.cpp | 90 + optional/simpleraytracer/Triangle.h | 27 + optional/simpleraytracer/unit_limiter.cpp | 15 + optional/simpleraytracer/unit_limiter.h | 10 + src/AMD/Include/amd.h | 411 + src/AMD/Include/amd_internal.h | 347 + src/AMD/Makefile | 73 + src/AMD/README.txt | 213 + src/AMD/Source/amd.f | 1214 + src/AMD/Source/amd_1.c | 180 + src/AMD/Source/amd_2.c | 1842 ++ src/AMD/Source/amd_aat.c | 184 + src/AMD/Source/amd_control.c | 63 + src/AMD/Source/amd_defaults.c | 37 + src/AMD/Source/amd_dump.c | 179 + src/AMD/Source/amd_global.c | 83 + src/AMD/Source/amd_info.c | 119 + src/AMD/Source/amd_order.c | 199 + src/AMD/Source/amd_post_tree.c | 120 + src/AMD/Source/amd_postorder.c | 206 + src/AMD/Source/amd_preprocess.c | 118 + src/AMD/Source/amd_valid.c | 92 + src/AMD/Source/amdbar.f | 1206 + src/CHOLMOD.diff | 89 + src/CHOLMOD/Check/License.txt | 24 + src/CHOLMOD/Check/cholmod_check.c | 2709 ++ src/CHOLMOD/Check/cholmod_read.c | 1319 + src/CHOLMOD/Check/cholmod_write.c | 744 + src/CHOLMOD/Check/lesser.txt | 504 + src/CHOLMOD/Cholesky/License.txt | 24 + src/CHOLMOD/Cholesky/cholmod_amd.c | 211 + src/CHOLMOD/Cholesky/cholmod_analyze.c | 942 + src/CHOLMOD/Cholesky/cholmod_colamd.c | 209 + src/CHOLMOD/Cholesky/cholmod_etree.c | 226 + src/CHOLMOD/Cholesky/cholmod_factorize.c | 428 + src/CHOLMOD/Cholesky/cholmod_postorder.c | 291 + src/CHOLMOD/Cholesky/cholmod_rcond.c | 160 + src/CHOLMOD/Cholesky/cholmod_resymbol.c | 608 + src/CHOLMOD/Cholesky/cholmod_rowcolcounts.c | 536 + src/CHOLMOD/Cholesky/cholmod_rowfac.c | 735 + src/CHOLMOD/Cholesky/cholmod_solve.c | 1684 ++ src/CHOLMOD/Cholesky/cholmod_spsolve.c | 396 + src/CHOLMOD/Cholesky/lesser.txt | 504 + src/CHOLMOD/Cholesky/t_cholmod_lsolve.c | 850 + src/CHOLMOD/Cholesky/t_cholmod_ltsolve.c | 849 + src/CHOLMOD/Cholesky/t_cholmod_rowfac.c | 457 + src/CHOLMOD/Cholesky/t_cholmod_solve.c | 177 + src/CHOLMOD/Core/License.txt | 25 + src/CHOLMOD/Core/cholmod_aat.c | 301 + src/CHOLMOD/Core/cholmod_add.c | 281 + src/CHOLMOD/Core/cholmod_band.c | 373 + src/CHOLMOD/Core/cholmod_change_factor.c | 1226 + src/CHOLMOD/Core/cholmod_common.c | 701 + src/CHOLMOD/Core/cholmod_complex.c | 549 + src/CHOLMOD/Core/cholmod_copy.c | 406 + src/CHOLMOD/Core/cholmod_dense.c | 701 + src/CHOLMOD/Core/cholmod_error.c | 79 + src/CHOLMOD/Core/cholmod_factor.c | 936 + src/CHOLMOD/Core/cholmod_memory.c | 575 + src/CHOLMOD/Core/cholmod_sparse.c | 651 + src/CHOLMOD/Core/cholmod_transpose.c | 1138 + src/CHOLMOD/Core/cholmod_triplet.c | 772 + src/CHOLMOD/Core/cholmod_version.c | 37 + src/CHOLMOD/Core/lesser.txt | 504 + src/CHOLMOD/Core/t_cholmod_change_factor.c | 660 + src/CHOLMOD/Core/t_cholmod_dense.c | 265 + src/CHOLMOD/Core/t_cholmod_transpose.c | 317 + src/CHOLMOD/Core/t_cholmod_triplet.c | 175 + src/CHOLMOD/Include/License.txt | 8 + src/CHOLMOD/Include/README.txt | 25 + src/CHOLMOD/Include/cholmod.h | 125 + src/CHOLMOD/Include/cholmod_blas.h | 455 + src/CHOLMOD/Include/cholmod_camd.h | 102 + src/CHOLMOD/Include/cholmod_check.h | 427 + src/CHOLMOD/Include/cholmod_cholesky.h | 565 + src/CHOLMOD/Include/cholmod_complexity.h | 264 + src/CHOLMOD/Include/cholmod_config.h | 85 + src/CHOLMOD/Include/cholmod_core.h | 2395 ++ src/CHOLMOD/Include/cholmod_internal.h | 404 + src/CHOLMOD/Include/cholmod_io64.h | 45 + src/CHOLMOD/Include/cholmod_matrixops.h | 237 + src/CHOLMOD/Include/cholmod_modify.h | 306 + src/CHOLMOD/Include/cholmod_partition.h | 166 + src/CHOLMOD/Include/cholmod_supernodal.h | 172 + src/CHOLMOD/Include/cholmod_template.h | 238 + src/CHOLMOD/Makefile | 75 + src/CHOLMOD/MatrixOps/License.txt | 25 + src/CHOLMOD/MatrixOps/cholmod_drop.c | 183 + src/CHOLMOD/MatrixOps/cholmod_horzcat.c | 203 + src/CHOLMOD/MatrixOps/cholmod_norm.c | 452 + src/CHOLMOD/MatrixOps/cholmod_scale.c | 217 + src/CHOLMOD/MatrixOps/cholmod_sdmult.c | 149 + src/CHOLMOD/MatrixOps/cholmod_ssmult.c | 487 + src/CHOLMOD/MatrixOps/cholmod_submatrix.c | 425 + src/CHOLMOD/MatrixOps/cholmod_symmetry.c | 488 + src/CHOLMOD/MatrixOps/cholmod_vertcat.c | 201 + src/CHOLMOD/MatrixOps/gpl.txt | 340 + src/CHOLMOD/MatrixOps/t_cholmod_sdmult.c | 726 + src/CHOLMOD/Modify/License.txt | 25 + src/CHOLMOD/Modify/cholmod_rowadd.c | 678 + src/CHOLMOD/Modify/cholmod_rowdel.c | 461 + src/CHOLMOD/Modify/cholmod_updown.c | 1570 + src/CHOLMOD/Modify/gpl.txt | 340 + src/CHOLMOD/Modify/t_cholmod_updown.c | 214 + src/CHOLMOD/Modify/t_cholmod_updown_numkr.c | 746 + src/CHOLMOD/Partition/License.txt | 25 + src/CHOLMOD/Partition/cholmod_camd.c | 231 + src/CHOLMOD/Partition/cholmod_ccolamd.c | 208 + src/CHOLMOD/Partition/cholmod_csymamd.c | 144 + src/CHOLMOD/Partition/cholmod_metis.c | 795 + src/CHOLMOD/Partition/cholmod_nesdis.c | 2168 ++ src/CHOLMOD/Partition/lesser.txt | 504 + src/CHOLMOD/README.txt | 81 + src/CHOLMOD/Supernodal/License.txt | 25 + .../Supernodal/cholmod_super_numeric.c | 307 + src/CHOLMOD/Supernodal/cholmod_super_solve.c | 217 + .../Supernodal/cholmod_super_symbolic.c | 862 + src/CHOLMOD/Supernodal/gpl.txt | 340 + src/CHOLMOD/Supernodal/t_cholmod_gpu.c | 972 + .../Supernodal/t_cholmod_super_numeric.c | 912 + .../Supernodal/t_cholmod_super_solve.c | 450 + src/COLAMD/Include/colamd.h | 251 + src/COLAMD/Makefile | 56 + src/COLAMD/README.txt | 118 + src/COLAMD/Source/colamd.c | 3604 +++ src/COLAMD/Source/colamd_global.c | 24 + src/DensityGrid.cpp | 281 + src/DensityGrid.h | 85 + src/DensityGrid_3d.cpp | 305 + src/DensityGrid_3d.h | 85 + src/Makefile.am | 438 + src/NetDataTypes.cpp | 221 + src/NetDataTypes.h | 926 + src/NetRoutines.cpp | 286 + src/NetRoutines.h | 61 + src/SuiteSparse_config/Makefile | 43 + src/SuiteSparse_config/README.txt | 48 + src/SuiteSparse_config/SuiteSparse_config.c | 191 + src/SuiteSparse_config/SuiteSparse_config.h | 202 + src/SuiteSparse_config/SuiteSparse_config.mk | 393 + .../SuiteSparse_config_GPU.mk | 393 + .../SuiteSparse_config_Mac.mk | 395 + src/adjlist.c | 930 + src/arpack.c | 1429 + src/array.c | 50 + src/array.pmt | 90 + src/atlas-edges.h | 1296 + src/atlas.c | 82 + src/attributes.c | 442 + src/basic_query.c | 64 + src/bfgs.c | 222 + src/bigint.c | 329 + src/bigint.h | 107 + src/bignum.c | 1983 ++ src/bignum.h | 125 + src/bipartite.c | 1147 + src/blas.c | 110 + src/bliss.cc | 262 + src/bliss/bignum.hh | 133 + src/bliss/bliss_heap.cc | 99 + src/bliss/defs.cc | 42 + src/bliss/defs.hh | 128 + src/bliss/graph.cc | 5609 ++++ src/bliss/graph.hh | 997 + src/bliss/heap.hh | 83 + src/bliss/igraph-changes.md | 36 + src/bliss/kqueue.hh | 162 + src/bliss/kstack.hh | 141 + src/bliss/orbit.cc | 144 + src/bliss/orbit.hh | 111 + src/bliss/partition.cc | 1143 + src/bliss/partition.hh | 308 + src/bliss/uintseqhash.cc | 117 + src/bliss/uintseqhash.hh | 65 + src/bliss/utils.cc | 122 + src/bliss/utils.hh | 69 + src/cattributes.c | 4211 +++ src/centrality.c | 3516 +++ src/cliquer/README | 61 + src/cliquer/cliquer.c | 1778 ++ src/cliquer/cliquer.h | 57 + src/cliquer/cliquer_graph.c | 768 + src/cliquer/cliquerconf.h | 68 + src/cliquer/graph.h | 75 + src/cliquer/misc.h | 73 + src/cliquer/reorder.c | 425 + src/cliquer/reorder.h | 26 + src/cliquer/set.h | 389 + src/cliques.c | 1401 + src/clustertool.cpp | 692 + src/cocitation.c | 777 + src/cohesive_blocks.c | 611 + src/coloring.c | 161 + src/community.c | 3845 +++ src/community_leiden.c | 1086 + src/complex.c | 392 + src/components.c | 1253 + src/conversion.c | 951 + src/cores.c | 159 + src/cs/UFconfig.h | 118 + src/cs/cs.h | 756 + src/cs/cs_add.c | 48 + src/cs/cs_amd.c | 384 + src/cs/cs_chol.c | 79 + src/cs/cs_cholsol.c | 46 + src/cs/cs_compress.c | 42 + src/cs/cs_counts.c | 81 + src/cs/cs_cumsum.c | 37 + src/cs/cs_dfs.c | 56 + src/cs/cs_dmperm.c | 164 + src/cs/cs_droptol.c | 29 + src/cs/cs_dropzeros.c | 29 + src/cs/cs_dupl.c | 54 + src/cs/cs_entry.c | 33 + src/cs/cs_ereach.c | 43 + src/cs/cs_etree.c | 50 + src/cs/cs_fkeep.c | 45 + src/cs/cs_gaxpy.c | 37 + src/cs/cs_happly.c | 39 + src/cs/cs_house.c | 50 + src/cs/cs_ipvec.c | 29 + src/cs/cs_leaf.c | 42 + src/cs/cs_load.c | 46 + src/cs/cs_lsolve.c | 38 + src/cs/cs_ltsolve.c | 38 + src/cs/cs_lu.c | 107 + src/cs/cs_lusol.c | 46 + src/cs/cs_malloc.c | 55 + src/cs/cs_maxtrans.c | 112 + src/cs/cs_multiply.c | 55 + src/cs/cs_norm.c | 36 + src/cs/cs_permute.c | 45 + src/cs/cs_pinv.c | 31 + src/cs/cs_post.c | 44 + src/cs/cs_print.c | 66 + src/cs/cs_pvec.c | 29 + src/cs/cs_qr.c | 94 + src/cs/cs_qrsol.c | 73 + src/cs/cs_randperm.c | 47 + src/cs/cs_reach.c | 39 + src/cs/cs_scatter.c | 42 + src/cs/cs_scc.c | 61 + src/cs/cs_schol.c | 46 + src/cs/cs_spsolve.c | 48 + src/cs/cs_sqr.c | 108 + src/cs/cs_symperm.c | 59 + src/cs/cs_tdfs.c | 44 + src/cs/cs_transpose.c | 45 + src/cs/cs_updown.c | 68 + src/cs/cs_usolve.c | 38 + src/cs/cs_util.c | 139 + src/cs/cs_utsolve.c | 38 + src/decomposition.c | 471 + src/degree_sequence.cpp | 490 + src/distances.c | 211 + src/dotproduct.c | 280 + src/dqueue.c | 55 + src/dqueue.pmt | 384 + src/drl_Node.h | 68 + src/drl_Node_3d.h | 68 + src/drl_graph.cpp | 1306 + src/drl_graph.h | 132 + src/drl_graph_3d.cpp | 873 + src/drl_graph_3d.h | 124 + src/drl_layout.cpp | 471 + src/drl_layout.h | 65 + src/drl_layout_3d.cpp | 118 + src/drl_layout_3d.h | 65 + src/drl_parse.cpp | 197 + src/drl_parse.h | 72 + src/eigen.c | 1520 + src/embedding.c | 1169 + src/f2c.h | 234 + src/f2c_dummy.c | 27 + src/fast_community.c | 1067 + src/feedback_arc_set.c | 665 + src/flow.c | 2529 ++ src/foreign-dl-header.h | 42 + src/foreign-dl-lexer.l | 140 + src/foreign-dl-parser.y | 309 + src/foreign-gml-header.h | 30 + src/foreign-gml-lexer.l | 99 + src/foreign-gml-parser.y | 258 + src/foreign-graphml.c | 1847 ++ src/foreign-lgl-header.h | 35 + src/foreign-lgl-lexer.l | 101 + src/foreign-lgl-parser.y | 148 + src/foreign-ncol-header.h | 34 + src/foreign-ncol-lexer.l | 100 + src/foreign-ncol-parser.y | 142 + src/foreign-pajek-header.h | 43 + src/foreign-pajek-lexer.l | 148 + src/foreign-pajek-parser.y | 755 + src/foreign.c | 3390 +++ src/forestfire.c | 263 + src/fortran_intrinsics.c | 53 + src/games.c | 4806 +++ src/gengraph_box_list.cpp | 108 + src/gengraph_box_list.h | 89 + src/gengraph_definitions.h | 216 + src/gengraph_degree_sequence.cpp | 420 + src/gengraph_degree_sequence.h | 101 + src/gengraph_graph_molloy_hash.cpp | 1173 + src/gengraph_graph_molloy_hash.h | 219 + src/gengraph_graph_molloy_optimized.cpp | 2221 ++ src/gengraph_graph_molloy_optimized.h | 288 + src/gengraph_hash.h | 308 + src/gengraph_header.h | 120 + src/gengraph_mr-connected.cpp | 186 + src/gengraph_powerlaw.cpp | 270 + src/gengraph_powerlaw.h | 86 + src/gengraph_qsort.h | 568 + src/gengraph_random.cpp | 278 + src/gengraph_random.h | 214 + src/gengraph_vertex_cover.h | 75 + src/glet.c | 871 + src/glpk_support.c | 100 + src/gml_tree.c | 261 + src/hacks.c | 54 + src/heap.c | 1082 + src/heap.pmt | 350 + src/hrg_dendro.h | 313 + src/hrg_graph.h | 167 + src/hrg_graph_simp.h | 160 + src/hrg_rbtree.h | 160 + src/hrg_splittree_eq.h | 183 + src/igraph_arpack_internal.h | 219 + src/igraph_blas_internal.h | 65 + src/igraph_buckets.c | 198 + src/igraph_cliquer.c | 399 + src/igraph_cliquer.h | 29 + src/igraph_error.c | 290 + src/igraph_estack.c | 67 + src/igraph_estack.h | 47 + src/igraph_f2c.h | 227 + src/igraph_fixed_vectorlist.c | 80 + src/igraph_flow_internal.h | 42 + src/igraph_glpk_support.h | 48 + src/igraph_gml_tree.h | 91 + src/igraph_grid.c | 543 + src/igraph_hacks_internal.h | 57 + src/igraph_hashtable.c | 128 + src/igraph_heap.c | 64 + src/igraph_hrg.cc | 1074 + src/igraph_hrg_types.cc | 3726 +++ src/igraph_interrupt_internal.h | 69 + src/igraph_lapack_internal.h | 184 + src/igraph_marked_queue.c | 115 + src/igraph_marked_queue.h | 70 + src/igraph_math.h | 100 + src/igraph_psumtree.c | 102 + src/igraph_set.c | 320 + src/igraph_stack.c | 89 + src/igraph_strvector.c | 591 + src/igraph_trie.c | 391 + src/igraph_types_internal.h | 395 + src/infomap.cc | 322 + src/infomap_FlowGraph.cc | 422 + src/infomap_FlowGraph.h | 78 + src/infomap_Greedy.cc | 614 + src/infomap_Greedy.h | 85 + src/infomap_Node.cc | 72 + src/infomap_Node.h | 52 + src/interrupt.c | 46 + src/iterators.c | 1916 ++ src/lad.c | 1664 ++ src/lapack.c | 954 + src/layout.c | 2425 ++ src/layout_dh.c | 459 + src/layout_fr.c | 701 + src/layout_gem.c | 246 + src/layout_kk.c | 680 + src/lsap.c | 631 + src/matching.c | 1029 + src/math.c | 324 + src/matrix.c | 158 + src/matrix.pmt | 1634 + src/maximal_cliques.c | 501 + src/maximal_cliques_template.h | 409 + src/memory.c | 99 + src/microscopic_update.c | 1209 + src/mixing.c | 300 + src/motifs.c | 1125 + src/operators.c | 1242 + src/optimal_modularity.c | 259 + src/other.c | 429 + src/paths.c | 175 + src/plfit/arithmetic_ansi.h | 133 + src/plfit/arithmetic_sse_double.h | 294 + src/plfit/arithmetic_sse_float.h | 291 + src/plfit/error.c | 75 + src/plfit/error.h | 86 + src/plfit/gss.c | 153 + src/plfit/gss.h | 146 + src/plfit/hzeta.c | 651 + src/plfit/hzeta.h | 96 + src/plfit/kolmogorov.c | 66 + src/plfit/kolmogorov.h | 43 + src/plfit/lbfgs.c | 1378 + src/plfit/lbfgs.h | 736 + src/plfit/mt.c | 88 + src/plfit/mt.h | 102 + src/plfit/options.c | 52 + src/plfit/platform.h | 64 + src/plfit/plfit.c | 1309 + src/plfit/plfit.h | 143 + src/plfit/plfit.inc | 10 + src/plfit/rbinom.c | 209 + src/plfit/sampling.c | 301 + src/plfit/sampling.h | 177 + src/pottsmodel_2.cpp | 2224 ++ src/pottsmodel_2.h | 167 + src/progress.c | 153 + src/prpack.cpp | 103 + src/prpack.h | 54 + src/prpack/prpack.h | 11 + src/prpack/prpack.inc | 23 + src/prpack/prpack_base_graph.cpp | 333 + src/prpack/prpack_base_graph.h | 42 + src/prpack/prpack_csc.h | 30 + src/prpack/prpack_csr.h | 16 + src/prpack/prpack_edge_list.h | 16 + src/prpack/prpack_igraph_graph.cpp | 146 + src/prpack/prpack_igraph_graph.h | 26 + src/prpack/prpack_preprocessed_ge_graph.cpp | 64 + src/prpack/prpack_preprocessed_ge_graph.h | 26 + src/prpack/prpack_preprocessed_graph.h | 17 + src/prpack/prpack_preprocessed_gs_graph.cpp | 81 + src/prpack/prpack_preprocessed_gs_graph.h | 30 + src/prpack/prpack_preprocessed_scc_graph.cpp | 202 + src/prpack/prpack_preprocessed_scc_graph.h | 39 + .../prpack_preprocessed_schur_graph.cpp | 121 + src/prpack/prpack_preprocessed_schur_graph.h | 33 + src/prpack/prpack_result.cpp | 12 + src/prpack/prpack_result.h | 27 + src/prpack/prpack_solver.cpp | 878 + src/prpack/prpack_solver.h | 178 + src/prpack/prpack_utils.cpp | 60 + src/prpack/prpack_utils.h | 34 + src/pstdint.h | 817 + src/qsort.c | 209 + src/qsort_r.c | 8 + src/random.c | 2497 ++ src/random_walk.c | 287 + src/sbm.c | 607 + src/scan.c | 880 + src/scg.c | 2293 ++ src/scg_approximate_methods.c | 172 + src/scg_exact_scg.c | 68 + src/scg_headers.h | 128 + src/scg_kmeans.c | 103 + src/scg_optimal_method.c | 240 + src/scg_utils.c | 93 + src/separators.c | 833 + src/sir.c | 255 + src/spanning_trees.c | 520 + src/sparsemat.c | 3057 ++ src/spectral_properties.c | 436 + src/spmatrix.c | 1050 + src/st-cuts.c | 1550 + src/stack.pmt | 294 + src/statusbar.c | 130 + src/structural_properties.c | 7257 +++++ src/structural_properties_internal.h | 47 + src/structure_generators.c | 2453 ++ src/sugiyama.c | 1340 + src/topology.c | 3136 ++ src/triangles.c | 978 + src/triangles_template.h | 118 + src/triangles_template1.h | 88 + src/type_indexededgelist.c | 1707 ++ src/types.c | 146 + src/vector.c | 466 + src/vector.pmt | 2684 ++ src/vector_ptr.c | 628 + src/version.c | 67 + src/visitors.c | 593 + src/walktrap.cpp | 168 + src/walktrap_communities.cpp | 936 + src/walktrap_communities.h | 175 + src/walktrap_graph.cpp | 250 + src/walktrap_graph.h | 105 + src/walktrap_heap.cpp | 241 + src/walktrap_heap.h | 134 + src/zeroin.c | 203 + tests/Makefile.am | 44 + tests/arpack.at | 68 + tests/atlocal.in | 11 + tests/attributes.at | 58 + tests/basic.at | 81 + tests/bipartite.at | 33 + tests/centralization.at | 27 + tests/cliques.at | 77 + tests/coloring.at | 29 + tests/community.at | 102 + tests/components.at | 44 + tests/conversion.at | 44 + tests/eigen.at | 59 + tests/embedding.at | 27 + tests/flow.at | 66 + tests/foreign.at | 114 + tests/hrg.at | 37 + tests/iterators.at | 57 + tests/layout.at | 80 + tests/matching.at | 26 + tests/microscopic.at | 41 + tests/motifs.at | 32 + tests/mt.at | 33 + tests/operators.at | 65 + tests/other.at | 32 + tests/qsort.at | 32 + tests/random.at | 54 + tests/scg.at | 79 + tests/separators.at | 53 + tests/structural_properties.at | 230 + tests/structure_generators.at | 142 + tests/testsuite.at | 64 + tests/topology.at | 65 + tests/types.at | 211 + tests/version.at | 29 + tests/visitors.at | 37 + tools/NEXT_VERSION | 1 + tools/arpack-sed.txt | 93 + tools/autoconf/as-version.m4 | 74 + tools/autoconf/ax_tls.m4 | 76 + tools/bump_version.sh | 74 + tools/create-msvc-projectfile.py | 71 + tools/exclude.txt | 2 + tools/extract_body.sh | 4 + tools/getglpk.sh | 281 + tools/getversion.sh | 13 + tools/insert-banner.sh | 43 + tools/jekyll_header.sh | 14 + tools/lapack/CompletePolish | 32 + tools/lapack/Makefile | 15 + tools/lapack/comment.l | 14 + tools/lapack/delete.sed | 4 + tools/lapack/extra/len_trim.f | 14 + tools/lapack/getlapack.sh | 191 + tools/lapack/lapack.patch | 156 + tools/lapack/lenscrub.l | 42 + tools/lapack/mt.patch | 498 + tools/lapack/split.sed | 55 + tools/leakcheck | 133 + tools/leakcheck.supp | 6 + tools/ltmain.patch | 15 + tools/protect_braces.sh | 6 + tools/removeexamples.py | 28 + tools/seqdict/__init__.py | 2 + tools/seqdict/mdict.py | 195 + tools/seqdict/ndict.py | 233 + tools/stimulus.py | 1488 + tools/test-icc-compiler.sh | 27 + tools/virtual/packer/OSX-10.8.4/template.json | 66 + tools/virtual/packer/floppy/Autounattend.xml | 161 + .../packer/floppy/install-cygwin-sshd.bat | 88 + tools/virtual/packer/floppy/oracle-cert.cer | Bin 0 -> 1398 bytes .../packer/floppy/set-power-config.bat | 5 + tools/virtual/packer/http/preseed.cfg | 68 + tools/virtual/packer/scripts/apt.sh | 4 + tools/virtual/packer/scripts/base.sh | 7 + tools/virtual/packer/scripts/build_time.sh | 2 + tools/virtual/packer/scripts/cleanup.sh | 32 + tools/virtual/packer/scripts/compilers.sh | 6 + tools/virtual/packer/scripts/git.sh | 3 + tools/virtual/packer/scripts/igraphdeps.sh | 6 + .../virtual/packer/scripts/install-python.sh | 45 + tools/virtual/packer/scripts/installr-svn.sh | 15 + tools/virtual/packer/scripts/installr.sh | 41 + tools/virtual/packer/scripts/jenkins.sh | 4 + tools/virtual/packer/scripts/osx-vagrant.sh | 15 + tools/virtual/packer/scripts/otherdeb.sh | 3 + tools/virtual/packer/scripts/rdeps.sh | 6 + tools/virtual/packer/scripts/sudo.sh | 6 + .../virtual/packer/scripts/support/kcpassword | 1 + tools/virtual/packer/scripts/vagrant.sh | 7 + tools/virtual/packer/scripts/vbox.sh | 14 + tools/virtual/packer/scripts/vmware.sh | 17 + .../packer/scripts/win-change-home-dirs.sh | 3 + .../packer/scripts/win-postinstall64.sh | 48 + .../virtual/packer/scripts/xcode-cli-tools.sh | 36 + .../packer/ubuntu-13.10-32/template.json | 94 + .../packer/ubuntu-13.10-64/template.json | 95 + tools/virtual/packer/windows7/template.json | 35 + .../org.igraph.tekton.build-c-0.5-main.plist | 30 + .../org.igraph.tekton.build-c-develop.plist | 30 + .../org.igraph.tekton.build-r-0.5-main.plist | 30 + ....igraph.tekton.build-r-0.7-graphlets.plist | 30 + .../org.igraph.tekton.build-r-develop.plist | 30 + .../org.igraph.tekton.check-r-0.5-main.plist | 32 + .../org.igraph.tekton.check-r-develop.plist | 32 + ...g.igraph.tekton.check-rdevel-develop.plist | 32 + .../org.igraph.tekton.update-r-svn.plist | 29 + tools/virtual/vagrant/load-all.sh | 17 + .../vagrant/provisioners/apt-get-install.sh | 4 + .../vagrant/provisioners/apt-update.sh | 5 + .../virtual/vagrant/provisioners/compilers.sh | 5 + .../virtual/vagrant/provisioners/createdir.sh | 7 + .../vagrant/provisioners/igraphdeps.sh | 9 + .../vagrant/provisioners/install-service.sh | 22 + .../vagrant/provisioners/installR-asan.sh | 47 + .../vagrant/provisioners/installR-svn.sh | 25 + .../virtual/vagrant/provisioners/installR.sh | 45 + tools/virtual/vagrant/provisioners/sshkey.sh | 16 + tools/virtual/vagrant/run-script.sh | 26 + tools/virtual/vagrant/scripts/build-c.sh | 44 + tools/virtual/vagrant/scripts/build-r.sh | 49 + tools/virtual/vagrant/scripts/check-r-asan.sh | 67 + tools/virtual/vagrant/scripts/check-r.sh | 73 + tools/virtual/vagrant/scripts/key/dummy | 0 tools/virtual/vagrant/scripts/update-r-svn.sh | 14 + .../vagrant/ubuntu-13.04-x86_64/Vagrantfile | 28 + .../vagrant/ubuntu-13.04-x86_64/error/dummy | 0 .../vagrant/ubuntu-13.04-x86_64/output/dummy | 0 tools/virtual/vagrant/unload-all.sh | 5 + 1475 files changed, 379803 insertions(+) create mode 100644 .astylerc create mode 100644 .editorconfig create mode 100644 .travis.yml create mode 100644 .zenodo.json create mode 100644 ACKNOWLEDGEMENTS.md create mode 100644 AUTHORS create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 ONEWS create mode 100644 README.md create mode 100644 appveyor.yml create mode 100755 bootstrap.sh create mode 100644 configure.ac create mode 100644 doc/Makefile.am create mode 100644 doc/abstracts/wien08.txt create mode 100644 doc/adjlist.xxml create mode 100644 doc/arpack.xxml create mode 100644 doc/attributes.xxml create mode 100644 doc/basicigraph.xxml create mode 100644 doc/bibdatabase.xml create mode 100644 doc/bipartite.xxml create mode 100644 doc/c-docbook.re create mode 100644 doc/cliques.xxml create mode 100644 doc/coloring.xxml create mode 100644 doc/community.xxml create mode 100644 doc/devhelp.xsl create mode 100644 doc/docbook.outlang create mode 100755 doc/doxrox.py create mode 100644 doc/dqueue.xxml create mode 100644 doc/embedding.xxml create mode 100644 doc/error.xxml create mode 100644 doc/fdl.xml create mode 100644 doc/flows.xxml create mode 100644 doc/foreign.xxml create mode 100644 doc/frplots-small.png create mode 100644 doc/frplots.png create mode 100644 doc/generators.xxml create mode 100644 doc/gpl.xml create mode 100644 doc/graphlets.xxml create mode 100644 doc/gtk-doc.xsl create mode 100644 doc/heap.xxml create mode 100644 doc/hrg.xxml create mode 100644 doc/html/home.png create mode 100644 doc/html/left.png create mode 100644 doc/html/right.png create mode 100644 doc/html/style.css create mode 100644 doc/html/toggle.js create mode 100644 doc/html/up.png create mode 100644 doc/igraph-docs.xml create mode 100644 doc/igraph.3 create mode 100644 doc/igraph.info.diff create mode 100644 doc/igraphlogo/igraph-white.svg.gz create mode 100644 doc/igraphlogo/igraph.svg.gz create mode 100644 doc/igraphlogo/igraph2.svg.gz create mode 100644 doc/installation.xml create mode 100644 doc/introduction.xml create mode 100644 doc/isomorphism.xxml create mode 100644 doc/iterators.xxml create mode 100644 doc/kkplots-small.png create mode 100644 doc/kkplots.png create mode 100644 doc/layout.xxml create mode 100644 doc/licenses.xml create mode 100644 doc/licenses/Licence_CeCILL-B_V1-en.txt create mode 100644 doc/licenses/Licence_CeCILL-B_V1-fr.txt create mode 100644 doc/licenses/gpl-2.0.txt create mode 100644 doc/licenses/gpl-3.0.txt create mode 100644 doc/licenses/lgpl-2.1.txt create mode 100644 doc/licenses/lgpl-3.0.txt create mode 100644 doc/matrix.xxml create mode 100644 doc/memory.xxml create mode 100644 doc/motifs.xxml create mode 100644 doc/nongraph.xxml create mode 100644 doc/operators.xxml create mode 100644 doc/papers/iccs06/ICCS.bst create mode 100644 doc/papers/iccs06/ICCSsty.tex create mode 100644 doc/papers/iccs06/Makefile create mode 100644 doc/papers/iccs06/arch.fig create mode 100644 doc/papers/iccs06/csardi2.tex create mode 100644 doc/papers/iccs06/grid2.fig create mode 100644 doc/papers/iccs06/karate.fig create mode 100644 doc/papers/iccs06/net.bib create mode 100644 doc/pmt.xml create mode 100644 doc/presentations/iccs06/Makefile create mode 100644 doc/presentations/iccs06/arch.fig create mode 100644 doc/presentations/iccs06/demo1.R create mode 100644 doc/presentations/iccs06/demo2.R create mode 100644 doc/presentations/iccs06/demo3.R create mode 100644 doc/presentations/iccs06/iccs06.tex create mode 100644 doc/presentations/images/CCSS.svg.gz create mode 100644 doc/presentations/images/RMKI-BME.svg.gz create mode 100644 doc/presentations/images/arch.svg.gz create mode 100644 doc/presentations/images/cblocks.pdf create mode 100644 doc/presentations/images/clustering.png create mode 100644 doc/presentations/images/degreedist.png create mode 100644 doc/presentations/images/diameter.png create mode 100644 doc/presentations/images/diameter2.png create mode 100644 doc/presentations/images/groups.pdf create mode 100644 doc/presentations/images/igraph.svg create mode 100644 doc/presentations/images/karate3d.png create mode 100644 doc/presentations/images/python3d.png create mode 100644 doc/presentations/images/small3.pdf create mode 100644 doc/presentations/images/small4.pdf create mode 100644 doc/presentations/images/tkplot.png create mode 100644 doc/presentations/images/vicsek.pdf create mode 100644 doc/presentations/infovis07/BME.svg.gz create mode 100644 doc/presentations/infovis07/Makefile create mode 100644 doc/presentations/infovis07/RMKI.svg.gz create mode 100644 doc/presentations/infovis07/dendrogram.svg.gz create mode 100644 doc/presentations/infovis07/infovis07-igraph.tex create mode 100644 doc/presentations/infovis07/plots.R create mode 100644 doc/presentations/infovis07/screenshots.svg.gz create mode 100644 doc/presentations/largescale06/abstract.tex create mode 100644 doc/presentations/netsci06/arch.fig create mode 100644 doc/presentations/netsci06/demo.R create mode 100644 doc/presentations/netsci06/grid1.fig create mode 100644 doc/presentations/netsci06/grid2.fig create mode 100644 doc/presentations/netsci06/igraph-netsci06.tex create mode 100644 doc/presentations/netsci07/Makefile create mode 100644 doc/presentations/netsci07/netsci07.tex create mode 100644 doc/presentations/netsci07/python3d-2.png create mode 100644 doc/presentations/netsci07/sna_screenshot.jpg create mode 100644 doc/presentations/netsci10/igraph_poster.pdf create mode 100644 doc/presentations/netsci10/igraph_poster.svg.gz create mode 100644 doc/presentations/nips08/Makefile create mode 100644 doc/presentations/nips08/NIPS2008.R create mode 100644 doc/presentations/nips08/boxplot.pdf create mode 100644 doc/presentations/nips08/centnet-betweenness.pdf create mode 100644 doc/presentations/nips08/centnet-closeness.pdf create mode 100644 doc/presentations/nips08/centnet-constraint.pdf create mode 100644 doc/presentations/nips08/centnet-degree.pdf create mode 100644 doc/presentations/nips08/centnet-evcent.pdf create mode 100644 doc/presentations/nips08/centnet-infocent.pdf create mode 100644 doc/presentations/nips08/centnet-trans.pdf create mode 100644 doc/presentations/nips08/centnet.pdf create mode 100644 doc/presentations/nips08/communities.pdf create mode 100644 doc/presentations/nips08/dend.pdf create mode 100644 doc/presentations/nips08/igraph.tex create mode 100644 doc/presentations/nips08/spinglass1.pdf create mode 100644 doc/presentations/nips08/spinglass2.pdf create mode 100644 doc/presentations/tex/fltfonts.def create mode 100644 doc/presentations/tex/foil17.clo create mode 100644 doc/presentations/tex/foil20.clo create mode 100644 doc/presentations/tex/foil25.clo create mode 100644 doc/presentations/tex/foil30.clo create mode 100644 doc/presentations/tex/foils.cls create mode 100644 doc/presentations/tex/foils.sty create mode 100644 doc/presentations/tex/foilshrt.clo create mode 100644 doc/presentations/unil2009/abstract.txt create mode 100644 doc/presentations/user2008/Libxml2-Logo-180x168.png create mode 100644 doc/presentations/user2008/Makefile create mode 100644 doc/presentations/user2008/Rlogo-2.png create mode 100644 doc/presentations/user2008/SQLite.png create mode 100644 doc/presentations/user2008/arch.svg.gz create mode 100644 doc/presentations/user2008/attr.svg.gz create mode 100644 doc/presentations/user2008/ba.svg.gz create mode 100644 doc/presentations/user2008/bipartite.svg.gz create mode 100644 doc/presentations/user2008/csardi.tex create mode 100644 doc/presentations/user2008/er.svg.gz create mode 100644 doc/presentations/user2008/g1.svg.gz create mode 100644 doc/presentations/user2008/g2.svg.gz create mode 100644 doc/presentations/user2008/g3.svg.gz create mode 100644 doc/presentations/user2008/g4.svg.gz create mode 100644 doc/presentations/user2008/g5.svg.gz create mode 100644 doc/presentations/user2008/g6.svg.gz create mode 100644 doc/presentations/user2008/g7.svg.gz create mode 100644 doc/presentations/user2008/g8.svg.gz create mode 100644 doc/presentations/user2008/gmplogo2.png create mode 100644 doc/presentations/user2008/hash_small.png create mode 100644 doc/presentations/user2008/hist3d_2lights.jpg create mode 100644 doc/presentations/user2008/hyper.svg.gz create mode 100644 doc/presentations/user2008/igraph0.svg.gz create mode 100644 doc/presentations/user2008/igraph1.svg.gz create mode 100644 doc/presentations/user2008/igraph11.svg.gz create mode 100644 doc/presentations/user2008/igraph2.svg.gz create mode 100644 doc/presentations/user2008/igraph3.svg.gz create mode 100644 doc/presentations/user2008/indd.svg.gz create mode 100644 doc/presentations/user2008/jgplot.png create mode 100644 doc/presentations/user2008/logo125.png create mode 100644 doc/presentations/user2008/matrix.jpg create mode 100644 doc/presentations/user2008/mixed.svg.gz create mode 100644 doc/presentations/user2008/orgnet.svg.gz create mode 100644 doc/presentations/user2008/outdd.svg.gz create mode 100644 doc/presentations/user2008/pause.sty create mode 100644 doc/presentations/user2008/plot.svg.gz create mode 100644 doc/presentations/user2008/rglplot.png create mode 100644 doc/presentations/user2008/schema.svg.gz create mode 100755 doc/presentations/user2008/source_c.png create mode 100755 doc/presentations/user2008/source_cpp.png create mode 100644 doc/presentations/user2008/tkplot.png create mode 100644 doc/presentations/user2008/transitivity.svg.gz create mode 100644 doc/presentations/wien08/3dplot.png create mode 100644 doc/presentations/wien08/adjacency.pdf create mode 100644 doc/presentations/wien08/adjlist.pdf create mode 100644 doc/presentations/wien08/cb.Rdata create mode 100644 doc/presentations/wien08/code.R create mode 100644 doc/presentations/wien08/commstr.jpg create mode 100644 doc/presentations/wien08/edgelist.pdf create mode 100644 doc/presentations/wien08/edgelist.svg create mode 100644 doc/presentations/wien08/ercomps.pdf create mode 100644 doc/presentations/wien08/ex-betw.pdf create mode 100644 doc/presentations/wien08/ex-close.pdf create mode 100644 doc/presentations/wien08/ex-deg.pdf create mode 100644 doc/presentations/wien08/ex-ev.pdf create mode 100644 doc/presentations/wien08/ex-pagerank.pdf create mode 100644 doc/presentations/wien08/example.graph.Rdata create mode 100644 doc/presentations/wien08/example.pdf create mode 100644 doc/presentations/wien08/frplots.pdf create mode 100644 doc/presentations/wien08/homepage.png create mode 100644 doc/presentations/wien08/karate.net create mode 100644 doc/presentations/wien08/lesmis.txt create mode 100644 doc/presentations/wien08/presentation.tex create mode 100644 doc/presentations/wien08/relations.csv create mode 100644 doc/presentations/wien08/small1.pdf create mode 100644 doc/presentations/wien08/small2.pdf create mode 100644 doc/presentations/wien08/traits.csv create mode 100644 doc/progress.xxml create mode 100644 doc/random.xxml create mode 100644 doc/scg.xxml create mode 100644 doc/separators.xxml create mode 100755 doc/sitemap_gen.py create mode 100644 doc/sitemap_gen_config.xml create mode 100644 doc/sna_screenshot-small.jpg create mode 100644 doc/sna_screenshot.jpg create mode 100644 doc/sparsemat.xxml create mode 100644 doc/sparsematrix.xxml create mode 100644 doc/spatialgames.xxml create mode 100644 doc/stack.xxml create mode 100644 doc/status.xxml create mode 100644 doc/structural.xxml create mode 100644 doc/strvector.xxml create mode 100644 doc/threading.xxml create mode 100644 doc/tutorial.xml create mode 100644 doc/vector.xxml create mode 100644 doc/version-greater-or-equal.xsl create mode 100644 doc/visitors.xxml create mode 100644 doc/zachary-small.png create mode 100644 doc/zachary.png create mode 100644 examples/benchmarks/bench.h create mode 100644 examples/benchmarks/igraph_cliques.c create mode 100644 examples/benchmarks/igraph_coloring.c create mode 100644 examples/benchmarks/igraph_maximal_cliques.c create mode 100644 examples/benchmarks/igraph_random_walk.c create mode 100644 examples/benchmarks/igraph_transitivity.c create mode 100644 examples/simple/2wheap.c create mode 100644 examples/simple/LINKS.NET create mode 100644 examples/simple/VF2-compat.c create mode 100644 examples/simple/adjlist.c create mode 100644 examples/simple/ak-4102.max create mode 100644 examples/simple/assortativity.c create mode 100644 examples/simple/assortativity.out create mode 100644 examples/simple/bellman_ford.c create mode 100644 examples/simple/bellman_ford.out create mode 100644 examples/simple/biguint.c create mode 100644 examples/simple/biguint.out create mode 100644 examples/simple/biguint_betweenness.c create mode 100644 examples/simple/bipartite.net create mode 100644 examples/simple/blas.c create mode 100644 examples/simple/blas.out create mode 100644 examples/simple/bug-1033045.c create mode 100644 examples/simple/bug-1033045.out create mode 100644 examples/simple/bug-1149658.c create mode 100644 examples/simple/cattr_bool_bug.c create mode 100644 examples/simple/cattr_bool_bug.graphml create mode 100644 examples/simple/cattributes.c create mode 100644 examples/simple/cattributes.out create mode 100644 examples/simple/cattributes2.c create mode 100644 examples/simple/cattributes2.out create mode 100644 examples/simple/cattributes3.c create mode 100644 examples/simple/cattributes3.out create mode 100644 examples/simple/cattributes4.c create mode 100644 examples/simple/cattributes4.out create mode 100644 examples/simple/cattributes5.c create mode 100644 examples/simple/cattributes5.out create mode 100644 examples/simple/celegansneural.gml create mode 100644 examples/simple/centralization.c create mode 100644 examples/simple/cohesive_blocks.c create mode 100644 examples/simple/cohesive_blocks.out create mode 100644 examples/simple/d_indheap.c create mode 100644 examples/simple/d_indheap.out create mode 100644 examples/simple/dijkstra.c create mode 100644 examples/simple/dijkstra.out create mode 100644 examples/simple/dominator_tree.c create mode 100644 examples/simple/dominator_tree.out create mode 100644 examples/simple/dot.c create mode 100644 examples/simple/dot.out create mode 100644 examples/simple/dqueue.c create mode 100644 examples/simple/dqueue.out create mode 100644 examples/simple/edgelist1.dl create mode 100644 examples/simple/edgelist2.dl create mode 100644 examples/simple/edgelist3.dl create mode 100644 examples/simple/edgelist4.dl create mode 100644 examples/simple/edgelist5.dl create mode 100644 examples/simple/edgelist6.dl create mode 100644 examples/simple/edgelist7.dl create mode 100644 examples/simple/eigenvector_centrality.c create mode 100644 examples/simple/eigenvector_centrality.out create mode 100644 examples/simple/even_tarjan.c create mode 100644 examples/simple/flow.c create mode 100644 examples/simple/flow2.c create mode 100644 examples/simple/flow2.out create mode 100644 examples/simple/foreign.c create mode 100644 examples/simple/foreign.out create mode 100644 examples/simple/fullmatrix1.dl create mode 100644 examples/simple/fullmatrix2.dl create mode 100644 examples/simple/fullmatrix3.dl create mode 100644 examples/simple/fullmatrix4.dl create mode 100644 examples/simple/gml.c create mode 100644 examples/simple/gml.out create mode 100644 examples/simple/graphml-default-attrs.xml create mode 100644 examples/simple/graphml-hsa05010.xml create mode 100644 examples/simple/graphml-lenient.xml create mode 100755 examples/simple/graphml-malformed.xml create mode 100644 examples/simple/graphml-namespace.xml create mode 100644 examples/simple/graphml.c create mode 100644 examples/simple/graphml.out create mode 100644 examples/simple/heap.c create mode 100644 examples/simple/igraph_add_edges.c create mode 100644 examples/simple/igraph_add_edges.out create mode 100644 examples/simple/igraph_add_vertices.c create mode 100644 examples/simple/igraph_adjacency.c create mode 100644 examples/simple/igraph_adjacency_spectral_embedding.c create mode 100644 examples/simple/igraph_adjacency_spectral_embedding.out create mode 100644 examples/simple/igraph_all_st_cuts.c create mode 100644 examples/simple/igraph_all_st_cuts.out create mode 100644 examples/simple/igraph_all_st_mincuts.c create mode 100644 examples/simple/igraph_all_st_mincuts.out create mode 100644 examples/simple/igraph_are_connected.c create mode 100644 examples/simple/igraph_arpack_rnsolve.c create mode 100644 examples/simple/igraph_arpack_rnsolve.out create mode 100644 examples/simple/igraph_array.c create mode 100644 examples/simple/igraph_array.out create mode 100644 examples/simple/igraph_atlas.c create mode 100644 examples/simple/igraph_atlas.out create mode 100644 examples/simple/igraph_average_path_length.c create mode 100644 examples/simple/igraph_barabasi_game.c create mode 100644 examples/simple/igraph_barabasi_game2.c create mode 100644 examples/simple/igraph_betweenness.c create mode 100644 examples/simple/igraph_bfs.c create mode 100644 examples/simple/igraph_bfs.out create mode 100644 examples/simple/igraph_bfs2.c create mode 100644 examples/simple/igraph_bfs2.out create mode 100644 examples/simple/igraph_biconnected_components.c create mode 100644 examples/simple/igraph_biconnected_components.out create mode 100644 examples/simple/igraph_bipartite_create.c create mode 100644 examples/simple/igraph_bipartite_create.out create mode 100644 examples/simple/igraph_bipartite_projection.c create mode 100644 examples/simple/igraph_bridges.c create mode 100644 examples/simple/igraph_bridges.out create mode 100644 examples/simple/igraph_cliques.c create mode 100644 examples/simple/igraph_cliques.out create mode 100644 examples/simple/igraph_cocitation.c create mode 100644 examples/simple/igraph_cocitation.out create mode 100644 examples/simple/igraph_coloring.c create mode 100644 examples/simple/igraph_community_edge_betweenness.c create mode 100644 examples/simple/igraph_community_edge_betweenness.out create mode 100644 examples/simple/igraph_community_fastgreedy.c create mode 100644 examples/simple/igraph_community_fastgreedy.out create mode 100644 examples/simple/igraph_community_infomap.c create mode 100644 examples/simple/igraph_community_infomap.out create mode 100644 examples/simple/igraph_community_label_propagation.c create mode 100644 examples/simple/igraph_community_leading_eigenvector.c create mode 100644 examples/simple/igraph_community_leading_eigenvector.out create mode 100644 examples/simple/igraph_community_leading_eigenvector2.c create mode 100644 examples/simple/igraph_community_leading_eigenvector2.out create mode 100644 examples/simple/igraph_community_leiden.c create mode 100644 examples/simple/igraph_community_multilevel.c create mode 100644 examples/simple/igraph_community_multilevel.out create mode 100644 examples/simple/igraph_community_optimal_modularity.c create mode 100644 examples/simple/igraph_complementer.c create mode 100644 examples/simple/igraph_complementer.out create mode 100644 examples/simple/igraph_complex.c create mode 100644 examples/simple/igraph_compose.c create mode 100644 examples/simple/igraph_compose.out create mode 100644 examples/simple/igraph_convergence_degree.c create mode 100644 examples/simple/igraph_convergence_degree.out create mode 100644 examples/simple/igraph_convex_hull.c create mode 100644 examples/simple/igraph_convex_hull.out create mode 100644 examples/simple/igraph_copy.c create mode 100644 examples/simple/igraph_correlated_game.c create mode 100644 examples/simple/igraph_create.c create mode 100644 examples/simple/igraph_decompose.c create mode 100644 examples/simple/igraph_decompose.out create mode 100644 examples/simple/igraph_degree.c create mode 100644 examples/simple/igraph_degree.out create mode 100644 examples/simple/igraph_degree_sequence_game.c create mode 100644 examples/simple/igraph_degree_sequence_game.out create mode 100644 examples/simple/igraph_delete_edges.c create mode 100644 examples/simple/igraph_delete_vertices.c create mode 100644 examples/simple/igraph_density.c create mode 100644 examples/simple/igraph_density.out create mode 100644 examples/simple/igraph_deterministic_optimal_imitation.c create mode 100644 examples/simple/igraph_diameter.c create mode 100644 examples/simple/igraph_diameter.out create mode 100644 examples/simple/igraph_difference.c create mode 100644 examples/simple/igraph_difference.out create mode 100644 examples/simple/igraph_disjoint_union.c create mode 100644 examples/simple/igraph_disjoint_union.out create mode 100644 examples/simple/igraph_eccentricity.c create mode 100644 examples/simple/igraph_eccentricity.out create mode 100644 examples/simple/igraph_edge_betweenness.c create mode 100644 examples/simple/igraph_edge_betweenness.out create mode 100644 examples/simple/igraph_eigen_matrix.c create mode 100644 examples/simple/igraph_eigen_matrix.out create mode 100644 examples/simple/igraph_eigen_matrix2.c create mode 100644 examples/simple/igraph_eigen_matrix2.out create mode 100644 examples/simple/igraph_eigen_matrix3.c create mode 100644 examples/simple/igraph_eigen_matrix3.out create mode 100644 examples/simple/igraph_eigen_matrix4.c create mode 100644 examples/simple/igraph_eigen_matrix4.out create mode 100644 examples/simple/igraph_eigen_matrix_symmetric.c create mode 100644 examples/simple/igraph_eigen_matrix_symmetric.out create mode 100644 examples/simple/igraph_eigen_matrix_symmetric_arpack.c create mode 100644 examples/simple/igraph_eigen_matrix_symmetric_arpack.out create mode 100644 examples/simple/igraph_empty.c create mode 100644 examples/simple/igraph_erdos_renyi_game.c create mode 100644 examples/simple/igraph_es_adj.c create mode 100644 examples/simple/igraph_es_adj.out create mode 100644 examples/simple/igraph_es_fromto.c create mode 100644 examples/simple/igraph_es_fromto.out create mode 100644 examples/simple/igraph_es_pairs.c create mode 100644 examples/simple/igraph_es_path.c create mode 100644 examples/simple/igraph_feedback_arc_set.c create mode 100644 examples/simple/igraph_feedback_arc_set.out create mode 100644 examples/simple/igraph_feedback_arc_set_ip.c create mode 100644 examples/simple/igraph_feedback_arc_set_ip.out create mode 100644 examples/simple/igraph_fisher_yates_shuffle.c create mode 100644 examples/simple/igraph_from_prufer.c create mode 100644 examples/simple/igraph_from_prufer.out create mode 100644 examples/simple/igraph_full.c create mode 100644 examples/simple/igraph_get_all_shortest_paths_dijkstra.c create mode 100644 examples/simple/igraph_get_all_shortest_paths_dijkstra.out create mode 100644 examples/simple/igraph_get_all_simple_paths.c create mode 100644 examples/simple/igraph_get_all_simple_paths.out create mode 100644 examples/simple/igraph_get_eid.c create mode 100644 examples/simple/igraph_get_eid.out create mode 100644 examples/simple/igraph_get_eids.c create mode 100644 examples/simple/igraph_get_eids.out create mode 100644 examples/simple/igraph_get_shortest_paths.c create mode 100644 examples/simple/igraph_get_shortest_paths.out create mode 100644 examples/simple/igraph_get_shortest_paths2.c create mode 100644 examples/simple/igraph_get_shortest_paths2.out create mode 100644 examples/simple/igraph_get_shortest_paths_dijkstra.c create mode 100644 examples/simple/igraph_get_shortest_paths_dijkstra.out create mode 100644 examples/simple/igraph_girth.c create mode 100644 examples/simple/igraph_gomory_hu_tree.c create mode 100644 examples/simple/igraph_grg_game.c create mode 100644 examples/simple/igraph_growing_random_game.c create mode 100644 examples/simple/igraph_has_multiple.c create mode 100644 examples/simple/igraph_hashtable.c create mode 100644 examples/simple/igraph_hashtable.out create mode 100644 examples/simple/igraph_hrg.c create mode 100644 examples/simple/igraph_hrg2.c create mode 100644 examples/simple/igraph_hrg2.out create mode 100644 examples/simple/igraph_hrg3.c create mode 100644 examples/simple/igraph_hrg3.out create mode 100644 examples/simple/igraph_i_cutheap.c create mode 100644 examples/simple/igraph_i_cutheap.out create mode 100644 examples/simple/igraph_i_layout_sphere.c create mode 100644 examples/simple/igraph_independent_sets.c create mode 100644 examples/simple/igraph_independent_sets.out create mode 100644 examples/simple/igraph_induced_subgraph_map.c create mode 100644 examples/simple/igraph_intersection.c create mode 100644 examples/simple/igraph_intersection.out create mode 100644 examples/simple/igraph_intersection2.c create mode 100644 examples/simple/igraph_intersection2.out create mode 100644 examples/simple/igraph_is_degree_sequence.c create mode 100644 examples/simple/igraph_is_directed.c create mode 100644 examples/simple/igraph_is_loop.c create mode 100644 examples/simple/igraph_is_loop.out create mode 100644 examples/simple/igraph_is_minimal_separator.c create mode 100644 examples/simple/igraph_is_multiple.c create mode 100644 examples/simple/igraph_is_multiple.out create mode 100644 examples/simple/igraph_is_separator.c create mode 100644 examples/simple/igraph_is_tree.c create mode 100644 examples/simple/igraph_isomorphic_bliss.c create mode 100644 examples/simple/igraph_isomorphic_vf2.c create mode 100644 examples/simple/igraph_k_regular_game.c create mode 100644 examples/simple/igraph_k_regular_game.out create mode 100644 examples/simple/igraph_knn.c create mode 100644 examples/simple/igraph_lapack_dgeev.c create mode 100644 examples/simple/igraph_lapack_dgeevx.c create mode 100644 examples/simple/igraph_lapack_dgehrd.c create mode 100644 examples/simple/igraph_lapack_dgehrd.out create mode 100644 examples/simple/igraph_lapack_dgesv.c create mode 100644 examples/simple/igraph_lapack_dgesv.out create mode 100644 examples/simple/igraph_lapack_dsyevr.c create mode 100644 examples/simple/igraph_laplacian.c create mode 100644 examples/simple/igraph_laplacian.out create mode 100644 examples/simple/igraph_lattice.c create mode 100644 examples/simple/igraph_layout_davidson_harel.c create mode 100644 examples/simple/igraph_layout_grid.c create mode 100644 examples/simple/igraph_layout_grid.out create mode 100644 examples/simple/igraph_layout_lgl.c create mode 100644 examples/simple/igraph_layout_mds.c create mode 100644 examples/simple/igraph_layout_mds.out create mode 100644 examples/simple/igraph_layout_merge.c create mode 100644 examples/simple/igraph_layout_merge2.c create mode 100644 examples/simple/igraph_layout_merge2.out create mode 100644 examples/simple/igraph_layout_merge3.c create mode 100644 examples/simple/igraph_layout_reingold_tilford.c create mode 100644 examples/simple/igraph_layout_reingold_tilford.in create mode 100644 examples/simple/igraph_layout_sugiyama.c create mode 100644 examples/simple/igraph_layout_sugiyama.out create mode 100644 examples/simple/igraph_lcf.c create mode 100644 examples/simple/igraph_local_transitivity.c create mode 100644 examples/simple/igraph_marked_queue.c create mode 100644 examples/simple/igraph_maximal_cliques.c create mode 100644 examples/simple/igraph_maximal_cliques.out create mode 100644 examples/simple/igraph_maximal_cliques2.c create mode 100644 examples/simple/igraph_maximal_cliques2.out create mode 100644 examples/simple/igraph_maximal_cliques3.c create mode 100644 examples/simple/igraph_maximal_cliques3.out create mode 100644 examples/simple/igraph_maximal_cliques4.c create mode 100644 examples/simple/igraph_maximal_cliques4.out create mode 100644 examples/simple/igraph_maximum_bipartite_matching.c create mode 100644 examples/simple/igraph_mincut.c create mode 100644 examples/simple/igraph_mincut.out create mode 100644 examples/simple/igraph_minimal_separators.c create mode 100644 examples/simple/igraph_minimum_size_separators.c create mode 100644 examples/simple/igraph_minimum_size_separators.out create mode 100644 examples/simple/igraph_minimum_spanning_tree.c create mode 100644 examples/simple/igraph_minimum_spanning_tree.out create mode 100644 examples/simple/igraph_moran_process.c create mode 100644 examples/simple/igraph_motifs_randesu.c create mode 100644 examples/simple/igraph_motifs_randesu.out create mode 100644 examples/simple/igraph_neighbors.c create mode 100644 examples/simple/igraph_neighbors.out create mode 100644 examples/simple/igraph_pagerank.c create mode 100644 examples/simple/igraph_pagerank.out create mode 100644 examples/simple/igraph_power_law_fit.c create mode 100644 examples/simple/igraph_power_law_fit.out create mode 100644 examples/simple/igraph_preference_game.c create mode 100644 examples/simple/igraph_psumtree.c create mode 100644 examples/simple/igraph_qsort.c create mode 100644 examples/simple/igraph_qsort.out create mode 100644 examples/simple/igraph_qsort_r.c create mode 100644 examples/simple/igraph_qsort_r.out create mode 100644 examples/simple/igraph_radius.c create mode 100644 examples/simple/igraph_random_sample.c create mode 100644 examples/simple/igraph_random_walk.c create mode 100644 examples/simple/igraph_read_graph_dl.c create mode 100644 examples/simple/igraph_read_graph_dl.out create mode 100644 examples/simple/igraph_read_graph_graphdb.c create mode 100644 examples/simple/igraph_read_graph_graphdb.out create mode 100644 examples/simple/igraph_read_graph_lgl-1.lgl create mode 100644 examples/simple/igraph_read_graph_lgl-2.lgl create mode 100644 examples/simple/igraph_read_graph_lgl-3.lgl create mode 100644 examples/simple/igraph_read_graph_lgl.c create mode 100644 examples/simple/igraph_read_graph_lgl.out create mode 100644 examples/simple/igraph_realize_degree_sequence.c create mode 100644 examples/simple/igraph_realize_degree_sequence.out create mode 100644 examples/simple/igraph_reciprocity.c create mode 100644 examples/simple/igraph_rewire.c create mode 100644 examples/simple/igraph_ring.c create mode 100644 examples/simple/igraph_rng_get_exp.c create mode 100644 examples/simple/igraph_rng_get_exp.out create mode 100644 examples/simple/igraph_roulette_wheel_imitation.c create mode 100644 examples/simple/igraph_scg_grouping.c create mode 100644 examples/simple/igraph_scg_grouping.out create mode 100644 examples/simple/igraph_scg_grouping2.c create mode 100644 examples/simple/igraph_scg_grouping2.out create mode 100644 examples/simple/igraph_scg_grouping3.c create mode 100644 examples/simple/igraph_scg_grouping3.out create mode 100644 examples/simple/igraph_scg_grouping4.c create mode 100644 examples/simple/igraph_scg_grouping4.out create mode 100644 examples/simple/igraph_scg_semiprojectors.c create mode 100644 examples/simple/igraph_scg_semiprojectors.out create mode 100644 examples/simple/igraph_scg_semiprojectors2.c create mode 100644 examples/simple/igraph_scg_semiprojectors2.out create mode 100644 examples/simple/igraph_scg_semiprojectors3.c create mode 100644 examples/simple/igraph_scg_semiprojectors3.out create mode 100644 examples/simple/igraph_set.c create mode 100644 examples/simple/igraph_set.out create mode 100644 examples/simple/igraph_similarity.c create mode 100644 examples/simple/igraph_similarity.out create mode 100644 examples/simple/igraph_simplify.c create mode 100644 examples/simple/igraph_simplify.out create mode 100644 examples/simple/igraph_small.c create mode 100644 examples/simple/igraph_small.out create mode 100644 examples/simple/igraph_sparsemat.c create mode 100644 examples/simple/igraph_sparsemat.out create mode 100644 examples/simple/igraph_sparsemat2.c create mode 100644 examples/simple/igraph_sparsemat2.out create mode 100644 examples/simple/igraph_sparsemat3.c create mode 100644 examples/simple/igraph_sparsemat3.out create mode 100644 examples/simple/igraph_sparsemat4.c create mode 100644 examples/simple/igraph_sparsemat4.out create mode 100644 examples/simple/igraph_sparsemat5.c create mode 100644 examples/simple/igraph_sparsemat5.out create mode 100644 examples/simple/igraph_sparsemat6.c create mode 100644 examples/simple/igraph_sparsemat7.c create mode 100644 examples/simple/igraph_sparsemat8.c create mode 100644 examples/simple/igraph_sparsemat9.c create mode 100644 examples/simple/igraph_sparsemat_is_symmetric.c create mode 100644 examples/simple/igraph_sparsemat_minmax.c create mode 100644 examples/simple/igraph_sparsemat_minmax.out create mode 100644 examples/simple/igraph_sparsemat_which_minmax.c create mode 100644 examples/simple/igraph_sparsemat_which_minmax.out create mode 100644 examples/simple/igraph_star.c create mode 100644 examples/simple/igraph_stochastic_imitation.c create mode 100644 examples/simple/igraph_strvector.c create mode 100644 examples/simple/igraph_strvector.out create mode 100644 examples/simple/igraph_subisomorphic_lad.c create mode 100644 examples/simple/igraph_subisomorphic_lad.out create mode 100644 examples/simple/igraph_to_prufer.c create mode 100644 examples/simple/igraph_to_undirected.c create mode 100644 examples/simple/igraph_to_undirected.out create mode 100644 examples/simple/igraph_topological_sorting.c create mode 100644 examples/simple/igraph_topological_sorting.out create mode 100644 examples/simple/igraph_transitive_closure_dag.c create mode 100644 examples/simple/igraph_transitive_closure_dag.out create mode 100644 examples/simple/igraph_transitivity.c create mode 100644 examples/simple/igraph_tree.c create mode 100644 examples/simple/igraph_tree.out create mode 100644 examples/simple/igraph_trie.c create mode 100644 examples/simple/igraph_trie.out create mode 100644 examples/simple/igraph_union.c create mode 100644 examples/simple/igraph_union.out create mode 100644 examples/simple/igraph_version.c create mode 100644 examples/simple/igraph_vs_nonadj.c create mode 100644 examples/simple/igraph_vs_nonadj.out create mode 100644 examples/simple/igraph_vs_seq.c create mode 100644 examples/simple/igraph_vs_seq.out create mode 100644 examples/simple/igraph_vs_vector.c create mode 100644 examples/simple/igraph_vs_vector.out create mode 100644 examples/simple/igraph_weighted_adjacency.c create mode 100644 examples/simple/igraph_weighted_adjacency.out create mode 100644 examples/simple/igraph_weighted_cliques.c create mode 100644 examples/simple/igraph_weighted_cliques.out create mode 100644 examples/simple/igraph_write_graph_leda.c create mode 100644 examples/simple/igraph_write_graph_leda.out create mode 100644 examples/simple/igraph_write_graph_lgl.c create mode 100644 examples/simple/igraph_write_graph_pajek.c create mode 100644 examples/simple/igraph_write_graph_pajek.out create mode 100644 examples/simple/indheap.c create mode 100644 examples/simple/input.dl create mode 100644 examples/simple/iso_b03_m1000.A00 create mode 100644 examples/simple/isomorphism_test.c create mode 100644 examples/simple/isomorphism_test.out create mode 100644 examples/simple/karate.gml create mode 100644 examples/simple/levc-stress.c create mode 100644 examples/simple/lineendings.c create mode 100644 examples/simple/lineendings.out create mode 100644 examples/simple/matrix.c create mode 100644 examples/simple/matrix.out create mode 100644 examples/simple/matrix2.c create mode 100644 examples/simple/matrix2.out create mode 100644 examples/simple/matrix3.c create mode 100644 examples/simple/mt.c create mode 100644 examples/simple/nodelist1.dl create mode 100644 examples/simple/nodelist2.dl create mode 100644 examples/simple/pajek.c create mode 100644 examples/simple/pajek1.net create mode 100644 examples/simple/pajek2.c create mode 100644 examples/simple/pajek2.net create mode 100644 examples/simple/pajek2.out create mode 100644 examples/simple/pajek3.net create mode 100644 examples/simple/pajek4.net create mode 100644 examples/simple/pajek5.net create mode 100644 examples/simple/pajek6.net create mode 100644 examples/simple/pajek_bip.net create mode 100644 examples/simple/pajek_bip2.net create mode 100644 examples/simple/pajek_bipartite.c create mode 100644 examples/simple/pajek_bipartite.out create mode 100644 examples/simple/pajek_bipartite2.c create mode 100644 examples/simple/pajek_bipartite2.out create mode 100644 examples/simple/pajek_signed.c create mode 100644 examples/simple/pajek_signed.net create mode 100644 examples/simple/pajek_signed.out create mode 100644 examples/simple/random_seed.c create mode 100644 examples/simple/scg.c create mode 100644 examples/simple/scg.out create mode 100644 examples/simple/scg2.c create mode 100644 examples/simple/scg2.out create mode 100644 examples/simple/scg3.c create mode 100644 examples/simple/scg3.out create mode 100644 examples/simple/single_target_shortest_path.c create mode 100644 examples/simple/single_target_shortest_path.out create mode 100644 examples/simple/spinglass.c create mode 100644 examples/simple/spmatrix.c create mode 100644 examples/simple/spmatrix.out create mode 100644 examples/simple/stack.c create mode 100644 examples/simple/test.gxl create mode 100644 examples/simple/tls1.c create mode 100644 examples/simple/tls2.c create mode 100644 examples/simple/tls2.out create mode 100644 examples/simple/topology.c create mode 100644 examples/simple/topology.out create mode 100644 examples/simple/triad_census.c create mode 100644 examples/simple/triad_census.out create mode 100644 examples/simple/vector.c create mode 100644 examples/simple/vector.out create mode 100644 examples/simple/vector2.c create mode 100644 examples/simple/vector2.out create mode 100644 examples/simple/vector3.c create mode 100644 examples/simple/vector_ptr.c create mode 100644 examples/simple/walktrap.c create mode 100644 examples/simple/walktrap.out create mode 100644 examples/simple/watts_strogatz_game.c create mode 100644 examples/simple/wikti_en_V_syn.elist create mode 100644 examples/tests/cattr_bool_bug2.c create mode 100644 examples/tests/cattr_bool_bug2.graphml create mode 100644 examples/tests/cattr_bool_bug2.out create mode 100644 examples/tests/igraph_closeness.c create mode 100644 examples/tests/igraph_closeness.out create mode 100644 examples/tests/igraph_community_fluid_communities.c create mode 100644 examples/tests/igraph_community_fluid_communities.out create mode 100644 examples/tests/igraph_community_label_propagation.c create mode 100644 examples/tests/igraph_community_label_propagation.out create mode 100644 examples/tests/igraph_community_leiden.c create mode 100644 examples/tests/igraph_community_leiden.out create mode 100644 examples/tests/igraph_decompose_strong.c create mode 100644 examples/tests/igraph_decompose_strong.out create mode 100644 examples/tests/igraph_layout_reingold_tilford_extended.c create mode 100644 examples/tests/igraph_layout_reingold_tilford_extended.in create mode 100644 examples/tests/maximal_cliques_callback.c create mode 100644 examples/tests/maximal_cliques_hist.c create mode 100644 examples/tests/maximal_cliques_hist.out create mode 100644 examples/tests/rng_reproducibility.c create mode 100644 examples/tests/rng_reproducibility.out create mode 100644 examples/tests/simplify_and_colorize.c create mode 100644 examples/tests/simplify_and_colorize.out create mode 100644 examples/tests/test_utilities.inc create mode 100644 examples/tests/tree.c create mode 100644 examples/tests/tree.out create mode 100644 igraph.pc.in create mode 100644 igraph_Info.plist.in create mode 100644 include/igraph.h create mode 100644 include/igraph_adjlist.h create mode 100644 include/igraph_arpack.h create mode 100644 include/igraph_array.h create mode 100644 include/igraph_array_pmt.h create mode 100644 include/igraph_attributes.h create mode 100644 include/igraph_bipartite.h create mode 100644 include/igraph_blas.h create mode 100644 include/igraph_centrality.h create mode 100644 include/igraph_cliques.h create mode 100644 include/igraph_cocitation.h create mode 100644 include/igraph_cohesive_blocks.h create mode 100644 include/igraph_coloring.h create mode 100644 include/igraph_community.h create mode 100644 include/igraph_complex.h create mode 100644 include/igraph_components.h create mode 100644 include/igraph_constants.h create mode 100644 include/igraph_constructors.h create mode 100644 include/igraph_conversion.h create mode 100644 include/igraph_datatype.h create mode 100644 include/igraph_decls.h create mode 100644 include/igraph_dqueue.h create mode 100644 include/igraph_dqueue_pmt.h create mode 100644 include/igraph_eigen.h create mode 100644 include/igraph_embedding.h create mode 100644 include/igraph_epidemics.h create mode 100644 include/igraph_error.h create mode 100644 include/igraph_flow.h create mode 100644 include/igraph_foreign.h create mode 100644 include/igraph_games.h create mode 100644 include/igraph_graphlets.h create mode 100644 include/igraph_heap.h create mode 100644 include/igraph_heap_pmt.h create mode 100644 include/igraph_hrg.h create mode 100644 include/igraph_interface.h create mode 100644 include/igraph_interrupt.h create mode 100644 include/igraph_iterators.h create mode 100644 include/igraph_lapack.h create mode 100644 include/igraph_layout.h create mode 100644 include/igraph_lsap.h create mode 100644 include/igraph_matching.h create mode 100644 include/igraph_matrix.h create mode 100644 include/igraph_matrix_pmt.h create mode 100644 include/igraph_memory.h create mode 100644 include/igraph_microscopic_update.h create mode 100644 include/igraph_mixing.h create mode 100644 include/igraph_motifs.h create mode 100644 include/igraph_neighborhood.h create mode 100644 include/igraph_nongraph.h create mode 100644 include/igraph_operators.h create mode 100644 include/igraph_paths.h create mode 100644 include/igraph_pmt.h create mode 100644 include/igraph_pmt_off.h create mode 100644 include/igraph_progress.h create mode 100644 include/igraph_psumtree.h create mode 100644 include/igraph_qsort.h create mode 100644 include/igraph_random.h create mode 100644 include/igraph_scan.h create mode 100644 include/igraph_scg.h create mode 100644 include/igraph_separators.h create mode 100644 include/igraph_sparsemat.h create mode 100644 include/igraph_spmatrix.h create mode 100644 include/igraph_stack.h create mode 100644 include/igraph_stack_pmt.h create mode 100644 include/igraph_statusbar.h create mode 100644 include/igraph_structural.h create mode 100644 include/igraph_strvector.h create mode 100644 include/igraph_threading.h.in create mode 100644 include/igraph_topology.h create mode 100644 include/igraph_transitivity.h create mode 100644 include/igraph_types.h create mode 100644 include/igraph_vector.h create mode 100644 include/igraph_vector_pmt.h create mode 100644 include/igraph_vector_ptr.h create mode 100644 include/igraph_vector_type.h create mode 100644 include/igraph_version.h.in create mode 100644 include/igraph_visitor.h create mode 100644 interfaces/R/README create mode 100644 interfaces/functions.def create mode 100644 interfaces/java/COPYING create mode 100644 interfaces/java/README create mode 100644 interfaces/java/build.xml create mode 100644 interfaces/java/etc/enums/Connectedness.properties create mode 100644 interfaces/java/etc/enums/NeighborMode.properties create mode 100644 interfaces/java/etc/enums/StarMode.properties create mode 100644 interfaces/java/src/c/config.h.in create mode 100644 interfaces/java/src/c/conversion.c create mode 100644 interfaces/java/src/c/conversion.h create mode 100644 interfaces/java/src/c/jni_utils.c create mode 100644 interfaces/java/src/c/jni_utils.h create mode 100644 interfaces/java/src/c/net_sf_igraph_Graph.c.in create mode 100644 interfaces/java/src/c/net_sf_igraph_VertexSet.c create mode 100644 interfaces/java/src/c/net_sf_igraph_VertexSet.h create mode 100644 interfaces/java/src/c/net_sf_igraph_enum.pmt create mode 100644 interfaces/java/src/c/net_sf_igraph_enum_impl.pmt create mode 100644 interfaces/java/src/c/net_sf_igraph_enums.c create mode 100644 interfaces/java/src/c/net_sf_igraph_enums.h create mode 100644 interfaces/java/src/c/net_sf_igraph_pmt.h create mode 100644 interfaces/java/src/java/CoreException.java create mode 100644 interfaces/java/src/java/GenericEnum.java.in create mode 100644 interfaces/java/src/java/Graph.java.in create mode 100644 interfaces/java/src/java/VertexSet.java create mode 100644 interfaces/java/src/java/util/LongRangeIterator.java create mode 100644 interfaces/java/src/tests/BasicGraphTests.java create mode 100644 interfaces/java/types-C.def create mode 100644 interfaces/java/types-Java.def create mode 100644 interfaces/shell/Makefile.am create mode 100644 interfaces/shell/interface.c.in create mode 100644 interfaces/shell/types.def create mode 100644 m4/ax_check_compile_flag.m4 create mode 100644 m4/libtool.m4 create mode 100755 optional/simpleraytracer/Color.cpp create mode 100755 optional/simpleraytracer/Color.h create mode 100755 optional/simpleraytracer/Light.cpp create mode 100755 optional/simpleraytracer/Light.h create mode 100755 optional/simpleraytracer/Point.cpp create mode 100755 optional/simpleraytracer/Point.h create mode 100644 optional/simpleraytracer/RIgraphRay.cpp create mode 100755 optional/simpleraytracer/Ray.cpp create mode 100755 optional/simpleraytracer/Ray.h create mode 100755 optional/simpleraytracer/RayTracer.cpp create mode 100755 optional/simpleraytracer/RayTracer.h create mode 100755 optional/simpleraytracer/RayVector.cpp create mode 100755 optional/simpleraytracer/RayVector.h create mode 100755 optional/simpleraytracer/Shape.cpp create mode 100755 optional/simpleraytracer/Shape.h create mode 100755 optional/simpleraytracer/Sphere.cpp create mode 100755 optional/simpleraytracer/Sphere.h create mode 100755 optional/simpleraytracer/Triangle.cpp create mode 100755 optional/simpleraytracer/Triangle.h create mode 100755 optional/simpleraytracer/unit_limiter.cpp create mode 100755 optional/simpleraytracer/unit_limiter.h create mode 100644 src/AMD/Include/amd.h create mode 100644 src/AMD/Include/amd_internal.h create mode 100644 src/AMD/Makefile create mode 100644 src/AMD/README.txt create mode 100644 src/AMD/Source/amd.f create mode 100644 src/AMD/Source/amd_1.c create mode 100644 src/AMD/Source/amd_2.c create mode 100644 src/AMD/Source/amd_aat.c create mode 100644 src/AMD/Source/amd_control.c create mode 100644 src/AMD/Source/amd_defaults.c create mode 100644 src/AMD/Source/amd_dump.c create mode 100644 src/AMD/Source/amd_global.c create mode 100644 src/AMD/Source/amd_info.c create mode 100644 src/AMD/Source/amd_order.c create mode 100644 src/AMD/Source/amd_post_tree.c create mode 100644 src/AMD/Source/amd_postorder.c create mode 100644 src/AMD/Source/amd_preprocess.c create mode 100644 src/AMD/Source/amd_valid.c create mode 100644 src/AMD/Source/amdbar.f create mode 100644 src/CHOLMOD.diff create mode 100644 src/CHOLMOD/Check/License.txt create mode 100644 src/CHOLMOD/Check/cholmod_check.c create mode 100644 src/CHOLMOD/Check/cholmod_read.c create mode 100644 src/CHOLMOD/Check/cholmod_write.c create mode 100644 src/CHOLMOD/Check/lesser.txt create mode 100644 src/CHOLMOD/Cholesky/License.txt create mode 100644 src/CHOLMOD/Cholesky/cholmod_amd.c create mode 100644 src/CHOLMOD/Cholesky/cholmod_analyze.c create mode 100644 src/CHOLMOD/Cholesky/cholmod_colamd.c create mode 100644 src/CHOLMOD/Cholesky/cholmod_etree.c create mode 100644 src/CHOLMOD/Cholesky/cholmod_factorize.c create mode 100644 src/CHOLMOD/Cholesky/cholmod_postorder.c create mode 100644 src/CHOLMOD/Cholesky/cholmod_rcond.c create mode 100644 src/CHOLMOD/Cholesky/cholmod_resymbol.c create mode 100644 src/CHOLMOD/Cholesky/cholmod_rowcolcounts.c create mode 100644 src/CHOLMOD/Cholesky/cholmod_rowfac.c create mode 100644 src/CHOLMOD/Cholesky/cholmod_solve.c create mode 100644 src/CHOLMOD/Cholesky/cholmod_spsolve.c create mode 100644 src/CHOLMOD/Cholesky/lesser.txt create mode 100644 src/CHOLMOD/Cholesky/t_cholmod_lsolve.c create mode 100644 src/CHOLMOD/Cholesky/t_cholmod_ltsolve.c create mode 100644 src/CHOLMOD/Cholesky/t_cholmod_rowfac.c create mode 100644 src/CHOLMOD/Cholesky/t_cholmod_solve.c create mode 100644 src/CHOLMOD/Core/License.txt create mode 100644 src/CHOLMOD/Core/cholmod_aat.c create mode 100644 src/CHOLMOD/Core/cholmod_add.c create mode 100644 src/CHOLMOD/Core/cholmod_band.c create mode 100644 src/CHOLMOD/Core/cholmod_change_factor.c create mode 100644 src/CHOLMOD/Core/cholmod_common.c create mode 100644 src/CHOLMOD/Core/cholmod_complex.c create mode 100644 src/CHOLMOD/Core/cholmod_copy.c create mode 100644 src/CHOLMOD/Core/cholmod_dense.c create mode 100644 src/CHOLMOD/Core/cholmod_error.c create mode 100644 src/CHOLMOD/Core/cholmod_factor.c create mode 100644 src/CHOLMOD/Core/cholmod_memory.c create mode 100644 src/CHOLMOD/Core/cholmod_sparse.c create mode 100644 src/CHOLMOD/Core/cholmod_transpose.c create mode 100644 src/CHOLMOD/Core/cholmod_triplet.c create mode 100644 src/CHOLMOD/Core/cholmod_version.c create mode 100644 src/CHOLMOD/Core/lesser.txt create mode 100644 src/CHOLMOD/Core/t_cholmod_change_factor.c create mode 100644 src/CHOLMOD/Core/t_cholmod_dense.c create mode 100644 src/CHOLMOD/Core/t_cholmod_transpose.c create mode 100644 src/CHOLMOD/Core/t_cholmod_triplet.c create mode 100644 src/CHOLMOD/Include/License.txt create mode 100644 src/CHOLMOD/Include/README.txt create mode 100644 src/CHOLMOD/Include/cholmod.h create mode 100644 src/CHOLMOD/Include/cholmod_blas.h create mode 100644 src/CHOLMOD/Include/cholmod_camd.h create mode 100644 src/CHOLMOD/Include/cholmod_check.h create mode 100644 src/CHOLMOD/Include/cholmod_cholesky.h create mode 100644 src/CHOLMOD/Include/cholmod_complexity.h create mode 100644 src/CHOLMOD/Include/cholmod_config.h create mode 100644 src/CHOLMOD/Include/cholmod_core.h create mode 100644 src/CHOLMOD/Include/cholmod_internal.h create mode 100644 src/CHOLMOD/Include/cholmod_io64.h create mode 100644 src/CHOLMOD/Include/cholmod_matrixops.h create mode 100644 src/CHOLMOD/Include/cholmod_modify.h create mode 100644 src/CHOLMOD/Include/cholmod_partition.h create mode 100644 src/CHOLMOD/Include/cholmod_supernodal.h create mode 100644 src/CHOLMOD/Include/cholmod_template.h create mode 100644 src/CHOLMOD/Makefile create mode 100644 src/CHOLMOD/MatrixOps/License.txt create mode 100644 src/CHOLMOD/MatrixOps/cholmod_drop.c create mode 100644 src/CHOLMOD/MatrixOps/cholmod_horzcat.c create mode 100644 src/CHOLMOD/MatrixOps/cholmod_norm.c create mode 100644 src/CHOLMOD/MatrixOps/cholmod_scale.c create mode 100644 src/CHOLMOD/MatrixOps/cholmod_sdmult.c create mode 100644 src/CHOLMOD/MatrixOps/cholmod_ssmult.c create mode 100644 src/CHOLMOD/MatrixOps/cholmod_submatrix.c create mode 100644 src/CHOLMOD/MatrixOps/cholmod_symmetry.c create mode 100644 src/CHOLMOD/MatrixOps/cholmod_vertcat.c create mode 100644 src/CHOLMOD/MatrixOps/gpl.txt create mode 100644 src/CHOLMOD/MatrixOps/t_cholmod_sdmult.c create mode 100644 src/CHOLMOD/Modify/License.txt create mode 100644 src/CHOLMOD/Modify/cholmod_rowadd.c create mode 100644 src/CHOLMOD/Modify/cholmod_rowdel.c create mode 100644 src/CHOLMOD/Modify/cholmod_updown.c create mode 100644 src/CHOLMOD/Modify/gpl.txt create mode 100644 src/CHOLMOD/Modify/t_cholmod_updown.c create mode 100644 src/CHOLMOD/Modify/t_cholmod_updown_numkr.c create mode 100644 src/CHOLMOD/Partition/License.txt create mode 100644 src/CHOLMOD/Partition/cholmod_camd.c create mode 100644 src/CHOLMOD/Partition/cholmod_ccolamd.c create mode 100644 src/CHOLMOD/Partition/cholmod_csymamd.c create mode 100644 src/CHOLMOD/Partition/cholmod_metis.c create mode 100644 src/CHOLMOD/Partition/cholmod_nesdis.c create mode 100644 src/CHOLMOD/Partition/lesser.txt create mode 100644 src/CHOLMOD/README.txt create mode 100644 src/CHOLMOD/Supernodal/License.txt create mode 100644 src/CHOLMOD/Supernodal/cholmod_super_numeric.c create mode 100644 src/CHOLMOD/Supernodal/cholmod_super_solve.c create mode 100644 src/CHOLMOD/Supernodal/cholmod_super_symbolic.c create mode 100644 src/CHOLMOD/Supernodal/gpl.txt create mode 100644 src/CHOLMOD/Supernodal/t_cholmod_gpu.c create mode 100644 src/CHOLMOD/Supernodal/t_cholmod_super_numeric.c create mode 100644 src/CHOLMOD/Supernodal/t_cholmod_super_solve.c create mode 100644 src/COLAMD/Include/colamd.h create mode 100644 src/COLAMD/Makefile create mode 100644 src/COLAMD/README.txt create mode 100644 src/COLAMD/Source/colamd.c create mode 100644 src/COLAMD/Source/colamd_global.c create mode 100644 src/DensityGrid.cpp create mode 100644 src/DensityGrid.h create mode 100644 src/DensityGrid_3d.cpp create mode 100644 src/DensityGrid_3d.h create mode 100644 src/Makefile.am create mode 100644 src/NetDataTypes.cpp create mode 100644 src/NetDataTypes.h create mode 100644 src/NetRoutines.cpp create mode 100644 src/NetRoutines.h create mode 100644 src/SuiteSparse_config/Makefile create mode 100644 src/SuiteSparse_config/README.txt create mode 100644 src/SuiteSparse_config/SuiteSparse_config.c create mode 100644 src/SuiteSparse_config/SuiteSparse_config.h create mode 100644 src/SuiteSparse_config/SuiteSparse_config.mk create mode 100644 src/SuiteSparse_config/SuiteSparse_config_GPU.mk create mode 100644 src/SuiteSparse_config/SuiteSparse_config_Mac.mk create mode 100644 src/adjlist.c create mode 100644 src/arpack.c create mode 100644 src/array.c create mode 100644 src/array.pmt create mode 100644 src/atlas-edges.h create mode 100644 src/atlas.c create mode 100644 src/attributes.c create mode 100644 src/basic_query.c create mode 100644 src/bfgs.c create mode 100644 src/bigint.c create mode 100644 src/bigint.h create mode 100644 src/bignum.c create mode 100644 src/bignum.h create mode 100644 src/bipartite.c create mode 100644 src/blas.c create mode 100644 src/bliss.cc create mode 100644 src/bliss/bignum.hh create mode 100644 src/bliss/bliss_heap.cc create mode 100644 src/bliss/defs.cc create mode 100644 src/bliss/defs.hh create mode 100644 src/bliss/graph.cc create mode 100644 src/bliss/graph.hh create mode 100644 src/bliss/heap.hh create mode 100644 src/bliss/igraph-changes.md create mode 100644 src/bliss/kqueue.hh create mode 100644 src/bliss/kstack.hh create mode 100644 src/bliss/orbit.cc create mode 100644 src/bliss/orbit.hh create mode 100644 src/bliss/partition.cc create mode 100644 src/bliss/partition.hh create mode 100644 src/bliss/uintseqhash.cc create mode 100644 src/bliss/uintseqhash.hh create mode 100644 src/bliss/utils.cc create mode 100644 src/bliss/utils.hh create mode 100644 src/cattributes.c create mode 100644 src/centrality.c create mode 100644 src/cliquer/README create mode 100644 src/cliquer/cliquer.c create mode 100644 src/cliquer/cliquer.h create mode 100644 src/cliquer/cliquer_graph.c create mode 100644 src/cliquer/cliquerconf.h create mode 100644 src/cliquer/graph.h create mode 100644 src/cliquer/misc.h create mode 100644 src/cliquer/reorder.c create mode 100644 src/cliquer/reorder.h create mode 100644 src/cliquer/set.h create mode 100644 src/cliques.c create mode 100644 src/clustertool.cpp create mode 100644 src/cocitation.c create mode 100644 src/cohesive_blocks.c create mode 100644 src/coloring.c create mode 100644 src/community.c create mode 100644 src/community_leiden.c create mode 100644 src/complex.c create mode 100644 src/components.c create mode 100644 src/conversion.c create mode 100644 src/cores.c create mode 100644 src/cs/UFconfig.h create mode 100644 src/cs/cs.h create mode 100644 src/cs/cs_add.c create mode 100644 src/cs/cs_amd.c create mode 100644 src/cs/cs_chol.c create mode 100644 src/cs/cs_cholsol.c create mode 100644 src/cs/cs_compress.c create mode 100644 src/cs/cs_counts.c create mode 100644 src/cs/cs_cumsum.c create mode 100644 src/cs/cs_dfs.c create mode 100644 src/cs/cs_dmperm.c create mode 100644 src/cs/cs_droptol.c create mode 100644 src/cs/cs_dropzeros.c create mode 100644 src/cs/cs_dupl.c create mode 100644 src/cs/cs_entry.c create mode 100644 src/cs/cs_ereach.c create mode 100644 src/cs/cs_etree.c create mode 100644 src/cs/cs_fkeep.c create mode 100644 src/cs/cs_gaxpy.c create mode 100644 src/cs/cs_happly.c create mode 100644 src/cs/cs_house.c create mode 100644 src/cs/cs_ipvec.c create mode 100644 src/cs/cs_leaf.c create mode 100644 src/cs/cs_load.c create mode 100644 src/cs/cs_lsolve.c create mode 100644 src/cs/cs_ltsolve.c create mode 100644 src/cs/cs_lu.c create mode 100644 src/cs/cs_lusol.c create mode 100644 src/cs/cs_malloc.c create mode 100644 src/cs/cs_maxtrans.c create mode 100644 src/cs/cs_multiply.c create mode 100644 src/cs/cs_norm.c create mode 100644 src/cs/cs_permute.c create mode 100644 src/cs/cs_pinv.c create mode 100644 src/cs/cs_post.c create mode 100644 src/cs/cs_print.c create mode 100644 src/cs/cs_pvec.c create mode 100644 src/cs/cs_qr.c create mode 100644 src/cs/cs_qrsol.c create mode 100644 src/cs/cs_randperm.c create mode 100644 src/cs/cs_reach.c create mode 100644 src/cs/cs_scatter.c create mode 100644 src/cs/cs_scc.c create mode 100644 src/cs/cs_schol.c create mode 100644 src/cs/cs_spsolve.c create mode 100644 src/cs/cs_sqr.c create mode 100644 src/cs/cs_symperm.c create mode 100644 src/cs/cs_tdfs.c create mode 100644 src/cs/cs_transpose.c create mode 100644 src/cs/cs_updown.c create mode 100644 src/cs/cs_usolve.c create mode 100644 src/cs/cs_util.c create mode 100644 src/cs/cs_utsolve.c create mode 100644 src/decomposition.c create mode 100644 src/degree_sequence.cpp create mode 100644 src/distances.c create mode 100644 src/dotproduct.c create mode 100644 src/dqueue.c create mode 100644 src/dqueue.pmt create mode 100644 src/drl_Node.h create mode 100644 src/drl_Node_3d.h create mode 100644 src/drl_graph.cpp create mode 100644 src/drl_graph.h create mode 100644 src/drl_graph_3d.cpp create mode 100644 src/drl_graph_3d.h create mode 100644 src/drl_layout.cpp create mode 100644 src/drl_layout.h create mode 100644 src/drl_layout_3d.cpp create mode 100644 src/drl_layout_3d.h create mode 100644 src/drl_parse.cpp create mode 100644 src/drl_parse.h create mode 100644 src/eigen.c create mode 100644 src/embedding.c create mode 100644 src/f2c.h create mode 100644 src/f2c_dummy.c create mode 100644 src/fast_community.c create mode 100644 src/feedback_arc_set.c create mode 100644 src/flow.c create mode 100644 src/foreign-dl-header.h create mode 100644 src/foreign-dl-lexer.l create mode 100644 src/foreign-dl-parser.y create mode 100644 src/foreign-gml-header.h create mode 100644 src/foreign-gml-lexer.l create mode 100644 src/foreign-gml-parser.y create mode 100644 src/foreign-graphml.c create mode 100644 src/foreign-lgl-header.h create mode 100644 src/foreign-lgl-lexer.l create mode 100644 src/foreign-lgl-parser.y create mode 100644 src/foreign-ncol-header.h create mode 100644 src/foreign-ncol-lexer.l create mode 100644 src/foreign-ncol-parser.y create mode 100644 src/foreign-pajek-header.h create mode 100644 src/foreign-pajek-lexer.l create mode 100644 src/foreign-pajek-parser.y create mode 100644 src/foreign.c create mode 100644 src/forestfire.c create mode 100644 src/fortran_intrinsics.c create mode 100644 src/games.c create mode 100644 src/gengraph_box_list.cpp create mode 100644 src/gengraph_box_list.h create mode 100644 src/gengraph_definitions.h create mode 100644 src/gengraph_degree_sequence.cpp create mode 100644 src/gengraph_degree_sequence.h create mode 100644 src/gengraph_graph_molloy_hash.cpp create mode 100644 src/gengraph_graph_molloy_hash.h create mode 100644 src/gengraph_graph_molloy_optimized.cpp create mode 100644 src/gengraph_graph_molloy_optimized.h create mode 100644 src/gengraph_hash.h create mode 100644 src/gengraph_header.h create mode 100644 src/gengraph_mr-connected.cpp create mode 100644 src/gengraph_powerlaw.cpp create mode 100644 src/gengraph_powerlaw.h create mode 100644 src/gengraph_qsort.h create mode 100644 src/gengraph_random.cpp create mode 100644 src/gengraph_random.h create mode 100644 src/gengraph_vertex_cover.h create mode 100644 src/glet.c create mode 100644 src/glpk_support.c create mode 100644 src/gml_tree.c create mode 100644 src/hacks.c create mode 100644 src/heap.c create mode 100644 src/heap.pmt create mode 100644 src/hrg_dendro.h create mode 100644 src/hrg_graph.h create mode 100644 src/hrg_graph_simp.h create mode 100644 src/hrg_rbtree.h create mode 100644 src/hrg_splittree_eq.h create mode 100644 src/igraph_arpack_internal.h create mode 100644 src/igraph_blas_internal.h create mode 100644 src/igraph_buckets.c create mode 100644 src/igraph_cliquer.c create mode 100644 src/igraph_cliquer.h create mode 100644 src/igraph_error.c create mode 100644 src/igraph_estack.c create mode 100644 src/igraph_estack.h create mode 100644 src/igraph_f2c.h create mode 100644 src/igraph_fixed_vectorlist.c create mode 100644 src/igraph_flow_internal.h create mode 100644 src/igraph_glpk_support.h create mode 100644 src/igraph_gml_tree.h create mode 100644 src/igraph_grid.c create mode 100644 src/igraph_hacks_internal.h create mode 100644 src/igraph_hashtable.c create mode 100644 src/igraph_heap.c create mode 100644 src/igraph_hrg.cc create mode 100644 src/igraph_hrg_types.cc create mode 100644 src/igraph_interrupt_internal.h create mode 100644 src/igraph_lapack_internal.h create mode 100644 src/igraph_marked_queue.c create mode 100644 src/igraph_marked_queue.h create mode 100644 src/igraph_math.h create mode 100644 src/igraph_psumtree.c create mode 100644 src/igraph_set.c create mode 100644 src/igraph_stack.c create mode 100644 src/igraph_strvector.c create mode 100644 src/igraph_trie.c create mode 100644 src/igraph_types_internal.h create mode 100644 src/infomap.cc create mode 100644 src/infomap_FlowGraph.cc create mode 100644 src/infomap_FlowGraph.h create mode 100644 src/infomap_Greedy.cc create mode 100644 src/infomap_Greedy.h create mode 100644 src/infomap_Node.cc create mode 100644 src/infomap_Node.h create mode 100644 src/interrupt.c create mode 100644 src/iterators.c create mode 100644 src/lad.c create mode 100644 src/lapack.c create mode 100644 src/layout.c create mode 100644 src/layout_dh.c create mode 100644 src/layout_fr.c create mode 100644 src/layout_gem.c create mode 100644 src/layout_kk.c create mode 100644 src/lsap.c create mode 100644 src/matching.c create mode 100644 src/math.c create mode 100644 src/matrix.c create mode 100644 src/matrix.pmt create mode 100644 src/maximal_cliques.c create mode 100644 src/maximal_cliques_template.h create mode 100644 src/memory.c create mode 100644 src/microscopic_update.c create mode 100644 src/mixing.c create mode 100644 src/motifs.c create mode 100644 src/operators.c create mode 100644 src/optimal_modularity.c create mode 100644 src/other.c create mode 100644 src/paths.c create mode 100644 src/plfit/arithmetic_ansi.h create mode 100644 src/plfit/arithmetic_sse_double.h create mode 100644 src/plfit/arithmetic_sse_float.h create mode 100644 src/plfit/error.c create mode 100644 src/plfit/error.h create mode 100644 src/plfit/gss.c create mode 100644 src/plfit/gss.h create mode 100644 src/plfit/hzeta.c create mode 100644 src/plfit/hzeta.h create mode 100644 src/plfit/kolmogorov.c create mode 100644 src/plfit/kolmogorov.h create mode 100644 src/plfit/lbfgs.c create mode 100644 src/plfit/lbfgs.h create mode 100644 src/plfit/mt.c create mode 100644 src/plfit/mt.h create mode 100644 src/plfit/options.c create mode 100644 src/plfit/platform.h create mode 100644 src/plfit/plfit.c create mode 100644 src/plfit/plfit.h create mode 100644 src/plfit/plfit.inc create mode 100644 src/plfit/rbinom.c create mode 100644 src/plfit/sampling.c create mode 100644 src/plfit/sampling.h create mode 100644 src/pottsmodel_2.cpp create mode 100644 src/pottsmodel_2.h create mode 100644 src/progress.c create mode 100644 src/prpack.cpp create mode 100644 src/prpack.h create mode 100644 src/prpack/prpack.h create mode 100644 src/prpack/prpack.inc create mode 100644 src/prpack/prpack_base_graph.cpp create mode 100644 src/prpack/prpack_base_graph.h create mode 100644 src/prpack/prpack_csc.h create mode 100644 src/prpack/prpack_csr.h create mode 100644 src/prpack/prpack_edge_list.h create mode 100644 src/prpack/prpack_igraph_graph.cpp create mode 100644 src/prpack/prpack_igraph_graph.h create mode 100644 src/prpack/prpack_preprocessed_ge_graph.cpp create mode 100644 src/prpack/prpack_preprocessed_ge_graph.h create mode 100644 src/prpack/prpack_preprocessed_graph.h create mode 100644 src/prpack/prpack_preprocessed_gs_graph.cpp create mode 100644 src/prpack/prpack_preprocessed_gs_graph.h create mode 100644 src/prpack/prpack_preprocessed_scc_graph.cpp create mode 100644 src/prpack/prpack_preprocessed_scc_graph.h create mode 100644 src/prpack/prpack_preprocessed_schur_graph.cpp create mode 100644 src/prpack/prpack_preprocessed_schur_graph.h create mode 100644 src/prpack/prpack_result.cpp create mode 100644 src/prpack/prpack_result.h create mode 100644 src/prpack/prpack_solver.cpp create mode 100644 src/prpack/prpack_solver.h create mode 100644 src/prpack/prpack_utils.cpp create mode 100644 src/prpack/prpack_utils.h create mode 100644 src/pstdint.h create mode 100644 src/qsort.c create mode 100644 src/qsort_r.c create mode 100644 src/random.c create mode 100644 src/random_walk.c create mode 100644 src/sbm.c create mode 100644 src/scan.c create mode 100644 src/scg.c create mode 100644 src/scg_approximate_methods.c create mode 100644 src/scg_exact_scg.c create mode 100644 src/scg_headers.h create mode 100644 src/scg_kmeans.c create mode 100644 src/scg_optimal_method.c create mode 100644 src/scg_utils.c create mode 100644 src/separators.c create mode 100644 src/sir.c create mode 100644 src/spanning_trees.c create mode 100644 src/sparsemat.c create mode 100644 src/spectral_properties.c create mode 100644 src/spmatrix.c create mode 100644 src/st-cuts.c create mode 100644 src/stack.pmt create mode 100644 src/statusbar.c create mode 100644 src/structural_properties.c create mode 100644 src/structural_properties_internal.h create mode 100644 src/structure_generators.c create mode 100644 src/sugiyama.c create mode 100644 src/topology.c create mode 100644 src/triangles.c create mode 100644 src/triangles_template.h create mode 100644 src/triangles_template1.h create mode 100644 src/type_indexededgelist.c create mode 100644 src/types.c create mode 100644 src/vector.c create mode 100644 src/vector.pmt create mode 100644 src/vector_ptr.c create mode 100644 src/version.c create mode 100644 src/visitors.c create mode 100644 src/walktrap.cpp create mode 100644 src/walktrap_communities.cpp create mode 100644 src/walktrap_communities.h create mode 100644 src/walktrap_graph.cpp create mode 100644 src/walktrap_graph.h create mode 100644 src/walktrap_heap.cpp create mode 100644 src/walktrap_heap.h create mode 100644 src/zeroin.c create mode 100644 tests/Makefile.am create mode 100644 tests/arpack.at create mode 100644 tests/atlocal.in create mode 100644 tests/attributes.at create mode 100644 tests/basic.at create mode 100644 tests/bipartite.at create mode 100644 tests/centralization.at create mode 100644 tests/cliques.at create mode 100644 tests/coloring.at create mode 100644 tests/community.at create mode 100644 tests/components.at create mode 100644 tests/conversion.at create mode 100644 tests/eigen.at create mode 100644 tests/embedding.at create mode 100644 tests/flow.at create mode 100644 tests/foreign.at create mode 100644 tests/hrg.at create mode 100644 tests/iterators.at create mode 100644 tests/layout.at create mode 100644 tests/matching.at create mode 100644 tests/microscopic.at create mode 100644 tests/motifs.at create mode 100644 tests/mt.at create mode 100644 tests/operators.at create mode 100644 tests/other.at create mode 100644 tests/qsort.at create mode 100644 tests/random.at create mode 100644 tests/scg.at create mode 100644 tests/separators.at create mode 100644 tests/structural_properties.at create mode 100644 tests/structure_generators.at create mode 100644 tests/testsuite.at create mode 100644 tests/topology.at create mode 100644 tests/types.at create mode 100644 tests/version.at create mode 100644 tests/visitors.at create mode 100644 tools/NEXT_VERSION create mode 100644 tools/arpack-sed.txt create mode 100644 tools/autoconf/as-version.m4 create mode 100644 tools/autoconf/ax_tls.m4 create mode 100755 tools/bump_version.sh create mode 100755 tools/create-msvc-projectfile.py create mode 100644 tools/exclude.txt create mode 100755 tools/extract_body.sh create mode 100755 tools/getglpk.sh create mode 100755 tools/getversion.sh create mode 100755 tools/insert-banner.sh create mode 100755 tools/jekyll_header.sh create mode 100755 tools/lapack/CompletePolish create mode 100644 tools/lapack/Makefile create mode 100644 tools/lapack/comment.l create mode 100644 tools/lapack/delete.sed create mode 100644 tools/lapack/extra/len_trim.f create mode 100755 tools/lapack/getlapack.sh create mode 100644 tools/lapack/lapack.patch create mode 100644 tools/lapack/lenscrub.l create mode 100644 tools/lapack/mt.patch create mode 100644 tools/lapack/split.sed create mode 100755 tools/leakcheck create mode 100644 tools/leakcheck.supp create mode 100644 tools/ltmain.patch create mode 100755 tools/protect_braces.sh create mode 100755 tools/removeexamples.py create mode 100644 tools/seqdict/__init__.py create mode 100644 tools/seqdict/mdict.py create mode 100644 tools/seqdict/ndict.py create mode 100755 tools/stimulus.py create mode 100755 tools/test-icc-compiler.sh create mode 100644 tools/virtual/packer/OSX-10.8.4/template.json create mode 100755 tools/virtual/packer/floppy/Autounattend.xml create mode 100644 tools/virtual/packer/floppy/install-cygwin-sshd.bat create mode 100644 tools/virtual/packer/floppy/oracle-cert.cer create mode 100644 tools/virtual/packer/floppy/set-power-config.bat create mode 100644 tools/virtual/packer/http/preseed.cfg create mode 100644 tools/virtual/packer/scripts/apt.sh create mode 100644 tools/virtual/packer/scripts/base.sh create mode 100644 tools/virtual/packer/scripts/build_time.sh create mode 100644 tools/virtual/packer/scripts/cleanup.sh create mode 100644 tools/virtual/packer/scripts/compilers.sh create mode 100644 tools/virtual/packer/scripts/git.sh create mode 100644 tools/virtual/packer/scripts/igraphdeps.sh create mode 100755 tools/virtual/packer/scripts/install-python.sh create mode 100644 tools/virtual/packer/scripts/installr-svn.sh create mode 100644 tools/virtual/packer/scripts/installr.sh create mode 100644 tools/virtual/packer/scripts/jenkins.sh create mode 100644 tools/virtual/packer/scripts/osx-vagrant.sh create mode 100644 tools/virtual/packer/scripts/otherdeb.sh create mode 100644 tools/virtual/packer/scripts/rdeps.sh create mode 100644 tools/virtual/packer/scripts/sudo.sh create mode 100644 tools/virtual/packer/scripts/support/kcpassword create mode 100644 tools/virtual/packer/scripts/vagrant.sh create mode 100644 tools/virtual/packer/scripts/vbox.sh create mode 100644 tools/virtual/packer/scripts/vmware.sh create mode 100644 tools/virtual/packer/scripts/win-change-home-dirs.sh create mode 100644 tools/virtual/packer/scripts/win-postinstall64.sh create mode 100644 tools/virtual/packer/scripts/xcode-cli-tools.sh create mode 100644 tools/virtual/packer/ubuntu-13.10-32/template.json create mode 100644 tools/virtual/packer/ubuntu-13.10-64/template.json create mode 100644 tools/virtual/packer/windows7/template.json create mode 100644 tools/virtual/vagrant/agents/org.igraph.tekton.build-c-0.5-main.plist create mode 100644 tools/virtual/vagrant/agents/org.igraph.tekton.build-c-develop.plist create mode 100644 tools/virtual/vagrant/agents/org.igraph.tekton.build-r-0.5-main.plist create mode 100644 tools/virtual/vagrant/agents/org.igraph.tekton.build-r-0.7-graphlets.plist create mode 100644 tools/virtual/vagrant/agents/org.igraph.tekton.build-r-develop.plist create mode 100644 tools/virtual/vagrant/agents/org.igraph.tekton.check-r-0.5-main.plist create mode 100644 tools/virtual/vagrant/agents/org.igraph.tekton.check-r-develop.plist create mode 100644 tools/virtual/vagrant/agents/org.igraph.tekton.check-rdevel-develop.plist create mode 100644 tools/virtual/vagrant/agents/org.igraph.tekton.update-r-svn.plist create mode 100755 tools/virtual/vagrant/load-all.sh create mode 100755 tools/virtual/vagrant/provisioners/apt-get-install.sh create mode 100755 tools/virtual/vagrant/provisioners/apt-update.sh create mode 100755 tools/virtual/vagrant/provisioners/compilers.sh create mode 100755 tools/virtual/vagrant/provisioners/createdir.sh create mode 100755 tools/virtual/vagrant/provisioners/igraphdeps.sh create mode 100755 tools/virtual/vagrant/provisioners/install-service.sh create mode 100755 tools/virtual/vagrant/provisioners/installR-asan.sh create mode 100755 tools/virtual/vagrant/provisioners/installR-svn.sh create mode 100755 tools/virtual/vagrant/provisioners/installR.sh create mode 100755 tools/virtual/vagrant/provisioners/sshkey.sh create mode 100755 tools/virtual/vagrant/run-script.sh create mode 100755 tools/virtual/vagrant/scripts/build-c.sh create mode 100755 tools/virtual/vagrant/scripts/build-r.sh create mode 100755 tools/virtual/vagrant/scripts/check-r-asan.sh create mode 100755 tools/virtual/vagrant/scripts/check-r.sh create mode 100644 tools/virtual/vagrant/scripts/key/dummy create mode 100755 tools/virtual/vagrant/scripts/update-r-svn.sh create mode 100644 tools/virtual/vagrant/ubuntu-13.04-x86_64/Vagrantfile create mode 100644 tools/virtual/vagrant/ubuntu-13.04-x86_64/error/dummy create mode 100644 tools/virtual/vagrant/ubuntu-13.04-x86_64/output/dummy create mode 100755 tools/virtual/vagrant/unload-all.sh diff --git a/.astylerc b/.astylerc new file mode 100644 index 0000000..bf94194 --- /dev/null +++ b/.astylerc @@ -0,0 +1,37 @@ +# General Options: +# - Only display errors +# - Redirect stderr to stdout +# - Enforce linux lineendings +# - Preserve file modification date +# - Do not create file backups, everything should be VCSed anyway +--quiet +--errors-to-stdout +--lineend=linux +--preserve-date +--suffix=none + +# Style +--style=java + +# Use 4 spaces +--indent=spaces=4 +--convert-tabs + +# Paddings around operators, parentheses, and a header +--pad-oper +--pad-header + +# Continuation blocks should have no extra indentation +--min-conditional-indent=0 + +# Indent preprocessor blocks and defines +--indent-preproc-block +--indent-preproc-define + +# Add braces around single-line branches +--add-braces + +# Keep complex statement sequences on the same line; they are that way for +# a reason +--keep-one-line-statements + diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e7257b8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{c,cc,cpp,h,hh,hpp,pmt}] +indent_style = space +indent_size = 4 + +[Makefile] +indent_style = tab diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..25cadf1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,58 @@ + +language: c + +addons: + apt: + packages: + - gfortran + - flex + - bison + - docbook2x + - xmlto + - texinfo + - source-highlight + - libxml2-utils + - xsltproc + - fop + homebrew: + packages: + - flex + - bison + +script: + - make check + +after_failure: + - find tests/testsuite.dir -name testsuite.log -exec cat \{\} \; + +jobs: + include: + - stage: test + os: linux + install: + - ./bootstrap.sh + - ./configure + - make + - stage: test + os: osx + install: + - ./bootstrap.sh + - ./configure --enable-asan + - make + - stage: documentation + language: shell + os: linux + install: + - ./bootstrap.sh + - ./configure + - cd doc + script: + - make html + - make pdf + +notifications: + email: + on_success: change + on_failure: always + +sudo: false diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 0000000..d354588 --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,13 @@ +{ + "title": "igraph", + "upload_type": "software", + "keywords": [ + "graph theory", + "network analysis" + ], + "creators": [ + { + "name": "The igraph Core Team" + } + ] +} diff --git a/ACKNOWLEDGEMENTS.md b/ACKNOWLEDGEMENTS.md new file mode 100644 index 0000000..e1801a8 --- /dev/null +++ b/ACKNOWLEDGEMENTS.md @@ -0,0 +1,272 @@ +# Acknowledgements + +[igraph](https://igraph.org) includes or links to code from the following sources. + + +#### [bliss 0.73](http://www.tcs.hut.fi/Software/bliss/) + +Copyright (c) 2003-2015 Tommi Junttila. + +License: [GNU LGPLv3][lgpl3] + + +#### [Cliquer 1.21](https://users.aalto.fi/~pat/cliquer.html) + +Copyright (C) 2002 Sampo Niskanen, Patric ÖstergÃ¥rd. + +License: [GNU GPLv2][gpl2] or later + + +#### [PRPACK](https://github.com/DavidKurokawa/prpack) + +Copyright (C) David Kurokawa, David Gleich, Chen Greif. + + +#### [gengraph](https://www-complexnetworks.lip6.fr/~latapy/FV/generation.html) + +Algorithm by Fabien Viger and Matthieu Latapy. + +Implementation Copyright (C) Fabien Viger. + +License: [GNU GPLv2][gpl2] or later + + +#### [Walktrap 0.2](https://www-complexnetworks.lip6.fr/~latapy/PP/walktrap.html) + +Algorithm by Pascal Pons and Matthieu Latapy. + +Implementation Copyright (C) 2004-2005 Pascal Pons. + +License: [GNU GPLv2][gpl2] or later + + +#### [plfit](https://github.com/ntamas/plfit) + +Copyright (C) 2010-2011 Tamás Nepusz. + +License: [GNU GPLv2][gpl2] + +#### DrL + +Copyright 2007 Sandia Corporation. Under the terms of Contract +DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains +certain rights in this software. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + * Neither the name of Sandia National Laboratories nor the names of +its contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#### [Hierarchical Random Graphs](http://tuvalu.santafe.edu/~aaronc/hierarchy/) + +Copyright (C) 2006-2008 Aaron Clauset. + +License: [GNU GPLv2][gpl2] or later + + +#### SCGlib (Spectral Coarse Graining) + +Copyright (C) 2008 David Morton de Lachapelle + +License: [GNU GPLv2][gpl2] or later + + +#### Spinglass community detection + +Copyright (C) 2004 by Joerg Reichardt. + +License: [GNU GPLv2][gpl2] or later + + +#### [LAD version 1](http://liris.cnrs.fr/csolnon/LAD.html) + +Copyright (C) Christine Solnon. + +License: [CeCILL-B license](https://cecill.info/licences.en.html) + + +#### [LAPACK 3.5.0](http://www.netlib.org/lapack/) + +Copyright (c) 1992-2011 The University of Tennessee and The University of Tennessee Research Foundation. All rights reserved. + +Copyright (c) 2000-2011 The University of California Berkeley. All rights reserved. + +Copyright (c) 2006-2012 The University of Colorado Denver. All rights reserved. + +License: [New BSD license](http://www.netlib.org/lapack/LICENSE.txt) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer listed + in this license in the documentation and/or other materials + provided with the distribution. + +- Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +The copyright holders provide no reassurances that the source code +provided does not infringe any patent, copyright, or any other +intellectual property rights of third parties. The copyright holders +disclaim any liability to any recipient for claims brought against +recipient by any third party for infringement of that parties +intellectual property rights. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +#### [f2c](http://www.netlib.org/f2c/) + +Copyright 1990 - 1997 by AT&T, Lucent Technologies and Bellcore. + +Permission to use, copy, modify, and distribute this software +and its documentation for any purpose and without fee is hereby +granted, provided that the above copyright notice appear in all +copies and that both that the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the names of AT&T, Bell Laboratories, +Lucent or Bellcore or any of their entities not be used in +advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +AT&T, Lucent and Bellcore disclaim all warranties with regard to +this software, including all implied warranties of +merchantability and fitness. In no event shall AT&T, Lucent or +Bellcore be liable for any special, indirect or consequential +damages or any damages whatsoever resulting from loss of use, +data or profits, whether in an action of contract, negligence or +other tortious action, arising out of or in connection with the +use or performance of this software. + + +#### [SuiteSparse](http://www.suitesparse.com) + + * AMD, Copyright (c) 2009-2012 by Timothy A. Davis (http://www.suitesparse.com), +Patrick R. Amestoy, and Iain S. Duff. All Rights Reserved. AMD is available +under alternate licences; contact T. Davis for details. + + License: [GNU LGPLv2.1][lgpl2] or later + + * CHOLMOD/Check Module. Copyright (C) 2005-2006, Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. + + License: [GNU LGPLv2.1][lgpl2] or later + + * CHOLMOD/Cholesky module, Copyright (C) 2005-2006, Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + + License: [GNU LGPLv2.1][lgpl2] or later + + * CHOLMOD/Core Module. Copyright (C) 2005-2006, Univ. of Florida. +Author: Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + + License: [GNU LGPLv2.1][lgpl2] or later + + * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, +Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + + License: [GNU GPLv2][gpl2] or later + + * CHOLMOD/Modify Module. +Copyright (C) 2005-2006, Timothy A. Davis and William W. Hager +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + + License: [GNU GPLv2][gpl2] or later + + * CHOLMOD/Partition Module. +Copyright (C) 2005-2006, Univ. of Florida. Author: Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + + License: [GNU LGPLv2.1][lgpl2] or later + + * CHOLMOD/Supernodal Module. +Copyright (C) 2005-2006, Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + + License: [GNU GPLv2][gpl2] or later + + * COLAMD, Copyright 1998-2012, Timothy A. Davis. http://www.suitesparse.com + + License: [GNU LGPLv2.1][lgpl2] or later + + * CXSPARSE: a Concise Sparse Matrix package - Extended. Copyright (c) 2006-2009, Timothy A. Davis. + + License: [GNU LGPLv2.1][lgpl2] or later + + +#### [GLPK (GNU Linear Programming Kit) Version 4.45](https://www.gnu.org/software/glpk/) + +Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, +2009, 2010 Andrew Makhorin, Department for Applied Informatics, +Moscow Aviation Institute, Moscow, Russia. All rights reserved. +E-mail: . + +License: [GNU GPLv3][gpl3] or later + + +#### [GMP (GNU Multiple Precision Arithmetic Library)](https://gmplib.org/) + +Copyright (C) Free Software Foundation, Inc. + +License: [GNU LGPLv3][lgpl3] or later; or [GNU GPLv2][gpl2] or later + + +#### [libxml2](http://xmlsoft.org/) + +Copyright (C) 1998-2012 Daniel Veillard. + +License: [MIT license][mit] + + + [mit]: https://opensource.org/licenses/mit-license.html + [gpl2]: https://www.gnu.org/licenses/gpl-2.0.html + [lgpl2]: https://www.gnu.org/licenses/lgpl-2.1.html + [gpl3]: https://www.gnu.org/licenses/gpl-3.0.html + [lgpl3]: https://www.gnu.org/licenses/lgpl-3.0.html diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..2326e78 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +Gabor Csardi +Tamas Nepusz +Szabolcs Horvat +Vincent Traag diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..42cb8a8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,107 @@ +# igraph C library changelog + +## [Unreleased] + +### Added + +### Changed + +### Fixed + +### Other + +## [0.8.1] - 2020-03-13 + +### Changed + + - Improved interruptability: `igraph_degree_sequence_game()` + - Improved argument checking: `igraph_forest_fire_game()` + - Updated the plfit library to version 0.8.1 + +### Fixed + + - `igraph_community_edge_betweenness()`: fix for graphs with no edges (PR #1312) + - `igraph_bridges()` now handles multigraphs correctly (PR #1335) + - `igraph_avg_nearest_neighbor_degree()`: fix for memory leak in weighted case (PR #1339) + - `igraph_community_leiden()`: fix crash bug (PR #1357) + +### Other + + - Included `ACKOWLEDGEMENTS.md` + - Documentation improvements + +## [0.8.0] - 2020-01-29 + +### Added + + * Trees + + - `igraph_to_prufer()` and `igraph_from_prufer()` convert labelled trees to/from Prüfer sequences + - `igraph_tree_game()` samples uniformly from the set of labelled trees + - `igraph_is_tree()` checks if a graph is a tree + - `igraph_random_spanning_tree()` picks a spanning tree of a graph uniformly at random + - `igraph_random_edge_walk()` returns the indices of edges traversed by a random walk; useful for multigraphs + + * Community detection + + - `igraph_community_fluid_communities()` detects communities based on interacting fluids + - `igraph_community_leiden()` detects communities with the Leiden method + + * Cliques + + - `igraph_maximal_cliques_hist()` counts maximal cliques of each size + - `igraph_maximal_cliques_callback()` calls a function for each maximal clique + - `igraph_clique_size_hist()` counts cliques of each size + - `igraph_cliques_callback()` calls a function for each clique + - `igraph_weighted_cliques()` finds weighted cliques in graphs with integer vertex weights + - `igraph_weighted_clique_number()` computes the weighted clique number + - `igraph_largest_weighted_cliques()` finds the largest weighted cliques + + * Graph generators + + - `igraph_hsbm_game()` for a hierarchical stochastic block model + - `igraph_hsbm_list_game()` for a more general hierarchical stochastic block model + - `igraph_correlated_game()` generates pairs of correlated random graphs by perturbing existing adjacency matrix + - `igraph_correlated_pair_game()` generates pairs of correlated random graphs + - `igraph_tree_game()` samples uniformly from the set of labelled trees + - `igraph_dot_product_game()` generates a random dot product graph + - `igraph_realize_degree_sequence()` creates a single graph with a given degree sequence (Havel-Hakimi algorithm) + + * Graph embeddings + + - `igraph_adjacency_spectral_embedding()` and `igraph_laplacian_spectral_embedding()` provide graph embedddings + - `igraph_dim_select()` provides dimensionality selection for singular values using profile likelihood + + * Isomorphism + + - `igraph_automorphism_group()` computes the generators of the automorphism group of a simple graph + - `igraph_simplify_and_colorize()` encodes edge and self-loop multiplicities into edge and vertex colors; use in conjunction with VF2 to test isomorphism of non-simple graphs + + * Other + + - `igraph_bridges()` finds edges whose removal would disconnect a graph + - `igraph_vertex_coloring_greedy()` computes a vertex coloring using a greedy algorithm + - `igraph_rewire_directed_edges()` randomly rewires only the starting points or only the endpoints of directed edges + - Various `igraph_local_scan_*` functions provide local counts and statistics of neighborhoods + - `igraph_sample_sphere_surface()` samples points uniformly from the surface of a sphere + - `igraph_sample_sphere_volume()` samples points uniformly from the volume of a sphere + - `igraph_sample_dirichlet()` samples points from a Dirichlet distribution + - `igraph_malloc()`, to be paired with the existing `igraph_free()` + +### Changed + + - `igraph_degree_sequence_game()`: new method added for uniform sampling: `IGRAPH_DEGSEQ_SIMPLE_NO_MULTIPLE_UNIFORM` + - `igraph_modularity_matrix()`: removed `membership` argument (PR #1194) + - `igraph_avg_nearest_neighbor_degree()`: added `mode` and `neighbor_degree_mode` arguments (PR #1214). + - `igraph_get_all_simple_paths()`: added `cutoff` argument (PR #1232). + - `igraph_unfold_tree()`: no longer preserves edge ordering of original graph + - `igraph_decompose()`: support strongly connected components + - `igraph_isomorphic_bliss()`, `igraph_canonical_permutation()`, `igraph_automorphisms()`: added additional arguments to support vertex colored graphs (PR #873) + - `igraph_extended_chordal_ring`: added argument to support direction (PR #1096), and fixed issue #1093. + +### Other + + - The [Bliss isomorphism library](http://www.tcs.hut.fi/Software/bliss/) was updated to version 0.73. This version adds support for vertex colored and directed graphs. + - igraph now uses the high-performance [Cliquer library](https://users.aalto.fi/~pat/cliquer.html) to find (non-maximal) cliques + - Provide proper support for Windows, using `__declspec(dllexport)` and `__declspec(dllimport)` for `DLL`s and static usage by using `#define IGRAPH_STATIC 1`. + - Provided integer versions of `dqueue` and `stack` data types. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b5edf4e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,235 @@ +# Contributing to this project + +Please take a moment to review this document in order to make the contribution +process easy and effective for everyone involved. + +Following these guidelines helps to communicate that you respect the time of +the developers managing and developing this open source project. In return, +they should reciprocate that respect in addressing your issue or assessing +patches and features. + + +## Using the issue tracker + +The issue tracker is the preferred channel for [bug reports](#bugs), +[features requests](#features) and [submitting pull +requests](#pull-requests), but please respect the following restrictions: + +* Please **do not** use the issue tracker for personal support requests (use + our [igraph support forum](https://igraph.discourse.group)). + +* Please **do not** derail or troll issues. Keep the discussion on topic and + respect the opinions of others. + +Please also take a look at our [tips on writing igraph code](#tips) before +getting your hands dirty. + + + +## Bug reports + +A bug is a _demonstrable problem_ that is caused by the code in the repository. +Good bug reports are extremely helpful - thank you! + +Guidelines for bug reports: + +1. **Make sure that the bug is in the C code of igraph and not in one of the + higher level interfaces** — if you are using igraph from R, Python + or Mathematica, consider submitting your issue in + [igraph/rigraph](https://github.com/igraph/rigraph/issues/new), + [igraph/python-igraph](https://github.com/igraph/python-igraph/issues/new) + or [szhorvat/IGraphM](https://github.com/szhorvat/IGraphM/issues/new) + instead. If you are unsure whether your issue is in the C layer, submit + a bug report in the repository of the higher level interface — + we will transfer the issue here if it indeed affects the C layer. + +2. **Use the GitHub issue search** — check if the issue has already been + reported. + +3. **Check if the issue has been fixed** — try to reproduce it using the + latest `master` or development branch in the repository. + +4. **Isolate the problem** — create a [short, self-contained, correct + example](http://sscce.org/). + +A good bug report shouldn't leave others needing to chase you up for more +information. Please try to be as detailed as possible in your report. What is +your environment? What steps will reproduce the issue? What would you expect to +be the outcome? All these details will help people to fix any potential bugs. + +Example: + +> Short and descriptive example bug report title +> +> A summary of the issue and the compiler/OS environment in which it occurs. If +> suitable, include the steps required to reproduce the bug. +> +> 1. This is the first step +> 2. This is the second step +> 3. Further steps, etc. +> +> `` - a link to the reduced test case +> +> Any other information you want to share that is relevant to the issue being +> reported. This might include the lines of code that you have identified as +> causing the bug, and potential solutions (and your opinions on their +> merits). + + + +## Feature requests + +Feature requests are welcome. But take a moment to find out whether your idea +fits with the scope and aims of the project. It's up to *you* to make a strong +case to convince the project's developers of the merits of this feature. Please +provide as much detail and context as possible. + + + +## Pull requests + +Good pull requests - patches, improvements, new features - are a fantastic +help. They should remain focused in scope and avoid containing unrelated +commits. + +**Please ask first** before embarking on any significant pull request (e.g. +implementing features, refactoring code, porting to a different language), +otherwise you risk spending a lot of time working on something that the +project's developers might not want to merge into the project. + +Please adhere to the coding conventions used throughout a project (indentation, +accurate comments, etc.) and any other requirements (such as test coverage). + +Follow this process if you'd like your work considered for inclusion in the +project: + +1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, + and configure the remotes: + + ```bash + # Clone your fork of the repo into the current directory + git clone https://github.com// + # Navigate to the newly cloned directory + cd + # Assign the original repo to a remote called "upstream" + git remote add upstream https://github.com// + ``` + +2. If you cloned a while ago, get the latest changes from upstream: + + ```bash + git checkout + git pull upstream + ``` + +3. Create a new topic branch (off the main project development branch) to + contain your feature, change, or fix: + + ```bash + git checkout -b + ``` + +4. Commit your changes in logical chunks. Please adhere to these [git commit + message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) + or your code is unlikely be merged into the main project. Use Git's + [interactive rebase](https://help.github.com/articles/interactive-rebase) + feature to tidy up your commits before making them public. + +5. We have a handy [checklist for new igraph + functions](https://github.com/igraph/igraph/wiki/Checklist-for-new-(and-old)-functions). + If you have added any new functions to igraph, please go through the + checklist to ensure that your functions play nicely with the rest of the + library. + +6. Locally merge (or rebase) the upstream development branch into your topic branch: + + ```bash + git pull [--rebase] upstream + ``` + +7. Push your topic branch up to your fork: + + ```bash + git push origin + ``` + +8. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) + with a clear title and description. + +**IMPORTANT**: By submitting a patch, you agree to allow the project owner to +license your work under the same license as that used by the project. + + + +## Writing igraph Code + +Some tips on writing igraph code. In general, look at how things are done, +and try to do them similarly. (Unless you think they are not done well, in +which case please tell us.) + +### Code Formatting + +We use UNIX line endings and we prefer four spaces for indentation. Otherwise +we are not too picky about code style; the general advice is that you should +look at the style of some recently committed bigger change around the parts +that you intend to change, and try to mimic that. The code style within igraph +is not stricly the same, but we want to keep it reasonably similar. + +### C vs. C++ + +Try to use C, unless you are updating already existing C++ code, or +you have other good reason for C++ (but then maybe ask us first). + +### Data types + +Please try to use igraph's data types for vectors, matrices, stacks, etc. +If they lack some functionality you need, please tell us. + +### Memory Allocation, Error Handling + +Please use igraph's memory allocation functions. Please also use the +`FINALLY` stack: `IGRAPH_FINALLY`, `IGRAPH_FINALLY_CLEAN`, etc. See examples +in the C code. + +### Random Numbers + +Please look at how random numbers are generated in any function in `src/games.c`. +Do the same. I.e. use `RNG_BEGIN`, `RNG_END`, and igraph's RNG calls. Do +not use the libc RNGs or other RNGs. + +### Documentation + +Please document your new functions. The C documentation is included in the C +source code. + +### Test Cases + +Unless you change something trivial, please consider adding test cases. +This is important! See the `tests`, `examples/simple` and `examples/tests` +directories for existing tests that you can use as examples. + +Whenever possible, make sure that the tests are determistic. If you are using +random numbers or a random graph generator in the tests, seed the random number +generator with a constant in the main function of the test to make sure that +every run generates the same set of random numbers. + +### Ask Us! + +In general, if you are not sure about something, please ask! You can +open an issue on GitHub, open a thread in our +[igraph support forum](https://igraph.discourse.group), or write to +[@ntamas](https://github.com/ntamas), [@vtraag](https://github.com/vtraag), +[@szhorvat](https://github.com/szhorvat) or +[@gaborcsardi](https://github.com/gaborcsardi). +We prefer the igraph support forum, because then others can learn from it +too. + +## Legal Stuff + +This is a pain to deal with, but we can't avoid it, unfortunately. + +So, igraph is licensed under the "General Public License (GPL) version 2, or +later". The igraph manual is licensed under the "GNU Free Documentation +License". By submitting a patch or PR, you agree to allow the project owner to +license your work under the same license as that used by the project. + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..412e6ad --- /dev/null +++ b/ChangeLog @@ -0,0 +1 @@ +See CHANGELOG.md for a list of changes between versions. \ No newline at end of file diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..ad57135 --- /dev/null +++ b/INSTALL @@ -0,0 +1 @@ +Instructions for installation are provided at https://igraph.org/c/ \ No newline at end of file diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..1ee6c24 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,90 @@ +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS=src tests +DOC_FILES = $(top_srcdir)/doc/Makefile.in \ + $(top_srcdir)/doc/igraph.3 + +pkgconfigdir = @libdir@/pkgconfig +pkgconfig_DATA = igraph.pc + +EXTRA_DIST = igraph.pc IGRAPH_VERSION $(top_srcdir)/include/* $(DOC_FILES) examples \ + $(wildcard $(top_srcdir)/optional/glpk/*.{inc,c,h}) \ + $(wildcard $(top_srcdir)/optional/glpk/{README,COPYING}) \ + $(wildcard $(top_srcdir)/optional/glpk/amd/*.{c,h}) \ + $(wildcard $(top_srcdir)/optional/glpk/amd/{README,COPYING}) \ + $(wildcard $(top_srcdir)/optional/glpk/colamd/*.{c,h}) \ + $(wildcard $(top_srcdir)/optional/glpk/colamd/{README,COPYING}) \ + tests/testsuite \ + ACKNOWLEDGEMENTS.md CHANGELOG.md + +tests/testsuite: + cd tests && make testsuite + +install-exec-hook: + if test -f $(top_builddir)/src/.libs/cygigraph-0.dll ; \ + then cp $(top_builddir)/src/.libs/cygigraph-0.dll \ + $(DESTDIR)$(libdir) ; fi + +install-info: + if test -f doc/igraph.info; then d="doc"; \ + else d=$(srcdir); fi; \ + $(INSTALL_DATA) $$d/igraph.info $(infodir); \ + if $(SHELL) -c 'install-info --version' \ + >/dev/null 2>&1; then \ + install-info --infodir=$(infodir) $$d/igraph.info; \ + else true; fi + +dist-hook: + rm -rf `find $(distdir)/examples -type d -name .arch-ids` + +MAINTAINERCLEANFILES = Makefile.in + +## to make sure make deb will generate Debian packages +.PHONY: framework msvc parsersources + +framework: all + rm -rf $(top_builddir)/igraph.framework + mkdir -p $(top_builddir)/igraph.framework/Versions/$(VERSION)/Headers + mkdir -p $(top_builddir)/igraph.framework/Versions/$(VERSION)/Resources + ln -s $(VERSION) $(top_builddir)/igraph.framework/Versions/Current + ln -s Versions/Current/Headers $(top_builddir)/igraph.framework/Headers + ln -s Versions/Current/Resources $(top_builddir)/igraph.framework/Resources + cp $(top_srcdir)/include/* $(top_builddir)/igraph.framework/Headers/ + if [ $(top_builddir) != $(top_srcdir) ]; then cp $(top_builddir)/include/* $(top_builddir)/igraph.framework/Headers/; fi + cp $(top_builddir)/src/.libs/libigraph.dylib $(top_builddir)/igraph.framework/Versions/Current/igraph + ln -s Versions/Current/igraph $(top_builddir)/igraph.framework/igraph + cp $(top_builddir)/igraph_Info.plist $(top_builddir)/igraph.framework/Versions/Current/Resources/Info.plist + +parsersources: + cd src; make parsersources + +## in case you are thinking about replacing the ugly find;echo hack below with +## xargs -r, don't. This is not supported on macOS (no -r option in xargs +## there) +msvc: parsersources + rm -rf $(top_builddir)/igraph-$(VERSION)-msvc + mkdir $(top_builddir)/igraph-$(VERSION)-msvc + mkdir $(top_builddir)/igraph-$(VERSION)-msvc/include + cp -r $(top_srcdir)/src $(top_builddir)/igraph-$(VERSION)-msvc/ + cp -r $(top_srcdir)/include/*.h $(top_builddir)/igraph-$(VERSION)-msvc/include + if [ "x$(top_srcdir)" != "x$(top_builddir)" ]; then cp -r $(top_builddir)/include $(top_builddir)/src $(top_builddir)/igraph-$(VERSION)-msvc; fi + cp -r $(top_srcdir)/msvc/include $(top_builddir)/igraph-$(VERSION)-msvc/winclude + cp -r $(top_srcdir)/msvc/src/f2c/arith.h $(top_builddir)/igraph-$(VERSION)-msvc/src/f2c + ( find $(top_builddir)/igraph-$(VERSION)-msvc/src -type d \( -name .deps -o -name .libs \); echo ':' ) | xargs rm -rf + ( find $(top_builddir)/igraph-$(VERSION)-msvc/src -type f \( -name '*.o' -o -name '*.lo' -o -name '*.la' \); echo ':' ) | xargs rm -f + ( find $(top_builddir)/igraph-$(VERSION)-msvc/src -type f \( -name 'Makefile*' -o -name '.dirstamp' \); echo ':' ) | xargs rm -f + rm -rf $(top_builddir)/igraph-$(VERSION)-msvc/src/f2c/arith + rm -rf $(top_builddir)/igraph-$(VERSION)-msvc/src/config.h + rm -rf $(top_builddir)/igraph-$(VERSION)-msvc/src/*~ + rm -rf $(top_builddir)/igraph-$(VERSION)-msvc/include/*~ + mkdir $(top_builddir)/igraph-$(VERSION)-msvc/winlib + $(top_srcdir)/tools/create-msvc-projectfile.py \ + $(top_builddir)/igraph-$(VERSION)-msvc \ + $(top_srcdir)/msvc/igraph.vcproj \ + $(top_builddir)/src/Makefile + cp $(top_srcdir)/msvc/igraph.sln $(top_builddir)/igraph-$(VERSION)-msvc + cp -r $(top_srcdir)/msvc/igraphtest $(top_builddir)/igraph-$(VERSION)-msvc/test + rm -rf igraph-$(VERSION)-msvc.zip + zip -q -r igraph-$(VERSION)-msvc.zip igraph-$(VERSION)-msvc + +CLEANFILES= diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..289fe7e --- /dev/null +++ b/NEWS @@ -0,0 +1,6 @@ +News about each release of igraph from version 0.8 onwards can be found in +CHANGELOG.md. + +Archived news items before version 0.7 are to be found in ONEWS -- these are +most likely of historical interest only. + diff --git a/ONEWS b/ONEWS new file mode 100644 index 0000000..f02fed5 --- /dev/null +++ b/ONEWS @@ -0,0 +1,1435 @@ + +igraph 0.6.5 +============ + +Released February 24, 2013 + +The version number is not a mistake, we jump to 0.6.5 from 0.6, +for technical reasons. + +R: new features and bug fixes +----------------------------- + +- Added a vertex shape API for defining new vertex shapes, and also + a couple of new vertex shapes. +- Added the get.data.frame() function, opposite of graph.data.frame(). +- Added bipartite support to the Pajek reader and writer, closes bug + \#1042298. +- `degree.sequence.game()` has a new method now: "simple_no_multiple". +- Added the is.degree.sequence() and is.graphical.degree.sequence() + functions. +- rewire() has a new method: "loops", that can create loop edges. +- Walktrap community detection now handles isolates. +- layout.mds() returns a layout matrix now. +- layout.mds() uses LAPACK instead of ARPACK. +- Handle the '~' character in write.graph and read.graph. Bug + \#1066986. +- Added k.regular.game(). +- Use vertex names to plot if no labels are specified in the function + call or as vetex attributes. Fixes issue \#1085431. +- power.law.fit() can now use a C implementation. + +- Fixed a bug in barabasi.game() when out.seq was an empty vector. +- Fixed a bug that made functions with a progress bar fail if called + from another package. +- Fixed a bug when creating graphs from a weighted integer adjacency + matrix via graph.adjacency(). Bug \#1019624. +- Fixed overflow issues in centralization calculations. +- Fixed a minimal.st.separators() bug, some vertex sets were incorrectly + reported as separators. Bug \#1033045. +- Fixed a bug that mishandled vertex colors in VF2 isomorphism + functions. Bug \#1032819. +- Pajek exporter now always quotes strings, thanks to Elena Tea Russo. +- Fixed a bug with handling small edge weights in shortest paths + calculation in shortest.paths() (Dijkstra's algorithm.) Thanks to + Martin J Reed. +- Weighted transitivity uses V(graph) as 'vids' if it is NULL. +- Fixed a bug when 'pie' vertices were drawn together with other + vertex shapes. +- Speed up printing graphs. +- Speed up attribute queries and other basic operations, by avoiding + copying of the graph. Bug \#1043616. +- Fixed a bug in the NCV setting for ARPACK functions. It cannot be + bigger than the matrix size. +- layout.merge()'s DLA mode has better defaults now. +- Fixed a bug in layout.mds() that resulted vertices on top of each + other. +- Fixed a bug in layout.spring(), it was not working properly. +- Fixed layout.svd(), which was completely defunct. +- Fixed a bug in layout.graphopt() that caused warnings and on + some platforms crashes. +- Fixed community.to.membership(). Bug \#1022850. +- Fixed a graph.incidence() crash if it was called with a non-matrix + argument. +- Fixed a get.shortest.paths bug, when output was set to "both". +- Motif finding functions return NA for isomorphism classes that are + not motifs (i.e. not connected). Fixes bug \#1050859. +- Fixed get.adjacency() when attr is given, and the attribute has some + complex type. Bug \#1025799. +- Fixed attribute name in graph.adjacency() for dense matrices. Bug + \#1066952. +- Fixed erratic behavior of alpha.centrality(). +- Fixed igraph indexing, when attr is given. Bug \#1073705. +- Fixed a bug when calculating the largest cliques of a directed + graph. Bug \#1073800. +- Fixed a bug in the maximal clique search, closes \#1074402. +- Warn for negative weights when calculating PageRank. +- Fixed dense, unweighted graph.adjacency when diag=FALSE. Closes + issue \#1077425. +- Fixed a bug in eccentricity() and radius(), the results were often + simply wrong. +- Fixed a bug in get.all.shortest.paths() when some edges had zero weight. +- graph.data.frame() is more careful when vertex names are numbers, to + avoid their scientific notation. Fixes issue \#1082221. +- Better check for NAs in vertex names. Fixes issue \#1087215 +- Fixed some potential crashes in the DrL layout generator. +- Fixed a bug in the Reingold-Tilford layout when the graph is + directed and mode != ALL. +- Eliminate gap between vertex and edge when plotting an edge without an arrow. + Fixes \#1118448. +- Fixed a bug in has.multiple() that resulted in false negatives for + some undirected graphs. +- Fixed a crash in weighted betweenness calculation. +- R plotting: fixed a bug that caused misplaced arrows at rectangle + vertex shapes. + +Python news and fixes +--------------------- + +- Added bipartite support to the Pajek reader and writer, closes bug + \#1042298. +- Graph.Degree_Sequence() has a new method now: "no_multiple". +- Added the is_degree_sequence() and is_graphical_degree_sequence() + functions. +- rewire() has a new mode: "loops", that can create loop edges. +- Walktrap community detection now handles isolates. +- Added Graph.K_Regular(). +- power_law_fit() now uses a C implementation. +- Added support for setting the frame (stroke) width of vertices using the + frame_width attribute or the vertex_frame_width keyword argument in plot() +- Improved Inkscape-friendly SVG output from Graph.write_svg(), thanks to drlog +- Better handling of named vertices in Graph.delete_vertices() +- Added experimental Gephi graph streaming support; see igraph.remote.gephi and + igraph.drawing.graph.GephiGraphStreamingDrawer +- Nicer __repr__ output for Flow and Cut instances +- Arrows are now placed correctly around diamond-shaped nodes on plots +- Added Graph.TupleList, a function that allows one to create graphs with + edge attributes quickly from a list of tuples. +- plot() now also supports .eps as an extension, not only .ps + +- Fixed overflow issues in centralization calculations. +- Fixed a bug that mishandled vertex colors in VF2 isomorphism + functions. Bug \#1032819. +- Pajek exporter now always quotes strings, thanks to Elena Tea Russo. +- Fixed a bug with handling small edge weights in shortest paths + calculation in Graph.shortest_paths() (Dijkstra's algorithm.) Thanks to + Martin J Reed. +- Fixed a bug in the NCV setting for ARPACK functions. It cannot be + bigger than the matrix size. +- Fixed a bug in Graph.layout_mds() that resulted vertices on top of each + other. +- Motif finding functions return nan for isomorphism classes that are + not motifs (i.e. not connected). Fixes bug \#1050859. +- Fixed a bug when calculating the largest cliques of a directed + graph. Bug \#1073800. +- Warn for negative weights when calculating PageRank. +- Fixed a bug in Graph.eccentricity() and Graph.radius(), the results were often + simply wrong. +- Fixed a bug in Graph.get.all.shortest.paths() when some edges had zero weight. +- Fixed some potential crashes in the DrL layout generator. +- Fixed a bug in the Reingold-Tilford layout when the graph is + directed and mode != ALL. +- Fixed a bug in Graph.layout_sugiyama() when the graph had no edges. +- Fixed a bug in Graph.community_label_propagation() when initial labels + contained -1 entries. Issue \#1105460. +- Repaired the DescartesCoordinateSystem class (which is not used too frequently + anyway) +- Fixed a bug that caused segfaults when an igraph Graph was used in a thread + forked from the main Python interpreter thread +- Fixed a bug that affected file handles created from Python strings in the + C layer +- Fixed a bug in has_multiple() that resulted in false negatives + for some undirected graphs. +- Fixed a crash in weighted betweenness calculation. + +C library news and changes +-------------------------- + +- Added bipartite support to the Pajek reader and writer, closes bug + \#1042298. +- igraph_layout_mds() uses LAPACK instead of ARPACK. +- igraph_degree_sequence_game has a new method: + IGRAPH_DEGSEQ_SIMPLE_NO_MULTIPLE. +- Added the igraph_is_degree_sequence() and + igraph_is_graphical_degree_sequence() functions. +- igraph_rewire() has a new method: IGRAPH_REWIRING_SIMPLE_LOOPS, + that can create loops. +- Walktrap community detection now handles isolates. +- Added igraph_k_regular_game(). +- Added igraph_power_law_fit. + +- Fixed a bug in igraph_barabasi_game when outseq was an empty vector. +- Fixed overflow issues in centralization calculations. +- Fixed an invalid return value of igraph_vector_ptr_pop_back. +- Fixed a igraph_all_minimal_st_separators() bug, some vertex sets + were incorrectly reported as separators. Bug \#1033045. +- Pajek exporter now always quotes strings, thanks to Elena Tea Russo. +- Fixed a bug with handling small edge weights in + igraph_shortest_paths_dijkstra(), thanks to Martin J Reed. +- Fixed a bug in the NCV setting for ARPACK functions. It cannot be + bigger than the matrix size. +- igraph_layout_merge_dla uses better default parameter values now. +- Fixed a bug in igraph_layout_mds() that resulted vertices on top of + each other. +- Attribute handler table is not thread-local any more. +- Motif finding functions return IGRAPH_NAN for isomorphism classes + that are not motifs (i.e. not connected). Fixes bug \#1050859. +- Fixed a bug when calculating the largest cliques of a directed + graph. Bug \#1073800. +- Fix a bug in degree_sequence_game(), in_seq can be an empty vector as + well instead of NULL, for an undirected graph. +- Fixed a bug in the maximal clique search, closes \#1074402. +- Warn for negative weights when calculating PageRank. +- Fixed a bug in igraph_eccentricity() (and also igraph_radius()), + the results were often simply wrong. +- Fixed a bug in igraph_get_all_shortest_paths_dijkstra() when edges + had zero weight. +- Fixed some potential crashes in the DrL layout generator. +- Fixed a bug in the Reingold-Tilford layout when the graph is + directed and mode != ALL. +- Fixed a bug in igraph_has_multiple() that resulted in false negatives + for some undirected graphs. +- Fixed a crash in weighted betweenness calculation. + +igraph 0.6 +========== + +Released June 11, 2012 + +See also the release notes at +http://igraph.sf.net/relnotes-0.6.html + +R: Major new features +--------------------- + +- Vertices and edges are numbered from 1 instead of 0. + Note that this makes most of the old R igraph code incompatible + with igraph 0.6. If you want to use your old code, please use + the igraph0 package. See more at http://igraph.sf.net/relnotes-0.6.html. +- The '\[' and '\[\[' operators can now be used on igraph graphs, + for '\[' the graph behaves as an adjacency matrix, for '[[' is + is treated as an adjacency list. It is also much simpler to + manipulate the graph structure, i.e. add/remove edges and vertices, + with some new operators. See more at ?graph.structure. +- In all functions that take a vector or list of vertices or edges, + vertex/edge names can be given instead of the numeric ids. +- New package 'igraphdata', contains a number of data sets that can + be used directly in igraph. +- Igraph now supports loading graphs from the Nexus online data + repository, see nexus.get(), nexus.info(), nexus.list() and + nexus.search(). +- All the community structure finding algorithm return a 'communities' + object now, which has a bunch of useful operations, see + ?communities for details. +- Vertex and edge attributes are handled much better now. They + are kept whenever possible, and can be combined via a flexible API. + See ?attribute.combination. +- R now prints igraph graphs to the screen in a more structured and + informative way. The output of summary() was also updated + accordingly. + +R: Other new features +--------------------- + +- It is possible to mark vertex groups on plots, via + shading. Communities and cohesive blocks are plotted using this by + default. +- Some igraph demos are now available, see a list via + 'demo(package="igraph")'. +- igraph now tries to select the optimal layout algorithm, when + plotting a graph. +- Added a simple console, using Tcl/Tk. It contains a text area + for status messages and also a status bar. See igraph.console(). +- Reimplemented igraph options support, see igraph.options() and + getIgraphOpt(). +- Igraph functions can now print status messages. + +R: New or updated functions +--------------------------- + +Community detection +------------------- +- The multi-level modularity optimization community structure detection + algorithm by Blondel et al. was added, see multilevel.community(). +- Distance between two community structures: compare.communities(). +- Community structure via exact modularity optimization, + optimal.community(). +- Hierarchical random graphs and community finding, porting the code + from Aaron Clauset. See hrg.game(), hrg.fit(), etc. +- Added the InfoMAP community finding method, thanks to Emmanuel + Navarro for the code. See infomap.community(). + +Shortest paths +-------------- +- Eccentricity (eccentricity()), and radius (radius()) calculations. +- Shortest path calculations with get.shortest.paths() can now + return the edges along the shortest paths. +- get.all.shortest.paths() now supports edge weights. + +Centrality +---------- +- Centralization scores for degree, closeness, betweenness and + eigenvector centrality. See centralization.scores(). +- Personalized Page-Rank scores, see page.rank(). +- Subgraph centrality, subgraph.centrality(). +- Authority (authority.score()) and hub (hub.score()) scores support + edge weights now. +- Support edge weights in betweenness and closeness calculations. +- bonpow(), Bonacich's power centrality and alpha.centrality(), + Alpha centrality calculations now use sparse matrices by default. +- Eigenvector centrality calculation, evcent() now works for + directed graphs. +- Betweenness calculation can now use arbitrarily large integers, + this is required for some lattice-like graphs to avoid overflow. + +Input/output and file formats +----------------------------- +- Support the DL file format in graph.read(). See + http://www.analytictech.com/networks/dataentry.htm. +- Support writing the LEDA file format in write.graph(). + +Plotting and layouts +-------------------- +- Star layout: layout.star(). +- Layout based on multidimensional scaling, layout.mds(). +- New layouts layout.grid() and layout.grid.3d(). +- Sugiyama layout algorithm for layered directed acyclic graphs, + layout.sugiyama(). + +Graph generators +---------------- +- New graph generators: static.fitness.game(), static.power.law.game(). +- barabasi.game() was rewritten and it supports three algorithms now, + the default algorithm does not generate multiple or loop edges. + The graph generation process can now start from a supplied graph. +- The Watts-Strogatz graph generator, igraph_watts_strogatz() can + now create graphs without loop edges. + +Others +------ +- Added the Spectral Coarse Graining algorithm, see scg(). +- The cohesive.blocks() function was rewritten in C, it is much faster + now. It has a nicer API, too. See demo("cohesive"). +- Added generic breadth-first and depth-first search implementations + with many callbacks, graph.bfs() and graph_dfs(). +- Support vertex and edge coloring in the VF2 (sub)graph isomorphism + functions (graph.isomorphic.vf2(), graph.count.isomorphisms.vf2(), + graph.get.isomorphisms.vf2(), graph.subisomorphic.vf2(), + graph.count.subisomorphisms.vf2(), graph.get.subisomorphisms.vf2()). +- Assortativity coefficient, assortativity(), assortativity.nominal() + and assortativity.degree(). +- Vertex operators that work by vertex names: + graph.intersection.by.name(), graph.union.by.name(), + graph.difference.by.name(). Thanks to Magnus Torfason for + contributing his code! +- Function to calculate a non-induced subraph: subgraph.edges(). +- More comprehensive maximum flow and minimum cut calculation, + see functions graph.maxflow(), graph.mincut(), stCuts(), stMincuts(). +- Check whether a directed graph is a DAG, is.dag(). +- has.multiple() to decide whether a graph has multiple edges. +- Added a function to calculate a diversity score for the vertices, + graph.diversity(). +- Graph Laplacian calculation (graph.laplacian()) supports edge + weights now. +- Biconnected component calculation, biconnected.components() + now returns the components themselves. +- bipartite.projection() calculates multiplicity of edges. +- Maximum cardinality search: maximum.cardinality.search() and + chordality test: is.chordal() +- Convex hull computation, convex.hull(). +- Contract vertices, contract.vertices(). + +New in the Python interface +--------------------------- + +TODO + +Major changes in the Python interface +------------------------------------- + +TODO + +New in the C layer +------------------ + +- Maximum cardinality search: igraph_maximum_cardinality_search() and + chordality test: igraph_is_chordal(). +- Support the DL file format, igraph_read_graph_dl(). See + http://www.analytictech.com/networks/dataentry.htm. +- Added generic breadth-first and depth-first search implementations + with many callbacks (igraph_bfs(), igraph_dfs()). +- Centralization scores for degree, closeness, betweenness and + eigenvector centrality, see igraph_centralization(). +- Added igraph_sparsemat_t, a type that implements sparse + matrices based on the CXSparse library by Tim Davis. + See http://www.cise.ufl.edu/research/sparse/CXSparse/. +- Personalized Page-Rank scores, igraph_personalized_pagerank() and + igraph_personalized_pagerank_vs(). +- Assortativity coefficient, igraph_assortativity(), + igraph_assortativity_nominal(), and igraph_assortativity_degree(). +- The multi-level modularity optimization community structure detection + algorithm by Blondel et al. was added, see igraph_community_multilevel(). +- Added the igraph_version() function. +- Star layout: igraph_layout_star(). +- Function to calculate a non-induced subraph: igraph_subgraph_edges(). +- Distance between two community structures: igraph_compare_communities(). +- Community structure via exact modularity optimization, + igraph_community_optimal_community(). +- More comprehensive maximum flow and minimum cut calculation, + see functions igraph_maxflow(), igraph_mincut(), + igraph_all_st_cuts(), igraph_all_st_mincuts(). +- Layout based on multidimensional scaling, igraph_layout_mds(). +- It is now possible to access the random number generator(s) via an + API. Multiple RNGs can be used, from external sources as well. + The default RNG is MT19937. +- Added igraph_get_all_shortest_paths_dijkstra, for calculating all + non-negatively weighted shortest paths. +- Check whether a directed graph is a DAG, igraph_is_dag(). +- Cohesive blocking, a'la Moody & White, igraph_cohesive_blocks(). +- Igraph functions can now print status messages, see igraph_status() + and related functions. +- Support writing the LEDA file format, igraph_write_graph_leda(). +- Contract vertices, igraph_contract_vertices(). +- The C reference manual has now a lot of example programs. +- Hierarchical random graphs and community finding, porting the code + from Aaron Clauset. See igraph_hrg_game(), igraph_hrg_fit(), etc. +- igraph_has_multiple() to decide whether a graph has multiple edges. +- New layouts igraph_layout_grid() and igraph_layout_grid_3d(). +- igraph_integer_t is really an integer now, it used to be a double. +- igraph_minimum_spanning_tree(), calls either the weighted or + the unweighted implementation. +- Eccentricity (igraph_eccentricity()), and radius (igraph_radius()) + calculations. +- Several game theory update rules, written by Minh Van Nguyen. See + igraph_deterministic_optimal_imitation(), + igraph_stochastic_imitation(), igraph_roulette_wheel_imitation(), + igraph_moran_process(), +- Sugiyama layout algorithm for layered directed acyclic graphs, + igraph_layout_sugiyama(). +- New graph generators: igraph_static_fitness_game(), + igraph_static_power_law_game(). +- Added the InfoMAP community finding method, thanks to Emmanuel + Navarro for the code. See igraph_community_infomap(). +- Added the Spectral Coarse Graining algorithm, see igraph_scg(). +- Added a function to calculate a diversity score for the vertices, + igraph_diversity(). + +Major changes in the C layer +---------------------------- + +- Authority (igraph_authority_score()) and hub (igraph_hub_score()) scores + support edge weights now. +- Graph Laplacian calculation (igraph_laplacian()) supports edge + weights now. +- Support edge weights in betweenness (igraph_betweenness()) and closeness + (igraph_closeness()) calculations. +- Support vertex and edge coloring in the VF2 graph isomorphism + algorithm (igraph_isomorphic_vf2(), igraph_count_isomorphisms_vf2(), + igraph_get_isomorphisms_vf2(), igraph_subisomorphic_vf2(), + igraph_count_subisomorphisms_vf2(), igraph_get_subisomorphisms_vf2()). +- Added print operations for the igraph_vector*_t, igraph_matrix*_t and + igraph_strvector_t types. +- Biconnected component calculation (igraph_biconnected_components()) + can now return the components themselves. +- Eigenvector centrality calculation, igraph_eigenvector_centrality() + now works for directed graphs. +- Shortest path calculations with get_shortest_paths() and + get_shortest_paths_dijkstra() can now return the edges along the paths. +- Betweenness calculation can now use arbitrarily large integers, + this is required for some lattice-like graphs to avoid overflow. +- igraph_bipartite_projection() calculates multiplicity of edges. +- igraph_barabasi_game() was rewritten and it supports three + algorithms now, the default algorithm does not generate multiple or + loop edges. +- The Watts-Strogatz graph generator, igraph_watts_strogatz() can + now create graphs without loop edges. +- igraph should be now thread-safe, on architectures that support + thread-local storage (Linux and Windows: yes, Mac OSX: no). + +We also fixed numerous bugs, too many to include them here, sorry. +You may look at our bug tracker at https://bugs.launchpad.net/igraph +to check whether a bug was fixed or not. Thanks for all the people +reporting bugs. Special thanks to Minh Van Nguyen for a lot of bug +reports, documentation fixes and contributed code! + +igraph 0.5.3 +============ + +Released November 22, 2009 + +Bugs corrected in the R interface +--------------------------------- +- Some small changes to make 'R CMD check' clean +- Fixed a bug in graph.incidence, the 'directed' and 'mode' arguments + were not handled correctly +- Betweenness and edge betweenness functions work for graphs with + many shortest paths now (up to the limit of long long int) +- When compiling the package, the configure script fails if there is + no C compiler available +- igraph.from.graphNEL creates the right number of loop edges now +- Fixed a bug in bipartite.projection() that caused occasional crashes + on some systems + +New in the Python interface +--------------------------- +- Added support for weighted diameter +- get_eid() considers edge directions by default from now on +- Fixed a memory leak in the attribute handler +- 'NaN' and 'inf' are treated correctly now + +Bugs corrected in the C layer +----------------------------- +- Betweenness and edge betweenness functions work for graphs with + many shortest paths now (up to the limit of long long int) +- The configure script fails if there is no C compiler available +- Fixed a bug in igraph_community_spinglass, when csize was a NULL + pointer, but membership was not +- Fixed a bug in igraph_bipartite_projection that caused occasional + crashes on some systems + +igraph 0.5.2 +============ + +Released April 10, 2009 + +See also the release notes at +http://igraph.sf.net/relnotes-0.5.2.html + +New in the R interface +---------------------- + +- Added progress bar support to beweenness() and + betweenness.estimate(), layout.drl() +- Speeded up betweenness estimation +- Speeded up are.connected() +- Johnson's shortest paths algorithm added +- shortest.paths() has now an 'algorithm' argument to choose from the + various implementations manually +- Always quote symbolic vertex names when printing graphs or edges +- Average nearest neighbor degree calculation, graph.knn() +- Weighted degree (also called strength) calculation, graph.strength() +- Some new functions to support bipartite graphs: graph.bipartite(), + is.bipartite(), get.indicence(), graph.incidence(), + bipartite.projection(), bipartite.projection.size() +- Support for plotting curved edges with plot.igraph() and tkplot() +- Added support for weighted graphs in alpha.centrality() +- Added the label propagation community detection algorithm by + Raghavan et al., label.propagation.community() +- cohesive.blocks() now has a 'cutsetHeuristic' argument to choose + between two cutset algorithms +- Added a function to "unfold" a tree, unfold.tree() +- New tkplot() arguments to change the drawing area +- Added a minimal GUI, invoke it with tkigraph() +- The DrL layout generator, layout.drl() has a three dimensional mode + now. + +Bugs corrected in the R interface +--------------------------------- + +- Fixed a bug in VF2 graph isomorphism functions +- Fixed a bug when a sparse adjacency matrix was requested in + get.adjacency() and the graph was named +- VL graph generator in degree.sequence.game() checks now that + the sum of the degrees is even +- Many fixes for supporting various compilers, e.g. GCC 4.4 and Sun's + C compiler +- Fixed memory leaks in graph.automorphisms(), Bellman-Ford + shortest.paths(), independent.vertex.sets() +- Fix a bug when a graph was imported from LGL and exported to NCOL + format (\#289596) +- cohesive.blocks() creates its temporary file in the session + temporary directory +- write.graph() and read.graph() now give error messages when unknown + arguments are given +- The GraphML reader checks the name of the attributes to avoid adding + a duplicate 'id' attribute +- It is possible to change the 'ncv' ARPACK parameter for + leading.eigenvector.community() +- Fixed a bug in path.length.hist(), 'unconnected' was wrong + for unconnected and undirected graphs +- Better handling of attribute assingment via iterators, this is now + also clarified in the manual +- Better error messages for unknown vertex shapes +- Make R package unload cleanly if unloadNamespace() is used +- Fixed a bug in plotting square shaped vertices (\#325244) +- Fixed a bug in graph.adjacency() when the matrix is a sparse matrix + of class "dgTMatrix" + +New in the Python interface +--------------------------- + +- Speeded up betweenness estimation +- Johnson's shortest paths algorithm added (selected automatically + by Graph.shortest_paths() if needed) +- Weighted degree (also called strength) calculation, Graph.strength() +- Some new methods to support bipartite graphs: Graph.Bipartite(), + Graph.is_bipartite(), Graph.get_indicence(), Graph.Incidence(), + Graph.bipartite_projection(), Graph.bipartite_projection_size() +- Added the label propagation community detection algorithm by + Raghavan et al., Graph.community_label_propagation() +- Added a function to "unfold" a tree, Graph.unfold_tree() +- setup.py script improvements +- Graph plotting now supports edge_arrow_size and edge_arrow_width +- Added Graph.Formula to create small graphs from a simple notation +- VertexSeq and EdgeSeq objects can now be indexed by slices + +New in the C layer +------------------ + +- Added progress bar support to igraph_betweenness() and + igraph_betweenness_estimate(), igraph_layout_drl() +- Speeded up igraph_betweenness_estimate(), igraph_get_eid(), + igraph_are_connected(), igraph_get_eids() +- Added igraph_get_eid2() +- Johnson's shortest path algorithm added: + igraph_shortest_paths_johnson() +- Average nearest neighbor degree calculation, + igraph_avg_nearest_neighbor_degree() +- Weighted degree (also called strength) calculation, + igraph_strength() +- Some functions to support bipartite graphs: igraph_full_bipartite(), + igraph_bipartite_projection(), igraph_create_bipartite(), + igraph_incidence(), igraph_get_incidence(), + igraph_bipartite_projection_size(), igraph_is_bipartite() +- Added the label propagation community detection algorithm by + Raghavan et al., igraph_community_label_propagation() +- Added an example that shows how to set the random number generator's + seed from C (examples/simple/random_seed.c) +- Added a function to "unfold" a tree, igraph_unfold_tree() +- C attribute handler updates: added functions to query many + vertices/edges at once +- Three dimensional DrL layout, igraph_layout_drl_3d() + +Bugs corrected in the C layer +----------------------------- + +- Fixed a bug in igraph_isomorphic_function_vf2(), affecting all VF2 + graph isomorphism functions +- VL graph generator in igraph_degree_sequence_game() checks now that + the sum of the degrees is even +- Many small corrections to make igraph compile with Microsoft Visual + Studio 2003, 2005 and 2008 +- Many fixes for supporting various compilers, e.g. GCC 4.4 and Sun's + C compiler +- Fix a bug when a graph was imported from LGL and exported to NCOL + format (\#289596) +- Fixed memory leaks in igraph_automorphisms(), + igraph_shortest_paths_bellman_ford(), + igraph_independent_vertex_sets() +- The GraphML reader checks the name of the attributes to avoid adding + a duplicate 'id' attribute +- It is possible to change the 'ncv' ARPACK parameter for + igraph_community_leading_eigenvector() +- Fixed a bug in igraph_path_length_hist(), 'unconnected' was wrong + for unconnected and undirected graphs. + +igraph 0.5.1 +============ + +Released July 14, 2008 + +See also the release notes at +http://igraph.sf.net/relnotes-0.5.1.html + +New in the R interface +---------------------- + +- A new layout generator called DrL. +- Uniform sampling of random connected undirected graphs with a + given degree sequence. +- Edge labels are plotted at 1/3 of the edge, this is better if + the graph has mutual edges. +- Initial and experimental vertex shape support in 'plot'. +- New function, 'graph.adjlist' creates igraph graphs from + adjacency lists. +- Conversion to/from graphNEL graphs, from the 'graph' R package. +- Fastgreedy community detection can utilize edge weights now, this + was missing from the R interface. +- The 'arrow.width' graphical parameter was added. +- graph.data.frame has a new argument 'vertices'. +- graph.adjacency and get.adjacency support sparse matrices, + the 'Matrix' package is required to use this functionality. +- graph.adjacency adds column/row names as 'name' attribute. +- Weighted shortest paths using Dijkstra's or the Belmann-Ford + algorithm. +- Shortest path functions return 'Inf' for unreachable vertices. +- New function 'is.mutual' to find mutual edges in a directed graph. +- Added inverse log-weighted similarity measure (a.k.a. Adamic/Adar + similarity). +- preference.game and asymmetric.preference.game were + rewritten, they are O(|V|+|E|) now, instead of O(|V|^2). +- Edge weight support in function 'get.shortest.paths', it uses + Dijkstra's algorithm. + +Bugs corrected in the R interface +--------------------------------- + +- A bug was corrected in write.pajek.bgraph. +- Several bugs were corrected in graph.adjacency. +- Pajek reader bug corrected, used to segfault if '\*Vertices' + was missing. +- Directedness is handled correctly when writing GML files. + (But note that 'correct' conflicts the standard here.) +- Corrected a bug when calculating weighted, directed PageRank on an + undirected graph. (Which does not make sense anyway.) +- Several bugs were fixed in the Reingold-Tilford layout to avoid + edge crossings. +- A bug was fixed in the GraphML reader, when the value of a graph + attribute was not specified. +- Fixed a bug in the graph isomorphism routine for small (3-4 vertices) + graphs. +- Corrected the random sampling implementation (igraph_random_sample), + now it always generates unique numbers. This affects the + Gnm Erdos-Renyi generator, it always generates simple graphs now. +- The basic igraph constructor (igraph_empty_attrs, all functions + are expected to call this internally) now checks whether the number + of vertices is finite. +- The LGL, NCOL and Pajek graph readers handle errors properly now. +- The non-symmetric ARPACK solver returns results in a consistent form + now. +- The fast greedy community detection routine now checks that the graph + is simple. +- The LGL and NCOL parsers were corrected to work with all + kinds of end-of-line encodings. +- Hub & authority score calculations initialize ARPACK parameters now. +- Fixed a bug in the Walktrap community detection routine, when applied + to unconnected graphs. +- Several small memory leaks were removed, and a big one from the Spinglass + community structure detection function + +New in the Python interface +--------------------------- + +- A new layout generator called DrL. +- Uniform sampling of random connected undirected graphs with a + given degree sequence. +- Methods parameters accepting igraph.IN, igraph.OUT and igraph.ALL + constants now also accept these as strings ("in", "out" and "all"). + Prefix matches also allowed as long as the prefix match is unique. +- Graph.shortest_paths() now supports edge weights (Dijkstra's and + Bellman-Ford algorithm implemented) +- Graph.get_shortest_paths() also supports edge weights + (only Dijkstra's algorithm yet) +- Added Graph.is_mutual() to find mutual edges in a directed graph. +- Added inverse log-weighted similarity measure (a.k.a. Adamic/Adar + similarity). +- preference.game and asymmetric.preference.game were + rewritten, they are O(|V|+|E|) now, instead of O(|V|^2). +- ARPACK options can now be modified from the Python interface + (thanks to Kurt Jacobson) +- Layout.to_radial() added -- now you can create a top-down tree + layout by the Reingold-Tilford algorithm and then turn it to a + radial tree layout +- Added Graph.write_pajek() to save graphs in Pajek format +- Some vertex and edge related methods can now also be accessed via + the methods of VertexSeq and EdgeSeq, restricted to the current + vertex/edge sequence of course +- Visualisations now support triangle shaped vertices +- Added Graph.mincut() +- Added Graph.Weighted_Adjacency() to create graphs from weighted + adjacency matrices +- Kamada-Kawai and Fruchterman-Reingold layouts now accept initial + vertex positions +- Graph.Preference() and Graph.Asymmetric_Preference() were + rewritten, they are O(|V|+|E|) now, instead of O(|V|^2). + +Bugs corrected in the Python interface +-------------------------------------- + +- Graph.constraint() now properly returns floats instead of integers + (thanks to Eytan Bakshy) +- Graphs given by adjacency matrices are now finally loaded and saved + properly +- Graph.Preference() now accepts floats in type distributions +- A small bug in Graph.community_edge_betweenness() corrected +- Some bugs in numeric attribute handling resolved +- VertexSeq and EdgeSeq objects can now be subsetted by lists and + tuples as well +- Fixed a bug when dealing with extremely small layout sizes +- Eigenvector centality now always return positive values +- Graph.authority_score() now really returns the authority scores + instead of the hub scores (blame copypasting) +- Pajek reader bug corrected, used to segfault if '\*Vertices' + was missing. +- Directedness is handled correctly when writing GML files. + (But note that 'correct' conflicts the standard here.) +- Corrected a bug when calculating weighted, directed PageRank on an + undirected graph. (Which does not make sense anyway.) +- Several bugs were fixed in the Reingold-Tilford layout to avoid + edge crossings. +- A bug was fixed in the GraphML reader, when the value of a graph + attribute was not specified. +- Fixed a bug in the graph isomorphism routine for small (3-4 vertices) + graphs. +- Corrected the random sampling implementation (igraph_random_sample), + now it always generates unique numbers. This affects the + Gnm Erdos-Renyi generator, it always generates simple graphs now. +- The LGL, NCOL and Pajek graph readers handle errors properly now. +- The non-symmetric ARPACK solver returns results in a consistent form + now. +- The fast greedy community detection routine now checks that the graph + is simple. +- The LGL and NCOL parsers were corrected to work with all + kinds of end-of-line encodings. +- Hub & authority score calculations initialize ARPACK parameters now. +- Fixed a bug in the Walktrap community detection routine, when applied + to unconnected graphs. +- Several small memory leaks were removed, and a big one from the Spinglass + community structure detection function + +New in the C layer +------------------ + +- A new layout generator called DrL. +- Uniform sampling of random connected undirected graphs with a + given degree sequence. +- Some stochastic test results are ignored (for spinglass community + detection, some Erdos-Renyi generator tests) +- Weighted shortest paths, Dijkstra's algorithm. +- The unweigthed shortest path routine returns 'Inf' for unreachable + vertices. +- New function, igraph_adjlist can create igraph graphs from + adjacency lists. +- New function, igraph_weighted_adjacency can create weighted graphs + from weight matrices. +- New function, igraph_is_mutual to search for mutual edges. +- Added inverse log-weighted similarity measure (a.k.a. Adamic/Adar + similarity). +- igraph_preference_game and igraph_asymmetric_preference_game were + rewritten, they are O(|V|+|E|) now, instead of O(|V|^2). +- The Bellman-Ford shortest path algorithm was added. +- Added weighted variant of igraph_get_shortest_paths, based on + Dijkstra's algorithm. +- Several small memory leaks were removed, and a big one from the Spinglass + community structure detection function + +Bugs corrected in the C layer +----------------------------- + +- Several bugs were corrected in the (still experimental) C attribute + handler. +- Pajek reader bug corrected, used to segfault if '\*Vertices' + was missing. +- Directedness is handled correctly when writing GML files. + (But note that 'correct' conflicts the standard here.) +- Corrected a bug when calculating weighted, directed PageRank on an + undirected graph. (Which does not make sense anyway.) +- Some code polish to make igraph compile with GCC 4.3 +- Several bugs were fixed in the Reingold-Tilford layout to avoid + edge crossings. +- A bug was fixed in the GraphML reader, when the value of a graph + attribute was not specified. +- Fixed a bug in the graph isomorphism routine for small (3-4 vertices) + graphs. +- Corrected the random sampling implementation (igraph_random_sample), + now it always generates unique numbers. This affects the + Gnm Erdos-Renyi generator, it always generates simple graphs now. +- The basic igraph constructor (igraph_empty_attrs, all functions + are expected to call this internally) now checks whether the number + of vertices is finite. +- The LGL, NCOL and Pajek graph readers handle errors properly now. +- The non-symmetric ARPACK solver returns results in a consistent form + now. +- The fast greedy community detection routine now checks that the graph + is simple. +- The LGL and NCOL parsers were corrected to work with all + kinds of end-of-line encodings. +- Hub & authority score calculations initialize ARPACK parameters now.x +- Fixed a bug in the Walktrap community detection routine, when applied + to unconnected graphs. + +igraph 0.5 +========= + +Released February 14, 2008 + +See also the release notes at http://igraph.sf.net/relnotes-0.5.html + +New in the R interface +---------------------- + +- The 'rescale', 'asp' and 'frame' graphical parameters were added +- Create graphs from a formula notation (graph.formula) +- Handle graph attributes properly +- Calculate the actual minimum cut for undirected graphs +- Adjacency lists, get.adjlist and get.adjedgelist added +- Eigenvector centrality computation is much faster now +- Proper R warnings, instead of writing the warning to the terminal +- R checks graphical parameters now, the unknown ones are not just + ignored, but an error message is given +- plot.igraph has an 'add' argument now to compose plots with multiple + graphs +- plot.igraph supports the 'main' and 'sub' arguments +- layout.norm is public now, it can normalize a layout +- It is possible to supply startup positions to layout generators +- Always free memory when CTRL+C/ESC is pressed, in all operating + systems +- plot.igraph can plot square vertices now, see the 'shape' parameter +- graph.adjacency rewritten when creating weighted graphs +- We use match.arg whenever possible. This means that character scalar + options can be abbreviated and they are always case insensitive + +- VF2 graph isomorphism routines can check subgraph isomorphism now, + and they are able to return matching(s) +- The BLISS graph isomorphism algorithm is included in igraph now. See + canonical.permutation, graph.isomorphic.bliss +- We use ARPACK for eigenvalue/eigenvector calculation. This means that the + following functions were rewritten: page.rank, + leading.eigenvector.community.\*, evcent. New functions based on + ARPACK: hub.score, authority.score, arpack. +- Edge weights for Fruchterman-Reingold layout (layout.fruchterman.reingold). +- Line graph calculation (line.graph) +- Kautz and de Bruijn graph generators (graph.kautz, graph.de.bruijn) +- Support for writing graphs in DOT format +- Jaccard and Dice similarity coefficients added (similarity.jaccard, + similarity.dice) +- Counting the multiplicity of edges (count.multiple) +- The graphopt layout algorithm was added, layout.graphopt +- Generation of "famous" graphs (graph.famous). +- Create graphs from LCF notation (graph.cf). +- Dyad census and triad cencus functions (dyad.census, triad.census) +- Cheking for simple graphs (is.simple) +- Create full citation networks (graph.full.citation) +- Create a histogram of path lengths (path.length.hist) +- Forest fire model added (forest.fire.game) +- DIMACS reader can handle different file types now +- Biconnected components and articulation points (biconnected.components, + articulation.points) +- Kleinberg's hub and authority scores (hub.score, authority.score) +- as.undirected handles attributes now +- Geometric random graph generator (grg.game) can return the + coordinates of the vertices +- Function added to convert leading eigenvector community structure result to + a membership vector (community.le.to.membership) +- Weighted fast greedy community detection +- Weighted page rank calculation +- Functions for estimating closeness, betweenness, edge betweenness by + introducing a cutoff for path lengths (closeness.estimate, + betweenness.estimate, edge.betweenness.estimate) +- Weighted modularity calculation +- Function for permuting vertices (permute.vertices) +- Betweenness and closeness calculations are speeded up +- read.graph can handle all possible line terminators now (\r, \n, \r\n, \n\r) +- Error handling was rewritten for walktrap community detection, + the calculation can be interrupted now +- The maxflow/mincut functions allow to supply NULL pointer for edge + capacities, implying unit capacities for all edges + +Bugs corrected in the R interface +--------------------------------- + +- Fixed a bug in cohesive.blocks, cohesive blocks were sometimes not + calculated correctly + +New in the Python interface +--------------------------- + +- Added shell interface: igraph can now be invoked by calling the script called + igraph from the command line. The script launches the Python interpreter and + automatically imports igraph functions into the main namespace +- Pickling (serialization) support for Graph objects +- Plotting functionality based on the Cairo graphics library (so you need to + install python-cairo if you want to use it). Currently the following + objects can be plotted: graphs, adjacency matrices and dendrograms. Some + crude support for plotting histograms is also implemented. Plots can be + saved in PNG, SVG and PDF formats. +- Unified Graph.layout method for accessing layout algorithms +- Added interfaces to walktrap community detection and the BLISS isomorphism + algorithm +- Added dyad and triad census functionality and motif counting +- VertexSeq and EdgeSeq objects can now be restricted to subsets of the + whole network (e.g., you can select vertices/edges based on attributes, + degree, centrality and so on) + +New in the C library +-------------------- + +- Many types (stack, matrix, dqueue, etc.) are templates now + They were also rewritten to provide a better organized interface +- VF2 graph isomorphism routines can check subgraph isomorphism now, + and they are able to return matching(s) +- The BLISS graph isomorphism algorithm is included in igraph now. See + igraph_canonical_permutation, igraph_isomorphic_bliss +- We use ARPACK for eigenvalue/eigenvector calculation. This means that the + following functions were rewritten: igraph_pagerank, + igraph_community_leading_eigenvector_\*. New functions based on + ARPACK: igraph_eigenvector_centrality, igraph_hub_score, + igraph_authority_score, igraph_arpack_rssolve, igraph_arpack_rnsolve +- Experimental C attribute interface added. I.e. it is possible to use + graph/vertex/edge attributes from C code now. + +- Edge weights for Fruchterman-Reingold layout. +- Line graph calculation. +- Kautz and de Bruijn graph generators +- Support for writing graphs in DOT format +- Jaccard and Dice similarity coefficients added +- igraph_count_multiple added +- igraph_is_loop and igraph_is_multiple "return" boolean vectors +- The graphopt layout algorithm was added, igraph_layout_graphopt +- Generation of "famous" graphs, igraph_famous +- Create graphs from LCF notation, igraph_lcf, igraph_lcf_vector +- igraph_add_edge adds a single edge to the graph +- Dyad census and triad cencus functions added +- igraph_is_simple added +- progress handlers are allowed to stop calculation +- igraph_full_citation to create full citation networks +- igraph_path_length_hist, create a histogram of path lengths +- forest fire model added +- DIMACS reader can handle different file types now +- Adjacency list types made public now (igraph_adjlist_t, igraph_adjedgelist_t) +- Biconnected components and articulation points can be computed +- Eigenvector centrality computation +- Kleinberg's hub and authority scores +- igraph_to_undirected handles attributes now +- Geometric random graph generator can return the coordinates of the vertices +- Function added to convert leading eigenvector community structure result to + a membership vector (igraph_le_community_to_membership) +- Weighted fast greedy community detection +- Weighted page rank calculation +- Functions for estimating closeness, betweenness, edge betweenness by + introducing a cutoff for path lengths +- Weighted modularity calculation +- igraph_permute_vertices added +- Betweenness ans closeness calculations are speeded up +- Startup positions can be supplied to the Kamada-Kawai layout + algorithms +- igraph_read_graph_\* functions can handle all possible line + terminators now (\r, \n, \r\n, \n\r) +- Error handling was rewritten for walktrap community detection, + the calculation can be interrupted now +- The maxflow/mincut functions allow to supply a null pointer for edge + capacities, implying unit capacities for all edges + +Bugs corrected in the C library +------------------------------- + +- Memory leak fixed in adjacency list handling +- Memory leak fixed in maximal independent vertex set calculation +- Fixed a bug when rewiring undirected graphs with igraph_rewire +- Fixed edge betweenness community structure detection for unconnected graphs +- Make igraph compile with Sun Studio +- Betweenness bug fixed, when not computing for all vertices +- memory usage of clique finding reduced +- Corrected bugs for motif counts when not all motifs were counted, + but a 'cut' vector was used +- Bugs fixed in trait games and cited type game +- Accept underscore as letter in GML files +- GML file directedness notation reversed, more logical this way + +igraph 0.4.5 +========= + +Released January 1, 2008 + +New: +- Cohesive block finding in the R interface, thanks to Peter McMahan + for contributing his code. See James Moody and Douglas R. White, + 2003, in Structural Cohesion and Embeddedness: A Hierarchical + Conception of Social Groups American Sociological Review 68(1):1-25 +- Biconnected components and articulation points. +- R interface: better printing of attributes. +- R interface: graph attributes can be used via '$'. + +New in the C library: +- igraph_vector_bool_t data type. + +Bug fixed: +- Erdos-Renyi random graph generators rewritten. + +igraph 0.4.4 +========= + +Released October 3, 2007 + +This release should work seemlessly with the new R 2.6.0 version. +Some other bugs were also fixed: +- A bug was fixed in the Erdos-Renyi graph generator, which sometimes + added an extra vertex. +- MSVC compilation issues were fixed. +- MinGW compilation fixes. + +igraph 0.4.3 +========= + +Released August 13, 2007 + +The next one in the sequence of bugfix releases. Thanks to many people +sending bug reports. Here are the changes: +- Some memory leaks removed when using attributes from R or Python. +- GraphML parser: entities and character data in multiple chunks are now handled correctly. +- A bug corrected in edge betweenness community structure detection, + it failed if called many times from the same program/session. +- Bug corrected in 'adjacent edges' edge iterator. +- Python interface: edge and vertex attribute deletion bug corrected. +- Edge betweeness community structure: handle unconnected graphs properly. +- Fixed bug related to fast greedy community detection in unconnected graphs. +- Use a different kind of parser (Push) for reading GraphML files. This is almost + invisible for users but fixed a nondeterministic bug when reading in GraphML + files. +- R interface: plot now handles properly if called with a vector as the edge.width + argument for directed graphs. +- R interface: bug (typo) corrected for walktrap.community and weighted graphs. +- Test suite should run correctly on Cygwin now. + +igraph 0.4.2 +========= + +Released June 7, 2007 + +This is another bugfix release, as there was a serious bug in the +R package of the previous version: it could not read and write graphs +to files in any format under MS Windows. + +Some other bits added: +- circular Reingold-Tilford layout generator for trees +- corrected a bug, Pajek files are written properly under MS Windows now. +- arrow.size graphical edge parameter added in the R interface. + +igraph 0.4.1 +========= + +Released May 23, 2007 + +This is a minor release, it corrects a number of bugs, mostly in the +R package. + +igraph 0.4 +========= + +Released May 21, 2007 + +The major new additions in this release is a bunch of community +detection algorithms and support for the GML file format. Here +is the complete list of changes: + + +New in the C library +-------------------- + +- internal representation changed +- neighbors always returns an ordered list +- igraph_is_loop and igraph_is_multiple added + +- topological sorting +- VF2 isomorphism algorithm +- support for reading the file format of the Graph Database for isomorphism +- igraph_mincut cat calculate the actual minimum cut +- girth calculation added, thanks to Keith Briggs +- support for reading and writing GML files + +- Walktrap community detection algorithm added, thanks to Matthieu Latapy + and Pascal Pons +- edge betweenness based community detection algorithm added +- fast greedy algorithm for community detection by Clauset et al. added + thanks to Aaron Clauset for sharing his code +- leading eigenvector community detection algorithm by Mark Newman added +- igraph_community_to_membership supporting function added, creates + a membership vector from a community structure merge tree +- modularity calculation added + +New in the R interface +---------------------- + +- as the internal representation changed, graphs stored with 'save' + with an older igraph version cannot be read back with the new + version reliably. +- neighbors returns ordered lists + +- topological sorting +- VF2 isomorphism algorithm +- support for reading graphs from the Graph Database for isomorphism +- girth calculation added, thanks to Keith Briggs +- support for reading and writing GML files + +- Walktrap community detection algorithm added, thanks to Matthieu Latapy + and Pascal Pons +- edge betweenness based community detection algorithm added +- fast greedy algorithm for community detection by Clauset et al. added + thanks to Aaron Clauset for sharing his code +- leading eigenvector community detection algorithm by Mark Newman added +- functions for creating denrdograms from the output of the + community detection algorithms added +- community.membership supporting function added, creates + a membership vector from a community structure merge tree +- modularity calculation added + +- graphics parameter handling is completely rewritten, uniform handling + of colors and fonts, make sure you read ?igraph.plotting +- new plotting parameter for edges: arrow.mode +- a bug corrected when playing a nonlinear barabasi.game +- better looking plotting in 3d using rglplot: edges are 3d too +- rglplot layout is allowed to be two dimensional now +- rglplot suspends updates while drawing, this makes it faster +- loop edges are correctly plotted by all three plotting functions + +- better printing of attributes when printing graphs +- summary of a graph prints attribute names +- is.igraph rewritten to make it possible to inherit from the 'igraph' class +- somewhat better looking progress meter for functions which support it + +Others +------ + +- proper support for Debian packages (re)added +- many functions benefit from the new internal representation and are + faster now: transitivity, reciprocity, graph operator functions like + intersection and union, etc. +- igraph compiles with Microsoft Visual C++ now +- there were some internal changes to make igraph a real graph algorithm + platform in the near future, but these are undocumented now + +Bugs corrected +-------------- + +- corrected a bug when reading Pajek files: directed graphs were read as undirected + +Debian package repository available +================================== + +Debian Linux users can now install and update the C interface +using the standard package manager. Just add the following two +lines to /etc/apt/sources.list and install the libigraph and +libigraph-dev packages. Packages for the Python interface are +coming soon. + +deb http://cneurocvs.rmki.kfki.hu /packages/binary/ + +deb-src http://cneurocvs.rmki.kfki.hu /packages/source/ + +igraph 0.3.3 +============ + +Released February 28, 2007 + +New in the C library +-------------------- + +* igraph_connect_neighborhood, nomen est omen +* igraph_watts_strogatz_game and igraph_rewire_edges +* K-core decomposition: igraph_coreness +* Clique and independent vertex set related functions: + igraph_cliques, igraph_independent_vertex_sets, + igraph_maximal_cliques, igraph_maximal_independent_vertex_sets, + igraph_independence_number, igraph_clique_number, + Some of these function were ported from the very_nauty library + of Keith Briggs, thanks Keith! +* The GraphML file format now supports graph attributes +* Transitivity calculation speeded up +* Correct transitivity calculation for multigraphs (ie. non-simple graphs) + +New in the R interface +---------------------- + +* connect.neighborhood +* watts.strogatz.game and rewire.edges +* K-core decomposition: graph.coreness +* added the 'innei' and 'outnei' shorthands for vertex sequence indexing + see help(iterators) +* Clique and independent vertex set related functions: + cliques, largest.cliques, maximal.cliques, clique.number, + independent.vertex.sets, largest.independent.vertex.sets, + maximal.independent.vertex.sets, independence.number +* The GraphML file format now supports graph attributes +* edge.lty argument added to plot.igraph and tkplot +* Transitivity calculation speeded up +* Correct transitivity calculation for multigraphs (ie. non-simple graphs) +* alpha.centrality added, calculates Bonacich alpha centrality, see docs. + +Bugs corrected +-------------- + +* 'make install' installs the library correctly on Cygwin now +* Pajek parser corrected to read files with MacOS newline characters correctly +* overflow bug in transitivity calculation for large graphs corrected +* an internal memcpy/memmove bug causing some segfaults removed +* R interface: tkplot bug with graphs containing a 'name' attribute +* R interface: attribute handling bug when adding vertices +* R interface: color selection bug corrected +* R interface: plot.igraph when plotting loops + +Python interface documentation +==================== + +Jan 8, 2007 + +The documentation of the Python interface is available. +See section 'documentation' in the menu on the left. + +igraph 0.3.2 +========= + +Released Dec 19, 2006 + +This is a new major release, it contains many new things: + +Changes in the C library +------------------------ + +- igraph_maxdegree added, calculates the maximum degree in the graph +- igraph_grg_game, geometric random graphs +- igraph_density, graph density calculation +- push-relabel maximum flow algorithm added, igraph_maxflow_value +- minimum cut functions added based on maximum flow: + igraph_st_mincut_value, igraph_mincut_value, the Stoer-Wagner + algorithm is implemented for undirected graphs +- vertex connectivity functions, usually based on maximum flow: + igraph_st_vertex_connectivity, igraph_vertex_connectivity +- edge connectivity functions, usually based on maximum flow: + igraph_st_edge_connectivity, igraph_edge_connectivity +- other functions based on maximum flow: igraph_edge_disjoint_paths, + igraph_vertex_disjoint_paths, igraph_adhesion, igraph_cohesion +- dimacs file format added +- igraph_to_directed handles attributes +- igraph_constraint calculation corrected, it handles weighted graphs +- spinglass-based community structure detection, the Joerg Reichardt -- + Stefan Bornholdt algorithm added: igraph_spinglass_community, + igraph_spinglass_my_community +- igraph_extended_chordal_rings, it creates extended chordal rings +- 'no' argument added to igraph_clusters, it is possible to calculate + the number of clusters without calculating the clusters themselves +- minimum spanning tree functions keep attributes now and also the + direction of the edges is kept in directed graphs +- there are separate functions to calculate different types of + transitivity now +- igraph_delete_vertices rewritten to allocate less memory for the new + graph +- neighborhood related functions added: igraph_neighborhood, + igraph_neighborhood_size, igraph_neighborhood_graphs +- two new games added based on different node types: + igraph_preference_game and igraph_asymmetric_preference_game +- Laplacian of a graph can be calculated by the igraph_laplacian function + +Changes in the R interface +-------------------------- + +- bonpow function ported from SNA to calculate Bonacich power centrality +- get.adjacency supports attributes now, this means that it sets the + colnames and rownames attributes and can return attribute values in + the matrix instead of 0/1 +- grg.game, geometric random graphs +- graph.density, graph density calculation +- edge and vertex attributes can be added easily now when added new + edges with add.edges or new vertices with add.vertices +- graph.data.frame creates graph from data frames, this can be used to + create graphs with edge attributes easily +- plot.igraph and tkplot can plot self-loop edges now +- graph.edgelist to create a graph from an edge list, can also handle + edge lists with symbolic names +- get.edgelist has now a 'names' argument and can return symbolic + vertex names instead of vertex ids, by default id uses the 'name' + vertex attribute is returned +- printing graphs on screen also prints symbolic symbolic names + (the 'name' attribute if present) +- maximum flow and minimum cut functions: graph.maxflow, graph.mincut +- vertex and edge connectivity: edge.connectivity, vertex.connectivity +- edge and vertex disjoint paths: edge.disjoint.paths, + vertex.disjoint.paths +- White's cohesion and adhesion measure: graph.adhesion, graph.cohesion +- dimacs file format added +- as.directed handles attributes now +- constraint corrected, it handles weighted graphs as well now +- weighted attribute to graph.adjacency +- spinglass-based community structure detection, the Joerg Reichardt -- + Stefan Bornholdt algorithm added: spinglass.community +- graph.extended.chordal.ring, extended chordal ring generation +- no.clusters calculates the number of clusters without calculating + the clusters themselves +- minimum spanning tree functions updated to keep attributes +- transitivity can calculate local transitivity as well +- neighborhood related functions added: neighborhood, + neighborhood.size, graph.neighborhood +- new graph generators based on vertex types: preference.game and + asymmetric.preference.game + +Bugs corrected +-------------- + +- attribute handling bug when deleting edges corrected +- GraphML escaping and NaN handling corrected +- bug corrected to make it possible compile the R package without the + libxml2 library +- a bug in Erdos-Renyi graph generation corrected: it had problems + with generating large directed graphs +- bug in constraint calculation corrected, it works well now +- fixed memory leaks in igraph_read_graph_graphml +- error handling bug corrected in igraph_read_graph_graphml +- bug corrected in R version of graph.laplacian when normalized + Laplacian is requested +- memory leak corrected in get.all.shortest.paths in the R package + +igraph 0.2.1 +========= + +Released Aug 23, 2006 + +This is a bug-fix release. Bugs fixed: +- igraph_reciprocity (reciprocity in R) corrected to avoid segfaults +- some docs updates +- various R package updated to make it conform to the CRAN rules + +igraph 0.2 +========= + +Released Aug 18, 2006 + +Release time at last! There are many new things in igraph 0.2, the +most important ones: +- reading writing Pajek and GraphML formats with attributes + (not all Pajek and GraphML files are supported, see documentation + for details) +- iterators totally rewritten, it is much faster and cleaner now +- the RANDEDU fast motif search algorithm is implemented +- many new graph generators, both games and regular graphs +- many new structural properties: transitivity, reciprocity, etc. +- graph operators: union, intersection, difference, structural holes, etc. +- conversion between directed and undirected graphs +- new layout algorithms for trees and large graphs, 3D layouts + +and many more. + +New things in the R package: +- support for CTRL+C +- new functions: Graph Laplacian, Burt's constraint, etc. +- vertex/edge sequences totally rewritten, smart indexing (see manual) +- new R manual and tutorial: 'Network Analysis with igraph', still + under development but useful +- very basic 3D plotting using OpenGL + +Although this release was somewhat tested on Linux, MS Windows, Mac +OSX, Solaris 8 and FreeBSD, no heavy testing was done, so it might +contain bugs, and we kindly ask you to send bug reports to make igraph +better. + +igraph mailing lists +==================== + +Aug 18, 2006 + +I've set up two igraph mailing lists: igraph-help for +general igraph questions and discussion and +igraph-anonunce for announcements. See +http://lists.nongnu.org/mailman/listinfo/igraph-help and +http://lists.nongnu.org/mailman/listinfo/igraph-announce +for subscription information, archives, etc. + +igraph 0.1 +========= + +Released Jan 30, 2006 + +After about a year of development this is the first "official" release +of the igraph library. This release should be considered as beta +software, but it should be useful in general. Please send your +questions and comments. + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..765983c --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ + +[![Build status Linux](https://travis-ci.org/igraph/igraph.svg?branch=master)](https://travis-ci.org/igraph/igraph) +[![Build status Windows](https://ci.appveyor.com/api/projects/status/github/igraph/igraph?branch=master&svg=true)](https://ci.appveyor.com/project/ntamas/igraph/branch/master) +[![DOI](https://zenodo.org/badge/8546198.svg)](https://zenodo.org/badge/latestdoi/8546198) + +The igraph library +------------------ + +igraph is a C library for creating, manipulating and analysing graphs. +It is intended to be as powerful (i.e. fast) as possible to enable +working with large graphs. + +See https://igraph.org for installation instructions +and documentation. + +Igraph can also be used from: + + - R — https://github.com/igraph/rigraph + - Python — https://github.com/igraph/python-igraph + - Mathematica — https://github.com/szhorvat/IGraphM diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..0c84776 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,96 @@ +# This file is based on one which was automatically generated by conda-smithy +# and the one in matplotlib. +# It uses conda environment to get the build dependencies for a full windows +# build, both the "normal" one and the msvc based one. + +environment: + PATH: C:\msys64\usr\bin;C:\msys64\mingw64\bin;C:\Windows\System32;C:\Windows;%PATH% + MSYSTEM: MINGW64 + TARGET_ARCH: "x64" + + matrix: + - + PYTHON_VERSION: "2.7" + CONDA_INSTALL_LOCN: "C:\\Miniconda-x64" + - + PYTHON_VERSION: "3.5" + CONDA_INSTALL_LOCN: "C:\\Miniconda35-x64" + - + PYTHON_VERSION: "3.6" + CONDA_INSTALL_LOCN: "C:\\Miniconda36-x64" + - + PYTHON_VERSION: "3.7" + CONDA_INSTALL_LOCN: "C:\\Miniconda37-x64" + - + PYTHON_VERSION: "NONE" + CONDA_INSTALL_LOCN: "C:\\Miniconda37-x64" + +# We always use a 64-bit machine, but can build x86 distributions +# with the PYTHON_ARCH variable (which is used by CMD_IN_ENV). +platform: + - x64 + +init: + - cmd: "ECHO %PYTHON_VERSION% %CONDA_INSTALL_LOCN%" + +# all our builds have to happen in install... +build: false + +install: + # setup conda environment for building + - cmd: set "PATH=%CONDA_INSTALL_LOCN%;%CONDA_INSTALL_LOCN%\scripts;%PATH%" + - cmd: set PYTHONUNBUFFERED=1 + + # update mysy2 + - C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -Sy pacman-mirrors" + - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -Sy" + - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S autoconf automake bison flex" + - C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S libxml2-devel zip" + + + # also install a msvc build environment -> use libxml2 from conda-forge + # updating conda always updates python, even with "no-update-deps" and + # updating python takes ages on appveyor... So just keep the shorter PATH + # workaround for the activate failure and don't update conda itself... + #- cmd: conda update conda --no-update-dependencies + - cmd: conda config --add channels conda-forge + - cmd: conda config --set show_channel_urls yes + - cmd: conda config --set always_yes true + - cmd: if [%PYTHON_VERSION%] NEQ [NONE] conda install --quiet libxml2 python=%PYTHON_VERSION% + - cmd: conda info -a + + # Now start with the build: first the msys2 based one + - cmd: bash bootstrap.sh + - cmd: bash configure + # for testing purpose removed, takes ages... + - cmd: if %PYTHON_VERSION%==NONE make + + # now make the msvc builds + - cmd: if [%PYTHON_VERSION%] NEQ [NONE] make msvc + + # now build the with the right compiler for each python version + - cmd: if [%PYTHON_VERSION%] NEQ [NONE] cd igraph-*-msvc + + - cmd: if %PYTHON_VERSION%==2.7 call "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat" + - cmd: if %PYTHON_VERSION%==2.7 vcbuild.exe /upgrade + - cmd: if %PYTHON_VERSION%==2.7 vcbuild.exe igraph.vcproj "Release|%TARGET_ARCH%" + + - cmd: if %PYTHON_VERSION%==3.4 call "%VS100COMNTOOLS%\vsvars32.bat" + - cmd: if %PYTHON_VERSION%==3.4 VCUpgrade.exe /overwrite igraph.vcproj + - cmd: if %PYTHON_VERSION%==3.4 msbuild.exe igraph.vcxproj /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + + - cmd: if %PYTHON_VERSION%==3.5 call "%VS140COMNTOOLS%\vsvars32.bat" + - cmd: if %PYTHON_VERSION%==3.5 devenv /upgrade igraph.vcproj + - cmd: if %PYTHON_VERSION%==3.5 msbuild.exe igraph.vcxproj /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + +test_script: + - cmd: cd "%APPVEYOR_BUILD_FOLDER%" + - cmd: set "PATH=%APPVEYOR_BUILD_FOLDER%\src\.libs;%PATH%" + - cmd: path + - cmd: if [%PYTHON_VERSION%]==[NONE] make check + +on_failure: + - cmd: echo zipping everything after a failure... + - cmd: cd "%APPVEYOR_BUILD_FOLDER%" + - cmd: 7z a failed_state.zip . |grep -v "Compressing" + - cmd: appveyor PushArtifact failed_state.zip diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..8a25a44 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,36 @@ +#! /bin/sh + +cd "`dirname $0`" + +## Find out our version number, need git for this +printf "Finding out version number/string... " +tools/getversion.sh > IGRAPH_VERSION +cat IGRAPH_VERSION + +for i in glibtoolize libtoolize; do + LIBTOOLIZE=`which $i` && break +done +if [ -z "$LIBTOOLIZE" ]; then + echo libtoolize or glibtoolize not found or not in the path! + exit 1 +fi + +mkdir -p m4 + +set -x + +# Order of commands in the next few lines are taken from here: +# https://stackoverflow.com/a/11279735/156771 + +$LIBTOOLIZE --force --copy + +aclocal -I m4 --install +autoheader +autoconf + +automake --foreign --add-missing --force-missing --copy + +# Try to patch ltmain.sh to allow -fsanitize=* linker flags to be passed +# through to the linker. Don't do anything if it fails; maybe libtool has +# been upgraded already. +patch -N -p0 -r- /dev/null || true diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..b422caf --- /dev/null +++ b/configure.ac @@ -0,0 +1,410 @@ +AC_INIT(igraph, esyscmd([tr -d '\n' < IGRAPH_VERSION]), igraph@igraph.org) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_SRCDIR(src/games.c) +AM_INIT_AUTOMAKE([foreign subdir-objects]) +AC_CONFIG_HEADERS([config.h]) + +m4_include(tools/autoconf/ax_tls.m4) +m4_include(tools/autoconf/as-version.m4) + +AS_VERSION + +# Define list of additional libraries that have to be linked to igraph when +# another app tries to link to the static library of igraph. This is substituted +# into igraph.pc later on. +PKGCONFIG_LIBS_PRIVATE="-lxml2 -lz -lm" +AC_SUBST(PKGCONFIG_LIBS_PRIVATE) + +# Test suite +AC_CONFIG_TESTDIR(tests) +AC_CONFIG_FILES([tests/Makefile tests/atlocal]) + +# Don't allow AC_PROG_CC to set a default CFLAGS or CXXFLAGS +: ${CFLAGS=""} +: ${CXXFLAGS=""} + +AC_LANG(C) +AC_PROG_CC + +# Tricky check for C++ compiler, because Autoconf has a weird bug: +# http://lists.gnu.org/archive/html/autoconf/2006-03/msg00067.html +AC_PROG_CXX +AC_LANG_PUSH([C++]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include +const char hw[] = "Hello, World\n";]], + [[std::cout << hw;]])], + [AC_PROG_CXXCPP + cxx_error=no], + [AC_MSG_ERROR([no C++ compiler found or it cannot create executables])]) +AC_LANG_POP([C++]) + +AM_PROG_LEX +AC_PROG_YACC + +AC_CHECK_HEADER([sys/times.h], + [AC_DEFINE([HAVE_TIMES_H], [1], [Define to 1 if you have the sys/times.h header])], + [CPPFLAGS="$CPPFLAGS -DMSDOS"], + ) + +AC_LIBTOOL_WIN32_DLL +AC_LIBTOOL_DLOPEN +AC_PROG_LIBTOOL +AM_MISSING_PROG([AUTOM4TE], [autom4te]) + +AC_HEADER_STDC +AC_CHECK_HEADERS([stdarg.h stdlib.h string.h time.h unistd.h stdint.h sys/int_types.h]) +LIBS_SAVE=$LIBS +LIBS="$LIBS -lm" +AC_CHECK_FUNCS([expm1 rint rintf finite log2 snprintf log1p round fabsl fmin strcasecmp isnan strdup _strdup ftruncate stpcpy]) +AC_CHECK_DECLS(isfinite,,,[#include ]) +AC_CHECK_DECL([stpcpy], + [AC_DEFINE([HAVE_STPCPY_SIGNATURE], [1], [Define to 1 if the stpcpy function has a signature])]) +LIBS=$LIBS_SAVE + +AC_DEFUN([IGRAPH_WARNING], +[AC_MSG_CHECKING(whether compiler accepts $1) +AC_SUBST(WARNING_CFLAGS) +ac_save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $1" +AC_TRY_COMPILE(, +[int x;], +WARNING_CFLAGS="$WARNING_CFLAGS $1" +AC_MSG_RESULT(yes), +AC_MSG_RESULT(no)) +CFLAGS="$ac_save_CFLAGS"]) + +AC_DEFUN([IGRAPH_CC_SWITCH], +[AC_MSG_CHECKING(whether compiler supports $1) +ac_save_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS $1" +AC_TRY_COMPILE(, +[int x;], +AC_MSG_RESULT(yes) +$2, +AC_MSG_RESULT(no) +$3) +CFLAGS="$ac_save_CFLAGS"]) + +## Solaris cc does not support -ffloat-store. +FLOATSTORE= +IGRAPH_CC_SWITCH([-ffloat-store], [FLOATSTORE="-ffloat-store"], []) +IGRAPH_CC_SWITCH([-fstore], [FLOATSTORE="$FLOATSTORE -fstore"], []) +AC_SUBST(FLOATSTORE) + +AC_ARG_ENABLE(gcc-warnings, + AC_HELP_STRING([--enable-gcc-warnings], + [turn on lots of GCC warnings (not recommended)]), +[case "${enableval}" in + yes|no) ;; + *) AC_MSG_ERROR([bad value ${enableval} for gcc-warnings option]) ;; + esac], + [enableval=no]) +if test "${enableval}" = yes; then + IGRAPH_WARNING(-Werror) + AC_SUBST([WERROR_CFLAGS], [$WARNING_CFLAGS]) + WARNING_CFLAGS= + IGRAPH_WARNING(-Wall) + IGRAPH_WARNING(-W) + IGRAPH_WARNING(-Wbad-function-cast) + IGRAPH_WARNING(-Wcast-align) + IGRAPH_WARNING(-Wcast-qual) + IGRAPH_WARNING(-Wformat) + IGRAPH_WARNING(-Wmissing-declarations) + IGRAPH_WARNING(-Wmissing-prototypes) + IGRAPH_WARNING(-Wnested-externs) + IGRAPH_WARNING(-Wshadow) + IGRAPH_WARNING(-Wstrict-prototypes) + IGRAPH_WARNING(-Wwrite-strings) +else + WARNING_CFLAGS= + IGRAPH_WARNING(-Wall) +fi + +use_gprof=no +AC_ARG_ENABLE(profiling, + AC_HELP_STRING([--enable-profiling], [Enable gprof profiling]), + [use_gprof=$enableval], [use_gprof=no]) + +use_asan=no +AC_ARG_ENABLE(asan, + AC_HELP_STRING([--enable-asan], [Enable Clang address sanitizer]), + [use_asan=$enableval], [use_asan=no]) + +debug=no +AC_ARG_ENABLE(debug, + AC_HELP_STRING([--enable-debug], [Enable debug build]), + [debug=$enableval]) + +graphml_support=yes +AC_ARG_ENABLE(graphml, + AC_HELP_STRING([--disable-graphml], [Disable support for GraphML format]), + [graphml_support=$enableval], [graphml_support=yes]) + +HAVE_LIBXML=0 +if test $graphml_support = yes; then + AC_PATH_PROG([XML2CONFIG], [xml2-config], [none]) + if test "$XML2CONFIG" = "none"; then + # Hmmm, no xml2-config. Older versions of OS X do not have it while still + # having libxml2, so let's try an educated guess if + # /usr/include/libxml2/libxml/parser.h exists. + AC_CHECK_FILE([/usr/include/libxml2/libxml/parser.h], [ + XML2_LIBS="-lxml2 -lz -lm" + XML2_CFLAGS="-I/usr/include/libxml2" + ], [ + graphml_support=no + ]) + else + XML2_LIBS=`$XML2CONFIG --libs` + XML2_CFLAGS=`$XML2CONFIG --cflags` + if test -f /usr/bin/sw_vers -a `sw_vers -productVersion | grep -c "^10\.11\."` -gt 0 -a `echo $XML2_LIBS | grep -c '/Developer/'` -gt 0; then + # We are on OS X 10.11, which has a known bug with xml2-config; see + # https://github.com/igraph/igraph/issues/973 . We work around it + # here + XML2_LIBS=`$XML2CONFIG --exec-prefix=/usr --libs` + fi + fi + AC_CHECK_LIB([xml2], [xmlSAXUserParseFile], [ + ac_save_CFLAGS="$CFLAGS" + ac_save_CPPFLAGS="$CPPFLAGS" + CFLAGS=${XML2_CFLAGS} + CPPFLAGS=${XML2_CFLAGS} + AC_CHECK_HEADER([libxml/parser.h], [ + HAVE_LIBXML=1 + AC_DEFINE([HAVE_LIBXML], [1], [Define to 1 if you have the libxml2 libraries installed]) + CFLAGS="$ac_save_CFLAGS ${XML2_CFLAGS}" + CPPFLAGS="$ac_save_CPPFLAGS" + AC_SUBST(XML2_LIBS) + AC_SUBST(XML2_CFLAGS) + ], [ + graphml_support=no + CFLAGS="$ac_save_CFLAGS" + CPPFLAGS="$ac_save_CPPFLAGS" + ]) + ], [ + graphml_support=no + ]) +fi + +AC_LANG_PUSH([C++]) +gmp_support=no +AC_ARG_ENABLE(gmp, AC_HELP_STRING([--disable-gmp], [Compile without the GMP library])) +if test "x$enable_gmp" != "xno"; then + AC_CHECK_LIB([gmp], [__gmpz_add], [ + AC_CHECK_HEADER([gmp.h], [ + AC_DEFINE([HAVE_GMP], [1], [Define to 1 if you have the GMP library]) + gmp_support=yes + LDFLAGS="${LDFLAGS} -lgmp" + PKGCONFIG_LIBS_PRIVATE="${PKGCONFIG_LIBS_PRIVATE} -lgmp" + ]) + ]) +fi +AC_LANG_POP([C++]) + +tls_support=no +HAVE_TLS=0 +THREAD_LOCAL= +AC_ARG_ENABLE(tls, AC_HELP_STRING([--enable-tls], [Compile with thread-local storage])) +if test "x$enable_tls" = "xyes"; then + keywords="__thread __declspec(thread)" + for kw in $keywords ; do + AC_TRY_COMPILE([int $kw test;], [], ac_cv_tls=$kw) + AC_TRY_COMPILE([int $kw test;], [], ac_cv_tls=$kw ; break ;) + done + AX_TLS([ + AC_DEFINE([HAVE_TLS], [1], [Define to 1 if you want to use thread-local storage for global igraph structures]) + tls_support=yes + HAVE_TLS=1 + THREAD_LOCAL=$ac_cv_tls + ], []) +fi +AC_SUBST(HAVE_TLS) +AC_DEFINE_UNQUOTED([IGRAPH_THREAD_LOCAL], $THREAD_LOCAL, + [Keyword for thread local storage, or empty if not available]) +AC_DEFINE_UNQUOTED([IGRAPH_F77_SAVE], [static IGRAPH_THREAD_LOCAL], + [Keyword for thread local storage, or just static if not available]) + +AC_ARG_WITH([external-f2c], [AS_HELP_STRING([--with-external-f2c], + [Use external F2C library [default=no]])], + [internal_f2c=no], + [internal_f2c=yes]) +AC_ARG_WITH([external-blas], [AS_HELP_STRING([--with-external-blas], + [Use external BLAS library [default=no]])], + [internal_blas=no], + [internal_blas=yes]) +AC_ARG_WITH([external-lapack], [AS_HELP_STRING([--with-external-lapack], + [Use external LAPACK library [default=no]])], + [internal_lapack=no], + [internal_lapack=yes]) +AC_ARG_WITH([external-arpack], [AS_HELP_STRING([--with-external-arpack], + [Use external ARPACK library [default=no]])], + [internal_arpack=no], + [internal_arpack=yes]) + +AC_ARG_WITH([external-glpk], [AS_HELP_STRING([--with-external-glpk], + [Use external GLPK library [default=no]])], + [internal_glpk=no], + [internal_glpk=yes]) + +needs_f2c="no" +if test "$internal_blas" = "yes" -o "$internal_lapack" = "yes" -o "$internal_arpack" = "yes"; then + needs_f2c="yes" +fi + +if test "$needs_f2c" = "yes"; then + if test "$internal_f2c" = "no"; then + AC_CHECK_LIB([f2c], [f77_alloc_], [], + AC_CHECK_LIB([f2c], [f77_alloc], [], + AC_CHECK_LIB([f2c], [F77_ALLOC_], [], + AC_CHECK_LIB([f2c], [F77_ALLOC], [], + [AC_MSG_RESULT(not found, trying to use -lf2c anyway.)])))) + LDFLAGS="${LDFLAGS} -lf2c" + else + AC_DEFINE([INTERNAL_F2C], [1], [Define to 1 if you use the internal F2C library]) + fi +else + internal_f2c=no +fi + +if test "$internal_blas" = "no"; then + AC_CHECK_LIB([blas], [daxpy_], [], + AC_CHECK_LIB([blas], [daxpy], [], + AC_CHECK_LIB([blas], [DAXPY_], [], + AC_CHECK_LIB([blas], [DAXPY], [], + [AC_MSG_RESULT(not found, trying to use -lblas anyway.)])))) + LDFLAGS="${LDFLAGS} -lblas" + PKGCONFIG_LIBS_PRIVATE="${PKGCONFIG_LIBS_PRIVATE} -lblas" +else + AC_DEFINE([INTERNAL_BLAS], [1], [Define to 1 if you use the internal BLAS library]) +fi + +if test "$internal_lapack" = "no"; then + AC_CHECK_LIB([lapack], [dlarnv_], [], + AC_CHECK_LIB([lapack], [dlarnv], [], + AC_CHECK_LIB([lapack], [DLARNV_], [], + AC_CHECK_LIB([lapack], [DLARNV], [], + [AC_MSG_RESULT(not found, trying to use -llapack anyway.)])))) + LDFLAGS="${LDFLAGS} -llapack" + PKGCONFIG_LIBS_PRIVATE="${PKGCONFIG_LIBS_PRIVATE} -llapack" +else + AC_DEFINE([INTERNAL_LAPACK], [1], [Define to 1 if you use the internal LAPACK library]) +fi + +if test "$internal_arpack" = "no"; then + if test "$tls_support" = "yes"; then + AC_MSG_ERROR([Thread-local storage only supported with internal ARPACK library]) + fi + AC_CHECK_LIB([arpack], [dsaupd_], [], + AC_CHECK_LIB([arpack], [dsaupd], [], + AC_CHECK_LIB([arpack], [DSAUPD_], [], + AC_CHECK_LIB([arpack], [DSAUPD], [], + [AC_MSG_RESULT(not found, trying to use -larpack anyway.)])))) + LDFLAGS="${LDFLAGS} -larpack" + PKGCONFIG_LIBS_PRIVATE="${PKGCONFIG_LIBS_PRIVATE} -larpack" +else + AC_DEFINE([INTERNAL_ARPACK], [1], [Define to 1 if you use the internal ARPACK library]) +fi + +glpk_support=no +AC_ARG_ENABLE(glpk, AC_HELP_STRING([--disable-glpk], [Compile without the GLPK library])) +if test "x$enable_glpk" != "xno"; then + if test "$internal_glpk" = "no"; then + AC_CHECK_LIB([glpk], [glp_read_mps], [ + AC_CHECK_HEADER([glpk.h], [ + AC_EGREP_CPP(yes, [ + #include + #if GLP_MAJOR_VERSION > 4 || (GLP_MAJOR_VERSION == 4 && GLP_MINOR_VERSION >= 38) + yes + #endif + ], [ + AC_DEFINE([HAVE_GLPK], [1], [Define to 1 if you have the GLPK library]) + glpk_support=yes + LDFLAGS="${LDFLAGS} -lglpk" + PKGCONFIG_LIBS_PRIVATE="${PKGCONFIG_LIBS_PRIVATE} -lglpk" + ]) + ]) + ]) + else + AC_DEFINE([HAVE_GLPK], [1], [Define to 1 if you have the GLPK library]) + AC_DEFINE([INTERNAL_GLPK], [1], [Define to 1 if you use the internal GLPK library]) + glpk_support=yes + fi +else + internal_glpk=no +fi + +# Link time optimization feature in newer gcc/g++ +# based on http://svn.r-project.org/R/trunk/configure.ac +AC_ARG_ENABLE([lto], + [AS_HELP_STRING([--enable-lto],[enable link-time optimization @<:@default=no@:>@])], + [if test "x${enableval}" = xyes -o "x${enableval}" = x; then + want_lto=yes + elif test "x${enableval}" = xno; then + want_lto=no + else + AC_MSG_ERROR([Invalid response to --enable-lto (got ${enableval})]) + fi], + [want_lto=no] +) +use_lto=no +if test "x${want_lto}" = xyes; then + AX_CHECK_COMPILE_FLAG([-flto], + [use_lto=yes; CFLAGS="$CFLAGS -flto"; CXXFLAGS="$CXXFLAGS -flto"], + [AC_MSG_ERROR([Compiler doesn't support -flto, requested by link-time optimization (--enable-lto)])]) + LTO=-flto +fi +AC_SUBST(LTO) +AM_CONDITIONAL(BUILD_LTO, [test "x${want_lto}" != xno]) + +AM_CONDITIONAL(INTERNAL_GLPK, test x$internal_glpk = xyes) +AM_CONDITIONAL(INTERNAL_ARPACK, test x$internal_arpack = xyes) +AM_CONDITIONAL(INTERNAL_LAPACK, test x$internal_lapack = xyes) +AM_CONDITIONAL(INTERNAL_BLAS, test x$internal_blas = xyes) +AM_CONDITIONAL(INTERNAL_F2C, test x$internal_f2c = xyes) + +if test "$debug" = "yes"; then + CFLAGS="${CFLAGS} -ggdb -O0" + CPPFLAGS="${CPPFLAGS} -DRC_DEBUG" + CXXFLAGS="${CXXFLAGS} -ggdb -O0" +fi + +if test "$use_gprof" = "yes"; then + CFLAGS="${CFLAGS} -pg" + CXXFLAGS="${CXXFLAGS} -pg" +fi + +if test "$use_asan" = "yes"; then + CFLAGS="${CFLAGS} -g -fsanitize=address -fno-omit-frame-pointer" + CXXFLAGS="${CXXFLAGS} -g -fsanitize=address -fno-omit-frame-pointer" +fi + +if test "$use_asan" != "yes" -a "$use_gprof" != "yes" -a "$debug" != "yes"; then + CFLAGS="${CFLAGS} -O3" + CPPFLAGS="${CPPFLAGS} -O3" + CXXFLAGS="${CXXFLAGS} -O3" +fi + +AC_CONFIG_FILES([Makefile src/Makefile igraph.pc igraph_Info.plist doc/Makefile include/igraph_version.h include/igraph_threading.h]) +AC_OUTPUT + +AC_MSG_RESULT([igraph successfully configured.]) +AC_MSG_RESULT([ GraphML format support -- $graphml_support]) +AC_MSG_RESULT([ GMP library support -- $gmp_support]) +AC_MSG_RESULT([ GLPK library support -- $glpk_support]) +AC_MSG_RESULT([ Thread-local storage -- $tls_support]) +AC_MSG_RESULT([ Use internal ARPACK -- $internal_arpack]) +AC_MSG_RESULT([ Use internal LAPACK -- $internal_lapack]) +AC_MSG_RESULT([ Use internal BLAS -- $internal_blas]) +if test "$needs_f2c" != "yes"; then + AC_MSG_RESULT([ Use internal F2C -- f2c not needed]) +else + AC_MSG_RESULT([ Use internal F2C -- $internal_f2c]) +fi +if test "$glpk_support" != "no"; then + AC_MSG_RESULT([ Use internal GLPK -- $internal_glpk]) +fi +AC_MSG_RESULT([ Debug build -- $debug]) +AC_MSG_RESULT([ Clang AddressSanitizer -- $use_asan]) +AC_MSG_RESULT([ Profiling -- $use_gprof]) +AC_MSG_RESULT([ Link time optimization -- $use_lto]) + diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..34f365b --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,286 @@ +## Process this file with automake to produce Makefile.in + +DOXROX=$(top_srcdir)/doc/doxrox.py +REGEX=$(top_srcdir)/doc/c-docbook.re +SRCDIR=$(top_srcdir)/src +INCLUDEDIR=$(top_srcdir)/include +BUILD_INCLUDEDIR=$(top_builddir)/include +REMOVEEXAMPLES=$(top_srcdir)/tools/removeexamples.py + +DOCINCLUDES = vector.xml error.xml matrix.xml basicigraph.xml generators.xml \ + structural.xml iterators.xml attributes.xml layout.xml \ + foreign.xml nongraph.xml isomorphism.xml motifs.xml \ + operators.xml flows.xml community.xml cliques.xml \ + sparsematrix.xml stack.xml dqueue.xml heap.xml strvector.xml \ + adjlist.xml arpack.xml bipartite.xml visitors.xml random.xml \ + separators.xml memory.xml sparsemat.xml hrg.xml \ + scg.xml spatialgames.xml threading.xml progress.xml status.xml \ + graphlets.xml embedding.xml coloring.xml + +DOCFIX = fdl.xml gpl.xml installation.xml introduction.xml \ + tutorial.xml licenses.xml pmt.xml + +DOCFIX2 = $(patsubst %,$(top_srcdir)/doc/%,$(DOCFIX)) + +all: doctemplate + +html: html/stamp + +jekyll: jekyll/stamp + +info: igraph.info + +dvi: igraph-docs.dvi + +pdf: igraph-docs.pdf + +ps: igraph-docs.ps + +doctemplate: $(DOCINCLUDES) + +tags: $(DOCINCLUDES) $(DOCFIX) + cat $(DOCINCLUDES) $(DOCFIX)| grep 'id="[^-"]*">' | sed 's/.*id="\([^"]*\)">.*/\1@@/' | tr '@' '\t' | sort > tags + +EXAMPLES = \ + $(patsubst $(top_srcdir)/%.c,$(top_builddir)/%.c.xml,$(wildcard $(top_srcdir)/examples/simple/*.c)) \ + $(patsubst $(top_srcdir)/%.c,$(top_builddir)/%.c.xml,$(wildcard $(top_srcdir)/examples/tests/*.c)) + +$(top_builddir)/examples/simple/%.c.xml: $(top_srcdir)/examples/simple/%.c tags + if [ ! -d "$(top_builddir)/examples/simple" -a "x$(top_srcdir)" != "x$(top_builddir)" ]; then mkdir -p $(top_builddir)/examples/simple; fi + source-highlight --src-lang c --out-format docbook --input $< --output $@ --gen-references=inline --ctags="" --outlang-def=$(top_srcdir)/doc/docbook.outlang + +$(top_builddir)/examples/tests/%.c.xml: $(top_srcdir)/examples/tests/%.c tags + if [ ! -d "$(top_builddir)/examples/tests" -a "x$(top_srcdir)" != "x$(top_builddir)" ]; then mkdir -p $(top_builddir)/examples/tests; fi + source-highlight --src-lang c --out-format docbook --input $< --output $@ --gen-references=inline --ctags="" --outlang-def=$(top_srcdir)/doc/docbook.outlang + +igraph-docs.xml: $(DOCINCLUDES) $(EXAMPLES) + if [ "x$(top_srcdir)" != "x$(top_builddir)" ]; then cp $(top_srcdir)/doc/igraph-docs.xml . ; fi + touch igraph-docs.xml + +igraph-docs0.xml: igraph-docs.xml + sed "s/@IGRAPH_VERSION@/`cat $(top_srcdir)/IGRAPH_VERSION`/g" $(top_srcdir)/doc/igraph-docs.xml > igraph-docs0.xml + +vector.xml: vector.xxml $(INCLUDEDIR)/igraph_vector_pmt.h $(SRCDIR)/vector.pmt $(SRCDIR)/vector_ptr.c $(SRCDIR)/vector.c $(INCLUDEDIR)/igraph_vector_ptr.h + $(DOXROX) -t $< -e $(REGEX) -o $@ $(INCLUDEDIR)/igraph_vector_pmt.h \ + $(SRCDIR)/vector.pmt $(SRCDIR)/vector_ptr.c $(SRCDIR)/vector.c \ + $(INCLUDEDIR)/igraph_vector_ptr.h + +error.xml: error.xxml $(INCLUDEDIR)/igraph_error.h $(SRCDIR)/igraph_error.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(INCLUDEDIR)/igraph_error.h $(SRCDIR)/igraph_error.c + +matrix.xml: matrix.xxml $(INCLUDEDIR)/igraph_matrix.h $(SRCDIR)/matrix.pmt + $(DOXROX) -t $< -e $(REGEX) -o $@ $(INCLUDEDIR)/igraph_matrix.h $(SRCDIR)/matrix.pmt + +sparsematrix.xml: sparsematrix.xxml $(INCLUDEDIR)/igraph_spmatrix.h $(SRCDIR)/spmatrix.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(INCLUDEDIR)/igraph_spmatrix.h $(SRCDIR)/spmatrix.c + +sparsemat.xml: sparsemat.xxml $(SRCDIR)/sparsemat.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/sparsemat.c + +hrg.xml: hrg.xxml $(SRCDIR)/igraph_hrg.cc $(INCLUDEDIR)/igraph_hrg.h + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/igraph_hrg.cc $(INCLUDEDIR)/igraph_hrg.h + +scg.xml: scg.xxml $(SRCDIR)/scg.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/scg.c + +embedding.xml: embedding.xxml $(SRCDIR)/embedding.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/embedding.c + +basicigraph.xml: basicigraph.xxml $(SRCDIR)/type_indexededgelist.c $(SRCDIR)/structural_properties.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/type_indexededgelist.c $(SRCDIR)/structural_properties.c + +generators.xml: generators.xxml \ + $(SRCDIR)/atlas.c \ + $(SRCDIR)/forestfire.c \ + $(SRCDIR)/games.c \ + $(SRCDIR)/structure_generators.c \ + $(SRCDIR)/structural_properties.c \ + $(SRCDIR)/sbm.c \ + $(SRCDIR)/dotproduct.c \ + $(SRCDIR)/degree_sequence.cpp + $(DOXROX) -t $< -e $(REGEX) -o $@ \ + $(SRCDIR)/atlas.c \ + $(SRCDIR)/forestfire.c \ + $(SRCDIR)/games.c \ + $(SRCDIR)/structure_generators.c \ + $(SRCDIR)/structural_properties.c \ + $(SRCDIR)/sbm.c \ + $(SRCDIR)/dotproduct.c \ + $(SRCDIR)/degree_sequence.cpp + +structural.xml: structural.xxml $(SRCDIR)/structural_properties.c \ + $(SRCDIR)/spanning_trees.c \ + $(SRCDIR)/conversion.c $(SRCDIR)/basic_query.c \ + $(SRCDIR)/cocitation.c $(SRCDIR)/components.c \ + $(SRCDIR)/spectral_properties.c $(SRCDIR)/cores.c \ + $(SRCDIR)/centrality.c $(SRCDIR)/decomposition.c \ + $(SRCDIR)/mixing.c $(INCLUDEDIR)/igraph_arpack.h \ + $(SRCDIR)/distances.c $(SRCDIR)/feedback_arc_set.c \ + $(SRCDIR)/matching.c $(SRCDIR)/triangles.c \ + $(SRCDIR)/paths.c $(INCLUDEDIR)/igraph_centrality.h + $(DOXROX) -t $< -e $(REGEX) -o $@ \ + $(SRCDIR)/structural_properties.c $(SRCDIR)/spanning_trees.c \ + $(SRCDIR)/conversion.c $(SRCDIR)/basic_query.c $(SRCDIR)/cocitation.c \ + $(SRCDIR)/components.c $(SRCDIR)/spectral_properties.c $(SRCDIR)/cores.c \ + $(SRCDIR)/centrality.c $(SRCDIR)/decomposition.c $(SRCDIR)/mixing.c \ + $(INCLUDEDIR)/igraph_arpack.h $(SRCDIR)/distances.c \ + $(SRCDIR)/feedback_arc_set.c $(SRCDIR)/matching.c $(SRCDIR)/triangles.c \ + $(SRCDIR)/paths.c $(INCLUDEDIR)/igraph_centrality.h \ + $(SRCDIR)/scan.c + + +iterators.xml: iterators.xxml $(SRCDIR)/iterators.c $(INCLUDEDIR)/igraph_iterators.h + $(DOXROX) -c -t $< -e $(REGEX) -o $@ $(SRCDIR)/iterators.c \ + $(INCLUDEDIR)/igraph_iterators.h + +attributes.xml: attributes.xxml $(SRCDIR)/attributes.c $(INCLUDEDIR)/igraph_attributes.h $(SRCDIR)/cattributes.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/attributes.c \ + $(INCLUDEDIR)/igraph_attributes.h $(SRCDIR)/cattributes.c + +layout.xml: layout.xxml $(SRCDIR)/layout.c $(INCLUDEDIR)/igraph_layout.h $(SRCDIR)/drl_layout.cpp $(SRCDIR)/drl_layout_3d.cpp $(SRCDIR)/sugiyama.c $(SRCDIR)/layout_fr.c $(SRCDIR)/layout_kk.c $(SRCDIR)/layout_gem.c $(SRCDIR)/layout_dh.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/layout.c $(INCLUDEDIR)/igraph_layout.h $(SRCDIR)/drl_layout.cpp $(SRCDIR)/drl_layout_3d.cpp $(SRCDIR)/sugiyama.c $(SRCDIR)/layout_fr.c $(SRCDIR)/layout_kk.c $(SRCDIR)/layout_gem.c $(SRCDIR)/layout_dh.c + +foreign.xml: foreign.xxml $(SRCDIR)/foreign.c $(SRCDIR)/foreign-graphml.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/foreign.c \ + $(SRCDIR)/foreign-graphml.c + +nongraph.xml: nongraph.xxml $(SRCDIR)/other.c $(SRCDIR)/random.c $(SRCDIR)/version.c $(INCLUDEDIR)/igraph_nongraph.h + $(DOXROX) -t $< -e $(REGEX) -o $@ $(INCLUDEDIR)/igraph_nongraph.h $(SRCDIR)/other.c $(SRCDIR)/random.c $(SRCDIR)/version.c $(SRCDIR)/dotproduct.c + +isomorphism.xml: isomorphism.xxml $(SRCDIR)/topology.c $(INCLUDEDIR)/igraph_topology.h $(SRCDIR)/bliss.cc $(SRCDIR)/lad.c + $(DOXROX) -c -t $< -e $(REGEX) -o $@ $(SRCDIR)/topology.c $(INCLUDEDIR)/igraph_topology.h $(SRCDIR)/bliss.cc $(SRCDIR)/lad.c + +coloring.xml: coloring.xxml $(SRCDIR)/coloring.c $(INCLUDEDIR)/igraph_coloring.h + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/coloring.c $(INCLUDEDIR)/igraph_coloring.h + +motifs.xml: motifs.xxml $(INCLUDEDIR)/igraph_motifs.h $(SRCDIR)/motifs.c \ + $(SRCDIR)/triangles.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(INCLUDEDIR)/igraph_motifs.h \ + $(SRCDIR)/motifs.c $(SRCDIR)/triangles.c + +operators.xml: operators.xxml $(SRCDIR)/operators.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/operators.c + +flows.xml: flows.xxml $(SRCDIR)/flow.c $(SRCDIR)/st-cuts.c $(SRCDIR)/cohesive_blocks.c $(INCLUDEDIR)/igraph_flow.h + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/flow.c $(SRCDIR)/st-cuts.c $(SRCDIR)/cohesive_blocks.c $(INCLUDEDIR)/igraph_flow.h + +community.xml: community.xxml $(SRCDIR)/community.c $(SRCDIR)/clustertool.cpp $(SRCDIR)/walktrap.cpp $(SRCDIR)/community_leiden.c $(SRCDIR)/fast_community.c $(SRCDIR)/optimal_modularity.c $(INCLUDEDIR)/igraph_community.h $(SRCDIR)/infomap.cc + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/community.c $(SRCDIR)/clustertool.cpp $(SRCDIR)/walktrap.cpp $(SRCDIR)/community_leiden.c $(SRCDIR)/fast_community.c $(SRCDIR)/optimal_modularity.c $(INCLUDEDIR)/igraph_community.h $(SRCDIR)/infomap.cc + +cliques.xml: cliques.xxml $(SRCDIR)/cliques.c $(SRCDIR)/maximal_cliques.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/cliques.c $(SRCDIR)/maximal_cliques.c $(INCLUDEDIR)/igraph_cliques.h + +stack.xml: stack.xxml $(SRCDIR)/stack.pmt + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/stack.pmt + +dqueue.xml: dqueue.xxml $(SRCDIR)/dqueue.pmt + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/dqueue.pmt + +heap.xml: heap.xxml $(SRCDIR)/heap.pmt + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/heap.pmt + +strvector.xml: strvector.xxml $(SRCDIR)/igraph_strvector.c $(INCLUDEDIR)/igraph_strvector.h + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/igraph_strvector.c $(INCLUDEDIR)/igraph_strvector.h + +adjlist.xml: adjlist.xxml $(SRCDIR)/adjlist.c $(INCLUDEDIR)/igraph_adjlist.h + $(DOXROX) -c -t $< -e $(REGEX) -o $@ $(SRCDIR)/adjlist.c $(INCLUDEDIR)/igraph_adjlist.h + +arpack.xml: arpack.xxml $(INCLUDEDIR)/igraph_arpack.h $(SRCDIR)/arpack.c $(INCLUDEDIR)/igraph_blas.h $(SRCDIR)/blas.c $(INCLUDEDIR)/igraph_lapack.h $(SRCDIR)/lapack.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(INCLUDEDIR)/igraph_arpack.h $(SRCDIR)/arpack.c $(INCLUDEDIR)/igraph_blas.h $(SRCDIR)/blas.c $(INCLUDEDIR)/igraph_lapack.h $(SRCDIR)/lapack.c + +bipartite.xml: bipartite.xxml $(SRCDIR)/bipartite.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/bipartite.c + +visitors.xml: visitors.xxml $(SRCDIR)/visitors.c $(SRCDIR)/random_walk.c $(INCLUDEDIR)/igraph_visitor.h + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/visitors.c $(SRCDIR)/random_walk.c $(INCLUDEDIR)/igraph_visitor.h + +random.xml: random.xxml $(SRCDIR)/random.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/random.c + +separators.xml: separators.xxml $(SRCDIR)/separators.c $(SRCDIR)/st-cuts.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/separators.c \ + $(SRCDIR)/st-cuts.c + +memory.xml: memory.xxml $(SRCDIR)/memory.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/memory.c + +spatialgames.xml: spatialgames.xxml \ + $(SRCDIR)/microscopic_update.c $(SRCDIR)/sir.c $(INCLUDEDIR)/igraph_epidemics.h + $(DOXROX) -t $< -e $(REGEX) -o $@ \ + $(SRCDIR)/microscopic_update.c $(SRCDIR)/sir.c $(INCLUDEDIR)/igraph_epidemics.h + +threading.xml: threading.xxml $(BUILD_INCLUDEDIR)/igraph_threading.h + $(DOXROX) -t $< -e $(REGEX) -o $@ $(BUILD_INCLUDEDIR)/igraph_threading.h + +progress.xml: progress.xxml $(INCLUDEDIR)/igraph_progress.h $(SRCDIR)/progress.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(INCLUDEDIR)/igraph_progress.h $(SRCDIR)/progress.c + +status.xml: status.xxml $(INCLUDEDIR)/igraph_statusbar.h $(SRCDIR)/statusbar.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(INCLUDEDIR)/igraph_statusbar.h $(SRCDIR)/statusbar.c + +graphlets.xml: graphlets.xxml $(SRCDIR)/glet.c + $(DOXROX) -t $< -e $(REGEX) -o $@ $(SRCDIR)/glet.c + +html/stamp: igraph-docs0.xml $(DOCFIX2) gtk-doc.xsl + if [ "x$(top_srcdir)" != "x$(top_builddir)" ]; then cp $(DOCFIX2) . ; fi && \ + xmlto -x $(top_srcdir)/doc/gtk-doc.xsl -o html xhtml igraph-docs0.xml \ + && touch html/stamp + +jekyll/stamp: html/stamp + rm -rf jekyll && mkdir jekyll && cp html/* jekyll/ && rm jekyll/stamp + cd html && for i in *.html; do \ + cat $$i | ../../tools/extract_body.sh \ + | ../../tools/protect_braces.sh \ + | ../../tools/jekyll_header.sh \ + > ../jekyll/$$i; done + touch jekyll/stamp + +igraph.info0.diff: igraph.info.diff + sed "s/@IGRAPH_VERSION@/`cat $(top_srcdir)/IGRAPH_VERSION`/g" $(top_srcdir)/doc/igraph.info.diff > igraph.info0.diff + +igraph.info: igraph-docs0.xml $(DOCFIX2) igraph.info0.diff + if [ "x$(top_srcdir)" != "x$(top_builddir)" ]; then cp $(DOCFIX2) . ; fi && \ + cat tutorial.xml | sed '/]*>//g;}' | \ + sed 's/<\/link>//g' >tutorial-info.xml \ + && sed 's/tutorial\.xml/tutorial-info.xml/' igraph-docs0.xml > igraph-docs-info.xml \ + && xmllint --xinclude -o igraph-docs-info2.xml igraph-docs-info.xml \ + && $(REMOVEEXAMPLES) igraph-docs-info2.xml igraph-docs-info3.xml \ + && sed '/igraph-docs-info4.xml\ + && cat igraph-docs-info3.xml >> igraph-docs-info4.xml \ + && db2x_xsltproc --xinclude -s texi igraph-docs-info4.xml -o igraph.txml \ + && db2x_texixml --encoding=utf8 igraph.txml \ + && mv igraph_reference_manual.texi igraph.texi \ + && makeinfo --no-split --number-sections igraph.texi \ + && mv igraph_reference_manual.info igraph.info \ + && patch < igraph.info0.diff + +igraph-docs.dvi: igraph-docs0.xml $(DOCFIX2) + if [ "x$(top_srcdir)" != "x$(top_builddir)" ]; then cp $(DOCFIX2) . ; fi && \ + xmllint --xinclude -o igraph-docs2.xml igraph-docs0.xml \ + && $(REMOVEEXAMPLES) igraph-docs2.xml igraph-docs3.xml \ + && sed '/igraph-docs4.xml\ + && cat igraph-docs3.xml >> igraph-docs4.xml \ + && env < /dev/null hash_extra=650000 docbook2dvi igraph-docs4.xml \ + && mv igraph-docs4.dvi igraph-docs.dvi + +igraph-docs.ps: igraph-docs0.xml $(DOCFIX2) + if [ "x$(top_srcdir)" != "x$(top_builddir)" ]; then cp $(DOCFIX2) . ; fi && \ + xmllint --xinclude -o igraph-docs2.xml igraph-docs0.xml \ + && $(REMOVEEXAMPLES) igraph-docs2.xml igraph-docs3.xml \ + && sed '/igraph-docs4.xml\ + && cat igraph-docs3.xml >> igraph-docs4.xml \ + && env < /dev/null hash_extra=65000 docbook2s igraph-docs4.xml \ + && mv igraph-docs4.ps igraph-docs.ps + +igraph-docs.pdf: igraph-docs0.xml $(DOCFIX2) + if [ "x$(top_srcdir)" != "x$(top_builddir)" ]; then cp $(DOCFIX2) . ; fi && \ + xmllint --xinclude -o igraph-docs2.xml igraph-docs0.xml \ + && $(REMOVEEXAMPLES) igraph-docs2.xml igraph-docs3.xml \ + && sed '/igraph-docs4.xml\ + && cat igraph-docs3.xml >> igraph-docs4.xml \ + && xsltproc http://docbook.sourceforge.net/release/xsl/current/fo/docbook.xsl \ + igraph-docs4.xml > igraph-docs.fo \ + && fop -fo igraph-docs.fo -pdf igraph-docs.pdf + +CLEANFILES=$(DOCINCLUDES) html/*.html html/stamp \ + igraph-docs.{dvi,info,pdf,ps,texi,txml} igraph-docs2.xml igraph-docs0.xml diff --git a/doc/abstracts/wien08.txt b/doc/abstracts/wien08.txt new file mode 100644 index 0000000..85cba37 --- /dev/null +++ b/doc/abstracts/wien08.txt @@ -0,0 +1,24 @@ + +Practical statistical network analysis + +The igraph R package provides a platform for developing +graph algorithms. As many (classic and recent) algorithms are +already included in igraph, it is also handy in +exploratory network analyis. igraph has a very simple and +fast graph representation, this allows handling of huge +graphs, with millions of vertices and edges. + +In this lecture, I first introduce igraph's data model, together +with the basic concepts of graph theory. Then, I will show some +examples for + - centrality measures, + - community structure detection algorithms, + - cohesive blocks, +and how they can be calculated with igraph. +Several examples will be shown for + - creating and importing graphs from collected data or from + other formats, + - graph visualization, + - rapid prototyping of graph algorithms. + +Basic calculus, statistics and R knowledge is expected. diff --git a/doc/adjlist.xxml b/doc/adjlist.xxml new file mode 100644 index 0000000..bd31ef6 --- /dev/null +++ b/doc/adjlist.xxml @@ -0,0 +1,53 @@ + + +]> + +
+Adjacency lists + + + +
Adjacent vertices + + + + + + + + +
+ +
Incident edges + + + + +
+ +
Lazy adjacency list for vertices + + + + +
+ +
Lazy incidence list for edges + + + + +
+ +
Deprecated functions + + + + + + +
+ +
\ No newline at end of file diff --git a/doc/arpack.xxml b/doc/arpack.xxml new file mode 100644 index 0000000..ec47656 --- /dev/null +++ b/doc/arpack.xxml @@ -0,0 +1,51 @@ + + + +]> + + +Using BLAS, LAPACK and ARPACK for igraph matrices and graphs + +
+ + + +
+ +
+ +
Matrix factorization, solving linear systems + + + +
+
Eigenvalues and eigenvectors of matrices + + + +
+
+ +
+ +
Data structures + + + + + + +
+ +
ARPACK solvers + + + +
+ +
+ +
diff --git a/doc/attributes.xxml b/doc/attributes.xxml new file mode 100644 index 0000000..3ee76f8 --- /dev/null +++ b/doc/attributes.xxml @@ -0,0 +1,110 @@ + + +]> + + +Graph, Vertex and Edge Attributes + + + +
+The Attribute Handler Interface + + + + +
+ +
+Accessing attributes from C + + + +
Query attributes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+Set attributes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
Remove attributes + + + + + + + + + + + +
+ +
+ +
diff --git a/doc/basicigraph.xxml b/doc/basicigraph.xxml new file mode 100644 index 0000000..99c24dd --- /dev/null +++ b/doc/basicigraph.xxml @@ -0,0 +1,125 @@ + + +]> + + +About &igraph; graphs, the basic interface + +
The &igraph; data model + +The &igraph; library can handle directed and +undirected graphs. The &igraph; graphs are multisets +of ordered (if directed) or unordered (if undirected) labeled pairs. +The labels of the pairs plus the number of vertices always starts with +zero and ends with the number of edges minus one. In addition to that +a table of metadata is also attached to every graph, its most +important entries are the number of vertices in the graph and whether +the graph is directed or undirected. + + + +Like the edges, the &igraph; vertices are also +labeled by numbers between zero and the number of vertices minus one. +So, to summarize, a directed graph can be imagined like this: + + + ( vertices: 6, + directed: yes, + { + (0,2), + (2,2), + (2,3), + (3,3), + (3,4), + (3,4), + (4,1) + } + ) + + +Here the edges are ordered pairs or vertex ids, and the graph is a multiset +of edges plus some meta-data. + + + +An undirected graph is like this: + + + ( vertices: 6, + directed: no, + { + {0,2}, + {2}, + {2,3}, + {3}, + {3,4}, + {3,4}, + {4,1} + } + ) + + +Here an edge is a set of one or two vertex ids, two for most of the +time, except for loop edges. A graph is a multiset of edges plus meta data, +just like in the directed case. + + +It is possible to convert a directed graph to an undirected one, +see the +igraph_to_directed() +and +igraph_to_undirected() functions. + + +Note that &igraph; has some limited support for +graphs with multiple edges. The support means that multiple edges can +be stored in &igraph; graphs, but for most functions +(like +igraph_betweenness()) it is not checked +that they work well on graphs with multiple edges. +To eliminate multiple edges from a graph, you can use + + igraph_simplify(). + +
+ +
The basic interface + + +
Graph Constructors and Destructors + + + + +
+ +
Basic Query Operations + + + + + + + + + + +
+ +
Adding and Deleting Vertices and Edges + + + + + +
+ +
Deprecated functions + +
+ +
+ +
diff --git a/doc/bibdatabase.xml b/doc/bibdatabase.xml new file mode 100644 index 0000000..b782901 --- /dev/null +++ b/doc/bibdatabase.xml @@ -0,0 +1,51 @@ + + + + + + + + Albert-László + Barabási + RékaAlbert + + Emergence of scaling in random networks + Science + 1999 + 286 + 509-512 + + + + + LászlóZalányi + GáborCsárdi + TamásKiss + MátéLengyel + RebeccaWarner + JanTobochnik + PéterÉrdi + + Properties of a random attachment growing network + Phyisical Review E + 2003 + 68 + 066104 + + + + + L. R.Ford Jr. + D. R.Fulkerson + + Maximal ow through a network + Canadian J. Math. + 1956 + 8 + 399--404 + + + + diff --git a/doc/bipartite.xxml b/doc/bipartite.xxml new file mode 100644 index 0000000..2e39b5e --- /dev/null +++ b/doc/bipartite.xxml @@ -0,0 +1,34 @@ + + +]> + + +Bipartite, i.e. two-mode graphs + +
+ +
+ +
Create two-mode networks + + + +
+ +
Incidence matrices + + +
+ +
Project a two-mode graphs + + +
+ +
Other operations on bipartite graphs + +
+ +
diff --git a/doc/c-docbook.re b/doc/c-docbook.re new file mode 100644 index 0000000..26037c8 --- /dev/null +++ b/doc/c-docbook.re @@ -0,0 +1,655 @@ +REPLACE ----- remove the " * " prefix first -----------------*- mode:python -*- +^[ ]\*[ ] +WITH -------------------------------------------------------------------------- +REPLACE ----- remove the " *" lines ------------------------------------------- +^[ ]\*\s*\n +WITH -------------------------------------------------------------------------- +\n +REPLACE ----- for the template functions -------------------------------------- + +FUNCTION\( +(?P[^, \)]*)\s*,\s* +(?P[^\)]*) +\)\s* + +WITH + +\g_\g + +REPLACE ----- template type --------------------------------------------------- + +TYPE\( +(?P[^\)]*) +\) + +WITH + +\g_t + +REPLACE ----- template base type, we cowardly assume real number -------------- + +BASE + +WITH + +igraph_real_t + +REPLACE ----- function object, extract its signature -------------------------- + +(?P\A.*?) # head of the comment +\\function\s+ # \function keyword +(?P(?P
(igraph_)|(IGRAPH_)|())(?P\w+)) # the keyword, remove igraph_ prefix
+[\s]*(?P[^\n]*?)\n        # brief description
+(?P.*?)\*\/               # tail of the comment
+\s*
+(DECLDIR )?                      # strip DECLDIR from prototype
+(?P.*?\))                   # function head
+(?=(\s*;)|(\s*\{))               # prototype ends with ; function head with {
+.*\Z                             # and the remainder
+
+WITH --------------------------------------------------------------------------
+
+
+<function>\g<name></function> — \g<brief> +\g + + +\g; + + + +\g +\g + +
+ +REPLACE ----- for functions (not used currently) ------------------- + +(?P[^<]*)\n + +RUN --------------------------------------------------------------------------- + +if matched != None: + dr_params=string.split(matched.group("params"), ',') + dr_out="" + for dr_i in dr_params: + dr_i=string.strip(dr_i) + if dr_i=="...": + dr_out=dr_out+"" + else: + dr_words=re.match(r"([\w\*\&\s]+)(\b\w+)$", dr_i).groups() + dr_out=dr_out+""+dr_words[0]+""+dr_words[1]+ \ + "\n" + actch=actch[0:matched.start()]+dr_out+actch[matched.end():] + +REPLACE ----- function parameter descriptions, head --------------------------- + +(?P\A.*?) # head of the comment +\\param\b # first \param commant + +WITH -------------------------------------------------------------------------- + +\g +Arguments: + +\param + +REPLACE ----- function parameter descriptions, tail --------------------------- + +# the end of the params is either an empty line after the last \param +# command or a \return or \sa statement (others might be added later) +# or the end of the comment + +\\param\b # the last \param command +(?P.*?) # the text of the \param command +(?P # this marks the end of the \param text + (\\return\b)|(\\sa\b)| # it is either a \return or \sa or + (\n\s*?\n)| # (at least) one empty line or + (\*\/)) # the end of the comment +(?P.*?\Z) # remaining part + +WITH + +\param\g +\g\g + +REPLACE ----- function parameter descriptions --------------------------------- + +\\param\b\s* # \param command +(?P(\w+)|(...))\s+ # name of the parameter +(?P.*?) # text of the \param command +(?=(\\param)|()| + (\n\s*\n)) + + +WITH -------------------------------------------------------------------------- + + \g: + + \g + +REPLACE ----- \return command ------------------------------------------------- + +# a return statement ends with an empty line or the end of the comment +\\return\b\s* # \return command +(?P.*?) # the text +(?=(\n\s*?\n)| # empty line or + (\*\/)| # the end of the comment or + (\\sa\b)) # \sa command + +WITH ----------------------------------------------------------------------TODO + +Returns: + + + \g + + + +REPLACE ----- variables ------------------------------------------------------- + +(?P\A.*?) # head of the comment +\\var\s+ # \var keyword + argument +(?P(?P
(igraph_)|(IGRAPH_)|())(?P\w+))
+[\s]*(?P[^\n]*?)\n         # brief description
+(?P.*?)\*\/                # tail of the comment
+\s*(?P[^;]*;)                # the definition of the variable
+.*\Z                              # and the remainder
+
+WITH --------------------------------------------------------------------------
+
+
<function>\g<name></function> — \g<brief> +\g + + +\g + + +\g\g + +
+ +REPLACE ----- \define --------------------------------------------------------- + +(?P\A.*?) # head of the comment +\\define\s+ # \define command +(?P(?P
(igraph_)|(IGRAPH_)|())(?P\w+))
+[\s]*(?P[^\n]*?)\n         # brief description
+(?P.*?)\*\/                # tail of the comment
+\s*                               # whitespace
+(?P\#define\s+[\w0-9,()]+)           # macro
+.*\Z                              # drop the remainder
+
+WITH --------------------------------------------------------------------------
+
+
<function>\g<name></function> — \g<brief> +\g + + +\g + + +\g\g + +
+ +REPLACE ----- \section without title ------------------------------------------ + +(?P\A.*?) # head of the comment +\\section\s+(?P\w+)\s*$ # \section + argument +(?P.*?)\*\/ # tail of the comment +.*\Z # and the remainder, this is dropped + +WITH + +\g +\g + +REPLACE ----- \section with title --------------------------------------------- + +(?P\A.*?) # head of the comment +\\section\s+(?P\w+) # \section + argument +(?P.*?) # section title +\n\s*?\n # empty line +(?P<after>.*?)\*\/ # tail of the comment +.*\Z # and the remainder, this is dropped + +WITH + +<title>\g<title> +\g +\g + +REPLACE ----- \section with title --------------------------------------------- + +(?P\A.*?) # head of the comment +\\section\s+(?P\w+) # \section + argument +(?P.*?)\s*\*\/ # section title +.*\Z # and the remainder, this is dropped + +WITH + +<title>\g<title> +\g + +REPLACE ----- an enumeration typedef ------------------------------------------ + +(?P\A.*?) # head of the comment +\\typedef\s+ # \typedef command +(?P(?P
(igraph_)|(IGRAPH_)|())(?P\w+))
+[\s]*(?P[^\n]*?)\n         # brief description
+(?P.*?)                    # tail of the comment
+ \*\/\s*                          # closing the comment
+(?Ptypedef\s*enum\s*\{       # typedef enum
+ [^\}]*\}\s*\w+\s*;)                  # rest of the definition
+.*\Z
+
+WITH --------------------------------------------------------------------------
+
+
<function>\g<name></function> — \g<brief> +\g + + +\g + + + +\g\g + +
+ +REPLACE ----- enumeration value descriptions, head ---------------------------- + +(?P\A.*?) # head of the comment +\\enumval\b # first \param commant + +WITH -------------------------------------------------------------------------- + +\g +Values: + +\enumval + +REPLACE ----- enumeration value descriptions, tail ---------------------------- + +\\enumval\b # the last \enumval command +(?P.*?) # the text of the \enumval command +(?P # this marks the end of the \enumval text + (\\return\b)|(\\sa\b)| # it is either a \return or \sa or + (\n\s*?\n)| # (at least) one empty line or + (\*\/)) # the end of the comment +(?P.*?\Z) # remaining part + +WITH + +\enumval\g +\g\g + +REPLACE ----- enumeration value descriptions ---------------------------------- + +\\enumval\b\s* # \enumval command +(?P(\w+)|(...))\s+ # name of the parameter +(?P.*?) # text of the \enumval command +(?=(\\enumval)|()| + (\n\s*\n)) + +WITH -------------------------------------------------------------------------- + + \g: + + \g + +REPLACE ----- \struct --------------------------------------------------------- + +(?P\A.*?) # head of the comment +\\struct\s+ # \struct command +(?P(?P
(igraph_)|(IGRAPH_)|())(?P[\w_]+))
+[\s]*(?P[^\n]*?)(?=\n)     # brief description
+(?P.*?)                    # tail of the command
+\*\/\s*                           # closing the comment
+(?Ptypedef \s*struct\s*\w+\s*\{
+ .*\}\s*\w+\s*;)
+.*\Z
+
+WITH --------------------------------------------------------------------------
+
+
<function>\g<name></function> — \g<brief> +\g + + +\g + + + +\g\g + +
+ +REPLACE ----- structure member descriptions, one block ------------------------ + +^[\s]*\n +(?P.*?) # empty line+text +(?P\\member\b.*?) # member commands +(?= # this marks the end of the \member text + (\\return\b)|(\\sa\b)| # it is either a \return or \sa or + (^[\s]*\n)| # (at least) one empty line or + (\*\/)) # the end of the comment + +WITH -------------------------------------------------------------------------- + + +\g +Values: + +\g + + +REPLACE ----- structure member descriptions ----------------------------------- + +\\member\b\s* # \enumval command +(?P(\w+)|(...))\s+ # name of the parameter +(?P.*?) # text of the \enumval command +(?=(\\member)|()| + (\n\s*\n)) + +WITH -------------------------------------------------------------------------- + + \g: + + \g + +REPLACE ----- \typedef function ----------------------------------------------- + +(?P.*?) # comment head +\\typedef\s+ # \typedef command +(?P(?P
(igraph_)|(IGRAPH_)|())(?P\w+))
+[\s]*(?P[^\n]*?)\n         # brief description
+(?P.*?)                    # comment tail
+\*\/                              # end of comment block
+\s*
+(?Ptypedef\s+[^;]*;)        # the typedef definition
+.*\Z
+
+WITH --------------------------------------------------------------------------
+
+
<function>\g<name></function> — \g<brief> +\g + +\g + + +\g\g + +
+ +REPLACE ----- ignore doxygen \ingroup command --------------------------------- + +\\ingroup\s+\w+ + +WITH -------------------------------------------------------------------------- + +REPLACE ----- ignore doxygen \defgroup command -------------------------------- + +\\defgroup\s+\w+ + +WITH -------------------------------------------------------------------------- + +REPLACE ----- add the contents of \brief to the description ------------------- + +\\brief\b + +WITH -------------------------------------------------------------------------- + +REPLACE ----- \varname command ------------------------------------------------ + +\\varname\b\s* +(?P\w+\b) + +WITH + +\g + +REPLACE ----- references, \ref command ---------------------------------------- + +\\ref\b\s* +(?P\w+)(?P([\(][\)])?) + +WITH -------------------------------------------------------------------------- + +\g\g + +REPLACE ----- \sa command ----------------------------------------------------- + +\\sa\b +\s* +(?P.*?) +(?=(\n\s*?\n)|(\*\/)) + +WITH ----------------------------------------------------------------------TODO + +See also: + + + \g + + + +REPLACE ----- \em command ----------------------------------------------------- + +\\em\b +\s* +(?P[^\s]+) + +WITH + +\g + +REPLACE ----- \emb command ---------------------------------------------------- + +\\emb\b + +WITH + + + +REPLACE ----- \eme command ---------------------------------------------------- + +\\eme\b + +WITH + + + +REPLACE ----- \verbatim ------------------------------------------------------- + +\\verbatim\b + +WITH + + + +REPLACE ----- \endverbatim ---------------------------------------------------- + +\\endverbatim\b + +WITH + + + +REPLACE ----- \clist ---------------------------------------------------------- + +\\clist\b + +WITH + + + +REPLACE ----- \cli ------------------------------------------------------------ + +\\cli\s+(?P.*?)$ +(?P.*?) +(?=(\\cli)|(\\endclist)) + +WITH -------------------------------------------------------------------------- + +\g + +\g + + +REPLACE ----- \endclist ------------------------------------------------------- + +\\endclist\b + +WITH + + + +REPLACE ----- \olist ---------------------------------------------------------- + +\\olist\b + +WITH + + + +REPLACE ----- \oli ------------------------------------------------------------ + +\\oli\s+(?P.*?) +(?=(\\oli)|(\\endolist)) + +WITH + + +\g + + +REPLACE ----- \endolist ------------------------------------------------------- + +\\endolist\b + +WITH + + + +REPLACE ----- \ilist ---------------------------------------------------------- + +\\ilist\b + +WITH + + + +REPLACE ----- \ili ------------------------------------------------------------ + +\\ili\s+(?P.*?) +(?=(\\ili)|(\\endilist)) + +WITH + + +\g + + +REPLACE ----- \endilist ------------------------------------------------------- + +\\endilist\b + +WITH + + + +REPLACE ----- doxygen \c command is for ---------------------------- + +\\c\s+(?P[\w\-^\']+)\b + +WITH + +\g + +REPLACE ----- doxygen \p command is for --------------------------- + +\\p\s+(?P\w+)\b + +WITH + +\g + +REPLACE ----- doxygen \type command is for ----------------------------- + +\\type\s+(?P\w+)\b + +WITH + +\g + +REPLACE ----- doxygen \a command is for ----------------------------- + +\\a\s+(?P\w+)\b + +WITH + +\g + +REPLACE ----- doxygen \quote command is for --------------------------- + +\\quote\s+ + +WITH + + + +REPLACE ----- doxygen \endquote command is for ----------------------- + +\s*\\endquote\b + +WITH + + + +REPLACE ----- replace with ----------------------------------- + +<(?P/?)code> + +WITH -------------------------------------------------------------------------- + +<\gliteral> + +REPLACE ----- add http:// and https:// links ---------------------------------- + +(?Phttps?:\/\/.*?) +(?=(\s)|\)) + +WITH -------------------------------------------------------------------------- + +\g + +REPLACE ----- blockquote ------------------------------------------------------ + +\\blockquote + +WITH -------------------------------------------------------------------------- + +
+ +REPLACE ----- blockquote ------------------------------------------------------ + +\\endblockquote + +WITH -------------------------------------------------------------------------- + +
+ +REPLACE ----- example file --------------------------------------------------- + +\\example\b\s* +(?P[^\n]*?)\n + +WITH -------------------------------------------------------------------------- + + + File <code>\g<filename></code> + + + diff --git a/doc/cliques.xxml b/doc/cliques.xxml new file mode 100644 index 0000000..2ca18d4 --- /dev/null +++ b/doc/cliques.xxml @@ -0,0 +1,40 @@ + +]> + + +Cliques and Independent Vertex Sets + + +These functions calculate various graph properties related +to cliques and independent vertex sets. + + +
Cliques + + + + + + + + + + +
+ +
Weighted cliques + + + +
+ +
Independent Vertex Sets + + + + +
+ +
diff --git a/doc/coloring.xxml b/doc/coloring.xxml new file mode 100644 index 0000000..82f0f84 --- /dev/null +++ b/doc/coloring.xxml @@ -0,0 +1,13 @@ + + +]> + + +Graph Coloring + + + + + diff --git a/doc/community.xxml b/doc/community.xxml new file mode 100644 index 0000000..a88283d --- /dev/null +++ b/doc/community.xxml @@ -0,0 +1,59 @@ + + +]> + + +Detecting Community Structure + +
Common functions related to community structure + + + + + + + +
+ +
Community structure based on statistical mechanics + + +
+ +
Community structure based on eigenvectors of matrices + + + + +
+ +
Walktrap: community structure based on random walks + +
+ +
Edge betweenness based community detection + + +
+ +
Community structure based on the optimization of modularity + + + +
+ +
Fluid Communities + +
+ +
Label propagation + +
+ +
The InfoMAP algorithm + +
+ +
diff --git a/doc/devhelp.xsl b/doc/devhelp.xsl new file mode 100644 index 0000000..919290f --- /dev/null +++ b/doc/devhelp.xsl @@ -0,0 +1,131 @@ + + + + + + + + + + + + book + + + .devhelp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + diff --git a/doc/docbook.outlang b/doc/docbook.outlang new file mode 100644 index 0000000..44e14af --- /dev/null +++ b/doc/docbook.outlang @@ -0,0 +1,36 @@ +# by Stuart Rackham +# http://www.methods.co.nz/asciidoc/source-highlight-filter.html + +extension "xml" + +bold "$text" +italics "$text" + +anchor "$text" +postline_reference "$text -> $linenum" +postdoc_reference "$text -> $linenum" +reference "$text" + +doctemplate +" +
+ +$title + +" +" +
+" +end + +nodoctemplate +"" +" +" +end + +translations +"&" "&" +"<" "<" +">" ">" +end diff --git a/doc/doxrox.py b/doc/doxrox.py new file mode 100755 index 0000000..a010d4f --- /dev/null +++ b/doc/doxrox.py @@ -0,0 +1,271 @@ +#! /usr/bin/env python + +# IGraph R package +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA +# +################################################################### + +import sys +import getopt +import re +import string + +################# +# constants, these might turn to parameters some time +################# +doxhead='\/\*\*' + +################# +# global variables +################# +verbose=False +cutit=False + +######################################################################### +# The main function +######################################################################### + +def main(): + + global verbose, cutit + + # get command line arguments + try: + optlist, args = getopt.getopt(sys.argv[1:], 't:e:o:hvc', ['help']) + except getopt.GetoptError: + # print help information and exit: + usage() + sys.exit(2) + + # handle command line arguments + templatefile=regexfile=outputfile="" + verbose=False + + for o, a in optlist: + if o in ("-h", "--help"): + usage() + sys.exit() + if o == "-t": + templatefile = a + if o == "-e": + regexfile = a + if o == "-o": + outputfile = a + if o == "-v": + verbose = True + if o == "-c": + cutit = True + + if templatefile == "" or regexfile == "" or outputfile == "": + print("Error, some special file is not given") + usage() + sys.exit(2) + + if templatefile in args or regexfile in args or outputfile in args: + print("Error, special file is also used as an input file") + usage() + sys.exit(2) + + if templatefile == regexfile or templatefile == outputfile or \ + regexfile == outputfile: + print('Error, some special files are the same') + usage() + sys.exit(2) + + # get all regular expressions + if verbose: + print 'Reading regular expressions...', + regexlist=readregex(regexfile) + if verbose: + print("done, "+str(len(regexlist))+" rules read.") + + # parse all input files and extract chunks, apply rules + docchunks=dict() + for ifile in args: + if verbose: + print 'Parsing input file '+ifile+'...', + try: + f=open(ifile, 'r') + strinput=f.read() + f.close() + except IOError: + print("Error reading input file: "+ifile) + sys.exit(3) + parsestring(strinput, regexlist, docchunks) + if verbose: + print('done, '+str(len(docchunks))+" chunks read.") + + # substitute the template file + try: + if verbose: + print "Reading template file...", + tfile=open(templatefile, 'r') + tstring=tfile.read() + tfile.close() + if verbose: + print('done.') + except IOError: + print("Error reading the template file: "+templatefile) + sys.exit(7) + if verbose: + print "Substituting template file...", + chunkit=re.finditer(r'', tstring) + outstring="" + last=0 + for chunk in chunkit: + outstring=outstring+tstring[last:chunk.start()]+\ + docchunks[chunk.group(1)] + last=chunk.end() + outstring=outstring+tstring[last:] + if verbose: + print "done." + + # write output file + try: + if verbose: + print "Writing output file...", + ofile=open(outputfile, 'w') + ofile.write(outstring) + ofile.close() + except IOError: + print("Error writing output file:"+outputfile) + sysexit(8) + if verbose: + print "done." + +######################################################################### +# End of the main function +######################################################################### + +################# +# read the regular expressions +################# +def readregex(regexfile): + lines=[] + mode="empty" + actreplace="" + actwith="" + acttype="" + lineno=1 + try: + f=open(regexfile, "r") + for line in f: + # a new pattern block starts + if line[0:7]=="REPLACE": + if mode not in ("empty","with"): + print("Parse error in regex file ("+regexfile+"), line "+ + str(lineno)) + sys.exit(4) + else: + if (actreplace != ""): + readregexappend(lines, actreplace, actwith, acttype) + actreplace=actwith="" + mode="replace" + # the second half of the pattern block starts + elif line[0:4]=="WITH" or line[0:3]=="RUN": + if mode != "replace": + print("Parse error in regex file ("+regexfile+"), line "+ + str(lineno)) + sys.exit(4) + else: + mode="with" + if line[0:4]=="WITH": + acttype="with" + else: + acttype="run" + # empty line, do nothing + elif re.match("^\s*$", line): + 1==1 + # normal line, append + else: + if mode=="replace": + actreplace=actreplace+line + elif mode=="with": + actwith=actwith+line + else: + print("Parse error in regex file ("+regexfile+"), line "+ + str(lineno)) + sys.exit(4) + lineno=lineno+1 + + if actreplace != "": + readregexappend(lines, actreplace, actwith, acttype) + f.close() + except IOError: + print("Error reading regex file: "+regexfile) + sys.exit(4) + return (lines) + +def readregexappend(lines, actreplace, actwith, acttype): + compactreplace=re.compile(actreplace,re.VERBOSE|re.MULTILINE|re.DOTALL) + actwith=actwith[:(len(actwith)-1)] + lines.append( (compactreplace, actwith, acttype) ) + +################# +# parse an input file string +################# +def parsestring(strinput, regexlist, docchunks): + global cutit + # split the file + chunks=re.split(doxhead, strinput) + chunks=chunks[1:] + # apply all rules to the chunks + for ch in chunks: + if cutit: + ch=ch.split("/*")[0] + actch=ch + name="" + for reg in regexlist: + matched=reg[0].match(actch) + if name=="" and matched != None: + try: + name=matched.group('name') + except IndexError: + name="" + if reg[2]=="with": + try: + actch=reg[0].sub(reg[1], actch) + except IndexError: + print("Index error:"+ch[0:60]+"...") + print("Pattern:\n"+reg[0].pattern) + print("Current state:"+actch[0:60]+"...") + sys.exit(6) + elif reg[2]=="run": + exec(reg[1]) + if name=="": + print("Chunk without a name ignored:"+ch[0:60]+"...") + continue + if docchunks.has_key(name): + print("Multiple defined name: "+name) + sys.exit(6) + if verbose: + print name, + docchunks[name]=string.strip(actch) + return(docchunks) + +################# +# print out some help +################# +def usage(): + print("Usage: " + sys.argv[0] + " [-vh] -t template-file -e regex-file -o output-file\n"+ + " [--help] [inputfile]...") + + +if __name__ == "__main__": + main() diff --git a/doc/dqueue.xxml b/doc/dqueue.xxml new file mode 100644 index 0000000..8cd4022 --- /dev/null +++ b/doc/dqueue.xxml @@ -0,0 +1,23 @@ + + +]> + +
+Double-ended queues + + + + + + + + + + + + + + +
diff --git a/doc/embedding.xxml b/doc/embedding.xxml new file mode 100644 index 0000000..4076bc0 --- /dev/null +++ b/doc/embedding.xxml @@ -0,0 +1,16 @@ + + +]> + + +Embedding of graphs + +
Functions + + + +
+ +
diff --git a/doc/error.xxml b/doc/error.xxml new file mode 100644 index 0000000..4f88f6b --- /dev/null +++ b/doc/error.xxml @@ -0,0 +1,72 @@ + + +]> + + +Error Handling + +
+ +
+ +
+ + + + + +
+ +
+ + + +
+ +
+ + + + + + + + +
+ +
+Advanced topics + +
+ + +
+ +
+ + + + + +
+ +
+ + + + +
+ +
+ +
+ +
+ +
+ +
+ +
diff --git a/doc/fdl.xml b/doc/fdl.xml new file mode 100644 index 0000000..c9a1357 --- /dev/null +++ b/doc/fdl.xml @@ -0,0 +1,420 @@ + + +
+ + Version 1.2, November 2002 + 200020012002 + Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + + +The GNU Free Documentation License + +
0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + +
1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall subject +(or to related matters) and contains nothing that could fall directly +within that overall subject. (Thus, if the Document is in part a +textbook of mathematics, a Secondary Section may not explain any +mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + +
2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no other +conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + +
3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to give +them a chance to provide you with an updated version of the Document. + +
4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + + + + Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. + + List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. + + State on the Title page the name of the publisher of the + Modified Version, as the publisher. + + Preserve all the copyright notices of the Document. + + Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. + + Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. + + Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. + + Include an unaltered copy of this License. + + Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. + + Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. + + For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. + + Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. + + Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. + + Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. + + Preserve any Warranty Disclaimers. + + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + +
5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + +
6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other documents +released under this License, and replace the individual copies of this +License in the various documents with a single copy that is included in +the collection, provided that you follow the rules of this License for +verbatim copying of each of the documents in all other respects. + +You may extract a single document from such a collection, and distribute +it individually under this License, provided you insert a copy of this +License into the extracted document, and follow this License in all +other respects regarding verbatim copying of that document. + +
7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + +
8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + +
9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document except +as expressly provided for under this License. Any other attempt to +copy, modify, sublicense or distribute the Document is void, and will +automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + +
10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions +of the GNU Free Documentation License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. + +
G.1.1 ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. + +
+
diff --git a/doc/flows.xxml b/doc/flows.xxml new file mode 100644 index 0000000..2d53e46 --- /dev/null +++ b/doc/flows.xxml @@ -0,0 +1,48 @@ + + +]> + + +Maximum Flows, Minimum Cuts and related measures + +
Maximum Flows + + + + +
+ +
Cuts and minimum cuts + + + + + + + +
+ +
Connectivity + + + + +
+ +
Edge- and Vertex-Disjoint Paths + + +
+ +
Graph Adhesion and Cohesion + + +
+ +
Cohesive Blocks + +
+ +
diff --git a/doc/foreign.xxml b/doc/foreign.xxml new file mode 100644 index 0000000..645dc4e --- /dev/null +++ b/doc/foreign.xxml @@ -0,0 +1,50 @@ + + +]> + + +Reading and Writing Graphs from and to Files + + + +
Simple edge list and similar formats + + + + + + + + +
+ +
Binary formats + +
+ +
GraphML format + + +
+ +
GML format + + +
+ +
Pajek format + + +
+ +
UCINET's DL file format + +
+ +
Graphviz format + +
+ +
diff --git a/doc/frplots-small.png b/doc/frplots-small.png new file mode 100644 index 0000000000000000000000000000000000000000..9badedd858bfea3b273aafb55fa32cd8576d617c GIT binary patch literal 2013 zcmV<32O{{1P)M2SO6tNUfRb>Z( z6qGP-Q`vdRl;Ctb>k%YHUJ5ID>;1!vmKS;p@#pTDDYN3><1a@7MRQrd;8((v%>W)Z zh1$dwm?e7b;%&Mp^|deP*o?e<5<&SoN;%g)sU^l|wT>X7@adyhCA(T;2-k#&A~t$+ zQRU2Sc{DApb;+id=uNaa@eH@dADFD3Oih$dZ5_=Mon(x(D1t_f*lGn8e?LGv+ZUAS z44Zc*^C9p~qa>KV!=^o(UReyz2YX_4HB79R9Y3;48Mv76)!3U6m4_{utUcU7yldQ;Na6C)V`5M4<;ahi za%;p-Y7q~mAmV!*`X)Jquele&zfe!cpjsP9AiBNnl!4JWEW)hb@iEF}vgc0X{S+~R zPt@pe3Adn(qvQ$BDlV(ko-)y__3t z&W|I{=LX#i>_7gU-0^Qxsvg+augY}hFG6|VMYpZ~laz8sNm602BlyF+ z%7wkJfluXh1gYyqed!Hlh>ihXduX{-zd{tR(BYiBw>;Bs_jO82JSz$X-wImiQ$m{n zKz2smiS8lKhm%V|FMW+w=E8{yR<05BGPUQp1Ipk^gI_&;=Ld>TXB&&!94{!8>pkw> zB8FU{t*g1l;gl%fA4(PRhK=M*T%pD zjEeROsb;YS#omWpBh(NO%-XrDKdWL^DLK~+7A-UuJ>ri&<=;1pe#IF4sr@-ZEACxRJknwG*!rT-eb+I*}fO6Ki2s*v@#cK|et_h#JhElj9xT&Dq z$!`;$46dyZTc4+L4D6|UNl>V;&VYlL5TU>j>v!mf%}gqoQX=l0FL=6=HM$Z%VP74I z6=jXd3$U#YFoVfBxM?9WgJZ(;sop&n2%UjiC?9Le=K}Sw@ZZTt-$Z$BGFR=M$4H4> zmerZ-eMNaIlt=CYVCrwM^mQXAH5_#!1_#K&H& z=2{Oy$taM*k-IvVdP>sPlqo1QEoxPIed=HII*`Bn*uz}k&Qd-qS4jQrF7}@rMX<7T zT$W(>uH(u-3<6wrI!Sg{T-d(T$aE=hW=hls-p$~sY1;R_XpazjewQxPjMMgcDcEq| zV{ZMW0hIICGmmwRzjno>Y33w@G%>7t0j>E_UEx${2W) zx{9sW#y{}{Gmo*`Xd4Y8zuTIc=;vbFLa}sbB2SpYaT!U6)Z3cc@Usw)b+7$$rK;_+ zc{!!J^Q?#9OddX0hnX2w6nsNJ4 zI_rUi!!~nDZNH@cAdw=MxV&&Pa_bazL94kze3%V)^wOY0TO12}Eqn1P9vtFm%6L zsaT-E*l4jZVGIMG7;!+Sp)KnOZcbJ*(bR^^vVlwiU1OLe!sxd*ALCpQWHM|un_x(9 zW8e$Vej7nvlk!HhVN`?<`=u(stw?G8#)c`}AB8Hfi^7eqZZ1A$?^ssfn9%3sz*J-+Fcj<{x{J~OZ8YYKdG-ZMsVlUTv0j;$za!29fQ-2n0 zJGwL!`+)}`bUL4BRj5_0UwKB-*>#4`Q|S2k>RvC-GOYtl6NmW!5ciaP06}Je4*`_% zoiXpztZ?g78+Kw4F<}ObiZxb;fRD!iq-6aU^3Iz+=A&XpY=f+Hq^OFU!-8NfRe&%z z{t5FBa*or+7Zb>KoaaxetL{UkY6H4VXTT93osL#&o{u_L@L@s?q0v}sHYDdWHwrB0 vU3WXpTNn5oW)}f8p(+`hN!#%s$})cfu*E*~@AGaB00000NkvXXu0mjftE0~L literal 0 HcmV?d00001 diff --git a/doc/frplots.png b/doc/frplots.png new file mode 100644 index 0000000000000000000000000000000000000000..99580b888af2972705209b6a05f24d8d31a8db93 GIT binary patch literal 13137 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 NIca67`cI~z{|6VD?-l?6 literal 0 HcmV?d00001 diff --git a/doc/generators.xxml b/doc/generators.xxml new file mode 100644 index 0000000..06410d5 --- /dev/null +++ b/doc/generators.xxml @@ -0,0 +1,71 @@ + + +]> + + +Graph Generators + + + +
Deterministic Graph Generators + + + + + + + + + + + + + + + + + + + + + +
+ +
Games: Randomized Graph Generators + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
diff --git a/doc/gpl.xml b/doc/gpl.xml new file mode 100644 index 0000000..d888ed8 --- /dev/null +++ b/doc/gpl.xml @@ -0,0 +1,444 @@ + + +
+ + Version 2, June 1991 + 19891991 + Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + +THE GNU GENERAL PUBLIC LICENSE +
Preamble + + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + + + The precise terms and conditions for copying, distribution and +modification follow. + + +
+
GNU GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + + + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + + + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + + You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + + + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + + + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + + + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + + + Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + + + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + + + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + + + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + + + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + + + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + + NO WARRANTY + + + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + + + END OF TERMS AND CONDITIONS + + +
+
How to Apply These Terms to Your New Programs + + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + +Also add information on how to contact you by electronic and paper mail. + + + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + + + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + + + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + + + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + + +
+
diff --git a/doc/graphlets.xxml b/doc/graphlets.xxml new file mode 100644 index 0000000..6b06995 --- /dev/null +++ b/doc/graphlets.xxml @@ -0,0 +1,20 @@ + + +]> + + +Graphlets + +
+ +
+ +
Performing graphlet decomposition + + + +
+ +
diff --git a/doc/gtk-doc.xsl b/doc/gtk-doc.xsl new file mode 100644 index 0000000..4fc7db0 --- /dev/null +++ b/doc/gtk-doc.xsl @@ -0,0 +1,368 @@ + + + + + + + + + + bibdatabase.xml + 1 + 0 + 2 + + book toc + chapter toc + section toc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.36 + + + + +FATAL-ERROR: You need the DocBook XSL Stylesheets version 1.36 or higher +to build the documentation. +Get a newer version at http://docbook.sourceforge.net/projects/xsl/ + + + + + + + + + + + + + + + + + + + + + + + <ANCHOR id=" + + " href=" + + + / + + + "> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.66 + + + + + + + + + + + + 1.66 + + + + + + + + + + +
+ + + +
+
+ + + +
+
+ + + + + + + +
+ + + +
+ + + +

+ + + +

+
+ +

+ + + + + + + + +

+
+
+

+ +

+
+ + + +
+
+
+ + + +
+ +
+
+ +
diff --git a/doc/heap.xxml b/doc/heap.xxml new file mode 100644 index 0000000..3cb9eda --- /dev/null +++ b/doc/heap.xxml @@ -0,0 +1,20 @@ + + +]> + +
+Maximum and minimum heaps + + + + + + + + + + + +
diff --git a/doc/hrg.xxml b/doc/hrg.xxml new file mode 100644 index 0000000..813d33b --- /dev/null +++ b/doc/hrg.xxml @@ -0,0 +1,41 @@ + + +]> + + +Hierarchical random graphs + +
+ +
+ +
Representing HRGs + + + + + +
+ +
Fitting HRGs + + +
+ +
HRG sampling + + +
+ +
Conversion to and from igraph graphs + + +
+ +
Predicting missing edges + +
+ +
diff --git a/doc/html/home.png b/doc/html/home.png new file mode 100644 index 0000000000000000000000000000000000000000..17003611d9df2b066afc682cbde962f3a575002d GIT binary patch literal 654 zcmV;90&)F`P)~yY zO1cF+0vxb!W?!x?K+*#62Jq)nA4q`)5S6sgX4ao{=)(Mgq+YMr)7sjak|a^9)zS!j zlk{-n29mabXYF=7SYBQx&vO8xC}MYams+hxqtO7sImhPaCf@rq;I^3!#u*2aUP)55 zT2&N90xmEJ0s&fGT~(T<3d2xYmK9C>IP*x-M@ib*+0pFm>>uW37N2Wzaq-fCnIZE9 zpb8}0+uN+KuQM2oZVHfP8U6kQdo3?>Wo2dT)WeM9So8DqhLi#T0 z-i(>mfjhvbsYV`;4sgfJ-p>G-SqJ!fjR6BQYs1h*y9xaN0l{VB;o%`08yiy@)$8@~ z2PD1gcDuiy;j1tR0v#V8OH%W)25-YKyx(j#IXO9*YWf0mb8}QG6@b@;cHxh9{t7+@ o!Yd`f8L$sLH?yBt^q3C6015TtIu@BS5dZ)H07*qoM6N<$f*igdr~m)} literal 0 HcmV?d00001 diff --git a/doc/html/left.png b/doc/html/left.png new file mode 100644 index 0000000000000000000000000000000000000000..2d05b3d5b4aeec9384bbfe404bfc4ed0897051c4 GIT binary patch literal 459 zcmV;+0W|)JP)40xL?wO*>WZ(J#ML5j2<9jD6A%Q&kC}jOeEc;X{s;`zcnxLeZR6?6h#^ihmNF6NpGdilO$m<82oD9WQ|6nVv1`? z>KufRi{?QPXg;4;wroQu4?mN1Ydd@|kaQ|ZyWLK!)yi7Wb%=0{}lD)tfliHAUyWRQ+fD_;aV6j->y6!O_8bENg6P)Cd4HCN^TYHBC0dz3r5|}*T3c5!K}0^NPTey!^rYo;W&eW{b1SE%dR-1ljcju- zJITo5P_e{cPDWDszO|97o#m$fni3V4d%~7^?0HU4-k!+X`e~w55Q}HA=c?CM9`EK` z^o5GF_RsnG`ey+9wOf8O4bzg>7W*;jU~M?g`OZAA$mNp|Lz<$s+~N9!2`ir8RcClo$(Q~19INM~9}j;&*|enC yGd}kJak0wj?aUKd8;%}`i}SSew>!A-2iw}^5}Rh(M>+vRkipZ{&t;ucLK6U4uc96R literal 0 HcmV?d00001 diff --git a/doc/igraph-docs.xml b/doc/igraph-docs.xml new file mode 100644 index 0000000..00092ef --- /dev/null +++ b/doc/igraph-docs.xml @@ -0,0 +1,133 @@ + + + + + + + + +]> + + + + &igraph; Reference Manual + &version; + + GáborCsárdi + Department of Statistics, Harvard University + +
1 Oxford street, Cambridge, MA, 02138 USA
+
+
+ TamásNepusz + Department of Biological Physics, + Eötvös University +
1/a Pázmány Péter sétány, 1117 Budapest, Hungary
+
+
+
+ + + This manual is for &igraph;, version &version;. + + + Copyright (C) 2005-2020 Gábor Csárdi and Tamás Nepusz. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover + Texts. A copy of the license is included in the section entitled + GNU Free Documentation License. + + +
+ + + + + + + + + + + + + + + Data structure library: vector, matrix, other data types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Advanced igraph programming + + + + + + + + + + + +
diff --git a/doc/igraph.3 b/doc/igraph.3 new file mode 100644 index 0000000..f3f01f8 --- /dev/null +++ b/doc/igraph.3 @@ -0,0 +1,46 @@ +.\" Hey, Emacs! This is an -*- nroff -*- source file. +.\" +.\" Copyright (C) 2006-2012 Tamas Nepusz +.\" Pázmány Péter sétány 1/a, 1117 Budapest, Hungary +.\" +.\" This is free software; you can redistribute it and/or modify it under +.\" the terms of the GNU General Public License as published by the Free +.\" Software Foundation; either version 2, or (at your option) any later +.\" version. +.\" +.\" This is distributed in the hope that it will be useful, but WITHOUT +.\" ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +.\" FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +.\" for more details. +.\" +.\" You should have received a copy of the GNU General Public License with +.\" your Debian GNU/Linux system, in /usr/share/common-licenses/GPL, or with +.\" the dpkg source package as the file COPYING. If not, write to the Free +.\" Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.TH IGRAPH 3 "March 2007" "igraph library" +.SH NAME +igraph \- a library for creating and manipulating graphs +.SH DESCRIPTION +.B igraph +is a library for creating and manipulating graphs. +It is intended to be as powerful (ie. fast) as possible to enable the +analysis of large graphs. +.SH DOCUMENTATION +The full documentation can be downloaded from the homepage of the +library: +.RI < http://igraph.org > +.PP +You might also try the info pages of igraph if they are installed: + +info igraph-docs + +.SH BUGS +If you think you have found a bug in igraph, feel free to use the +mailing list at +.B igraph-help@nongnu.org. + +.SH AUTHORS +Gabor Csardi , +.br +Tamas Nepusz diff --git a/doc/igraph.info.diff b/doc/igraph.info.diff new file mode 100644 index 0000000..980b061 --- /dev/null +++ b/doc/igraph.info.diff @@ -0,0 +1,20 @@ +--- igraph.info.orig 2019-08-16 10:35:48.540887736 +0200 ++++ igraph.info 2019-08-16 10:41:57.388760592 +0200 +@@ -2,8 +2,7 @@ + from igraph.texi. + + START-INFO-DIR-ENTRY +-* igraph Reference Manual: (igraph_reference_manual). +- [MISSING TEXT] ++* Igraph Reference Manual: (igraph.info). A library for graphs, version @IGRAPH_VERSION@ + END-INFO-DIR-ENTRY + +  +@@ -11,6 +10,7 @@ + + igraph Reference Manual + *********************** ++Version @IGRAPH_VERSION@ + + * Menu: + diff --git a/doc/igraphlogo/igraph-white.svg.gz b/doc/igraphlogo/igraph-white.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..60b54058fef4f894642fda1310afb63dcb3f5706 GIT binary patch literal 6138 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 M0Y{7LoH|kf0L?=s_y7O^ literal 0 HcmV?d00001 diff --git a/doc/igraphlogo/igraph.svg.gz b/doc/igraphlogo/igraph.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..b9f3d21c40c26c7723aa22f5b5fae35320ff5343 GIT binary patch literal 6101 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 b^Oxyk{UCq6o8SE(Ye?eC(NO>Z{9O!T literal 0 HcmV?d00001 diff --git a/doc/igraphlogo/igraph2.svg.gz b/doc/igraphlogo/igraph2.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..69fbc409b2719e3cece4161726cb81da1a8b1a53 GIT binary patch literal 1952 zcmV;R2VeLfiwFo~^~^~C18HY+VQ^?NE^~Hg0PR@YZrex_ea}~LnU@$VQtTVMo3T7t zOb}pz*_WMJ?BjqYTM{P{DUg(9`Rn&I#hYx(v3HV5u)7uvkyX`~I#qSLTGv0^Z#T}J zDXXl=7cTJw*D-lgq*=aNxc~h1qo-V_s^dJ3H$`q1ZeF-Q+`PH|i|08%mL{%E>Kw9q z?fjH~s*-qToOkQG-bM5I;c)P?R_GVyYJTN-9!6Got2d70VA;Hi(q!Sb_Pc$#Y3$Nu zZZ>9X^17N6pUmCCJQ>UryLfhIl483p@~Rn`S8qqgWxDLG?PdEJwD^$g=Y-kNF`C#ITr@@D)YOY1elt7-GPHrZ-j5ARvJaIs^`iD=%t9YlR%1U_p` z+iAxt9yY48EIOMeEfTx4g`2I)c((neOJJHDp`vxauql31eeuu5?Okxw}WIftkeYA+4o3az^&Y8oz-|pe96FGv0K2apl zcqWsE$CDvJD&Q-aw0T&#l5;=MoU6}I+HT7$%50SZ9wwk}mP|Z`1f{jmlmzl$W=51V=cD7x-LZxkra^b#RHh-<+wkT6mcB-)XJ5v?N z#;iULN#QuF64_X6Od011khdW9s(qH4GhM4FZhwaxQ4@W=rkmc~l>&lx4PrPm|x{B9YUa+vaeL-dM>{4fI>Nt(- zcu12@f&>e?!UMBL|M>9nrp?9cB#Hi2l%KlPbR26C-=eG+u6xsquhS#~7jEPFCfg!h zmPY>y?nA=%iZf$dyThcXX-jh=)yb13O}3dGH2n_XB)h}2z%|SEcUx?k(xHV?f~CexNBs0WgT6(&Ep0K>^qMko23u| zoq3e|iqcSus}AZ_=l^Ida4NI=cVGxD7-xDG;4{2=giD_=O~lNTLi(Ih!mrMV17|@% z`p8`G;(9&c+%O9yT!z{m#E|~oA)ql9l910hVZM|~apy-!l`lgsd#M%*=V4^rtXDTr!hN)ag`)4sy2y-dwGrEpIW(GG46ft_Hg zHEbuuZiUc9kC($Y$+;G0E&`qI`{K-RzRt*flZ|Y7#cyUi=0D_ znP@2ZC^T4)Al~6Pf=^>TN{6aGZjkTG0@PMz4<;g<@9y89-mNamZAEWRlDoLf;=DeS z9U9U)6IFF->SW!^P{h&Op!sWFy!Hf$$a|O;pG@>t3r#g`Ur$sSp|5080Yi!%wVF+q z8x(7FyRYl9?DrzeBd~@kJL#}Sv!`Xe%{IsAV`zgpyj z*_e0E>uWfa!5EgUaBL*W1hm%s9Bui@^>PnP&7Hxh^jYr_TPZ@t6??t!&C4dkSHwG6 z8sl6o%lLTO%wv?EPy|X%s;0hu9B6OCYtTFSVbT|6#O=$wr>|0D~fAGlH9>rc{ut%gHi51v)Mo;IKp! zFVTFVX`@$9DUNh9d%z9NlCU&s)*kBc-iWzbdL&-$mHMwCxQp!G}bC+7bwjWBy-=UmRP7&s_Y>}4#{{we^X18bu*XnqwK-aPaR z8ka@QW?pDpaK%J8!7bkwk_vs&M~-~W^XJbvYe#MStD^h5^XK^%=t7V5U3796I6(W7 m$soj;K` + +]> + + +Installation + + +The easiest way to install the igraph C library +depends on your system, and it might also change, +so we no longer include installation instructions here. +Please see the igraph homepage at https://igraph.org/c/ +instead. + + + diff --git a/doc/introduction.xml b/doc/introduction.xml new file mode 100644 index 0000000..c0a0ffd --- /dev/null +++ b/doc/introduction.xml @@ -0,0 +1,105 @@ + + +]> + + +Introduction + + +igraph is a library for creating and manipulating graphs. +You can look at it in two ways: first, igraph contains the implementation +of quite a lot of graph algorithms. These include classic graph +algorithms like graph isomorphism, graph girth and connectivity and +also the new wave graph algorithms like transitivity, graph motifs and +community structure detection. Skim through the table of contents +or the index of this book to get an impression of what is available. + + +Second, igraph provides a platform for developing and/or +implementing graph algorithms. It has an efficient data structure +for representing graphs, and a number of other data structures like +flexible vectors, stacks, heaps, queues, adjacency lists that are useful for implementing graph algorithms. In fact these data structures evolved along with the +implementation of the classic and non-classic graph algorithms which +make up the major part of the igraph library. This way, they were fine-tuned +and checked for correctness several times. + + + +Our main goal with developing igraph was to create a graph library +which is efficient on large, but not extremely large graphs. More +precisely, it is assumed that the graph(s) fit into the physical +memory of the computer. Nowadays this means graphs with +several million vertices and/or edges. Our definition of efficient is +that it runs fast, both in theory and (more importantly) in practice. + + + +We believe that one of the big strengths of igraph is that it can be +embedded into a higher-level language or environment. Three such +embeddings (or interfaces if you look at them another way) +are currently being developed by us: an R +package, a Python extension module, and a Mathematica (Wolfram Language) package. Others are +likely to come. High level languages such as R or Python make it +possible to use graph routines with much greater comfort, without +actually writing a single line of C code. They have some, usually very +small, speed penalty compared to the C version, but add ease of use and much +flexibility. This manual, however, covers only the C library. If you +want to use Python, R or the Wolfram Language, please see the documentation written +specifically for these interfaces and come back here only if you are +interested in some detail which is not covered in those documents. + + + +We still consider igraph as a child project. It has much room for +development and we are sure that it will improve a lot in the near +future. Any feedback we can get from the users is very important for +us, as most of the time these questions and comments guide us in what +to add and what to improve. + + + +igraph is open source and distributed under the terms of the GNU GPL. +We strongly believe that all the algorithms used in science, let that +be graph theory or not, should have an efficient open-source +implementation allowing use and modification for anyone. + + +
&igraph; is free software + + igraph library + + Copyright (C) 2003-2012 Gábor Csardi <csardi.gabor@gmail.com> + 334 Harvard st, Cambridge MA, 02139, USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +
+ +
Citing &igraph; + +To cite &igraph; in publications, please use the following +reference: + +Gábor Csárdi, Tamás Nepusz: The igraph software package for complex network +research. InterJournal Complex Systems, 1695, 2006. + +The igraph C library is assigned the DOI 10.5281/zenodo.3630268 on Zenodo. + + +
+ +
diff --git a/doc/isomorphism.xxml b/doc/isomorphism.xxml new file mode 100644 index 0000000..a95b94d --- /dev/null +++ b/doc/isomorphism.xxml @@ -0,0 +1,57 @@ + + +]> + + +Graph Isomorphism + +
The simple interface + + + +
+ +
The BLISS algorithm + + + + + + + +
+ +
The VF2 algorithm + + + + + + + + + + + +
+ +
The LAD algorithm + + +
+ +
Functions for graphs with 3 or 4 vertices + + + + +
+ +
Utility functions + + +
+ +
diff --git a/doc/iterators.xxml b/doc/iterators.xxml new file mode 100644 index 0000000..b80af22 --- /dev/null +++ b/doc/iterators.xxml @@ -0,0 +1,99 @@ + + +]> + + +Vertex and Edge Selectors and Sequences, Iterators + +
+ +
+ +
Vertex selector constructors + + + + + + + + + + +
+ +
Generic vertex selector operations + + + + + +
+ +
Immediate vertex selectors + + + + + +
+ +
Vertex iterators + + +
+ + + + + +
+ +
Edge selector constructors + + + + + + + + + + +
+ +
Immediate edge selectors + + + + + +
+ +
Generic edge selector operations + + + + + +
+ +
Edge iterators + + +
+ + + + + +
+ + + + +
diff --git a/doc/kkplots-small.png b/doc/kkplots-small.png new file mode 100644 index 0000000000000000000000000000000000000000..bdea6a0f21d4a0eda29e84afc96bd4bdfa9cef11 GIT binary patch literal 2029 zcmV;}U`ZxJ8+6nqMO%xRgY*fi^9sN-UWu&0?~ z+oGzXLHvUUL<}Xt|>5Gd#%!%e+DIe+YZe9_5Oq8FL z@|wRPj9Tg)n!_{+ znuyYYjhr#)ZVL;#U~&F;lxOecH~>&@LSbtW!-XHRlb)Co4ijK2cg-0H;w%K^cvKycT>Eexr^2AnlM-Itpp9q%o@j#H0x=dW8h5R7-ic=hS^J$dwYz2XNim| zZPl4YoZ^{-UBY}cA@L>CuE7b-$Ww0&$ZTz&i{g_k2#+*`_BfUeDVLjJB0lTne; z{JgR^6xpe$NF2wiVzwU<$shs5n=wJQE84r-W99<43_>n)Q$gPqGRqD#-$E!QPy*-c zBnH*9yPTW2qfByLA?TRu$bBk)#`j#yZ((XXJJ4EZ6VIN2pMHI}>Ka$ElP_cP*ux&0La&^*Og_P+@YJK+JGDr&~>+R0&MeQp`}?YPc;%y#boK zR8FWlhioscaoVvY<6P947_b}gn?q@(f)cf(KJ%~hPyhg|vcu}_?WV`xC#7tgN4Xcm zfsdb3hvq8F2f>3%uc=BC{JU^r_1}W@3^l$(XrD(`oU1YIz2*Hosl{6W3-LOf`s#5_ z>3o?mZUY%ka(R(53F@sJUODPIiQjO2#4!aQW}bGdlU-ec3d^UC#WsSw)|=2Wv#!m} zr%D{Vt_N`hWBf;GCE5^GrZLa8$MBf%<*CAMeiDRVAT)vfm z&yPO<*I$(Te{3~5k(Zm7EDNOjk)29Lq8z@Bg1X^qh_}c*xat$M&RvYag@dvS{bx{o zUnDGZ94_&8MKKq!I^aA38#;*dH!)cMv(8T`^cS8EQ9Z(hHk}YNa+6!m*P+P(#zrJm zM8%1d7B{NNuL9%8;J%)Edb0LtFeB|O6Qc>p!YsD!g4>y3wk)U9JdLJCB@#eo>l*CJlU` z(W$M?FxL6sg5)gE9fTNr;i}qU0~LGo6s%7YDQwR2&)~yHknD_Bx`p zOMQs;I?m6#%kDn)$%Ju3b&;YQ8y0hflk%h>yB(_z!D$-i7N9u%^)r@`D;|m00000 LNkvXXu0mjfWPHjy literal 0 HcmV?d00001 diff --git a/doc/kkplots.png b/doc/kkplots.png new file mode 100644 index 0000000000000000000000000000000000000000..af34523b500afa070724d7b2aab780412a14f5a6 GIT binary patch literal 13225 zcmajGWlS7i^sYO@;O;KPi@RH)xE1%}?(Q|nyi;Hyr5Nz;Af#Cv2Bzjw%qjuJOP}3YP`onM%z#D+u}E_`UuMeY*(hE_imo z>vaej3@B|P%+Z~(5RZsw=SXisdxB|I($^C`F1y5bJiT7$8PcT28whXworWCzO9}S6Si#QPOPCuvhO@`hE*IhpWuHbcpQD9AirX^V++$$u9`BMolh|ulc9R|y3+KYp($0x3DJqEdg3uvRj4F@3|d+3jRMNM)c2xR zLEx^oNvjjif-wupr!A;M934jnAcIP@bVDoLtvvRLQ-S!Y3@Lzn&{T(nc0$T=>E{NM zHR*Otv9p)0jJ#G;*7>K)@^J*Iv%(V213+09&}fYnd{zfm;4{&K(Wmt1&=V`Lb$Esl z###g81HM^r=cwOzQO{4IG$l3^`4_+LqYup7>!j@rz^wuyH-NvZ0zha1Xxs^1!NY20 zi8qkFUMOo`TazdQDwL_F_YjZiZy$`gj$^F`kvn29N0lvdbSN<*4|E0|dYGleGEta~ z0^*xG*s?k{v4<{E={gCmSKAi>x)AnhPmXwSqOkk$glvfnJd?t9QwBQPnN57c@$wAn zN5lJ!yB|VAT8-{4qKeXjZr8|B!9yB=f6R+i?GZfM+HHr!33`mIxlTMx*;b#47wSt; z-HgU%3g?Z%(PBPG!Jh#(FF7y~-*1j2%k^<@!>kjsg?G%Db(VoT8qpb|RM=W;%7m5JmZnuSy%+t6}@pw^j$BAyZ#EQ|T@ zR)N*}wHbxhfSd1Z8?jdlw)0B7@sdz7Ah{UoWw~4-8~OJBI2W$Pz#c`5_O7ijziT-! z8KOPon(!YkFbJ(w4l%`gT5x|q$AD-s$%6Kop~*}%Z44et9#x2zlhyP^bO@$??#W_{ zkmX55clDPJX@$=lSOMdI#u?Qk{JM#=!yMMe7X;q4vyY0O|6}PHURpx3pvh1b9+>+( zcv^o6VaaInK>~ZyEBz;++ZvAvkFU0G41j=65}t&Dk4JbYbj%9F<-)UHPQtGC5agBA z1mG2Ck!Q;`o+to*>ory^5PxfqZUnF|zwh&`@0ypVdW5uv+ymNd>}&~3J>lZU%!W~T z7~&#_TwtEFfxN{i7#c3OZ9fBPNWRGK4L-Dolz;SJZ{ zpVXAu%# zTC`I9^=2hSL2WYLZTjDj?I|Ijz=z_V*IFOUVkPStk_IO+Q9M|O(=5pLj zTz^{~wiS%eNSLP_AXj@Wmc>lu(R4~>KN~4QgjWoI%J8TKvF)v_DAg83_uLhmt}JAz zE!m8-(<5$GKjZecnqb$Yom+4b@GA)PC=IQhLww#RS4gT%`8&$w z$3ia=rkn*Qg=Bz)b?m&Ae)oWiuR+nPM|_NMf${I^NhYa`ieJl?KXva;dv(UNd5?Uq z##PoS6-IHLhwlZV9!FFch{Ha!17j`u zrTnkB5E*Aa{qnXSySa(50vSo!Yy%?nxY-(+*F6Vq|TohS*&} z=aro66fSyhXad5VBQ@`{Dx>age3I|X59?5fyX6fWD0)csv^NEi?_?_(0GctWsB|X# zg3bTA5p<~H2bM#u*ivX+5Y~%YPpBb;I_oG9exXp-2ZPo> zI*-u2wdC(uER$qN>uC`0zqlty@EjSL^t|f1=Yt*rn5H~}fZCj5zBpz;#}uX1`&sv= z*eJu1?cius$J3Qu#UuESxv>Q6jRTpC4uY${sfq(@i;q~T#$*06kJ@z}s@)!XpJI$f zS%*$7<2%WQ*gTrqjsQ1)d_u65y`BDZX`4b?MO>|xC=(T{k9Be-Ods7!r-9OQybIAB$_XwnI*dr4QUEo$VHYT z!C<)G!sadU-EmR=I6SZe!d!y5%P6lT%}DI(qDz~M0vRqhwT^~=xS}?%3MHhQlVS3K z8q!Ndmcm%z>h`|h5yo#ENL)?Hd>(1dQ>XP1o#`X?F7eazKysbev-yXNXGZ^tOcz() zelOFH^I)K~;W~LNc67N#?u3FmOw-}1lWllz5AJ*jE`xJC7|cX_PeOlLOYa{hD`LAG zAD2Z9aY9y6M0}L$-Z3?3kl6%yJUr$61W*M<;SmZ+hYZcchwGT9BMMfzh&khl&?0(# z<1Lmdg45)}xV$3?=Jn%C^K~Op;%IWy=7Uq7soVI|l#0Yv;5*BN&yQtNZ;#cNy`Ptr znI}ILH)a~b1Zrr{R1#y;mgWxaDb`ShivcLpfvN9iy^C;!R z{`g8NqlJ;qN9i98&jl(gvBcbmU^Y;Xt-iEbnWRl@E6P+=M`{%!Z5-MD+UnYDh5eLc zZP}>N+h3-dB$<`!ZJ-kKm>Y>uaf3~2%<6E~@75J}VA~7Qp;}zCdh4OfYG)}4tLND7 z-C;0hJF6m94a+7C&JcQNO}_v5~G zn__}*g)73sq0vDWnI0=1tlwOT@Dgx^Wb!Q!h+ZwguR?p9E`o63i#l4;(ZZSzcu9== z#Wg=O^^;aP2Uxd$da$hwZkDv5+iYVup9?UBU>YEhCpZTt>suHmttHU5Sr^~jkq3n- zgL|p&?q&41&klq6N3OWwCIEf9c*)O`sw3tJNy>`(e)*?mXr1hUwNr; zUFehzfp+R!&g`uIDTlE?slCRm1+W{nQvO4e2NVt*b*1t&b4VFyeblR;4 zla4Y0vb;5@k;HI0$&)OLuFNwgsz#ATz+BeZy@~+TMq+IQsTv#I=D^tGxj4iMGY#WV zDe&PCT4zS-mvG?E02J71}f^OTxgs7mq4w_ylB8uE)$PM zu+}83;X)P(Mf%$rp|2s6moCvL-A6LCa{5EQl&D{&LBh$T+Z__!pe@Cx%5UEKFy6`l zefDFD;>|(}iu?4x0T&rGS2SoJXNp^^TH2k%DQuR+zYfkxti-6&k{~!owx{VS_G4h6 zNtEjzV{eup9`k{*auo%9W8}k{zo)YMRBDtk@m=Ry{ATIG?QUrAz4orceC|kN)DoKe z5rEWfxGYhA`)I;}rmBqhlvmJOZ}k}17Vff7+t0{IiT%>)J3j-)#oc!5jEG0mNvQZI z(0W94vpx!PKh6*GZAShX|2+{);eslf3PLVcGl2zNyy-{geBHIXAe5XR%JTe9HUF5V z^6mqD?qKNsyUiUaVIckgg{J=iX#WQEFm;S{nZAGp_!`Sie$Yd8u~6v==5G=w=Gk5?Nx|Y(72sJV%wc#T)UyV z7@%1|H&N|Z`Op8#*|PP|wx?LHL8pZKmZB0f)>c40`3+#$g>qN`c2}qm2&2+qzIE_1 ziM{-;{j7!)VoEZE@Gdxx6FO&LcRQc8p>nZ!`uXe&+k{k_oMB^=ex^kb)nL80WBL3` zM1(lfS6B|OP3->S!BH#d(O~4Q0{o@okK@H_r1|Ssz$RUxiDIVBQLTSa1sc?Y7G$CQ zxQhn8ka;Mz532aOv@&wLFgL2(A)?WSW;$_FZJg>~t|LOQ+x%+#-Pndmd+Sx-YECq_0BDpz$0q{KIy*`vNYZJeU{WG+6KPgTHxBcA;#PKqgHVf*3hrVbiHnz z+Z78@2F0<=e$ycV^P+FsS7(V_c_RKm(Hm2dux@74eESbs)|t2(>m)2eyZaNO6L!;6yEMg zbL~3}MslnKB7Wa#T74&NQ>iCi&}F)&_5k89*%z$iVA^XVoU9}Ly&>+kRWtGRThjQ5 zb=hbh$_9iiwp(zN6p-L9I3V8sSl(s?_KcZ2jUfDSA|B5#z91<`M?WDmF>P#Ztf>vs zs;mE);0>%3b?^NxmZy;HC*z0ZM8drf5}k zIrn_jvuReakrRR;;fKi0KzD7oWnR9N=X{O5T^iJNS#38yo+n^1!rs)#KOq+DpJ zlMD4vww|(4-6QJ-MII4)b-dc#&-zF#S3D~+7DtvCWo2&Xt23mNEA>>5XS>+yU=K#>A?^^6nI0Lb7RS{$3YcVX?smzXWtAq>m8As>t}M!7w?&L% za{yKd4T_D(I#C*ugwASCqKMK?p3(w^*A|hm@6^z-&pXTUtMF3a&1m;XKZMqnv-oIG zzICjEPL>j)y8R^vfstg>#j{h9%@&+v)tmq#$i-=VpUtXKgA=Q0dA^(vt+HMmM6DNz zljr+^$2~C=Zc^0?w($Am*Q^ebi-05Ja6Gf6p1Y7@ZYdUGqU@+9WxDAP<+wHwuVzXD zl`euEy_MUdv+J%usTkEw&09|$HSXKmGc5U|g(9^jk^(f!(Ood+Pw((>fb=%Cb0IiJ}!wC0TrYlW5Jm&xI7=o#Dp>PR08V%5@5a4qU9o2rG(-b#g=ynztP9jhyFYQJWv50kXG~uJ!(5m zvJ=A7G366{@7BQ(rl*5~sRPa{y7w$v7N5Gx$>usWg7abbdTkcn>s|8SQHiRUGRi?!QX$KFt zV>oum)P`YYm}pyn;;qg)4LLXrRK4v%r*+#4Ub61cj%^$=WB=SInv}SKCV8YC<`iZ* zwx9J?#<`Mn@dleOq1y{$)k09L2?F?P&qg`iIV@Z5cDokOaT@}sT=Vp<@mQY9bn1Rw z?h{YcNO!UxBH+s-`LHSHnd)KO31vR|9iHvMr`@au4IjC`#68%iJ;~_(G(^Rqw(yP= zXHjy}PZ^G}kkbpKq}(rJ-yF$d49^z84QHQE@9h6hHYMkfI>^knklp6Gm>fkphBW4- zf3?I4?1si$&6hT9BLM2R)#j5Nm>?dz#4YOYvI*nq%+;Q8{wAxP4nKptD0U>jNnUqu zs-t|pCR(ihSyeM514mkkI>JHpxfsWL(4c1<_4fCYC1?DAA$%vcSCM4oX8nQ?x+7`)jy+h+$67LY z_p$|lu5F-ecVR22?22I2aG-YvldAFkctOb_io? z0I_KUho7)H{MK=CeY2$0IIaPkBLzf>SKvQ&{Il7qzHs__-pN}FNAmrMO5fQ*yd7>M z>m1#%96!KV-BFB_4Q*Q<*tZP?%V@J zWyf%hz|9WiH69=&W{%gJlLyyv%Ca#k0kcE{ym0ox=F8#1@SM*)(N_fPT1b8GB{Rt}Rk2 za01cJSvT_{UjiO99oj9MbPSN_VEg6ArRP8-Kua3Fr!yBo!K$gW%7VD%?t`3y0*lkm^_e~b73S0DO68&VJ-Y7bG^F_>SK z^AF``{`taU3Y5YWB^C}9G?H1j{L3!p3+Cr;9wYP1jG69KNqZIlGsBk>xY5-ir~4)d zzIu*?P#*3c6pPFG;*LcT{Mq=Bik)-J3!g-}kurme^!57s3y2;R>EZy(*5JO=AH_i| z*99joW~yaG7m5@JFVQ_4B^h^K?%2SHAIffaA-W z%@}7g2J&b%=TblA7g4*(1#SrkNv53)EuUeidGJ&`w{GrU7o0^d4U9Rv-i)Y^s`657 z^7dS@7Vc?8(+iK>#^;UP;ZSBx?01WwCu%L-B+l$VrK#nqp1bFgUz5wn$*PyaqifY# zuiNZ%^4exgPd$;~WH^>T#>QgWDVHe33+r*eCO`Sfdb3>XfRh0DnH7?lup9P%*$;%X zT*`L83=q=}c^fwVy<)BfE5^BNnE}c*yQ&c%sF80Wi%rJT5BmyDY0SMZwwUL`5M`z1 z#x}RkT#p8RL}3Jr>Ea;*kCxU&@C!DG}t6m8BhyOEz zSk=GS&zrNPsHW@@Sv|I5jX_9#X^a*=_USi8JR+wvNsJu!linZ*Nq4Bj=UG`oucEo| zFm&d)&Fi#(Okkqzc+J!V*c$q}HcXV^{m{Tos7at)2o6Oi4CWfUt2A zg)6Z9VBD%si7f)L<6_CmKs(ij6Hvcier&Ll2?IvL_R?b>l$Zs8tjmY|QPqKGUnM8Q z|2yH~(*AFw3viCHc4;rZ*Yniml7ND>Kkji+Zqwm~Zk3-Eab+D~CoTMs8$9N*C*zWlxG`HP(BL%ChHHTh z?0JhI^G^ob1L_a?h`JAKep3%IH}6~BS2F4Ls|&6(U_~)T*AkVfrG_+JSg01g){IgW z!=B_&cfH@MWVIBpUY~1xEha7(QTB;T2dC>)>_q0{ z0H^oHV+7KAgg4d-H)oJA*0?0{n(Xu=)w~rJgE1h=@#MXYyadp+;=b&4Q9*;@1x|^h ztDB~(eq^YiT)evh`&-0?_Me(z)M+9e`#W@(tb(RIloK{*}cX3Z7qVM6P(!}O+hxVJ7v{EmBfy{2ms@lF#teK3i! za(f_u`nSkcx9lGN{t?Z0+$c~g;keZ7cKPC5P!YXdEq8NmmgjaSfO44qkgqT0U{;!A4wvOc68C)WEz483)$ z#y;}82JyNkQ4FA~dxUg}`mEMxlLJlfNq2g)dN`CS=i9EHo9$X!@nE?%Eif8SPn9}~ z#{LLk{Z?CeopD^YP^*5b`p>bw^Se7-o-l=Q-jiC~8y9$j+z$v@Z`I-%o4eYFhcF?X z{U!VG;0*4>M_tFN#%ePqHtii?M!jT0B;Vq>)cS*E9L0Ihdr~sh!=>bsJa`}ZPgube zk`qf2#r$^P4s=A2?|#F;MjyZEA|A$hkt<2z8c z>eI%(N+K6UKFBu|C4icVrEt?MzCCcJCo)y=K2=a|l837!y|+4^@i&iOfMI_+VxK`(ZER$87w2ryl7?<2?Gt6IQ)|KRxkxY)%KmCoD; zfh&S(64_~J01Jc`7~R;kWio~x?9U&Pz0OHXJMxdEm%x-p?mV!vKb+CsW~LH=MXS6q zK+`u@(g<{S-wRS%Md94EewzCbC#k%nB$^>-^)YgQyi;foHte%=aR`qt*D>%T|Bs8#sw}0ic~N_V zOJv0)qC`D$nTrKaF5HKrN=w`q0NT(u^Hw@cYT1e-2;(zMvVDHq@7}6{4*Z z5ScX9Gb3#0hNkaf`#Jj%yN(s|%t;LKtt8$kT62I7nv}e(zCoMRbl}WLO}zOQRRqd% zeBgTQ8wZ-$W(Df4^~4mEobBe1{~A^PI1~Tw%GKY&hedteRHP6`+7?s35Rn&>zk&1j zN`}jKE&i$2!gV?Id|o_*J=)YLZccJ>F3irx944Ey=JA!T=}2zDDk2B%g;;x&Y8uXW zKe}EOgW@ix@r-EH(8`+q6zf;@nV-QaSL>S3L^YC6VFC?axgLlSF~PJ)lxAK;D0xp( zk|jWa%_({3$(L>`Ox4b-6=u)>VFw4?KJng@g-^oquz(_f&$C-%0lZRrofxDWZ05Mq z)A;S7$Z4^6yuR0YlAEH3rkYAGKGzqy937G4VaR4#JI#MA_E1I`hDWb0LSglO|eWG1Z>OxD+aD&%PBv0UG~+DW`U zqsZttG_5#w9#~hhzaB}pLR3+vO=76jL<$b%#^Dm#TXi}g5g?0yl5R2XF!;=l&*Ek4 zql^2qJEf(Le*1^Zn#rxan%P~XerZbL?~eq!0rd8~R1n?X;(n>&kbEkPv@7WO>=F(Q zy}zYEQ}o;yXty`VF}B;wC%Mi(@%@^jfGF8!=S;c$<-?zCm;#gH#%4X#&7t!b&&#_G zmA&;&vxVJO606JXJ9t`O`}22E-ht#Te)8w6PeAh4O!|iqYD5WY7p({KulWCIc|x? zZa`ijBxuU7i2oA#4F*Am&S;7r!wTYf!$A zaMeG%aDQVpWy-g!a?_Q`Hf$(d_v*`LKO17le=rNyAKDyRA+UK7mQ8lUtbGo36EWE| z`o`%C?0iEn6BujL#d|#0A2|n$&HeY1q|ZOGPx(=nX9GNNYkwZg+C=&JRgJob^}Bcu z&(gV~XE}z{x(C`cibTv`o(jhEP^d|eEEd}oCP_KLK<5wcb$zD9RMGG4FB|k0vMV8X z^@6e3ou-)u5+V|J{A5rVf&StBveq5-cmp_*^k;E-e#4#ZkWe$zj@f_i)lt}BI}6n z!!sO<)|1e4MSN3iE3qQ}$${(!$id*;`rYnMY;ouJqCi3Hdow5Kd7hCiGRKL!S}BjT z=vMZ4jQ2R9I-)^piIr$BS4DQiWOg}t>_r9nB*eR?aUcjWJqO2F0TX}7tb$5TUFxmO zImb{B(d@8(SitRMAT`%S$}o`)FDE9e?%^TH_rXuqp?pQ%z--kAre|&zap+8sb*Ap9 z1XLh&P;{;Wu94(}3+*Ji9Bs5vtAxch>kG^)m+ZBa9Aq%5(Yu&=2dL_^9!mv}j;6PoW2lrdW3p?&=-E;g_qa2_C(zRWo|Qp4{NcOu3if#xT!qDvix_(<$u4uDCml2Ar4RrX#7hKqz6X*6 z*O0MM(tr(*JwbY>!pb1EpF&ia_D{)H=sUtot?IQ$DkY?+=kO3jJtiM^ zZSMF!#i++q?kb|0)RcBkt9FYXDq6)Im8&Is5(=Z8oXYtLcwA1j;SrV4#E*oeToJc} zm4lAm`YIfha_DX|rA()`<363YjhS zRg63738Rx#r?i!18t!us@qgid{Mx&tJ86t-EhzQ2*w~1>&(>P=QEN7nSDPThrFdbn5^iNWH(th zqn^w|Hm&`o5C)p|x9JcRz};!1rkTLCUr?OXmE4dHp$L!6<`HjCTS|ykrLlT27ZtvPx_Yl!KbSv$FG zoh#({CFc6K>IPTri7WB=N3YgXajOWY=3u4l^zR&f z)=_-1utzRI_>Ac5qW4~rk~7vDttQ@z(Ocygb(W6AE1I%RMp&hgBMBJPs=65q(hT^= znBkJcXvcfJA%O<$^PH#G&8aZzt6^!5#fLCgx*&jzt)UWFV-cBfwHqQfSTvuoZ*Nh=$42k0wPU5^9{19dmXq~A>Kv2PskN?S}F-$IE2T9 z*cz`%K*IV_Jb+vbBH~I7C3NuAdleIAx$A)BtDUVwPo%QmZubwFDz7zT9p3$sPIFIe zcI9CbrGp700O0-UMoc`Fsv98CEuvh~C%&veg5lczC6n)O)s*#o0wAr-fy%-jTnhd* zQCh?ga^;oAgfLqaZ4SJhk(*J{_u}MAbvT6=i7MeO0nJiP3v;r~^tpAfDzZs$_WYBR4&JhboYjgTGI>{MF^{*j) zWZYBa5!zDMgYxTM@DT&-PeZ19Dah^E~2The{x1d>#m6S@OULb@$Mn@FhGmZdT(%Ol&u0t=gm=eTtbQj1-5M z8fU6@j7UV6YwOUpZS=~r58a`}Rgab1UQAK&j$ae!p!dnqrtEeeZp#GmIQRpGtP(I> z!y0(}W1Z9|sO%5Yzj1Msa7Imi8;(4FWW?eWUurPkM=cYNT6mh58D=p$t<71g}J||t&@LzOx zfMQMmw7#ggQZa%WAI`TMKOow9F6y4L<(VA_`qWGmGM-Jc3=C zlLl1J=b?&p%s5Lq3G2@M_L0EP{Z8Bx?~t4lEK>o52=Aq;9~i5V!?z@AhWx~?3`W1? zVpXYX3x+Fo;h^4rq5LpU>^nf(=VMjobXXx(zsM;PLd!t`kU{0aKJ(`HI%2bJGY4l& zi@pP%*N+FF`ag45gob?p8!ZowdG}$YVKoBj--Qn3W_v?rsCM@eCRkGX8W+b?c0t+r z`lWsqWq%g|S7W%U=x?$W}6RN(@(L8=^%)tg^3C;%bGLF#YVWGGO4 zp`RWFnw0=Gz4jC&YFP(T(=9LxMDh9^fAENN%nDqTl%vBAu^%Hz8%Xn_?Fsi7-Da&k z$xvFh0g7)o%*QO$s;h5&a0faIJy3Ik8U(na0A>8mn;Qd`8I^%%*^+{)Uh9Yk z)eD^z0e$v4DYTJeDe=7YLE?P_xhaY2f3=1tSwkn6rx3*hDpM`-1rTxtO$7QW;lEbQ z#n%dVn6V9R$e^x56{QbkY0^O%5{r*6P}wn?P;9n8gl3YPc(yq^QL@D)*rE) zFnGm&xW2?LntkySuJassNZsRX$^Q(#ZK|v@5nVlL+)3BunyZmg=0@EqiSm}6)5~iB z5FOi2Ff_ex@B9ogFG;Q=gxW?81bp)J1(4RWS>I@OllJmGvIX|#68%6EuK$s0b_)226v|`^C+xZj z@d&$D0T5Lq{f@<&ntZ}D58MBBd5PWAUfbPetqG5qU2iDKWnF;4$G>h%}_>sQO!pGPxds0Y3>Ws8goFk@Rmu*&eL%SMY!$M#F8 zf^40Trz}EQE0_EbfJf}Ed*54Pty%Jgo3iWcj6i5EM#Qh^FM*E~e-uw!akLTO>xJMS zHlPx;P3!2QNthAXXQ@T8laMkv#{;FMVWQ^WsV&(dn5M2oEvS16;Qee&ghRveic|9; zV)9l|XT)`6iN9m=4t*U>Ua&Sdfj1J{-+1&I#*C?@&T=%Y7E_Nzlpn(5EJ=8!8ly)hgV{v_>wSQ%%4&QzaE_6Udu))7PcJ<0zmHUQL`DI9T4p!L)hWIfWytZvXWM zQ%+=|+OMDtnBaomFWbDNu(evJj`*y9?$Ik`E_bwKa>GZMDS5tMTW6#$ z*+f>|(V%M+;OKf*enfWAB8Id#jf>4WQ?!cp24JTRXc^jQ3!*_UYwMIq5_->`E%KDL zqP*Nuu+m(E0W74ibDqp#y~>7a{=vmqkwfBcV4nuoPnQyf@g^p&pgHV5N=UbU_5&hA zG<;CM4ph6wPF0qj^@2?IM7FBg3j4yNUIt@0;R?NBbV6se3#rMpK4lDH9JaX zdecB=Asv+u4BSDK-j#>r@+ACGoi^&V45OPW5BQd*Hpt=_(VZiupRT;(q2(rznX>th z)2C=`Z{=TZJLfvjtl)uW41O~I2!PR=A3v6Y=Jw-fnSZ}njQ55aHC9g-04cVrfDD6& zmH-X{GKqnpS`=Q=wOv-8C&i&{(WXjsSJ2ux&dJl zdo3?gG1hVXgn2LwINkL%xWtsVI7drbbgmYw(LJ0U%*XhVgz9c6{FJS$6!85ap@i{SB;`I4__8*ngr(^ER;|bo_tb}RvUjRN z%Aa}~Vg!npu92Cy(J*VQV%KpKJ5OE>;4KTJn&7eeXh#jXJj`eLJtOB`%~c2x2|9HY zR@k8cO!;}mUehWhy=SYQP~FLXU0~{);;NRim!CT`Gsb4WEe-yt z_ei1%n+$s7Rymh5A5>GJMXw&5EBwaL*+E&8HVfqtH?- zO}H9%vN24U^Pv=<82%xTOU6uo{vIw)HPIQqquI;Az?1cL1OS`ehP#}u5cuZ2c(6n) zTJ@>F2xj3D>AYzDl~By&o4~i5Vub(SHm)}+WYolWZHAhJ|Nh(n6l7GT>!eIW{}&Ix BNeTb} literal 0 HcmV?d00001 diff --git a/doc/layout.xxml b/doc/layout.xxml new file mode 100644 index 0000000..1bd38d1 --- /dev/null +++ b/doc/layout.xxml @@ -0,0 +1,49 @@ + + +]> + + +Generating Layouts for Graph Drawing + +
2D layout generators + + + + + + + +
The DrL layout generator + + + + + + +
+ + + + + + + + + +
+ +
3D layout generators + + + + + +
+ +
Merging layouts + +
+ +
diff --git a/doc/licenses.xml b/doc/licenses.xml new file mode 100644 index 0000000..6e8a478 --- /dev/null +++ b/doc/licenses.xml @@ -0,0 +1,14 @@ + + +]> + + +Licenses for igraph and this manual + + + + + diff --git a/doc/licenses/Licence_CeCILL-B_V1-en.txt b/doc/licenses/Licence_CeCILL-B_V1-en.txt new file mode 100644 index 0000000..da41897 --- /dev/null +++ b/doc/licenses/Licence_CeCILL-B_V1-en.txt @@ -0,0 +1,515 @@ + +CeCILL-B FREE SOFTWARE LICENSE AGREEMENT + + + Notice + +This Agreement is a Free Software license agreement that is the result +of discussions between its authors in order to ensure compliance with +the two main principles guiding its drafting: + + * firstly, compliance with the principles governing the distribution + of Free Software: access to source code, broad rights granted to + users, + * secondly, the election of a governing law, French law, with which + it is conformant, both as regards the law of torts and + intellectual property law, and the protection that it offers to + both authors and holders of the economic rights over software. + +The authors of the CeCILL-B (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) +license are: + +Commissariat à l'Energie Atomique - CEA, a public scientific, technical +and industrial research establishment, having its principal place of +business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France. + +Centre National de la Recherche Scientifique - CNRS, a public scientific +and technological establishment, having its principal place of business +at 3 rue Michel-Ange, 75794 Paris cedex 16, France. + +Institut National de Recherche en Informatique et en Automatique - +INRIA, a public scientific and technological establishment, having its +principal place of business at Domaine de Voluceau, Rocquencourt, BP +105, 78153 Le Chesnay cedex, France. + + + Preamble + +This Agreement is an open source software license intended to give users +significant freedom to modify and redistribute the software licensed +hereunder. + +The exercising of this freedom is conditional upon a strong obligation +of giving credits for everybody that distributes a software +incorporating a software ruled by the current license so as all +contributions to be properly identified and acknowledged. + +In consideration of access to the source code and the rights to copy, +modify and redistribute granted by the license, users are provided only +with a limited warranty and the software's author, the holder of the +economic rights, and the successive licensors only have limited liability. + +In this respect, the risks associated with loading, using, modifying +and/or developing or reproducing the software by the user are brought to +the user's attention, given its Free Software status, which may make it +complicated to use, with the result that its use is reserved for +developers and experienced professionals having in-depth computer +knowledge. Users are therefore encouraged to load and test the +suitability of the software as regards their requirements in conditions +enabling the security of their systems and/or data to be ensured and, +more generally, to use and operate it in the same conditions of +security. This Agreement may be freely reproduced and published, +provided it is not altered, and that no provisions are either added or +removed herefrom. + +This Agreement may apply to any or all software for which the holder of +the economic rights decides to submit the use thereof to its provisions. + + + Article 1 - DEFINITIONS + +For the purpose of this Agreement, when the following expressions +commence with a capital letter, they shall have the following meaning: + +Agreement: means this license agreement, and its possible subsequent +versions and annexes. + +Software: means the software in its Object Code and/or Source Code form +and, where applicable, its documentation, "as is" when the Licensee +accepts the Agreement. + +Initial Software: means the Software in its Source Code and possibly its +Object Code form and, where applicable, its documentation, "as is" when +it is first distributed under the terms and conditions of the Agreement. + +Modified Software: means the Software modified by at least one +Contribution. + +Source Code: means all the Software's instructions and program lines to +which access is required so as to modify the Software. + +Object Code: means the binary files originating from the compilation of +the Source Code. + +Holder: means the holder(s) of the economic rights over the Initial +Software. + +Licensee: means the Software user(s) having accepted the Agreement. + +Contributor: means a Licensee having made at least one Contribution. + +Licensor: means the Holder, or any other individual or legal entity, who +distributes the Software under the Agreement. + +Contribution: means any or all modifications, corrections, translations, +adaptations and/or new functions integrated into the Software by any or +all Contributors, as well as any or all Internal Modules. + +Module: means a set of sources files including their documentation that +enables supplementary functions or services in addition to those offered +by the Software. + +External Module: means any or all Modules, not derived from the +Software, so that this Module and the Software run in separate address +spaces, with one calling the other when they are run. + +Internal Module: means any or all Module, connected to the Software so +that they both execute in the same address space. + +Parties: mean both the Licensee and the Licensor. + +These expressions may be used both in singular and plural form. + + + Article 2 - PURPOSE + +The purpose of the Agreement is the grant by the Licensor to the +Licensee of a non-exclusive, transferable and worldwide license for the +Software as set forth in Article 5 hereinafter for the whole term of the +protection granted by the rights over said Software. + + + Article 3 - ACCEPTANCE + +3.1 The Licensee shall be deemed as having accepted the terms and +conditions of this Agreement upon the occurrence of the first of the +following events: + + * (i) loading the Software by any or all means, notably, by + downloading from a remote server, or by loading from a physical + medium; + * (ii) the first time the Licensee exercises any of the rights + granted hereunder. + +3.2 One copy of the Agreement, containing a notice relating to the +characteristics of the Software, to the limited warranty, and to the +fact that its use is restricted to experienced users has been provided +to the Licensee prior to its acceptance as set forth in Article 3.1 +hereinabove, and the Licensee hereby acknowledges that it has read and +understood it. + + + Article 4 - EFFECTIVE DATE AND TERM + + + 4.1 EFFECTIVE DATE + +The Agreement shall become effective on the date when it is accepted by +the Licensee as set forth in Article 3.1. + + + 4.2 TERM + +The Agreement shall remain in force for the entire legal term of +protection of the economic rights over the Software. + + + Article 5 - SCOPE OF RIGHTS GRANTED + +The Licensor hereby grants to the Licensee, who accepts, the following +rights over the Software for any or all use, and for the term of the +Agreement, on the basis of the terms and conditions set forth hereinafter. + +Besides, if the Licensor owns or comes to own one or more patents +protecting all or part of the functions of the Software or of its +components, the Licensor undertakes not to enforce the rights granted by +these patents against successive Licensees using, exploiting or +modifying the Software. If these patents are transferred, the Licensor +undertakes to have the transferees subscribe to the obligations set +forth in this paragraph. + + + 5.1 RIGHT OF USE + +The Licensee is authorized to use the Software, without any limitation +as to its fields of application, with it being hereinafter specified +that this comprises: + + 1. permanent or temporary reproduction of all or part of the Software + by any or all means and in any or all form. + + 2. loading, displaying, running, or storing the Software on any or + all medium. + + 3. entitlement to observe, study or test its operation so as to + determine the ideas and principles behind any or all constituent + elements of said Software. This shall apply when the Licensee + carries out any or all loading, displaying, running, transmission + or storage operation as regards the Software, that it is entitled + to carry out hereunder. + + + 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS + +The right to make Contributions includes the right to translate, adapt, +arrange, or make any or all modifications to the Software, and the right +to reproduce the resulting software. + +The Licensee is authorized to make any or all Contributions to the +Software provided that it includes an explicit notice that it is the +author of said Contribution and indicates the date of the creation thereof. + + + 5.3 RIGHT OF DISTRIBUTION + +In particular, the right of distribution includes the right to publish, +transmit and communicate the Software to the general public on any or +all medium, and by any or all means, and the right to market, either in +consideration of a fee, or free of charge, one or more copies of the +Software by any means. + +The Licensee is further authorized to distribute copies of the modified +or unmodified Software to third parties according to the terms and +conditions set forth hereinafter. + + + 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION + +The Licensee is authorized to distribute true copies of the Software in +Source Code or Object Code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's + warranty and liability as set forth in Articles 8 and 9, + +and that, in the event that only the Object Code of the Software is +redistributed, the Licensee allows effective access to the full Source +Code of the Software at a minimum during the entire period of its +distribution of the Software, it being understood that the additional +cost of acquiring the Source Code shall not exceed the cost of +transferring the data. + + + 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE + +If the Licensee makes any Contribution to the Software, the resulting +Modified Software may be distributed under a license agreement other +than this Agreement subject to compliance with the provisions of Article +5.3.4. + + + 5.3.3 DISTRIBUTION OF EXTERNAL MODULES + +When the Licensee has developed an External Module, the terms and +conditions of this Agreement do not apply to said External Module, that +may be distributed under a separate license agreement. + + + 5.3.4 CREDITS + +Any Licensee who may distribute a Modified Software hereby expressly +agrees to: + + 1. indicate in the related documentation that it is based on the + Software licensed hereunder, and reproduce the intellectual + property notice for the Software, + + 2. ensure that written indications of the Software intended use, + intellectual property notice and license hereunder are included in + easily accessible format from the Modified Software interface, + + 3. mention, on a freely accessible website describing the Modified + Software, at least throughout the distribution term thereof, that + it is based on the Software licensed hereunder, and reproduce the + Software intellectual property notice, + + 4. where it is distributed to a third party that may distribute a + Modified Software without having to make its source code + available, make its best efforts to ensure that said third party + agrees to comply with the obligations set forth in this Article . + +If the Software, whether or not modified, is distributed with an +External Module designed for use in connection with the Software, the +Licensee shall submit said External Module to the foregoing obligations. + + + 5.3.5 COMPATIBILITY WITH THE CeCILL AND CeCILL-C LICENSES + +Where a Modified Software contains a Contribution subject to the CeCILL +license, the provisions set forth in Article 5.3.4 shall be optional. + +A Modified Software may be distributed under the CeCILL-C license. In +such a case the provisions set forth in Article 5.3.4 shall be optional. + + + Article 6 - INTELLECTUAL PROPERTY + + + 6.1 OVER THE INITIAL SOFTWARE + +The Holder owns the economic rights over the Initial Software. Any or +all use of the Initial Software is subject to compliance with the terms +and conditions under which the Holder has elected to distribute its work +and no one shall be entitled to modify the terms and conditions for the +distribution of said Initial Software. + +The Holder undertakes that the Initial Software will remain ruled at +least by this Agreement, for the duration set forth in Article 4.2. + + + 6.2 OVER THE CONTRIBUTIONS + +The Licensee who develops a Contribution is the owner of the +intellectual property rights over this Contribution as defined by +applicable law. + + + 6.3 OVER THE EXTERNAL MODULES + +The Licensee who develops an External Module is the owner of the +intellectual property rights over this External Module as defined by +applicable law and is free to choose the type of agreement that shall +govern its distribution. + + + 6.4 JOINT PROVISIONS + +The Licensee expressly undertakes: + + 1. not to remove, or modify, in any manner, the intellectual property + notices attached to the Software; + + 2. to reproduce said notices, in an identical manner, in the copies + of the Software modified or not. + +The Licensee undertakes not to directly or indirectly infringe the +intellectual property rights of the Holder and/or Contributors on the +Software and to take, where applicable, vis-à-vis its staff, any and all +measures required to ensure respect of said intellectual property rights +of the Holder and/or Contributors. + + + Article 7 - RELATED SERVICES + +7.1 Under no circumstances shall the Agreement oblige the Licensor to +provide technical assistance or maintenance services for the Software. + +However, the Licensor is entitled to offer this type of services. The +terms and conditions of such technical assistance, and/or such +maintenance, shall be set forth in a separate instrument. Only the +Licensor offering said maintenance and/or technical assistance services +shall incur liability therefor. + +7.2 Similarly, any Licensor is entitled to offer to its licensees, under +its sole responsibility, a warranty, that shall only be binding upon +itself, for the redistribution of the Software and/or the Modified +Software, under terms and conditions that it is free to decide. Said +warranty, and the financial terms and conditions of its application, +shall be subject of a separate instrument executed between the Licensor +and the Licensee. + + + Article 8 - LIABILITY + +8.1 Subject to the provisions of Article 8.2, the Licensee shall be +entitled to claim compensation for any direct loss it may have suffered +from the Software as a result of a fault on the part of the relevant +Licensor, subject to providing evidence thereof. + +8.2 The Licensor's liability is limited to the commitments made under +this Agreement and shall not be incurred as a result of in particular: +(i) loss due the Licensee's total or partial failure to fulfill its +obligations, (ii) direct or consequential loss that is suffered by the +Licensee due to the use or performance of the Software, and (iii) more +generally, any consequential loss. In particular the Parties expressly +agree that any or all pecuniary or business loss (i.e. loss of data, +loss of profits, operating loss, loss of customers or orders, +opportunity cost, any disturbance to business activities) or any or all +legal proceedings instituted against the Licensee by a third party, +shall constitute consequential loss and shall not provide entitlement to +any or all compensation from the Licensor. + + + Article 9 - WARRANTY + +9.1 The Licensee acknowledges that the scientific and technical +state-of-the-art when the Software was distributed did not enable all +possible uses to be tested and verified, nor for the presence of +possible defects to be detected. In this respect, the Licensee's +attention has been drawn to the risks associated with loading, using, +modifying and/or developing and reproducing the Software which are +reserved for experienced users. + +The Licensee shall be responsible for verifying, by any or all means, +the suitability of the product for its requirements, its good working +order, and for ensuring that it shall not cause damage to either persons +or properties. + +9.2 The Licensor hereby represents, in good faith, that it is entitled +to grant all the rights over the Software (including in particular the +rights set forth in Article 5). + +9.3 The Licensee acknowledges that the Software is supplied "as is" by +the Licensor without any other express or tacit warranty, other than +that provided for in Article 9.2 and, in particular, without any warranty +as to its commercial value, its secured, safe, innovative or relevant +nature. + +Specifically, the Licensor does not warrant that the Software is free +from any error, that it will operate without interruption, that it will +be compatible with the Licensee's own equipment and software +configuration, nor that it will meet the Licensee's requirements. + +9.4 The Licensor does not either expressly or tacitly warrant that the +Software does not infringe any third party intellectual property right +relating to a patent, software or any other property right. Therefore, +the Licensor disclaims any and all liability towards the Licensee +arising out of any or all proceedings for infringement that may be +instituted in respect of the use, modification and redistribution of the +Software. Nevertheless, should such proceedings be instituted against +the Licensee, the Licensor shall provide it with technical and legal +assistance for its defense. Such technical and legal assistance shall be +decided on a case-by-case basis between the relevant Licensor and the +Licensee pursuant to a memorandum of understanding. The Licensor +disclaims any and all liability as regards the Licensee's use of the +name of the Software. No warranty is given as regards the existence of +prior rights over the name of the Software or as regards the existence +of a trademark. + + + Article 10 - TERMINATION + +10.1 In the event of a breach by the Licensee of its obligations +hereunder, the Licensor may automatically terminate this Agreement +thirty (30) days after notice has been sent to the Licensee and has +remained ineffective. + +10.2 A Licensee whose Agreement is terminated shall no longer be +authorized to use, modify or distribute the Software. However, any +licenses that it may have granted prior to termination of the Agreement +shall remain valid subject to their having been granted in compliance +with the terms and conditions hereof. + + + Article 11 - MISCELLANEOUS + + + 11.1 EXCUSABLE EVENTS + +Neither Party shall be liable for any or all delay, or failure to +perform the Agreement, that may be attributable to an event of force +majeure, an act of God or an outside cause, such as defective +functioning or interruptions of the electricity or telecommunications +networks, network paralysis following a virus attack, intervention by +government authorities, natural disasters, water damage, earthquakes, +fire, explosions, strikes and labor unrest, war, etc. + +11.2 Any failure by either Party, on one or more occasions, to invoke +one or more of the provisions hereof, shall under no circumstances be +interpreted as being a waiver by the interested Party of its right to +invoke said provision(s) subsequently. + +11.3 The Agreement cancels and replaces any or all previous agreements, +whether written or oral, between the Parties and having the same +purpose, and constitutes the entirety of the agreement between said +Parties concerning said purpose. No supplement or modification to the +terms and conditions hereof shall be effective as between the Parties +unless it is made in writing and signed by their duly authorized +representatives. + +11.4 In the event that one or more of the provisions hereof were to +conflict with a current or future applicable act or legislative text, +said act or legislative text shall prevail, and the Parties shall make +the necessary amendments so as to comply with said act or legislative +text. All other provisions shall remain effective. Similarly, invalidity +of a provision of the Agreement, for any reason whatsoever, shall not +cause the Agreement as a whole to be invalid. + + + 11.5 LANGUAGE + +The Agreement is drafted in both French and English and both versions +are deemed authentic. + + + Article 12 - NEW VERSIONS OF THE AGREEMENT + +12.1 Any person is authorized to duplicate and distribute copies of this +Agreement. + +12.2 So as to ensure coherence, the wording of this Agreement is +protected and may only be modified by the authors of the License, who +reserve the right to periodically publish updates or new versions of the +Agreement, each with a separate number. These subsequent versions may +address new issues encountered by Free Software. + +12.3 Any Software distributed under a given version of the Agreement may +only be subsequently distributed under the same version of the Agreement +or a subsequent version. + + + Article 13 - GOVERNING LAW AND JURISDICTION + +13.1 The Agreement is governed by French law. The Parties agree to +endeavor to seek an amicable solution to any disagreements or disputes +that may arise during the performance of the Agreement. + +13.2 Failing an amicable solution within two (2) months as from their +occurrence, and unless emergency proceedings are necessary, the +disagreements or disputes shall be referred to the Paris Courts having +jurisdiction, by the more diligent Party. + + +Version 1.0 dated 2006-09-05. diff --git a/doc/licenses/Licence_CeCILL-B_V1-fr.txt b/doc/licenses/Licence_CeCILL-B_V1-fr.txt new file mode 100644 index 0000000..594abea --- /dev/null +++ b/doc/licenses/Licence_CeCILL-B_V1-fr.txt @@ -0,0 +1,519 @@ + +CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL-B + + + Avertissement + +Ce contrat est une licence de logiciel libre issue d'une concertation +entre ses auteurs afin que le respect de deux grands principes préside à +sa rédaction: + + * d'une part, le respect des principes de diffusion des logiciels + libres: accès au code source, droits étendus conférés aux + utilisateurs, + * d'autre part, la désignation d'un droit applicable, le droit + français, auquel elle est conforme, tant au regard du droit de la + responsabilité civile que du droit de la propriété intellectuelle + et de la protection qu'il offre aux auteurs et titulaires des + droits patrimoniaux sur un logiciel. + +Les auteurs de la licence CeCILL-B (pour Ce[a] C[nrs] I[nria] L[ogiciel] +L[ibre]) sont: + +Commissariat à l'Energie Atomique - CEA, établissement public de +recherche à caractère scientifique, technique et industriel, dont le +siège est situé 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris. + +Centre National de la Recherche Scientifique - CNRS, établissement +public à caractère scientifique et technologique, dont le siège est +situé 3 rue Michel-Ange, 75794 Paris cedex 16. + +Institut National de Recherche en Informatique et en Automatique - +INRIA, établissement public à caractère scientifique et technologique, +dont le siège est situé Domaine de Voluceau, Rocquencourt, BP 105, 78153 +Le Chesnay cedex. + + + Préambule + +Ce contrat est une licence de logiciel libre dont l'objectif est de +conférer aux utilisateurs une très large liberté de modification et de +redistribution du logiciel régi par cette licence. + +L'exercice de cette liberté est assorti d'une obligation forte de +citation à la charge de ceux qui distribueraient un logiciel incorporant +un logiciel régi par la présente licence afin d'assurer que les +contributions de tous soient correctement identifiées et reconnues. + +L'accessibilité au code source et les droits de copie, de modification +et de redistribution qui découlent de ce contrat ont pour contrepartie +de n'offrir aux utilisateurs qu'une garantie limitée et de ne faire +peser sur l'auteur du logiciel, le titulaire des droits patrimoniaux et +les concédants successifs qu'une responsabilité restreinte. + +A cet égard l'attention de l'utilisateur est attirée sur les risques +associés au chargement, à l'utilisation, à la modification et/ou au +développement et à la reproduction du logiciel par l'utilisateur étant +donné sa spécificité de logiciel libre, qui peut le rendre complexe à +manipuler et qui le réserve donc à des développeurs ou des +professionnels avertis possédant des connaissances informatiques +approfondies. Les utilisateurs sont donc invités à charger et tester +l'adéquation du logiciel à leurs besoins dans des conditions permettant +d'assurer la sécurité de leurs systèmes et/ou de leurs données et, plus +généralement, à l'utiliser et l'exploiter dans les mêmes conditions de +sécurité. Ce contrat peut être reproduit et diffusé librement, sous +réserve de le conserver en l'état, sans ajout ni suppression de clauses. + +Ce contrat est susceptible de s'appliquer à tout logiciel dont le +titulaire des droits patrimoniaux décide de soumettre l'exploitation aux +dispositions qu'il contient. + + + Article 1 - DEFINITIONS + +Dans ce contrat, les termes suivants, lorsqu'ils seront écrits avec une +lettre capitale, auront la signification suivante: + +Contrat: désigne le présent contrat de licence, ses éventuelles versions +postérieures et annexes. + +Logiciel: désigne le logiciel sous sa forme de Code Objet et/ou de Code +Source et le cas échéant sa documentation, dans leur état au moment de +l'acceptation du Contrat par le Licencié. + +Logiciel Initial: désigne le Logiciel sous sa forme de Code Source et +éventuellement de Code Objet et le cas échéant sa documentation, dans +leur état au moment de leur première diffusion sous les termes du Contrat. + +Logiciel Modifié: désigne le Logiciel modifié par au moins une +Contribution. + +Code Source: désigne l'ensemble des instructions et des lignes de +programme du Logiciel et auquel l'accès est nécessaire en vue de +modifier le Logiciel. + +Code Objet: désigne les fichiers binaires issus de la compilation du +Code Source. + +Titulaire: désigne le ou les détenteurs des droits patrimoniaux d'auteur +sur le Logiciel Initial. + +Licencié: désigne le ou les utilisateurs du Logiciel ayant accepté le +Contrat. + +Contributeur: désigne le Licencié auteur d'au moins une Contribution. + +Concédant: désigne le Titulaire ou toute personne physique ou morale +distribuant le Logiciel sous le Contrat. + +Contribution: désigne l'ensemble des modifications, corrections, +traductions, adaptations et/ou nouvelles fonctionnalités intégrées dans +le Logiciel par tout Contributeur, ainsi que tout Module Interne. + +Module: désigne un ensemble de fichiers sources y compris leur +documentation qui permet de réaliser des fonctionnalités ou services +supplémentaires à ceux fournis par le Logiciel. + +Module Externe: désigne tout Module, non dérivé du Logiciel, tel que ce +Module et le Logiciel s'exécutent dans des espaces d'adressage +différents, l'un appelant l'autre au moment de leur exécution. + +Module Interne: désigne tout Module lié au Logiciel de telle sorte +qu'ils s'exécutent dans le même espace d'adressage. + +Parties: désigne collectivement le Licencié et le Concédant. + +Ces termes s'entendent au singulier comme au pluriel. + + + Article 2 - OBJET + +Le Contrat a pour objet la concession par le Concédant au Licencié d'une +licence non exclusive, cessible et mondiale du Logiciel telle que +définie ci-après à l'article 5 pour toute la durée de protection des droits +portant sur ce Logiciel. + + + Article 3 - ACCEPTATION + +3.1 L'acceptation par le Licencié des termes du Contrat est réputée +acquise du fait du premier des faits suivants: + + * (i) le chargement du Logiciel par tout moyen notamment par + téléchargement à partir d'un serveur distant ou par chargement à + partir d'un support physique; + * (ii) le premier exercice par le Licencié de l'un quelconque des + droits concédés par le Contrat. + +3.2 Un exemplaire du Contrat, contenant notamment un avertissement +relatif aux spécificités du Logiciel, à la restriction de garantie et à +la limitation à un usage par des utilisateurs expérimentés a été mis à +disposition du Licencié préalablement à son acceptation telle que +définie à l'article 3.1 ci dessus et le Licencié reconnaît en avoir pris +connaissance. + + + Article 4 - ENTREE EN VIGUEUR ET DUREE + + + 4.1 ENTREE EN VIGUEUR + +Le Contrat entre en vigueur à la date de son acceptation par le Licencié +telle que définie en 3.1. + + + 4.2 DUREE + +Le Contrat produira ses effets pendant toute la durée légale de +protection des droits patrimoniaux portant sur le Logiciel. + + + Article 5 - ETENDUE DES DROITS CONCEDES + +Le Concédant concède au Licencié, qui accepte, les droits suivants sur +le Logiciel pour toutes destinations et pour la durée du Contrat dans +les conditions ci-après détaillées. + +Par ailleurs, si le Concédant détient ou venait à détenir un ou +plusieurs brevets d'invention protégeant tout ou partie des +fonctionnalités du Logiciel ou de ses composants, il s'engage à ne pas +opposer les éventuels droits conférés par ces brevets aux Licenciés +successifs qui utiliseraient, exploiteraient ou modifieraient le +Logiciel. En cas de cession de ces brevets, le Concédant s'engage à +faire reprendre les obligations du présent alinéa aux cessionnaires. + + + 5.1 DROIT D'UTILISATION + +Le Licencié est autorisé à utiliser le Logiciel, sans restriction quant +aux domaines d'application, étant ci-après précisé que cela comporte: + + 1. la reproduction permanente ou provisoire du Logiciel en tout ou + partie par tout moyen et sous toute forme. + + 2. le chargement, l'affichage, l'exécution, ou le stockage du + Logiciel sur tout support. + + 3. la possibilité d'en observer, d'en étudier, ou d'en tester le + fonctionnement afin de déterminer les idées et principes qui sont + à la base de n'importe quel élément de ce Logiciel; et ceci, + lorsque le Licencié effectue toute opération de chargement, + d'affichage, d'exécution, de transmission ou de stockage du + Logiciel qu'il est en droit d'effectuer en vertu du Contrat. + + + 5.2 DROIT D'APPORTER DES CONTRIBUTIONS + +Le droit d'apporter des Contributions comporte le droit de traduire, +d'adapter, d'arranger ou d'apporter toute autre modification au Logiciel +et le droit de reproduire le logiciel en résultant. + +Le Licencié est autorisé à apporter toute Contribution au Logiciel sous +réserve de mentionner, de façon explicite, son nom en tant qu'auteur de +cette Contribution et la date de création de celle-ci. + + + 5.3 DROIT DE DISTRIBUTION + +Le droit de distribution comporte notamment le droit de diffuser, de +transmettre et de communiquer le Logiciel au public sur tout support et +par tout moyen ainsi que le droit de mettre sur le marché à titre +onéreux ou gratuit, un ou des exemplaires du Logiciel par tout procédé. + +Le Licencié est autorisé à distribuer des copies du Logiciel, modifié ou +non, à des tiers dans les conditions ci-après détaillées. + + + 5.3.1 DISTRIBUTION DU LOGICIEL SANS MODIFICATION + +Le Licencié est autorisé à distribuer des copies conformes du Logiciel, +sous forme de Code Source ou de Code Objet, à condition que cette +distribution respecte les dispositions du Contrat dans leur totalité et +soit accompagnée: + + 1. d'un exemplaire du Contrat, + + 2. d'un avertissement relatif à la restriction de garantie et de + responsabilité du Concédant telle que prévue aux articles 8 + et 9, + +et que, dans le cas où seul le Code Objet du Logiciel est redistribué, +le Licencié permette un accès effectif au Code Source complet du +Logiciel pendant au moins toute la durée de sa distribution du Logiciel, +étant entendu que le coût additionnel d'acquisition du Code Source ne +devra pas excéder le simple coût de transfert des données. + + + 5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE + +Lorsque le Licencié apporte une Contribution au Logiciel, le Logiciel +Modifié peut être distribué sous un contrat de licence autre que le +présent Contrat sous réserve du respect des dispositions de l'article +5.3.4. + + + 5.3.3 DISTRIBUTION DES MODULES EXTERNES + +Lorsque le Licencié a développé un Module Externe les conditions du +Contrat ne s'appliquent pas à ce Module Externe, qui peut être distribué +sous un contrat de licence différent. + + + 5.3.4 CITATIONS + +Le Licencié qui distribue un Logiciel Modifié s'engage expressément: + + 1. à indiquer dans sa documentation qu'il a été réalisé à partir du + Logiciel régi par le Contrat, en reproduisant les mentions de + propriété intellectuelle du Logiciel, + + 2. à faire en sorte que l'utilisation du Logiciel, ses mentions de + propriété intellectuelle et le fait qu'il est régi par le Contrat + soient indiqués dans un texte facilement accessible depuis + l'interface du Logiciel Modifié, + + 3. à mentionner, sur un site Web librement accessible décrivant le + Logiciel Modifié, et pendant au moins toute la durée de sa + distribution, qu'il a été réalisé à partir du Logiciel régi par le + Contrat, en reproduisant les mentions de propriété intellectuelle + du Logiciel, + + 4. lorsqu'il le distribue à un tiers susceptible de distribuer + lui-même un Logiciel Modifié, sans avoir à en distribuer le code + source, à faire ses meilleurs efforts pour que les obligations du + présent article 5.3.4 soient reprises par le dit tiers. + +Lorsque le Logiciel modifié ou non est distribué avec un Module Externe +qui a été conçu pour l'utiliser, le Licencié doit soumettre le dit +Module Externe aux obligations précédentes. + + + 5.3.5 COMPATIBILITE AVEC LES LICENCES CeCILL et CeCILL-C + +Lorsqu'un Logiciel Modifié contient une Contribution soumise au contrat +de licence CeCILL, les stipulations prévues à l'article 5.3.4 sont +facultatives. + +Un Logiciel Modifié peut être distribué sous le contrat de licence +CeCILL-C. Les stipulations prévues à l'article 5.3.4 sont alors +facultatives. + + + Article 6 - PROPRIETE INTELLECTUELLE + + + 6.1 SUR LE LOGICIEL INITIAL + +Le Titulaire est détenteur des droits patrimoniaux sur le Logiciel +Initial. Toute utilisation du Logiciel Initial est soumise au respect +des conditions dans lesquelles le Titulaire a choisi de diffuser son +oeuvre et nul autre n'a la faculté de modifier les conditions de +diffusion de ce Logiciel Initial. + +Le Titulaire s'engage à ce que le Logiciel Initial reste au moins régi +par le Contrat et ce, pour la durée visée à l'article 4.2. + + + 6.2 SUR LES CONTRIBUTIONS + +Le Licencié qui a développé une Contribution est titulaire sur celle-ci +des droits de propriété intellectuelle dans les conditions définies par +la législation applicable. + + + 6.3 SUR LES MODULES EXTERNES + +Le Licencié qui a développé un Module Externe est titulaire sur celui-ci +des droits de propriété intellectuelle dans les conditions définies par +la législation applicable et reste libre du choix du contrat régissant +sa diffusion. + + + 6.4 DISPOSITIONS COMMUNES + +Le Licencié s'engage expressément: + + 1. à ne pas supprimer ou modifier de quelque manière que ce soit les + mentions de propriété intellectuelle apposées sur le Logiciel; + + 2. à reproduire à l'identique lesdites mentions de propriété + intellectuelle sur les copies du Logiciel modifié ou non. + +Le Licencié s'engage à ne pas porter atteinte, directement ou +indirectement, aux droits de propriété intellectuelle du Titulaire et/ou +des Contributeurs sur le Logiciel et à prendre, le cas échéant, à +l'égard de son personnel toutes les mesures nécessaires pour assurer le +respect des dits droits de propriété intellectuelle du Titulaire et/ou +des Contributeurs. + + + Article 7 - SERVICES ASSOCIES + +7.1 Le Contrat n'oblige en aucun cas le Concédant à la réalisation de +prestations d'assistance technique ou de maintenance du Logiciel. + +Cependant le Concédant reste libre de proposer ce type de services. Les +termes et conditions d'une telle assistance technique et/ou d'une telle +maintenance seront alors déterminés dans un acte séparé. Ces actes de +maintenance et/ou assistance technique n'engageront que la seule +responsabilité du Concédant qui les propose. + +7.2 De même, tout Concédant est libre de proposer, sous sa seule +responsabilité, à ses licenciés une garantie, qui n'engagera que lui, +lors de la redistribution du Logiciel et/ou du Logiciel Modifié et ce, +dans les conditions qu'il souhaite. Cette garantie et les modalités +financières de son application feront l'objet d'un acte séparé entre le +Concédant et le Licencié. + + + Article 8 - RESPONSABILITE + +8.1 Sous réserve des dispositions de l'article 8.2, le Licencié a la +faculté, sous réserve de prouver la faute du Concédant concerné, de +solliciter la réparation du préjudice direct qu'il subirait du fait du +Logiciel et dont il apportera la preuve. + +8.2 La responsabilité du Concédant est limitée aux engagements pris en +application du Contrat et ne saurait être engagée en raison notamment: +(i) des dommages dus à l'inexécution, totale ou partielle, de ses +obligations par le Licencié, (ii) des dommages directs ou indirects +découlant de l'utilisation ou des performances du Logiciel subis par le +Licencié et (iii) plus généralement d'un quelconque dommage indirect. En +particulier, les Parties conviennent expressément que tout préjudice +financier ou commercial (par exemple perte de données, perte de +bénéfices, perte d'exploitation, perte de clientèle ou de commandes, +manque à gagner, trouble commercial quelconque) ou toute action dirigée +contre le Licencié par un tiers, constitue un dommage indirect et +n'ouvre pas droit à réparation par le Concédant. + + + Article 9 - GARANTIE + +9.1 Le Licencié reconnaît que l'état actuel des connaissances +scientifiques et techniques au moment de la mise en circulation du +Logiciel ne permet pas d'en tester et d'en vérifier toutes les +utilisations ni de détecter l'existence d'éventuels défauts. L'attention +du Licencié a été attirée sur ce point sur les risques associés au +chargement, à l'utilisation, la modification et/ou au développement et à +la reproduction du Logiciel qui sont réservés à des utilisateurs avertis. + +Il relève de la responsabilité du Licencié de contrôler, par tous +moyens, l'adéquation du produit à ses besoins, son bon fonctionnement et +de s'assurer qu'il ne causera pas de dommages aux personnes et aux biens. + +9.2 Le Concédant déclare de bonne foi être en droit de concéder +l'ensemble des droits attachés au Logiciel (comprenant notamment les +droits visés à l'article 5). + +9.3 Le Licencié reconnaît que le Logiciel est fourni "en l'état" par le +Concédant sans autre garantie, expresse ou tacite, que celle prévue à +l'article 9.2 et notamment sans aucune garantie sur sa valeur commerciale, +son caractère sécurisé, innovant ou pertinent. + +En particulier, le Concédant ne garantit pas que le Logiciel est exempt +d'erreur, qu'il fonctionnera sans interruption, qu'il sera compatible +avec l'équipement du Licencié et sa configuration logicielle ni qu'il +remplira les besoins du Licencié. + +9.4 Le Concédant ne garantit pas, de manière expresse ou tacite, que le +Logiciel ne porte pas atteinte à un quelconque droit de propriété +intellectuelle d'un tiers portant sur un brevet, un logiciel ou sur tout +autre droit de propriété. Ainsi, le Concédant exclut toute garantie au +profit du Licencié contre les actions en contrefaçon qui pourraient être +diligentées au titre de l'utilisation, de la modification, et de la +redistribution du Logiciel. Néanmoins, si de telles actions sont +exercées contre le Licencié, le Concédant lui apportera son aide +technique et juridique pour sa défense. Cette aide technique et +juridique est déterminée au cas par cas entre le Concédant concerné et +le Licencié dans le cadre d'un protocole d'accord. Le Concédant dégage +toute responsabilité quant à l'utilisation de la dénomination du +Logiciel par le Licencié. Aucune garantie n'est apportée quant à +l'existence de droits antérieurs sur le nom du Logiciel et sur +l'existence d'une marque. + + + Article 10 - RESILIATION + +10.1 En cas de manquement par le Licencié aux obligations mises à sa +charge par le Contrat, le Concédant pourra résilier de plein droit le +Contrat trente (30) jours après notification adressée au Licencié et +restée sans effet. + +10.2 Le Licencié dont le Contrat est résilié n'est plus autorisé à +utiliser, modifier ou distribuer le Logiciel. Cependant, toutes les +licences qu'il aura concédées antérieurement à la résiliation du Contrat +resteront valides sous réserve qu'elles aient été effectuées en +conformité avec le Contrat. + + + Article 11 - DISPOSITIONS DIVERSES + + + 11.1 CAUSE EXTERIEURE + +Aucune des Parties ne sera responsable d'un retard ou d'une défaillance +d'exécution du Contrat qui serait dû à un cas de force majeure, un cas +fortuit ou une cause extérieure, telle que, notamment, le mauvais +fonctionnement ou les interruptions du réseau électrique ou de +télécommunication, la paralysie du réseau liée à une attaque +informatique, l'intervention des autorités gouvernementales, les +catastrophes naturelles, les dégâts des eaux, les tremblements de terre, +le feu, les explosions, les grèves et les conflits sociaux, l'état de +guerre... + +11.2 Le fait, par l'une ou l'autre des Parties, d'omettre en une ou +plusieurs occasions de se prévaloir d'une ou plusieurs dispositions du +Contrat, ne pourra en aucun cas impliquer renonciation par la Partie +intéressée à s'en prévaloir ultérieurement. + +11.3 Le Contrat annule et remplace toute convention antérieure, écrite +ou orale, entre les Parties sur le même objet et constitue l'accord +entier entre les Parties sur cet objet. Aucune addition ou modification +aux termes du Contrat n'aura d'effet à l'égard des Parties à moins +d'être faite par écrit et signée par leurs représentants dûment habilités. + +11.4 Dans l'hypothèse où une ou plusieurs des dispositions du Contrat +s'avèrerait contraire à une loi ou à un texte applicable, existants ou +futurs, cette loi ou ce texte prévaudrait, et les Parties feraient les +amendements nécessaires pour se conformer à cette loi ou à ce texte. +Toutes les autres dispositions resteront en vigueur. De même, la +nullité, pour quelque raison que ce soit, d'une des dispositions du +Contrat ne saurait entraîner la nullité de l'ensemble du Contrat. + + + 11.5 LANGUE + +Le Contrat est rédigé en langue française et en langue anglaise, ces +deux versions faisant également foi. + + + Article 12 - NOUVELLES VERSIONS DU CONTRAT + +12.1 Toute personne est autorisée à copier et distribuer des copies de +ce Contrat. + +12.2 Afin d'en préserver la cohérence, le texte du Contrat est protégé +et ne peut être modifié que par les auteurs de la licence, lesquels se +réservent le droit de publier périodiquement des mises à jour ou de +nouvelles versions du Contrat, qui posséderont chacune un numéro +distinct. Ces versions ultérieures seront susceptibles de prendre en +compte de nouvelles problématiques rencontrées par les logiciels libres. + +12.3 Tout Logiciel diffusé sous une version donnée du Contrat ne pourra +faire l'objet d'une diffusion ultérieure que sous la même version du +Contrat ou une version postérieure. + + + Article 13 - LOI APPLICABLE ET COMPETENCE TERRITORIALE + +13.1 Le Contrat est régi par la loi française. Les Parties conviennent +de tenter de régler à l'amiable les différends ou litiges qui +viendraient à se produire par suite ou à l'occasion du Contrat. + +13.2 A défaut d'accord amiable dans un délai de deux (2) mois à compter +de leur survenance et sauf situation relevant d'une procédure d'urgence, +les différends ou litiges seront portés par la Partie la plus diligente +devant les Tribunaux compétents de Paris. + + +Version 1.0 du 2006-09-05. diff --git a/doc/licenses/gpl-2.0.txt b/doc/licenses/gpl-2.0.txt new file mode 100644 index 0000000..d420d1e --- /dev/null +++ b/doc/licenses/gpl-2.0.txt @@ -0,0 +1,281 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + \ No newline at end of file diff --git a/doc/licenses/gpl-3.0.txt b/doc/licenses/gpl-3.0.txt new file mode 100644 index 0000000..810fce6 --- /dev/null +++ b/doc/licenses/gpl-3.0.txt @@ -0,0 +1,621 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS diff --git a/doc/licenses/lgpl-2.1.txt b/doc/licenses/lgpl-2.1.txt new file mode 100644 index 0000000..a345e48 --- /dev/null +++ b/doc/licenses/lgpl-2.1.txt @@ -0,0 +1,459 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + \ No newline at end of file diff --git a/doc/licenses/lgpl-3.0.txt b/doc/licenses/lgpl-3.0.txt new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/doc/licenses/lgpl-3.0.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/doc/matrix.xxml b/doc/matrix.xxml new file mode 100644 index 0000000..7a17224 --- /dev/null +++ b/doc/matrix.xxml @@ -0,0 +1,119 @@ + + +]> + +
+Matrices + +
+ +
+ +
+ + + + +
+ +
Initializing elements + + +
+ +
Copying matrices + + + +
+ +
+ + + + + +
+ +
Operations on rows and columns + + + + + + + + + +
+ +
+Matrix operations + + + + + + + + + + + +
+ +
Matrix comparisons + + + + + +
+ +
+Combining matrices + + +
+ +
+Finding minimum and maximum + + + + + + +
+ +
+Matrix properties + + + + + + + + +
+ +
Searching for elements + + +
+ +
+Resizing operations + + + + + + +
+ +
+ diff --git a/doc/memory.xxml b/doc/memory.xxml new file mode 100644 index 0000000..8fb92b7 --- /dev/null +++ b/doc/memory.xxml @@ -0,0 +1,13 @@ + + +]> + + +Memory (de)allocation + + + + + diff --git a/doc/motifs.xxml b/doc/motifs.xxml new file mode 100644 index 0000000..c33d143 --- /dev/null +++ b/doc/motifs.xxml @@ -0,0 +1,32 @@ + + +]> + + +Graph Motifs, Dyad Census and Triad Census + + +This section deals with functions which find small induced subgraphs in a +graph. These were first defined for subgraphs of two and three vertices +by Holland and Leinhardt, and named dyad census and triad census. + + + + + +
Finding triangles + + +
+ +
Graph motifs + + + + + +
+ +
diff --git a/doc/nongraph.xxml b/doc/nongraph.xxml new file mode 100644 index 0000000..678abd8 --- /dev/null +++ b/doc/nongraph.xxml @@ -0,0 +1,37 @@ + + +]> + + +Not Graph Related Functions + +
Igraph Version Number + +
+ +
Running Mean of a Time Series + +
+ +
Random Sampling from Very Long Sequences + +
+ +
Random Sampling of Spatial Points + + + +
+ +
Convex Hull of A Set of Points on A Plane + +
+ +
Fitting Power-law Distributions to Empirical Data + + +
+ +
diff --git a/doc/operators.xxml b/doc/operators.xxml new file mode 100644 index 0000000..536bcb8 --- /dev/null +++ b/doc/operators.xxml @@ -0,0 +1,25 @@ + + +]> + + +Graph Operators + +
Union and intersection + + + + + + +
+ +
Other set-like operators + + + +
+ +
diff --git a/doc/papers/iccs06/ICCS.bst b/doc/papers/iccs06/ICCS.bst new file mode 100644 index 0000000..f5efa52 --- /dev/null +++ b/doc/papers/iccs06/ICCS.bst @@ -0,0 +1 @@ +% Adapted to the specifications of the % Proceedings of the International Conference on Complex Systems % B. Sendhoff 09/03/97 % % PLEASE NOTE: % only the following entries have been tested: % article, inproceedings, book, inbook, techreport % the other entries should run without error but might not always % stick to the conventions of the ICCS 1997 % % ======================================================================== % % ACM Transactions bibliography style (24-Jan-88 version) % A lot like abbrv.bst, but names come out "Last, initials", and in \sc. % Some dates are parenthesized. % % History % 2/ 6/86 (HWT) Original version, by Howard Trickey. % 3/ 5/86 (HWT) Put in pp. everywhere but articles, as per ACM style. % 1/24/88 (OP&HWT) Updated for BibTeX version 0.99a, Oren Patashnik; % corrected the abbreviations to "Mar." and "Sept."; % THIS `acm' VERSION DOES NOT WORK WITH BIBTEX 0.98i. ENTRY { address author booktitle chapter edition editor howpublished institution journal key month note number organization pages publisher school series title type volume year } {} { label } INTEGERS { output.state before.all mid.sentence after.sentence after.block } FUNCTION {init.state.consts} { #0 'before.all := #1 'mid.sentence := #2 'after.sentence := #3 'after.block := } STRINGS { s t t1 t2 } FUNCTION {output.nonnull} { 's := output.state mid.sentence = { " " * write$ } { output.state after.block = %% { add.period$ write$ { ", " * write$ newline$ "\newblock " write$ } { output.state before.all = 'write$ { add.period$ " " * write$ } if$ } if$ mid.sentence 'output.state := } if$ s } FUNCTION {output} { duplicate$ empty$ 'pop$ 'output.nonnull if$ } FUNCTION {output.check} { 't := duplicate$ empty$ { pop$ "empty " t * " in " * cite$ * warning$ } 'output.nonnull if$ } FUNCTION {output.bibitem} { newline$ "\bibitem{" write$ cite$ write$ "}" write$ newline$ "" before.all 'output.state := } FUNCTION {fin.entry} { add.period$ write$ newline$ } FUNCTION {new.block} { output.state before.all = 'skip$ { after.block 'output.state := } if$ } FUNCTION {new.sentence} { output.state after.block = 'skip$ { output.state before.all = 'skip$ { after.sentence 'output.state := } if$ } if$ } FUNCTION {not} { { #0 } { #1 } if$ } FUNCTION {and} { 'skip$ { pop$ #0 } if$ } FUNCTION {or} { { pop$ #1 } 'skip$ if$ } FUNCTION {new.block.checka} { empty$ 'skip$ 'new.block if$ } FUNCTION {new.block.checkb} { empty$ swap$ empty$ and 'skip$ 'new.block if$ } FUNCTION {field.or.null} { duplicate$ empty$ { pop$ "" } 'skip$ if$ } FUNCTION {emphasize} { duplicate$ empty$ { pop$ "" } { "{\em " swap$ * "}" * } if$ } FUNCTION {emphasizeic} { duplicate$ empty$ { pop$ "" } { "{\em " swap$ * "\/}" * } if$ } FUNCTION {scapify} { duplicate$ empty$ { pop$ "" } { "{\sc " swap$ * "}" * } if$ } INTEGERS { nameptr namesleft numnames } FUNCTION {format.names} { 's := #1 'nameptr := % nameptr = 1; s num.names$ 'numnames := % numnames = num.name$(s); numnames 'namesleft := { namesleft #0 > } { nameptr #1 = { s nameptr "{vv~}{ll}" format.name$ 't1 := s nameptr "{, jj}{, ff}" format.name$ 't2 := t1 "{\rm " * t2 * "}" * 't := } { s nameptr "{ff~}{vv~}" format.name$ 't1 := s nameptr "{ll}{, jj}" format.name$ 't2 := "{\rm " t1 * "}" * t2 * 't := } if$ nameptr #1 > { namesleft #1 > { ", " * t * } { t "others" = { ", {\rm et~al.}" * } { ", {\rm and} " * t * } if$ } if$ } 't if$ nameptr #1 + 'nameptr := namesleft #1 - 'namesleft := } while$ } % For names inside entries (e.g., editors of an "In ..."); % this is exactly ABBRV.BST's `format.names' function. FUNCTION {format.innames} { 's := #1 'nameptr := s num.names$ 'numnames := numnames 'namesleft := { namesleft #0 > } { s nameptr "{f.~}{vv~}{ll}{, jj}" format.name$ 't := nameptr #1 > { namesleft #1 > { ", " * t * } { numnames #2 > { "," * } 'skip$ if$ t "others" = { " et~al." * } { " and " * t * } if$ } if$ } 't if$ nameptr #1 + 'nameptr := namesleft #1 - 'namesleft := } while$ } FUNCTION {format.authors} { author empty$ { "" } { author format.names scapify } if$ } FUNCTION {format.editors} { editor empty$ { "" } { editor format.names scapify editor num.names$ #1 > { " eds." * } { " ed." * } if$ } if$ } FUNCTION {format.ineditors} { editor empty$ { "" } { "(" editor format.innames scapify * editor num.names$ #1 > { " eds.)" * } { " ed.)" * } if$ } if$ } FUNCTION {format.title} { title empty$ { "" } { "``" title "''" * "t" change.case$ * } if$ } FUNCTION {n.dashify} { 't := "" { t empty$ not } { t #1 #1 substring$ "-" = { t #1 #2 substring$ "--" = not { "--" * t #2 global.max$ substring$ 't := } { { t #1 #1 substring$ "-" = } { "-" * t #2 global.max$ substring$ 't := } while$ } if$ } { t #1 #1 substring$ * t #2 global.max$ substring$ 't := } if$ } while$ } FUNCTION {format.date} { year empty$ { month empty$ { "" } { "there's a month but no year in " cite$ * warning$ month } if$ } { month empty$ %% 'year { "(" year * ")" * } { "(" month * " " * year * ")" *} if$ } if$ } FUNCTION {format.btitle} { title emphasize } FUNCTION {tie.or.space.connect} { duplicate$ text.length$ #3 < { "~" } { " " } if$ swap$ * * } FUNCTION {either.or.check} { empty$ 'pop$ { "can't use both " swap$ * " fields in " * cite$ * warning$ } if$ } FUNCTION {format.bvolume} { volume empty$ { "" } { "vol.~" volume * series empty$ 'skip$ { " of " * series emphasize * } if$ "volume and number" number either.or.check } if$ } FUNCTION {format.number.series} { volume empty$ { number empty$ { series field.or.null } { output.state mid.sentence = { "no.~" } { "No.~" } if$ number * series empty$ { "there's a number but no series in " cite$ * warning$ } { " in " * series * } if$ } if$ } { "" } if$ } FUNCTION {format.edition} { edition empty$ { "" } { output.state mid.sentence = { edition "l" change.case$ "~ed." * } { edition "t" change.case$ "~ed." * } if$ } if$ } FUNCTION {format.pages} { pages empty$ { "" } { pages n.dashify } if$ } INTEGERS { multiresult } FUNCTION {multi.page.check} { 't := #0 'multiresult := { multiresult not t empty$ not and } { t #1 #1 substring$ duplicate$ "-" = swap$ duplicate$ "," = swap$ "+" = or or { #1 'multiresult := } { t #2 global.max$ substring$ 't := } if$ } while$ multiresult } FUNCTION {format.pp.pages} { pages empty$ { "" } { pages multi.page.check { "pp.~" pages n.dashify * } { "p.~" pages * } if$ } if$ } FUNCTION {format.journal.vol.num.date} { journal empty$ { "empty journal in " cite$ * warning$ "" } { journal volume empty$ 'skip$ { " {\bf" * volume * "}" * } if$ number empty$ 'emphasizeic { emphasize ", " * number * } if$ year empty$ { "empty year in " cite$ * warning$ } %% { " (" * format.date * ")" * } { " " * format.date * } if$ } if$ } FUNCTION {format.chapter.pages} { chapter empty$ 'format.pp.pages { type empty$ { "ch.~" chapter * } { type "l" change.case$ chapter tie.or.space.connect } if$ pages empty$ 'skip$ { ", " * format.pp.pages * } if$ } if$ } FUNCTION {format.in.ed.booktitle} { booktitle empty$ { "" } { booktitle emphasize editor empty$ 'skip$ { ", " * format.ineditors * } if$ } if$ } % The proceedings title (it's on the stack) gets an (address, date) appended FUNCTION {format.proc.date} { duplicate$ empty$ { pop$ "" } { year empty$ { "empty year in " cite$ * warning$ address empty$ 'emphasize { emphasizeic " (" * address * ")" * } if$ } { emphasizeic %% " (" * address empty$ 'skip$ { " (" * address * ", " * ")" *} if$ %% format.date * %% ")" * } if$ } if$ } FUNCTION {format.in.proc.date} { booktitle empty$ { "" } { booktitle format.proc.date } if$ } FUNCTION {empty.misc.check} { author empty$ title empty$ howpublished empty$ month empty$ year empty$ note empty$ and and and and and key empty$ not and { "all relevant fields are empty in " cite$ * warning$ } 'skip$ if$ } FUNCTION {format.thesis.type} { type empty$ 'skip$ { pop$ type "t" change.case$ } if$ } FUNCTION {format.tr.number} { type empty$ { "{\em Tech. Rep. no.}" } 'type if$ number empty$ { "t" change.case$ } { number emphasize tie.or.space.connect } if$ } FUNCTION {format.article.crossref} { key empty$ { journal empty$ { "need key or journal for " cite$ * " to crossref " * crossref * warning$ "" } { "In {\em " journal * "\/}" * } if$ } { "In " key * } if$ " \cite{" * crossref * "}" * } FUNCTION {format.crossref.editor} { editor #1 "{vv~}{ll}" format.name$ scapify editor num.names$ duplicate$ #2 > { pop$ " et~al." * } { #2 < 'skip$ { editor #2 "{ff }{vv }{ll}{ jj}" format.name$ scapify "others" = { " et~al." * } { " and " * editor #2 "{vv~}{ll}" format.name$ * } if$ } if$ } if$ } FUNCTION {format.book.crossref} { volume empty$ { "empty volume in " cite$ * "'s crossref of " * crossref * warning$ "In " } { "Vol.~" volume * " of " * } if$ editor empty$ editor field.or.null author field.or.null = or { key empty$ { series empty$ { "need editor, key, or series for " cite$ * " to crossref " * crossref * warning$ "" * } { "{\em " * series * "\/}" * } if$ } { key * } if$ } { format.crossref.editor * } if$ " \cite{" * crossref * "}" * } FUNCTION {format.incoll.inproc.crossref} { editor empty$ editor field.or.null author field.or.null = or { key empty$ { booktitle empty$ { "need editor, key, or booktitle for " cite$ * " to crossref " * crossref * warning$ "" } { "In {\em " booktitle * "\/}" * } if$ } { "In " key * } if$ } { "In " format.crossref.editor * } if$ " \cite{" * crossref * "}" * } FUNCTION {article} { output.bibitem format.authors "author" output.check new.block format.title "title" output.check new.block crossref missing$ { format.journal.vol.num.date output new.block format.pages output } { format.article.crossref output.nonnull new.block format.pp.pages output } if$ new.block note output fin.entry } FUNCTION {book} { output.bibitem author empty$ { format.editors "author and editor" output.check } { format.authors output.nonnull crossref missing$ { "author and editor" editor either.or.check } 'skip$ if$ } if$ new.block format.btitle "title" output.check format.edition output crossref missing$ { format.bvolume output new.block format.number.series output new.block publisher "publisher" output.check address output } { new.block format.book.crossref output.nonnull } if$ format.date "year" output.check new.block note output fin.entry } FUNCTION {booklet} { output.bibitem format.authors output new.block format.title "title" output.check howpublished address new.block.checkb howpublished output address output format.date output new.block note output fin.entry } FUNCTION {inbook} { output.bibitem author empty$ { format.editors "author and editor" output.check } { format.authors output.nonnull crossref missing$ { "author and editor" editor either.or.check } 'skip$ if$ } if$ new.block format.btitle "title" output.check format.edition output new.block crossref missing$ { format.bvolume output new.block format.number.series output new.sentence publisher "publisher" output.check new.block address output } { new.block format.book.crossref output.nonnull } if$ format.date "year" output.check new.block format.chapter.pages "chapter and pages" output.check new.block note output fin.entry } FUNCTION {incollection} { output.bibitem format.authors "author" output.check new.block format.title "title" output.check new.block crossref missing$ { format.in.ed.booktitle "booktitle" output.check format.edition output format.bvolume output format.number.series output new.sentence publisher "publisher" output.check address output format.date "year" output.check } { format.incoll.inproc.crossref output.nonnull } if$ new.block format.chapter.pages output new.block note output fin.entry } FUNCTION {inproceedings} { output.bibitem format.authors "author" output.check new.block format.title "title" output.check new.block crossref missing$ { format.in.proc.date "booktitle" output.check format.ineditors output new.block format.bvolume output format.number.series output new.block organization output new.block publisher output format.date "year" output.check } { format.incoll.inproc.crossref output.nonnull } if$ new.block format.pages output new.block note output fin.entry } FUNCTION {conference} { inproceedings } FUNCTION {manual} { output.bibitem author empty$ { organization scapify output } { format.authors output.nonnull } if$ new.block format.btitle "title" output.check format.edition output author empty$ { address new.block.checka } { organization address new.block.checkb organization output } if$ address output format.date output new.block note output fin.entry } FUNCTION {misc} { output.bibitem format.authors output title howpublished new.block.checkb format.title output howpublished new.block.checka howpublished output format.date output new.block note output fin.entry empty.misc.check } FUNCTION {phdthesis} { output.bibitem format.authors "author" output.check new.block format.btitle "title" output.check new.block "PhD thesis" format.thesis.type output.nonnull school "school" output.check address output format.date "year" output.check new.block note output fin.entry } FUNCTION {proceedings} { output.bibitem editor empty$ { organization scapify output } { format.editors output.nonnull } if$ new.block title format.proc.date "title" output.check new.block format.bvolume output format.number.series output editor empty$ 'skip$ { organization output } if$ new.block publisher output new.block note output fin.entry } FUNCTION {techreport} { output.bibitem format.authors "author" output.check new.block format.title "title" output.check new.block format.tr.number output.nonnull new.block institution "institution" output.check new.block address output format.date "year" output.check new.block note output fin.entry } FUNCTION {unpublished} { output.bibitem format.authors "author" output.check new.block format.title "title" output.check new.block note "note" output.check format.date output fin.entry } FUNCTION {default.type} { misc } MACRO {jan} {"Jan."} MACRO {feb} {"Feb."} MACRO {mar} {"Mar."} MACRO {apr} {"Apr."} MACRO {may} {"May"} MACRO {jun} {"June"} MACRO {jul} {"July"} MACRO {aug} {"Aug."} MACRO {sep} {"Sept."} MACRO {oct} {"Oct."} MACRO {nov} {"Nov."} MACRO {dec} {"Dec."} MACRO {acmcs} {"ACM Comput. Surv."} MACRO {acta} {"Acta Inf."} MACRO {cacm} {"Commun. ACM"} MACRO {ibmjrd} {"IBM J. Res. Dev."} MACRO {ibmsj} {"IBM Syst.~J."} MACRO {ieeese} {"IEEE Trans. Softw. Eng."} MACRO {ieeetc} {"IEEE Trans. Comput."} MACRO {ieeetcad} {"IEEE Trans. Comput.-Aided Design Integrated Circuits"} MACRO {ipl} {"Inf. Process. Lett."} MACRO {jacm} {"J.~ACM"} MACRO {jcss} {"J.~Comput. Syst. Sci."} MACRO {scp} {"Sci. Comput. Programming"} MACRO {sicomp} {"SIAM J. Comput."} MACRO {tocs} {"ACM Trans. Comput. Syst."} MACRO {tods} {"ACM Trans. Database Syst."} MACRO {tog} {"ACM Trans. Gr."} MACRO {toms} {"ACM Trans. Math. Softw."} MACRO {toois} {"ACM Trans. Office Inf. Syst."} MACRO {toplas} {"ACM Trans. Program. Lang. Syst."} MACRO {tcs} {"Theoretical Comput. Sci."} READ FUNCTION {sortify} { purify$ "l" change.case$ } INTEGERS { len } FUNCTION {chop.word} { 's := 'len := s #1 len substring$ = { s len #1 + global.max$ substring$ } 's if$ } FUNCTION {sort.format.names} { 's := #1 'nameptr := "" s num.names$ 'numnames := numnames 'namesleft := { namesleft #0 > } { nameptr #1 > { " " * } 'skip$ if$ s nameptr "{vv{ } }{ll{ }}{ f{ }}{ jj{ }}" format.name$ 't := nameptr numnames = t "others" = and { "et al" * } { t sortify * } if$ nameptr #1 + 'nameptr := namesleft #1 - 'namesleft := } while$ } FUNCTION {sort.format.title} { 't := "A " #2 "An " #3 "The " #4 t chop.word chop.word chop.word sortify #1 global.max$ substring$ } FUNCTION {author.sort} { author empty$ { key empty$ { "to sort, need author or key in " cite$ * warning$ "" } { key sortify } if$ } { author sort.format.names } if$ } FUNCTION {author.editor.sort} { author empty$ { editor empty$ { key empty$ { "to sort, need author, editor, or key in " cite$ * warning$ "" } { key sortify } if$ } { editor sort.format.names } if$ } { author sort.format.names } if$ } FUNCTION {author.organization.sort} { author empty$ { organization empty$ { key empty$ { "to sort, need author, organization, or key in " cite$ * warning$ "" } { key sortify } if$ } { "The " #4 organization chop.word sortify } if$ } { author sort.format.names } if$ } FUNCTION {editor.organization.sort} { editor empty$ { organization empty$ { key empty$ { "to sort, need editor, organization, or key in " cite$ * warning$ "" } { key sortify } if$ } { "The " #4 organization chop.word sortify } if$ } { editor sort.format.names } if$ } FUNCTION {presort} { type$ "book" = type$ "inbook" = or 'author.editor.sort { type$ "proceedings" = 'editor.organization.sort { type$ "manual" = 'author.organization.sort 'author.sort if$ } if$ } if$ " " * year field.or.null sortify * " " * title field.or.null sort.format.title * #1 entry.max$ substring$ 'sort.key$ := } ITERATE {presort} SORT STRINGS { longest.label } INTEGERS { number.label longest.label.width } FUNCTION {initialize.longest.label} { "" 'longest.label := #1 'number.label := #0 'longest.label.width := } FUNCTION {longest.label.pass} { number.label int.to.str$ 'label := number.label #1 + 'number.label := label width$ longest.label.width > { label 'longest.label := label width$ 'longest.label.width := } 'skip$ if$ } EXECUTE {initialize.longest.label} ITERATE {longest.label.pass} FUNCTION {begin.bib} { preamble$ empty$ 'skip$ { preamble$ write$ newline$ } if$ "\begin{thebibliography}{" longest.label * "}" * write$ newline$ } EXECUTE {begin.bib} EXECUTE {init.state.consts} ITERATE {call.type$} FUNCTION {end.bib} { newline$ "\end{thebibliography}" write$ newline$ } EXECUTE {end.bib} \ No newline at end of file diff --git a/doc/papers/iccs06/ICCSsty.tex b/doc/papers/iccs06/ICCSsty.tex new file mode 100644 index 0000000..12ad84d --- /dev/null +++ b/doc/papers/iccs06/ICCSsty.tex @@ -0,0 +1 @@ +% tt 4-30-98 -*- Mode:TeX -*- % ICCSsty.tex (LATEX style for International Conference on Complex Systems); LaTeX2e only! % revised to fit given Word format % Copyright (c) Tommaso Toffoli 1998 %---------------- OVERALL GEOMETRY ---------------- \pagestyle{headings} % % \headheight .15in % % \headsep .28in % \footskip 0in \textheight 7.55in \footskip 0in \textwidth 4.8in \oddsidemargin .85in \evensidemargin .85in \sloppy \makeatletter %---------------- TITLE SECTION ---------------- \ifx\UNDEF\mail\def\mail{ }\else\fi \ifx\UNDEF\prange\def\prange{0 0}\else\fi \gdef\@empty{} \def\Mail#1 #2 {\gdef\thecontact{#1}\gdef\theaddr{#2}} \def\Range#1 #2 {\gdef\thefirstpage{#1}\gdef\thelastpage{#2}} {\let\'\mail \expandafter\Mail\' } % do not remove space between ' and } {\let\'\prange \expandafter\Range\' } % do not remove space between ' and } % \setcounter{page}{\thefirstpage} \gdef\@shtitle{\relax} \long\def\shtitle#1{\gdef\@shtitle{#1}} % \gdef\@shauthor{\relax} % \long\def\shauthor#1{\gdef\@shauthor{#1}} \long\def\author#1{\gdef\@author{#1}} \def\affil#1{\par\noindent{\rm#1\par}} \gdef\@abstract{} \long\def\abstract#1{\gdef\@abstract{#1}} \renewcommand{\@evenhead}{\thepage\qquad\qquad\@shtitle\hfil} \renewcommand{\@oddhead}{\hfil\@shtitle\qquad\qquad\thepage} \def\maketitle{\thispagestyle{empty}\chapter{\@title}} \renewcommand\chapter{\if@openright\cleardoublepage\else\clearpage\fi \thispagestyle{empty}% \global\@topnum\z@ \@afterindentfalse \secdef\@chapter\@schapter} \def\@makechapterhead#1{% \vspace*{50\p@}% {\parindent \z@ \raggedleft \normalfont \ifnum \c@secnumdepth >\m@ne \if@mainmatter \huge %\@chapapp{} \thechapter \par\nobreak \vskip 20\p@ \fi \fi \interlinepenalty\@M \Huge \bfseries #1\par\nobreak \vskip.25in \large\bfseries\@author\par\nobreak \vskip 40\p@} \ifx\@abstract\@empty\else{\small\@abstract\par\vskip20\p@}\fi } %---------------- EMPHASIS ---------------- %% Use boldface for terms being defined \let\tdf\textbf \let\df\bf %% Use slanted rather than italic for emphasis \DeclareRobustCommand\em {\@nomath\em \ifdim \fontdimen\@ne\font >\z@ \upshape \else \slshape \fi} \let\tem\emph %% use \sl rather than \it in theorems [to be upgraded] \def\@begintheorem#1#2{\sl \trivlist \item[\hskip \labelsep{\bf #1\ #2}]} \def\@opargbegintheorem#1#2#3{\sl \trivlist \item[\hskip \labelsep{\bf #1\ #2\ (#3)}]} %======================== CROSS-REFERENCING ================== \newcommand{\sect}[1]{\S\ref{sect:#1}} % Ref. to section or subsection % to appear as, say, $2.37 \newcommand{\baresect}[1]{\ref{sect:#1}} % Ref. to one more section or % subsection to appear as, say, 2.37 \newcommand{\eq}[1]{(\ref{eq:#1})} % Ref. to equation % to appear as, say, (2.37) \newcommand{\foot}[1]{footnote \ref{foot:#1}} % Ref. to footnote % to appear as, say, `footnote 7'. \newcommand{\fig}[1]{Fig.~\ref{fig:#1}} \newcommand{\sectlabel}[1]{\label{sect:#1}} \newcommand{\eqlabel}[1]{\label{eq:#1}} \newcommand{\figlabel}[1]{\label{fig:#1}} %======================== SECTIONING ======================== %% Small sections % \newcommand{\smallsection}[1]{\par\medskip\noindent{\em#1}\par\smallskip} \let\smallsection\subsubsection \setcounter{secnumdepth}{2} \newcommand{\parhead}[1]{\tsl{#1}.\quad} % Distinguished paragraph % heading; slanted, followed by \quad %% allow printing 0 as a section (or subsection, item, etc.) number % \def\@arabic#1{\ifnum #1>0 \number #1\fi} % original definition \def\@arabic#1{\number #1} % my redefinition %======================== FIGURES ======================== %% change captions from \normalsize to \small \long\def\@makecaption#1#2{ \vskip\abovecaptionskip \sbox\@tempboxa{{\small {\bf #1}: #2}}% \ifdim\wd\@tempboxa>\hsize {\small {\bf #1}: #2\par} \else \global\@minipagefalse \hbox to\hsize{\hfil\box\@tempboxa\hfil} \fi \vskip \belowcaptionskip} \def\figstrut#1{\hbox to\linewidth{\vrule height#1\hfill}} %%%% Single-column figure \newcommand{\Fig}[4][!htb]{% [position] label, picture, caption \begin{figure}[#1] \centering\leavevmode#3% \caption{#4} \figlabel{#2} \end{figure} } \let\figonecol\Fig %%%% Two-column figure \newcommand{\Figwide}[4][!t]{% [position] label, picture, caption \begin{figure*}[#1] \centering\leavevmode#3% \caption{#4} \figlabel{#2} \end{figure*} } \let\figtwocol\Figwide %%%% Float-page figure \newcommand{\Figpage}[3]{% label, picture, caption \begin{figure*}[p] \centering\leavevmode#2% \caption{#3} \figlabel{#1} \end{figure*} } \let\figpage\Figpage \renewenvironment{thebibliography}[1] {\section*{\bibname \@mkboth{\MakeUppercase\bibname}{\MakeUppercase\bibname}}% \list{\@biblabel{\@arabic\c@enumiv}}% {\settowidth\labelwidth{\@biblabel{#1}}% \leftmargin\labelwidth \advance\leftmargin\labelsep \@openbib@code \usecounter{enumiv}% \let\p@enumiv\@empty \renewcommand\theenumiv{\@arabic\c@enumiv}}% \sloppy \clubpenalty4000 \@clubpenalty \clubpenalty \widowpenalty4000% \sfcode`\.\@m} {\def\@noitemerr {\@latex@warning{Empty `thebibliography' environment}}% \endlist} \makeatother \bibliographystyle{ICCS} \endinput \def\maketitle{% \thispagestyle{empty} \raggedleft \noindent\@author\par \medskip \noindent\hrule \smallskip \noindent{\Huge\sf\@title\par} \vfil\vfil\vfil\vfil {\narrower\narrower\noindent\@abstract\par}\newpage} \ No newline at end of file diff --git a/doc/papers/iccs06/Makefile b/doc/papers/iccs06/Makefile new file mode 100644 index 0000000..2d68c6b --- /dev/null +++ b/doc/papers/iccs06/Makefile @@ -0,0 +1,29 @@ +SOURCE = csardi2 + +all: $(SOURCE).pdf + +FIGTEX = ~/bin/figtex + +FIG = $(wildcard *.fig) +EPS = $(patsubst %.fig, %.eps, $(FIG)) +EPST = $(patsubst %.fig, %.eps_t, $(FIG)) +ALLEPS = $(EPS) + +$(SOURCE).dvi: $(SOURCE).tex $(ALLEPS) $(EPST) net.bib + latex $(SOURCE) && bibtex $(SOURCE) && \ + latex $(SOURCE) && latex $(SOURCE) + +$(SOURCE).pdf: $(SOURCE).dvi + dvips -o $(SOURCE).ps -t letter $(SOURCE).dvi && \ + ps2pdf $(SOURCE).ps + +# Figures + +%.eps : %.fig + $(FIGTEX) $< + +clean: + rm -f $(SOURCE){.aux,.bbl,.blg,.dvi,.log,.out,.pdf,.ps,.toc}\ + $(EPS) $(EPST) + +.PHONY: clean diff --git a/doc/papers/iccs06/arch.fig b/doc/papers/iccs06/arch.fig new file mode 100644 index 0000000..bf3331e --- /dev/null +++ b/doc/papers/iccs06/arch.fig @@ -0,0 +1,25 @@ +#FIG 3.2 Produced by xfig version 3.2.5-alpha5 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +0 32 #eaeaea +2 4 0 1 0 32 50 -1 20 0.000 0 0 7 0 0 5 + 5310 4755 3015 4755 3015 3300 5310 3300 5310 4755 +2 4 0 1 0 32 50 -1 20 0.000 0 0 7 0 0 5 + 7950 4755 5655 4755 5655 3300 7950 3300 7950 4755 +2 4 0 1 0 32 50 -1 20 0.000 0 0 7 0 0 5 + 9600 4755 8340 4755 8340 3300 9600 3300 9600 4755 +2 4 0 1 0 32 50 -1 20 0.000 0 0 7 0 0 5 + 9630 8550 3015 8550 3015 7065 9630 7065 9630 8550 +2 4 0 1 0 32 50 -1 20 0.000 0 0 7 0 0 5 + 9630 6630 3015 6630 3015 5145 9630 5145 9630 6630 +4 1 0 50 -1 18 28 0.0000 4 60 405 8955 4125 ...\001 +4 1 0 50 -1 18 28 0.0000 4 465 1590 6765 4125 Python\001 +4 1 0 50 -1 18 28 0.0000 4 360 1500 4170 4125 GNU R\001 +4 1 0 50 -1 18 28 0.0000 4 465 6075 6345 6015 Graph operations (C library)\001 +4 1 0 50 -1 18 28 0.0000 4 465 7305 6345 7995 Basic graph operations (C library)\001 diff --git a/doc/papers/iccs06/csardi2.tex b/doc/papers/iccs06/csardi2.tex new file mode 100644 index 0000000..e960b1f --- /dev/null +++ b/doc/papers/iccs06/csardi2.tex @@ -0,0 +1,373 @@ +\documentclass[twoside]{book}% Specify document type +\usepackage{graphicx} % Load macros for PostScript figures +\usepackage{url} +\usepackage{psfrag} +\usepackage{fancyvrb} +\input ICCSsty.tex % Load style for Complex Systems '97 + +\newcommand{\figfigure}[2]{% + \begin{psfrags} + \input #2.eps_t + \includegraphics[width=#1]{#2.eps} + \end{psfrags} +} + +%--------------------------------------------------------- +% Give data to appear in the title section + + \shtitle{The igraph software package for complex network research} + \title{The igraph software package for complex network research} + \author{% + G\'abor Cs\'ardi\affil{Center for Complex Systems Studies, + Kalamazoo College, Kalamazoo, MI, USA \\ + and \\ + Department of Biophysics, KFKI Research Institute for Particle + and Nuclear Physics of the Hungarian Academy of Sciences, + Budapest, Hungary\\csardi@kzoo.edu} + + Tam\'as Nepusz\affil{Department of Biophysics, KFKI Research + Institute for Particle and Nuclear Physics of the Hungarian + Academy of Sciences, Budapest, Hungary \\ + and \\ + Department of Measurement and Information Systems, Budapest + University of Technology and Economics, Budapest, Hungary \\ + ntamas@rmki.kfki.hu} + } +\date{May 10, 2006} +\abstract{The igraph software package provides handy tools for + researchers in network science. It is an open source portable + library capable of handling huge graphs with millions of vertices + and edges and it is also suitable to grid computing. It contains + routines for creating, manipulating and visualizing networks, + calculating various structural properties, importing from and + exporting to various file formats and many more. Via its interfaces + to high-level languages like GNU R and Python it supports rapid + development and fast prototyping. +} + +%--------------------------------------------------------- +% Here is the document + +\begin{document} % Matched by \end{document} +\maketitle % Typeset the title section + +\section{Introduction} + +This paper does not present results of scientific research, but +introduces a software package which gives handy tools into the hands +of researchers doing network science. The authors strongly believe +that the tools scientists use are important because they +can increase productivity by several factors and thereby enhance +scientific progress. + +\subsection{Why another network analysis package?} + +The igraph library was developed because of the lack of network +analysis software which (1)~can handle large graphs efficiently, +(2)~can be embedded into a higher level program or programming language +(like Python, Perl or GNU R) and (3) can be used both interactively +and non-interactively. + +The capability of handling large graphs was important because the +authors were confronted with graphs with millions of vertices and edges. + +Embedding igraph into Python or GNU R creates a very productive +research environment, well suited for rapid development. All the +expressing power of GNU R (or other higher level languange) is readily +available in a convenient integrated environment for generating, +manipulating and measuring graphs, and evaluating these measurements. + +Interactive means of software usage is nowadays considered as superior +to non-interactive interfaces, which is very true for most +cases. Dealing with large graphs can be different though -- if it takes +three months to calculate the diameter of a graph, nobody wants that to +be interactive. + +\section{Features} + +In the addition to the three goal features in the previous section, +others showed up as a side-effect. Let us discuss these features here. + +\paragraph{Open source.} Igraph is open source, it is free for +non-commercial or commercial use and distributed according to the GNU +General Public License. Being open source means that in addition to +the binary format of the program, the user can +always get the source code format enabling additions and corrections. +This is a very important feature for the users. With open source +software, you can add new functionality and correct deficiencies or +hire somebody to do this for you. With closed source software this is +impossible. + +\paragraph{Efficient implementation.} Igraph uses space and time +efficient data structures and implements the current state-of-the-art +algorithms. All igraph functions are carefully profiled to create the +fastest implementation possible. + +\paragraph{Portability.} The library is written in ANSI~C, it is thus +portable to most platforms. It is tested on different Linux flavors, +Mac OS X, MS Windows and Sun OS. The R and Python interfaces are also +portable to many architectures. + +\paragraph{Layered architecture.} The igraph library has a layered +architecture, the three layers are connected through well defined +interfaces. Each layer can be replaced with an alternate +implementation without changing the other components. See the details +in~\sect{arch}. + +\paragraph{Open, embeddable system.} The core igraph library is an +open system, it can be embedded into higher level languages or +programs. The current distribution contains interfaces to two high +level languages: GNU R and Python. + +\paragraph{High level operations.} The higher level interfaces provide +abstract operations and data types. These support rapid +program development, see~\sect{fast} for an example. + +\paragraph{Documentation.} The C library is very well documented, the +documentation is available in various formats supporting both online +browsing and printing. For each function its time requirements are +documented. + +\paragraph{Drawbacks.} The library lacks functionality in some areas +compared to other network analysis packages. One such area is graph +visualization, another one is various social network analysis methods +like block-modeling, p$^{*}$ methods, etc. Note that this piece of +software is heavily under development, so expect much more +functionality in the near future. Igraph also does not have a +graphical user interface, but a Python-based GUI is under development +and will be available for download soon and the R interface also +provides a facility for visual manipulation of small graphs. + +\section{Example applications} + +\subsection{Grid computing} + +In this section we give an example for using the igraph library +for large scale computation. The task presented here is to calculate +the diameter of the US patent citation network. In this network the +nodes are US patents granted between 1963 and 2000 and two patents are +connected if one cites the other. The largest component of the network +contains more than 3~million nodes and 15~million edges. The +(undirected) diameter of a network is the largest undirected shortest +path connecting two vertices. + +For calculating the diameter of a graph you need to calculate the +length of the shortest path between all possible pairs of nodes, so +this is computationally very expensive. We used the following approach +with igraph. + +First we wrote a simple program in C which downloads the data set from +a web server and then starts calculating the shortest paths from a +given source node to all other nodes in the network by using +Dijkstra's algorithm \cite{dijkstra59} implemented in the igraph +library. We will call this program the \emph{worker}. + +The worker downloads the id of the source node from a second web +server. This web server simply gives a different source node id every +time one is requested by the workers. As soon as the worker has +finished with the calculation of the shortest paths to all nodes it +stores the result on a third web server and asks the second web-server +for a new source node id, etc. The architecture of the system can be +seen in +\fig{grid}. + +\begin{figure} +\centering +\figfigure{0.75\textwidth}{grid2} +\caption{The architecture of the system used for calculating the + diameter of a large graph. A worker node (1) downloads the network + data from the data web-server, then (2) it requests a source vertex + id from the task web-server, (3) calculates the shortest paths from + that source vertex and (4) stores the result on the task + web-server. Then a new source vertex id is requested, etc.} +\figlabel{grid} +\end{figure} + +This system is very robust in the sense that there is no single point +of failure. The workers can be run in any grid-based environment from +which they can access the WWW. They can be run on different platforms +as well. + +\subsection{Fast prototyping, rapid development}\sectlabel{fast} + +\paragraph{Newman's community finding algorithm} +The second example we present is very different from the first. Here +we will use the GNU R interface to the igraph library to implement +and apply Newman's spectral community finding algorithm +\cite{newman06}. +First we load the igraph package into R and download the Zachary +Karate-club network data \cite{zachary77} from the web. +\begin{Verbatim}[fontsize=\small,numbers=left] +library(igraph) +g <- read.graph("http://geza.kzoo.edu/~csardi/karate.net", format="pajek") +\end{Verbatim} + +Now we implement the community finding algorithm. +\begin{Verbatim}[fontsize=\small,numbers=left,firstnumber=last] +community.newman <- function(g) { + deg <- degree(g) + ec <- ecount(g) + B <- get.adjacency(g) - outer(deg, deg, function(x,y) x*y/2/ec) + diag(B) <- 0 + eigen(B)$vectors[,1] +} +\end{Verbatim} +This algorithm creates a modularity matrix which is the difference of +the adjacency matrix of the graph and the null-model matrix. The +latter contains the probabilities that two nodes are connected in a +random graph if the +degrees of the nodes are given. Then the network is divided into two +communities based on the eigenvector associated with the largest +positive eigenvalue of the modularity matrix: all vertices having the +same sign in this eigenvector belong to the same community. + +Now we are ready to apply this algorithm to the Karate-club data and +set the color of the vertices based on their communities. +\begin{Verbatim}[fontsize=\small,numbers=left,firstnumber=last] +mem <- community.newman(g) +V(g)$color <- ifelse(mem < 0, "grey", "green") +\end{Verbatim} + +We also set the size of the vertices based on the first eigenvector, +the farther this value is from zero the more the given vertex is in +the \emph{core} of the community. We also set the color of the edges +across the two communities to red. +\begin{Verbatim}[fontsize=\small,numbers=left,firstnumber=last] +scale <- function(v, a, b) { + v <- v-min(v) ; v <- v/max(v) ; v <- v * (b-a) ; v+a +} +V(g)$size <- scale(abs(mem), 15, 25) +E(g)$color <- "grey" +E(g)[ V(g)[color=="grey"] %--% V(g)[color=="green"] ]$color <- "red" +plot(g, layout=layout.kamada.kawai, vertex.color="a:color", + vertex.size="a:size", edge.color="a:color") +\end{Verbatim} +See the resulting plot in \fig{karate}. + +\begin{figure}[t] +\centering +\figfigure{0.65\textwidth}{karate} +\caption{The two communities identified correctly in the Zachary + karate-club network. The size of the vertices is proportional to + the absolute value of their coordinate in the first eigenvector and + expresses how strongly they belong to a community. All edges across the + two communities are painted red.} +\figlabel{karate} +\end{figure} + +\paragraph{PageRank algorithm in 19 lines} Using the Python interface +of igraph, one can easily create a prototype of the original PageRank +algorithm in only 19 lines of code (not counting empty lines): + +\begin{Verbatim}[fontsize=\small,numbers=left] +from igraph import * +from copy import copy + +def pagerank(g, damping=0.85, epsilon=0.001, iters=100): + pageranks = [1-damping] * g.vcount() + outlinks = g.degree(type=OUT) + mindiff = epsilon + newprs = [0] * g.vcount() + + while mindiff >= epsilon and iters > 0: + iters = iters - 1 + for n in range(g.vcount()): + neis = g.neighbors(n, IN) + pr = 0.0 + if len(neis) > 0: + for n2 in neis: pr = pr + pageranks[n2] / outlinks[n2] + pr = pr*damping + newprs[n] = pr+1-damping + + mindiff = min([abs(newprs[n]-pageranks[n]) for n in range(g.vcount())]) + pageranks = copy(newprs) + + return pageranks +\end{Verbatim} + +\section{The igraph architecture}\sectlabel{arch} + +The igraph system has a layered architecture consisting three +layers. The lowest layer contains the very basic operations only, and +is implemented in C. It is only this layer which can manipulate the +internal igraph data structures directly. This means that this layer +can be easily replaced with and alternate graph representation if +needed. + +The second layer contains almost all network analysis functions, this +is also implemented in C. + +The third layer contains the higher level interfaces, so far +interfaces to GNU R and Python are implemented. + +\begin{figure} +\centering +\figfigure{0.6\textwidth}{arch} +\caption{The architecture of the igraph system. See the text for a + description. } +\figlabel{arch} +\end{figure} + +\section{Current functionality} + +Please note that new functionality is added to the library every +week, so check the igraph homepage at +\url{http://cneurocvs.rmki.kfki.hu/igraph} if you cannot see here the +algorithms or measures you're looking for. + +\paragraph{Graph generation} Igraph can generate various regular and +random graphs: $\bullet$ regular structures: star, ring and full +graphs, circular and non-circular lattices with any number of +dimensions, regular trees $\bullet$ graphs based on Barab\'asi's +preferential attachment model \cite{barabasi99a}, also with nonlinear +attachment exponent and various variations $\bullet$ Random +(Erd\H{o}s-R\'enyi) graphs, both $G(n,p)$ and $G(n,m)$ types +\cite{erdos59}, directed and undirected ones $\bullet$ graphs having a +given degree sequence, directed or undirected ones \cite{newman01} +$\bullet$ growing random graphs, also for modeling citation networks +\cite{callaway01} $\bullet$ growing random graphs where the connection +probability depends on some vertex properties $\bullet$ graphs from +the Graph Atlas \cite{read98} $\bullet$ all non-isomorphic graphs of a +given size. + +\paragraph{Centrality measures} The following centrality measures +\cite{freeman79} can be calculated: $\bullet$ degree $\bullet$ +closeness $\bullet$ vertex and edge betweenness $\bullet$ eigenvector +centrality $\bullet$ page rank \cite{page98}. + +\paragraph{Path length based properties} One or all shortest paths +between vertices can be calculated, and also based on this the +diameter and the average path length of the graph. + +\paragraph{Graph components} Weakly and strongly connected components +can be calculated, and also the minimum spanning forest of a graph. + +\paragraph{Graph motifs} Graph motifs of three or four components can +be calculated, both undirected and directed motifs \cite{wernicke06}. + +\paragraph{Random rewiring} Existing graphs can be rewired randomly +while preserving their degree distribution, allowing the user to +generate an arbitrary set of graphs with the same degree distribution. + +\paragraph{Vertex and edge sets} Igraph provides a simple way to +manipulate subsets of vertices and/or edges of a graph, +see~\sect{fast} for an example. + +\paragraph{Vertex and edge attributes} Numeric or non-numeric +attributes can be assigned to the vertices and edges of a graph, and +queried and set by using a simple notation, see~\sect{fast}. + +\paragraph{File formats} Igraph can read and write simple edge +list files and also Pajek \cite{nooy05} and GraphML \cite{brandes01} files. + +\paragraph{Graph layouts} The following layout generators are part of +igraph: $\bullet$ simple circle and sphere layouts, random layouts +$\bullet$ Fruchterman-Reingold layout, 2D and 3D \cite{fruchterman91} $\bullet$ +Kamada-Kawai layout, 2D and 3D \cite{kamada89} $\bullet$ spring embedder layout +$\bullet$ LGL layout generator for large graphs \cite{adai04} +$\bullet$ Grid-based Fruchterman-Reingold layout for large graphs +$\bullet$ Reingold-Tilford layout \cite{reingold81} for trees. + +\bibliography{net} + +\end{document} diff --git a/doc/papers/iccs06/grid2.fig b/doc/papers/iccs06/grid2.fig new file mode 100644 index 0000000..8652f69 --- /dev/null +++ b/doc/papers/iccs06/grid2.fig @@ -0,0 +1,100 @@ +#FIG 3.2 Produced by xfig version 3.2.5-alpha5 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +0 32 #a0ffa0 +0 33 #a0a0ff +6 5940 4680 7830 5535 +2 4 0 1 0 30 50 -1 20 0.000 0 0 7 0 0 5 + 7830 5535 5940 5535 5940 4680 7830 4680 7830 5535 +4 1 0 50 -1 0 24 0.0000 4 270 1110 6885 5220 worker\001 +-6 +6 5940 3510 7830 4365 +2 4 0 1 0 30 50 -1 20 0.000 0 0 7 0 0 5 + 7830 4365 5940 4365 5940 3510 7830 3510 7830 4365 +4 1 0 50 -1 0 24 0.0000 4 270 1110 6885 4050 worker\001 +-6 +6 5940 2250 7830 3105 +2 4 0 1 0 30 50 -1 20 0.000 0 0 7 0 0 5 + 7830 3105 5940 3105 5940 2250 7830 2250 7830 3105 +4 1 0 50 -1 0 24 0.0000 4 270 1110 6885 2790 worker\001 +-6 +6 5940 990 7830 1845 +2 4 0 1 0 30 50 -1 20 0.000 0 0 7 0 0 5 + 7830 1845 5940 1845 5940 990 7830 990 7830 1845 +4 1 0 50 -1 0 24 0.0000 4 270 1110 6885 1530 worker\001 +-6 +6 9975 3645 12585 5490 +2 4 0 1 0 32 51 -1 20 0.000 0 0 7 0 0 5 + 12585 5490 9975 5490 9975 3645 12585 3645 12585 5490 +4 1 0 50 -1 0 24 0.0000 4 270 795 11242 4245 Task\001 +4 1 0 50 -1 0 24 0.0000 4 270 645 11242 4725 web\001 +4 1 0 50 -1 0 24 0.0000 4 195 975 11242 5205 server\001 +-6 +6 1170 3600 3780 5445 +6 1965 3870 2940 5100 +4 1 0 50 -1 0 24 0.0000 4 270 750 2452 4140 Data\001 +4 1 0 50 -1 0 24 0.0000 4 270 645 2452 4620 web\001 +4 1 0 50 -1 0 24 0.0000 4 195 975 2452 5100 server\001 +-6 +2 4 0 1 0 33 51 -1 20 0.000 0 0 7 0 0 5 + 3780 5445 1170 5445 1170 3600 3780 3600 3780 5445 +-6 +6 8774 1095 9240 1561 +1 3 0 1 0 33 50 -1 -1 0.000 1 0.0000 9007 1328 233 233 9007 1328 9240 1328 +4 1 0 50 -1 0 24 0.0000 4 270 195 9007 1448 4\001 +-6 +6 8159 1095 8625 1561 +1 3 0 1 0 33 50 -1 -1 0.000 1 0.0000 8392 1328 233 233 8392 1328 8625 1328 +4 1 0 50 -1 0 24 0.0000 4 270 195 8392 1448 2\001 +-6 +6 4732 1117 5198 1583 +1 3 0 1 0 33 50 -1 -1 0.000 1 0.0000 4965 1350 233 233 4965 1350 5198 1350 +4 1 0 50 -1 0 24 0.0000 4 270 195 4965 1470 1\001 +-6 +1 3 0 1 0 7 49 -1 20 0.000 1 0.0000 7687 1177 233 233 7687 1177 7920 1177 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 9975 4230 9375 4230 9375 1620 7815 1620 +2 4 0 1 0 30 50 -1 20 0.000 0 0 7 0 0 5 + 7830 7920 5940 7920 5940 7065 7830 7065 7830 7920 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 9975 4965 9555 4965 9555 7485 7815 7485 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 9975 4695 9375 4695 9375 5175 7815 5175 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 9975 4545 9225 4545 9225 3990 7815 3990 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 9975 4380 9300 4380 9300 2745 7815 2745 +2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 4 + 0 0 1.00 60.00 120.00 + 3780 4965 4200 4965 4200 7485 5940 7485 +2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 4 + 0 0 1.00 60.00 120.00 + 3780 4695 4380 4695 4380 5175 5940 5175 +2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 4 + 0 0 1.00 60.00 120.00 + 3780 4545 4530 4545 4530 3990 5940 3990 +2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 4 + 0 0 1.00 60.00 120.00 + 3780 4380 4455 4380 4455 2745 5940 2745 +2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 1 0 4 + 0 0 1.00 60.00 120.00 + 3780 4230 4380 4230 4380 1620 5940 1620 +4 1 0 50 -1 0 24 0.0000 4 270 1110 6885 7605 worker\001 +4 1 0 50 -1 0 24 0.0000 4 270 885 6885 6405 \\ldots\001 +4 1 0 46 -1 0 24 0.0000 4 270 195 7687 1297 3\001 diff --git a/doc/papers/iccs06/karate.fig b/doc/papers/iccs06/karate.fig new file mode 100644 index 0000000..8662d62 --- /dev/null +++ b/doc/papers/iccs06/karate.fig @@ -0,0 +1,241 @@ +#FIG 3.2 Produced by xfig version 3.2.5-alpha5 +Landscape +Flush left +Inches +Letter +100.00 +Single +-2 +1200 2 +0 32 #ff0000 +0 33 #bebebe +0 34 #a0ffa0 +0 35 #00008b +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 6208 3453 427 427 6208 3453 6635 3453 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 6296 4365 349 349 6296 4365 6645 4365 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 7593 4880 294 294 7593 4880 7887 4880 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 7021 3119 338 338 7021 3119 7359 3119 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 5258 3079 294 294 5258 3079 5552 3079 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 4312 3066 298 298 4312 3066 4610 3066 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 4751 3891 298 298 4751 3891 5049 3891 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 7910 2417 321 321 7910 2417 8231 2417 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 8542 4979 280 280 8542 4979 8822 4979 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 9525 4067 262 262 9525 4067 9787 4067 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 5552 2154 294 294 5552 2154 5846 2154 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 6285 1868 291 291 6285 1868 6576 1868 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 7792 3966 290 290 7792 3966 8082 3966 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 10228 5267 297 297 10228 5267 10525 5267 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 10262 5975 297 297 10262 5975 10559 5975 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 3417 3409 262 262 3417 3409 3679 3409 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 4927 4997 293 293 4927 4997 5220 4997 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 10055 4616 297 297 10055 4616 10352 4616 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 7114 5477 262 262 7114 5477 7376 5477 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 10122 6689 297 297 10122 6689 10419 6689 +1 3 0 1 0 33 100 0 20 4.000 1 0.0000 6050 5208 293 293 6050 5208 6343 5208 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 9748 7268 297 297 9748 7268 10045 7268 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 8126 8035 321 321 8126 8035 8447 8035 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 6708 8402 256 256 6708 8402 6964 8402 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 6235 7953 262 262 6235 7953 6497 7953 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 9589 8188 285 285 9589 8188 9874 8188 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 7629 7191 279 279 7629 7191 7908 7191 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 7945 6576 268 268 7945 6576 8213 6576 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 8914 7872 318 318 8914 7872 9232 7872 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 9297 4646 278 278 9297 4646 9575 4646 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 7036 6229 275 275 7036 6229 7311 6229 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 8962 5872 386 386 8962 5872 9348 5872 +1 3 0 1 0 34 100 0 20 4.000 1 0.0000 8718 6822 421 421 8718 6822 9139 6822 +# End of XFig header +2 1 0 1 32 7 101 0 -1 0.000 0 0 -1 0 0 2 + 6330 3862 6957 5965 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6169 3879 6076 4915 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6383 3843 7007 5237 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 5935 3782 5114 4771 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6615 3584 7516 3876 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6229 3025 6271 2159 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6344 3047 6757 1817 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6015 3071 5684 2417 +2 1 0 1 32 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6566 3687 8307 4826 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6573 3230 7636 2584 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 5798 3576 5037 3805 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 5789 3367 4605 3126 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 5810 3296 5532 3187 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6604 3290 6708 3248 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6506 3760 7388 4669 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6249 3878 6262 4017 +2 1 0 1 32 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6644 4398 9020 4620 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6198 4701 6132 4926 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6503 4647 6959 5266 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 5978 4512 5193 4874 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6634 4275 7511 4041 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6519 4096 7705 2664 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6472 4063 6850 3412 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6621 4494 7319 4772 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7655 4593 7730 4250 +2 1 0 1 32 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7886 4911 8263 4950 +2 1 0 1 32 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7864 4766 9282 4169 +2 1 0 1 32 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7831 5053 8649 5646 +2 1 0 1 32 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7653 5169 7891 6313 +2 1 0 1 32 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7598 5175 7625 6912 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7631 4588 7869 2736 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7502 4600 7125 3441 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7248 3369 7596 3751 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6849 2828 6433 2120 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7286 2910 7658 2616 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 5347 2799 5463 2435 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 5102 3329 4909 3638 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 4033 3173 3662 3315 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 4553 2889 5315 2329 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 4453 3330 4611 3628 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 4470 3790 3664 3498 +2 1 0 1 33 7 101 0 -1 0.000 0 0 -1 0 0 2 + 8569 5259 8678 6402 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 8661 5233 8798 5523 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 8661 5233 8798 5523 +2 1 0 1 32 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7881 4242 8588 6421 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 10021 5480 9011 6520 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 9960 5395 9310 5706 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 10001 6118 9087 6619 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 9966 5952 9347 5903 +2 1 0 1 33 7 101 0 -1 0.000 0 0 -1 0 0 2 + 9901 4870 8936 6462 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 9860 4840 9215 5581 +2 1 0 1 32 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7316 5646 8394 6551 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 9826 6717 9137 6782 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 9879 6518 9278 6095 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 9475 7150 9105 6990 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 9602 7009 9151 6209 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 8441 7970 8602 7937 +2 1 0 1 33 7 101 0 -1 0.000 0 0 -1 0 0 2 + 8267 7747 8533 7201 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7963 7758 7771 7431 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7805 8021 6498 7965 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6746 8148 6995 6501 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6863 8197 7460 7413 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6522 8225 6426 8134 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 6346 7715 6920 6479 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 9436 7947 8944 7178 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 9331 8067 9203 8007 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7894 7101 8318 6957 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 8201 6658 8316 6694 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7694 6481 7293 6327 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 8855 7559 8795 7237 +2 1 0 1 33 7 101 0 -1 0.000 0 0 -1 0 0 2 + 8922 7553 8953 6258 +2 1 0 1 33 7 101 0 -1 0.000 0 0 -1 0 0 2 + 9225 4915 8826 6415 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 9223 4914 9064 5500 +2 1 0 1 33 7 101 0 -1 0.000 0 0 -1 0 0 2 + 7296 6321 8320 6682 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 7307 6179 8582 5943 +2 1 0 1 33 7 100 0 -1 4.000 0 0 -1 0 0 2 + 8866 6246 8823 6414 +2 3 0 1 0 33 100 0 20 4.000 0 0 -1 0 0 18 + 6845 1283 6944 1301 7029 1354 7090 1435 7118 1532 7108 1632 + 7063 1722 6989 1790 6895 1827 6794 1827 6700 1790 6626 1722 + 6581 1632 6572 1532 6599 1435 6660 1354 6746 1301 6845 1283 +2 1 0 1 33 7 101 0 -1 0.000 0 0 -1 0 0 2 + 8242 7736 8823 6232 +2 1 0 1 33 7 101 0 -1 0.000 0 0 -1 0 0 2 + 9451 4319 8836 6418 +4 1 35 100 0 16 12 0.0000 4 165 120 6208 3521 0\001 +4 1 35 100 0 16 12 0.0000 4 165 120 6296 4435 1\001 +4 1 35 100 0 16 12 0.0000 4 165 120 7593 4951 2\001 +4 1 35 100 0 16 12 0.0000 4 165 120 7021 3188 3\001 +4 1 35 100 0 16 12 0.0000 4 165 120 5258 3149 4\001 +4 1 35 100 0 16 12 0.0000 4 165 120 4312 3133 5\001 +4 1 35 100 0 16 12 0.0000 4 165 120 4751 3960 6\001 +4 1 35 100 0 16 12 0.0000 4 165 120 7910 2486 7\001 +4 1 35 100 0 16 12 0.0000 4 165 120 8542 5048 8\001 +4 1 35 100 0 16 12 0.0000 4 165 120 9525 4135 9\001 +4 1 35 100 0 16 12 0.0000 4 165 240 5552 2222 10\001 +4 1 35 100 0 16 12 0.0000 4 165 240 6845 1627 11\001 +4 1 35 100 0 16 12 0.0000 4 165 240 6285 1939 12\001 +4 1 35 100 0 16 12 0.0000 4 165 240 7792 4034 13\001 +4 1 35 100 0 16 12 0.0000 4 165 240 10228 5337 14\001 +4 1 35 100 0 16 12 0.0000 4 165 240 10262 6044 15\001 +4 1 35 100 0 16 12 0.0000 4 165 240 3417 3478 16\001 +4 1 35 100 0 16 12 0.0000 4 165 240 4927 5067 17\001 +4 1 35 100 0 16 12 0.0000 4 165 240 10055 4685 18\001 +4 1 35 100 0 16 12 0.0000 4 165 240 7114 5546 19\001 +4 1 35 100 0 16 12 0.0000 4 165 240 10122 6758 20\001 +4 1 35 100 0 16 12 0.0000 4 165 240 6050 5278 21\001 +4 1 35 100 0 16 12 0.0000 4 165 240 9748 7338 22\001 +4 1 35 100 0 16 12 0.0000 4 165 240 8126 8104 23\001 +4 1 35 100 0 16 12 0.0000 4 165 240 6708 8472 24\001 +4 1 35 100 0 16 12 0.0000 4 165 240 6235 8022 25\001 +4 1 35 100 0 16 12 0.0000 4 165 240 9589 8256 26\001 +4 1 35 100 0 16 12 0.0000 4 165 240 7629 7261 27\001 +4 1 35 100 0 16 12 0.0000 4 165 240 7945 6645 28\001 +4 1 35 100 0 16 12 0.0000 4 165 240 8914 7940 29\001 +4 1 35 100 0 16 12 0.0000 4 165 240 9297 4714 30\001 +4 1 35 100 0 16 12 0.0000 4 165 240 7036 6297 31\001 +4 1 35 100 0 16 12 0.0000 4 165 240 8962 5941 32\001 +4 1 35 100 0 16 12 0.0000 4 165 240 8718 6891 33\001 diff --git a/doc/papers/iccs06/net.bib b/doc/papers/iccs06/net.bib new file mode 100644 index 0000000..30534f2 --- /dev/null +++ b/doc/papers/iccs06/net.bib @@ -0,0 +1,1559 @@ + +@PhdThesis{clarkson03, + author = { Gavin Clarkson }, + title = { Objective Identification of Patent Thickets: + A Network Analytic Approach }, + school = { Harvard Business School }, + year = { 2003 } +} + +@Misc{vazquez01a, + author = { Alexei Vazquez }, + title = { Statistics of Citation Networks }, + howpublished = { cond-mat/0105031 }, + month = { May }, + year = { 2001 } +} + +@Article{vazquez01, + author = { Alexei Vazquez }, + title = { Knowing a network by walking on it: emergence of scaling }, + journal = { Europhys. Lett. }, + year = { 2001 }, + volume = { 54 }, + pages = { 430 }, + note = { cond-mat/0006132 } +} + +@TechReport{breschi04, + author = { Stefano Breschi and Francesco Lissoni }, + title = { Knowledge networks from patent data: + Methodological issues and research targets }, + year = { 2004 }, + month = { Jan }, + institution = { CESPRI, Centre for Research on Innovation and + Internationalisation Processes, + Universita' Bocconi, Milano, Italy }, + type = { CESPRI Working Papers }, + url = { http://ideas.repec.org/p/cri/cespri/wp150.html}, + number = { 150 } +} + +@Misc{hajra04, + author = { Kamalika Basu Hajra and Parongama Sen }, + title = { Aging in citation networks }, + howpublished = { cond-mat/0409017}, + year = { 2004 }, + month = { Sep } +} + +@Misc{redner04, + author = { Sidney Redner }, + title = { Citation Statistics From More Than a Century of Physical Review }, + howpublished = { physics/0407137 }, + year = { 2004 }, + url = { http://arxiv.org/abs/physics/0407137 } +} + +@Article{redner05, + author = { Sidney Redner }, + title = { Citation Statistics from 110 Years of Physical Review }, + journal = { Physics Today }, + volume = { 58 }, + pages = { 49 }, + year = { 2005 } +} + +@Article{borner04, + author = { Katy B\"orner and Jeegar T. Maru and Robert L. Goldstone }, + title = { The simultaneous evolution of author and paper networks }, + journal = { Proc. Natl. Acad. Sci. USA }, + year = { 2004 }, + month = { Apr }, + volume = { 101 }, + pages = { 5266--5273 } +} + +@Misc{sen04, + author = { Parongama Sen }, + title = { Directed Accelerated Growth: Application in + Citation Network }, + howpublished = { cond-mat/0409154 }, + year = { 2004 }, + month = { Sep } +} + +@Article{mitzenmacher04, + author = { Michael Mitzenmacher }, + title = { A brief history of generative models for power law and + lognormal distributions }, + journal = { Internet Mathematics }, + volume = { 1 }, + pages = { 226--251 }, + year = { 2004 }, +} + +@Misc{goldstein04, + author = { Michel L. Goldstein and Steven A. Morris and + Gary G. Yen }, + title = { A Group-Based Yule Model for Bipartite + Author-Paper Networks }, + howpublished = { cond-mat/0409205 }, + year = { 2004 }, + month = { Sep } +} + +@Book{wasserman94, + author = { Wasserman, S. and Faust, K. }, + title = { Social network analysis methods and applications}, + year = { 1994 }, + publisher = { Cambridge University Press }, + address = { New York} +} + +@Article{zhu03, + author = { Han Zhu and Xinran Wang and Jian-Yang Zhu }, + title = { Effect of aging on network structure }, + year = { 2003 }, + journal = { Phys. Rev. E }, + volume = { 68 }, + pages = { 056121 } +} + +@Article{dorogovtsev00, + author = { S. N. Dorogovtsev and J. F. F. Mendes }, + title = { Evolution of networks with aging of sites }, + year = { 2000 }, + journal = { Phys. Rev. E }, + volume = { 62 }, + number = { 2 }, + pages = { 1842--1845 } +} + +@Article{klemm02, + author = { Konstantin Klemm and V{\'\i{}}ctor M. Egu{\'\i{}}luz }, + title = { Highly clustered scale-free networks }, + year = { 2002 }, + journal = { Phys. Rev. E }, + volume = { 65 }, + pages = { 036123 } +} + +@Article{amaral00, + author = { L. A. N. Amaral and A. Scala and M. Barh{\'e}l\'emy and + H. E. Stanley }, + title = { Classes of small-world networks }, + journal = { Proc. Natl. Acad. Sci. USA }, + year = { 2000 }, + volume = { 97 }, + number = { 21 }, + pages = { 11149--11152 }, + month = { 10 }, +} + +@Article{albert00, + author = { R\'eka Albert and Albert-L\'aszl\'o Barab\'asi }, + title = { Topology of evolving networks: Local + events and universality }, + journal = { Phys. Rev. Lett. }, + volume = { 85 }, + pages = { 5234 }, + year = { 2000 } +} + +@Article{barabasi99, + author = { Albert-L\'aszl\'o Barab\'asi and R\'eka Albert + and Hawoong Jeong}, + title = { Mean-field theory for scale-free random networks }, + journal = { Physica~A }, + volume = { 272 }, + pages = { 173--187}, + year = { 1999 }, + note = { cond-mat/9907068 } +} + +@Article{newman04, + author = { Newman, M. E. J. }, + title = { Detecting community structure in networks }, + journal = { Eur. Phys. J. B }, + volume = { 38 }, + pages = { 321--330 }, + year = { 2004 } +} + +@Article{ravasz03, + author = { Erzs\'ebet Ravasz and Albert-L\'aszl\'o Barab\'asi }, + title = { Hierarchical Organization in Complex Networks }, + journal = { Phys. Rev. E }, + volume = { 67 }, + pages = { 026112 }, + year = { 2003 } +} + +@Article{newman04a, + author = { M. E. J. Newman }, + title = { Power laws, {P}areto distributions and {Z}ipf's law }, + journal = { Contemporary Physics }, + year = { 2005 }, + volume = { in press }, + url = { http://aps.arxiv.org/abs/cond-mat/0412004/ } +} + +@Article{newman03, + author = { M. E. J. Newman }, + title = { The structure and function of complex networks }, + journal = { SIAM Review }, + volume = { 45 }, + pages = { 167--256 }, + year = { 2003 } +} + +@Article{brin98, + Author = { Brin, S. and Page, L. }, + Title = { The anatomy of a large-scale hypertextual + Web search engine }, + Journal = { Computer Networks }, + Volume = { 30 }, + Pages = { 107-- 117}, + Year = { 1998 } +} + +@techreport{page98, + author = { Lawrence Page and Sergey Brin and Rajeev Motwani and + Terry Winograd }, + institution = { Stanford Digital Library Technologies Project }, + title = { The PageRank Citation Ranking: Bringing Order to the Web }, + year = { 1998 }, + url = { citeseer.ist.psu.edu/page98pagerank.html } +} + +@Article{morris04, + author = { S. A. Morris }, + title = { Manifestation of emerging specialities in journal + literature: a growth model of papers, references, + exemplars, bibliographic coupling, co-citation and + clustering coefficient distribution }, + journal = {J. Am. Soc. Inf. Sci. Technol.}, + volume = { in press }, + year = { 2005 } +} + +@article{morris02, + author = {Steven A. Morris and G. Yen and Zheng Wu and Benyam Asnake}, + title = {Time line visualization of research fronts}, + journal = {J. Am. Soc. Inf. Sci. Technol.}, + volume = {54}, + number = {5}, + year = {2003}, + issn = {1532-2882}, + pages = {413--422}, + doi = {http://dx.doi.org/10.1002/asi.10227}, + publisher = {John Wiley \& Sons, Inc.}, + } + +@article{singh04, + author = { Jasjit Singh }, + title = { Collaborative Networks as Determinants of Knowledge + Diffusion Patterns }, + year = { 2004 }, + volume = { Forthcoming }, + journal = { Management Science } +} + +@TECHREPORT{duguet03, +AUTHOR={Duguet Emmanuel and MacGarvie Megan}, +TITLE={How Well Do Patent Citations Measure Flows of Technology? + Evidence from French Innovation Surveys}, +YEAR=2004, +MONTH=Nov, +INSTITUTION={Economics Working Paper Archive at WUSTL}, +TYPE={Development and Comp Systems}, +URL={http://ideas.repec.org/p/wpa/wuwpdc/0411018.html}, +NUMBER={0411018} +} + +@TECHREPORT{paci03, +AUTHOR={Raffaele Paci and Ernesto Batteta}, +TITLE={Innovation Networks and Knowledge Flows across the European Regions}, +YEAR=2003, +INSTITUTION={Centre for North South Economic Research, University of + Cagliari and Sassari, Sardinia}, +TYPE={Working Paper CRENoS}, +URL={http://ideas.repec.org/p/cns/cnscwp/200313.html}, +NUMBER={200313} +} + +@techreport{criscuolo02, +AUTHOR={Criscuolo,Paola}, +TITLE={Reverse Technology Transfer: A Patent Citation Analysis of the + European Chemical and Pharmaceutical sectors}, +YEAR=2002, +INSTITUTION={Maastricht : MERIT, Maastricht Economic Research + Institute on Innovation and Technology}, +TYPE={Research Memoranda}, +URL={http://ideas.repec.org/p/dgr/umamer/2002036.html}, +NUMBER={036} +} + +@article{leydesdorff05, + author = { Loet Leydesdorff }, + title = { The university-industry knowledge + relationship: Analyzing patents and + the science base of technologies }, + journal = {J. Am. Soc. Inf. Sci. Technol.}, + valume = { forthcoming }, + year = 2005 +} + +@inproceedings{cassi04, + author = { Lorenzo Cassi and Lorenzo Zirulia }, + title = { Friend and Rivals: modelling the social relations of + inventors. }, + booktitle = { 4th Congress on Proximity Economics: Proximity, Networks + and Co-ordination }, + year = {2004} +} + +@article{hummon89, + author = { Hummon N.P. and Doreian P. }, + title = { Connectivity in a Citation Network: + The Development of DNA Theory }, + journal = { Social Networks }, + volume = 11, + year = 1989, + pages = { 39--63} +} + +@misc{batagelj03, + author = { Vladimir Batagelj }, + title = { Efficient Algorithms for Citation Network Analysis }, + note = { arXiv:cs.DL/0309023 }, + year = 2003 +} + +@TECHREPORT{breschi03, +AUTHOR={Stefano Breschi and Francesco Lissoni}, +TITLE={Mobility and Social Networks: Localised Knowledge Spillovers Revisited}, +YEAR=2003, +MONTH=Mar, +INSTITUTION={CESPRI, Centre for Research on Innovation and Internationalisation Processes, Universita' Bocconi, Milano, Italy}, +TYPE={CESPRI Working Papers}, +URL={http://ideas.repec.org/p/cri/cespri/wp142.html}, +NUMBER={142} +} + +@ARTICLE{cowan00, +AUTHOR={Cowan, Robin and David, Paul A and Foray, Dominique}, +TITLE={The Explicit Economics of Knowledge Codification and Tacitness}, +JOURNAL={Industrial and Corporate Change}, +YEAR=2000, +VOLUME={9}, +NUMBER={2}, +PAGES={211-53}, +MONTH={June}, +URL={http://ideas.repec.org/a/oup/indcch/v9y2000i2p211-53.html} +} + +@TECHREPORT{cowan02, +AUTHOR={R. Cowan and N. Jonard and J.-B. Zimmermann}, +TITLE={The Joint Dynamics of Networks and Knowledge}, +YEAR=2002, +MONTH=Jul, +INSTITUTION={Society for Computational Economics}, +TYPE={Computing in Economics and Finance 2002}, +URL={http://ideas.repec.org/p/sce/scecf2/354.html}, +NUMBER={354} +} + +@ARTICLE{cowan97, +AUTHOR={Cowan, Robin and Foray, Dominique}, +TITLE={The Economics of Codification and the Diffusion of Knowledge}, +JOURNAL={Industrial and Corporate Change}, +YEAR=1997, +VOLUME={6}, +NUMBER={3}, +PAGES={595-622}, +MONTH={September}, +URL={http://ideas.repec.org/a/oup/indcch/v6y1997i3p595-622.html} +} + +@TECHREPORT{cowan00a, +AUTHOR={Cowan,Robin and Jonard,Nicolas}, +TITLE={The Dynamics of Collective Invention}, +YEAR=2000, +INSTITUTION={Maastricht : MERIT, Maastricht Economic Research Institute on Innovation and Technology}, +TYPE={Research Memoranda}, +URL={http://ideas.repec.org/p/dgr/umamer/2000018.html}, +NUMBER={018} +} + +@TECHREPORT{cowan01, +AUTHOR={Cowan,Robin and Jonard,Nicolas}, +TITLE={The Workings of Scientific Communities}, +YEAR=2001, +INSTITUTION={Maastricht : MERIT, Maastricht Economic Research Institute on Innovation and Technology}, +TYPE={Research Memoranda}, +URL={http://ideas.repec.org/p/dgr/umamer/2001030.html}, +NUMBER={030} +} + +@TECHREPORT{cowan03, +AUTHOR={Cowan,Robin and Jonard,Nicolas and Ã\u2013zman,Müge}, +TITLE={Knowledge Dynamics in a Network Industry}, +YEAR=2003, +INSTITUTION={Maastricht : MERIT, Maastricht Economic Research Institute on Innovation and Technology}, +TYPE={Research Memoranda}, +URL={http://ideas.repec.org/p/dgr/umamer/2003003.html}, +NUMBER={003} +} + +@TECHREPORT{cowan04, +AUTHOR={Cowan,Robin and Jonard,Nicolas and Zimmermann,J-B}, +TITLE={On the creation of networks and knowledge}, +YEAR=2004, +INSTITUTION={Maastricht : MERIT, Maastricht Economic Research Institute on Innovation and Technology}, +TYPE={Research Memoranda}, +URL={http://ideas.repec.org/p/dgr/umamer/2004010.html}, +NUMBER={010} +} + +@TECHREPORT{cowan04a, +AUTHOR={Cowan,Robin}, +TITLE={Network models of innovation and knowledge diffusion}, +YEAR=2004, +INSTITUTION={Maastricht : MERIT, Maastricht Economic Research Institute on Innovation and Technology}, +NOTE={available at http://ideas.repec.org/p/dgr/umamer/2004016.html}, +NUMBER={016} +} + + +@Article{barabasi99a, + author = {Barab\'asi, Albert-L\'aszl\'o and R\'eka Albert}, + title = {Emergence of scaling in random networks}, + journal = {Science}, + year = {1999}, + volume = {286}, + number = {5439}, + pages = {509--512} +} + +@Article{albert02, + author = { R\'eka Albert and Albert-L\'aszl\'o Barab\'asi }, + title = { Statistical mechanics of complex networks }, + journal = { Reviews of Modern Physics }, + volume = { 74 }, + pages = { 47 }, + year = { 2002 } +} + +@Book{nocedal99, + author = { Nocedal, J. and Wright, S. J. }, + year = { 1999 }, + title = { Numerical Optimization }, + publisher = { Springer } +} + +@Article{price65, + author = { Price, D. J. de S.}, + title = { Networks of scientific papers}, + journal = { Science }, + volume = { 149 }, + pages = { 510-­515 }, + year = { 1965 } +} + +@Article{redner98, + author = { Redner, S. }, + title = { How popular is your papeer? An empirical + study of the citation distribution }, + journal = { Eur. Phys. J. B }, + volume = { 4 }, + pages = { 131­-134 }, + year = { 1998 } +} + +@book{egghe00, + author = { Egghe, L. and Rousseau, R. }, + title = { Introduction to Informetrics }, + publisher = { Elsevier }, + address = { Amsterdam }, + year = { 1990 } +} + +@Article{jeong03, + title = { Measuring preferential attachment for evolving networks }, + author = { Hawoong Jeong and Zolt\'an N\'eda and + Albert-L\'aszl\'o Barab\'asi }, + journal = { Europhys. Lett. }, + volume = { 61 }, + pages = { 567--572 }, + year = { 2003 } +} + +@Misc{fronczak05, + author = { Agata Fronczak and Piotr Fronczak and Janusz A. Holyst }, + title = { How to calculate the main characteristics of random uncorrelated networks}, + year = { 2005 }, + howpublished = { cond-mat/0502663 }, + url = { http://arxiv.org/abs/cond-mat/0502663 } +} + +@Article{bollobas04, + author = { B\'ela Bollob\'as and Oliver Riordan }, + title = { The diameter of a scale-free random graph }, + year = { 2004 }, + journal = { Combinatorica }, + volume = { 24 }, + number = { 1 }, + pages = { 5--34 } +} + +@Misc{hall04, + author = { Bronwyn H. Hall }, + title = { Exploring the patent explosion }, + institution = { National Bureau of Economic Research }, + year = { 2004 }, + number = { Working Paper 10605 }, + note = { Working Paper 10605, National Bureau of Economic Research } +} + +@book{jaffe04, + author = { Adam B. Jaffe and Josh Lerner }, + title = { Innovation and Its Discontents : How Our Broken Patent + System is Endangering Innovation and Progress, and What + to Do About It }, + publisher = { Princeton University Press }, + year = { 2004 } +} + +@article{allison02, + author = { Allison, John R. and Mark A. Lemley }, + title = { The Growing Complexity of the United States Patent System }, + volume = { 82 }, + journal = { Boston University Law Review }, + pages = { 77 }, + year = { 2002 } +} + +@article{allison04, + author = { Allison, John R. and Mark A. Lemley and Kimberly A. Moore + and R. Derek Trunkey }, + title = { Valuable Patents }, + volume = { 92 }, + journal = { Georgetown Law Journal }, + pages = { 435 }, + year = { 2004 } +} + +@article{astebro03, + author = { Astebro, Thomas }, + title = { The Return to Independent Invention: Evidence of + Unrealistic Optimism, Risk Seeking or Skewness + Loving? }, + volume = { 113 }, + journal = { The Economic Journal }, + pages = { 226--239 }, + year = { 2003 } +} + +@book{bak99, + author = { Bak, Per }, + title = { How Nature Works: The Science of Self-Organized + Criticality }, + publisher = { Springer-Verlag Telos }, + year = { 1999 } +} + +@book{barabasi02, + author = { Barab\'asi, Albert-L\'aszl\'o }, + title = { Linked: The New Science of Networks }, + publisher = { Perseus Publishing }, + year = { 2002 } +} + +@inproceedings{bessen03, + author = { Bessen, James and Robert M. Hunt }, + title = { The Software Patent Experiment }, + booktitle = { Proceedings of OECD Conference on Patents, Innovation + and Economic Performance OECD }, + month = { April }, + year = { 2003 } +} + +@article{bessen05, + author = { Bessen, James and Michael J. Meurer }, + title = { Lessons For Patent Policy From Empirical Research On + Patent Litigation }, + volume = { 9 }, + journal = { Lewis \& Clark Law Review }, + pages = { 1 }, + year = { 2005 } +} + +@article{burk03, + author = { Burk, Dan L. and Mark A. Lemley }, + title = { Policy Levers in Patent Law }, + volume = { 79 }, + journal = { Virginia Law Review }, + pages = { 101 }, + year = { 2003 } +} + +@incollection{burk05, + author = { Burk, Dan L. and Mark A. Lemley }, + title = { Designing Optimal Software Patents }, + booktitle = { Intellectual Property Rights In Frontier Industries: + Software And Biotechnology }, + editor = { Robert Hahn }, + publisher = { AEI Press }, + year = { 2005 } +} + +@book{chakrabarti05, + editor = { Chakrabarti, Bikas K. and Arnab Chatterjee and Sudhakar + Yarlagadda }, + title = { Econophysics of Wealth Distributions (New Economic + Windows) }, + publisher = { Springer }, + year = { 2005 } +} + +@book{cohen03, + editor = { Cohen, Wesley M. and Stephen A. Merrill }, + title = { Patents in the Knowledge-Based Economy }, + year = { 2003 }, + publisher = { National Research Council of the National Academies, + The National Academies Press} +} + +@misc{crouch05, + author = { Crouch, Dennis }, + title = { Children Rejoice -- Peanut Butter and Jelly Patent + Rejected on Appeal }, + howpublished = { Patently-O Blog, April 8 }, + url = { http://patentlaw.typepad.com }, + year = { 2005 } +} + +@book{dorogovtsev03, + author = { Dorogovtsev, S. N. and J. F. F. Mendes }, + title = { Evolution of Networks. From Biological Nets to the + Internet and WWW }, + publisher = { Oxford University Press }, + year = { 2003 } +} + +@article{dreyfuss89, + author= { Dreyfuss, Rochelle Cooper }, + title = { The Federal Circuit: A Case Study in Specialized Courts }, + volume = { 64 }, + journal = { New York University Law Review }, + pages = { 1 }, + year = { 1989 } +} + +@article{dreyfuss04, + author = { Dreyfuss, Rochelle Cooper }, + title = { The Federal Circuit: A Continuing Experiment in + Specialization }, + volume = { 54 }, + journal = { Case Western Reserve Law Review }, + pages = { 769 }, + year = { 2004 } +} + +@article{duguet05, + author = { Duguet, Emmanuel and Megan MacGarvie }, + title = { How Well Do Patent Citations Measure Flows of Technology? + Evidence from French Innovation Surveys }, + volume = { 14 }, + journal = { Economics of Innovation and New Technology }, + pages = { 374--93 }, + year = { 2005 } +} + +@Misc{ftc03, + author = { {Federal Trade Commission} }, + title = { To Promote Innovation: + The Proper Balance of Competition and Patent Law + and Policy }, + howpublished = { Report }, + year = { 2003 }, + month = { October } +} + +@article{frenken05, + author = { Frenken, Koen }, + title = { Technological Innovation and Complexity Theory }, + journal = { Economics of Innovation and New Technology }, + year = { 2005 }, + volume = { forthcoming } +} + +@incollection{graham03, + author = { Graham, Stuart J. H. and David C. Mowery }, + title = { Intellectual Property Protection in the U.S. Software + Industry }, + editor = { Cohen, Wesley M. and Stephen A. Merrill }, + booktitle = { Patents in the Knowledge-Based Economy }, + publisher = { National Research Council of the + National Academies, The National Academies Press }, + year = { 2003 } +} + +@article{griliches90, + author = { Griliches, Zvi }, + title = { Patent Statistics as Economic Indicators: A Survey }, + volume = { 28 }, + journal = { Journal of Economic Literature }, + pages = { 1661--1707 }, + year = { 1990 } +} + +@article{hagedoorn03, + author = { Hagedoorn, John and Myriam Cloodt }, + title = { Measuring Innovative Performance: Is There an Advantage + in Using Multiple Indicators? }, + volume = { 32 }, + journal = { Research Policy }, + pages = { 1365-1379 }, + year = { 2003 } +} + +@article{hall01, + author = { Hall, Bronwyn and Rosemarie Ziedonis }, + title = { The Patent Paradox Revisited: An Empirical Study of + Patenting in the U.S. Semiconductor Industry + 1979-1995 }, + volume = { 32 }, + journal = { RAND Journal of Economics }, + pages = { 101 }, + year = { 2001 } +} + +@InCollection{hall03, + author = { Hall, Bronwyn H. and Adam B. Jaffe and Manuel + Trajtenberg }, + editor = { Adam B. Jaffe and Manuel Trajtenberg}, + booktitle = { Patents, Citations, and + Innovations: A Window on the Knowledge Economy }, + title = {The NBER Patent Citation Data File: Lessons, + Insights and Methodological Tools}, + publisher = { MIT Press }, + year = { 2003 }, +} + +@article{hall05, + author = { Hall, Bronwyn H. }, + title = { Exploring the Patent Explosion }, + journal = { Journal of Technology Transfer }, + volume = { 30 }, + pages = { 35--48 }, + year = { 2005 } +} + +@article{harhoff98, + author = { Harhoff, D., F. Narin and F.M. Scherer and K. Vopel }, + title = { Citation Frequency and the Value of Patented + Inventions }, + volume = { 81 }, + journal = { Review of Economics and Statistics }, + pages = { 511--515 }, + year = { 1998 } +} + +@article{harhoff03, + author = { Harhoff, Dietmar and Scherer, Frederic M. and Vopel, + Katrin }, + title = { Citations, Family Size, Opposition and the Value of Patent + Rights }, + volume = { 32 }, + journal = { Research Policy }, + pages = { 1343--1363 }, + year = { 2003 } +} + +@article{heald05, + author = { Heald, Paul J. }, + title = { A Transaction Costs Theory of Patent Law }, + volume = { 66 }, + journal = { Ohio State Law Journal }, + year = { 2005 } +} + +@article{heller98, + author = { Heller, Michael and Rebecca S. Eisenberg }, + title = { Can Patents Deter Innovation? The Anticommons in + Biomedical Research }, + volume = { 280 }, + journal = { Science }, + pages = { 698--701 }, + month = { May }, + year = { 1998 } +} + +@article{hohenberg77, + author = { Hohenberg, P.C. and B. I. Halperin }, + title = { Theory of Dynamic Critical Phenomena }, + volume = { 49 }, + journal = { Reviews of Modern Physics }, + pages = { 435 }, + year = { 1977 } +} + +@Book{jaffe02, + author = { A. Jaffe and M. Trajtenberg }, + title = { Patents, Citations \& Innovations: A Window on the + Knowledge Economy }, + publisher = { MIT Press }, + year = { 2003 } +} + +@book{jensen98, + author = { Jensen, Henrik Jeldtoft }, + title = { Self-Organized Criticality: Emergent Complex Behavior in + Physical and Biological Systems }, + publisher = { Cambridge University Press }, + series = { Cambridge Lecture Notes in Physics }, + number = { 10 }, + year = { 1998 } +} + +@article{krapivsky00, + author = { Krapivsky, P.L. and S. Redner and F. Leyvraz }, + title = { Connectivity of Growing Random Networks }, + volume = { 85 }, + journal = { Physical Review Letters }, + pages = { 4629--4632 }, + year = { 2000 } +} + +@article{landes04, + author = { Landes, William M. and Richard A. Posner }, + title = { An Empirical Analysis of the Patent Court }, + volume = { 71 }, + journal = { University of Chicago Law Review }, + pages = { 111 }, + year = { 2004 } +} + +@article{lanjouw04, + author = { Lanjouw, Jean O. and Mark Schankerman }, + title = { Patent Quality And Research Productivity: Measuring + Innovation With Multiple Indicators }, + volume = { 114 }, + journal = { Economic Journal }, + pages = { 441--465 }, + year = { 2004 } +} + +@article{lemley05, + author = { Lemley, Mark A. and Carl Shapiro }, + title = { Probabilistic Patents }, + volume = { 19 }, + journal = { Journal of Economic Perspectives }, + pages = { 75--98 }, + year = { 2005 } +} + +@article{litmann97, + author = { Litmann, Allan N. }, + title = { Restoring the Balance of Our Patent System }, + volume = { 37 }, + journal = { IDEA }, + pages = { 545 }, + year = { 1997 } +} + +@article{long02, + author = { Long, Clarisa }, + title = { Patent Signals }, + volume = { 69 }, + journal = { University of Chicago Law Review }, + pages = { 625 }, + year = { 2002 } +} + +@article{lunney01, + author = { Lunney, Glynn S., Jr. }, + title = { E-Commerce And Equivalence: Definining The Proper Scope Of + Internet Patents Symposium: E-Obviousness }, + volume = { 7 }, + journal = { Michigan Telecommunications and Technology Law + Review }, + pages = { 363 }, + year = { 2000/2001 } +} + +@book{ma00, + author = { Ma, Shanggeng and Shang-Keng Ma }, + title = { Modern Theory of Critical Phenomena }, + publisher = { Perseus }, + year = { 2000 } +} + +@misc{malchup58, + author = { Malchup, Fritz }, + title = { An Economic Review of the Patent System }, + howpublished = { Study No. 15 of the Subcomm. On Patents, + Trademarks, and Copyrights of the Committee on the + Judiciary, 85th Cong., 2d sess. }, + year = { 1958 } +} + +@book{mandelbrot04, + author = { Mandelbrot, Benoit and Richard L. Hudson }, + title = { The (Mis)behavior of Markets }, + publisher = { Basic Books }, + year = { 2004 } +} + +@article{mann05, + author = { Mann, Ronald J. }, + title = { The Myth of the Software Patent Thicket: An Empirical + Investigation of the Relationship Between + Intellectual Property and Innovation in Software + Firms }, + volume = { 83 }, + journal = { Texas Law Review }, + pages = { 961 }, + year = { 2005 } +} + +@book{mantegna99, + author = { Mantegna, Rosario N. and H. Eugene Stanley }, + title = { An Introduction to Econophysics: Correlations and + Complexity in Finance }, + publisher = { Cambridge University Press }, + year = { 1999 } +} + +@article{marco05, + author = { Marco, Alan C. }, + title = { The Option Value of Patent Litigation: Theory and + Evidence }, + journal = { Review of Financial Economics }, + volume = { forthcoming }, + year = { 2005 } +} + +@article{marsili05, + author = { Marsili, Orietta and Ammon Salter }, + title = { `{I}nequality' of Innovation: Skewed Distributions and the + Returns to Innovation in Dutch Manufacturing }, + volume = { 14 }, + journal = { Economics of Innovation and New Technologies }, + pages = { 83--102 }, + year = { 2005 } +} + +@article{maurseth05, + author = { Maurseth, Per Botolf }, + title = { Lovely but Dangerous: The Impact of Patent Citations on + Patent Renewal }, + volume = { 14 }, + journal = { Economics of Innovation and New + Technologies }, + pages = { 351--374 }, + year = { 2005 } +} + +@article{merges90, + author = { Merges, Robert P. and Richard R. Nelson }, + title = { On the Complex Economics of Patent Scope }, + volume = { 90 }, + journal = { Columbia Law Review }, + pages = { 839 }, + year = { 1990 } +} + +@article{merges00, + author = { Merges, Robert P. }, + title = { One Hundred Years of Solicitude: Intellectual Property + Law, 1900-2000 }, + volume = { 88 }, + journal = { California Law Review }, + pages = { 2187 }, + year = { 2000 } +} + +@book{merrill04, + editor = { Merrill, Stephen A. and Richard C. Levin and Mark + B. Myers }, + title = { A Patent System for the 21st Century }, + publisher = { National Research Council of the National Academies, + National Academies Press }, + year = { 2004 } +} + +@unpublished{meurer05, + author = { Meurer, Michael and James Bessen }, + title = { The Patent Litigation Explosion }, + note = { Presentation at the Annual Meeting of the American Law and + Economics Association (May 6-7) }, + year = { 2005 } +} + +@article{moore05, + author = { Moore, Kimberly A. }, + title = { Worthless Patents }, + volume = { 20 }, + journal = { Berkeley Technology Law Journal }, + year = { 2005 } +} + +@Article{newman04b, + author = { Newman, M. E. J. }, + title = { Fast Algorithm for Detecting Community Structure in + Networks }, + volume = { 69 }, + journal = { Physical Review E }, + pages = { 066133 }, + year = { 2004 } +} + +@article{newman04c, + author = { Newman, M. E. J. and M. Girvan }, + title = { Finding and Evaluating Community Structure in Networks }, + volume = { 69 }, + journal = { Physical Review E }, + pages = { 026113 }, + year = { 2004 } +} + +@article{palla05, + author = { Palla, Gergely and Imre Der\'enyi and Ill\'es Farkas and + Tam\'as Vicsek }, + title = { Uncovering the Overlapping Community + Structure of Complex Networks in Nature and Society }, + volume = { 435 }, + journal = { Nature }, + pages = { 814--818 }, + year = { 2005 } +} + +@book{pastorsatorras04, + author = { Pastor-Satorras, Romualdo and Alessandro Vespignani }, + title = { Evolution and Structure of the Internet : A Statistical + Physics Approach }, + publisher = { Cambridge University Press }, + year = { 2004 } +} + +@article{rai03, + author = { Rai, Arti K. }, + title = { Engaging Facts And Policy: A Multi-Institutional Approach + To Patent System Reform }, + volume = { 103 }, + journal = { Columbia Law Review }, + pages = { 1035 }, + year = {2003 } +} + +@article{scherer00, + author = { Scherer, F.M. and Dietmar Harhoff }, + title = { Technology Policy for a World of Skew-Distributed Outcomes }, + volume = { 29 }, + journal = { Research Policy }, + pages = { 559--566 }, + year = { 2000 } +} + +@article{scherer00a, + author = { Scherer, F. M. and Dietmar Harhoff and Jorg Kukies }, + title = { Uncertainty and the Size Distribution of Rewards from + Innovation }, + volume = { 10 }, + journal = { Journal of Evolutionary Economics }, + pages = { 175--200 }, + year = { 2000 } +} + +@book{shapiro01, + author = { Shapiro, Carl }, + title = { Navigating the Patent Thicket: Cross Licenses, Patent + Pools, and Standard-Setting in Innovation Policy and + the Economy }, + volume = { I }, + note = { Adam Jaffe and Joshua Lerner and Scott Stern, eds. }, + publisher = { MIT Press }, + year = { 2001 } +} + +@article{silverberg05, + author = { Silverberg, Gerald and Bart Verspagen }, + title = { A Percolation Model of Innovation in Complex Technology + Spaces }, + volume = { 29 }, + journal = { Journal of Economic Dynamics and Control }, + pages = { 225--244 }, + year = { 2005 } +} + +@article{song05, + author = { Song, Chaoming and Shlomo Havlin and Hern\'an A. Makse }, + title = { Self-similarity of Complex Networks }, + volume = { 433 }, + journal = { Nature }, + pages = { 392--395 }, + year = { 2005 } +} + +@book{stanley71, + author = { Stanley, H. Eugene }, + title = { Introduction to Phase Transitions and Critical Phenomena }, + publisher = { Oxford University Press }, + year = { 1971 } +} + +@article{stanley99, + author = { Stanley, H. Eugene }, + title = { Scaling, Universality, and Renormalization: Three Pillars + of Modern Critical Phenomena }, + volume = { 71 }, + journal = { Reviews of Modern Physics }, + pages = { S358 }, + year = { 1999 } +} + +@article{ thomas03, + author = { Thomas, John R. }, + title = { Formalism At The Federal Circuit }, + volume = { 52 }, + journal = { American University Law Review }, + pages = { 771 }, + year = { 2003 } +} + +@article{trajtenberg90, + author = { Trajtenberg, Manuel }, + title = { A Penny for your Quotes: Patent Citations and the Value + of Innovations }, + volume = { 21 }, + journal = { RAND Journal of Economics }, + pages = { 172--87 }, + year = { 1990 } +} + +@misc{turner05, + author = { Turner, John L. }, + title = { In Defense of the Patent Friendly Court Hypothesis: + Theory and Evidence }, + url = { http://www.terry.uga.edu/~jlturner/PatentFCH.pdf }, + year = { 2005 } +} + +@article{varian04, + author = { Varian, Hal R. }, + title = { Patent Protection Gone Awry }, + journal = { New York Times, Economic Scene }, + year = { October 21, 2004 } +} + +@article{wagner05, + author = { Wagner, R. Polk and Gideon Parchomovsky }, + title = { Patent Portfolios }, + volume = { 154 }, + journal = { University of Pennsylvania Law Review }, + year = { 2005 } +} + +@article{watts98, + author = { Watts, Duncan J. and Steven H. Strogatz }, + title = { Collective dynamics of small world networks }, + volume = { 393 }, + journal = { Nature }, + pages = { 440--442 }, + year = { 1998 } +} + +@book{watts02, + author = { Watts, Duncan J. }, + title = { Six Degrees: The Science of a Connected Age }, + publisher = { W.W. Norton \& Co. }, + year = { 2002 } +} + +@book{wille02, + author = { Wille, Luc T. }, + title = { New Directions in Statistical Physics: Econophysics, + Bioinformatics, and Pattern Recognition }, + publisher = { Springer }, + year = { 2002 } +} + +@article{wu04, + author = { Wu, Fang and Bernardo A. Huberman }, + title = { Finding Communities in Linear Time: A Physics Approach }, + volume = { 38 }, + journal = { European Physics Journal B }, + pages = { 331--338 }, + year = { 2004 } +} + +@article{zhou03, + author = { Zhou, Haijun }, + title = { Distance, Dissimilarity Index, and Network Community + Structure }, + volume = { 67 }, + journal = { Physical Review E }, + pages = { 061901 }, + year = { 2003 } +} + +@incollection{ziedonis04, + author = { Ziedonis, Arvids and Bhaven N. Sampat }, + title = { Patent Citations and the Economic Value of Patents: A + Preliminary Assessment }, + booktitle = { Handbook of Quantitative Science and Technology + Research }, + editor = { Henk Moed and Wolfgang Gl\"anzel and Ulrich Schmoch }, + publisher = { Kluwer Academic Publishers }, + year = { 2004 } +} + +@Article{zalanyi03, + author = { Zal\'anyi, L\'aszl\'o and Cs\'ardi, G\'abor and Kiss, Tam\'as + and Lengyel, M\'at\'e and Warner, Rebecca and + Tobochnik, Jan and \'Erdi, P\'eter }, + title = { Properties of a random attachment growing network }, + journal = { Physical Review E }, + volume = { 68 }, + pages = { 066104 }, + year = { 2003 } +} + +@Article{podolny95, + author = { J. M. Podolny and T. E. Stuart }, + title = { A Role-Based Ecology of Technological Change }, + journal = { American Journal of Sociology }, + volume = { 100 }, + pages = { 1224 }, + year = { 1995 } +} + +@Article{wartburg05, + author = { I. von Wartburg and T. Teichert and K. Rost }, + title = { Inventive progress measured by multi-stage patent citation analysis }, + journal = { Research Policy }, + volume = { 34 }, + pages = { 1591 }, + year = { 2005 } +} + +@Article{kossinets06, + author = { Gueorgi Kossinets and Duncan J. Watts }, + title = { Empirical Analysis of an Evolving Social Network }, + journal = { Science }, + volume = { 311 }, + pages = { 88--90 }, + year = { 2006 } +} + +@Article{krapivsky01, + author = { P. L. Krapivsky and S. Redner }, + title = { Organization of Growing Random Networks }, + journal = { Phyisical Review E }, + volume = { 63 }, + pages = { 066123 }, + year = { 2001 } +} + +@Article{boccaletti06, + author = { S. Boccaletti and V. Latora and Y. Moreno and M. Chavez and + D.-U. Hwang }, + title = { Complex networks: Structure and dynamics }, + journal = { Physics Reports }, + volume = { 424 }, + year = { 2006 }, + pages = { 175--308 } +} + +@Article{watts04, + author = { D. J. Watts }, + title = { The ``new'' science of networks }, + journal = { Annual Review of Sociology }, + volume = { 30 }, + pages = { 243--270}, + year = { 2004 } +} + +@Article{caldarelli02, + author = { G. Caldarelli and A. Capocci and P.D.L. Rios and M.A. Mu{\~n}oz }, + title = { Scale-Free Networks from Varying Vertex Intrinsic Fitness }, + journal = { Physical Review Letters }, + volume = { 89 }, + pages = { 258702 }, + year = { 2002 } +} + +@Misc{roth05, + title = { Measuring Generalized Preferential Attachment in + Dynamic Social Networks }, + author = { Camille Roth }, + howpublished = { arxiv:nlin.AO/0507021 }, + year = { 2005 } +} + +@Article{ergun02, + title = { Growing Random Networks with Fitness }, + author = { G. Ergun and G. J. Rodgers }, + journal = { Physica A }, + volume = { 303 }, + year = { 2002 }, + pages = { 261--272 } +} + +@Article{bianconi01, + author = { Bianconi G. and Barab\'asi, A.-L. }, + title = { Competition and multiscaling in evolving networks }, + journal = { Europhysics Letters }, + volume = { 54 }, + pages = { 436--442 }, + year = { 2001 } +} + +@Article{barabasi04, + author = { Albert-L\'aszl\'o Barab\'asi and Zolt\'an N. Oltvai }, + title = { Network Biology: Understanding the Cells's Functional + Organization }, + journal = { Nature Reviews Genetics }, + volume = { 5 }, + pages = { 101--113 }, + year = { 2004 } +} + +@InProceedings{kleinberg99, + author = { Kleinberg, J. M. and Kumar S. R. and Raghavan, P. + and Rajagopalan, S. and Tomkins, A. }, + title = { The Web as a graph: Measurements, models and methods }, + booktitle = { Proceedings of the International Conference on + Combinatorics and Computing, no. 1627 in Lecture + Notes in Computer Science }, + publisher = { Springer }, + year = { 1999 } +} + +@InProceedings{berger04, + title = { Competition-Induced Preferential Attachment }, + author = { N. Berger and C. Borgs and J. T. Chayes and R. M. D'Souza + and R. D. Kleinberg }, + booktitle = { Proceedings of the 31st International Colloquium on + Automata, Languages and Programming }, + pages = { 208--221 }, + year = { 2004 } +} + +@Misc{csardi05, + title = { Modeling innovation by a kinetic description of the patent + citation system }, + author = { G\'abor Cs\'ardi and Katherine J Strandburg and + L\'aszl\'o Zal\'anyi and Jan Tobochnik and P\'eter + \'Erdi }, + howpublished = { physics/0508132 }, + url = { http://arxiv.org/abs/physics/0508132 }, + year = { 2005 } +} + +@Inproceedings{csardi06, + title = { Dynamics of citation networks }, + author = { G\'abor Cs\'ardi }, + booktitle = { Proceedings of the Artificial Conference on Artificial + Neural Networks }, + page = { to appear }, + year = { 2006 } +} + +@Article{belisle92, + author = { Belisle, C. J. P. }, + year = { 1992 }, + title = { Convergence theorems for a class of + simulated annealing algorithms on Rd }, + journal = { Journal of Applied Probability }, + volume = { 29 }, + pages = { 885--895 } +} + +@Article{byrd95, + author = { Byrd, R. H. and Lu, P. and Nocedal, J. and Zhu, C. }, + year = { 1995 }, + title = { A limited memory algorithm for bound constrained + optimization }, + journal = { SIAM Journal of Scientific Computing }, + volume = { 16 }, + pages = { 1190--1208 } +} + +@Article{fletcher64, + author = { Fletcher, R. and Reeves, C. M. }, + year = { 1964 }, + title = { Function minimization by conjugate gradients }, + journal = { Computer Journal}, + volume = { 7 }, + pages = { 148--154 } +} + +@Book{nash90, + author = { Nash, J. C. }, + year = { 1990 }, + title = { Compact Numerical Methods for Computers, + Linear Algebra and Function Minimisation }, + publisher = { Adam Hilger } +} + +@Article{nelder65, + author = { Nelder, J. A. and Mead, R. }, + year = { 1965 }, + title = { A simplex algorithm for function minimization }, + journal = { Computer Journal }, + volume = { 7 }, + pages = { 308--313 } +} + +@Book{read98, + author = { Read, Ronald C. and Wilson, Robin J. }, + title = { An Atlas of Graphs }, + publisher = { Oxford University Press }, + year = { 1998 } +} + + +@Article{newman06, + author = { M. E. J. Newman }, + title = { Modularity and community structure in networks }, + journal = {Proc. Natl. Acad. Sci. USA }, + volume = { { in press } }, + year = { 2006 } +} + +@Article{dijkstra59, + author = { E. W. Dijkstra }, + title = { A note on two problems in connexion with graphs }, + journal = { Numerische Mathematik }, + volume = { 1 }, + year = { 1959 }, + pages = { 269--271 } +} + +@Article{erdos59, + author = { {Erd\H{o}s}, P. and R\'enyi, A. }, + title = { On random graphs }, + journal = { Publicationes Mathematicae }, + volume = { 6 }, + pages = { 290--297 }, + year = { 195 } +} + +@Article{newman01, + title = { Random graphs with arbitrary degree distributions and + their applications }, + author = { M. E. J. Newman and S. H. Strogatz + and D. J. Watts }, + journal = { Phys. Rev. E }, + volume = { 64 }, + pages = { 026118 }, + year = { 2001 } +} + +@Article{callaway01, + title = { Are randomly grown graphs really random? }, + author = { D. S. Callaway and J. E. Hopcroft and J. M. Kleinberg, + M. E. J. Newman and S. H. Strogatz }, + journal = { Phys. Rev. E }, + volume = { 64 }, + pages = { 041902 }, + year = { 2001 } +} + +@Article{freeman79, + author = { Freeman, L.C. }, + year = { 1979 }, + title = { Centrality in Social Networks {I}: + Conceptual Clarification }, + journal = { Social Networks }, + volume = { 1 }, + pages = { 215--239 } +} + +@Inproceedings{wernicke05, + author = { S. Wernicke }, + title = {A faster algorithm for detecting network motifs }, + booktitle = { Proceedings of the 5th Workshop on Algorithms in + Bioinformatics (WABI '05) }, + publisher = { Springer-Verlag }, + year = { 2005 } +} + +@Article {wernicke06, + author = { S. Wernicke and F. Rasche }, + title = { {FANMOD}: a tool for fast network motif detection }, + journal = { Bioinformatics }, + volume = { 22 }, + number = { 9 }, + pages = { 1152--1153 }, + year = { 2006 } +} + +@book{nooy05, + author = { W. de Nooy and A. Mrvar and V. Batagelj }, + title = { Exploratory Social Network Analysis with Pajek }, + publisher = { Cambridge University Press }, + year = { 2005 } +} + +@Inproceedings{brandes01, + author = { U. Brandes and M. Eiglsperger and I. Herman and + M. Himsolt and M.S. Marshall }, + title = { GraphML Progress Report: Structural Layer Proposal }, + booktitle = { Proc. 9th Intl. Symp. Graph Drawing (GD '01) }, + pages = { 501--512 } +} + +@Article{fruchterman91, + author = { T. M. J. Fruchterman and E. M. Reingold }, + title = { Graph drawing by force-directed placement }, + journal = { Software -- Practice and Experience }, + volume = { 21 }, + pages = { 1129--1164 }, + year = { 1991 } +} + +@Article{kamada89, + author = { Kamada, T. and Kawai, S.}, + year = { 1989 }, + title = { An Algorithm for Drawing General Undirected Graphs }, + journal = { Information Processing Letters }, + volume = { 31 }, + number = { 1 }, + pages = { 7--15 } +} + +@Article{adai04, + author = { Adai A T and Date S V and Wieland S and Marcotte E M }, + title = { {LGL:} creating a map of protein function with an + algorithm for visualizing very large biological + networks }, + journal = { J Mol Biol }, + year = { 2004 }, + volume = { 340 }, + pages = { 179--90 } +} + +@Article{reingold81, + author = { E. Reingold and J. Tilford }, + title = { Tidier drawing of trees }, + journal = { {IEEE} Transactions on Software Engineering }, + volume = { 7 }, + pages = { 223--228 }, + year = { 1981 } +} + +@Article{zachary77, + author = { W. W. Zachary }, + title = { An information flow model for conflict + and fission in small groups }, + journal = { Journal of Anthropological Research }, + volume = { 33 }, + pages = { 452-­473 }, + year = { 1977 } +} diff --git a/doc/pmt.xml b/doc/pmt.xml new file mode 100644 index 0000000..1a718fc --- /dev/null +++ b/doc/pmt.xml @@ -0,0 +1,120 @@ + + +]> + +
+About template types + +Some of the container types listed in this section are defined for +many base types. This is similar to templates in C++ and generics in +Ada, but it is implemented via preprocessor macros since the C language +cannot handle it. Here is the list of template types and the all base +types they currently support: + + +vector + Vector is currently defined for igraph_real_t, + long int (long), char (char), + igraph_bool_t (bool). The default is + igraph_real_t. + + +matrix + Matrix is currently defined for igraph_real_t, + long int (long), char (char), + igraph_bool_t (bool). The default is + igraph_real_t. + + +array3 + Array3 is currently defined for igraph_real_t, + long int (long), char (char), + igraph_bool_t (bool). The default is + igraph_real_t. + + +stack + Stack is currently defined for igraph_real_t, + long int (long), char (char), + igraph_bool_t (bool). The default is + igraph_real_t. + + +double-ended queue + Dqueue is currently defined for igraph_real_t, + long int (long), char (char), + igraph_bool_t (bool). The default is + igraph_real_t. + + +heap + Heap is currently defined for igraph_real_t, + long int (long), char (char). + In addition both maximum and minimum heaps are available. + The default is the igraph_real_t maximum heap. + + + + + +The name of the base element (in parentheses) is added to the function +names, except for the default type. + + + +Some examples: + + + + igraph_vector_t is a vector of + igraph_real_t elements. Its functions are + igraph_vector_init, + igraph_vector_destroy, + igraph_vector_sort, etc. + + + + igraph_vector_bool_t is a vector of + igraph_bool_t elements, initialize it with + igraph_vector_bool_init, destroy it with + igraph_vector_bool_destroy, etc. + + + + igraph_heap_t is a maximum heap with + igraph_real_t elements. The corresponding functions are + igraph_heap_init, + igraph_heap_pop, etc. + + + + igraph_heap_min_t is a minimum heap with + igraph_real_t elements. The corresponding functions are + called igraph_heap_min_init, + igraph_heap_min_pop, etc. + + + + igraph_heap_long_t is a maximum heap with long + int elements. Its function have the + igraph_heap_long_ prefix. + + + + igraph_heap_min_long_t is a minimum heap containing + long int elements. Its functions have the + igraph_heap_min_long_ prefix. + + + + + + +Note that the VECTOR and the MATRIX macros can be used on all +vector and matrix types. + + +
diff --git a/doc/presentations/iccs06/Makefile b/doc/presentations/iccs06/Makefile new file mode 100644 index 0000000..4e65a9c --- /dev/null +++ b/doc/presentations/iccs06/Makefile @@ -0,0 +1,31 @@ +SOURCE = iccs06 + +all: $(SOURCE).pdf + +FIGTEX = ~/bin/figtex + +FIG = $(wildcard *.fig images/*.fig) +EPS = $(patsubst %.fig, %.eps, $(FIG)) +EPST = $(patsubst %.fig, %.eps_t, $(FIG)) +ALLEPS = $(EPS) +INCLUDES = $(wildcard *.R) + +$(SOURCE).pdf: $(SOURCE).tex $(ALLEPS) $(EPST) $(INCLUDES) + latex $(SOURCE) && latex $(SOURCE) && \ + dvips -o $(SOURCE).ps -t landscape $(SOURCE).dvi && \ + gs -q -dSAFER -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \ + -sOutputFile=$(SOURCE).pdf -dCompatibilityLevel=1.3 \ + -dAutoRotatePages=/None \ + -dPDFSETTINGS=/screen \ + $(SOURCE).ps + +# Figures + +%.eps : %.fig + $(FIGTEX) $< + +clean: + rm -f $(SOURCE){.aux,.bbl,.blg,.dvi,.log,.out,.pdf,.ps,.toc}\ + $(EPS) $(EPST) + +.PHONY: clean diff --git a/doc/presentations/iccs06/arch.fig b/doc/presentations/iccs06/arch.fig new file mode 100644 index 0000000..ca27246 --- /dev/null +++ b/doc/presentations/iccs06/arch.fig @@ -0,0 +1,25 @@ +#FIG 3.2 Produced by xfig version 3.2.5-alpha5 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +0 32 #eaeaea +2 4 0 1 0 10 50 -1 20 0.000 0 0 7 0 0 5 + 9600 4755 8340 4755 8340 3300 9600 3300 9600 4755 +2 4 0 1 0 11 50 -1 20 0.000 0 0 7 0 0 5 + 5310 4755 3015 4755 3015 3300 5310 3300 5310 4755 +2 4 0 1 0 3 50 -1 20 0.000 0 0 7 0 0 5 + 7950 4755 5655 4755 5655 3300 7950 3300 7950 4755 +2 4 0 1 0 29 50 -1 20 0.000 0 0 7 0 0 5 + 9630 6630 3015 6630 3015 5145 9630 5145 9630 6630 +2 4 0 1 0 32 50 -1 20 0.000 0 0 7 0 0 5 + 9630 8550 3015 8550 3015 7065 9630 7065 9630 8550 +4 1 0 50 -1 18 28 0.0000 4 60 405 8955 4125 ...\001 +4 1 0 50 -1 18 28 0.0000 4 465 1590 6765 4125 Python\001 +4 1 0 50 -1 18 28 0.0000 4 465 6075 6345 6015 Graph operations (C library)\001 +4 1 0 50 -1 18 28 0.0000 4 465 7305 6345 7995 Basic graph operations (C library)\001 +4 1 24 50 -1 18 28 0.0000 4 360 1500 4170 4125 GNU R\001 diff --git a/doc/presentations/iccs06/demo1.R b/doc/presentations/iccs06/demo1.R new file mode 100644 index 0000000..0911ebd --- /dev/null +++ b/doc/presentations/iccs06/demo1.R @@ -0,0 +1,18 @@ +library(igraph) +g <- \emph{read.graph}("http://localhost/~csardi/karate.net", format="pajek") + +community.newman <- function(g) \{ + deg <- \emph{degree}(g) ; ec <- \emph{ecount}(g) + B <- \emph{get.adjacency}(g) - outer(deg, deg, function(x,y) x*y / 2 / ec) + diag(B) <- 0 + Re(eigen(B)$vectors[,1]) +\} +mem <- community.newman(g) +\emph{V(g)$color} <- ifelse(mem < 0, "grey", "green") + +scale <- function(v, a, b) \{ v <- v-min(v) ; v <- v/max(v) ; v <- v * (b-a) ; v+a \} +\emph{V(g)$size} <- scale(abs(mem), 15, 25) +\emph{E(g)$color} <- "grey" +\emph{E(g)[ V(g)[ color=="grey" ] %--% V(g)[ color=="green" ] ]$color} <- "red" +\emph{tkplot}(g, layout=layout.kamada.kawai, vertex.color="a:color", + vertex.size="a:size", edge.color="a:color") diff --git a/doc/presentations/iccs06/demo2.R b/doc/presentations/iccs06/demo2.R new file mode 100644 index 0000000..baa133f --- /dev/null +++ b/doc/presentations/iccs06/demo2.R @@ -0,0 +1,18 @@ +library(igraph) + +exps <- seq(0.5, 1.5, length=16) + +par(mar=c(2,2,2,2)) +layout( matrix(1:16, nr=4, byrow=TRUE)) +layout.show(16) + +maxdeg <- numeric() +for (ex in exps) \{ + g <- \emph{barabasi.game}(100000, power=ex) + maxdeg <- c(maxdeg, max(\emph{degree}(g, mode="in"))) + plot(\emph{degree.distribution}(g, mode="in"), log="xy", xlab=NA, ylab=NA) +\} + +x11() +plot(exps, maxdeg, type="b") + diff --git a/doc/presentations/iccs06/demo3.R b/doc/presentations/iccs06/demo3.R new file mode 100644 index 0000000..23b785c --- /dev/null +++ b/doc/presentations/iccs06/demo3.R @@ -0,0 +1,18 @@ +library(igraph) +g <- \emph{erdos.renyi.game}(5000, 0.8/5000) + +cl <- \emph{clusters}(g) +large <- which(cl$csize > 3)-1 +g2 <- \emph{subgraph}(g, which(cl$membership %in% large)-1) + +graphs <- \emph{decompose.graph}(g2) +layouts <- lapply(graphs, \emph{layout.kamada.kawai}) +coords <- \emph{layout.merge}(graphs, layouts) +g3 <- \emph{graph.disjoint.union}(graphs) + +cl3 <- \emph{clusters}(g3) +cl.no <- length(cl3$csize) +colorbar <- heat.colors(cl.no) +\emph{V(g3)$color} <- colorbar[cl3$membership+1] + +\emph{plot}(g3, layout=coords, vertex.size=1, labels=NA, vertex.color="a:color") diff --git a/doc/presentations/iccs06/iccs06.tex b/doc/presentations/iccs06/iccs06.tex new file mode 100644 index 0000000..58cfb6b --- /dev/null +++ b/doc/presentations/iccs06/iccs06.tex @@ -0,0 +1,182 @@ +\documentclass[landscape]{foils} + +\usepackage{ae} +\usepackage{hyperref} +\usepackage{thumbpdf} +\usepackage{graphicx} +\usepackage{color} +\usepackage[left=0cm,right=1cm,top=2cm,bottom=2cm]{geometry} +\usepackage[display]{texpower} +\usepackage{psfrag} +\usepackage{ragged2e} +\usepackage{amstext} +\usepackage{xspace} +\usepackage{fancyvrb} + +\newcommand{\figfigure}[2]{% + \begin{psfrags}% + \input #2.eps_t% + \includegraphics[width=#1]{#2.eps}% + \end{psfrags}% +} + +\newcommand{\stitle}[1]{{\color{yellow}\centering\Large #1\par\vspace*{10pt}\hrule}} + +\setlength{\columnsep}{0.5cm} +\setlength{\columnseprule}{0.4pt} + +\renewcommand{\emph}[1]{\textcolor{yellow}{\bf #1}} + +\newcommand{\igraph}{\texttt{\emph{igraph}}\xspace} + +\begin{document} + +\RaggedRight +\color{white} +\pagecolor{black} +\fvset{fontsize=\small} +\fvset{commandchars=\\\{\}} +\definecolor{grey}{gray}{0.75} +\fvset{frame=single, numbers=left, rulecolor=\color{grey}} + +\MyLogo{\color{grey}The \igraph library for complex network research -- NECSI ICCS 2006} + +\thispagestyle{empty} +\vspace*{1cm} +{\centering +\hrule +\Large +\vspace*{1cm} +{\bf The \igraph library for complex network research} +\vspace*{1cm} +\par +\hrule +\par +\vspace*{2cm} +\normalsize G\'abor Cs\'ardi\\ +\small \verb+csardi@rmki.kfki.hu+ +\par +\vspace*{1cm} +\normalsize Tam\'as Nepusz\\ +\small \verb+ntamas@rmki.kfki.hu+ +\par +\vspace*{1.5cm} +Center for Complex Systems Studies, Kalamazoo College, Kalamazoo, MI, and\\ +Department of Biophysics, +KFKI Research Institute for Nuclear and Particle Physics of the +Hungarian Academy of Sciences\\ +} + +\newpage +\stitle{Why???}\pause + +\begin{center} +Because every existing software package lacked something. +\end{center}\pause + +\vfill +\stitle{Being open} + +\begin{center} +The most important feature. +\end{center} +\vfill + +\newpage +\stitle{Design goals} + +\begin{itemize} +\item Handling \emph{large} data sets time- and + space-efficiently. Millions of vertices and/or edges. All basic + operations are linear in time and space. \pause +\item Open: (1) open source (2) \emph{extendable} and (3) \emph{embeddable}. \pause +\item \emph{Interactive} and \emph{non-interactive}. \pause +\item Supporting \emph{rapid development}. \pause +\end{itemize} + +\stitle{Additional Features (side effects)} + +\begin{itemize} +\item \emph{Portable} (both the C layer and the and Python + layers). \pause +\item Well \emph{documented}. The time complexity of every operation is + defined. See homepage for documentation. +\end{itemize} + +\newpage +\stitle{The \igraph architecture} +\begin{center} +\color{black} +\figfigure{.7\textwidth}{arch} +\end{center} + +\newpage +\stitle{Functionality} + +\begin{itemize} +\item Handles directed and undirected graphs with possibly multiple + edges and self-loops. No hypergraphs. \pause +\item Graph generation: various regular and random graphs. Efficient + algorithms for large random graphs. \pause +\item Random graphs with a given degree sequence, rewiring of graphs.\pause +\item Path length properties, centrality measures. Page-rank + algorithm. \pause +\item Graph components, weakly or strongly connected, minimum spanning + tree. \pause +\item Vertex and edge sets/sequences, high level interfaces support + graph, vertex and edge attributes. An attribute can be an arbitrary + object. Vertex and edge selection based on attributes. +\end{itemize} + +\newpage +\stitle{Functionality (contd.)} +\begin{itemize} +\item File formats. Some simple file formats and also Pajek (import + only) and GraphML. (Basic support right now.) \pause +\item Graph layouts, regular and force-based layouts in 2D and 3D. \pause +\item High level interfaces support graph visualization in 2D. + (interactive and non-interactive, many file formats to export: EPS, + PDF, SVG, JPG, PNG, FIG, etc.) or 3D + (with R). \pause +\item Graph operators: graph intersection, union, composition. \pause +\item Graph motifs: an implementation of the fast RAND-ESU algorithm. +\end{itemize} + +\newpage +\stitle{Demo 1: Nonlinear Preferential Attachment} +\VerbatimInput{demo2.R} + +\newpage +\stitle{Demo 2: Community Structure Detection Algorithm} +\VerbatimInput{demo1.R} + +\newpage +\stitle{Demo 3: Clusters in a Random Graph} +\VerbatimInput{demo3.R} + +\newpage +\stitle{Where to get it} + +\emph{Home page:}\\ +\url{http://cneurocvs.rmki.kfki.hu/igraph} +\vfill + +\emph{Mailing lists:}\\ +\url{http://lists.nongnu.org/mailman/listinfo/igraph-help}\\ +\url{http://lists.nongnu.org/mailman/listinfo/igraph-announce}\\ +\url{http://www.r-project.org/mail.html} +\vfill + +\emph{On SourceForge:}\\ +\url{http://www.sourceforge.net/projects/igraph} +\vfill + +\emph{On Savannah:}\\ +\url{http://savannah.nongnu.org/projects/igraph} +\vfill + +\emph{Source code:}\\ +\url{http://arch.sv.nongnu.org/archives/igraph} +\vfill + +\end{document} diff --git a/doc/presentations/images/CCSS.svg.gz b/doc/presentations/images/CCSS.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..1acc4aef9f2542c8e4e9edf85980b9b17916591b GIT binary patch literal 93900 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 z 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
NpQ7<7^uE}Tq}H;qWVYu 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�W01PTUXvw zKaXD;M-~@_@#X{umIbC7{#muUA)=Ud&zo4?m_o@zUy9LV2y)48qm>{ng;vL0{l->^z@;3umU-nSL5I;!62 ze_YR?`~q@%Wj>x4mE;V)MJdPnL)I$t>=MsgsJ1dCy~?-j34|7i<~7D}mJ6uAYcuVF zAyEdd&g4&1&Cxi6sJ%giJ|M+qFpy)Rx`T~oUC6L4b(3^xsyTDxa(mmJG?KdSSHhYC z#S!AToXg1D8njTz$jdQ4=O&6o-(68KS!M&vDK{6mM{7Ih!i=fQBNn79=se-Bb;W~? z>DHl!Xv=mgbL8b|hD&eTT$}&&GhNol`fK5ugfDCV^4e=pBBG5&37eO7#Z45)xGR9k z$_`KfQbavHtWRsTArM=vATR%9Jz8N)fe2K|O`bI+g!80>398|GgP}bM1)F$YfNm&l zpQzpJjMIte*++>j^V?nf#~yUZ*XbYSVZPa{bae{(Y;d=C;dqtS~*V?JfI*xYv_% z6M~#oL%*+sKL%>By+&NErsx^h%A|9s=`d$*JTP5cSzft{n%Ttz z;;W#PAgH9~BOiT=jvG72=WZT}zFvnZ+=6u-rf(}?arWZ*+~Ya?8m`o)uDiSe=oY)I z%T1H2g`M6}ZJc!9>fA=Il9D30i>U*C;2mKbM_q3SJhQe};9D*3CLjRCS(k#xX<_>@ zgnxBiil#)A7gIMC9^({58m9v`E1k+Vjeg*MN!V(u5@u}jX&+h3J8Slf3y`V(O+&K} z&HW*zVeGpUEpUsGug+h7JAYHWA9RZ0j3mi*j-0hm=R!an(ZuqHqBEFy1$TMP3TNnO zCKBT^ivD3RQR454o*D(9VHaCv zJWfwoAxi(=)7zOU;8@<)anc9>h848o5zoA>Re-N{X26;NE9xMRaa}7Xi4@9+&u1XR zI_rmV?7GC;`qKz9u#91?I8@RQ+Gv+1?HQV&@Du8pZ#Z8lA7F*tFFLh>!pU5LC)kk* z^Sz!HS={COSd^{UM*nn`Qw=TtSsSgOJ_jj7%sXYro=w;*RU|0=_wuIMFkxLs2s&sC z)^=#5ln2)QVN<(iH;(MKzV=%LKFWU4+gdlh#@pH?oKO{hQ1?B9vJRgxzKgDVA7hL* zEZW~e=rp*>b|SVRe~Xm{m@zcc8Gfqo!tJbZ^~+Dw3%8P*Y=rQfz`zuG*{v$`cqNodmbYbG*=21moqb%}KBnLbDDh}N=AiSzpVrSe z_&%gfV@Bii>{=)x!ajU$Z;$dFa|KpoQHff)4P%(xodLO02D(t2WZcABJE?(EJ zFkl{=dPX9$4`5x<7{(n*BYcNHErE;pCe2KEg}%dS=D*8y1k-Nw9qkpE05}};JinT| z^yt^ZTn@N?wJ+QG1=Ozgb@6^?wcKMCfd}2fZqU|+5>+T<@!+o5m!-spq`%i!Q>!1JhfB*WM|25`c{~@;hU;o4Z^ZVcZ^&fwe&VT*G@BjGgKm7KefAfF- z_rL!2Y5Twa^!q>l=AVB5*I)m9tokp1`2G5SzbVJ|yWjrhAAkGjKmYb${S4dsr+@zK zKm7i$|N5JFO{2g2+yA!z!(V^>m%pC3_?LhB!*Bkwu3hi(&wu{)FTeizfBbsA;d#G5 z{qe8=;V-}ce}DZ=8UOq*|H=QAe_B8I=YRU`A8h%5e*OJ_`^Uc?6F>1Ee*1^t|F=K> z<{y6jBX3~KAOGPW|MchePQUxzA3R*Y`CkY1=HRirp85>j^OZbmaXrHS^tj#k)wBNB z-~Q9@|M0KB`QQHh```ZI+yDND-~Pk@eZBL){Qch`{mDm$r~Uxl!-xILU;p_1|NKJ(`t85|>Cb=nx4Oyy z7W1b>%V;aFaP?NzyA79fBE)b{`%j4_xoS}ay`l4G9=71 XO}D@M+rRve_5c1qPsl#NHID-TinWTO literal 0 HcmV?d00001 diff --git a/doc/presentations/images/RMKI-BME.svg.gz b/doc/presentations/images/RMKI-BME.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..5b43c13e04236587a5fbc4070022ac54dda35f9b GIT binary patch literal 30406 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/images/cblocks.pdf b/doc/presentations/images/cblocks.pdf new file mode 100644 index 0000000000000000000000000000000000000000..423abf77089dd29d7cd0a04ac2220ae21700b112 GIT binary patch literal 149219 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 z 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/images/clustering.png b/doc/presentations/images/clustering.png new file mode 100644 index 0000000000000000000000000000000000000000..1d929b0d59d36c57fd96d7a0849e2cc0ee541ea9 GIT binary patch literal 80490 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%0DjgF(Er1B1{Ey;sY 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 Z`@I5Ys5TqsMF7B;l$g9|rLaNZ{{VbK6K((i literal 0 HcmV?d00001 diff --git a/doc/presentations/images/diameter2.png b/doc/presentations/images/diameter2.png new file mode 100644 index 0000000000000000000000000000000000000000..5ff6ee1962dcb30078b9bf1a17027cec2c87bfda GIT binary patch literal 51772 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/images/groups.pdf b/doc/presentations/images/groups.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ecc236370080181a15be7e14f7c1de211c7439dd GIT binary patch literal 156108 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 z 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

(Bh&4!4d{iR)VGw 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/images/igraph.svg b/doc/presentations/images/igraph.svg new file mode 100644 index 0000000..84181a9 --- /dev/null +++ b/doc/presentations/images/igraph.svg @@ -0,0 +1,2783 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + X + + + + + + + + + + + + + + + + + + ABCDEFGHIJ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + X + + + + + + + + + + + + + + + + + + + ABCDEFGHIJ + + + + + + + + + + + + + + + + + + diff --git a/doc/presentations/images/karate3d.png b/doc/presentations/images/karate3d.png new file mode 100644 index 0000000000000000000000000000000000000000..f77badd12c272ae7c66a5fbba754701ab85bd434 GIT binary patch literal 126895 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%0DA3ij>25I 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 avw6tS?vm~XFA9ndUE7qg>Qc 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 HHuC=gU_!@m literal 0 HcmV?d00001 diff --git a/doc/presentations/images/small3.pdf b/doc/presentations/images/small3.pdf new file mode 100644 index 0000000000000000000000000000000000000000..404bfcc7c706266dfc6a437e3c237ed0656ada07 GIT binary patch literal 26223 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/images/small4.pdf b/doc/presentations/images/small4.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6c637d61f078a1ae7c78f1872378f3826dce35b4 GIT binary patch literal 27030 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%0A=)-tb*qeF(a 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ⅇ_()ND@&$Za_rfFCvHkubsSR`<3+p^lCnO{`P%;-W=6$DD6<%0Da3tX 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 b`lKG(heLF6`tJ1oU*gd5M=NXo7?1uJjDI4_ literal 0 HcmV?d00001 diff --git a/doc/presentations/infovis07/Makefile b/doc/presentations/infovis07/Makefile new file mode 100644 index 0000000..c895709 --- /dev/null +++ b/doc/presentations/infovis07/Makefile @@ -0,0 +1,54 @@ + +all: ps + +ps: infovis07-igraph.ps + +pdf: infovis07-igraph.pdf + +infovis07-igraph.ps: infovis07-igraph.dvi + dvips -t a3 infovis07-igraph.dvi -o infovis07-igraph.ps + +infovis07-igraph.pdf: infovis07-igraph.ps + ps2pdf infovis07-igraph.ps + +IMAGES=karate3d.eps screenshots.eps arch.eps RMKI-BME.eps CCSS.eps + +%.png:: ../images/%.png + cp $< $@ + +%.svg.gz:: ../images/%.svg.gz + cp $< $@ + +karate3d.eps: karate3d.png + convert karate3d.png karate3d.eps + +screenshots.eps: screenshots.svg.gz degreedist.png clustering.png diameter.png \ + diameter2.png tkplot.png + gzip -dc screenshots.svg.gz >screenshots.svg && \ + inkscape -E screenshots.eps -y 0.0 screenshots.svg + +arch.eps: arch.svg.gz + gzip -dc arch.svg.gz >arch.svg && \ + inkscape -E arch.eps -y 0.0 arch.svg + +RMKI-BME.eps: RMKI-BME.svg.gz + gzip -dc RMKI-BME.svg.gz >RMKI-BME.svg && \ + inkscape -E RMKI-BME.eps -y 0.0 RMKI-BME.svg + +CCSS.eps: CCSS.svg.gz + gzip -dc CCSS.svg.gz >CCSS.svg && \ + inkscape -E CCSS.eps -y 0.0 CCSS.svg + +infovis07-igraph.dvi: infovis07-igraph.tex $(IMAGES) + latex infovis07-igraph.tex + +clean: + rm -f karate3d.eps screenshots.eps arch.eps arch.eps_t \ + RMKI-BME.eps CCSS.eps infovis07-igraph.pdf infovis07-igraph.ps \ + infovis07-igraph.aux infovis07-igraph.dvi infovis07-igraph.log \ + screenshots.svg arch.svg{,.gz} RMKI-BME.svg{,.gz} CCSS.svg{,.gz} \ + clustering.png degreedist.png diameter.png diameter2.png \ + karate3d.png python3d.png tkplot.png + + +.PHONY: all ps pdf clean diff --git a/doc/presentations/infovis07/RMKI.svg.gz b/doc/presentations/infovis07/RMKI.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..0fd78d90df8782b9988575fbd8b0b0fc0040e3e2 GIT binary patch literal 3813 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 bEn%T0{16s7wU`+H+z= zr74AaaKc?U_QcKj;4?zbbwsSMV;J!z7ms&vd|pIz)S*(fa?p4%O)H}_u251^d!frL zt=x2_4QvYaUF^)CJ3P%d6zHu9n|El^(~G>nUB!PUM7%ZJgX00Gq%zaK4&bZ_&veby&=T=rr|HF~>08SmVL<~K#{n5LEeyIVDro}jc1&AZ86 zecR+jL`&>qU*7Du9hGXw&HAoH`K~tN%8*gHH z^qtD2{XzkOLR@8mkX-{-+6L{b=MSl(v?j@KF?Tc<++lfr91a+JjwK)Wg=~0@t0_4uD05cw#LYl z&TpSqjWt()WhPFKFQ{Y*5i%EL)|+Fgm(;0`7CM(uY#24^?Nc)$`e{(?tQWt=GATfW z=Tl zvrSAxZplK7w@r|;ugpIq=cW9}3fs`v3nlQDTB6p=kK>264FhAQO#@FqyW`}1xY-NK zk61gD+qP{^n9NkZpN=BEK;QUy#P~>v<&Jxz#o{PJFx%fgs4Cn6OGwObnt2&3X{66GY^^Ci3#Agh zezIYqNZ5MfIFq-umQ^hUW8LlgOB7s^nQ~CUzc?J-tuX2{dUo5iwOw|=$Pxh`#HaOZ zZa`Ji^|&knUtOSuINeyy@C*SRW(v6>pP20NTMA)Z3CwhIp63eD!*j$&nAzlp8tvki zk^xkuAsD2;QP44CAiBi3>{LbpZGM}EJbBM)4e1(9oqD-(D>>pjuSfZZ$9$~QBa7Xm z=#*r&#PeHMUSfRtHw}ARpQ3)@loQ&!o)x?DD>=_ zvW832Y-03M5y5plHd@rRt1UmqX=QPj2jx3Lu4|9Vzmh@95=}Rez&Uaw|6E%>Q(LNY zK~qZVPj&V7y4S?0Sz^plZ9>e5fyI_{nx~A>mD+LJ7xJ^rwha*ti*dQ2_1lZ6M+Z-| zBpBwnZj94G;F8}Wsa+t4=_1*n-MqDmG6_NUEE|Fn*s)Huqy2Ef`Vl<;z_8by%)N6R zrB^EV4@${n6aSJkNPjU=7=eI*PVefhL;8>4@hK%KpzLnwCoteig!bcolYR%wBw#fR zp8cly2O?Q;7b-wnf9#Q1@4|iuw&7b1c0VORMJu+KFymmztD7>13=?8a?qmzS1pmoX%pVJ^U&Ei2GGUvsQ;~XvSmqVxUFQCT%5|B# z!kh2<0ZfkPB*81~KNZ|3iHpgSO*Qxz&B|?UCQJz>+TJnq+qBPi43LC3H;?SX1E!BNQiZB#DVA7Pt1&Zb!oyqT4pAh!_BcHLkN{ zS57&8*fKonrSpY&vYzYe@gc7UTU;4KE?QqS&Bq=5X)arT7f}O ztvsO5uP}FZg zHdt#16~(TlIz*0SZZ4+!UGVUWV1NFa>L0(_bqFp09>M~QhEA(mD@BloT1zt=gJU1R zboo}X!z^Zy#kN0%^oAgxgmP}g5JWE~Y7K!dYhW?kG zpH}D&JyaB|1uiAV$dvaMBdsGp{P|O@&~u49TqEqiaR z1?~0SgK6s6NYL2e0=Co+De*n;*-n57de4%kzy*! z`hE*llcjQEJ^I-}qTWZV}vogJefY>JYaw3evdAUQ6 z^;RnO5A}S_43`wwJJ6?quUvKXd9&ZVkqun4)HqH&|z{lETFBuJ|4()P-rF1 zz!m@N`S~1%PNJB389Kl9EA;p;duDJbz`)~GKn9~Ra(u8cw=T)Qhs#Eh4$0sX!oMSvDWSc#pTB@E<$mot8rJBj!WL}g19{lHEfx+yBp$DME{{|u0 zh}We(1Yy~uf(xr9TvEe@6XgU1lz}wWEu1}{>rscK8Tu~_Zz?2KT4m8Y&$~jj4-9Wo zLfilCE#E$GHIIrr#jDB8m#I5wJSjMsw7 zhTwi7={h>cB$TyJ=C|s;K81cq2$2x~TRr!i`HonRYqft3eT~=1SF5r??Q8?Dz0I~S z9!ZbX3H5NIXhSIE@>A93IC4&}fCH^kq+B>`2#NZ8S;Lm0d4HkZdc=7IdKarC)jTzx z8s+JF82!!`L+&%v!^db$Kcw4LI7U;v$ncg|yU8KE09WusO~?%qE$X6$BKGNY~%-`&szy5OuXR~9+|%HY=P@<9nQj)&-&?^pdV{{p9* zt9C-hCu4yDER@?1j>`l5vL8sHzoZ641pomHbKmY@;R9@CzMUvx^grYP`@R1)j{QR< z!^+rCf75dab2G#*tK8H4wMG1(palMBYihj9UO&VK1?wh^v+=j&09%sTsZzk=3+@{W zsy|8rLEo!re+=K{F&((6omj(GE))mod)wQr^9b0jGld$?fHMsWUlhI#bt?tcxcX_|a;XzOvws(C$%~%->=Z@)$gWUa->%<(vzeqW@-_H*|iaulIj_3mp$35qO$_{HXl?LjX-S;g>s?_A-7 z^7E`OjjSpf`Nu)GJYrDjvy1FMlEE2zJxRn`swFp+A0N)!-%TNa5Q=clXkxyecTLl6 zbD%VtHXR7ma_Bu8Y=wFsF=XbLb$s!Y0;KToi^Z2Zw+CDCg9nhnCb0gMDCmg; zT^LuDr5eDxR-aNwhkpOSqw`H5lX86#bj?8m0y|v$EO6DuB#??}4(bH6u9nowTIu#` zTR?6H--}-8+d+g9n*T|7dJul&P)j|%e;k0V5xNjhW|yuuq{uys_Ky?-F(Xz=WrZ`$ zw3q1*o}1pviXGT-b4R*0*T*q}TGU2+m&RA4+p+<4A(+wD97*%4^2~r1*s) zQSG8L?(t-bb9^Rhhrhyr#WooSRbQcMX~gM5alG5%p*9@v5ji#^lxp9cdar$&UPmiDY#}>>TFjE+De#&z{qjCfl3R5weSIBUi-`zY}usKl1fbyG-QMXOfk8*uYisI#q? zuue`W1XJCLc~A>~HY{Wbs?8dW~sQjRKHAot+$AHs8&R*v7c>SC)sKRt|Q4cL zO}yCfu#4jC!GFkz;hFa+DhlvA6t~q`B1H+t6v#}AsWDVKr<(*zB?q@^MHV;y_-vGs z1VcNx%YIPmZSjetiq*uPZZ-rT7U@043YT*By)=1#@r1R+)p zRXJMh*fz5j(4)O3*SoMa;=l%ipL#9dJ|t~IlgPqeTgNjJBVYpqvTw$2#7v>=SPi8J zh^$Daid$6Oa$3kLe{11Us;heckShZaKNeRX?!_z4=F^U(Az07ccR)f0Ndo!Q+nm{}aRda103U0=5~ zk@8gp0#;*Q4S(X4u+om_DKm=MiB6U491t&iuF8{cSK_YAE1(JH5@~yLG>r7P8U5C~ zjn)bR265h4VKpC~Fnoc(!9U}tr5+Ps7*QX69AXRK7y1iHip1-J-v4cRr=*6)=0@rML3#jEMC zW`M(ibHVSSbq=xsB7_D|tM%t{#kV88(Okcxs&5t@fYCqrc}8(A)$LiHMnD>xiGm>? z^Xr2#&@bW}%n5z;48vs1~g;JDbYVYxFGF(Fbvw$}J#S7~m20dZ-{ zX5#ACw$_rmsIm93qWk;X zimB#iOywV}^`fC_rU+L_W|M0`{0Qx-JG* zXc{7?&QR{}J9X&_V!3pBe|nSWaJUUi7s^!abpIDnR)NqO={88xaR8|g&b&RDXOlGU`zaOGe^koi^P@&VYx1J2V1g(q zPtDoV?U8^Gp{H93*QmVPTQ>&RTmcxg23A7vl34HB)f`!GUFld|5LdtW)gV#+>xT?_ zxtZbu$#RXC<$OVBdzIQTwQEW@xmGsU9N3@I0|tCi4h!ang0+qAHdn5qX2mt$hfKQG z)S1;I?UjzEXMUrQpLv`=j@K=On7M|eM7tT_^rf6cyZIrwD|>9dOv22CPO`eXrEcV? z{^{>JsByPp&Cfg;8R8mAQubCax^mEf!11AlLOZ)hP^!_G=XXRyMr!F-Z(2?<-61(L zZn6dENe1=%h{w9D6F==T7M!0mJkqu=9i%{%OkaTh%mBpa$_zIJZ<1y#FnZeL>AJ=N zS+f;?FUv_+`4MGXFr`>`rr6`LArT+Ml{;v4nnJD4N?sU&VI9FS(t+PA~gz|C{8U7WWLu&L+p0EGZr{2lk8#caW5aI@;RydiE6wpWJ5>mZL3l-&Vy)f_-xfLs5PvarF}s;b7)>s4uStoPyiy>uo7ZjFLR0W|>*<{d+GM1P3DpS$@!Jbz&ej zW@8T~(=dyl>TTrqo5jj{PLwGXK#s5Dvl#7^Q%D+1&7^ZG=l%C5eQb(37$$!$?aP83 z``D7&PZoWwj~4CHbcVWG`|3*3JDET|WBvG(t+o(HLsbN@`yngbH&Q`QSo^zNpcUK~ zqAl>;dFcseaIyr~3h~I%FgOL$Y_|LY$}@hc@3>hU2#i%-RS9H;H+|yHBqLTXMQ>$N z^9qDd1p4Lts}daB?wOedjEyP?8fVjNa2M0&8L10dUzV5uhjJA3|CEqj&#%Nk47fuv zkX^_$$!%euRhhp!;z#IC*xAe)3hdK#iq3I?ds`pTL7_suf&+`5;jq__wjWRr-)?^N z;>n`uY+(yEuLT9yQM+c~QZmX;{U+)1Kf)}a8RX%UYCV&JEPB&{YX6}JAIH^uuHCgu z`k%Y|i$2FA!Nmiq%j;h-%9Lu?$|rB}Dmx8wZC$^kaa2!;-?c^YSvfyh{z`*(;UBZJ zIW=?+Jkl*Xc{b!{UW>c@N3s$$?O>FX z7{EoKs}z0aQTz#4e$Ixwr&zoh0UG~@n`f|X_F9r0uWLC0?#o_B76*&_ZMf35(i!ug zI5Sp$GwHqGPzV&T`E$y%_V7`ghjtK3|1d!IBhQ+fXmg-;ko&y&v7c_QZNFc#Ztq7g z!*DVr`HlQ4h+xRO0Am#uBx zJJ%ABBPj~9;{@w)RI#$HqOmBEZ~U$jU&AuwXRM;|m!rCpCRorIQ?O9E99>>#wA7UU zuEE&LvBu<8t^M&%fEXKcT>tn6LsqY8iv>@A3zob7$JrF%a?cp+{oG|v$6QDDL-6rE zQ|ymQn~>Z~;|6!kx(d>A-Xure^Kb{LWF=o+T)4v_BBW6lRi2^hTnb{yYSBL)`6je% zq!d+WP#D`HBZg#q_FtOEK$V1+hdx^%z~x*I8w79>l+dEGw5>@O5%4J7+33?KuXE@T zqw#RTFQe2!Ih`SJPdoWouI2~uL~Tna`AhEy_d^l+gM%It4;vFV*HAB^-tNQuv*kSI zV{nfO|MLyp+GzH<{~CsBJ6aI3TT*Vn-bAVq2`aw^36Pag=g8~55}EqlW|)K0y!C1Q z^%i^kD##6g_xCVvQF)&`jN!NSk);>y6dP{u+1c)u%Fov2;sykS;ER<>hM-|%F$x6u z-;@GbRs5xMoa1TVd6jfqK1TSv@xCUmXH=k{ zl?;bCSTl7Zq#NA*~mf+Ay@wz4SKHzmSq@(#HD@I!VmqTfJ)$%;YD8r^RosQc0T<)~Xph+&(2 zHA8hl!p`?HZ|j>#%Rf>2s?L`Em1n%&_Gj(6V++Hp9d|2Q*S``Ewuo-c6;ahgwxb1L zFF-gdv;HGfQ#{%vi?FkQalRMwUVN{26LM&lZcpec-g%>=<(AOhJQeoq#09#;^qgzC z+}V0Y%^%v?-szbahLyY=d!&-V+h9|c$~&=V*Fe5;MwK+^mn;dthlF2bp+S4M)|aS~ z(ya4dGu_yO`gNOrG%z)pnj1)S-i@@*_%Z9sz*SveZI}Hmb%^j|+{j>)nLPtPX{Pi? z$?ovZu#e754M>sqY2-FdiQ|`DGnW?M@47B8-`QISb~pHU8-C>E&KVHNoGnnE*VqNu z1(akSlj#QhYG0Y{FxxX!NrX$l1s7J`^3!Ku7O@lX_OQL+-`ge=rModuMSW#O0b?yN z){|hS>90!mS={_XM6Q(f)FY`zBq|-BkFGJ|)UIHG5o2|Wj~bFxd0&wPHqj(CK8kke z(bk~gRAl5Uz6_#)fPn$5`pW0Oa8!!WZc0IxnbRnv$fvI+(We`Y95Sm8cY8?>tC|Fp z0%h-eAF*tpy$=>rygq3U*k2eq?$Q-|i-*37;~3^kxopGdyZwNN6#chsD>tLmyJ=6` z_fVF}+wd17*7$I0jHQ4gG_8bP4)T=!+=ozKKwSR@<>#y?F;>>}3wM#KH~7r3&{9==Z# zfY2uQLGN?f%~gl>o1xWvMXR^$ zAL-XkGkFxkdaHW)60Vtqmcy@^%3iP)>tlskiB#v5&o^dF1Z#r=Ir@+`-`F_%J~X-x zXz0zJKClwBbXSWg@tCLZv~Gc-K~*DFZOemAbh87;rU6wE5zkQ~mYG!ZkX zM3|*8U1tiYd3Fe4y9}PRb(XZSN=o0t;8PLOzF@6>Oo(?Q^|TDr-bE#&vT=FEkr$_V J5@Cyv_kWw5qUrzu literal 0 HcmV?d00001 diff --git a/doc/presentations/largescale06/abstract.tex b/doc/presentations/largescale06/abstract.tex new file mode 100644 index 0000000..90388d8 --- /dev/null +++ b/doc/presentations/largescale06/abstract.tex @@ -0,0 +1,66 @@ +\documentclass[12pt,letterpaper]{article} + +\usepackage{times} +\usepackage{fullpage} + +\setlength{\parindent}{0pt} +\setlength{\parskip}{10pt} + +\begin{document} +\author{\bf G\'abor Cs\'ardi$^{1,2}$ and Tam\'as Nepusz$^{1,3}$} +\title{\Large\bf The \texttt{igraph} software package for complex + network research} +\date{\bf\normalsize\texttt{\{csardi,ntamas\}@rmki.kfki.hu}\\[15pt] + $^1$ Department of Biophysics,\\ Research Institute for Nuclear and + Particle Phyisics \\ of the Hungarian Academy of Sciences, \\ + 29-33 Konkoly-Thege \'ut, Budapest 1121\\[10pt] + $^2$ Center for Complex Systems Studies, Kalamazoo College, \\ + 1200 Academy st, Kalamazoo, MI 49006 \\[10pt] + $^3$ Department of Measurement and Information Systems, \\ Budapest + University of Technology and Economics, \\ + 3-9 M\H{u}egyetem rkp, Budapest 1111 +} +\maketitle + +\pagestyle{empty}\thispagestyle{empty} +\enlargethispage{5cm} +\centerline{\bf\large Abstract} + +\noindent +This presentation does not cover results of scientific research, but +introduces a software package which gives handy tools into the hands +of researchers doing network science. The authors strongly believe +that the tools scientists use are important because they +can increase productivity by several factors and thereby enhance +scientific progress. + +The \texttt{igraph} library was developed because of the lack of network +analysis software which (1)~can handle large graphs efficiently, +(2)~can be embedded into a higher level program or programming language +(like Python, Perl or GNU R) and (3) can be used both interactively +and non-interactively. + +The capability of handling large graphs was important because the +authors were confronted with graphs with millions of vertices and edges. + +Embedding \texttt{igraph} into Python or GNU R creates a very productive +research environment, well suited for rapid development. All the +expressing power of GNU R (or other higher level languange) is readily +available in a convenient integrated environment for generating, +manipulating and measuring graphs, and evaluating these measurements. + +Interactive means of software usage is nowadays considered as superior +to non-interactive interfaces, which is very true for most +cases. Dealing with large graphs can be different though -- if it takes +three months to calculate the diameter of a graph, nobody wants that to +be interactive. + +In this presentation we will show the key features of the +\texttt{igraph} software package and various demonstration +tasks involving generating, analyzing and visualizing large graphs. + +\vspace*{10pt} +\noindent +\textbf{Key Words:} network analysis software, large networks + +\end{document} diff --git a/doc/presentations/netsci06/arch.fig b/doc/presentations/netsci06/arch.fig new file mode 100644 index 0000000..e4acf14 --- /dev/null +++ b/doc/presentations/netsci06/arch.fig @@ -0,0 +1,24 @@ +#FIG 3.2 Produced by xfig version 3.2.5-alpha5 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +2 4 0 1 0 13 50 -1 20 0.000 0 0 7 0 0 5 + 9630 6630 3015 6630 3015 5145 9630 5145 9630 6630 +2 4 0 1 0 23 50 -1 20 0.000 0 0 7 0 0 5 + 5310 4755 3015 4755 3015 3300 5310 3300 5310 4755 +2 4 0 1 0 22 50 -1 20 0.000 0 0 7 0 0 5 + 7950 4755 5655 4755 5655 3300 7950 3300 7950 4755 +2 4 0 1 0 21 50 -1 20 0.000 0 0 7 0 0 5 + 9600 4755 8340 4755 8340 3300 9600 3300 9600 4755 +2 4 0 1 0 1 50 -1 20 0.000 0 0 7 0 0 5 + 9630 8550 3015 8550 3015 7065 9630 7065 9630 8550 +4 1 6 50 -1 18 28 0.0000 4 465 1590 6765 4125 Python\001 +4 1 6 50 -1 18 28 0.0000 4 60 405 8955 4125 ...\001 +4 1 6 50 -1 18 28 0.0000 4 465 3780 6345 6015 Graph operations\001 +4 1 6 50 -1 18 28 0.0000 4 465 5010 6345 7995 Basic graph operations\001 +4 1 6 50 -1 18 28 0.0000 4 360 1500 4170 4125 GNU R\001 diff --git a/doc/presentations/netsci06/demo.R b/doc/presentations/netsci06/demo.R new file mode 100644 index 0000000..a168190 --- /dev/null +++ b/doc/presentations/netsci06/demo.R @@ -0,0 +1,68 @@ + +########################## Start R +R + +########################## Load igraph package +library(igraph) + +########################## Get the network + +g <- graph(directed=FALSE, + c(0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, + 0,10, 0,11, 0,12, 0,13, 0,17, 0,19, 0,21, 0,31, + 1, 2, 1, 3, 1, 7, 1,13, 1,17, 1,19, 1,21, 1,30, + 2, 3, 2, 7, 2,27, 2,28, 2,32, 2, 9, 2, 8, 2,13, + 3, 7, 3,12, 3,13, 4, 6, 4,10, 5, 6, 5,10, 5,16, + 6,16, 8,32, 8,32, 8,33, 9,33,13,33,14,32,14,33, + 15,32,15,33,18,32,18,33,19,33,20,32,20,33, + 22,32,22,33,23,25,23,27,23,32,23,33,23,29, + 24,25,24,27,24,31,25,31,26,29,26,33,27,33, + 28,31,28,33,29,32,29,33,30,32,30,33,31,32,31,33, + 32,33)) + +g <- read.graph("http://localhost/~csardi/karate.net", format="pajek") + +######################### Implement Newman's algorithm + +community.newman <- function(g) { + A <- get.adjacency(g) + deg <- degree(g) + ec <- ecount(g) + P <- outer(deg, deg, function(x,y) x*y / 2 /ec ) + B <- A - P + diag(B) <- 0 + + eigen(B)$vectors[,1] +} + +######################## Run it on the Karate club data + +mem <- community.newman(g) + +V(g)$color <- ifelse(mem < 0, "grey", "green") + +plot(g, layout=layout.kamada.kawai, vertex.color="a:color") + +######################## Vertex sizes + +scale <- function(v, a, b) { + v <- v-min(v) + v <- v/max(v) + v <- v * (b-a) + v+a +} + +V(g)$size <- scale(abs(mem), 15, 25) +E(g)$color <- "grey" +E(g)[ V(g)[color=="grey"] %--% V(g)[color=="green"] ]$color <- "red" + +tkplot(g, layout=layout.kamada.kawai, vertex.color="a:color", + vertex.size="a:size", edge.color="a:color") + +####################### A large graph + +g <- barabasi.game(10000, directed=FALSE) +plot(degree.distribution(g), log="xy", type="b") +coords <- layout.fruchterman.reingold.grid(g) +plot(g, layout=coords, vertex.size=0, labels=NA) + diff --git a/doc/presentations/netsci06/grid1.fig b/doc/presentations/netsci06/grid1.fig new file mode 100644 index 0000000..068fa56 --- /dev/null +++ b/doc/presentations/netsci06/grid1.fig @@ -0,0 +1,33 @@ +#FIG 3.2 Produced by xfig version 3.2.5-alpha5 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +6 1935 1260 2970 2520 +4 1 6 50 -1 0 24 0.0000 4 270 750 2452 1530 Data\001 +4 1 6 50 -1 0 24 0.0000 4 270 645 2452 2010 web\001 +4 1 6 50 -1 0 24 0.0000 4 195 975 2452 2490 server\001 +-6 +6 5940 990 7830 1845 +2 4 0 1 0 23 50 -1 20 0.000 0 0 7 0 0 5 + 7830 1845 5940 1845 5940 990 7830 990 7830 1845 +4 1 6 50 -1 0 24 0.0000 4 270 1110 6885 1530 worker\001 +-6 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 5940 1350 3780 1350 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 3780 4230 4380 4230 4380 1620 5940 1620 +2 4 0 1 0 14 51 -1 20 0.000 0 0 7 0 0 5 + 3780 5805 1170 5805 1170 3960 3780 3960 3780 5805 +2 4 0 1 0 1 51 -1 20 0.000 0 0 7 0 0 5 + 3780 2835 1170 2835 1170 990 3780 990 3780 2835 +4 1 6 50 -1 0 24 0.0000 4 270 795 2437 4560 Task\001 +4 1 6 50 -1 0 24 0.0000 4 270 645 2437 5040 web\001 +4 1 6 50 -1 0 24 0.0000 4 195 975 2437 5520 server\001 diff --git a/doc/presentations/netsci06/grid2.fig b/doc/presentations/netsci06/grid2.fig new file mode 100644 index 0000000..29c97c0 --- /dev/null +++ b/doc/presentations/netsci06/grid2.fig @@ -0,0 +1,90 @@ +#FIG 3.2 Produced by xfig version 3.2.5-alpha5 +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +6 1935 1260 2970 2520 +4 1 6 50 -1 0 24 0.0000 4 270 750 2452 1530 Data\001 +4 1 6 50 -1 0 24 0.0000 4 270 645 2452 2010 web\001 +4 1 6 50 -1 0 24 0.0000 4 195 975 2452 2490 server\001 +-6 +6 5940 990 7830 1845 +2 4 0 1 0 23 50 -1 20 0.000 0 0 7 0 0 5 + 7830 1845 5940 1845 5940 990 7830 990 7830 1845 +4 1 6 50 -1 0 24 0.0000 4 270 1110 6885 1530 worker\001 +-6 +6 3735 2430 5985 7515 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 3780 4380 4455 4395 4440 2745 5940 2745 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 3780 4545 4530 4545 4530 3990 5940 3990 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 3780 4695 4380 4695 4380 5175 5940 5175 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 3780 4800 4305 4800 4305 6315 5940 6315 +2 1 0 2 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 3780 4965 4200 4965 4200 7485 5940 7485 +2 1 1 2 0 7 50 -1 -1 6.000 0 0 -1 0 0 2 + 5940 2460 5385 2460 +2 1 1 2 0 7 50 -1 -1 6.000 0 0 -1 0 0 2 + 5940 3720 5385 3720 +2 1 1 2 0 7 50 -1 -1 6.000 0 0 -1 0 0 2 + 5940 4905 5385 4905 +2 1 1 2 0 7 50 -1 -1 6.000 0 0 -1 0 0 2 + 5940 6075 5385 6075 +2 1 1 2 0 7 50 -1 -1 6.000 0 0 -1 0 0 2 + 5940 7230 5385 7230 +-6 +6 5940 2250 7830 3105 +2 4 0 1 0 23 50 -1 20 0.000 0 0 7 0 0 5 + 7830 3105 5940 3105 5940 2250 7830 2250 7830 3105 +4 1 6 50 -1 0 24 0.0000 4 270 1110 6885 2790 worker\001 +-6 +6 5940 3510 7830 4365 +2 4 0 1 0 23 50 -1 20 0.000 0 0 7 0 0 5 + 7830 4365 5940 4365 5940 3510 7830 3510 7830 4365 +4 1 6 50 -1 0 24 0.0000 4 270 1110 6885 4050 worker\001 +-6 +6 5940 4680 7830 5535 +2 4 0 1 0 23 50 -1 20 0.000 0 0 7 0 0 5 + 7830 5535 5940 5535 5940 4680 7830 4680 7830 5535 +4 1 6 50 -1 0 24 0.0000 4 270 1110 6885 5220 worker\001 +-6 +6 5940 5895 7830 6750 +2 4 0 1 0 23 50 -1 20 0.000 0 0 7 0 0 5 + 7830 6750 5940 6750 5940 5895 7830 5895 7830 6750 +4 1 6 50 -1 0 24 0.0000 4 270 1110 6885 6435 worker\001 +-6 +6 5940 7065 7830 7920 +2 4 0 1 0 23 50 -1 20 0.000 0 0 7 0 0 5 + 7830 7920 5940 7920 5940 7065 7830 7065 7830 7920 +4 1 6 50 -1 0 24 0.0000 4 270 1110 6885 7605 worker\001 +-6 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 5940 1350 3780 1350 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 1 4 + 1 1 1.00 60.00 120.00 + 1 1 1.00 60.00 120.00 + 3780 4230 4380 4230 4380 1620 5940 1620 +2 4 0 1 0 14 51 -1 20 0.000 0 0 7 0 0 5 + 3780 5805 1170 5805 1170 3960 3780 3960 3780 5805 +2 4 0 1 0 1 51 -1 20 0.000 0 0 7 0 0 5 + 3780 2835 1170 2835 1170 990 3780 990 3780 2835 +4 1 6 50 -1 0 24 0.0000 4 270 795 2437 4560 Task\001 +4 1 6 50 -1 0 24 0.0000 4 270 645 2437 5040 web\001 +4 1 6 50 -1 0 24 0.0000 4 195 975 2437 5520 server\001 diff --git a/doc/presentations/netsci06/igraph-netsci06.tex b/doc/presentations/netsci06/igraph-netsci06.tex new file mode 100644 index 0000000..c981874 --- /dev/null +++ b/doc/presentations/netsci06/igraph-netsci06.tex @@ -0,0 +1,130 @@ +\documentclass[landscape,20pt]{foils} + +\usepackage{ae} +\usepackage{hyperref} +\usepackage{thumbpdf} +\usepackage[margin=2cm]{geometry} +\usepackage{graphicx} +\usepackage{color} +\usepackage{multicol} +\usepackage[document]{ragged2e} +\usepackage{pause} +\usepackage{amstext} +\usepackage{amsmath} + +\newcommand{\tit}[1]{{\centering\Large #1\par\vspace*{10pt}\hrule}} +\newcommand{\red}[1]{\textcolor{red}{#1}} +\newcommand{\dd}{\mathrm{d}} + +\setlength{\columnsep}{0.5cm} +\setlength{\columnseprule}{0.4pt} + +\newcommand{\pdflaunch}[1] {\pdfpageattr{/AA << /O << /S /Launch /F (#1) >>>>}} +\newcommand{\pdflaunchlink}[2]{% + \pdfstartlink attr{/Border [0 0 0]} user{/Subtype /Link /A << % + /S /Launch /F (#1) >>}% + \pdfliteral{0 1 0 0 k}% + {#2}\pdfliteral{0 0 0 1 k}\pdfendlink% + } + +%\renewcommand{\pause}{\relax} + +\renewcommand{\emph}[1]{\textcolor{red}{\bf #1}} + +\begin{document} + +\MyLogo{The \texttt{igraph} library -- NetSci 2006 conference} + +%%%%%%%%%%%%%%%%%%%%%% TITLEPAGE + +\mbox{}\vfill +\begin{center} +\LARGE +The \texttt{igraph} network analysis library\\[10pt] +\normalsize +\url{http://cneurocvs.rmki.kfki.hu/igraph} +\par +\large +G\'abor Cs\'ardi +\par +\normalsize +Center for Complex Systems Studies, Kalamazoo College,\\ +Department of Biophysics, KFKI Research Institute for Particle and +Nuclear Physics, Budapest, Hungary +\end{center} +\vfill + +\newpage +%%%%%%%%%%%%%%%%%%%%%% + +\tit{Why another network analysis library/program?} + +I needed something +\begin{itemize} +\item for \emph{large} data sets. Millions of nodes and vertices. +\item \emph{fast}. +\item open, \emph{embeddable} into another program/higher level + language for productivity. +\item non-interactive \emph{and} interactive. +\end{itemize} + +\newpage +%%%%%%%%%%%%%%%%%%%%%% + +\tit{Other features} + +(I don't really need them but it turned out that we have them.) +\begin{itemize} +\item Documentation. +\item Portability. +\item Layered architecture. +\item Open source. +\end{itemize} + +\newpage +%%%%%%%%%%%%%%%%%%%%%% + +\tit{What is \texttt{igraph}?} +\vfill + +\begin{center} +\includegraphics[width=.6\textwidth]{arch} +\end{center} +\vfill + +\newpage +%%%%%%%%%%%%%%%%%%%%%% + +\tit{Case study one: ``grid'' computation} + +\begin{center} +\includegraphics[width=.6\textwidth]{grid1} +\end{center} + +\newpage +%%%%%%%%%%%%%%%%%%%%%% + +\tit{Case study one: ``grid'' computation} + +\begin{center} +\includegraphics[width=.6\textwidth]{grid2} +\end{center} + +\newpage +%%%%%%%%%%%%%%%%%%%%%% + +\tit{Case study two: live demo} + +\newpage +%%%%%%%%%%%%%%%%%%%%%% + +\tit{To get it} + +\vfill +\begin{center} +\Large +\url{http://cneurocvs.rmki.kfki.hu/igraph} +\end{center} +\vfill + +\end{document} diff --git a/doc/presentations/netsci07/Makefile b/doc/presentations/netsci07/Makefile new file mode 100644 index 0000000..e1a51e2 --- /dev/null +++ b/doc/presentations/netsci07/Makefile @@ -0,0 +1,68 @@ + +all: ps + +ps: netsci07.ps + +pdf: netsci07.pdf + +netsci07.ps: netsci07.dvi + dvips -t a0 netsci07.dvi -o netsci07.ps + +netsci07.pdf: netsci07.ps + ps2pdf netsci07.ps + +IMAGES=karate3d.eps arch.eps RMKI-BME.eps CCSS.eps python3d.eps \ + sna_screenshot.eps tkplot.eps diameter.eps degreedist.eps python3d-2.eps + +%.png:: ../images/%.png + cp $< $@ + +%.svg.gz:: ../images/%.svg.gz + cp $< $@ + +karate3d.eps: karate3d.png + convert karate3d.png karate3d.eps + +arch.eps: arch.svg.gz + gzip -dc arch.svg.gz >arch.svg && \ + inkscape -E arch.eps -y 0.0 arch.svg + +RMKI-BME.eps: RMKI-BME.svg.gz + gzip -dc RMKI-BME.svg.gz >RMKI-BME.svg && \ + inkscape -E RMKI-BME.eps -y 0.0 RMKI-BME.svg + +CCSS.eps: CCSS.svg.gz + gzip -dc CCSS.svg.gz >CCSS.svg && \ + inkscape -E CCSS.eps -y 0.0 CCSS.svg + +python3d.eps: python3d.png + convert python3d.png python3d.eps + +python3d-2.eps: python3d-2.png + convert python3d-2.png python3d-2.eps + +sna_screenshot.eps: sna_screenshot.jpg + convert sna_screenshot.jpg sna_screenshot.eps + +tkplot.eps: tkplot.png + convert tkplot.png tkplot.eps + +diameter.eps: diameter.png + convert diameter.png diameter.eps + +degreedist.eps: degreedist.png + convert degreedist.png degreedist.eps + +netsci07.dvi: netsci07.tex $(IMAGES) + latex netsci07.tex + +clean: + rm -f karate3d.eps screenshots.eps arch.eps arch.eps_t \ + RMKI-BME.eps CCSS.eps netsci07.pdf netsci07.ps \ + python3d-2.eps sna_screenshot.eps \ + netsci07.aux netsci07.dvi netsci07.log \ + screenshots.svg arch.svg RMKI-BME.svg CCSS.svg \ + degreedist.eps diameter.eps python3d.eps tkplot.eps + + +.PHONY: all ps pdf clean diff --git a/doc/presentations/netsci07/netsci07.tex b/doc/presentations/netsci07/netsci07.tex new file mode 100644 index 0000000..1627ca4 --- /dev/null +++ b/doc/presentations/netsci07/netsci07.tex @@ -0,0 +1,269 @@ +\documentclass[a0]{sciposter} +\usepackage{graphicx} +\usepackage{color} +\usepackage{multicol} +\usepackage{url} +\usepackage{ragged2e} +\usepackage{watermark} +\usepackage{fancyvrb} +\usepackage{amstext} + +\newcommand{\figfigure}[2]{% + \includegraphics[width=#1]{#2} +} + +\setlength{\columnseprule}{.5pt} +\setlength{\parskip}{12pt} + +\title{ + The \textcolor{red}{igraph} platform for network science} +\author{ + G\'abor Cs\'ardi$^{\scriptscriptstyle 1,2}$ and + Tam\'as Nepusz$^{\scriptscriptstyle 1,3}$} +\institute{\mbox{}$^1$Department of Biophysics, + Research Institute for Particle and Nuclear Physics \\ + of the Hungarian Academy of Sciences, + Budapest, Hungary. \\ + \mbox{}$^2$Center for Complex Systems Studies, Kalamazoo College, + Kalamazoo, MI, USA. \\ + \mbox{}$^3$Dept. of Measurement and Information Systems, Budapest + University of Technology and Economics, Budapest, Hungary. +} +\email{csardi@rmki.kfki.hu \& ntamas@rmki.kfki.hu} +\leftlogo{RMKI-BME} +\rightlogo{CCSS} + +\begin{document} + +\watermark{\includegraphics[angle=-90,width=\textwidth]{karate3d}} + +\maketitle + +\begin{multicols}{3} + +\section{Features} + +\begin{itemize} +\item Efficient and simple graph representation. Storing a graph needs + 32 bytes per edge and 16 bytes per vertex. +\item Efficient implementation. The current state of the art + algorithms are implemented (if not that is considered as a bug). +\item Portable C (and some C++) code, works on most platforms: + Linux flavours, MS Windows variations, Mac OSX, Solaris, etc. +\item Wide variety of graph algorithms: random and regular graph + structures, centrality measures, shortest paths, 2D and 3D graph + layouts, graph motifs, cliques, network flows and cuts, community + structure detection, etc. +\item Interfaces from GNU R and Python, other interfaces can be added + without too much hassle. +\item Interactive and non-interactive, 2D and 3D + visualization from R and Python. +\item Graph, vertex and edge attributes, like edge weights, vertex + colors and other parameters for plotting. +\item Various import and export file formats: GraphML, GML, Pajek, + simple edge list, etc. +\item Well documented. +\item Open source and completely free for non-commercial and + commercial use. +\end{itemize} + +\section{Architecture} + +Simple and flexible layered architecture. +Modules interact via well-defined interfaces and +can be replaced individually without breaking other parts. + +\begin{center} +\figfigure{\columnwidth}{arch} +\end{center} + +\section{Examples and Screenshots} + +% \vspace{20pt}\hrule\vspace{20pt} +% \centerline{\bf Python interface screeshot} +% \vspace{20pt}\hrule\vspace{20pt} + +% \begin{center} +% \includegraphics[width=\columnwidth]{sna_screenshot} +% \end{center} + +% \columnbreak + +\vspace{20pt}\hrule\vspace{20pt} +\centerline{\bf Eigenvector based community structure} +\vspace{20pt}\hrule\vspace{20pt} + +\begin{Verbatim}[fontsize=\small,numbers=left] +g <- read.graph( + "http://www.rmki.kfki.hu/~csardi/karate.gml", + format="GML") + +community.newman <- function(g) { + d <- degree(g) + B <- get.adjacency(g)-outer(d, d, function(x,y) + x*y/2/ecount(g)) + diag(B) <- 0 + eigen(B)$vectors[,1] +} + +mem <- community.newman(g) +V(g)$color <- ifelse(mem < 0, "blue", "green") +V(g)$size <- abs(mem) * 35 ; E(g)$color <- "darkblue" +E(g) [ V(g)[mem>=0] %--% V(g)[mem>=0] ]$color <- + "darkgreen" +E(g) [ V(g)[mem< 0] %--% V(g)[mem>=0] ]$color <- "red" +tkplot(g, layout=layout.fruchterman.reingold, + vertex.label.dist=1) +\end{Verbatim} + +\begin{center} +\includegraphics[width=0.7\columnwidth]{tkplot} +\end{center} + +\vspace{20pt}\hrule\vspace{20pt} +\centerline{\bf Diameter of a scale-free graph} +\vspace{20pt}\hrule\vspace{20pt} + +\begin{Verbatim}[fontsize=\small,numbers=left] +g <- barabasi.game(100, directed=FALSE) +d <- get.diameter(g) +E(g)$color <- "SkyBlue2" +E(g)$width <- 1 +E(g, path=d)$color <- "red" +E(g, path=d)$width <- 2 +V(g)$label.color <- V(g)$color <- "blue" +V(g)[ d ]$label.color <- V(g)[ d ]$color <- "red" +plot(g, layout=layout.fruchterman.reingold, + vertex.label.dist=0.6, vertex.size=3) +title(main="Diameter of small scale-free graph", + xlab="created by igraph 0.4") +\end{Verbatim} + +\begin{center} +\includegraphics[width=0.7\columnwidth]{diameter} +\end{center} + +\vspace{20pt}\hrule\vspace{20pt} +\centerline{\bf Some degree distributions} +\vspace{20pt}\hrule\vspace{20pt} + +\begin{Verbatim}[fontsize=\small,numbers=left] +g <- barabasi.game(100000) +d <- degree(g, mode="in") +dd <- degree.distribution(g, mode="in", cumul=TRUE) +alpha <- power.law.fit(d, xmin=20) +plot(dd, log="xy", xlab="degree", + ylab="cumulative frequency", + col=1, main="Nonlinear preferential attachment") +lines(10:500, 10*(10:500)^(-coef(alpha)+1)) + +powers <- c(1.0, 0.9, 0.8, 0.7, 0.6) +for (p in seq(powers[-1])) { + g <- barabasi.game(100000, power=powers[p+1]) + dd <- degree.distribution(g, mode="in", cumul=TRUE) + points(dd, col=p+1, pch=p+1) +} +legend(1,1e-5, powers, col=1:5, pch=1:5, yjust=0, lty=0) +\end{Verbatim} + +\begin{center} +\includegraphics[width=0.7\columnwidth]{degreedist} +\end{center} + +\columnbreak +\vspace{20pt}\hrule\vspace{20pt} +\centerline{\bf Page Rank in Python} +\vspace{20pt}\hrule\vspace{20pt} + +\begin{Verbatim}[fontsize=\small,numbers=left] +from igraph import * +from copy import copy + +def pagerank(g, damping=0.85, epsilon=0.001, iters=100): + pageranks = [1-damping] * g.vcount() + outlinks = g.degree(type=OUT) + mindiff = epsilon + newprs = [0] * g.vcount() + + while mindiff >= epsilon and iters > 0: + iters = iters - 1 + for n in range(g.vcount()): + neis = g.neighbors(n, IN) + pr = 0.0 + if len(neis) > 0: + for n2 in neis: pr = pr+pageranks[n2]/outlinks[n2] + pr = pr*damping + newprs[n] = pr+1-damping + + mindiff = min([abs(newprs[n]-pageranks[n]) for n + in range(g.vcount())]) + pageranks = copy(newprs) + + return pageranks +\end{Verbatim} + +\vspace{20pt}\hrule\vspace{20pt} +\centerline{\bf 3D visualization in Python} +\vspace{20pt}\hrule\vspace{20pt} + +\begin{center} +\includegraphics[width=0.7\columnwidth]{python3d-2} +\end{center} + +\vspace{20pt}\hrule\vspace{20pt} +\centerline{\bf Dijkstra's shortest path algorithm} +\vspace{20pt}\hrule\vspace{20pt} + +\begin{Verbatim}[fontsize=\small,numbers=left] +wsp <- function(g, source, weights=E(g)$weight) { + vc <- vcount(g) + res <- numeric(vc) + visited <- logical(vc) + visited[source+1] <- TRUE + while (any(!visited)) { + edges <- E(g) [ V(g)[visited] %--% V(g)[!visited] ] + ft <- get.edges(g, edges) + weig <- weights[ edges+1 ] + + res[ft[,1]+1] + res[ft[,2]+1] + minw <- which.min(weig) + ft <- ft[minw,] + if (visited[ft[2]+1]) ft <- rev(ft) + res[ft[2]+1] <- weig[minw] + visited[ ft+1 ] <- TRUE + } + res +} +\end{Verbatim} + +\vspace{20pt}\hrule\vspace{20pt} +\centerline{\bf Weighted transitivity} +\vspace{20pt}\hrule\vspace{20pt} + +\[ + c(i)=\frac{\mathbf{A}^3_{ii}}{(\mathbf{A1A})_{ii}} \qquad + c_w(i)=\frac{\mathbf{W}^3_{ii}}{(\mathbf{WW_{\text{max}}W})_{ii}} +\] + +\begin{Verbatim}[fontsize=\small,numbers=left] +wtrans <- function(g) { + W <- get.adjacency(g, attr="weight") + WM <- matrix(max(W), nrow(W), ncol(W)) + diag(WM) <- 0 + diag( W %*% W %*% W ) / diag( W %*% WM %*% W) +} +\end{Verbatim} + +\section{Where to get it?} +\centerline{\bf\LARGE \url{http://igraph.sf.net}} + +\end{multicols} + +\enlargethispage{2cm} +\vfill +\hrule +\vfill +\centerline{\small NetSci 2007 International Workshop and Conference + on Network Science} +\vfill + +\end{document} diff --git a/doc/presentations/netsci07/python3d-2.png b/doc/presentations/netsci07/python3d-2.png new file mode 100644 index 0000000000000000000000000000000000000000..07f1769523a584f9a08ed88d5969e05e20be8aec GIT binary patch literal 124519 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%0Ao>t- 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

}w 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 U4rZ4 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%0AifVy!@ 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%0A+};akq(g7dfU@* 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�E}D1 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 yBTC#Kd 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

    EYT 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

  1. jDL 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

    #g^5;0G|#~GmkNpmHwt0g3|p96->@6yxEU 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�(0Yb4o{()J*1rYgJnsMz#~0m!+8}yy%euaD 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

    dslb1j4M}nu= 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 cXRsO(`h;RwHL`Yvg43HBkKj z*7~>RbNpNF|46Qo{uj?|P5)u~k1zNP|K+o*y|D?@SFq`!=>N6hv$C)-vEZBF{{v%X zW@P;e)t?RD=ARfn6ARNhd>@x|5ukfUc} z{Xgnu|6dp*J2T6F*ZYOBeTCyc>@l*lGyNYJE8~C5aWH++>pyi_nHbsr+h0~DCi?#^ z$Hw^IZDD0%WBww>f3$^_`AhkKVa#8Y{g<4hz5Z7hwukz2KT$MyH~y2OKX(!ZTiY+z p{mJz|I+VDL$rro+Fz+vy9US%T9sg#Pm6?T}1B!%1MD{z>{|CTbXTbmf literal 0 HcmV?d00001 diff --git a/doc/presentations/nips08/centnet-betweenness.pdf b/doc/presentations/nips08/centnet-betweenness.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5c936dac874305ccd1694f68b58bd0b41bd3d739 GIT binary patch literal 29733 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

    J`@*R!km_gQZ?cak;>`fVF*@l z2XI5=^q|7;&b93sg3!N?3J2$Yeg8RyV7x!-yDGSu=g z>4@$J#(SAz+hHH>KE;xl-r7icAI(CR1mg#r8;yzHHgS6)AO4UJWAsy-_s@|sR64Q( zp#@h&2)ci(J^jVfg~wu%b$tDMf;S}nfjpzKybFL$!h)x&DzF93FvP1MH#iMKTa1wk zcEq|I8|=4Vt!UZ(aM9f};j8N~t0($(!P?6|4p;}SgFBz-RE}>0?m)A$NjeOg7*R66kVN&E;Vx}N?)DIY~p>}Z;NJo zKDP7az>w6K$AB~2Rdpzd4M1153ubbJ%=BFF3dd&t0cO56dJ0Aa!nm3%{9OdcsK}L= zsALqUi^H1$fG;w755Ju)`L#SPF(wFF9kfx6WWj$UZ9bVR{~&D`nAuoZ{)L%g|8G1C z{U-+P|C_1y@3@s8(PLJAbg(|>T*BdNSwH!pOZ+4A&$@MazpjArZ99k&3i;x?Jom92 z<>K&Ze_MI%6sJA4Kkiq~J=dMbDeVp)2mQXUF$n-~45x`|qRY{`M{B#CeI^R)TdOan zs~aB|{}FOSXhmbb*t<0}3c^Js@cV`u6EdeIxMv}6N6z{B4T8j`WH34(3EaZse)@UA z>r0fvWfilnw`PMr)$_>1D%bDS`RKSC3!TfhfXNn0BFA{ z-*^c@4r2WFzJ-p2Vx>XUw zL6EWL&1d92M_A*HZ;JSgHxRYC)-<8MA@Ey2A4cT5Zzvr8%z1JGx8(2<3`Dec<-g=~w~ubnL7Ec2@R(xOn0Yx)!GT{8q*mh93l; zPp%!mz5XYZ=%dMB)z6+k>z~Gd34*#dVuq&1CJq2*y1y+H9Skj10BrwT5&Ku=bDSS& zAVa_>JV@aa@beE$=o6Ln-?RMd`u_v<`$Y87|0N0N+8cge>i=cBrgruYf+o6l{|sJA z_n#``-=q1zz61Q_-M?3as;Pm4iTwvF@M9JqtUv|;%g4#jgOQCLz|73@*TKNb_CMv% zHlO;xw9op#%Afkb>_5w&=U6{%85tS>)>&Bp(*C{uXS=`HgdcIXR<<(zA$n#6CP#%msccB4L-;J$(K_2Cz^uRjvrp4|2Id||1az6gJ$)CC;Uhu0GYn7shu@| zo`!{n4nSt&;9$c+`vJN7(2VVLZA?t{?P;v-jLFIW>PF7a+Q3o&1IYOQ+v?82@w39~$$2_QAkT$Mlf~|JRnCp7r0x>|m$+v8e3+!e=U)x*2}@>T~HT rSX+NY|9|md;#Ni`Wd)bjnX;f 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 pzx({pJeROBd5iLU2K9qj-B literal 0 HcmV?d00001 diff --git a/doc/presentations/nips08/centnet-degree.pdf b/doc/presentations/nips08/centnet-degree.pdf new file mode 100644 index 0000000000000000000000000000000000000000..be8758bd4c935aa83646052bc904fcfc613e03c6 GIT binary patch literal 29487 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/nips08/centnet-evcent.pdf b/doc/presentations/nips08/centnet-evcent.pdf new file mode 100644 index 0000000000000000000000000000000000000000..407f09136ca474e08eb35888958b45673f3b11a5 GIT binary patch literal 29992 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 U_J3KIk%N_y1wck7A}0#?e@K{7vH$=8 literal 0 HcmV?d00001 diff --git a/doc/presentations/nips08/centnet-infocent.pdf b/doc/presentations/nips08/centnet-infocent.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d83bf570b7efdfd133c4fff2779fe3cea78d7ae3 GIT binary patch literal 30041 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

    GL^p8qQ>^D(WR{X{OgvVmkpV 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/nips08/Makefile b/doc/presentations/nips08/Makefile new file mode 100644 index 0000000..6f50df1 --- /dev/null +++ b/doc/presentations/nips08/Makefile @@ -0,0 +1,8 @@ + +all: + TEXINPUTS=.:../tex: pdflatex igraph.tex + +clean: + rm igraph.{aux,log,pdf} + +.PHONY: all clean diff --git a/doc/presentations/nips08/NIPS2008.R b/doc/presentations/nips08/NIPS2008.R new file mode 100644 index 0000000..e812c89 --- /dev/null +++ b/doc/presentations/nips08/NIPS2008.R @@ -0,0 +1,639 @@ +####################################################################### +## 1) Basics +####################################################################### + +## Load the igraph package +library(igraph) + +## Create a small graph, A->B, A->C, B->C, C->E, D +## A=0, B=1, C=2, D=3, E=4 +g <- graph( c(0,1, 0,2, 1,2, 2,4), n=5 ) + +## Print a graph to the screen +g + +## Create an undirected graph as well +## A--B, A--C, B--C, C--E, D +g2 <- graph( c(0,1, 0,2, 1,2, 2,4), n=5, dir=FALSE ) +g2 + +## How to decide what kind of object a variable refers to +class(g2) +class(1) +class("foobar") + +## Is this object an igraph graph? +is.igraph(g) +is.igraph(1:10) + +## Summary, number of vertices, edges +summary(g) +vcount(g) +ecount(g) + +## Is the graph directed? +is.directed(g) +is.directed(g2) +is.directed(1:10) + +## Convert from directed to undirected +as.undirected(g) + +## And back +as.directed(as.undirected(g)) + +## Multiple edges +g <- graph( c(0,1,0,1, 0,2, 1,2, 3,4), n=5 ) +g + +is.simple(g) +is.multiple(g) + +## Remove multiple edges +g <- simplify(g) +is.simple(g) + +## Loop edges +g <- graph( c(0,0,0,1, 0,2, 1,2, 3,4), n=5 ) +g + +is.simple(g) +is.loop(g) + +## Remove loop edges +g <- simplify(g) +is.simple(g) + +## Naming vertices +g <- graph.ring(10) +V(g)$name <- letters[1:10] +V(g)$name +g +print(g, v=T) + +####################################################################### +## 2) Creating graphs +####################################################################### + +## The formula interface +library(igraph) + +## A simple undirected graph +g <- graph.formula(Alice-Bob-Cecil-Alice, + Daniel-Cecil-Eugene, Cecil-Gordon ) + +## Another undirected graph, ":" notation +g2 <- graph.formula(Alice-Bob:Cecil:Daniel, + Cecil:Daniel-Eugene:Gordon ) + +## A directed graph +g3 <- graph.formula(Alice +-+ Bob --+ Cecil + +-- Daniel, Eugene --+ Gordon:Helen ) + +## A graph with isolate vertices +g4 <- graph.formula(Alice -- Bob -- Daniel, + Cecil:Gordon, Helen ) + +## "Arrows" can be arbitrarily long +g5 <- graph.formula( Alice +---------+ Bob ) + +## From edge lists +el <- cbind( c(0, 0, 1, 2), + c(1, 2, 2, 4) ) +g <- graph.edgelist(el) +g + +## Symbolic edge lists +el <- cbind( c("Alice", "Alice", "Bob", "Cecil"), + c("Bob", "Cecil", "Cecil", "Ed") ) +g <- graph.edgelist(el) +g +summary(g) + +## Adjacency matrices +A <- matrix(sample(0:1, 100, rep=TRUE), 10, 10) +g <- graph.adjacency(A) + +####################################################################### +## 3) Manipulate graphs, add and delete vertices and edges +####################################################################### + +library(igraph) + +## Create undirected example graph +g2 <- graph.formula(Alice-Bob:Cecil:Daniel, + Cecil:Daniel-Eugene:Gordon ) +print(g2, v=T) + +## Remove alice +g3 <- delete.vertices(g2, 0) + +## Add three new vertices +g4 <- add.vertices(g3, 3) +print(g4, v=T) +igraph.par("print.vertex.attributes", TRUE) +igraph.par("plot.layout", layout.fruchterman.reingold) +g4 + +## Add three new vertices, with names this time +g4 <- add.vertices(g3, 3, attr=list(name=c("Helen", "Ike", "Jane"))) +g4 + +## Add some edges as well +g4 <- add.edges(g4, c(6,7, 5,7)) +g4 + +####################################################################### +## 4) Edge sequences +####################################################################### + +library(igraph) + +## Create directed example graph +g2 <- graph.formula(Alice -+ Bob:Cecil:Daniel, + Cecil:Daniel +-+ Eugene:Gordon ) +print(g2, v=T) + +## Sequence of all edges +E(g2) + +## Edge from a vertex to another +E(g2, P=c(0,1)) + +## Delete this edge +g3 <- delete.edges(g2, E(g2, P=c(0,1))) +g3 + +## Get the id of the edge +as.vector(E(g2, P=c(0,1))) + +## All adjacent edges of a vertex +E(g2)[ adj(2) ] + +## Or multiple vertices +E(g2)[ adj(c(2,0)) ] + +## Outgoing edges +E(g2)[ from(2) ] + +## Incoming edges +E(g2)[ to(2) ] + +## Edges along a path +E(g2, path=c(0,3,4)) + +####################################################################### +## 5) A real example, creating a graph from data frames +####################################################################### + +library(igraph) + +## We need some extra code, not in the current version yet +source("http://cneurocvs.rmki.kfki.hu/igraph/plus.R") + +## Read the comma-separated value file +vertices <- read.csv("http://cneurocvs.rmki.kfki.hu/igraph/judicial.csv") + +## Read the edges, just a table, space separated +edges <- read.table("http://cneurocvs.rmki.kfki.hu/igraph/allcites.txt") + +## And create the graph +jg <- graph.data.frame(edges, vertices=vertices, dir=TRUE) +summary(jg) + +## Save it for future use +save(jg, file="judicial.Rdata.gz", compress=TRUE) + +## Easy, huh? + +####################################################################### +## 6) Visualizing graphs +####################################################################### + +library(igraph) + +## Create a scale-free network +g <- barabasi.game(100, m=1) +g <- simplify(g) + +## simple plot +igraph.par("plot.layout", layout.fruchterman.reingold) +plot(g, vertex.size=3, vertex.label=NA, edge.arrow.size=0.6) + +## interactive +id <- tkplot(g, vertex.size=3, vertex.label=NA, edge.arrow.size=0.6) +coords <- tkplot.getcoords(id) + +## 3D +open3d() +rglplot(g, vertex.size=3, vertex.label=NA, edge.arrow.size=0.6) + +## A bit better 3D +coords <- layout.kamada.kawai(g, dim=3) +open3d() +rglplot(g, vertex.size=3, vertex.label=NA, edge.arrow.size=0.6, layout=coords) + +####################################################################### +## 7) Attributes +####################################################################### + +library(igraph) + +## Load the jurisdiction network +load("judicial.Rdata.gz") + +## If we don't have it then create it again +if (!exists("jg")) { + source("http://cneurocvs.rmki.kfki.hu/igraph/plus.R") + vertices <- read.csv("http://cneurocvs.rmki.kfki.hu/igraph/judicial.csv") + edges <- read.table("http://cneurocvs.rmki.kfki.hu/igraph/allcites.txt") + jg <- graph.data.frame(edges, vertices=vertices, dir=TRUE) +} + +## What do we have? +summary(jg) +V(jg)$year[1:100] +V(jg)$parties[1:10] + +## Select vertices based on attributes +V(jg) [ year >= 1990 ] +V(jg) [ overruled!=0 ] + +## Group network measures based on attributes +deg.per.year <- tapply(degree(jg, mode="out"), V(jg)$year, mean) + +## Plot it +plot( names(deg.per.year), deg.per.year ) + +## A more advanced example +g <- erdos.renyi.game(100, 1/100) +V(g)$color <- sample( c("red", "black"), + vcount(g), rep=TRUE) +E(g)$color <- "grey" +red <- V(g)[ color == "red" ] +bl <- V(g)[ color == "black" ] +E(g)[ red %--% red ]$color <- "red" +E(g)[ bl %--% bl ]$color <- "black" +plot(g, vertex.size=5, layout= + layout.fruchterman.reingold) + +####################################################################### +## 8) Centrality and its sensitivity +####################################################################### + +library(igraph) + +## Create it +g <- graph.formula(Andre----Beverly:Diane:Fernando:Carol, + Beverly--Andre:Diane:Garth:Ed, + Carol----Andre:Diane:Fernando, + Diane----Andre:Carol:Fernando:Garth:Ed:Beverly, + Ed-------Beverly:Diane:Garth, + Fernando-Carol:Andre:Diane:Garth:Heather, + Garth----Ed:Beverly:Diane:Fernando:Heather, + Heather--Fernando:Garth:Ike, + Ike------Heather:Jane, + Jane-----Ike ) +g <- simplify(g) + +## Hand-drawn coordinates +coords <- c(5,5,119,256,119,256,120,340,478, + 622,116,330,231,116,5,330,451,231,231,231) +coords <- matrix(coords, nc=2) + +## Labels the same as names +V(g)$label <- V(g)$name +g$layout <- coords # $ + +## Take a look at it +plot(g, asp=FALSE, vertex.label.color="blue", vertex.label.cex=1.5, + vertex.label.font=2, vertex.size=20, vertex.color="white", + vertex.frame.color="white", edge.color="black") + +## Add degree centrality to labels +V(g)$label <- paste(sep="\n", V(g)$name, degree(g)) + +## And plot again +plot(g, asp=FALSE, vertex.label.color="blue", vertex.label.cex=1.5, + vertex.label.font=2, vertex.size=20, vertex.color="white", + vertex.frame.color="white", edge.color="black") + +####################################################################### +## 9) Analyzing a (moderately) big graph +####################################################################### + +library(igraph) + +## Load the jurisdiction network +load("judicial.Rdata.gz") + +## If we don't have it then create it again +if (!exists("jg")) { + source("http://cneurocvs.rmki.kfki.hu/igraph/plus.R") + vertices <- read.csv("http://cneurocvs.rmki.kfki.hu/igraph/judicial.csv") + edges <- read.table("http://cneurocvs.rmki.kfki.hu/igraph/allcites.txt") + jg <- graph.data.frame(edges, vertices=vertices, dir=TRUE) +} + +## Basic data +summary(jg) + +## Is it a simple graph? +is.simple(jg) + +## Is it connected? +is.connected(jg) + +## How many components? +no.clusters(jg) + +## How big are these? +table(clusters(jg)$csize) + +## In-degree distribution +plot(degree.distribution(jg, mode="in"), log="xy") + +## Out-degree distribution +plot(degree.distribution(jg, mode="out"), log="xy") + +## Largest in- and out-degree, total degree +max(degree(jg, mode="in")) +max(degree(jg, mode="out")) +max(degree(jg, mode="all")) + +## Density +graph.density(jg) + +## Transitivity +transitivity(jg) + +## Transitivity of a random graph of the same size +g <- erdos.renyi.game(vcount(jg), ecount(jg), type="gnm") +transitivity(g) + +## Dyad census +dyad.census(jg) + +## Triad census +triad.census(jg) + +## Authority and Hub scores +authority.score(jg)$vector +cor(authority.score(jg)$vector, V(jg)$auth) + +hub.score(jg)$vector +cor(hub.score(jg)$vector, V(jg)$hub) + +###################################################################### +# 10) Community structure detection +###################################################################### + +library(igraph) + +## The concept of modularity +g <- graph.full(5) %du% graph.full(5) +g$layout <- layout.fruchterman.reingold +plot(g) +V(g)$color <- 2 +V(g)[0:4]$color <- 1 +plot(g) + +## This graph is modular +modularity(g, membership=V(g)$color-1) + +## If we have everyone in the same group, +## that is not modular +V(g)$color <- 1 +modularity(g, membership=V(g)$color-1) + +## If we assign the vertices randomly to two +## groups, that is not very modular, either +V(g)$color <- sample(1:2, vcount(g), rep=TRUE) +plot(g) +modularity(g, membership=V(g)$color-1) + +## Keep only the largest component for further analysis +load("judicial.Rdata.gz") + +cl <- clusters(jg) +which.max(cl$csize) +cl$membership == which.max(cl$csize)-1 +to.keep <- which(cl$membership == which.max(cl$csize)-1)-1 +jg2 <- subgraph(jg, to.keep) +summary(jg2) + +## Fast & greedy community detection +jg3 <- as.undirected(jg2, mode="collapse") +is.simple(jg3) +## system.time( fc <- fastgreedy.community(jg3) ) + +## We do it on a smaller graph instead +karate <- read.graph("http://cneurocvs.rmki.kfki.hu/igraph/karate.net", + format="pajek") +karate <- simplify(karate) + +system.time( fc <- fastgreedy.community(karate) ) +fc$modularity +max(fc$modularity) +which.max(fc$modularity) +memb <- community.to.membership(karate, fc$merges, which.max(fc$modularity)) +memb + +lay <- layout.kamada.kawai(karate) +plot(karate, layout=lay, vertex.size=5, vertex.label=NA, + vertex.color=memb$membership+1, asp=FALSE) + +## Spinglass community detection +## system.time(spc <- spinglass.community(jg3, spins=20)) + +system.time(spc <- spinglass.community(karate, spins=20)) +spc + +x11() +plot(karate, layout=lay, vertex.size=5, vertex.label=NA, + vertex.color=spc$membership+1, asp=FALSE) + +###################################################################### +# 11) Cohesive blocks +###################################################################### + +library(igraph) + +## Load the graph +cb <- graph( c(1,2,1,3,1,4,1,5,1,6, + 2,3,2,4,2,5,2,7, + 3,4,3,6,3,7, + 4,5,4,6,4,7, + 5,6,5,7,5,21, + 6,7, + 7,8,7,11,7,14,7,19, + 8,9,8,11,8,14, + 9,10, + 10,12,10,13, + 11,12,11,14, + 12,16, 13,16, 14,15, 15,16, + 17,18,17,19,17,20, + 18,20,18,21, + 19,20,19,22,19,23, + 20,21, 21,22,21,23, + 22,23)-1, dir=FALSE) + +## +V(cb)$label <- seq(vcount(cb)) # $ + +blocks <- cohesive.blocks(cb) +blocks + +summary(blocks) +blocks$blocks +lapply(blocks$blocks, "+", 1) +blocks$block.cohesion +plot(blocks, layout=layout.kamada.kawai, + vertex.label.cex=2, vertex.size=15, + vertex.label.color="black") + +###################################################################### +# 12) Weighted transitivity +###################################################################### + +library(igraph) + +## Define function +wtrans <- function(g) { + W <- get.adjacency(g, attr="weight") + WM <- matrix(max(W), nrow(W), ncol(W)) + diag(WM) <- 0 + diag( W %*% W %*% W ) / + diag( W %*% WM %*% W) +} + +## Try it on an example graph +g <- graph.formula(Andre----Beverly:Diane:Fernando:Carol, + Beverly--Andre:Diane:Garth:Ed, + Carol----Andre:Diane:Fernando, + Diane----Andre:Carol:Fernando:Garth:Ed:Beverly, + Ed-------Beverly:Diane:Garth, + Fernando-Carol:Andre:Diane:Garth:Heather, + Garth----Ed:Beverly:Diane:Fernando:Heather, + Heather--Fernando:Garth:Ike, + Ike------Heather:Jane, + Jane-----Ike ) +g <- simplify(g) + +## Check the fallback +E(g)$weight <- 1 +cbind(transitivity(g, "local"), wtrans(g)) + +## Now change a weight +E(g)[0]$weight <- 1/2 + +## Take a look at it +coords <- c(5,5,119,256,119,256,120,340,478, + 622,116,330,231,116,5,330,451,231,231,231) +coords <- matrix(coords, nc=2) +V(g)$label <- paste(sep="", V(g)$name, "\n", + round(transitivity(g, "local"),2), + ", ", round(wtrans(g), 2)) +g$layout <- coords # $ +plot(g, asp=FALSE, vertex.label.color="blue", vertex.label.cex=1.5, + vertex.label.font=2, vertex.size=20, vertex.color="white", + vertex.frame.color="white", edge.color="black", + edge.label=E(g)$weight, edge.label.cex=1.5, edge.label.color="red") + +###################################################################### +# 12) Clique percolation +###################################################################### + +library(igraph) + +## Function to calculate clique communities +clique.community <- function(graph, k) { + clq <- cliques(graph, min=k, max=k) + edges <- c() + for (i in seq(along=clq)) { + for (j in seq(along=clq)) { + if ( length(unique(c(clq[[i]], + clq[[j]]))) == k+1 ) { + edges <- c(edges, c(i,j)-1) + } + } + } + clq.graph <- simplify(graph(edges)) + V(clq.graph)$name <- + seq(length=vcount(clq.graph)) + comps <- decompose.graph(clq.graph) + + lapply(comps, function(x) { + unique(unlist(clq[ V(x)$name ])) + }) +} + +## Apply it to a graph, this is the example graph from +## the original publication +g <- graph.formula(A-B:F:C:E:D, B-A:D:C:E:F:G, C-A:B:F:E:D, D-A:B:C:F:E, + E-D:A:C:B:F:V:W:U, F-H:B:A:C:D:E, G-B:J:K:L:H, + H-F:G:I:J:K:L, I-J:L:H, J-I:G:H:L, K-G:H:L:M, + L-H:G:I:J:K:M, M-K:L:Q:R:S:P:O:N, N-M:Q:R:P:S:O, + O-N:M:P, P-Q:M:N:O:S, Q-M:N:P:V:U:W:R, R-M:N:V:W:Q, + S-N:P:M:U:W:T, T-S:V:W:U, U-E:V:Q:S:W:T, + V-E:U:W:T:R:Q, W-U:E:V:Q:R:S:T) +g <- simplify(g) + +## Hand-made layout to make it look like the original in the paper +lay <- c(387.0763, 306.6947, 354.0305, 421.0153, 483.5344, 512.1145, + 148.6107, 392.4351, 524.6183, 541.5878, 240.6031, 20, + 65.54962, 228.0992, 61.9771, 152.1832, 334.3817, 371.8931, + 421.9084, 265.6107, 106.6336, 57.51145, 605, 20, 124.8780, + 273.6585, 160.2439, 241.9512, 132.1951, 123.6585, 343.1707, + 465.1220, 317.561, 216.3415, 226.0976, 343.1707, 306.5854, + 123.6585, 360.2439, 444.3902, 532.1951, 720, 571.2195, + 639.5122, 505.3659, 644.3902) +lay <- matrix(lay, nc=2) +lay[,2] <- max(lay[,2])-lay[,2] + +## Take a look at it +plot(g, layout=lay, vertex.label=V(g)$name) + +## Calculate communities +res <- clique.community(g, k=4) + +## Paint them to different colors +colbar <- rainbow( length(res)+1 ) +for (i in seq(along=res)) { + V(g)[ res[[i]] ]$color <- colbar[i+1] +} + +## Paint the vertices in multiple communities to red +V(g)[ unlist(res)[ duplicated(unlist(res)) ] ]$color <- "red" + +## Plot with the new colors +plot(g, layout=lay, vertex.label=V(g)$name) + +###################################################################### +# 13) Compare to sna & network +###################################################################### + +library(igraph) +library(sna) +library(network) + +## Some (moderate size) data +load("judicial.Rdata.gz") + +## Try using an adjacency matrix +A <- get.adjacency(jg) + +## Try a 'network' object +el <- get.edgelist(jg, name=FALSE) +net <- network.initialize(vcount(jg)) +net2 <- network::add.edges(net, head=el[,1]+1, tail=el[,2]+1) + +## Great! Try calculating degree centrality +deg <- degree(net2) +deg <- igraph::degree(jg) + +## Ooops, try eigenvector centrality +ev <- evcent(net2) +ev <- igraph::evcent(jg)$vector + +## etc. diff --git a/doc/presentations/nips08/boxplot.pdf b/doc/presentations/nips08/boxplot.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3fa7e70484b45382bff9d0a4167199b2176edec3 GIT binary patch literal 64996 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

    K;QORFYL`VVOSMtT;}G+MT5n2IxHHH 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/nips08/communities.pdf b/doc/presentations/nips08/communities.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e5a0f49348fa510c0bf3d8832d2b72cbe7ebfec7 GIT binary patch literal 9372 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/nips08/dend.pdf b/doc/presentations/nips08/dend.pdf new file mode 100644 index 0000000..1857d12 --- /dev/null +++ b/doc/presentations/nips08/dend.pdf @@ -0,0 +1,802 @@ +%PDF-1.1 +%âãÏÓ\r +1 0 obj +<< +/CreationDate (D:20080612084932) +/ModDate (D:20080612084932) +/Title (R Graphics Output) +/Producer (R 2.7.0) +/Creator (R) +>> +endobj +2 0 obj +<< +/Type /Catalog +/Pages 3 0 R +>> +endobj +5 0 obj +<< +/Type /Font +/Subtype /Type1 +/Name /F1 +/BaseFont /ZapfDingbats +>> +endobj +6 0 obj +<< +/Type /Page +/Parent 3 0 R +/Contents 7 0 R +/Resources 4 0 R +>> +endobj +7 0 obj +<< +/Length 8 0 R +>> +stream +q +Q q +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +59.04 87.20 m 59.04 399.19 l S +59.04 87.20 m 51.84 87.20 l S +59.04 139.20 m 51.84 139.20 l S +59.04 191.20 m 51.84 191.20 l S +59.04 243.20 m 51.84 243.20 l S +59.04 295.19 m 51.84 295.19 l S +59.04 347.19 m 51.84 347.19 l S +59.04 399.19 m 51.84 399.19 l S +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 41.76 83.86 Tm (0) Tj +/F2 1 Tf 0.00 12.00 -12.00 0.00 41.76 135.86 Tm (1) Tj +/F2 1 Tf 0.00 12.00 -12.00 0.00 41.76 187.86 Tm (2) Tj +/F2 1 Tf 0.00 12.00 -12.00 0.00 41.76 239.86 Tm (3) Tj +/F2 1 Tf 0.00 12.00 -12.00 0.00 41.76 291.86 Tm (4) Tj +/F2 1 Tf 0.00 12.00 -12.00 0.00 41.76 343.86 Tm (5) Tj +/F2 1 Tf 0.00 12.00 -12.00 0.00 41.76 395.86 Tm (6) Tj +ET +Q q +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +343.63 431.20 m 178.73 431.20 l S +178.73 431.20 m 178.73 409.71 l S +178.73 409.71 m 116.75 409.71 l S +116.75 409.71 m 116.75 308.89 l S +116.75 308.89 m 99.58 308.89 l S +99.58 308.89 m 99.58 90.84 l S +99.58 90.84 m 90.99 90.84 l S +90.99 90.84 m 90.99 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 89.01 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 95.21 67.39 Tm (24) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +99.58 90.84 m 108.16 90.84 l S +108.16 90.84 m 108.16 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 106.19 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 112.27 67.39 Tm (25) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +116.75 308.89 m 133.93 308.89 l S +133.93 308.89 m 133.93 105.42 l S +133.93 105.42 m 125.34 105.42 l S +125.34 105.42 m 125.34 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 123.37 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 129.45 67.39 Tm (23) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +133.93 105.42 m 142.52 105.42 l S +142.52 105.42 m 142.52 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 140.54 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 146.74 67.39 Tm (27) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +178.73 409.71 m 240.71 409.71 l S +240.71 409.71 m 240.71 389.84 l S +240.71 389.84 m 188.68 389.84 l S +188.68 389.84 m 188.68 328.34 l S +188.68 328.34 m 168.28 328.34 l S +168.28 328.34 m 168.28 151.98 l S +168.28 151.98 m 159.69 151.98 l S +159.69 151.98 m 159.69 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 157.72 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 163.80 67.39 Tm (28) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +168.28 151.98 m 176.87 151.98 l S +176.87 151.98 m 176.87 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 174.90 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 180.97 67.39 Tm (31) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +188.68 328.34 m 209.08 328.34 l S +209.08 328.34 m 209.08 236.93 l S +209.08 236.93 m 194.05 236.93 l S +194.05 236.93 m 194.05 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 192.07 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 198.15 74.07 Tm (9) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +209.08 236.93 m 224.11 236.93 l S +224.11 236.93 m 224.11 223.08 l S +224.11 223.08 m 211.22 223.08 l S +211.22 223.08 m 211.22 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 209.25 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 215.33 74.07 Tm (8) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +224.11 223.08 m 236.99 223.08 l S +236.99 223.08 m 236.99 101.26 l S +236.99 101.26 m 228.40 101.26 l S +228.40 101.26 m 228.40 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 226.43 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 232.62 74.07 Tm (2) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +236.99 101.26 m 245.58 101.26 l S +245.58 101.26 m 245.58 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 243.60 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 249.68 67.39 Tm (13) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +240.71 389.84 m 292.74 389.84 l S +292.74 389.84 m 292.74 348.68 l S +292.74 348.68 m 271.34 348.68 l S +271.34 348.68 m 271.34 94.56 l S +271.34 94.56 m 262.75 94.56 l S +262.75 94.56 m 262.75 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 260.78 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 266.86 67.39 Tm (26) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +271.34 94.56 m 279.93 94.56 l S +279.93 94.56 m 279.93 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 277.95 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 284.03 67.39 Tm (29) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +292.74 348.68 m 314.15 348.68 l S +314.15 348.68 m 314.15 270.73 l S +314.15 270.73 m 297.11 270.73 l S +297.11 270.73 m 297.11 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 295.13 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 301.21 67.39 Tm (30) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +314.15 270.73 m 331.19 270.73 l S +331.19 270.73 m 331.19 142.02 l S +331.19 142.02 m 314.28 142.02 l S +314.28 142.02 m 314.28 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 312.31 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 318.50 67.39 Tm (22) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +331.19 142.02 m 348.10 142.02 l S +348.10 142.02 m 348.10 132.65 l S +348.10 132.65 m 331.46 132.65 l S +331.46 132.65 m 331.46 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 329.48 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 335.56 67.39 Tm (20) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +348.10 132.65 m 364.74 132.65 l S +364.74 132.65 m 364.74 124.28 l S +364.74 124.28 m 348.64 124.28 l S +348.64 124.28 m 348.64 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 346.66 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 352.74 67.39 Tm (15) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +364.74 124.28 m 380.84 124.28 l S +380.84 124.28 m 380.84 116.95 l S +380.84 116.95 m 365.81 116.95 l S +365.81 116.95 m 365.81 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 363.84 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 370.03 67.39 Tm (14) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +380.84 116.95 m 395.87 116.95 l S +395.87 116.95 m 395.87 110.65 l S +395.87 110.65 m 382.99 110.65 l S +382.99 110.65 m 382.99 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 381.01 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 387.09 67.39 Tm (18) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +395.87 110.65 m 408.75 110.65 l S +408.75 110.65 m 408.75 92.38 l S +408.75 92.38 m 400.16 92.38 l S +400.16 92.38 m 400.16 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 398.19 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 404.27 67.39 Tm (32) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +408.75 92.38 m 417.34 92.38 l S +417.34 92.38 m 417.34 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 415.37 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 421.45 67.39 Tm (33) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +343.63 431.20 m 508.52 431.20 l S +508.52 431.20 m 508.52 429.17 l S +508.52 429.17 m 455.99 429.17 l S +455.99 429.17 m 455.99 289.26 l S +455.99 289.26 m 434.52 289.26 l S +434.52 289.26 m 434.52 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 432.54 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 438.62 67.39 Tm (16) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +455.99 289.26 m 477.46 289.26 l S +477.46 289.26 m 477.46 209.72 l S +477.46 209.72 m 460.28 209.72 l S +460.28 209.72 m 460.28 87.20 l S +460.28 87.20 m 451.69 87.20 l S +451.69 87.20 m 451.69 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 449.72 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 455.71 74.07 Tm (5) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +460.28 87.20 m 468.87 87.20 l S +468.87 87.20 m 468.87 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 466.90 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 472.97 74.07 Tm (6) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +477.46 209.72 m 494.64 209.72 l S +494.64 209.72 m 494.64 87.83 l S +494.64 87.83 m 486.05 87.83 l S +486.05 87.83 m 486.05 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 484.07 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 490.27 74.07 Tm (4) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +494.64 87.83 m 503.22 87.83 l S +503.22 87.83 m 503.22 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 501.25 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 507.33 67.39 Tm (10) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +508.52 429.17 m 561.06 429.17 l S +561.06 429.17 m 561.06 369.50 l S +561.06 369.50 m 520.40 369.50 l S +520.40 369.50 m 520.40 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 518.43 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 524.62 67.39 Tm (11) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +561.06 369.50 m 601.72 369.50 l S +601.72 369.50 m 601.72 253.42 l S +601.72 253.42 m 554.22 253.42 l S +554.22 253.42 m 554.22 184.82 l S +554.22 184.82 m 537.58 184.82 l S +537.58 184.82 m 537.58 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 535.60 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 541.79 67.39 Tm (12) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +554.22 184.82 m 570.86 184.82 l S +570.86 184.82 m 570.86 173.40 l S +570.86 173.40 m 554.75 173.40 l S +554.75 173.40 m 554.75 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 552.78 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 558.97 67.39 Tm (17) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +570.86 173.40 m 586.96 173.40 l S +586.96 173.40 m 586.96 162.45 l S +586.96 162.45 m 571.93 162.45 l S +571.93 162.45 m 571.93 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 569.95 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 576.15 67.39 Tm (21) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +586.96 162.45 m 601.99 162.45 l S +601.99 162.45 m 601.99 97.68 l S +601.99 97.68 m 589.11 97.68 l S +589.11 97.68 m 589.11 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 587.13 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 593.32 74.07 Tm (1) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +601.99 97.68 m 614.87 97.68 l S +614.87 97.68 m 614.87 89.02 l S +614.87 89.02 m 606.28 89.02 l S +606.28 89.02 m 606.28 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 604.31 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 610.39 74.07 Tm (3) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +614.87 89.02 m 623.46 89.02 l S +623.46 89.02 m 623.46 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 621.48 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 627.59 74.07 Tm (7) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +601.72 253.42 m 649.22 253.42 l S +649.22 253.42 m 649.22 196.71 l S +649.22 196.71 m 640.64 196.71 l S +640.64 196.71 m 640.64 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 638.66 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 644.74 74.07 Tm (0) Tj +ET +Q q 59.04 73.44 630.72 371.52 re W n +0.000 0.000 0.000 RG +0.75 w +[] 0 d +1 J +1 j +10.00 M +649.22 196.71 m 657.81 196.71 l S +657.81 196.71 m 657.81 87.20 l S +0.000 0.000 0.000 rg +BT +/F1 1 Tf 2 Tr 4.99 0 0 4.99 655.84 85.47 Tm (l) Tj 0 Tr +ET +Q q +BT +0.000 0.000 0.000 rg +/F2 1 Tf 0.00 12.00 -12.00 0.00 661.92 67.39 Tm (19) Tj +ET +Q +endstream +endobj +8 0 obj +13884 +endobj +3 0 obj +<< +/Type /Pages +/Kids [ +6 0 R +] +/Count 1 +/MediaBox [0 0 720 504] +>> +endobj +4 0 obj +<< +/ProcSet [/PDF /Text] +/Font << /F1 5 0 R /F2 10 0 R >> +/ExtGState << >> +>> +endobj +9 0 obj +<< +/Type /Encoding +/BaseEncoding /WinAnsiEncoding +/Differences [ 45/minus 96/quoteleft +144/dotlessi /grave /acute /circumflex /tilde /macron /breve /dotaccent +/dieresis /.notdef /ring /cedilla /.notdef /hungarumlaut /ogonek /caron /space] +>> +endobj +10 0 obj << +/Type /Font +/Subtype /Type1 +/Name /F2 +/BaseFont /Helvetica +/Encoding 9 0 R +>> endobj +xref +0 11 +0000000000 65535 f +0000000021 00000 n +0000000163 00000 n +0000014333 00000 n +0000014416 00000 n +0000000212 00000 n +0000000295 00000 n +0000000375 00000 n +0000014312 00000 n +0000014509 00000 n +0000014766 00000 n +trailer +<< +/Size 11 +/Info 1 0 R +/Root 2 0 R +>> +startxref +14863 +%%EOF diff --git a/doc/presentations/nips08/igraph.tex b/doc/presentations/nips08/igraph.tex new file mode 100644 index 0000000..6db2811 --- /dev/null +++ b/doc/presentations/nips08/igraph.tex @@ -0,0 +1,973 @@ +\documentclass[landscape,fleqno]{foils} + +\usepackage{ae} +%\usepackage{hyperref} +%\usepackage{thumbpdf} +\usepackage{graphicx} +\usepackage{color} +\usepackage[left=1cm,right=1cm,top=2cm,bottom=2cm]{geometry} +% \usepackage[display]{texpower} +%\usepackage{psfrag} +\usepackage{ragged2e} +\usepackage{amstext} +\usepackage{xspace} +\usepackage{fancyvrb} +\usepackage{amsmath} +\usepackage{url} +\usepackage{pause} +\usepackage{tabularx} + +\newcommand{\figfigure}[2]{% + \begin{psfrags}% + \input #2.eps_t% + \includegraphics[width=#1]{#2.eps}% + \end{psfrags}% +} + +\newcommand{\stitle}[1]{{\color{blue}\Large #1\par\vspace*{10pt}\hrule}} +\newcommand{\cstitle}[1]{{\centering\color{blue}\Large #1\par\vspace*{10pt}\hrule}} + +\setlength{\columnsep}{0.5cm} +\setlength{\columnseprule}{0.4pt} + +\renewcommand{\emph}[1]{\textcolor{red}{\bf #1}} + +\newcommand{\igraph}{\texttt{{igraph}}\xspace} + +\DefineVerbatimEnvironment{Myverb}{Verbatim} +{gobble=2,numbers=left,numbersep=5mm,frame=lines,fontsize=\small} + +\newenvironment{narrow}[2]{% + \begin{list}{}{% + \setlength{\topsep}{0pt}% + \setlength{\leftmargin}{#1}% + \setlength{\rightmargin}{#2}% + \setlength{\listparindent}{\parindent}% + \setlength{\itemindent}{\parindent}% + \setlength{\parsep}{\parskip}}% + \item[]}{\end{list}} + +\newcommand{\bull}{$\bullet$\xspace} + +\begin{document} + +\RaggedRight +% \color{white} +% \pagecolor{black} +\fvset{fontsize=\small} +\fvset{commandchars=\\\{\}} +\definecolor{grey}{gray}{0.75} +\fvset{frame=single, numbers=left, rulecolor=\color{grey}} + +\MyLogo{\color{black}\texttt{http://cneurocvs.rmki.kfki.hu/igraph/NIPS2008.html}} + +\thispagestyle{empty} +\vspace*{1cm} +{\centering +\hrule +\Large +\vspace*{1cm} +{\bf Large-scale network analysis} +\vspace*{1cm} +\par +\hrule +\par +\vspace*{2cm} +\normalsize G\'abor Cs\'ardi\\ +\small \verb+csardi@rmki.kfki.hu+ +\par +\vspace*{1.5cm} +Department of Biophysics, +KFKI Research Institute for Nuclear and Particle Physics of the\\ +Hungarian Academy of Sciences, Budapest, Hungary\\[15pt] +Currently at \\Department of Medical Genetics, \\ +University of Lausanne, Lausanne, Switzerland\\ +} + +\newpage +\stitle{Outline} + +\begin{enumerate} +{\centering +\item The igraph R package\par +\item What can you do with large graphs?\par +\item Some unique igraph features\par +\item Rapid prototyping\par +} +\end{enumerate} + +\newpage +\stitle{The \emph{igraph} software package} + +\begin{itemize} +\item R package, Python extension and C library. \pause +\item Under active development. \pause +\item Free for academic and commercial use (GPL). ``Standing on the + shoulder of giants.'' \pause +\item State of the art data structures and algorithms, works well with + large graphs. +\end{itemize} + +\newpage +\stitle{How \emph{LARGE}?} + +\begin{itemize} +\item Well, it depends what you want to calculate. \pause +\item Just to create and manipulate it, it is enough + if it fits into the memory. \pause +\item How do I know that it fits into the memory? \pause +\item igraph (typically) needs 32 bytes per edge and 16 bytes per + vertex. \pause +\item A graph with one million vertices and ten million edges needs + about 320 Mbytes. +\end{itemize} + +\newpage +\stitle{Installation} + +\begin{Myverb} + install.packages("igraph") +\end{Myverb} + +It is really that simple. Isn't it? \pause +\vspace*{2cm} + +You might also need\vspace*{-1cm} +\begin{Myverb} + install.packages("digest") + install.packages("rgl") +\end{Myverb} + +\newpage +\stitle{How to follow this ``lecture''?} +\begin{enumerate} +%\item Type +%\begin{Myverb} +% source("http://cneurocvs.rmki.kfki.hu/igraph/NIPS2008.R") +%\end{Myverb} +%and everything is run for you. You will see the commands on the screen +%before R actually runs them, and you'll need to press \verb+ENTER+ to +%proceed.\pause +\item Go to \textcolor{red}{\url{http://cneurocvs.rmki.kfki.hu/igraph/NIPS2008.html}} and + copy \& paste everything into your R session. You can skip any + example if you wish to. + \pause +\item You type in everything I type in. \pause +\item Sit back and watch. You can download the slides/code anyway. +\end{enumerate} + +\newpage +\stitle{The igraph data model} +\begin{itemize} +\item Binary relation (=\emph{edges}) between elements of a set + (=\emph{vertices}). \pause\\[-15pt] +\item If the pairs are unordered, then the graph is undirected:\\[-20pt] + + \begin{minipage}{0.7\textwidth} + \begin{align} + \text{vertices} & =\{A,B,C,D,E\} \nonumber\\ + \text{edges} & =( \{A,B\},\{A,C\},\{B,C\},\{C,E\} ). \nonumber + \end{align} + \end{minipage}\begin{minipage}{0.3\textwidth} + \includegraphics[width=0.8\textwidth]{../images/small3} + \end{minipage}\pause +\item Otherwise it is directed:\\[-20pt] + + \begin{minipage}{0.7\textwidth} + \begin{align} + \text{vertices} & =\{A,B,C,D,E\} \nonumber\\ + \text{edges} & =( (A,B),(A,C),(B,C),(C,E) ). \nonumber + \end{align} + \end{minipage}\begin{minipage}{0.3\textwidth} + \includegraphics[width=0.8\textwidth]{../images/small4} + \end{minipage} +\end{itemize} + +\newpage +\stitle{Vertex and edge ids} +\begin{narrow}{0cm}{13.5cm} +\begin{itemize} +\item Vertices are always numbered from 0. +\item Numbering is continual, form 0 to $n-1$. \pause +\item We have to translate vertex names to ids: +\begin{align} + V & =\{A,B,C,D,E\} \nonumber\\ + E & =( (A,B),(A,C),(B,C),(C,E) ). \nonumber\\ + A & =0, B=1, C=2, D=3, E=4. \nonumber +\end{align}\vspace*{-2cm}\pause +\begin{Myverb} + library(igraph) + g <- graph( c(0,1, 0,2, 1,2, 2,4), n=5 ) + g + g2 <- graph( c(0,1, 0,2, 1,2, 2,4), + n=5, dir=FALSE ) + g2 +\end{Myverb} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Working with igraph graphs} +\begin{Myverb} + ## How to decide what kind of object a variable refers to + class(g2) + class(1) + class("foobar") + + ## Is this object an igraph graph? + is.igraph(g) + is.igraph(1:10) + + ## Summary, number of vertices, edges + summary(g) + vcount(g) + ecount(g) + + ## Is the graph directed? + is.directed(g) + is.directed(g2) + is.directed(1:10) +\end{Myverb} + +\newpage +\stitle{Working with igraph graphs} +\begin{Myverb} + ## Convert from directed to undirected + as.undirected(g) + + ## And back + as.directed(as.undirected(g)) +\end{Myverb} + +\newpage +\stitle{The igraph data model, multiple edges} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item \igraph can handle multi-graphs: + \begin{align} + V & =\{A,B,C,D,E\} \nonumber\\ + E & =( (AB),(AB),(AC),(BC),(CE) ). \nonumber + \end{align} + \begin{Myverb} + g <- graph( c(0,1,0,1, 0,2, 1,2, 3,4), n=5 ) + g + \end{Myverb} +\end{itemize} +\end{narrow} + +\newpage +\stitle{The igraph data model, loop edges} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item \igraph can handle loop-edges: + \begin{align} + V & =\{A,B,C,D,E\} \nonumber\\ + E & =( (AA),(AB),(AC),(BC),(CE) ). \nonumber + \end{align} + \begin{Myverb} + g <- graph( c(0,0,0,1, 0,2, 1,2, 3,4), n=5 ) + g + \end{Myverb} +\end{itemize} +\end{narrow} + +\newpage +\stitle{The igraph data model, what cannot be represented} +\begin{itemize} +\item ``Mixed'' graphs, with undirected and directed edges. +\item Hypergraphs. +\item No direct support for bipartite (two-mode) graphs. +\end{itemize} + +% \newpage +% \stitle{Creating graphs, using vertex ids} +% \begin{narrow}{0cm}{15cm} +% \end{narrow} + +\newpage +\stitle{Naming vertices} +\begin{narrow}{0cm}{15cm} +\begin{Myverb} + g <- graph.ring(10) + V(g)$name <- letters[1:10] + V(g)$name + g + print(g, v=T) +\end{Myverb} +\end{narrow} + +\newpage +\stitle{Creating graphs, the formula interface} +\begin{narrow}{0cm}{15cm} +\begin{Myverb} + # A simple undirected graph + g <- graph.formula( Alice-Bob-Cecil-Alice, + Daniel-Cecil-Eugene, Cecil-Gordon ) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # Another undirected graph, ":" notation + g2 <- graph.formula( Alice-Bob:Cecil:Daniel, + Cecil:Daniel-Eugene:Gordon ) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # A directed graph + g3 <- graph.formula( Alice +-+ Bob --+ Cecil + +-- Daniel, Eugene --+ Gordon:Helen ) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # A graph with isolate vertices + g4 <- graph.formula( Alice -- Bob -- Daniel, + Cecil:Gordon, Helen ) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # "Arrows" can be arbitrarily long + g5 <- graph.formula( Alice +---------+ Bob ) +\end{Myverb} +\end{narrow} + +\newpage +\stitle{Creating graphs, from edge lists and adjacency matrices} +\begin{Myverb} + ## From edge lists + el <- cbind( c(0, 0, 1, 2), + c(1, 2, 2, 4) ) + g <- graph.edgelist(el) + g + + ## Symbolic edge lists + el <- cbind( c("Alice", "Alice", "Bob", "Cecil"), + c("Bob", "Cecil", "Cecil", "Ed") ) + g <- graph.edgelist(el) + g + summary(g) + + ## Adjacency matrices + A <- matrix(sample(0:1, 100, rep=TRUE), 10, 10) + g <- graph.adjacency(A) +\end{Myverb} + +\newpage +\stitle{Creating graphs, from data frames} +%% Here we start the example from Fowler's paper +%% use the new version of graph.data.frame and read.csv + +\begin{narrow}{0cm}{15cm} +\begin{Myverb} + source("http://cneurocvs.rmki.kfki.hu/igraph/plus.R") + vertices <- read.csv("judicial.csv") + edges <- read.table("allcites.txt") + jg <- graph.data.frame(edges, vertices=vertices, dir=TRUE) +\end{Myverb} +\end{narrow} + +\newpage +\stitle{Visualizing graphs} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item \verb+plot+ Uses traditional R graphics, non-interactive, 2d. + Publication quality plots in all formats R supports. \pause +\begin{Myverb} + g <- barabasi.game(100, m=1) + g <- simplify(g) + igraph.par("plot.layout", + layout.fruchterman.reingold) + plot(g, vertex.size=3, vertex.label=NA, + edge.arrow.size=0.6) +\end{Myverb} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Visualizing graphs} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item \verb+tkplot+ Uses Tcl/Tk via the \verb+tcltk+ package, + interactive, 2d. \pause +\begin{Myverb} + id <- tkplot(g, vertex.size=3, vertex.label=NA, + edge.arrow.size=0.6) + coords <- tkplot.getcoords(id) +\end{Myverb} +\pause +\item \verb+rglplot+ Needs the \verb+rgl+ package. \pause +\begin{Myverb} + rglplot(g, vertex.size=3, vertex.label=NA) + + coords <- layout.kamada.kawai(g, dim=3) + rglplot(g, vertex.size=3, vertex.label=NA, + layout=coords) +\end{Myverb} +\pause +\item (Almost) identical interfaces. +\end{itemize} +\end{narrow} + +\newpage +\stitle{Graph/vertex/edge attributes} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item Assigning/querying attributes: + {\small + \texttt{set.graph.attribute},~\texttt{get.graph.attribute}, + \texttt{set.vertex.attribute},~\texttt{get.vertex.attribute}, + \texttt{set.edge.attribute},~\texttt{get.edge.attribute}, + \texttt{list.graph.attributes}, + \texttt{list.vertex.attributes}, + \texttt{list.edge.attributes}. +} \pause +\item \verb+V(g)+ and \verb+E(g)+. \pause +\end{itemize} +\end{narrow} + +\newpage +\stitle{Graph/vertex/edge attributes} +\begin{Myverb} + ## Load the jurisdiction network + load("judicial.Rdata.gz") + + ## If we don't have it then create it again + if (!exists("jg")) \{ + source("http://cneurocvs.rmki.kfki.hu/igraph/plus.R") + vertices <- read.csv("http://cneurocvs.rmki.kfki.hu/igraph/judicial.csv") + edges <- read.table("http://cneurocvs.rmki.kfki.hu/igraph/allcites.txt") + jg <- graph.data.frame(edges, vertices=vertices, dir=TRUE) + \} +\end{Myverb} + +\newpage +\stitle{Graph/vertex/edge attributes} +\begin{Myverb} + ## What do we have? + summary(jg) + V(jg)$year + V(jg)$parties + + ## Select vertices based on attributes + V(jg) [ year >= 1990 ] + V(jg) [ overruled!=0 ] + + ## Group network measures based on attributes + deg.per.year <- tapply(degree(jg, mode="out"), + V(jg)$year, mean) + + ## Plot it + plot( names(deg.per.year), deg.per.year ) +\end{Myverb} + +\newpage +\stitle{Smart indexing} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item Easy access of attributes: + \begin{Myverb} + g <- erdos.renyi.game(100, 1/100) + V(g)$color <- sample( c("red", "black"), + vcount(g), rep=TRUE) + E(g)$color <- "grey" + red <- V(g)[ color == "red" ] + bl <- V(g)[ color == "black" ] + E(g)[ red %--% red ]$color <- "red" + E(g)[ bl %--% bl ]$color <- "black" + plot(g, vertex.size=5, layout= + layout.fruchterman.reingold) +\end{Myverb} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Centrality, the network} +\begin{Myverb} + g <- graph.formula( Andre----Beverly:Diane:Fernando:Carol, + Beverly--Andre:Diane:Garth:Ed, + Carol----Andre:Diane:Fernando, + Diane----Andre:Carol:Fernando:Garth:Ed:Beverly, + Ed-------Beverly:Diane:Garth, + Fernando-Carol:Andre:Diane:Garth:Heather, + Garth----Ed:Beverly:Diane:Fernando:Heather, + Heather--Fernando:Garth:Ike, + Ike------Heather:Jane, + Jane-----Ike ) + g <- simplify(g) + coords <- c(5,5,119,256,119,256,120,340,478, + 622,116,330,231,116,5,330,451,231,231,231) + coords <- matrix(coords, nc=2) + V(g)$label <- V(g)$name + g$layout <- coords # $ + plot(g, asp=FALSE, vertex.label.color="blue", vertex.label.cex=1.5, + vertex.label.font=2, vertex.size=20, vertex.color="white", + vertex.frame.color="white", edge.color="black") +\end{Myverb} + +\newpage +\stitle{Centrality, the network} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet} +\end{center} + +\newpage +\stitle{Centrality, degree} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet} +\par Number of adjacent edges. +\end{center} + +\newpage +\stitle{Centrality, degree} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet-degree} +\par Number of adjacent edges. +\end{center} + +\newpage +\stitle{Centrality, closeness} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet} +\par Reciproc of the average distance to other vertices. +\end{center} + +\newpage +\stitle{Centrality, closeness} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet-closeness} +\par Reciproc of the average distance to other vertices. +\end{center} + +\newpage +\stitle{Centrality, betweenness} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet} +\par Number of shortest paths going through a vertex. +\end{center} + +\newpage +\stitle{Centrality, betweenness} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet-betweenness} +\par Number of shortest paths going through a vertex. +\end{center} + +\newpage +\stitle{Centrality, eigenvector centrality} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet} +\par Number of adjacent edges, weighted by their ``goodness''. +\end{center} + +\newpage +\stitle{Centrality, eigenvector centrality} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet-evcent} +\par Number of adjacent edges, weighted by their ``goodness''. +\end{center} + +\newpage +\stitle{Centrality, Burt's constraint} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet} +\par Benefit of brokering between other actors. +\end{center} + +\newpage +\stitle{Centrality, Burt's constraint} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet-constraint} +\par Benefit of brokering between other actors. +\end{center} + +% \newpage +% \stitle{Centrality, information centrality} +% \begin{center} +% \includegraphics[width=0.8\textwidth]{centnet} +% \end{center} + +% \newpage +% \stitle{Centrality, information centrality} +% \begin{center} +% \includegraphics[width=0.8\textwidth]{centnet-infocent} +% \end{center} + +\newpage +\stitle{Centrality, transitivity} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet} +\par Are my friends also friends of each other? +\end{center} + +\newpage +\stitle{Centrality, transitivity} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet-trans} +\par Are my friends also friends of each other? +\end{center} + +\newpage +\stitle{Sensitivity of centrality measures} +\begin{center} +\includegraphics[width=0.8\textwidth]{centnet} +\par Remove two random edges, add two random edges. +\end{center} + +\newpage +\stitle{Sensitivity of centrality measures} +\begin{narrow}{0cm}{15cm} +\begin{Myverb} + cl <- numeric() + for (i in 1:100) \{ + g2 <- delete.edges(g, sample(ecount(g), 2)-1) + g2 <- g2 %u% + \item + g\_nm(vcount(g), 2) + cl <- cbind(cl, betweenness(g2)) + \} + cl <- as.data.frame(t(cl)) + colnames(cl) <- V(g)$name # $ + boxplot(cl, main="Sensitivity of betweenness") +\end{Myverb} +\end{narrow} + +\newpage +\stitle{Sensitivity of centrality measures} +\begin{center} +\includegraphics[width=0.8\textwidth]{boxplot} +\end{center} + +\newpage +\stitle{Working with a (moderately) large graph} +\begin{Myverb} + library(igraph) + + ## Load the jurisdiction network + load("judicial.Rdata.gz") + + ## If we don't have it then create it again + if (!exists("jg")) \{ + source("http://cneurocvs.rmki.kfki.hu/igraph/plus.R") + vertices <- read.csv("http://cneurocvs.rmki.kfki.hu/igraph/judicial.csv") + edges <- read.table("http://cneurocvs.rmki.kfki.hu/igraph/allcites.txt") + jg <- graph.data.frame(edges, vertices=vertices, dir=TRUE) + \} + + ## Basic data + summary(jg) + + ## Is it a simple graph? + is.simple(jg) +\end{Myverb} + +\newpage +\stitle{Working with a (moderately) large graph} +\begin{Myverb} + ## Is it connected? + is.connected(jg) + + ## How many components? + no.clusters(jg) + + ## How big are these? + table(clusters(jg)$csize) #$ + + ## In-degree distribution + plot(degree.distribution(jg, mode="in"), log="xy") + + ## Out-degree distribution + plot(degree.distribution(jg, mode="out"), log="xy") + + ## Largest in- and out-degree, total degree + max(degree(jg, mode="in")) + max(degree(jg, mode="out")) + max(degree(jg, mode="all")) +\end{Myverb} + +\newpage +\stitle{Working with a (moderately) large graph} +\begin{Myverb} + ## Density + graph.density(jg) + + ## Transitivity + transitivity(jg) + + ## Transitivity of a random graph of the same size + g <- erdos.renyi.game(vcount(jg), ecount(jg), type="gnm") + transitivity(g) + + ## Dyad census + dyad.census(jg) + + ## Triad census + triad.census(jg) + \end{Myverb} + +\newpage +\stitle{Working with a (moderately) large graph} +\begin{Myverb} + ## Authority and Hub scores + authority.score(jg)$vector + cor(authority.score(jg)$vector, V(jg)$auth) + + hub.score(jg)$vector + cor(hub.score(jg)$vector, V(jg)$hub) +\end{Myverb} + +\newpage +\stitle{Big table of ``what can be run''} + +\renewcommand{\arraystretch}{1.7} +\begin{tabularx}{\textwidth}{l|X} +Fast (millions) & creating graphs (most of the time) \bull + structural modification (add/delete edges/vertices) \bull + subgraph \bull simplify \bull graph.decompose \bull + degree \bull clusters \bull graph.density \bull is.simple, + is.loop, is.multiple \bull articulation points and biconnected + components \bull ARPACK stuff: page.rank, hub.score, + authority.score, evcent \bull transitivity \bull Burt's + constraint \bull dyad \& triad census, graph motifs \bull + $k$-cores \bull MST \bull reciprocity \bull modularity \bull + closeness and (edge) betweenness \it{estimation} \bull shortest + paths from one source \bull generating $G_{n,p}$ and $G_{n,m}$ + graphs \bull generating PA graphs with various PA exponents + \bull topological sort \\ +\hline +Slow (10000) & closeness \bull diameter \bull betweenness \bull all-pairs + shortest paths, average path length \bull most layout + generators \bull \\ +\hline +Very slow (100) & cliques \bull cohesive blocks \bull edge/vertex + connectivity \bull maximum flows and minimum cuts \bull + bonpow \bull alpha centrality \bull (sub)graph isomorphism\\ +\end{tabularx} + +\newpage +\stitle{Why is igraph sooooo slow?} + +\begin{tabularx}{\textwidth}{X|l} +cliques and independent vertex sets & Hard problem \\ +\hline +cohesive blocks & Semi-hard problem \\ +\hline +edge/vertex connectivity, maximum flows and minimum cuts & Semi-hard + problem \\ +\hline +Bonacich's power centrality, alpha centrality & Poor implementation \\ +\hline +(sub)graph isomorphism & Hard problem \\ +\hline +\emph{anything else} & \emph{contact us if you want to speed it up} \\ +\end{tabularx} + +% \newpage +% \stitle{Generating random graphs} +%% TODO + +\newpage +\stitle{Connection to other network/graph software} +\begin{itemize} +\item \texttt{sna} and \texttt{network} R packages. Currently throught + adjacency matrices. Use namespaces!\pause +\item Pajek. \texttt{.net} file format is supported. \pause +\begin{Myverb} + g <- read.graph("http://vlado.fmf.uni-lj.si/pub/networks/data/", + format="pajek") +\end{Myverb} +\pause +\item Visone. Use GraphML format. \pause +\item Cytoscape. Use GML format. \pause +\item GraphViz. igraph can write \texttt{.dot} files. \pause +\item In general. The \emph{GraphML} and \emph{GML} file formats + are fully supported, many programs can read/write these. +\end{itemize} + +\newpage +\stitle{Community structure detection} +\begin{center} +\includegraphics[width=0.7\textwidth]{communities}\\ +\small(Modularity and community structure in networks, by Mark Newman, +2006) +\end{center} + +\newpage +\stitle{Modularity score} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item How to define what is modular? Many proposed definitions, here + is a popular one: + \[ Q = \frac1{2|E|}\sum_{vw} [A_{vw} - p_{vw} ]\delta(c_v,c_w). \] \pause +\item Random graph null model: + \[ p_{vw} = p = \frac{1}{|V|(|V|-1)} \] \pause +\item Degree sequence based null model: + \[ p_{vw} = \frac{k_vk_w}{2|E|} \] +\end{itemize} +\end{narrow} + +\newpage +\stitle{``Fast-greedy'' algorithm} +\begin{center} +A Clauset, MEJ Newman, C Moore: Finding community structure in very +large networks, \url{http://www.arxiv.org/abs/cond-mat/0408187}\\ +\includegraphics[width=0.75\textwidth]{dend} +\end{center} + +\newpage +\stitle{``Spinglass'' algorithm} +\begin{center} +J. Reichardt and S. Bornholdt: Statistical Mechanics of Community +Detection, Phys. Rev. E, 74, 016110 (2006), +\url{http://arxiv.org/abs/cond-mat/0603718} +\includegraphics[width=0.42\textwidth]{spinglass1}\hfil% +\includegraphics[width=0.42\textwidth]{spinglass2} +\end{center} + +\newpage +\stitle{Cohesive blocks} + +\begin{itemize} +\item `Structural Cohesion and Embeddedness: a Hierarchical +Concept of Social Groups' by J.Moody and D.White, Americal +Sociological Review, 68, 103--127, 2003 \pause +\item Definition 1: A collectivity is structurally cohesive to the extent + that the social relations of its members hold it together. \pause +\item Definition 2: A group is structurally cohesive to the extent that + multiple independent relational paths among all pairs of members hold + it together. \pause +\item Vertex-independent paths and vertex connectivity. \pause +\item Vertex connectivity and network flows. +\end{itemize} + +\newpage +\stitle{Cohesive blocks} +\vspace*{-3cm} +{\centering +\includegraphics[width=0.9\textwidth]{../images/groups}\\ +} + +\newpage +\stitle{Cohesive blocks} +\includegraphics[width=0.6\textwidth]{../images/cblocks}\\ + +\newpage +\stitle{Cohesive blocks} +\begin{narrow}{0cm}{15cm} +\begin{Myverb} + cb <- graph( c(1,2,1,3,1,4,1,5,1,6, + 2,3,2,4,2,5,2,7, + 3,4,3,6,3,7, + 4,5,4,6,4,7, + 5,6,5,7,5,21, + 6,7, + 7,8,7,11,7,14,7,19, + 8,9,8,11,8,14, + 9,10, + 10,12,10,13, + 11,12,11,14, + 12,16, 13,16, 14,15, 15,16, + 17,18,17,19,17,20, + 18,20,18,21, + 19,20,19,22,19,23, + 20,21, 21,22,21,23, + 22,23)-1, dir=FALSE) + + V(cb)$label <- seq(vcount(cb)) # $ +\end{Myverb} +\end{narrow} + +\newpage +\stitle{Cohesive blocks} +\includegraphics[width=0.6\textwidth]{../images/cblocks}\\ + +\newpage +\stitle{Cohesive blocks} +\begin{Myverb} + blocks <- cohesive.blocks(cb) + blocks + + summary(blocks) + blocks$blocks + lapply(blocks$blocks, "+", 1) + blocks$block.cohesion #$ + plot(blocks, layout=layout.kamada.kawai, + vertex.label.cex=2, vertex.size=15, + vertex.label.color="black") +\end{Myverb} + +\newpage +\stitle{Rapid prototyping} +\begin{narrow}{0cm}{15cm} +Weighted transitivity +\[ c(i)=\frac{\mathbf{A}^3_{ii}}{(\mathbf{A1A})_{ii}} \] \pause +\[ c_w(i)=\frac{\mathbf{W}^3_{ii}}{(\mathbf{WW_{\text{max}}W})_{ii}} \] \pause +\begin{Myverb} + wtrans <- function(g) \{ + W <- get.adjacency(g, attr="weight") + WM <- matrix(max(W), nrow(W), ncol(W)) + diag(WM) <- 0 + diag( W %*% W %*% W ) / + diag( W %*% WM %*% W) + \} +\end{Myverb} +\end{narrow} + +\newpage +\stitle{Rapid prototyping} +\begin{narrow}{0cm}{15cm} +Clique percolation, +Palla et al., Nature 435 814--818, 2005\\ +\includegraphics[width=\linewidth]{../images/vicsek} +\end{narrow} + +\newpage +\stitle{Rapid prototyping, clique percolation} +\begin{narrow}{0cm}{15cm} +\vspace*{-10pt} +\begin{Myverb} + clique.community <- function(graph, k) \{ + clq <- cliques(graph, min=k, max=k) + edges <- c() + for (i in seq(along=clq)) \{ + for (j in seq(along=clq)) \{ + if ( length(unique(c(clq[[i]], + clq[[j]]))) == k+1 ) \{ + edges <- c(edges, c(i,j)-1) + \} + \} + \} + clq.graph <- simplify(graph(edges)) + V(clq.graph)$name <- + seq(length=vcount(clq.graph)) + comps <- decompose.graph(clq.graph) + + lapply(comps, function(x) \{ + unique(unlist(clq[ V(x)$name ])) + \}) + \} +\end{Myverb} +\end{narrow} + +\newpage +\cstitle{Acknowledgement} + +\begin{center} +\vfill +Tam\'as Nepusz\par\vfil +Peter McMahan, the BLISS, Walktrap and Spinglass projects\par\vfil +All the people who contributed code, sent bug reports, suggestions\par\vfil +The R project\par\vfil +Hungarian Academy of Sciences\par\vfil +The OSS community in general\par\vfill +\end{center} + +\newpage +\cstitle{More information} +\begin{center} +\vfill +\centerline{\LARGE\url{http://igraph.sf.net}}\par\vspace*{3cm} +Please send your comments, questions, feature requests, code (!) to the +\url{igraph-help} mailing list. (See \textit{Community} on the +homepage.)\par\vfill +\end{center} + +\end{document} + diff --git a/doc/presentations/nips08/spinglass1.pdf b/doc/presentations/nips08/spinglass1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..69fc338f3afc629c4cb86f4e2ecfa067904a44e8 GIT binary patch literal 48568 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 Zpzy$2xMP0~kT4u3Buv4{si3Jy@qgyG&D;P0 literal 0 HcmV?d00001 diff --git a/doc/presentations/nips08/spinglass2.pdf b/doc/presentations/nips08/spinglass2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6f153d8c384e06969b80e5e1f14cdb305730e080 GIT binary patch literal 48569 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/tex/fltfonts.def b/doc/presentations/tex/fltfonts.def new file mode 100644 index 0000000..974bb8e --- /dev/null +++ b/doc/presentations/tex/fltfonts.def @@ -0,0 +1,291 @@ +%% +%% This is file `fltfonts.def', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% foiltex.dtx (with options: `fonts') +%% ******************************************************************** +%% Copyright (C) 1995,1997,1998,2002,2008 IBM Corporation +%% This file is part of the FoilTeX package. Use of this is governed +%% by explicit restrictions. These can be found in the header of the +%% foiltex.ins file. +%% +%% Questions, comments or suggestions concerning this program can be +%% sent to +%% James (Jim) Hafner +%% IBM Research Division +%% Almaden Research Center, K56-B2 +%% 650 Harry Road +%% San Jose, CA 95120-6099 +%% email: hafner@almaden.ibm.com +%% ******************************************************************** +%% +%% These files are updated versions of the FoilTeX package for use with +%% the new LaTeX2e. There are many enhancements and a few bugs +%% have been fixed. Undoubtedly there are many more. Contact +%% the author if you find any bugs or have suggestions for improvement +%% of this suite of files. +%% ******************************************************************** +\def\foiltexdate{2008/01/28} +\def\foiltexversion{2.1.4b} +\NeedsTeXFormat{LaTeX2e}[1996/12/01] +\ProvidesFile{fltfonts.def} + [\foiltexdate\space v\foiltexversion\space + FoilTeX font definition file, Copyright IBM 1995,1997,1998,2002,2008] +%% \CharacterTable +%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +%% Digits \0\1\2\3\4\5\6\7\8\9 +%% Exclamation \! Double quote \" Hash (number) \# +%% Dollar \$ Percent \% Ampersand \& +%% Acute accent \' Left paren \( Right paren \) +%% Asterisk \* Plus \+ Comma \, +%% Minus \- Point \. Solidus \/ +%% Colon \: Semicolon \; Less than \< +%% Equals \= Greater than \> Question mark \? +%% Commercial at \@ Left bracket \[ Backslash \\ +%% Right bracket \] Circumflex \^ Underscore \_ +%% Grave accent \` Left brace \{ Vertical bar \| +%% Right brace \} Tilde \~} +%% +\def\f@encoding{OT1} +\def\f@family{fcmss} +\def\f@series{m} +\def\f@shape{n} +\begingroup +\nfss@catcodes +\providecommand{\flt@family}[5]{% + \DeclareFontShape{#1}{#2}{#3}{#4} + {<12><14.4><17.28><20.74><24.88><29.86> + <35.83><43.00><51.60> #5 }{}} +\providecommand{\fltodd@family}[6]{% + \DeclareFontShape{#1}{#2}{#3}{#4} + {<12.1><14.5><17.38> #5 + <12><14.4><17.28><20.74><24.88><29.86><35.83><43.00><51.60> #6}{}} +\providecommand{\flt@subfamily}[5]{% + \DeclareFontShape{#1}{#2}{#3}{#4}{<->ssub * #5}{}} +\providecommand{\fltEC@family}[5]{% + \DeclareFontShape{#1}{#2}{#3}{#4} + {<12><14.4><17.28><20.74><24.88><29.86> + <35.83><43.00><51.60> genb * #5}{}} +\providecommand{\fltmath@family}[6]{% + \DeclareFontShape{#1}{#2}{#3}{#4} + {<12.1><14.5><17.38> #5 + <20.74><24.88><29.86><35.83><43.00><51.60> #6}{}} +\if@useDCfonts +\def\EC{dc} +\def\ECrm{dcr} +\def\ECrb{dcb} +\def\ECui{dcu} +\else +\def\EC{ec} +\def\ECrm{ecrm} +\def\ECrb{ecrb} +\def\ECui{ecui} +\fi +\DeclareFontFamily{OT1}{fcmr}{} + \fltodd@family{OT1}{fcmr}{m}{n} {cmr7}{cmr10} + \flt@family{OT1}{fcmr}{m}{it} {cmti10} + \flt@family{OT1}{fcmr}{m}{sc} {cmcsc10} + \flt@family{OT1}{fcmr}{bx}{n} {cmbx10} + \flt@family{OT1}{fcmr}{bx}{sl} {cmbxsl10} + \flt@family{OT1}{fcmr}{m}{ui} {cmu10} + \flt@subfamily{OT1}{fcmr}{m}{sl} {fcmss/m/sl} + \flt@subfamily{OT1}{fcmr}{b}{n} {fcmr/bx/n} + \flt@subfamily{OT1}{fcmr}{bx}{it} {fcmr/bx/sl} +\DeclareFontFamily{OT1}{fcmss}{\fontdimen3\font=1.7\fontdimen3\font} + \flt@family{OT1}{fcmss}{m}{n} {cmss10} + \flt@family{OT1}{fcmss}{m}{sl} {cmssi10} + \flt@family{OT1}{fcmss}{sbc}{n} {cmssdc10} + \flt@family{OT1}{fcmss}{bx}{n} {cmssbx10} + \flt@subfamily{OT1}{fcmss}{m}{it} {fcmr/m/it} + \flt@subfamily{OT1}{fcmss}{m}{sc} {fcmr/m/sc} + \flt@subfamily{OT1}{fcmss}{m}{ui} {fcmr/m/ui} + \flt@subfamily{OT1}{fcmss}{b}{n} {fcmss/bx/n} + \flt@subfamily{OT1}{fcmss}{bx}{sl}{fcmr/bx/sl} + \flt@subfamily{OT1}{fcmss}{bx}{it}{fcmr/bx/it} +\DeclareFontFamily{OT1}{fcmtt}{\hyphenchar\font\m@ne} + \flt@family{OT1}{fcmtt}{m}{n} {cmtt10} + \flt@family{OT1}{fcmtt}{m}{sl} {cmsltt10} + \flt@subfamily{OT1}{fcmtt}{m}{it} {fcmtt/m/sl} + \flt@subfamily{OT1}{fcmtt}{bx}{n} {fcmtt/m/n} + \flt@subfamily{OT1}{fcmtt}{bx}{it}{fcmtt/m/it} + \flt@subfamily{OT1}{fcmtt}{bx}{sl}{fcmtt/m/sl} +\DeclareFontFamily{T1}{fcmr}{} + \if@magscaleECfonts + \flt@family{T1}{fcmr}{m}{n} {\ECrm 1000} + \flt@family{T1}{fcmr}{m}{it} {\EC ti1000} + \flt@family{T1}{fcmr}{m}{sc} {\EC cc1000} + \flt@family{T1}{fcmr}{bx}{n} {\EC bx1000} + \flt@family{T1}{fcmr}{bx}{sl} {\EC bl1000} + \flt@family{T1}{fcmr}{m}{ui} {\ECui 1000} + \else + \fltEC@family{T1}{fcmr}{m}{n} {\ECrm} + \fltEC@family{T1}{fcmr}{m}{it} {\EC ti} + \fltEC@family{T1}{fcmr}{m}{sc} {\EC cc} + \fltEC@family{T1}{fcmr}{bx}{n} {\EC bx} + \fltEC@family{T1}{fcmr}{bx}{sl} {\EC bl} + \fltEC@family{T1}{fcmr}{m}{ui} {\ECui} + \fi + \flt@subfamily{T1}{fcmr}{m}{sl} {fcmss/m/sl} + %\if@magscaleECfonts + % \flt@family{T1}{fcmr}{m}{sl} {\EC sl1000} + %\else + % \fltEC@family{T1}{fcmr}{m}{sl} {\EC sl} + %\fi + \flt@subfamily{T1}{fcmr}{b}{n} {fcmr/bx/n} + \flt@subfamily{T1}{fcmr}{bx}{it} {fcmr/bx/sl} +\DeclareFontFamily{T1}{fcmss}{\fontdimen3\font=1.7\fontdimen3\font} + \if@magscaleECfonts + \flt@family{T1}{fcmss}{m}{n} {\EC ss1000} + \flt@family{T1}{fcmss}{m}{sl} {\EC si1000} + \flt@family{T1}{fcmss}{bx}{n} {\EC sx1000} + \else + \fltEC@family{T1}{fcmss}{m}{n} {\EC ss} + \fltEC@family{T1}{fcmss}{m}{sl} {\EC si} + \fltEC@family{T1}{fcmss}{bx}{n} {\EC sx} + \fi + \flt@family{T1}{fcmss}{sbc}{n} {\EC ssdc10} + \flt@subfamily{T1}{fcmss}{m}{it} {fcmr/m/it} + %\if@magscaleECfonts + % \flt@family{T1}{fcmss}{m}{it} {\EC si1000} + %\else + % \fltEC@family{T1}{fcmss}{m}{it} {\EC si} + %\fi + \flt@subfamily{T1}{fcmss}{m}{sc} {fcmr/m/sc} + \flt@subfamily{T1}{fcmss}{m}{ui} {fcmr/m/ui} + \flt@subfamily{T1}{fcmss}{b}{n} {fcmss/bx/n} + \flt@subfamily{T1}{fcmss}{bx}{it} {fcmr/bx/it} + %\if@magscaleECfonts + % \flt@family{T1}{fcmss}{bx}{it} {\EC so1000} + %\else + % \fltEC@family{T1}{fcmss}{bx}{it}{\EC so} + %\fi + \flt@subfamily{T1}{fcmss}{bx}{sl} {fcmr/bx/sl} + %\if@magscaleECfonts + % \flt@family{T1}{fcmss}{bx}{sl} {\EC so1000} + %\else + % \fltEC@family{T1}{fcmss}{bx}{sl}{\EC so} + %\fi +\DeclareFontFamily{T1}{fcmtt}{\hyphenchar\font\m@ne} + \if@magscaleECfonts + \flt@family{T1}{fcmtt}{m}{n} {\EC tt1000} + \flt@family{T1}{fcmtt}{m}{sl} {\EC st1000} + \else + \fltEC@family{T1}{fcmtt}{m}{n} {\EC tt} + \fltEC@family{T1}{fcmtt}{m}{sl} {\EC st} + \fi + \flt@subfamily{T1}{fcmtt}{m}{it} {fcmtt/m/sl} + %\if@magscaleECfonts + % \flt@family{T1}{fcmtt}{m}{it} {\EC it1000} + %\else + % \fltEC@family{T1}{fcmtt}{m}{it} {\EC it} + %\fi + \flt@subfamily{T1}{fcmtt}{bx}{n} {fcmtt/m/n} + \flt@subfamily{T1}{fcmtt}{bx}{it} {fcmtt/m/it} + \flt@subfamily{T1}{fcmtt}{bx}{sl} {fcmtt/m/sl} +\DeclareFontFamily{OML}{fcmm}{\skewchar\font'177} + \fltmath@family{OML}{fcmm}{m}{it} {cmmi7}{cmmi10} + \fltmath@family{OML}{fcmm}{b}{it} {cmmib7}{cmmib10} + \flt@subfamily{OML}{fcmm}{bx}{it} {fcmm/b/it} +\DeclareFontFamily{OMS}{fcmsy}{\skewchar\font'60} + \fltmath@family{OMS}{fcmsy}{m}{n} {cmsy7}{cmsy10} + \fltmath@family{OMS}{fcmsy}{b}{n} {cmbsy7}{cmbsy10} + \flt@subfamily{OMS}{fcmsy}{bx}{n} {fcmsy/b/n} +\DeclareFontFamily{OML}{fcmss}{\skewchar\font'177} + \flt@subfamily{OML}{fcmss}{m}{n} {fcmm/m/it} + \flt@subfamily{OML}{fcmss}{m}{it} {fcmm/m/it} + \flt@subfamily{OML}{fcmss}{m}{sl} {fcmm/m/it} + \flt@subfamily{OML}{fcmss}{m}{sc} {fcmm/m/it} + \flt@subfamily{OML}{fcmss}{bx}{n} {fcmm/m/it} + \flt@subfamily{OML}{fcmss}{b}{n} {fcmm/m/it} + \flt@subfamily{OML}{fcmss}{bx}{sl}{fcmm/m/it} + \flt@subfamily{OML}{fcmss}{bx}{it}{fcmm/m/it} +\DeclareFontFamily{OMS}{fcmss}{\skewchar\font'60} + \flt@subfamily{OMS}{fcmss}{m}{n} {fcmsy/m/n} + \flt@subfamily{OMS}{fcmss}{m}{it} {fcmsy/m/n} + \flt@subfamily{OMS}{fcmss}{m}{sl} {fcmsy/m/n} + \flt@subfamily{OMS}{fcmss}{m}{sc} {fcmsy/m/n} + \flt@subfamily{OMS}{fcmss}{bx}{n} {fcmsy/b/n} + \flt@subfamily{OMS}{fcmss}{b}{n} {fcmsy/b/n} + \flt@subfamily{OMS}{fcmss}{bx}{sl}{fcmsy/b/n} + \flt@subfamily{OMS}{fcmss}{bx}{it}{fcmsy/b/n} +\DeclareFontFamily{OML}{fcmr}{\skewchar\font'177} + \flt@subfamily{OML}{fcmr}{m}{n} {fcmm/m/it} +\DeclareFontFamily{OML}{fcmtt}{\skewchar\font'177} + \flt@subfamily{OML}{fcmtt}{m}{n} {fcmm/m/it} +\DeclareFontFamily{OMS}{fcmr}{\skewchar\font'60} + \flt@subfamily{OMS}{fcmr}{m}{n} {fcmsy/m/n} +\DeclareFontFamily{OMS}{fcmtt}{\skewchar\font'60} + \flt@subfamily{OMS}{fcmtt}{m}{n} {fcmsy/m/n} +\DeclareFontFamily{OMX}{fcmex}{}{} + \fltmath@family{OMX}{fcmex}{m}{n} {cmex7}{cmex10} +\DeclareFontFamily{U}{lasy}{} + \fltmath@family{U}{lasy}{m}{n} {lasy7}{lasy10} + \flt@family{U}{lasy}{b}{n} {lasyb10} +\endgroup % end of nfss@catcodes group +\DeclareSymbolFont{flasy}{U}{lasy}{m}{n} +\def\rmdefault{fcmr} +\def\sfdefault{fcmss} +\def\ttdefault{fcmtt} +\def\itdefault{it} +\def\sldefault{sl} +\def\bfdefault{bx} +\renewcommand\familydefault{\sfdefault} +\DeclareOldFontCommand{\rm}{\normalfont\rmfamily}{\mathrm} +\DeclareOldFontCommand{\sf}{\normalfont\sffamily}{\mathsf} +\DeclareOldFontCommand{\tt}{\normalfont\ttfamily}{\mathtt} +\DeclareOldFontCommand{\bf}{\normalfont\bfseries}{\mathbf} +\DeclareOldFontCommand{\it}{\normalfont\itshape}{\mathit} +\DeclareOldFontCommand{\sl}{\normalfont\slshape}{\@nomath\sl} +\DeclareOldFontCommand{\sc}{\normalfont\scshape}{\@nomath\sc} +\DeclareRobustCommand\em{\@nomath\em \ifdim \fontdimen\@ne\font >\z@ + \upshape \else \slshape \fi} +\SetSymbolFont{operators}{normal}{OT1}{fcmr}{m}{n} +\SetSymbolFont{letters}{normal}{OML}{fcmm}{m}{it} +\SetSymbolFont{symbols}{normal}{OMS}{fcmsy}{m}{n} +\SetSymbolFont{largesymbols}{normal}{OMX}{fcmex}{m}{n} +\SetSymbolFont{operators}{bold}{OT1}{fcmr}{bx}{n} +\SetSymbolFont{letters}{bold}{OML}{fcmm}{b}{it} +\SetSymbolFont{symbols}{bold}{OMS}{fcmsy}{b}{n} +\SetSymbolFont{largesymbols}{bold}{OMX}{fcmex}{m}{n} +\DeclareMathAlphabet{\mathrm}{OT1}{fcmr}{m}{n} +\DeclareMathAlphabet{\mathbf}{OT1}{fcmr}{bx}{n} +\DeclareMathAlphabet{\mathsf}{OT1}{fcmss}{m}{n} +\DeclareMathAlphabet{\mathit}{OT1}{fcmr}{m}{it} +\DeclareMathAlphabet{\mathtt}{OT1}{fcmtt}{m}{n} +\DeclareRobustCommand*\cal{\@fontswitch{\relax}{\mathcal}} +\DeclareRobustCommand*\mit{\@fontswitch{\relax}{\mathnormal}} +\newcommand\@xii@ipt{12.1} +\newcommand\@xiv@vpt{14.5} +\newcommand\@xvii@iiipt{17.38} +\newcommand\@xxxpt{29.86} +\newcommand\@xxxvipt{35.83} +\newcommand\@xliiipt{43} +\newcommand\@lipt{51.60} +\DeclareMathSizes{\@xiipt}{\@xii@ipt}{\@xii@ipt}{\@xii@ipt} +\DeclareMathSizes{\@xivpt}{\@xiv@vpt}{\@xii@ipt}{\@xii@ipt} +\DeclareMathSizes{\@xviipt}{\@xvii@iiipt}{\@xii@ipt}{\@xii@ipt} +\DeclareMathSizes{\@xxpt}{\@xxpt}{\@xiv@vpt}{\@xii@ipt} +\DeclareMathSizes{\@xxvpt}{\@xxvpt}{\@xvii@iiipt}{\@xiv@vpt} +\DeclareMathSizes{\@xxxpt}{\@xxxpt}{\@xxpt}{\@xvii@iiipt} +\DeclareMathSizes{\@xxxvipt}{\@xxxvipt}{\@xxvpt}{\@xxpt} +\DeclareMathSizes{\@xliiipt}{\@xliiipt}{\@xxxpt}{\@xxvpt} +\if@compatibility\else + \DeclareMathSizes{\@lipt}{\@lipt}{\@xxxvipt}{\@xxxpt} +\fi +\if@compatibility + \font\tencirc=lcircle10 scaled \magstep4 + \font\tencircw=lcirclew10 scaled \magstep4 + \font\tenln=line10 scaled \magstep4 + \font\tenlnw=linew10 scaled \magstep4 +\else + \font\tencirc=lcircle10 + \font\tencircw=lcirclew10 + \font\tenln=line10 + \font\tenlnw=linew10 +\fi +\endinput +%% +%% End of file `fltfonts.def'. diff --git a/doc/presentations/tex/foil17.clo b/doc/presentations/tex/foil17.clo new file mode 100644 index 0000000..82f3162 --- /dev/null +++ b/doc/presentations/tex/foil17.clo @@ -0,0 +1,94 @@ +%% +%% This is file `foil17.clo', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% foiltex.dtx (with options: `17pt') +%% ******************************************************************** +%% Copyright (C) 1995,1997,1998,2002,2008 IBM Corporation +%% This file is part of the FoilTeX package. Use of this is governed +%% by explicit restrictions. These can be found in the header of the +%% foiltex.ins file. +%% +%% Questions, comments or suggestions concerning this program can be +%% sent to +%% James (Jim) Hafner +%% IBM Research Division +%% Almaden Research Center, K56-B2 +%% 650 Harry Road +%% San Jose, CA 95120-6099 +%% email: hafner@almaden.ibm.com +%% ******************************************************************** +%% +%% These files are updated versions of the FoilTeX package for use with +%% the new LaTeX2e. There are many enhancements and a few bugs +%% have been fixed. Undoubtedly there are many more. Contact +%% the author if you find any bugs or have suggestions for improvement +%% of this suite of files. +%% ******************************************************************** +\def\foiltexdate{2008/01/28} +\def\foiltexversion{2.1.4b} +\NeedsTeXFormat{LaTeX2e} +\ProvidesFile{foil17.clo} + [\foiltexdate\space v\foiltexversion\space + FoilTeX file (size option), Copyright IBM 1995,1997,1998,2002,2008] +%% \CharacterTable +%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +%% Digits \0\1\2\3\4\5\6\7\8\9 +%% Exclamation \! Double quote \" Hash (number) \# +%% Dollar \$ Percent \% Ampersand \& +%% Acute accent \' Left paren \( Right paren \) +%% Asterisk \* Plus \+ Comma \, +%% Minus \- Point \. Solidus \/ +%% Colon \: Semicolon \; Less than \< +%% Equals \= Greater than \> Question mark \? +%% Commercial at \@ Left bracket \[ Backslash \\ +%% Right bracket \] Circumflex \^ Underscore \_ +%% Grave accent \` Left brace \{ Vertical bar \| +%% Right brace \} Tilde \~} +%% +\def\normalsize{\@setfontsize\normalsize\@xviipt{22}% +\abovedisplayskip 20\p@ \@plus 3\p@ \@minus 4\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 2\p@ +\belowdisplayshortskip 4\p@ \@plus 2\p@ \@minus 2\p@ +\let\@listi\@listIb} +\normalsize + +\def\small{\@setfontsize\small\@xivpt{18}% +\abovedisplayskip 16\p@ \@plus 2\p@ \@minus 4\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 1\p@ +\belowdisplayshortskip 3\p@ \@plus 1\p@ \@minus 2\p@ +\let\@listi\@listIc} + +\def\footnotesize{\@setfontsize\footnotesize\@xiipt{15}% +\abovedisplayskip 13\p@ \@plus 2\p@ \@minus 4\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 1\p@ +\belowdisplayshortskip 2\p@ \@plus 1\p@ \@minus 1\p@ +\let\@listi\@listId} + +\def\large{\@setfontsize\large\@xxpt\@xxvpt +\abovedisplayskip 30\p@ \@plus 3\p@ \@minus 9\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 3\p@ +\belowdisplayshortskip 7\p@ \@plus 3\p@ \@minus 4\p@ +\let\@listi\@listIa} + +\let\scriptsize=\footnotesize +\let\tiny=\footnotesize +\def\Large{\@setfontsize\Large\@xxvpt{32}\let\@listi\@listIa} +\def\LARGE{\@setfontsize\LARGE\@xxxpt{38}\let\@listi\@listIa} +\def\huge{\@setfontsize\huge\@xxxvipt{45}\let\@listi\@listIa} +\def\Huge{\@setfontsize\Huge\@xliiipt{54}\let\@listi\@listIa} + +\def\big#1{{\hbox{$\left#1\vbox to14.5\p@{}\right.\n@space$}}} +\def\Big#1{{\hbox{$\left#1\vbox to19.5\p@{}\right.\n@space$}}} +\def\bigg#1{{\hbox{$\left#1\vbox to24.5\p@{}\right.\n@space$}}} +\def\Bigg#1{{\hbox{$\left#1\vbox to30\p@{}\right.\n@space$}}} +\endinput +%% +%% End of file `foil17.clo'. diff --git a/doc/presentations/tex/foil20.clo b/doc/presentations/tex/foil20.clo new file mode 100644 index 0000000..4a0dbab --- /dev/null +++ b/doc/presentations/tex/foil20.clo @@ -0,0 +1,93 @@ +%% +%% This is file `foil20.clo', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% foiltex.dtx (with options: `20pt') +%% ******************************************************************** +%% Copyright (C) 1995,1997,1998,2002,2008 IBM Corporation +%% This file is part of the FoilTeX package. Use of this is governed +%% by explicit restrictions. These can be found in the header of the +%% foiltex.ins file. +%% +%% Questions, comments or suggestions concerning this program can be +%% sent to +%% James (Jim) Hafner +%% IBM Research Division +%% Almaden Research Center, K56-B2 +%% 650 Harry Road +%% San Jose, CA 95120-6099 +%% email: hafner@almaden.ibm.com +%% ******************************************************************** +%% +%% These files are updated versions of the FoilTeX package for use with +%% the new LaTeX2e. There are many enhancements and a few bugs +%% have been fixed. Undoubtedly there are many more. Contact +%% the author if you find any bugs or have suggestions for improvement +%% of this suite of files. +%% ******************************************************************** +\def\foiltexdate{2008/01/28} +\def\foiltexversion{2.1.4b} +\NeedsTeXFormat{LaTeX2e} +\ProvidesFile{foil20.clo} + [\foiltexdate\space v\foiltexversion\space + FoilTeX file (size option), Copyright IBM 1995,1997,1998,2002,2008] +%% \CharacterTable +%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +%% Digits \0\1\2\3\4\5\6\7\8\9 +%% Exclamation \! Double quote \" Hash (number) \# +%% Dollar \$ Percent \% Ampersand \& +%% Acute accent \' Left paren \( Right paren \) +%% Asterisk \* Plus \+ Comma \, +%% Minus \- Point \. Solidus \/ +%% Colon \: Semicolon \; Less than \< +%% Equals \= Greater than \> Question mark \? +%% Commercial at \@ Left bracket \[ Backslash \\ +%% Right bracket \] Circumflex \^ Underscore \_ +%% Grave accent \` Left brace \{ Vertical bar \| +%% Right brace \} Tilde \~} +%% +\def\normalsize{\@setfontsize\normalsize\@xxpt\@xxvpt +\abovedisplayskip 30\p@ \@plus 3\p@ \@minus 9\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 3\p@ +\belowdisplayshortskip 7\p@ \@plus 3\p@ \@minus 4\p@ +\let\@listi\@listIa} +\normalsize + +\def\small{\@setfontsize\small\@xviipt{22}% +\abovedisplayskip 20\p@ \@plus 3\p@ \@minus 4\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 2\p@ +\belowdisplayshortskip 4\p@ \@plus 2\p@ \@minus 2\p@ +\let\@listi\@listIb} + +\def\footnotesize{\@setfontsize\footnotesize\@xivpt{18}% +\abovedisplayskip 16\p@ \@plus 2\p@ \@minus 4\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 1\p@ +\belowdisplayshortskip 3\p@ \@plus 1\p@ \@minus 2\p@ +\let\@listi\@listIc} + +\def\scriptsize{\@setfontsize\scriptsize\@xiipt{15}% + \if@compatibility\else\let\@listi\@listId\fi} +\let\tiny=\scriptsize +\def\large{\@setfontsize\large\@xxvpt{32}} +\def\Large{\@setfontsize\Large\@xxxpt{38}} +\def\LARGE{\@setfontsize\LARGE\@xxxvipt{45}} +\def\huge{\@setfontsize\huge\@xliiipt{54}} +\if@compatibility + \let\Huge=\huge +\else + \def\Huge{\@setfontsize\huge\@lipt{62}} +\fi + +\def\big#1{{\hbox{$\left#1\vbox to17\p@{}\right.\n@space$}}} +\def\Big#1{{\hbox{$\left#1\vbox to23\p@{}\right.\n@space$}}} +\def\bigg#1{{\hbox{$\left#1\vbox to27\p@{}\right.\n@space$}}} +\def\Bigg#1{{\hbox{$\left#1\vbox to35\p@{}\right.\n@space$}}} +\endinput +%% +%% End of file `foil20.clo'. diff --git a/doc/presentations/tex/foil25.clo b/doc/presentations/tex/foil25.clo new file mode 100644 index 0000000..debc96f --- /dev/null +++ b/doc/presentations/tex/foil25.clo @@ -0,0 +1,99 @@ +%% +%% This is file `foil25.clo', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% foiltex.dtx (with options: `25pt') +%% ******************************************************************** +%% Copyright (C) 1995,1997,1998,2002,2008 IBM Corporation +%% This file is part of the FoilTeX package. Use of this is governed +%% by explicit restrictions. These can be found in the header of the +%% foiltex.ins file. +%% +%% Questions, comments or suggestions concerning this program can be +%% sent to +%% James (Jim) Hafner +%% IBM Research Division +%% Almaden Research Center, K56-B2 +%% 650 Harry Road +%% San Jose, CA 95120-6099 +%% email: hafner@almaden.ibm.com +%% ******************************************************************** +%% +%% These files are updated versions of the FoilTeX package for use with +%% the new LaTeX2e. There are many enhancements and a few bugs +%% have been fixed. Undoubtedly there are many more. Contact +%% the author if you find any bugs or have suggestions for improvement +%% of this suite of files. +%% ******************************************************************** +\def\foiltexdate{2008/01/28} +\def\foiltexversion{2.1.4b} +\NeedsTeXFormat{LaTeX2e} +\ProvidesFile{foil25.clo} + [\foiltexdate\space v\foiltexversion\space + FoilTeX file (size option), Copyright IBM 1995,1997,1998,2002,2008] +%% \CharacterTable +%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +%% Digits \0\1\2\3\4\5\6\7\8\9 +%% Exclamation \! Double quote \" Hash (number) \# +%% Dollar \$ Percent \% Ampersand \& +%% Acute accent \' Left paren \( Right paren \) +%% Asterisk \* Plus \+ Comma \, +%% Minus \- Point \. Solidus \/ +%% Colon \: Semicolon \; Less than \< +%% Equals \= Greater than \> Question mark \? +%% Commercial at \@ Left bracket \[ Backslash \\ +%% Right bracket \] Circumflex \^ Underscore \_ +%% Grave accent \` Left brace \{ Vertical bar \| +%% Right brace \} Tilde \~} +%% +\def\normalsize{\@setfontsize\normalsize\@xxvpt{32}% +\abovedisplayskip 30\p@ \@plus 3\p@ \@minus 9\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 3\p@ +\belowdisplayshortskip 7\p@ \@plus 3\p@ \@minus 4\p@ +\let\@listi\@listIa} +\normalsize + +\def\small{\@setfontsize\small\@xxpt\@xxvpt +\abovedisplayskip 30\p@ \@plus 3\p@ \@minus 9\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 3\p@ +\belowdisplayshortskip 7\p@ \@plus 3\p@ \@minus 4\p@ +\if@compatibility + \let\@listi\@listIb +\else + \let\@listi\@listIa\fi +} + +\def\footnotesize{\@setfontsize\footnotesize\@xviipt{22}% +\abovedisplayskip 20\p@ \@plus 3\p@ \@minus 4\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 2\p@ +\belowdisplayshortskip 4\p@ \@plus 2\p@ \@minus 2\p@ +\let\@listi\@listIb} + +\def\scriptsize{\@setfontsize\scriptsize\@xivpt{18}% + \if@compatibility\else\let\@listi\@listIc\fi} +\def\tiny{\@setfontsize\tiny\@xiipt{15}% + \if@compatibility\else\let\@listi\@listId\fi} +\def\large{\@setfontsize\large\@xxxpt{38}} +\def\Large{\@setfontsize\Large\@xxxvipt{45}} +\def\LARGE{\@setfontsize\LARGE\@xliiipt{54}} +\if@compatibility + \let\huge=\LARGE + \let\Huge=\LARGE +\else + \def\huge{\@setfontsize\huge\@lipt{62}} + \let\Huge=\huge +\fi + +\def\big#1{{\hbox{$\left#1\vbox to21\p@{}\right.\n@space$}}} +\def\Big#1{{\hbox{$\left#1\vbox to29\p@{}\right.\n@space$}}} +\def\bigg#1{{\hbox{$\left#1\vbox to36\p@{}\right.\n@space$}}} +\def\Bigg#1{{\hbox{$\left#1\vbox to44\p@{}\right.\n@space$}}} +\endinput +%% +%% End of file `foil25.clo'. diff --git a/doc/presentations/tex/foil30.clo b/doc/presentations/tex/foil30.clo new file mode 100644 index 0000000..7c70876 --- /dev/null +++ b/doc/presentations/tex/foil30.clo @@ -0,0 +1,104 @@ +%% +%% This is file `foil30.clo', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% foiltex.dtx (with options: `30pt') +%% ******************************************************************** +%% Copyright (C) 1995,1997,1998,2002,2008 IBM Corporation +%% This file is part of the FoilTeX package. Use of this is governed +%% by explicit restrictions. These can be found in the header of the +%% foiltex.ins file. +%% +%% Questions, comments or suggestions concerning this program can be +%% sent to +%% James (Jim) Hafner +%% IBM Research Division +%% Almaden Research Center, K56-B2 +%% 650 Harry Road +%% San Jose, CA 95120-6099 +%% email: hafner@almaden.ibm.com +%% ******************************************************************** +%% +%% These files are updated versions of the FoilTeX package for use with +%% the new LaTeX2e. There are many enhancements and a few bugs +%% have been fixed. Undoubtedly there are many more. Contact +%% the author if you find any bugs or have suggestions for improvement +%% of this suite of files. +%% ******************************************************************** +\def\foiltexdate{2008/01/28} +\def\foiltexversion{2.1.4b} +\NeedsTeXFormat{LaTeX2e} +\ProvidesFile{foil30.clo} + [\foiltexdate\space v\foiltexversion\space + FoilTeX file (size option), Copyright IBM 1995,1997,1998,2002,2008] +%% \CharacterTable +%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +%% Digits \0\1\2\3\4\5\6\7\8\9 +%% Exclamation \! Double quote \" Hash (number) \# +%% Dollar \$ Percent \% Ampersand \& +%% Acute accent \' Left paren \( Right paren \) +%% Asterisk \* Plus \+ Comma \, +%% Minus \- Point \. Solidus \/ +%% Colon \: Semicolon \; Less than \< +%% Equals \= Greater than \> Question mark \? +%% Commercial at \@ Left bracket \[ Backslash \\ +%% Right bracket \] Circumflex \^ Underscore \_ +%% Grave accent \` Left brace \{ Vertical bar \| +%% Right brace \} Tilde \~} +%% +\def\normalsize{\@setfontsize\normalsize\@xxxpt{38}% +\abovedisplayskip 30\p@ \@plus 3\p@ \@minus 9\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 3\p@ +\belowdisplayshortskip 7\p@ \@plus 3\p@ \@minus 4\p@ +\let\@listi\@listIa} +\normalsize + +\def\small{\@setfontsize\small\@xxvpt{32}% +\abovedisplayskip 30\p@ \@plus 3\p@ \@minus 9\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 3\p@ +\belowdisplayshortskip 7\p@ \@plus 3\p@ \@minus 4\p@ +\if@compatibility + \let\@listi\@listIb +\else + \let\@listi\@listIa\fi +} + +\def\footnotesize{\@setfontsize\footnotesize\@xxpt\@xxvpt +\abovedisplayskip 30\p@ \@plus 3\p@ \@minus 9\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 3\p@ +\belowdisplayshortskip 7\p@ \@plus 3\p@ \@minus 4\p@ +\if@compatibility + \let\@listi\@listIb +\else + \let\@listi\@listIa\fi +} + +\def\scriptsize{\@setfontsize\scriptsize\@xviipt{22}% + \if@compatibility\else\let\@listi\@listIb\fi} +\def\tiny{\@setfontsize\tiny\@xivpt{18}% + \if@compatibility\else\let\@listi\@listIc\fi} +\def\large{\@setfontsize\large\@xxxvipt{45}} +\def\Large{\@setfontsize\Large\@xliiipt{54}} +\if@compatibility + \let\LARGE=\Large + \let\huge=\Large + \let\Huge=\Large +\else + \def\LARGE{\@setfontsize\LARGE\@lipt{62}} + \let\huge=\LARGE + \let\Huge=\LARGE +\fi + +\def\big#1{{\hbox{$\left#1\vbox to25.5\p@{}\right.\n@space$}}} +\def\Big#1{{\hbox{$\left#1\vbox to34.5\p@{}\right.\n@space$}}} +\def\bigg#1{{\hbox{$\left#1\vbox to43.5\p@{}\right.\n@space$}}} +\def\Bigg#1{{\hbox{$\left#1\vbox to52.5\p@{}\right.\n@space$}}} +\endinput +%% +%% End of file `foil30.clo'. diff --git a/doc/presentations/tex/foils.cls b/doc/presentations/tex/foils.cls new file mode 100644 index 0000000..0ae24fa --- /dev/null +++ b/doc/presentations/tex/foils.cls @@ -0,0 +1,837 @@ +%% +%% This is file `foils.cls', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% foiltex.dtx (with options: `package') +%% ******************************************************************** +%% Copyright (C) 1995,1997,1998,2002,2008 IBM Corporation +%% This file is part of the FoilTeX package. Use of this is governed +%% by explicit restrictions. These can be found in the header of the +%% foiltex.ins file. +%% +%% Questions, comments or suggestions concerning this program can be +%% sent to +%% James (Jim) Hafner +%% IBM Research Division +%% Almaden Research Center, K56-B2 +%% 650 Harry Road +%% San Jose, CA 95120-6099 +%% email: hafner@almaden.ibm.com +%% ******************************************************************** +%% +%% These files are updated versions of the FoilTeX package for use with +%% the new LaTeX2e. There are many enhancements and a few bugs +%% have been fixed. Undoubtedly there are many more. Contact +%% the author if you find any bugs or have suggestions for improvement +%% of this suite of files. +%% ******************************************************************** +\def\foiltexdate{2008/01/28} +\def\foiltexversion{2.1.4b} +\NeedsTeXFormat{LaTeX2e}[1996/12/01] +\ProvidesClass{foils} + [\foiltexdate\space v\foiltexversion\space + FoilTeX Class File, Copyright IBM 1995,1997,1998,2002,2008] +%% \CharacterTable +%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +%% Digits \0\1\2\3\4\5\6\7\8\9 +%% Exclamation \! Double quote \" Hash (number) \# +%% Dollar \$ Percent \% Ampersand \& +%% Acute accent \' Left paren \( Right paren \) +%% Asterisk \* Plus \+ Comma \, +%% Minus \- Point \. Solidus \/ +%% Colon \: Semicolon \; Less than \< +%% Equals \= Greater than \> Question mark \? +%% Commercial at \@ Left bracket \[ Backslash \\ +%% Right bracket \] Circumflex \^ Underscore \_ +%% Grave accent \` Left brace \{ Vertical bar \| +%% Right brace \} Tilde \~} +%% +\newif\if@openbib \@openbibfalse +\newif\if@landscape \@landscapefalse +\newif\if@dvips \@dvipsfalse +\newif\if@dvipsone \@dvipsonefalse +\newif\if@vtex \@vtexfalse +\ifx\VTeXversion\undefined\else\@vtextrue\fi % autodetect +\newif\if@header@rule \@header@rulefalse +\newif\if@footer@rule \@footer@rulefalse +\newif\if@pdftex \@pdftexfalse +\newif\if@magscaleECfonts \@magscaleECfontsfalse +\newif\if@useDCfonts \@useDCfontsfalse +\DeclareOption{a4paper} + {\setlength\paperheight {297mm}% + \setlength\paperwidth {210mm}} +\DeclareOption{letterpaper} + {\setlength\paperheight {11in}% + \setlength\paperwidth {8.5in}} +\DeclareOption{ledgerpaper} + {\setlength\paperheight {11in}% + \setlength\paperwidth {7.33in}} +\DeclareOption{legalpaper} + {\setlength\paperheight {14in}% + \setlength\paperwidth {8.5in}} +\DeclareOption{a3paper} + {\setlength\paperheight {420mm}% + \setlength\paperwidth {297mm}} +\DeclareOption{a2paper} + {\setlength\paperheight {594mm}% + \setlength\paperwidth {420mm}} +\DeclareOption{a1paper} + {\setlength\paperheight {840mm}% + \setlength\paperwidth {594mm}} +\DeclareOption{35mmSlide} + {\setlength\paperheight {11in}% + \setlength\paperwidth {7.33in}} +\DeclareOption{Screen4to3} + {\setlength\paperwidth {297mm}% + \setlength\paperheight {0.75\paperwidth}} +\DeclareOption{Screen16to9} + {\setlength\paperwidth {297mm}% + \setlength\paperheight {0.5625\paperwidth}} +\DeclareOption{landscape} + {\setlength\@tempdima {\paperheight}% + \setlength\paperheight {\paperwidth}% + \setlength\paperwidth {\@tempdima}% + \@landscapetrue} +\newcommand\@ptsize{} +\DeclareOption{shortform}{\renewcommand\@ptsize{shrt}} +\DeclareOption{17pt}{\renewcommand\@ptsize{17}} +\DeclareOption{20pt}{\renewcommand\@ptsize{20}} +\DeclareOption{25pt}{\renewcommand\@ptsize{25}} +\DeclareOption{30pt}{\renewcommand\@ptsize{30}} +\DeclareOption{leqno}{\input{leqno.clo}} +\DeclareOption{fleqn}{\input{fleqn.clo}} +\DeclareOption{draft}{\setlength\overfullrule{5pt}} +\DeclareOption{final}{\setlength\overfullrule{0pt}} +\DeclareOption{openbib}{\@openbibtrue} +\DeclareOption{headrule}{\@header@ruletrue} +\DeclareOption{footrule}{\@footer@ruletrue} +\DeclareOption{dvips}{\@dvipstrue} +\DeclareOption{dvipsone}{\@dvipsonetrue} +\DeclareOption{vtex}{\@vtextrue} +\DeclareOption{magscalefonts}{\@magscaleECfontstrue} +\DeclareOption{useDCfonts}{\@useDCfontstrue} +\DeclareOption{a5paper}{% + \ClassWarningNoLine{FoilTeX}{No 'a5paper' option for foils.}} +\DeclareOption{b5paper}{% + \ClassWarningNoLine{FoilTeX}{No 'b5paper' option for foils.}} +\DeclareOption{executivepaper}{% + \ClassWarningNoLine{FoilTeX}{No 'executivepaper' option for foils.}} +\DeclareOption{10pt}{% + \ClassWarningNoLine{FoilTeX}{No '10pt' foils option, try shortform, + 17pt, 20pt, 25pt or 30pt (defaulting to 20pt).}} +\DeclareOption{11pt}{% + \ClassWarningNoLine{FoilTeX}{No '11pt' foils option, try shortform, + 17pt, 20pt, 25pt or 30pt (defaulting to 20pt).}} +\DeclareOption{12pt}{% + \ClassWarningNoLine{FoilTeX}{No '12pt' foils option, try shortform, + 17pt, 20pt, 25pt or 30pt (defaulting to 20pt).}} +\DeclareOption{oneside}{} +\DeclareOption{twoside}{% + \ClassWarningNoLine{FoilTeX}{No 'twoside' option for foils.}} +\DeclareOption{openright}{% + \ClassWarningNoLine{FoilTeX}{No 'openright' option for foils.}} +\DeclareOption{openany}{% + \ClassWarningNoLine{FoilTeX}{No 'openany' option for foils.}} +\DeclareOption{titlepage}{} +\DeclareOption{notitlepage}{% + \ClassWarningNoLine{FoilTeX}{No 'notitlepage' option for foils.}} +\DeclareOption{onecolumn}{} +\DeclareOption{twocolumn}{% + \ClassWarningNoLine{FoilTeX}{No 'twocolumn' layout for foils.}} +\ExecuteOptions{letterpaper,20pt,final} +\InputIfFileExists{foiltex.cfg}{}{} +\ProcessOptions +\ifx\pdfoutput\undefined +\else + \ifx\pdfoutput\relax + \else + \ifcase\pdfoutput + \else + \@pdftextrue + \fi + \fi +\fi +\if@vtex + \if@dvips + \ClassWarningNoLine{FoilTeX}{% + Option 'dvips' is ignored when running vtex} + \fi + \if@dvipsone + \ClassWarningNoLine{FoilTeX}{% + Option 'dvipsone' is ignored when running vtex} + \fi + \@dvipsfalse\@dvipsonefalse +\fi +\if@pdftex + \if@dvips + \ClassWarningNoLine{FoilTeX}{% + Option 'dvips' is ignored when running pdflatex} + \fi + \if@dvipsone + \ClassWarningNoLine{FoilTeX}{% + Option 'dvipsone' is ignored when running pdflatex} + \fi + \@dvipsfalse\@dvipsonefalse +\fi +\if@landscape + \if@dvips\AtBeginDvi{\special{! /landplus90 true store}}\else% + \if@dvipsone{\special{landscape}}% + \else\if@vtex\ifnum\OpMode=2{% + \immediate\special{landscape}% + \AtBeginDocument{\mediaheight=\paperwidth\mediawidth=\paperheight}% + }\fi + \fi\fi\fi +\fi +\InputIfFileExists{fltfonts.cfg}{}{\input{fltfonts.def}} +\input{foil\@ptsize.clo} +\DeclareRobustCommand\FoilTeX{{\normalfont% + {\sffamily Foil}\kern-.03em{\rmfamily\TeX}}} +\renewcommand\_{\leavevmode\kern.06em\vbox{\hrule width.4em height.12ex}} +\renewcommand\footnoterule{\kern-3\p@\hrule width.4\textwidth\kern2.6\p@} +\newcommand\@makefntext[1]{\parindent 1em\noindent + \hbox to 1.8em{\hss\@makefnmark}#1} +\def\@makefnmark{\hbox{$^{\mathsf{\@thefnmark}}\m@th$}} +\let\@oldmarginpar\marginpar +\def\@marginragged{\if@reversemargin\raggedleft\else\raggedright\fi} +\def\@foilmarginpar{\@ifnextchar[{\@foilmarginparRL}{\@foilmarginparR}} +\def\@foilmarginparRL[#1]#2{% + \@oldmarginpar[{\@marginragged #1\par}]{{\@marginragged #2\par}}} +\def\@foilmarginparR#1{\@oldmarginpar{\@marginragged #1\par}} +\let\marginpar\@foilmarginpar +\def\Black#1{#1} +\def\globalColor#1{#1} +\newcommand\today{\ifcase\month\or + January\or February\or March\or April\or May\or June\or + July\or August\or September\or October\or November\or December\fi + \space\number\day, \number\year} +\setlength\lineskip{1\p@} +\setlength\normallineskip{1\p@} +\renewcommand\baselinestretch{1} +\setlength\parskip{18\p@ \@plus 4\p@ \@minus 4\p@} +\if@compatibility + \setlength\parindent{15\p@} + \setlength\jot{3\p@} +\else + \setlength\parindent{30\p@} + \setlength\jot{10\p@} +\fi +\righthyphenmin=100 +\lefthyphenmin=100 +\def\@eqnnum{\hbox{\reset@font\sffamily (\theequation)}} +\renewcommand\theequation{\arabic{equation}} +\if@compatibility + \newenvironment{titlepage}{% + \@restonecolfalse \newpage \thispagestyle{empty} + \setcounter{page}{0}}{\newpage} + \newcommand\@pnumwidth{1.55em} + \newcommand\@tocrmarg {2.55em} + \newcommand\@dotsep{4.5} +\fi +\def\sloppyfoils{\tolerance 9000 \hfuzz 2\p@ \vfuzz 2\p@ \hbadness 2000} +\sloppyfoils +\setlength\leftmargini{25\p@} +\setlength\leftmarginii{22\p@} +\setlength\leftmarginiii{18.7\p@} +\setlength\leftmarginiv{17\p@} +\setlength\leftmarginv{10\p@} +\setlength\leftmarginvi{10\p@} +\setlength\leftmargin\leftmargini +\if@compatibility + \setlength\labelwidth\leftmargini\advance\labelwidth-\labelsep + \setlength\labelsep{.5em} +\else + \setlength\labelsep{10\p@} + \setlength\labelwidth\leftmargini\advance\labelwidth-\labelsep +\fi +\setlength\partopsep{2\p@ \@plus 1\p@ \@minus 1\p@} +\newcommand\@listIa{\leftmargin\leftmargini +\topsep 14\p@ \@plus 2\p@ \@minus 4\p@ +\parsep 14\p@ \@plus 4\p@ \@minus 4\p@ +\itemsep 14\p@ \@plus 4\p@ \@minus 2\p@} +\newcommand\@listIb{\leftmargin\leftmargini +\topsep 8\p@ \@plus 2\p@ \@minus 2\p@ +\parsep 2\p@ \@plus 1\p@ \@minus 1\p@ +\itemsep \parsep} +\newcommand\@listIc{\leftmargin\leftmargini +\topsep 6\p@ \@plus 1\p@ \@minus 1\p@ +\parsep 2\p@ \@plus 1\p@ \@minus 1\p@ +\itemsep \parsep} +\newcommand\@listId{\leftmargin\leftmargini +\topsep 4\p@ \@plus 1\p@ \@minus 1\p@ +\parsep 2\p@ \@plus 1\p@ \@minus 1\p@ +\itemsep \parsep} +\newcommand\@listii{\leftmargin\leftmarginii + \labelwidth\leftmarginii\advance\labelwidth-\labelsep + \topsep \z@ + \parsep \z@ + \itemsep \parsep} +\newcommand\@listiii{\leftmargin\leftmarginiii + \labelwidth\leftmarginiii\advance\labelwidth-\labelsep + \partopsep 1\p@ \@plus 0\p@ \@minus 1\p@ + \topsep \z@ + \parsep \z@ + \itemsep \topsep} +\newcommand\@listiv{\leftmargin\leftmarginiv + \labelwidth\leftmarginiv\advance\labelwidth-\labelsep} +\newcommand\@listv{\leftmargin\leftmarginv + \labelwidth\leftmarginv\advance\labelwidth-\labelsep} +\newcommand\@listvi{\leftmargin\leftmarginvi + \labelwidth\leftmarginvi\advance\labelwidth-\labelsep} +\normalsize +\if@compatibility + \let\zerolistvertdimens\relax +\else + \def\zerolistvertdimens{\parskip0pt\topsep0pt\partopsep0pt% + \parsep0pt\itemsep0pt} +\fi +\if@compatibility + \def\@item[#1]{% + \if@noparitem + \@donoparitem + \else + \if@inlabel \indent \par \fi + \ifhmode \unskip\unskip \par \fi + \if@newlist + \if@nobreak + \@nbitem + \else + \addpenalty\@beginparpenalty + \addvspace\@topsep \addvspace{-\parskip} + \fi + \else + \addpenalty\@itempenalty \addvspace\itemsep + \fi + \global\@inlabeltrue + \fi + \everypar{\global\@minipagefalse\global\@newlistfalse + \if@inlabel + \global\@inlabelfalse \hskip -\parindent \box\@labels \penalty\z@ + \fi + \everypar{}} + \global\@nobreakfalse + \if@noitemarg \@noitemargfalse + \if@nmbrlist \refstepcounter{\@listctr}\fi + \fi + \sbox\@tempboxa{\globalColor{\makelabel{#1}}} \global\setbox\@labels + \hbox{\unhbox\@labels \hskip \itemindent + \hskip -\labelwidth \hskip -\labelsep + \ifdim + \wd\@tempboxa >\labelwidth \box\@tempboxa + \else + \hbox to\labelwidth {\unhbox\@tempboxa} + \fi + \hskip \labelsep} + \ignorespaces + } +\fi +\renewcommand\theenumi{\arabic{enumi}} +\renewcommand\theenumii{\alph{enumii}} +\renewcommand\theenumiii{\roman{enumiii}} +\renewcommand\theenumiv{\Alph{enumiv}} +\renewcommand\p@enumii{\theenumi} +\renewcommand\p@enumiii{\theenumi(\theenumii)} +\renewcommand\p@enumiv{\p@enumiii\theenumiii} +\newcommand\labelenumi{\theenumi.} +\newcommand\labelenumii{(\theenumii)} +\newcommand\labelenumiii{\theenumiii.} +\newcommand\labelenumiv{\theenumiv.} +\newcommand\labelitemi{$\m@th\bullet$} +\newcommand\labelitemii{{\normalfont\bfseries --}} +\newcommand\labelitemiii{$\m@th\ast$} +\newcommand\labelitemiv{$\m@th\cdot$} +\newcommand\descriptionlabel[1]{\hspace\labelsep \normalfont\bfseries #1} +\newenvironment{description}{\list{}{\labelwidth\z@ + \itemindent-\leftmargin \let\makelabel\descriptionlabel}}{\endlist} +\newenvironment{verse}{\let\\=\@centercr + \list{}{\itemsep\z@ \itemindent -1.5em\listparindent \itemindent + \rightmargin\leftmargin\advance\leftmargin 1.5em}\item[]}{\endlist} +\newenvironment{quotation}{\list{}{\listparindent 1.5em + \itemindent\listparindent + \rightmargin\leftmargin \parsep 0\p@ \@plus 1\p@}\item[]}{\endlist} +\newenvironment{quote}{\list{}{\rightmargin\leftmargin}\item[]}{\endlist} +\setlength\oddsidemargin{0\p@} +\setlength\evensidemargin{0\p@} +\setlength\topmargin{0\p@} +\setlength\headsep{14\p@} +\setlength\headheight{15\p@} +\if@compatibility + \setlength\footheight{25\p@} + \setlength\footskip{45\p@} + \setlength\@maxsep{20\p@} + \setlength\@dblmaxsep{20\p@} +\else + \newdimen\head@footskip + \setlength\head@footskip{1in} + \setlength\footskip{\head@footskip} + \addtolength\footskip{-\headsep} + \addtolength\footskip{-\headheight} +\fi +\setlength\footnotesep{10\p@} +\setlength{\skip\footins}{9\p@ \@plus 4\p@ \@minus 2\p@} +\skip\@mpfootins = \skip\footins +\setlength\marginparwidth{54\p@} +\setlength\marginparsep{10\p@} +\setlength\marginparpush{5\p@} +\if@compatibility + \setlength\textheight{7.6in} + \setlength\textwidth{6.5in} + \let\@rotdimens\relax + \let\@defaultdimens\relax +\else + \newdimen\@foilheight + \newdimen\@foilwidth + \setlength\textheight{\paperheight} + \addtolength\textheight{-2in} + \addtolength\textheight{-\head@footskip} + \setlength\textwidth{\paperwidth} + \addtolength\textwidth{-2in} + \def\setp@gelayoutdimens{% + \setlength\head@footskip{\footskip} + \addtolength\head@footskip{\headsep} + \addtolength\head@footskip{\headheight} + \setlength\@foilheight{\textheight} + \addtolength\@foilheight{\head@footskip} + \setlength\@foilwidth{\textwidth} + \def\@rotdimens{\textheight\@foilwidth \textwidth\@foilheight + \addtolength\textheight{-\head@footskip} + \vsize\textheight \hsize\textwidth \linewidth\textwidth + \columnwidth\textwidth \@colroom\textheight \@colht\textheight} + \def\@rotdimens@pdf{% + \setlength{\pdfpagewidth}{\strip@pt\paperheight truept}% + \setlength{\pdfpageheight}{\strip@pt\paperwidth truept}} + \def\@defaultdimens{\textheight\@foilheight \textwidth\@foilwidth + \addtolength\textheight{-\head@footskip} + \vsize\textheight \hsize\textwidth \linewidth\textwidth + \columnwidth\textwidth \@colroom\textheight \@colht\textheight} + \def\@defaultdimens@pdf{% + \setlength{\pdfpagewidth}{\strip@pt\paperwidth truept}% + \setlength{\pdfpageheight}{\strip@pt\paperheight truept}} + \@defaultdimens + \if@pdftex\@defaultdimens@pdf\fi} % end of \def\setp@gelayoutdimens +\fi +\AtBeginDocument{\if@compatibility\else\setp@gelayoutdimens\fi + \if@dvips + \AtBeginDvi{\special{% + papersize=\the\paperwidth,\the\paperheight}}% + \fi + \@ifpackageloaded{hyperref}{% + \@ifpackagelater{hyperref}{2007/10/29}{}{% + \def\@begindvi{\foil@begindvi \unvbox \@begindvibox + \ifHy@pageanchor \@hyperfixhead + \gdef\@begindvi{\foil@begindvi\@hyperfixhead}% + \else + \gdef\@begindvi{\foil@begindvi\HyPL@EveryPage}% + \fi}}}{}% +} +\@lowpenalty 51 +\@medpenalty 151 +\@highpenalty 301 +\@beginparpenalty -\@lowpenalty +\@endparpenalty -\@lowpenalty +\@itempenalty -\@lowpenalty +\setlength\arraycolsep{10\p@} +\setlength\tabcolsep{12\p@} +\setlength\arrayrulewidth{1\p@} +\setlength\doublerulesep{3\p@} +\setlength\tabbingsep\labelsep +\if@compatibility + \setlength\fboxsep{6\p@} +\else + \setlength\fboxsep{10\p@} +\fi +\setlength\fboxrule{1\p@} +\newlength\abovetitleskip +\newlength\titleauthorskip +\newlength\authorauthorskip +\newlength\authordateskip +\newlength\dateabstractskip +\setlength\abovetitleskip{2em} +\setlength\titleauthorskip{1.5em} +\setlength\authorauthorskip{.5em} +\setlength\authordateskip{1em} +\setlength\dateabstractskip{1em} +\def\maketitle{\par + \begingroup + \setcounter{page}{0} + \def\thefootnote{\fnsymbol{footnote}} \newpage + \@maketitle + \thispagestyle{foilheadings} + \@thanks + \endgroup + \setcounter{footnote}{0} + \let\maketitle\relax \let\@maketitle\relax + \gdef\@thanks{}\gdef\@author{}\gdef\@title{}\let\thanks\relax% +} +\def\@maketitle{\newpage + \zerolistvertdimens + \if@compatibility\else + \advance\abovetitleskip -\baselineskip % \null adds this space + \fi + \null\vskip\abovetitleskip + \begin{center} + {\Large\bfseries \@title \par} + \vskip\titleauthorskip + {\lineskip \authorauthorskip + \begin{tabular}[t]{c}\@author\end{tabular} + \par} + \vskip\authordateskip {\@date} + \end{center} + \par\vfil +} +\newcommand\abstractname{Abstract} +\newenvironment{abstract}{% + \if@compatibility + \dateabstractskip\parskip + \advance\dateabstractskip\topsep + \advance\dateabstractskip\baselineskip + \fi + \vskip\dateabstractskip + \centerline{\reset@font\bfseries\abstractname} + \if@compatibility\vspace{-.5em}\vspace{0\p@}\fi + \list{}{\listparindent 1.5em + \itemindent\listparindent \rightmargin\leftmargin + \zerolistvertdimens + }\item[]% + }{\endlist\vfill} +\newcommand\leftheader[1]{\gdef\@leftheader{#1}} +\newcommand\rightheader[1]{\gdef\@rightheader{#1}} +\newcommand\rightfooter[1]{\gdef\@rightfooter{#1}} +\leftheader{} +\rightheader{} +\rightfooter{\quad\textsf{\thepage}} +\newif\ifLogo \Logotrue +\newcommand\LogoOff{\Logofalse} +\newcommand\LogoOn{\Logotrue} +\newcommand\Restriction[1]{\gdef\@Restriction{#1}} +\Restriction{} +\newcommand\MyLogo[1]{\gdef\@MyLogo{\ifLogo{#1}\else\fi}} +\MyLogo{-- Typeset by \FoilTeX\ --} +\newsavebox\@tempfootbox +\newdimen\@tempfootht +\newcommand\ps@foilheadings{\let\@mkboth\@gobbletwo + \def\@oddhead{% + \ifnum \c@page>0 + {\Black{% + \if@header@rule\hbox to\z@{\rule[-5\p@]{\textwidth}{1\p@}\hss}\fi + \reset@font\tiny + \@leftheader\hfil\@rightheader}}% + \else + \hfill + \fi}% + \def\@oddfoot{% + \ifnum \c@page>0 + {\Black{% + \sbox\@tempfootbox{\tiny\@MyLogo\ \@Restriction\hfil\@rightfooter}% + \@tempfootht\ht\@tempfootbox + \advance\@tempfootht 5.66666\p@ + \if@footer@rule% + \hbox to\z@{\rule[\@tempfootht]{\textwidth}{1\p@}\hss}% + \fi% + \reset@font\tiny + \@MyLogo\ \@Restriction\hfil\@rightfooter}}% + \else + {\Black{\hfil\reset@font\footnotesize% + \@MyLogo\ \@Restriction\hfil}}% + \fi}% + \let\@evenhead\@oddhead% + \let\@evenfoot\@oddfoot% +} +\ps@foilheadings +\pagenumbering{arabic} +\onecolumn +\mark{{}{}} +\newcommand\refname{References} +\newdimen\bibindent +\setlength\bibindent{1.5em} +\newcommand\newblock{} +\newenvironment{thebibliography}[1]{ + \vskip 3.5ex \@plus -1ex \@minus -.2ex + \noindent{\large\bfseries\refname} + \vskip 2.3ex \@plus .2ex + \list{\@biblabel{\arabic{enumiv}}}% + {\settowidth\labelwidth{\@biblabel{#1}}% + \leftmargin\labelwidth + \advance\leftmargin\labelsep + \if@openbib + \advance\leftmargin\bibindent + \itemindent -\bibindent + \listparindent \itemindent + \parsep \z@ + \fi + \usecounter{enumiv}% + \let\p@enumiv\@empty + \renewcommand\theenumiv{\arabic{enumiv}}} + \if@openbib + \renewcommand\newblock{\par} + \else + \renewcommand\newblock{\hskip .11em \@plus .33em \@minus -.07em} + \fi + \sloppy\clubpenalty4000\widowpenalty4000% + \sfcode`\.=\@m\relax}% + {\def\@noitemerr{\@latex@warning{Empty `thebibliography' environment}}% + \endlist} +\newlength\abovecaptionskip +\newlength\belowcaptionskip +\newlength\@captionwidth +\newlength\captionwidth +\newcommand\captionfraction{1.0} +\newlength\abovefloatskip +\setlength\abovecaptionskip{15\p@ \@plus 5\p@ \@minus 5\p@} +\setlength\belowcaptionskip{0\p@} +\setlength\captionwidth\z@ +\setlength\abovefloatskip{20\p@ \@plus 5\p@ \@minus 10\p@} +\providecommand*\ext@table{lot}% +\providecommand*\ext@figure{lof}% +\newif\if@starmode\@starmodefalse +\newcommand\@makecaption[2]{% + \ifdim\captionwidth>\z@ + \ifdim\captionwidth>\hsize + \setlength\@captionwidth\hsize + \else + \setlength\@captionwidth\captionwidth + \fi + \else + \setlength\@captionwidth{\captionfraction\hsize} + \fi + \vskip \abovecaptionskip + \if@starmode\sbox\@tempboxa{#2}\else\sbox\@tempboxa{#1: #2}\fi% + \ifdim \wd\@tempboxa >\@captionwidth + \centering\parbox[t]{\@captionwidth}{\unhbox\@tempboxa\par} + \else + \hbox to\hsize{\hfil\box\@tempboxa\hfil} + \fi + \vskip\belowcaptionskip} +\long\def\@caption#1[#2]#3{\par \begingroup \@parboxrestore \normalsize + \@makecaption{\csname fnum@#1\endcsname}{\ignorespaces #3}\par + \endgroup} +\def\caption{\if@starmode\else\refstepcounter\@captype\fi% + \@dblarg{\@caption\@captype}} +\def\@xfloat#1[#2]{% + \def \@captype {#1}% + \ifhmode \@bsphack \fi \vskip\abovefloatskip + \vbox\bgroup \color@begingroup \normalcolor + \hsize\columnwidth \@parboxrestore + \if@nobreak + \def\outer@nobreak{\global\@nobreaktrue}\global\@nobreakfalse + \fi} +\def\end@nonfloat{\par\vskip\z@skip + \color@endgroup + \outer@nobreak + \egroup} +\newcommand\newnonfloat[2]{% + \expandafter\newcommand\csname#1name\endcsname{#2}% + \expandafter\newcounter{#1}% + \expandafter\renewcommand\csname the#1\endcsname + {\@arabic\csname c@#1\endcsname}% + \expandafter\newcommand\csname fnum@#1\endcsname + {\csname#1name\endcsname~\csname the#1\endcsname}% + \expandafter\newenvironment{#1}{\@float{#1}}{\end@nonfloat}% + \expandafter\newenvironment{#1*}{\@float{#1}\@starmodetrue}% + {\end@nonfloat}% +} +\newnonfloat{table}{Table} +\newnonfloat{figure}{Figure} +\newlength\foilheadskip +\if@compatibility + \setlength\foilheadskip{.25in} +\else + \setlength\foilheadskip{18\p@ \@plus 0\p@ \@minus 18\p@} +\fi +\if@dvips +\def\foil@rot@start{\special{ps: + 0 \strip@pt\@foilheight\space 72.27 div Resolution mul translate + 90 neg rotate}}% +\else\if@dvipsone +\def\foil@rot@start{\special{ps: + 0 \strip@pt\@foilheight\space 72.27 mul 65536 mul rmoveto + 90 rotate}}% +\else\if@vtex +\def\foil@rot@start{\special{pS: + \strip@pt\@foilwidth\space 72.27 div 72 mul 0 translate + 90 neg rotate}}% +\else\if@pdftex +\def\foil@rot@start{}% +\fi\fi\fi\fi +\newif\ifcur@rot@state +\newif\ifnew@rot@state +\newcommand\foilhead{\new@rot@statefalse% set state for the page + \@ifnextchar[{\@foilhead}{\@foilhead[0\p@]}} +\if@compatibility +\else + \newcommand\rotatefoilhead{% + \if@dvips + \new@rot@statetrue + \else\if@dvipsone + \new@rot@statetrue + \else\if@vtex + \new@rot@statetrue + \else\if@pdftex + \new@rot@statetrue + \else + \ClassWarningNoLine{FoilTex}{% + Without the 'dvips', 'dvipsone' or 'vtex' option \MessageBreak + (when running LaTeX), rotation is not supported} + \new@rot@statefalse + \fi\fi\fi\fi + \@ifnextchar[{\@foilhead}{\@foilhead[0\p@]}} +\fi +\def\@foilhead[#1]#2{\vfill\eject + \ifnew@rot@state + \cur@rot@statetrue\@rotdimens + \if@pdftex\@rotdimens@pdf\fi % add this if pdftex + \else + \cur@rot@statefalse\@defaultdimens + \if@pdftex\@defaultdimens@pdf\fi % add this if pdftex + \fi + {\color@begingroup\normalcolor + \reset@font\large\bfseries\centering#2\par\null\color@endgroup}% + \advance\foilheadskip by #1 \vspace{\foilheadskip} + \advance\foilheadskip by -#1} + %\let\old@shipoutsetup\@shipoutsetup + %\def\@shipoutsetup{% + % \ifcur@rot@state\foil@rot@start\fi + % \old@shipoutsetup} +\CheckCommand*\@begindvi{% + \unvbox \@begindvibox + \global\let \@begindvi \@empty} + % the old definitions + %\def \@begindvi{% + % \ifcur@rot@state\foil@rot@start\fi + % \unvbox \@begindvibox + % %\global\let \@begindvi \@empty + % \gdef\@begindvi{\ifcur@rot@state\foil@rot@start\fi\@empty} + %} +\def\foil@begindvi{% + \ifcur@rot@state\foil@rot@start\fi +} +\def\@begindvi{% + \foil@begindvi + \unvbox \@begindvibox + \gdef\@begindvi{\foil@begindvi\@empty} +} + %\CheckCommand*\@outputpage{% + %\begingroup + % \set@typeset@protect + % \@shipoutsetup + % \let \protect \noexpand + % \shipout \vbox{% + % \set@typeset@protect + % \aftergroup\set@typeset@protect + % \@begindvi + % \vskip \topmargin + % \moveright\@themargin \vbox {% + % \setbox\@tempboxa \vbox to\headheight{% + % \vfil + % \color@hbox + % \normalcolor + % \hb@xt@\textwidth {% + % \let \label \@gobble + % \let \index \@gobble + % \let \glossary \@gobble %% 21 Jun 91 + % \@thehead + % }% + % \color@endbox + % }% %% 22 Feb 87 + % \dp\@tempboxa \z@ + % \box\@tempboxa + % \vskip \headsep + % \box\@outputbox + % \baselineskip \footskip + % \color@hbox + % \normalcolor + % \hb@xt@\textwidth{% + % \let \label \@gobble + % \let \index \@gobble %% 22 Feb 87 + % \let \glossary \@gobble %% 21 Jun 91 + % \@thefoot + % }% + % \color@endbox + % }% + % }% + % \endgroup + % \global \@colht \textheight + % \stepcounter{page}% + % \let\firstmark\botmark + %} + %\def\@outputpage{% + %\begingroup + % \set@typeset@protect + % \@shipoutsetup + % \let \protect \noexpand + % \shipout \vbox{% + % \set@typeset@protect + % \aftergroup\set@typeset@protect + % \@begindvi + % \ifcur@rot@state\foil@rot@start\fi %% added by for foils.cls + % \vskip \topmargin + % \moveright\@themargin \vbox {% + % \setbox\@tempboxa \vbox to\headheight{% + % \vfil + % \color@hbox + % \normalcolor + % \hb@xt@\textwidth {% + % \let \label \@gobble + % \let \index \@gobble + % \let \glossary \@gobble %% 21 Jun 91 + % \@thehead + % }% + % \color@endbox + % }% %% 22 Feb 87 + % \dp\@tempboxa \z@ + % \box\@tempboxa + % \vskip \headsep + % \box\@outputbox + % \baselineskip \footskip + % \color@hbox + % \normalcolor + % \hb@xt@\textwidth{% + % \let \label \@gobble + % \let \index \@gobble %% 22 Feb 87 + % \let \glossary \@gobble %% 21 Jun 91 + % \@thefoot + % }% + % \color@endbox + % }% + % }% + % \endgroup + % \global \@colht \textheight + % \stepcounter{page}% + % \let\firstmark\botmark + %} +\def\newtheorem#1{\@ifnextchar[{\@Othm{#1}}{\@Nthm{#1}}} +\def\@Othm#1[#2]#3{\@Sthm{#1}{#3}\@othm{#1}[#2]{#3}} +\def\@Nthm#1#2{\@Sthm{#1}{#2}\@nthm{#1}{#2}} +\def\@Sthm#1#2{{{\global\@namedef{#1*}{\@starthm{#2}} + \global\@namedef{end#1*}{\@endtheorem}}}} +\def\@starthm#1{\@ifnextchar[{\@ystarthm{#1}}{\@xstarthm{#1}}} +\def\@xstarthm#1{\@beginstartheorem{#1} \ignorespaces} +\def\@ystarthm#1[#2]{\@opargbeginstartheorem{#1}{#2}\ignorespaces} +\def\@begintheorem#1#2{\trivlist + \item[\hskip\labelsep{\bfseries #1\ #2. }]\slshape} +\def\@opargbegintheorem#1#2#3{\trivlist + \item[\hskip\labelsep{\bfseries #1\ #2.\ [#3] }]\slshape} +\def\@beginstartheorem#1{\trivlist + \item[\hskip\labelsep{\bfseries #1. }]\slshape} +\def\@opargbeginstartheorem#1#2{\trivlist + \item[\hskip\labelsep{\bfseries #1.\ [#2] }]\slshape} +\newtheorem{Theorem}{Theorem} +\newtheorem{Lemma}{Lemma} +\newtheorem{Corollary}{Corollary} +\newtheorem{Proposition}{Proposition} +\newtheorem{Definition}{Definition} +\newenvironment{Proof}{\begin{trivlist}\item[] {\bfseries Proof.}}{% + \ifhmode\nolinebreak[4]~$\ProofBox$\else$\ProofBox$\fi \end{trivlist}} +\DeclareMathSymbol\ProofBox{0}{flasy}{"32} +\def\bm#1{\mathpalette\bmstyle{#1}} +\def\bmstyle#1#2{\mbox{\boldmath$#1#2$}} +\@namedef{boldequation*}{\boldmath$$} +\@namedef{endboldequation*}{$$\global\@ignoretrue\unboldmath} +\def\boldequation{\boldmath$$\refstepcounter{equation}} +\def\endboldequation{\eqno\@eqnnum% + $$\global\@ignoretrue\unboldmath} +\endinput +%% +%% End of file `foils.cls'. diff --git a/doc/presentations/tex/foils.sty b/doc/presentations/tex/foils.sty new file mode 100644 index 0000000..4222f8f --- /dev/null +++ b/doc/presentations/tex/foils.sty @@ -0,0 +1,56 @@ +%% +%% This is file `foils.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% foiltex.dtx (with options: `oldstyle') +%% ******************************************************************** +%% Copyright (C) 1995,1997,1998,2002,2008 IBM Corporation +%% This file is part of the FoilTeX package. Use of this is governed +%% by explicit restrictions. These can be found in the header of the +%% foiltex.ins file. +%% +%% Questions, comments or suggestions concerning this program can be +%% sent to +%% James (Jim) Hafner +%% IBM Research Division +%% Almaden Research Center, K56-B2 +%% 650 Harry Road +%% San Jose, CA 95120-6099 +%% email: hafner@almaden.ibm.com +%% ******************************************************************** +%% +%% These files are updated versions of the FoilTeX package for use with +%% the new LaTeX2e. There are many enhancements and a few bugs +%% have been fixed. Undoubtedly there are many more. Contact +%% the author if you find any bugs or have suggestions for improvement +%% of this suite of files. +%% ******************************************************************** +\def\foiltexdate{2008/01/28} +\def\foiltexversion{2.1.4b} +\NeedsTeXFormat{LaTeX2e} +\ProvidesFile{foils.sty} + [\foiltexdate\space v\foiltexversion\space + FoilTeX Compatibility Style File, Copyright IBM 1995,1997,1998,2002,2008] +%% \CharacterTable +%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +%% Digits \0\1\2\3\4\5\6\7\8\9 +%% Exclamation \! Double quote \" Hash (number) \# +%% Dollar \$ Percent \% Ampersand \& +%% Acute accent \' Left paren \( Right paren \) +%% Asterisk \* Plus \+ Comma \, +%% Minus \- Point \. Solidus \/ +%% Colon \: Semicolon \; Less than \< +%% Equals \= Greater than \> Question mark \? +%% Commercial at \@ Left bracket \[ Backslash \\ +%% Right bracket \] Circumflex \^ Underscore \_ +%% Grave accent \` Left brace \{ Vertical bar \| +%% Right brace \} Tilde \~} +%% +\@obsoletefile{foils.cls}{foils.sty} +\LoadClass{foils} +\endinput +%% +%% End of file `foils.sty'. diff --git a/doc/presentations/tex/foilshrt.clo b/doc/presentations/tex/foilshrt.clo new file mode 100644 index 0000000..16d0598 --- /dev/null +++ b/doc/presentations/tex/foilshrt.clo @@ -0,0 +1,80 @@ +%% +%% This is file `foilshrt.clo', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% foiltex.dtx (with options: `short') +%% ******************************************************************** +%% Copyright (C) 1995,1997,1998,2002,2008 IBM Corporation +%% This file is part of the FoilTeX package. Use of this is governed +%% by explicit restrictions. These can be found in the header of the +%% foiltex.ins file. +%% +%% Questions, comments or suggestions concerning this program can be +%% sent to +%% James (Jim) Hafner +%% IBM Research Division +%% Almaden Research Center, K56-B2 +%% 650 Harry Road +%% San Jose, CA 95120-6099 +%% email: hafner@almaden.ibm.com +%% ******************************************************************** +%% +%% These files are updated versions of the FoilTeX package for use with +%% the new LaTeX2e. There are many enhancements and a few bugs +%% have been fixed. Undoubtedly there are many more. Contact +%% the author if you find any bugs or have suggestions for improvement +%% of this suite of files. +%% ******************************************************************** +\def\foiltexdate{2008/01/28} +\def\foiltexversion{2.1.4b} +\NeedsTeXFormat{LaTeX2e} +\ProvidesFile{foilshrt.sty} + [\foiltexdate\space v\foiltexversion\space + FoilTeX file (size option), Copyright IBM 1995,1997,1998,2002,2008] +%% \CharacterTable +%% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z +%% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +%% Digits \0\1\2\3\4\5\6\7\8\9 +%% Exclamation \! Double quote \" Hash (number) \# +%% Dollar \$ Percent \% Ampersand \& +%% Acute accent \' Left paren \( Right paren \) +%% Asterisk \* Plus \+ Comma \, +%% Minus \- Point \. Solidus \/ +%% Colon \: Semicolon \; Less than \< +%% Equals \= Greater than \> Question mark \? +%% Commercial at \@ Left bracket \[ Backslash \\ +%% Right bracket \] Circumflex \^ Underscore \_ +%% Grave accent \` Left brace \{ Vertical bar \| +%% Right brace \} Tilde \~} +%% +\def\normalsize{\@setfontsize\normalsize\@xiipt{15} +\abovedisplayskip 12\p@ \@plus 3\p@ \@minus 4\p@ +\belowdisplayskip \abovedisplayskip +\abovedisplayshortskip \z@ \@plus 2\p@ +\belowdisplayshortskip 4\p@ \@plus 2\p@ \@minus 2\p@ +\let\@listi\@listId} +\normalsize + +\let\small\normalsize +\let\footnotesize\normalsize +\let\scriptsize=\footnotesize +\let\tiny=\footnotesize + +\def\large{\@setfontsize\large\@xivpt{18}\let\@listi\@listId} +\def\Large{\@setfontsize\Large\@xviipt{22}\let\@listi\@listId} +\def\LARGE{\@setfontsize\LARGE\@xxpt{25}\let\@listi\@listId} +\def\huge{\@setfontsize\huge\@xxvpt{30}\let\@listi\@listId} +\let\Huge\huge + +\AtBeginDocument{ +\def\@foilhead[#1]#2{{\vspace{2em}\pagebreak[1]% + \color@begingroup\normalcolor + \reset@font\large\bfseries\centering#2\nopagebreak[4]% + \par\null\color@endgroup}}% +\setlength\parskip{4\p@ \@plus 4\p@ \@minus 4\p@} +} +\endinput +%% +%% End of file `foilshrt.clo'. diff --git a/doc/presentations/unil2009/abstract.txt b/doc/presentations/unil2009/abstract.txt new file mode 100644 index 0000000..8d5ec17 --- /dev/null +++ b/doc/presentations/unil2009/abstract.txt @@ -0,0 +1,28 @@ + +Social network analysis with R and the igraph package + +Social network analysis is a discipline that emphasizes the +relationship of the actors in social systems, over their individual +properties. + +GNU R is a cross-platform, open source statistical software +package. It is a de facto standard for statistical software +development and data analysis. Igraph is an extension package to GNU +R, and implements several (social) network analysis methods. + +This three hours workshop has four main parts. The first part is +a crash-course to GNU R and igraph. The second part addresses the +problem of small world networks; the third deals with network +centrality, finding the key influential actors in social networks. +Finally, the fourth part discusses methods for finding +communities, building blocks in networks. + +All three problems are presented interactively, using various data +sets. You are encouraged to take your laptop with you or use the +desktop computers in the room. Please install R and the igraph package +on your laptop before the workshop; the desktop computers have +all the necessary software pre-installed. Please contact the +organizers if you have problems installing R or igraph. + +Basic statistics and graph theory background is expected from the +participants. diff --git a/doc/presentations/user2008/Libxml2-Logo-180x168.png b/doc/presentations/user2008/Libxml2-Logo-180x168.png new file mode 100644 index 0000000000000000000000000000000000000000..ed7b4404096b03df9c8664853ca2f72c1a496b1f GIT binary patch literal 6826 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 $(*F).svg + inkscape $(*F).svg -A $@ + rm $(*F).svg + +clean : + rm -rf $(ALLSVG) igraph.pdf csardi-final.pdf csardi.aux \ + csardi.log csardi.pdf + +.PHONY: clean diff --git a/doc/presentations/user2008/Rlogo-2.png b/doc/presentations/user2008/Rlogo-2.png new file mode 100644 index 0000000000000000000000000000000000000000..1f6f8a4fc8e852a32e39320a2849cbe8d2d22d84 GIT binary patch literal 212779 zcma&NV|1k3^9I`K*tRpVZ5tCzY}=XGn%JC)ZQGuBV%xTz+{`)W_rLe!UA^A!wcgbq zcI~QN&r?s;4pWemfQP|>0RRB-Qj(&I0040A^C5?V_9X5fk;W@fDE7va5kFi!4lpPLJE*)gazQ8qQv7&w?+biv z?7WX4f(-H;Xnnk|tDB77SPMxjOpVsBu~F7Cy~!q86YWV-B`mU~+nQqrolbV6Htds@any_o_foXQFN4t9=40=mTOa2+d2#Ye+ zI*^EXKd$^i0s6%4Jw^O4948gF**2OAnmw`{t6;J#KtOlHf-dCezrX-~TDIKCx~4nx zT6QbN8{hv~Y0&ZKBJ!erD18niXykdXevh;`nr*Eg+Uz>lKFz$JtC`tqtutpAk;q+SCn`Fg7&~xFU zEZDm43)%X!#Dc5meH-VANZ5&2$>jo9$?w6Y?XqM7GC~u(@VV3Ir6-?D=hhwC>6G{K zGI8{@+QW_U#?SP)mRd|X?f%>VCq;{f+KxK-wgl+(F8fTsn!8Qx@lcrb+SBfZY}7(v z0h-_UO-tTpO)ETBqr!QZ`sqoxU+@!0Um+c5{Ey(UjP89Q)nNyG*=JfFKme0bD+m?1 zlbRruLG&QDJNU{W-Z|ZTUjwJ#Yu?}ewlpmeH%87_9!QY4tD3i=wD048cJ~BAZVjQU z^%e=dl-!9haDgr8dE&{fey7p)T)VSB?Cyct{2MR-dEwx@*D*_$rsru*RbQT-FWPt} zAMApT=i2a;dHeVCRSizxEncTJJpUZA^rLKKP-a1(krli;*x1O`7c47Y*0w>b3KaFg zt_}6-0b`Fk7@{&<^L9;m06{6=nn2Y6JWD4H!vdUL?fqEv1;TIKs_|iRYtO29+ZFtcHT)+@SJ&>%o)uM%} zmS?P0ubnab?cX-`yWwBZ+yA-)X6~N@Fe-TSMem83_qstspE;$wg2Gmd&-I)(ozE3x z|3(9BAI$FP;>railMj)c44tZsym-zJ%~WBtnpdzcM6!Zc-Rk4~$Y*p*ooZ6r-&d$$z%bfh`U#FZUSZnTI3+)=3$b=)=(Uc0e6ONI{({jN7I@I53iZ#GnOkY!ifwvhbG_ z=qh;m{FRH~xj|4QB1ELrWDy&Yd82+Lq=OX*k}{27@u~Qt8HF0;7JU6O)x31sVP7UL zWV=h&tmScp3p*-{>qSoD_xYFs_jYu}Fp#qis5$*Om^1#eCN(3Ibs!Ud(Q;SDyF4K!-4T zP=!$>Oj{*_)!er}MFv&~*`^Y?j{XNwLB&=sJkP~hO_4mddRg3w=0cW9Y|sq0VeVRa z%Z3I7FCIIln)g6(0k&_!ptLU!2rod|09Dbp_D5uzCG~y;h3G7fLuum>-3*|5$gn>~ z&dOTlX6;X1hy`s&IGhTOO#Zjqjw-LGwaf6I8}P1c?hBf44fFqZ>g}E%R}aqho<`KV z+OPXVI>FQkJC5_2O!tn=@rvZ}p`5VYbGM=F$iC73uns z2e~4XzL_UR!%+DtmDL21PPTmiIDkQKkXf?__wCfLImwR5JbMGRyp(nWb1@QPqxL9a zQbt8$cq{Fkhe>39KHoW1D!eI zZ+a>Q4IZ>sy%Mkh(V5}@%ybFoUV5<66e*4!kg;zo%dcB%8ES5R0v-M zT$uoQ%^#9)r$l{RTEx}*ds2aWw>CFY%GJ-+QVWWhTm_P)GO;!I_9BWcZ&}h7oc5bU ze=Uj}+=;3f8}rorc(x0{wQIK6x!1C*s@C~LK;`1f&%7KQ6++-+zn2q2sO`QPf!IyQ zk)`f^T|%+>`gFt8_p8VXX|fgxcBg#I2C#K6<0CHer;^hl&sXB|2FCiwS+Dn&4>UU$ zo)G|1R$mwh<+mbFtU7Tjq1vzkfm&Pl4KMp%u!3#fg%zBMi*<1DY8NpgoKMB}Ds-Yq z5B%lFFh`7TCSR_<#(S4K;%rt5}y{gQ*x>oSfJ^-DfR+gc2D+euX`e{^)*8Sq;_v7 zd_0xN^gC*^gkZ;IfI85&e7Q`yy9B-4gnxJqY-eS3;3uTRG27JqIW;;d-RCm`=LdF6LfV zFf_u>r#um-kZ1ci#aL3ClOj{3=kIiu3y})T-0Y*kwbNKayd@D(X(AP-ZaI2@hhiivcxkp0he#%U8;Q&=waUh!@EMJtugHpG!n^7BQQA(- zSc4BN!a{}H*mIoF;KJOYX{LZ*=ETUIP*+A8TMiMKTDN8igGGyC+qgf;z4>77brWq~ z^&%N=U*3XuIJ(VQ*UeY%*W2_Q9tK>yuIO;@XjT44Sq`=y5A}SF&pPGTAP1{>I-eJJ zVtU4nDFW>EA~V1yBR1&(d0?2+C;5jg;wq(&;xCRanwQ-9sM+ANx1!bTSTH-QWf48R zN}df9o~Dg)P;;=eb3%l3B2`uy>;6afjg3krY@3{XV*0Bkt8zk!(3I`JSj5jkVqfTh zx&)Od+fW7|q7my>%u_plba)vKhK3QbwD>O~f@xYA&kW^55f#pg^?9`wTeYJIeV@o3 zROP#4b@I^uWOG@_(cxZ^+5bryg`Ge7!3%V#h6b@~j9o4|ST`m$4rq^_ z_c!o#c7I5W-ln7N-#Q}`A+@6HvJZRZgf@B43~ z6T8=Oj-H3EH+=Nk&!hg2DGpQvko=FBR#{K0MO&$5F%|qzx2Ut z{hq~;6brqof#?R%4a}bEhY2DGlJG^kZE9fGLeCiD#udP=Dj{h|rYr}eXXSR8%4lwhCde=3ph zWn0j$dwxXK4I@X&NZy7FycO#`B_TUk6ueuPH}cXm-*OSFR`6+e%2G0Hm^bfAgX^3z zdl*xzCqMQHovUkHxhXo3b#ATv!Va$hgHurVKwf3A%4A{)iidtvUSpT;S^4C$3v^hb z7^9uQwl?c!YwL-(K;$|1s30r9jKf2Lil+lN=!EG}IgR&_J;(IH%%8;YcQJRUddu3m z!m@37!f-pi8eG_TUb73j@LpM=G5%kE62U2Af4AI&Mf5S)E6r1@@HxZP3w*LBK*g3u zT7#?=F=&y$It$!;IaJrL?r^PMN_f--|;LtZm%Lz`lGn zu$MJ_o?M5=Uh*y#U>E=!ir{jnv9vS7B4+R+wr0^!P7s(pBNK4iVq=;-=pLixkJkb7 zXH%TNDbh!01-hE1b!8p4BxoxFK2CjAVv1>)M+)Pb}kWfU&Mf$ zNvT?$4CYrXCkp9@GqTyBsK!RZ1ko})2w>oGsHGF;4Y6+d6;~K>c~L$QNY~2(YWrH$ zOGOvHUlF1Lwq`yl3mRp+Ir2rVckicOr^6>zf9O1twOeZ5ndhPYpC8<=qGL85Y8qmE zuZ`??hvMc2v)t0V(NthxlBy!W=lhF(?8(8S{Mbft`8ogY;P6tISc9K;Qw}H7138)# z-z5ij31K2i1EVEWv3`;dvLc@=uq;@M8Y>shEwMCxU?%P46@KZQ%sZsfM}$RS@YA2& z-#WN(KH&ElRy`(pBpTshyv{G#{E0wWaymW?WTrR?H`?Gctc@a8DxcX_f7GUQzasf8 zUEi07cKr3%!luf(I=laf2Ij5X;Z}(1`Nyl7Dmw>RSQC6+v# zb7lMymFOzX!i*4fj>w!C2SOuwaYU9A(b^)zXMybHQeT_VBAk} zq3-13)!((()%zfA{&=Z9nwwgA;oG1iKmTi-=R)W6;TMXD41uQFhK{6?sbDsJY zo+g=*9kISz&C3f+O4^Gz(vtATz*X@$no?2^g`)Y5e2Wn=G^+1p`rIk zC2QR4owN~JPZt7s?$jVn^OJ`VPAlCiYP#SH+TLRWXH6UK7Y}noo&T|;pWk1f^xl`2 z6Xf~aum7a5(6r60&GsNU&m<@j1;2t}e&9WTO)dV^_TWjjeeeq0Eog+~VBfkj@wV`J zy^=AtV+b&8$CJ0F&=1q>pr}SDBPK7I11YWnCxW%AQK0>u7|t1XpiKqRY?7~I`n{Ug z7`2-Asfi`xnuKk|m=`|=5!NJ4l8JByrVQ)Pau4s_>y3T$Kvp1zFffH^QFz70H6-R( z1S77{BHIGzQ|2*fK0chobzOH@=(u=%cUA5FEEfK%1~mdiZ?j7B8N817e@2X5<4dMY zl8^%j7_oHcONHA1fcl>Og!u(s4}=T`H#S^&H$((- z0avx2A3orx|2g3WIIU5WgAX_H@{LIS$DxdI&k&kz+a_%;M)eHA1oyPrPJi$#6Ym!; z)|pKOVdkWMpg`#4%7b^ZHC{fMv!t0Ib@i1#omeS+fCZR*BaH;;J-+8v2VMUBZgaJg zIRYxd0wx7T=U{+53tDZ&gT|8!G^ZFKq&D8>@(Zm3q06ubiRiP*papShjJ!F-ib(Sq z*qUunuap9HfO9IXuw_PNVqGXJ4t>`v%)k4;rUBCSZ_rB^c-Ui#!>G8YopQa>L zR8%Ni4=LhP;(b9USOd&S7RVM zU`WxJ@EKa`&}uS(P(Fen;%QM3{lKtEET12fhnwCCkvIWtxq4u8=MQ;Ws3KC#usv=6 zN>9lmr&RtFJRYA1TC|Si*+AL3&sc|@_suZO;@`^iQ&+XytGU`zqAAMa>UjQCQ`N@x zI0?`223q&An-}e&7-@lrK`o=l+u13e&ckOS`YD6o@2o+khTV{}qJgWl-&xFp)3 zD9yc|1FF=(PGVTTxS3*3fY3?}bkF-Ua(xMkhxOX@9;SG_3S=qm!Kf?6gkLIJLtL6i zP;P#iwWcApW7Km&SZo3Ltx5S@UH34KGp@j5|1>V4j{#ciIg_4Kl4u}>9 z{Hn(``=@Mivhm?9P<>U}rl<26Yk$x9l_9S0_4}u$c6XaVF`?I7&73vC;wjM_Dv5b* zLW`$ijs4tkhMvmeM*Dl+!TG6$8pVr0KS;luPAKsfKUffM#~*?RPrI`!+(+}(gIg|H znMez z8?I;E6Pu}8t1VeV!W9ci*huB%PK(A9?%*ANkV(C$A@dj@CVG28^NiOw9tQpU=pH;G zPIXyJbT#XfMs4;4+TWIzvD>!o-Fy1J>Ik%`Rtf*Bcwk;WMs&U2B(a!f>F{|!-;RtM z5Bk^F=yxNDc^nZ&=c`(Yg-_GND9lysRYG`w>)I6yztVkxXB5-{>k^Suw!Ff{)Q3+I zp9{zcf*og7xv3C)fRvoux-$n+2kac?MaRgOz&NCl{C>pPae?v&f>4376Rb=>AmfZn z_NUiCB{)aGW$6IzTJ=uF@EPJmQUvb>PgFj3p7Y+4?@Syo8N4A9df3{2YKbmhK!93j z2I(kNLz0H(iz21Tt~2pE_83f&(!Mt)g;TqfW$^7@(^UGU*|xjyJUTB_eQfghy5;F+ z{9BDP_+4H8B-DMnAbh(F2R1x^7UFNUtx}k%`$@@(Tvvm+kZ8;B3V9*Ij#WkOStzW@ z5PLv3O3LD=ul_Th086aP^c)5%foPmjsVxjj3}iDRsj|0e1ivB*LP>lFaEv=`$&+gl?E*Iem zJlj(gCnZZ$=lC%pmwW0lBl?xVajbnl@RT^Lm+DY!XeQ#>TD()8qe09Jbs%#Xwq=db zit1x|MRXZLZq#kl4e>11_Cy4#S zemER?FaIW+)6Op4n)%91F3XjCbeZn&9+B3(7iZa(l%fxE5Lo2spDTw?DZ;5X;|74gN2Al+lloa+u~PBt zLiSfq0kXR_K{J>poe|1qQU*=n>h*rv*zNjJxu2R!`L z$$q4#MOu|%A`QHU+k||sbzG!>sSQ~ch00ESXGK=B<~Hijk0~GXAckC#h9(itBeW2( zkAT7;k2IFQ&K^?(4FACxvuqGpV5j8U9oXRz=E14>nEZJB@`>a=2jU&AbN>|8nR!<= zFZ`|d1(01%*MFAG)J$)s3O0OA5f{JMq zWF^!@Dk z+IC0mN9W=WMl>I82(7i4-a%!%%yutb=| z(<2rnt4ef27~_v~(u4V6kl)X1KuUkoy(S2*OTRP^;u@t0S23s8N7x{+hp=h|OoE67 zri>YC4DAb~@=-5wnLmIwx~;@pkoZuec75sVf#T*9Okr{X$B4!2BCf`Re=m#_3vd}k5*Q9$7=ro=wisHKi@s}%3}Cy zpKp(f)W{R0blrwtLy#*y3=e|4tv9N~>m8uh_Gf|3r(d)>+C{yPbxqjJ^%5UoYm;f0 zp;X4_&u!9o&Y(c->n=p8fFtcjx`Zb?F01L%?v*U^!?A$6<~QmkX3jqhD~0Kl9pVaL zPYzYr&HZ1!84~E78!FkOOJ(zbWs{Ty_XC=(oFc4`cT9mN){D& zk^}fe(+?MG(ZQO&U1SjD*g&mY)l@TqTH@AK95H?Ea@1U;VrvuXg0IqTvH`W3=|xx5 z2BG-dM_9J=Tzhuy+`6!tEi?Sbp+>`3YPf{jFUM+K->*+w>S(4|sC()gO57CoF57Nn zfOJO@6NI~rhar6(fSY*NuzNEy(UGvFvG9>6*eD!r`2Oy&1hUJ{J0#jOnsq199s+B8 zm#1w`;P`ZSu>Lf8Hg!otkddNgd|i2x@P)K3sVK{}r_2YmfH4l_Z_)?t#Gjn@P2#`lYd&a;>KW0l%}3kUtz zbw&O&#{bO%wE4Q)aIfj`Iqt(bF|{pV{0w@E<6VT9EJCsJ{S%0k~H*^Uw-b;Fs#pl3jlAhF4AowR_c)@0OeZId>4}q%eNDG@Lulbc*w7 zlf!P0@Uy~P6d7_=1zInWkg(bIY=C~NskzIe$V%lX>_No~Y!tcHj=)}>kBxK%E-Z5- zS)_Y--^ey~qf11We@uVej-pqd{G*1uy`F*kwp|y5bXGtAPLMA@wYKB=)flWi{!r>x z%7C0#l+?6YM(z^gALLXM1{pxN2AVTU?w7d5{m`s1rH=j(iUS;9<@U=g7VK9E&~FQq zY3hZ*g)q@HVyFes2y^gfg0w@CKj7_PMBFR;zTDFOYipl-Y`1jf-}M}kM3L#`b@wObGc&U+aO1fKqt4~|ubU4ep!BpL)_+SCwQ z7+4~`gV*w1Kh8Gh9B1CWw|@80H)VX!OW+G|9oCHp;eccv)#qW`qw0{iyUNe^VW6be zY3$o!)>!lgkFE`{i8owTQnnzwXqem$ZW3ppI!Sxy}`X@`icU5k#{HsH6aPNE}d>dNEBz#>-z@*n!KjId7o!$;I zNeQNb%NIe7ED)p;-1Z|yTz+1TM?*S7#;ye3reE-KH!l3wdxxkcSi7rT3s21_pb-B*P zDb!d5x^t4wP}J6F|LseJ&6Yz=3Y}mr;CEwnlW4>(nYk|kVd2Ac%{7YOy<@(vz(sZ& zq9Mpg2~~Mqv`|yg0ZvgSiONc~TmTuF*6M)21xZBibMWumFx~G$Q5>-n6uET7)Q#rD zn;Q6h@FtwE$*rv4o0B!O2VWiG9x-wn3-l}EzOS*;wZB&W?7JI`sp>kB@M-@SMcu9< zAKs1#t1`GeP5(w`4$DB31iPm+&*5k$eaUnLH_|bRb%{wE*mZr>J6ddixU;+wX69w~ zjj#}Oa*5cNjxMF*DY8`bpn?d)00_|ahiLD_Umh|+#)i-Aor%}V|uPu)~R%yKa1O{xr>FJz91}FFeKHh0q6FhBc>xoW5KanB27P!QJ)Om zd~Q9d3^7t8)cT<4dMx`lpAN#p7B;bSs4-aE1~B3OB1^JB7Qfd&36;JDN-MogmdGpW znXqY6lG2jdZ;}X`9(50+A?GHMc=};#C=zP`Agr=kK(cQFrku8%o5Wv2)b7m{ew4Ff zt_+%IaM_^M7Q?;hf;F!feZ6EwUi7?w>db6c;q$(ruto&8!L?|OCj-~$l>pS? zmS4R=u3}Q_FQea_>=8NXdSZBQf?4=mlI}e5yzhM6_*TifP?YiT@Jum8&G04%fM?Fv zL{^AMA3)J{K^A8#bBz%PFAUH{w=2Ezo4;zPREoC221Y+EjRZ2=hwj_kI+PhzRT38r zy7{?W1r5>Qr=DV^rXvaAWpfNfs}o{rg_Ho~6{YMm%I4_HRG&L%vyHaJpyyR8I$5jN zAeU9ggUG2k$crm^-HjcRU5X6du}&j$ zViVIwCjSJ3%4m}4X1FQOj+Y^R5A$yAbA)ipdj#h8ILE|M>-Y*c98%J96^UQaqk1Me zkq76u2%G86d`O}KID4v81rUIF>x8>VW@=|+OeD$d&Xw2$j1YVC!8FmCMO~Bt$F7j?fhp@-dqfPn#TWUDdUJ!B?!B{SJ4!)GR{+MQ=paP`-{1fqs-V z%gvoj8AtbjUitdt2%%f^u>Fl(ySuKjkM@C`%_gVExuDzkO5p7{>c2~((#Cu!D zd)WL`;SUS_8*Mfpod>rTYpfQ0?|%AJ{EG_BPCMQgA5~TpypA4sy-$ve7ZBa7b%yq@ z8u_fHiU+*}zD^#8xfttxzAzfDVsS%3<3Ea@B2{s4D!%x&-n2y9N2zS)lVImdwnhAs z`F7OJHg>cfwDfqmkxyfAsx5d83$nax*q_+)~=Mt+H&BRo@kzKccvGI&TN_x)hrK3=NAbc zWiegZanNd`#p&|~Z-0RATKVxu{@p9+a!X{0_Azoi40^vFPlhB08%|1?Rti~!h1$-B34&I|goTIJ*-gn$_~ zNmRs=(KbmEd)|t`HtCRQ0o5s6?)C)BOb=x!9IaG@?MK=3l0ir zlX$T3YD*vj!uM2xI&;t~%-LjKkz<2{6*FkRovgK`;@ai$ z2Kn(W@E^?H^RK^j3TUQ`6&7WB9YZr_4C|+6b&C$+6ayk+7+Ce%Cb_pp=bH)IV5exJxr;XK7FkwiemKF&Wxf zh-)9XW`ylJ=nQ7wGHA@!4WW;UKj1EzWDRFnr^xq~lp@yd83@rRXQA5F(h|atL2yi!`R zNh|(<9KciQPu2Vf!y^TgpU(2S_4gQ2NOpq}2n~t^02O7vgb~@Kwc>j)Ms|pOorCO_ zlT}MWvuqZ}!+oimPp#dcBLKaea0_?_<8D2_7z+SO!%cl0Ev>OA_h}d9xQW7eh zk8nZWEr>=fnFUTGuzuft1;iSE zT*dKdTRTAH-n>q3nSj8+D-*MiR?|qt18rrnWdBw`2O=2(hM>rRtk(M6#$V{zOjS1j zy10EG8DuZ3m2GxmQwMQaFOw(K=}1NOyGyrrz~}s(!w!7a$Nin_E8<1@zqD;+$De_>`Z{I8+wv~OOs?vWsbf(*jE$)bG`=_E zm{tI8b_pVHmU|pY1;+0tI^iR+_d}?l@PD9*Go-@)w)Jqm?TpC9Y!XMa zhhkJZ%CHfF9bl3Vy^cNNlVu7FshkMB;&la9)v)fKBBq`aNO ztwTIOe~r`9+U8$*6++GS|FJsnFzvLe6n~&v=&wggFPhi}^Ryv0f02YE^vpUHFe;YN z3rS+ehs9ZG^cM+1=ovlt=pR6Af5SidY)z{Do4JGreV^AhbXS(wwKK*{^|?VEXVB>g zM$x{bN5C`PiuOzJ(^5UsIX==KRu7^TPM)U~I^_@XRInb32im3@#{evUcE8Y|BfC^; zZLOZEv0Uieavz#H9$G`Lt)&CuQJk^7!#Uyha2|gJxJ$T&yV4bzT0w;oCJiCai^L8xK7<`HPCN z)t-8deWX+)Jd=#}Axvmm(xnm9APw+x07sonc9I0$Dm-&5r?(dD_Rou5&;7-?{p)f& zx&D@haLrAeJp{D-)hln}wAVCKI*N@l#2Spo?_N<`?8W{05Wo@y`j^2}SZh=!ARMN$ z)zf3W*j}n&J=6lHW~}&C+`{hO5)eFAeVK#dI0)w5^#iupQKn<_!< zVLsqg<)~@ii9&=UlBl|jj!B)=J_S;mHn36E58EI%5`GnXI(yinl*6-we$=8=90h7w z#5`s(DB`LD=t~v4G=(jIU<7eusy=SkjHMZ0m64@TFd<~~$!gZ~?kTK~&w1SaI;@KF z3T#Epp9?TX24mMiHW8}$s{7b4m_cZvsx04exX(k&xDSQbSi$wnAd{BM$~6r035Nq)X$xua#JWAw&oSjU0V zIBBuEW_9w^o8Bg|y*OA|0F_4s}2w#1XC!@~% zBZNJ#i%TtFD!vw|f){)RT2Ar614_S%ri?2K3^^9(*@#oP_4rcwDwSg9vN+?*LZJ*D zgc~Ak?!7vk%Byu;4(goF?pR1&3lf`t4N%Ygkjds^LxJ`K5VHZxp|p29Di%^Fvd`$Ep1Ls(3-< z-|dEtgzUWR9{Ie3GyHet#guGd2U}BvZo3p9t?r&kmD1?0hq0K2#?-SgDy6r)wdog$d%DvLL%zu z9P7NkJLY7*MXl04$4nt0m5cC$ma-y$BR!kUDg>T4E5bToXv$Bf8# z`MO6}C0qY-SR5H0oV|l>=q;2+B;dq*Qsh{7t9(iHg-N7&7+hm_qs`W)KdAlSnXB#V zB#&?Na|Mm^!j&O`?`7tO7LV)s>G9{ph~bhO(!1YRi`2!b3m!UhPRddU+VD;#z7ee0+M%tzHZQ8D%`bMdR!fJl=_Y$6phs2*qs(xfI^~H&g^V){BO2 zY}~sgFxt?>giufHEo3D9QzhEY2=f^Ak@%fmoj+n8vEb^#=c3#aHF85XX!ULb8wp)! zXuZtPGGSw5Vi4yei=6gPWCN>{ss0f88IeMfQu4&IQR4D;18p|rL>grrC27z!oC+YL zn(?9;ZYiHY`o3$VI5$hCT8WyE%zQ%wY%)R{&Q3dU04PI8%pJ`6M14$8K#5UM-tYx8 z-8Iv~b=lDC3veXS35$LlS$$|gU2uWF>4=cArN3u#(w04Hh2m>~T3dlf3Da0?U2L_$ zmVde8B(}F{w>%?%Q|}oVx$)^`gESe*``YF)bSBeTft>P%M*xE|j4#9ZN*P zE)5E%2gR+X4d0|m(P6sWioggqKeNHte90Aon~|>LHQjj|r;0P;&+^PZ*Jn@b^;++7 zVs~zmJ>c)?6CfDeG$1q8?*;L<%$Ef3hpCP)Y)0gRJcd(FIlvJ__nsj`V0!x-q7X{p=Hf`q|Ulw83h z^5+V>t}_=-r^89UkJqgCh*C|pPZ1}Gm*4xNNR9vDX`(Wd&(pMjvyhmNYX;Af>o>I@ zSu(*KN>K2dpiaFj6lQL-6AnGS(C`(!y8XTR3@h4fJGGGrXfi$3RNWHZ{tfn@{ZHPz ze$JHE#(Oj_Xx6sqFr7Nv6QMRDlPL?U8rjshF5922c5FEGkS81zrfG3Nwy?N7Wjx{p zF+(YlzR2KLnD_gTiVWSL1a6m&K6iEqCiiffOXZ3Fy`BsmIFPQ1!D3dCbEO^Z!B`S+ z9h>{L?swk}Bj?JYKfAF8!C+tt)>7;v-Pced@wD3|YY-=yi7|6UlQ=W6Hq6=qb>zMj zz4v(CB^a>^GH{)e^|M-#<^suH^~pbSMd7P-jS|(I3Jif zxD_Kc%YoM027SM$#M~jO5qg2D0c3ZrvtI{b)=;mEzQ$O}CR)lUi3!#QscS}3EeXMS zXmi6$0&HtjL611H!X$N@q2Z0so8O|N2;XuDwYp9!f2uTBeug=$P5VK|g|6rQl9?(+ zTN`Pg6Xm2Sc!>7Ww>`?5{xvv9r(~EOW%=m(-6Jx7i+0-5gz&tE0P&`59M`z1Lq$_V z7bi;g^d2Yf&$;lliFQpsXW!spxFUHuiHl6y-L7hjCtkG>f; z|5@#f!p@_|j@<39nfu7-c4HA`)1Dw8W)G`w1_!$4vgqGSWWI12CKAO_OOAu5Pgw7r z9w<9W2&K9PMvU$xKQr8z;*ncX*~HngP)$g||F&cgW;2onV?O08K*CE^A&O)#R5OU~ zp3D=~0=X&&$iAtDWzXmNGNeK+y|u&_G)<9^rk%p8XeVekN0ss}QE*05kidp5i*JGO zXzwm6$na~C+aoQ?DO3cw34XSZ?i-7}eiCUZR3DJfhCM>CnueJ&3$Hj*g)^KqLrq%2 z5+~@}#!x?&M>S~FlfL~)p6^TykAh7`D%U>w)J-h54!@Z}KQ-vR-!({6ectJG1Bn!P zPk5+2J+;pA*iV^>MJGY|}t5FEMoZSFmGpblhWmMJ4#d z#$kFQkj(UJ-k>FGgqfpC$Rq5pMRib%H&3+e<|!|7p*cb_m6{_N&!mjW`rOo`03cG) zXp12={~abM$ahPJ9Hg>_=rGFFnerNi{gi4LB)T zZMLFVpL;YIgMWc9^ZUpagm;;Ya#xwU@0F5*>w)Q;>FBW4TImzfq}w@Zh%mTlBob+q zY=h6&x>ir01mB<}x4pr`s4$FP%1(f9xuUV$2NqAV(%1BI)LNzn`>|&@9dt321KMt~ zt8i0XnT&jqKChdlO01!4W<4wkPoH2tw-#>JVc-l1o~no8PCVLN&eP$9r8IFhfop}@ zva;I1=DC1XxjoTrHEP+l9T2`>wv}4ye74*UP9FOnI$u7gGWlyzP9eRI>t_wB5esFQ zTu0dhHQWF&?$g>y<3!wonO*j*@ST$8hVWsq!S6uEEJ(LP!83jCC7%5oE+=+3_=CzI z+rnwuOiH9*e{kd|Yo6e`CZ~RJucC>ab7IY*R1k~qUM=iN*C32IQm)><%l@?YoAX6~ z;87bPp|nDuQCdZDdTt-CDiu>4X}igRC7f*L7-i`Qbnbh}>cRT%5E&H2-^Jloi!!caeHjY4Zw;tsLxBNBty2br4`Jcc<*CdrYbk|v*}JuQpS>agb&&`!{l_Pf3&j963o*> zY%ES(w(F8UH6Q?10hy^S`gU+c~sV1C5(q)q6vXY@MZ_$|MAvuE{qaXVwr@VmC2a5FZd3Z-(a zLv2QNuyX(Wmk*!(#UV|h>WO|%<<5H&5{xmm5#N0lG2BU$LTIsNx|mU zi^H%2&7+bWBrVzP+JQJJ@=x*9nu>+cSjhwS$U&n$HFUpCuxCfOgjuh7C?qB4qb73@ zYQ>SutC0_Gw_(|K*Y;|G3=zu45rmK_Hht349Y~Pm;2&YhSTpi1;?>E;K=Uo@yGeM( zl7o9i5fU;H4(S|qdM&1-;S=R4%0jTDwu|`X9Wp&&tAB;p72R;6Qvr;e_t~HS%>sP< zq1bmbTxEqn9O!YGVVz`$XKi&5TboLwD_(?nbMXo+!v>xze#?Ve7%-I$leBU$g$0R# z^(U*AOHe8=d|;0fTBD?!Xn{c5!g;BvU@~+tic1)`geBRu)S&lZ5o8}cE6MrX^s(6kL-R6ppH;HmrDu|V_39UiZ7df zC#^E?$(fe5&d+X8`vYh{nB%n1l;!&>pGU%%A!op!HK5*JDZ+1I^qW_Syg2?XHjpK% zQU>rR9GXc~3$R8>6jhJ@&eJMU$BIx(h<^#O$LUU|F@qa*hFelh?6wVRc0t8Pzb70$YC01qrCGN^%U5aoiy4zG1c0Z0;PVJ? zd2Sf&W=O&u^4YP-Sgw%CfsVf!$pP)8W5Rh&T`J#QmR^EdAxp1)};D+Iq7z3`w1)-T%_x zLp+}L7d_=cGe$c4*Y;4mELabZ}E=1Me(7kvuwc^0&Syki{M z#z@+ETNJTlq5#6_J~pAVQnZ+?E^}ba^lT{^&Qkjd&}m*~9Hxb!80e&Pg@S%<-O|?I zu()-()EJ2>mXZox_7L|0dQ`>Pe#m^}{n5)g!GP`0ub~-MXF3n5`3eGGJy~Q6ae!^b8i-uA z&U2^9dstRe!5YR`-k>S|o%Vc?z*_p8@!Z4XuX%Vr2rfmom zwr%T*ZInjc%F+p|GsZ1HPIo;>@51ixF6>T|N*#(0=bekk!ydZ6cJ1NfNSmCQ7V-cElK}H3ki$C$HB^hR|%NXWLN_W6FO94mAXTj;mj9}^JilVl%B#<@xqRasW3zr zlxW%j1HBS%V!`svGe4?@3_41xF;RyLEyQWOpzyP|vRVxa1ho%+p1^NFYZ|203sV4Q zbd-_DC~0D^Wk{BO%aTH!O)zj%B4JX}ch1v7b0cyd35A@Q-A-1`ur>a;7yzu+G4pXIpzqNZT~fjz)0(wbyUF>&Jd#{dKQ- zT~)0sRM&B-#vlL5AFVz8)Kjaw(_Kip)8#@rC7jq5sDi}6MNn~=X26Gm zQF}jM+;||$6k_E)XE|&Oy zckzb7Ak9@;9CK&U@~_2>V+_sGjZ4Bk0G&a!&5IWM6zI(E#kMmbSTvg`Qk9eDq@gMHj zN)NjbvzmykxDzYogbsd*poen@BQ<3}77vufkqXOfRFFiF#vtBx(1kyZL2+1+sY%(AbFfet$e_wX z-bxwJk}$rw=4ve^Sc{ZvV9Ky$Q5Xc!{I;Hy55o(*{7)5s7Wx|w?GzZem$m#=M9@jR!(?*~m9tmTs(g_sc){xV?0wlqo2CO&L#SLuoHkZgHH z(9S+)u5v5>U8o*-m+X2ype#wV-b%p9W_H8)t^w(D>lbv35I;osMAk@8P>TmC>`@dHqqQTzbbvgsPA6}# z1L>fqhIj5vYSJ>DO}$@w7bcTn^Vo&i?i6O-%r|VIAZ>U7iS(lgb{VwYLp;;r0wdWx zoLUDEn^W|FG|q$emoOSrr)WbPg(f0Or~RiJw-23a<$;Ge9$zZNDOo z)aTS4kSWdv0cO_OEm19w|0@*{a^GlnKYuRdMT5BMjp0USOBykfu8PupJkO#ArtJ4a zU*3&SD1(%i(@EahC6GOmpkvgY~mW>Yac2J39~3M@TOFu%1C>pKd`AWXG3=vmoJHJ{IY)T!%Y zTT-uiF{9|SMs*IrzUv6Jg>H=eoDpn-*y@piHCT2olJl(Nsv1DnS3)b-Fvh9YBe?a} z+c#GK&3etXu25aaGUGq|$NzH$K%Ls&*&?~Q(#gI*$%E-B3W~*ts_!7_DRhyRCW~=+ z%3X-LSGs9hXi-d_(pvP;&So>1>`vT&J3*bEz-;D$dOnBwY?@SQtaf_f%SZz$_OS>@ z4OFHzws-`nPO6t`bU#e9f<~@NDt*GpNb)VZ&>ohAjYjg+KSqCm}f% zPvX|$26rd4mh~Xyi7?cMPa3)y8K5IvaTS`wI4-6lew%O+W}sPUmn_0NV2}*^PLYOz zz!uAaX|!QdZ-KXaw!)btd2@$de$a2U)If?fKj96Q0Hw*u`-D$=v89K?siOOyCwK^- zx72tsa4E5_GY}U)FB3yb+1irIot?kXzM}qdp#hFi7@1ugL_fBs$s?%7M1z8+ZD7>2 z(2mD29*<$UEZJUGQ1sxhM$U=ai&VXmc)DLU7A-Y#)(g_cBlQ z0I&{qI8Ez{7>{3f91b5oe9yoA*J~Si>T=6s@Nk0x4AWRi)k;BU92C1aJa;k;7LVQC2}~wVrJhcG z?)1#H5;s0$nnbDz%ickW3l}!hy|Cb!Cdzd15F!O06mQt#;TV8;HZ|ylsx)pFoKjD6 zkZw_-VcUj=4Nle2&_L7b1l2l4=cN=6Y4x4P*QLB?s#05%CMxlw1t~(y`iox2lqeN^^I}Kdpo*&ZJ>KkM(IXakVq32W*y95SU^E)RXf%SxTh^$0?HY2q zo&#<*lC&jf5R7xJEuR!ykW)NZH^vyXDHJ_&sK$ylIrcy6QeQ>T?G!1VG-9)mg^vZY0G zV)mdJtObp3pz%w4IB7yR8d_va2!$)&?#arl85q6bc-SgV+-Qgu00+!B!&>M=g**KDX5hm zS331oL0Mb~^ijOz6`L@NyG}cfcj&>4ZJLBtGPPv&{9exu@Ew^$TZ|Lz(s%cOB(1kMh;0RPRxG z2tC)OkRu(HaOFM3WaR+I{IPDL3Qsv$2Rux;Qj?tQT!|FgjHrc*%-*)-{(NhVMZR%} zH6P)-%DXHoxlp{IWRvezfb-0s&A?ln0!fgKd@~WVg9za*BBRk5M&mJzM`IX`oB)GY z(u|Mha}B(9VK$$~iF{P64J|YsII&je;r;u-x){4BMD>L@QG7nZsx{so z?N>3|v730@b;n)svb$b>&%1u~$E#{xp}LMRK6vUcPd)hH7gy%pJX$>f{Ya%EM5;~? zOY&G6riI%%Ob3vTKHp&bXdFRp&lCorAOs4{IRP`eXee$mpbC@K1;5geygOHErB$HGD1olr3t_ZI|Sd`KUY ztI|Y~MqcR@f5+Mdhke?%#NN@w$7vFe7cRiDF-&Y+lV4bH(nj>~`+^#hHohsFpY_Rzf#^GNfHlm}S+-YLG$&eI!1fCuDG_-gW2v!( z6oZGYCf3+^oZ}P%TJVXXAFJaw1Mtk!VQn#rFQ|`Vo;ApYllV7|6J5fU1J_Dqz;0>F z@^Ie3mM0jvZdP7pqkdgL4UqD4U|5Yv`;<&_fwnZqc$>DFdSa}##8WO6JBNL~)I}LJ zNm2{)rIRw=Q;d%OPeV^+-vG-<5weBmVM^*}z^_vRc;m@V5C+EMF^tCtoJC}m+e1ue ze4Wh{rn6n>=JQ;&?rC|Bf0KL#C>AcWLPKk;2-af57_?#!17`0!zP&8uFs{>%T%KV7RdRiXM@dOYx@2Ub4z*Z+NWYkO-2r&7e_MbU1d*yLYs z&AaGwwUosJsp43n!iuWGnaCdUMx(KdRBByzX|N#>zno1ly}P>$J3HI3v$GA`+dHr` z*@4}OgX!Jb)J5XW=g`f48*!fs0g%G``Ddd*%Q_IEsJp1B6+R~f2=*8A)c8cvd};2N z)*7kOxlcM26}k(E2JM$TTMX2}D&r?9PT_B(dL8}fO_NeX;+KXlz__RnH(c?G@*MdT z!+m6u2MJW8D%}NT8G&&pDAGZN#;@Cn825E0s;f2~n#fE$(H5ER} z5v=j{@cOKNrST9sK5zgIjK?qm-y^mi zp>Yf_x{sLH=mu_+}h0ADQ72SR(&q3(*rFYOTx5t^28a_(ev9R&veL^ZX1`gIUbLp zY18su0Ji9to=jk8vg3hz;%pz29oY4W9xk_Z9&H}M;(@(unMxEcm|8F8nEuZPg=j&2)%Mz6G=SZm>+yaLE>k3&$GXJ#z*%+|p+-$DO&=mnP zB`F;v3fp@UV7KiE4jecD2M!!`{_D0$lR#sAYRD9JrxTdZW^yAKd`<+m+QZ?RB?Ez_ z=TCf`EmExwxzDn51rVP#kK5y_dk|*YwuN>)g6prl9*$je?LGhUpZ}{0))lJ1b;h6m z*`J*H>Q}!K)oEM=!A~gkL`J_h`2@CP@!_JSRi@Z#lp&41(!rY0pnU0eG=lMH1nsDW zR=1!OxEA8`xmTrkVB0Ix6A#lnlO5-qo=sz=cYx`R6?K#@%C3QoPa9DKebdBNqq3qW z?5|FL(n!425z^a4u)U+v-U5@FBLrWx1kr8qNVhEmX|MKccQOU7vI`zABm`@rv$Vkh zZpUs!P_`7N4f6QgE_tQHd-#M7V@)hFHJynU5CDT>@))cJ#l|3DwsdKf7YT(67%4n5 zw3RTBmgv|UjXc*9$c8SEHUV_{ld8xmjl`Qh48j`pJgfra1QQ9EqX*kiNH&mOi|6fT zO?V^~6!6kFRX_pRVJrv60J*s63+N;@e>>j(9odis;#55VIn=6_5^~&cO(94wb$E== z5n5^owGg9i`7cr=D~G>XNI)>`kop87^$E;WQq z&I?Urz(S8DBnRt7=^(gMEVXr{BBg z*U0i=jD^{33R_#-u(iDj+uPg7H@yRs$%LeNbn_18rXx*nN(V3ZF6(7rT1fL)1k>14 z8k(h~_rpboA-RLL1`mF72h5G{JT{_=v~Tkpo3!YlbT?Tcm$kUd@XpA~BnfE(NvoOq%;n0FnGh_XC#&t$eAYL)16ue8Y63@z#6?y00S?HgKkt*eTQFua^zNmqKkj;8Wk5>o&bcDU9CdSJ8H6o z&&46hD0E5XVg@Kugh|euIK!Jon-We`eV1fux?wYQoNkvTt;F$lL2)&hPi|Byc1&@f zEKgtc7d`U7JMX#5EOohpO(zO{YCO3%X{=tiaNxi}IB?(~jK`zcNX(@Lxs}n0FLXD- z8e-5SIwSyvX{-e?t3Cs3)G(%);9=?cIIYBi*xtP4u_oseHaQOy#Zfzg(Rd8Ujvd?h z#ee)u>o0p*wS!csei4p8{4am7vbD2yYHM>7I8fu@Qr-es?*5&tA8HXy7vUORBw1lz z%$fFnUxgywg1aKV^ij4LFW>`q{7a|^b2wqR>(3nmk%PVeqcV0Su& z`8>fiu@6|A!G*$0nn)2=vGL0O!n6jO1j1W~yTZU^`)K^X8$bC7QFm=rqJ0wtzaQJt z2wJa9N01Kt8!b_X&jx7U+L(@!!*A#!m^yLNG>-t+T~JU_k_Tph>Zn$ND;=-fC!KcX&Y>_W!4OSjZGI^w0#CO zmf?|q{C(rHE(T8lh@}QCIUoxV-Px?uB3~v3?H22gZGILY+YPxEd+XOhM1AG^9Pbw{_t}CI8)EK#xkc z0%CIrjjFf@JlMd*)RVCN8FUlV2H6F;6-|S=%jFy#G<4U z53z}e6AqpNV}ZmP!ZLvE&;Rip?$zs2>(%Q}-01W8Lu$x$x(k!tUFc>rc76z;Yl@Aa zEMB<@2}0rbZv|imIze|eigeX3yk00^bU#Dm&Qa2Nn8kF4<2$BW&%Y@7rDGnZPEZ|MTnFz~JMuu? zMuoNYz^IgRCyuUz*}Q|@-3jbWCcgMEDAPNzvomo8h#sbAAsWww4v2X9oI(IDn!(Gy zym*RxoN2{Bjm8TX*CI>{Ngkm|OKJ_SNfchRbOdm2`G2&CGbq%!MSDma;a_<47=!f% zfQB?6z{+%oi(OE&6dH{e_PO!iXak*3=)e|`P;^KVJ7R^q6$=oZMFg@Gku1f-AWMi4 zsh2Dnyah0|w(ue!gNlVnVu|gEw?eNKEE~k54oJ1fYBjTA^cEvV^g-QdVz)Kyx%U5- zI*B0avVwvutJN0w5{<7x8@ND`Y=@iEzXSPzI3R7KsS zob}CDIcW$V?szGx&+&7s9I(UW1}WMcvNNlQ0dGgJYNgr@c}q= z=wPbt@#?ji8<UhzV#9>j|_ z;A>h+neKsUP>`U|7u7XQ^7Mw{!vKRVg6XDl7KP3RReBe;wl-mFdkeO=w_#^{8+Inn z;^9tWyV2$`pU)9Yo9JC77k{!(f;#OoX|>YXK9i#C5=<*%YXPlEA&dyqxJXfH)YUK* z9wt>fd8OliBOpB@aP6Mgk|dSbD%$eqBMA(hBHbn5G|D?Qv8}y{EphW`Jlg#*Gw!;R;gd#;_?8@2bIpF{RAOJ~3K~&&evY9M?91&Lv z3NfB!BoP8=be{m(9I#1_IqEE%i$r;BpR|4W`T%U@G63emgNNYY!2>QiM2FOn4yGQi zXVYmauEw5gT-4~riC(WY%C)BT7qZWq6fm+0zcD7Bhk3y=5H+2$MF8t|G=kB11b5te z$Hv;f{dd)8U7`AgIX-migDc;7{2Qmvo;{N-DI-$MPG;`Mpn%6UEfvqy6Nwi=9p^9O zW+Ne?!nd`K_KwE?y=h(HVMwL$MR(R(n0FmacXwfXvICnJF2Ls2CQ_z%98AxqFq=-D zUwS@=c{lf7UerdM!8B1MxH|1qHTrBGV)3D;1}$_xEWxzaO$O6J(dQuDL~Bff>QURq zeb{)rOsi8v97%n$s5(5_IzoXf%WsbMkIp2^1-3OHm<~D9u1L^AXHrIb08nSc$x}W8 zOb4q+*D(u;;FpfMtgNUfMbufMLOXABvT7u+GxBsBoB0$C`@F%5LY|8N99uV{2i!35 z1{S*qd6q41Z_P8KgFSRdd|2RA~S+cZwn2I;}OR*6oKlkyMb1A%&(jVow2d zJ`1am(L$zJeTGtb3J0jnN`e*MbnZ}v7UTFkScCw1J&y)v0>__`HKP$Bo3}@Yyf;8f zmSJ{8qGI#?O82#FIZ-hoDI|v+Jah;S`s9$XvJoQrT({m`q7J8Xl*!}w*eVonbxuSD zURQ$7OA=TIL4m925>O}6#boIqP2-9f@3{R=c*E)M_BFgGV*>7|R6{>7)09X%xPb_@ zXHaoFD{3WxN!4h2E|QgM4BfoZN^)%(1on8PXW?Sn9>7|Y$#&SgJeeVbaKADXKBS2; zj?^~|Dy=HuI62*D=T$eTNmrwb=Wodhfjc_lrc)fYGjF;8+L@gqQ?_Z8pR80<5D(sL z(awSu>;hzKmFM@D6zuFes7mp%Dz{E@^;+eLA_1dRkwn526obz1c-$K{D0(n(^eFIX2~+FKx~I^Cc` zy5yN2jYhtjcoeu`vaW30#~#n6ysjZ)(lV*^e*2zJZj&gOxO4^xvC3g|)2=0%7kAp??k)!H1W ziCRO{Y@{^XG$9C(AGV>x(BB26xkE)ihQK#btF0wXY&rzKq^n{Up=FaDz!xC}dj|Nn z(aG`=3YMf{L}12Bat^{2ka06b*4$4pf&c+r4s^Cq^t)K8Mh#sQWWP83z6M#5ECNxL z`E~c_G8TZnX;`eQ~+!Pm~j!Sv>) zSEn~OVS8r>CfhqOnIhlxEZIDG>s9<|aYa1In`SVLmk%{d{wRBR|RT z2F!dOpVOgcD#m9_V+hAyKHLF}+dRlcAzm~(F~8Npf0vduZJsh>SYEaf?I(sz%y4`3 zXyq`5c+bNm5W{m^6$n7(_n}e>%-j@;32o7yVT=s9*4ja8GXFboQ(gw}07w0=O;zV~ z5kNw1v=IS7tXYWn1vTFo_nT6Mu8JsG#dKW+PVMU;r1s_)Z~yS;37@)S@iB(%AK|AL zeurA;5FSq0Ar3RBwKNz7Cyb!m(GJav=sXPJ8jeQEHU=T5nJU`({>SRB2AwJ4nKJGh z+9d0;66`_=yu&t^ts-cW6Ruz+K_sd1cqJrL-`UEYq*UVsB+*i&RIhp+w=_g5df)Z< zzyUaP=p}IAz=7CMCcyR1&Nl2$c75%Mp^anE>Ko0|w*;(hA$yt^n&Xu@y!rzy0~=Uj zHZaRmOe+G~;(XMOV0_>JoVex0#=l?xKPp&PsD6Hp4{dyK$u0zXbsoNaUY^0OEPB0x+WGECNYUNWr&@ym5VS1ZKZ<2>b z?}>^IJq$IKD1CuJZ~^^b7hXG2wl?4i!V1j#yd3 zYj(l1f?BGF*GMh0*%GNS2%v3jUK1S~GXv1*_rRMCn^+S2}Qu{;>yqXe{ zZ7>iAB|0h9^s@(rH@r5f`5?uIkon9BXbKj+OfUiP`$eU;+lpQbxN;3j(OAQfy)7x) zS~*dIrH@GG-E@6Ha9i|c%Y~0((WBIblPTDxENp&Nq_r}s=rOMRu@sSEg^il{uE(RX zQ?3sjgm&apt&B?x36@4P_vbE1t9Ur3Gxs^u0!9qhDcaQf$Wnt6l`NUSC&k*v$i2J( z(qzTfc%(e*5ga;nXyX@t{ukHZ{Khv|J4l7#zuKHD)MX0>g>XrorhuHkZ?9A> zLN^XhQ^Mo8T5F9-g}uIr53AL_(*#Mi;O8^BI50u)B~hx;3XlU5)X+#s?ns|oCGV=C zX(gdOc?qg57YURqG<0G4({x!eDY(Xh(%OAz8l5MbLXjWy4RcV|ntoQvo6S|>7zwDD zYOQ3zrq?T5v^A2lPNrb%o-Ay#y*=tR-p$k=qS73p2e5I&8|9n+j7DR4$x9Bw!Gnhq zTzg?@XJ^ORLXh7&x8cb?Yf?0gRs!Ss4fSUronxicAtbtl6*2Q^&9`Xf^~)JZ{GN`KY#z)Bac3^+Rf*Aeq;fTGjG=-m=;v(-U|{_A{v~X z%;NDzBbSjJW~}4UI3`v!+NrF@x`M);?QPiH-h}h#&%@^C1=!p~>h$geX0w?qMC@Ed z9&dLIBpEkXrvupbbwbRn9=Jh+($wYw)HAJgf09RRuip50yQWF%{n+`XLmS+f;(@ZJ z8}FA6t;pkdtAKRtvqAmkzG;h8TcHc$+;n1=vO#GUG*7xi>M(2QVr6w27b12ZgoBqk zoUFrtskuoPz0?Mh(%kuYJ`BwPq*Ebe7nkTmTl0=sQJ6aKRZmkW)QJ|4pg_<4ac2@_ z$9Cfgo=fU8hUYw8!xCU#N~IR^tR+wuUy(sHRX}m;VwE?9(xE686{@LJv%rT_a%s8; zUKLBp87f;n18@ypLg&U%!x3#4%|2azQ1*4m%(k&9k&ACM%vT!KUeIwjrhInGEOf#8 zVdHVZ3;wvZy&Eq6X>oVWx?B_&8W>!JdbXRv*^+waHvUIaUX9?l}F5^00XRaSc zMbSY={Wy+ZuU3_gUUgJv7#;N@C?bM_2-t&&KxGL7S81B1H@X{|?%H#YjEszkv%Tm3 z@ovBOJts1&x*MpfKJSMnYstzbGUB|?dEV#ueVQ)HwYd>nbm|LyvlN%tO3!BHH%A2x z!3q;wr;HhqDK;Esw;Ao{(i4f=P@#T2oE^v`O%Apxikn#24WY7a81 z(2GmxFpi5L2+-+tEOE_Ie526_S(+NBh=xHyZjh%5YbtJ#pHGz;p?CYP=W^2VP zYgOt@&pF7P_3Ydnq9EM=2mk$#TVdUb>aYEJ-`~CW@bTlv=9w{&wVgO_9z-;L7(11> zICUoiP2mx7@id|gVz`b+7Pxp0!$9PLrGN}nlb<9rq-l!H%>gzB0}O`)42MH(ZH+M6 z+QKB8AeDleL_LhgC(A2y`IU|gB5HTKX$a8})1u9=iRdAZm0hd-ut`jtWi7&$7V!?x z_mnfuA0zHchk|^^%Cu4+C6fa|V(Cc-d$OxNIh?z_7d7hn~`{@Y)EKsw7C8-0w&W1s@IMqAh# zZ2@pF81#|nxpo*;6v-67m3qyT#?YmDo=|4Hc|Jj&Bh|Fqt!=L3wo^hG{X9cPn7H`@yYlf$b>1Lt_7t+&X9#h4%NToREjMG1AUWjbcoGr3spB~q)@1qfUl+&(C?x^Y7xsUgI@HeYvE_S(8( zHC}VB6xSg-oeq*DQ7w&SQDQQgU^Lp|B{EH}wIQroZ7|U=3c$?kmsMn>4ZR(NwM7}R zBYg5X;9`m4L{f8hT4g@5|kfwCwJ!pE6LJ(>a^&RI4g zHeE=xE3I8zNu2OyqmmGbQ5YeNLOqb^`AVo)0Lr4kIGtcH;9`1nb3ld>M@AcBUKCtJ zi6i^^V<9e>j$6<9ZNr=1PE6N`A2c^EhZ>crXpVV!o=ajdW?ou8PC*cgNK9a~Ny>MG)q|s znvapJviSPtfGHQkr4idG7~|hZ$=zt~O7r)Tzn75@$-dUL;UaroH5^L{dO)lCVG=nu`;8<>%Igsb5SpYqB zC*}iu54(2l!qYB#8lJNIsd&m$o`R2j^uvd)z4kg>e);9td)Z#xeDlrdc6tj}U3GPP z-1`{C_(NZM04GnLz{8Jx1&=)PFmAlzrrYBro?ls6!Fqok>uYOj=#1W|PIWICsf%ai z`2Q?6Z=G8f(HO5D7isJQsNIM;^Q{i=5p zGz=x~B&?Z6pW1D=a&eY5Cxcw{)Qc8=|BZjx3hP!>e|6T!7Cth6{N(Y&gN+UBB&eH@ zyz1%cra{D}pVHK0DRH6mJd^05j%GbwhaH9?qA21nbr2y4MYEslsqZJvQVfTi*x1+W-^MH zF)r2t*L;6t<8V42t0Oh8TPOKgp{(C@Ef zW3Zuy5lweG6Yg{_#1=hPl9zSuGSKYww5SKY*fkF!YBAl!nl_M8%B9nc>86-RK!-Gg zPz@hSF-MYt9sPKY(gYiXNkwND!XBPJaZ7y zIO-E0&tO3lr@B;84&8dmqZ$Q@Li~9tE9Of@cu~WNr54jX8!bNv|J+TvXgTx<09C&t;ze$q5M zH;cJ&MBCfR8NYTq~n zqAw8KX%>Py@DT|InhzrKID_YTis}(~0X$KU?KnVLGUP>ujllpL{XY7GbqofZ7!HRR zjfR+{6HF$;ofe`>Mi*+;;!`fF+MV9MTAb56NPNX0dXVTx16ofCYE`l_EitI)XU3_ARX+_Wwv5bni(?Dh9lA92DszK1oP17MnW8kG^ z2GWt)*gHz~VES`270r->nP2paQbv2Yh|Y74NX&~;HH(!6k9NqoM@2Dur7W1KR9!T6 zU^oYxL_LTb%?#l@CFfkh64iv;O1!&@Rn6{mgK%zip6PSldg;3MuprskUB`h#ZtkRb zmh%pLAejGm)m2yGiYxXaP1A)mogA5e*?wGg)zu5P-1H6YCe{k>`A3gFGXLq%{0r{A z_io(!+~*xQwRjRIjvq&sXZkuq={^7%oVS?k;`OE)Lh184aBH!QsWA`Y*xHV-Ix)0p zBGet0qTC*IoK6yP8e=0(B{SxUry-9UsVTMZ&21W5oSwUuHzBU)!WAyymg(2NT&7Ev)J16xT%8b>RmdqAUaHm11KGM=$R)I%LH zQlbYMP%=|MdMG&4zURRUxHt}Eg}89*dCVi^Vp=vwiW?AL)rgZ48ASmQgP6h{ zlKEm=OfxEi#l;R)y$!KckquJbB9DG$c}pp>%i{eFA_n6}!-~kEM6=m|C`$z7@SyxW zg{+J(dE;eKiafM*qJEVjx!V#wl$dtS zI&q1&)6TRWJ`BWQri^)nLRkC06w|&an-L#h2x=v!<@COAlS@*KZHVbg4Cb-Qc)S|j z>5@%tLWG6LQzaI%wM(_zX$P8Fu@d^+EmhuGL^{esWukM*7N=rb32Hf{SQt5JwP_o} zji{mds#4q-G&s>zFq@?)O?heZ_Y9;O7>ZrJoda!ueN$a(`(_b!9G&`4JokKGM6d!r zqcFE?4p(1&4SdgED9YlS8zGu-yXw1na{R55v!I{p&HXWq~r`nV-dM|d(7>oUNxmLN% zlw5f2B0i(zBcyeVTzL5PO<%;MYi}a*c+`VgliKvcDBb27)k9tcDW94+ zO-9u$qY)3?Tu2>Z+J>mmb|S7(6gkSGK*35uZz*_QIxqN56v<38g2&U8kBSt)RHmQ? z!T%W~gQ~%GwqhF%ZZzj%!${*#~j z1a7+V<^vBt^dR~h>qKai2#ImJMw#0*=Uf?kUQ@$kQ<)bP*(x^Tn1wU7eYKXs#wV0OF6Z)~}W6q-srR7db7rIhe;j+?BvMfPq6w~zLMQgvM zpa#8|iD5tLQV2XB8d%eYLtW}WD+@7bSQ6n}5aBG`=sYht%|*pKOx`pdLM+LUC8&!p zZW5}(idal3D415lLI(>uwwtzcDphP0!N{K%71T|_nL2RvXDuT>v?$4u1HhiEVjRBj zqu1-!#;YpB zR+@^yqftQF86fB&+fhVnaST#$v@IZj2w#_fK%;5W#`3ue;o4lHW4q}NdH*I_NG+}? z#C04e=+1~pNCk|?V{C2?ct)O_P*zry!)&tMSy(qlLAG(M>py3x{WN`pW8B^MblVt> zv9TK4P8!>d%?1rNwrw=FZM(6#vCYQzyM4az?@!o!KYPy1oSAcF^t5$JDiXf+IAlAl z_D+5LWo-Bds6dOWn@>%BcyI5o4Wq!~%Z$-R zqRiOyQnNzMAhCGIEq4Z*!B|rrMaF76Lkm-!w>$P*7(9T@HSgX?U^_@N3(j!B^69E< z$JBQ6H>q#EOL35^1(hvOa@*ILoiE^MJ7TE!jw?W0oPX|<5df!`CtP?2e`KJ6GQ)eZ zYOT^y6JF&Bg3V5xz6_>NE4GNP{SOqDg@_Wr^tL@HZB!G8q^84k;a#VU^CXk=H`B+*&9v6Q?(^|NF8p1OtRp-5$H;dNgvBXmDzdzb#} zmQ~fwox7DGoG|HSb3x(+X%~TVQeGS>9cLwkpQ1cmSK{uxHo~dGFoi;y;88o*bnVWBz8xxy-S!4_lO%>qkHiR zg{P99wl!60OPJAs+;}uI$vW`WNId#U@u!A*;1&rPP8~xBihm0g{BLfTEC+61D$6}< z^%ayk$QX5wM3w2CaBAVU=+^WdWzMa$v^DdQx~eyR)XDqmQ#|VBez?AM^eGtqey$y^ z-n+%kQ1IUlOo9)GIlEsI!GL#mmUHE9Pw!#fXA_*rd=Q-Qxy}~!^Gd$py}TqtHfABT z67WhGrl{V9cM|9AY^^lD)WIC6c}TEcKMhu9>-3o?u=Xgaoou&3xm%qP{sqmMitdkg zs%WmE{;CTYOi9fY!Vvd`FtG@p==qf3NQxM(Od?e-jjl)7TvJqB!*!NW-+v5A;?ibG1yZ(KMu4Ps?NC?qctJR_CcPvdVc?asy_ zXnb^sE5Dfe9e}!cW{yo3*%)!+3V$P{iUVw#Lx>R-lgJ0P$$lYmDq*zRS4MfYHP}zb zfNG0~hKQAvO!7NwBCEwL_O298MDv~IG@ZvTQg&A+|^e)zs$4&=EOwGW-!S~Ab8zQRNmP+bAo1l^sidbZP zF$&i9$orF|L2s4}qxR!P_sffdrQurv_B;rR9rwbHofh|Ja;89jONb^XgP72f11C>7 z={LAy&p>}E0?1q%b!stjXFgoR&hF^>ciaVa^_k73KL#~^GbVwKd_qYGZ6PIjO#=;% z`{^|jXk=zOmxK|#>nL~TbhdH+T5~}ToEwMoUMM6eZpTdF{k#3EDBPJn*djlpKhtqA zauqUAr5?y@FIy~7rAe*z5j&WJcj<|rQoq!Z4!IhDiyq%E0b)sQB&USjXve)_1j6l+ zo5Sp0lHJD^>Ie2H4Pc45D7drhonGo`F9XUGid_18O2L+mYu|vFw!#;rO=QECX*IE1Wttn+IkB9C zWrUTL+VaPGpt0OIE^C)G-1yd=6_!k3m|q_CVeB({K^F=R0)`M_G1s&1zg1XO3FK72oVr8g}dV5#&DQkA8!@J1G z_2iyZnHwdvD(K}Bn1}F0i~S8q z5a?y{KXflCgLjKFZfFdef2UDPO0I3{3XX~rfKG?_Wo>Q9g5%I8kz@rFivg*MTOPKA zkc8vaxbU`tZZ}&P@JVt^(KGFf_=)s#@(J1Adj}l*2~L=Vx1homCLBPo4%b+sMbkDs z=Sq1di9MN3>lP<`tjetN7e}d)lD;~Tdr(z%Ysc}__(WvZnx?AP9%;($>(Y?rMIzCn zbidIvNoZZM^x#iqO^RjG(i!f2rAAV~PwCAd?C4>P+eMrLIu1ya!sT#8GtkazZdQVK z4KosCT?(E`gC^%f41G_tfc*D5O+U8}@N5LGy*6LoV6<^SRnD31r+q|E(>)Iz7yK=^ z$I1AvTRkw3ISm;XH8<@h*@cTxAqGrmYmJ@VG>1!nx@UXDb6RVaRQT44c5rdwNG2OI zyOXkQaj&OP7S&#Aib+XWj~sbE`HqNPt}247Kcp1w?^%T{p-MYOlIMJi{dqH zn&Hpl$UqDS!#58F0dQTznEy#xk~wHxnwH~S)J!b@(i!Q&63>77H&oJz-H(Q?F@j3Xgh+=HP?@uaB0bM3*h#)lGsQ2oC=o?Yf)h zujPM}n3Ib3z;*h!p?u5=x0LYP$+m|8Q5PZv<{fOGqH68fw&>=rD2Bpb5rJ1C+n*;) z10AX~t3eCeJjYJ}>22c077$wd5KU%%(j;rcCG^0lA7YB{XT*$mrQ>+4a|VZG?u4f! zKI~e8ZRwx0g;+*f?8I1Yf5rB?Ee0xuDH6x+3tREzPJ1lYMBt9$&Ae1}EZ6K+mVX%3KZu*q{NFg+xt$8wnwkImNd&rV%xieZiC0?@0Ho z{QVkn(Bf|E(L1o}I!!Lya<-oKaHo`+CEhpMA4hg+_CmUF-sfyWoz}TBBCF_PC$i{s z*D1qJI*(mrsfh(pOo(LB!x6^k@)EUZ3GvR`T;E3kb!-C$4!(tnOcFNtEwjRL8eb#^c48;Nl{s*&r-`=xHANA&ZA=nWskd0!CuQPDZ|-vkjbv zgslQ=RNR(4;FP!cgs`l=BB@!h#_tdfjc+VxA1 zg)K==*b@p=dTfRGqYe(t8KhLI2e0giBVu17$d;p0g#l9!2JM!E6RQ}{Ll@77UiQU? zuakqq@ksTgi&8=LVeV%^DaGtq5t@GOWtKWQJQ; zhKzJO=@7u>=B=g)@Xv?BaULFwo9&lHobOYVQ8nJmVJBe1q=0N| zW5e+yCL3x!pd+?a0Wy7_>zI%-}6*GUh`~h|pg@)O6m?OkR5JlRe_R3n`Nv zyCR6=IjheE;;2TbZg8Fgp+^xU@)BOZNKdGKriIn{2OEmQ&lO|CYm&=T=10w9e%E(| zjm%b3_r-)Pu&pf~Ii&v4q{xORO_Eti%9=?s#X_)24Iix$@2{2EId@}Q_PwZJ$R(cI zx^31>3Bk>Z6Blh!l7B&E&D_uuP)Pr~oGjZm3u88~KZJD%0$lTu!A$p}w?6wWBWZ4? z%9(#;fxzd(1*Y5X;m}53S36VC?K&_Gl^T-0)QBRgSI?QP-%M{2|7TrxK=_KdRw%xf z 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

    1co^I$wW1)fE+V3^ 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/SQLite.png b/doc/presentations/user2008/SQLite.png new file mode 100644 index 0000000000000000000000000000000000000000..e46caa8f8916c0e0d5503b26bedd4df79265233d GIT binary patch literal 3246 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/arch.svg.gz b/doc/presentations/user2008/arch.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..73acf5e147bdc12a648c2c0cf665d56cd294a580 GIT binary patch literal 3435 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 N{{d{k<|e{T007SdsSE%B literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/attr.svg.gz b/doc/presentations/user2008/attr.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..b93ea68494398acf619b7801c5a69ac2554cf7a5 GIT binary patch literal 3547 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 Yv?uuZ>U+O>H~0$YHcR6Z3;qE0KLS;g+5i9m literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/bipartite.svg.gz b/doc/presentations/user2008/bipartite.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..e5ec7d505ffa89573fd37493c0a17124b8379a52 GIT binary patch literal 1544 zcmV+j2KV_NiwFpQ4V*{-17c}#VRCe7bY(7cc4q+PSKV*hHV}W$U!f^4EoM<9^+`2O zQM4#fAbZ(5YocfNXi*yP3?mt`i3 zxk(T<4PGQ7&5G6B{PpukD>RK##YGzDqTqA05a!$Ki>n_j%Xlw&taxhdGqpB86}M#) zZ@KYmt<*NM?frg_vgQy8xw2mymIXJOZ>rZ%M(a9HJiq2hxzz6v%@ zU8NXf8)!Y})3~A%;<<(Yj=eoVrP#@YFX0|X1y}Zm&mTIXg;A=~ftWU&Bdwzt7x9Ld z+c@E+-JVzX_gSjebJKAG#AxWT>Tu1o)mlN)Lx#KnV^W=F>D&bCY4y^Y5_J`VkyDde z`9qTHk8hSiBabs8%Q5?{ac{7YK@iB3z=L4Z(58ass{Fnqd2#xoKk67SfHnRw5X1?n)eH1#x z4g49L5if9EGO6rgV3Cq>Q9^5M=H@0=GCRBiIGADh)8EzeYtyis9NTr0o3}2BEIY5$)$ndC-NJub)XEMonUo#Q_0rDx7v5Na5X-|R{V_}#^KY#ex;aQSIzX^HU zx3gx57l7w;vqL=%+exH*JXY7)1~R25=Z`S%pjJD=h*>vnFSHgbc|Aubmx(mlWcrf* zivpbdq?L52p?jh+w1ts@AAl*1m(|3xLRU@&N}hNJELQsbx#I1i2e)0Ca6#2pOW7XsL90PnN{ncp+6W-!;HNE(;o@$Q&aOu zVyDZZ-)ubZuPD`TG{j`aG`{P6vBxtvw6cI-LuVJSV=L=DjKHck*unrMqQy?Blg$j^ zipw|kI%8E^WUcM2%XLMyk)DyjN4^^{SkU@I>L2H+PlWGH`#azW#>yNwKvkrM%KF1o zWiKsslm1TD2W5DoKK>c_^?dJt55HaJBrJw!W1TL(C)QfDPXgK0`#`iC z)+t4=j^HpJ#Z3nj*7s_hPkS=r7|3#93!vT;^qw_!;Fy2DNS`0LzcC@EI35C7S_A7k>ik3&NW^7XSc~7XoSk literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/csardi.tex b/doc/presentations/user2008/csardi.tex new file mode 100644 index 0000000..d058304 --- /dev/null +++ b/doc/presentations/user2008/csardi.tex @@ -0,0 +1,842 @@ +\documentclass[landscape]{foils} + +\usepackage{ae} +%\usepackage{hyperref} +%\usepackage{thumbpdf} +\usepackage{graphicx} +\usepackage{color} +\usepackage[left=1cm,right=1cm,top=2cm,bottom=2cm]{geometry} +\usepackage{ragged2e} +\usepackage{amstext} +\usepackage{xspace} +\usepackage{fancyvrb} +\usepackage{amsmath} +\usepackage{url} +\usepackage{tabularx} +\usepackage{pause} + +\newcommand{\stitle}[1]{{\centering\color{blue}\Large #1\par\vspace*{10pt}\hrule}} +\newcommand{\cstitle}[1]{{\centering\color{blue}\Large #1\par\vspace*{10pt}\hrule}} + +\setlength{\columnsep}{0.5cm} +\setlength{\columnseprule}{0.4pt} + +\renewcommand{\emph}[1]{\textcolor{red}{\bf #1}} + +\newcommand{\igraph}{\texttt{{igraph}}\xspace} + +\DefineVerbatimEnvironment{Myverb}{Verbatim} +{numbers=left,numbersep=5mm,frame=lines,fontsize=\small} + +\newenvironment{narrow}[2]{% + \begin{list}{}{% + \setlength{\topsep}{0pt}% + \setlength{\leftmargin}{#1}% + \setlength{\rightmargin}{#2}% + \setlength{\listparindent}{\parindent}% + \setlength{\itemindent}{\parindent}% + \setlength{\parsep}{\parskip}}% + \item[]}{\end{list}} + +\newcommand{\bull}{$\bullet$\xspace} + +\newcommand{\command}[1]{\textcolor{red}{#1}} +\newcommand{\comment}[1]{\textcolor{blue}{#1}} + +\begin{document} + +\RaggedRight +% \color{white} +% \pagecolor{black} +\fvset{fontsize=\small} +\fvset{commandchars=\\\{\}} +\definecolor{grey}{gray}{0.75} +\fvset{frame=single, numbers=left, rulecolor=\color{grey}} + +\MyLogo{\color{black}igraph -- a package for network analysis} + +\thispagestyle{empty} +\vspace*{1cm} +{\centering +\hrule +\Large +\vspace*{1cm} +{\bf \textcolor{red}{igraph} -- a package for network analysis} +\vspace*{1cm} +\par +\hrule +\par +\vspace*{4cm} +\normalsize\textcolor{blue}{G\'abor Cs\'ardi}\\ +\small \textcolor{blue}{\texttt{Gabor.Csardi@unil.ch}} +\par +\vspace*{1.5cm} +Department of Medical Genetics, \\ +University of Lausanne, Lausanne, Switzerland\\ +} + +\newpage + +\stitle{Outline} + +% Outline: +\begin{enumerate} +\centering\Large +\vfill +\item Why another graph package?\\ +\vfill +\item igraph architecture, data model and data representation\\ +\vfill +\item Manipulating graphs\\ +\vfill +\item Features and their time complexity\\ +\vfill +\mbox{} +\vfill +\end{enumerate} + +\newpage +\stitle{Why another graph package?} +\begin{itemize} +\item \texttt{graph} is slow. \texttt{RBGL} is slow, too. +\marginpar{\vspace*{3.5cm}} +\marginpar{\hspace*{-8cm}\includegraphics{transitivity}} +\begin{Myverb} +> \command{ba2}\comment{ # graph \& RBGL} +\slshape A graphNEL graph with undirected edges +\slshape Number of Nodes = 100000 +\slshape Number of Edges = 199801 \pause +> \command{system.time(RBGL::transitivity(ba2))} +\slshape user system elapsed +\slshape 7.517 0.000 7.567 \pause +> \command{summary(ba)}\comment{ # igraph} +\slshape Vertices: 1e+05 +\slshape Edges: 199801 +\slshape Directed: FALSE +\slshape No graph attributes. +\slshape No vertex attributes. +\slshape No edge attributes. \pause +> \command{system.time(igraph::transitivity(ba))} +\slshape user system elapsed +\slshape 0.328 0.000 0.335 +\end{Myverb} +\end{itemize} + +\newpage +\stitle{Why another graph package?} +\begin{itemize} +\item \texttt{sna} is slow. \texttt{network} is slow, too. +\begin{Myverb} +> \command{net2}\comment{ # SNA \& network} +\slshape Network attributes: +\slshape vertices = 1e+05 +\slshape directed = TRUE +\slshape hyper = FALSE +\slshape loops = FALSE +\slshape multiple = FALSE +\slshape bipartite = FALSE +\slshape total edges= 199801 +\slshape missing edges= 0 +\slshape non-missing edges= 199801 +\slshape ... \pause +> \command{gtrans(net2)} +\slshape Error in matrix(0, nr = network.size(x), nc = network.size(x)) : +\slshape too many elements specified +\end{Myverb} +\end{itemize} + +\newpage +\stitle{Why another graph package?} +\begin{itemize} +\item \texttt{graph} is slow. \texttt{RBGL} is slow, too. +\item \texttt{sna} is slow. \texttt{network} is slow, too. +\item A generic solution was needed, i.e. a common C layer, that + can be interfaced from C/C++, R, Python, etc. +\end{itemize} \pause +\begin{center} +\includegraphics[width=0.8\textwidth]{schema} +\end{center} + +\newpage +\stitle{The igraph architecture} +\begin{center} +\vspace*{-2cm} +\includegraphics[width=0.8\textwidth]{arch} +\end{center} + +\newpage +\stitle{Dependencies} +\begin{itemize} +% TODO: better look, e.g. add logos +\item Standard C/C++ libraries. + \marginpar{\vspace*{-1.5cm}} + \marginpar{\hspace*{-6.5cm}% + \includegraphics[width=3cm]{source_c}% + \includegraphics[width=3cm]{source_cpp}% +% \includegraphics[width=3cm]{gccegg-65}% + }\\[-10pt]\pause +\item \texttt{stats} package, this is part of \texttt{base}. + \marginpar{\vspace*{0.3cm}} + \marginpar{\hspace*{-4cm}\mbox{ }% + \includegraphics[width=3cm]{Rlogo-2}}\\[-10pt]\pause +\item Optional: \texttt{libxml2} library, for reading \\ GraphML files + (included in Windows builds). + \marginpar{\vspace*{-2.3cm}} + \marginpar{\hspace*{-8.5cm}% + \includegraphics[width=4.5cm]{Libxml2-Logo-180x168} + }\\[-10pt]\pause +\item Optional: GMP library, graph automorphisms\\ + (not included in Windows builds). + \marginpar{\vspace*{-1.5cm}} + \marginpar{\hspace*{-6cm}% + \includegraphics[width=5.5cm]{gmplogo2} + }\\[-10pt]\pause +\item Suggested packages: \texttt{stats4}, \texttt{rgl}, + \texttt{tcltk}, \texttt{RSQLite},\\ + \texttt{digest}, \texttt{graph}, + \texttt{Matrix}.\\ +\begin{flushright} +\includegraphics[width=3cm]{Rlogo-2} +\includegraphics[width=3cm]{hist3d_2lights} +\includegraphics[width=2cm]{logo125} +\includegraphics[width=4cm]{SQLite} +\includegraphics[width=2cm]{hash_small} +\includegraphics[width=3cm]{plot} +\includegraphics[width=3cm]{matrix} +\end{flushright} +\end{itemize} + +\newpage +\stitle{The igraph data model, what cannot be represented} + +\begin{minipage}{0.6\textwidth} + ``Mixed'' graphs, with undirected and directed edges. + You can ``emulate'' them via graph attributes. +\end{minipage}\begin{minipage}{0.3\textwidth} + \includegraphics[height=0.65\textwidth]{mixed} +\end{minipage} \par \pause +\vspace*{-1cm} +{\raggedleft +\begin{minipage}{0.3\textwidth} + \raggedleft + \includegraphics[height=0.65\textwidth]{hyper} +\end{minipage}\begin{minipage}{0.6\textwidth} + Hypergraphs. Perhaps see the \texttt{hypergraph} package. +\end{minipage} \par +} \pause +\begin{minipage}{0.6\textwidth} +No direct support for bipartite (two-mode) graphs.\\ + It is possible to handle them via graph attributes. +\end{minipage}\begin{minipage}{0.3\textwidth} + \includegraphics[height=0.65\textwidth]{bipartite} +\end{minipage} + +\newpage +\stitle{Graph representation, sparse graphs} + +Flat data structures, indexed edge lists. Easy to +handle, good for many kind of questions. +\begin{center} + \includegraphics[width=0.55\textwidth]{igraph0} +\end{center} + +\newpage +\stitle{Graph representation, sparse graphs} + +Flat data structures, indexed edge lists. Easy to +handle, good for many kind of questions. +\begin{center} + \includegraphics[width=0.55\textwidth]{igraph1} +\end{center} + +\newpage +\stitle{Graph representation, sparse graphs} + +Flat data structures, indexed edge lists. Easy to +handle, good for many kind of questions. +\begin{center} + \includegraphics[width=0.55\textwidth]{igraph11} +\end{center} + +\newpage +\stitle{Graph representation, sparse graphs} + +Flat data structures, indexed edge lists. Easy to +handle, good for many kind of questions. +\begin{center} + \includegraphics[width=0.55\textwidth]{igraph2} +\end{center} + +\newpage +\stitle{Graph representation, sparse graphs} + +Flat data structures, indexed edge lists. Easy to +handle, good for many kind of questions. +\begin{center} + \includegraphics[width=0.55\textwidth]{igraph3} +\end{center} + +\newpage +\stitle{Graph representation, sparse graphs} + +Flat data structures, indexed edge lists. Easy to +handle, good for many kind of questions. +\begin{center} + \includegraphics[width=0.55\textwidth]{igraph} +\end{center} + +% \newpage +% \stitle{Vertex and edge ids} + +\newpage +\stitle{Creating graphs, via vertex ids} +% TODO: plot +\begin{Myverb} +> \command{g <- graph( c(0,1, 1,2, 2,3, 3,4), n=6, directed=TRUE )} +> \command{g} +\slshape Vertices: 6 +\slshape Edges: 4 +\slshape Directed: TRUE +\slshape Edges: + +\slshape [0] 0 -> 1 +\slshape [1] 1 -> 2 +\slshape [2] 2 -> 3 +\slshape [3] 3 -> 4 +\end{Myverb} +\begin{flushright} +\vspace*{-6cm} +\includegraphics[width=0.45\textwidth]{g1} +\end{flushright} + +\newpage +\stitle{Creating graphs, via vertex ids} +\begin{Myverb} +> \command{el <- cbind(0:9, 9:0)} +> \command{g <- graph( t(el), directed=TRUE)} +> \command{g} +\slshape Vertices: 10 +\slshape Edges: 10 +\slshape Directed: TRUE +\slshape Edges: + +\slshape [0] 0 -> 9 +\slshape [1] 1 -> 8 +\slshape [2] 2 -> 7 +\slshape [3] 3 -> 6 +\slshape [4] 4 -> 5 +\slshape [5] 5 -> 4 +\slshape [6] 6 -> 3 +\slshape [7] 7 -> 2 +\slshape [8] 8 -> 1 +\slshape [9] 9 -> 0 +\end{Myverb} +\begin{flushright} +\vspace*{-11.5cm} +\includegraphics[width=0.45\textwidth]{g2} +\end{flushright} + +\newpage +\stitle{Creating graphs, \texttt{graph.formula}} +\begin{Myverb} +\comment{# A simple undirected graph} +> \command{g <- graph.formula( Alice-Bob-Cecil-Alice, } +\command{ Daniel-Cecil-Eugene, Cecil-Gordon )} +> \command{g} +\slshape Vertices: 6 +\slshape Edges: 6 +\slshape Directed: FALSE +\slshape Edges: + +\slshape [0] Alice -- Bob +\slshape [1] Bob -- Cecil +\slshape [2] Alice -- Cecil +\slshape [3] Cecil -- Daniel +\slshape [4] Cecil -- Eugene +\slshape [5] Cecil -- Gordon +\end{Myverb} +\begin{flushright} +\vspace*{-9.5cm} +\includegraphics[width=0.45\textwidth]{g3} +\end{flushright} + +\newpage +\stitle{Creating graphs, \texttt{graph.formula}} +\begin{Myverb} +\comment{# Another undirected graph, ":" notation} +> \command{g2 <- graph.formula( Alice-Bob:Cecil:Daniel, } +\command{ Cecil:Daniel-Eugene:Gordon )} +> \command{g2} +\slshape Vertices: 6 +\slshape Edges: 7 +\slshape Directed: FALSE +\slshape Edges: + +\slshape [0] Alice -- Bob +\slshape [1] Alice -- Cecil +\slshape [2] Alice -- Daniel +\slshape [3] Cecil -- Eugene +\slshape [4] Cecil -- Gordon +\slshape [5] Daniel -- Eugene +\slshape [6] Daniel -- Gordon +\end{Myverb} +\begin{flushright} +\vspace*{-10.5cm} +\includegraphics[width=0.45\textwidth]{g4} +\end{flushright} + +\newpage +\stitle{Creating graphs, \texttt{graph.formula}} +\begin{Myverb} +\comment{# A directed graph} +> \command{g3 <- graph.formula( Alice +-+ Bob --+ Cecil } +\command{ +-- Daniel, Eugene --+ Gordon:Helen )} +> \command{g3} +\slshape Vertices: 7 +\slshape Edges: 6 +\slshape Directed: TRUE +\slshape Edges: + +\slshape [0] Bob -> Alice +\slshape [1] Alice -> Bob +\slshape [2] Bob -> Cecil +\slshape [3] Daniel -> Cecil +\slshape [4] Eugene -> Gordon +\slshape [5] Eugene -> Helen +\end{Myverb} +\begin{flushright} +\vspace*{-9.5cm} +\includegraphics[width=0.45\textwidth]{g5} +\end{flushright} + +\newpage +\stitle{Creating graphs, \texttt{graph.formula}} +\begin{Myverb} +\comment{# A graph with isolate vertices} +> \command{g4 <- graph.formula( Alice -- Bob -- Daniel, } +\command{ Cecil:Gordon, Helen )} +> \command{g4} +\slshape Vertices: 6 +\slshape Edges: 2 +\slshape Directed: FALSE +\slshape Edges: + +\slshape [0] Alice -- Bob +\slshape [1] Bob -- Daniel +> \command{V(g4)} +\slshape Vertex sequence: +\slshape [1] "Alice" "Bob" "Daniel" +\slshape [4] "Cecil" "Gordon" "Helen" +\end{Myverb} +\begin{flushright} +\vspace*{-9.5cm} +\includegraphics[width=0.45\textwidth]{g6} +\end{flushright} + +\newpage +\stitle{Creating graphs, \texttt{graph.formula}} +\begin{Myverb} +\comment{# "Arrows" can be arbitrarily long} +> \command{g5 <- graph.formula( Alice +---------+ Bob )} +> \command{g5} +\slshape Vertices: 2 +\slshape Edges: 2 +\slshape Directed: TRUE +\slshape Edges: + +\slshape [0] Bob -> Alice +\slshape [1] Alice -> Bob +\end{Myverb} +\begin{flushright} +\vspace*{-5.5cm} +\includegraphics[width=0.45\textwidth]{g7} +\end{flushright} + +\newpage +\stitle{Creating graphs, \texttt{graph.famous}} +\begin{Myverb} +> \command{graph.famous("Cubical")} +\slshape Vertices: 8 +\slshape Edges: 12 +\slshape Directed: FALSE +\slshape Edges: + +\slshape [0] 0 -- 1 +\slshape [1] 1 -- 2 +\slshape [2] 2 -- 3 +\slshape [3] 0 -- 3 +\slshape [4] 4 -- 5 +\slshape [5] 5 -- 6 +\slshape [6] 6 -- 7 +\slshape [7] 4 -- 7 +\slshape [8] 0 -- 4 +\slshape [9] 1 -- 5 +\slshape [10] 2 -- 6 +\slshape [11] 3 -- 7 +\end{Myverb} +\begin{flushright} +\vspace*{-11.5cm} +\includegraphics[width=0.45\textwidth]{g8} +\end{flushright} + +\newpage +\stitle{Creating graphs, \texttt{graph.data.frame}} +\begin{Myverb} +> \command{traits <- read.csv("traits.csv", head=F)} +> \command{traits} +\slshape V1 V2 V3 +\slshape 1 Alice Anderson 48 F +\slshape 2 Bob Bradford 33 M +\slshape 3 Cecil Connor 45 F +\slshape 4 David Daugher 34 M +\slshape 5 Esmeralda Escobar 21 F +\slshape 6 Frank Finley 36 M +\slshape 7 Gabi Garbo 44 F +\slshape 8 Helen Hunt 40 F +\slshape 9 Iris Irving 25 F +\slshape 10 James Jones 47 M +> \command{colnames(traits) <- c("name", "age", "gender")} +> \command{traits[,1] <- sapply(strsplit(as.character(traits[,1]), " "), "[", 1)} +\end{Myverb} + +\newpage +\stitle{Creating graphs, \texttt{graph.data.frame}} +\begin{Myverb} +> \command{relations <- read.csv("relations.csv", head=F)} +> \command{relations} +\slshape V1 V2 V3 V4 V5 +\slshape 1 Bob Alice N 4 4 +\slshape 2 Cecil Bob N 5 5 +\slshape 3 Cecil Alice Y 5 5 +\slshape 4 David Alice N 3 4 +\slshape 5 David Bob N 4 2 +\slshape 6 Esmeralda Alice Y 4 3 +\slshape 7 Frank Alice N 3 2 +\slshape 8 Frank Esmeralda N 4 4 +\slshape 9 Gabi Bob Y 5 5 +\slshape 10 Gabi Alice N 3 0 +\slshape 11 Helen Alice N 4 1 +\slshape 12 Iris Cecil N 0 1 +\slshape ... +> \command{colnames(relations) <- c("from", "to", "same.room", } +\command{ "friendship", "advice")} +\end{Myverb} + +\newpage +\stitle{Creating graphs, \texttt{graph.data.frame}} +\begin{Myverb} +> \command{orgnet <- graph.data.frame(relations, vertices=traits)} +> \command{summary(orgnet)} +\slshape Vertices: 10 +\slshape Edges: 34 +\slshape Directed: TRUE +\slshape No graph attributes. +\slshape Vertex attributes: name, age, gender. +\slshape Edge attributes: same.room, friendship, advice. +\end{Myverb} + +\newpage +\stitle{Creating graphs, \texttt{graph.data.frame}} +\begin{Myverb} +> \command{plot(orgnet, layout=layout.kamada.kawai, vertex.label=V(orgnet)$name, } +\command{ vertex.shape="rectangle", vertex.size=20, asp=FALSE)} +\end{Myverb} + % $ +\begin{center} +\vspace*{-2cm} +\includegraphics[width=0.8\textwidth]{orgnet} +\end{center} + +\newpage +\stitle{Creating graphs, random graphs} +\begin{Myverb} +> \command{er <- erdos.renyi.game(100, 100, type="gnm")} +> \command{plot(er, vertex.size=5, vertex.label=NA, asp=FALSE, vertex.shape="square",} +\command{ layout=layout.fruchterman.reingold, edge.color="black")} +\end{Myverb} +\begin{center} +\vspace*{-1cm} +\enlargethispage{2cm} +\includegraphics[width=0.75\textwidth]{er} +\end{center} + +\newpage +\stitle{Creating graphs, random graphs} +\begin{Myverb} +> \command{ba <- ba.game(100, power=1, m=1)} +> \command{plot(ba, vertex.size=3, vertex.label=NA, asp=FALSE, vertex.shape="square",} +\command{ layout=layout.fruchterman.reingold, edge.color="black", } +\command{ edge.arrow.size=0.5)} +\end{Myverb} +\begin{center} +\vspace*{-1cm} +\enlargethispage{2cm} +\includegraphics[width=0.75\textwidth]{ba} +\end{center} + +\newpage +\stitle{Meta data: graph/vertex/edge attributes} +\begin{itemize} +\item Assigning attributes: + \texttt{set/get.graph/vertex/edge.attribute}. \pause +\item \verb+V(g)+ and \verb+E(g)+. \pause +\item Easy access of attributes: + \begin{Myverb} +> \command{g <- erdos.renyi.game(30, 2/30)} +> \command{V(g)$color <- sample( c("red", "black"), } +\command{ vcount(g), rep=TRUE)} +> \command{V(g)$color} +\slshape [1] "red" "black" "red" "black" "black" "black" "red" "red" "red" +\slshape [10] "black" "black" "black" "red" "red" "black" "red" "black" "black" +\slshape [19] "red" "red" "black" "black" "red" "black" "black" "red" "black" +\slshape [28] "black" "black" "red" +> \command{E(g)$color <- "grey"} +\end{Myverb} +% $ +\end{itemize} + +\newpage +\stitle{Vertex/edge selection with attributes} +\begin{Myverb} +> \command{red <- V(g)[ color == "red" ]} +> \command{bl <- V(g)[ color == "black" ]} +> \command{E(g)[ red %--% red ]$color <- "red"} +> \command{E(g)[ bl %--% bl ]$color <- "black"} +> \command{plot(g, vertex.size=5, } +\command{ layout=layout.fruchterman.reingold, } +\command{ vertex.label=NA)} +\end{Myverb} +\vspace*{-5cm} +\begin{flushright} +\includegraphics[width=0.45\textwidth]{attr} +\end{flushright} + +\newpage +\stitle{Visualizing graphs} +\begin{itemize} +\item Three functions with (almost) identical interfaces. \pause +\item \verb+plot+ Uses traditional R graphics, non-interactive, 2d. + Publication quality plots in all formats R supports. +\begin{Myverb} +> \command{g <- barabasi.game(100, m=1)} +> \command{igraph.par("plot.layout", } +\command{ layout.fruchterman.reingold)} +> \command{plot(g, vertex.size=4, vertex.label=NA, } +\command{ edge.arrow.size=0.7, } +\command{ edge.color="black",} +\command{ vertex.color="red", frame=TRUE)} +\end{Myverb} +\end{itemize} +\begin{flushright} +\vspace*{-8cm} +\includegraphics[width=0.45\textwidth]{plot} +\end{flushright} + +\newpage +\stitle{Visualizing graphs} +\verb+tkplot+ Uses Tcl/Tk via the \verb+tcltk+ package, + interactive, 2d. +\begin{Myverb} +> \command{id <- tkplot(g, vertex.size=4, } +\command{ vertex.label=NA,} +\command{ edge.color="black",} +\command{ edge.arrow.size=0.7,} +\command{ vertex.color="red")} +> \command{coords <- tkplot.getcoords(id)} +\end{Myverb} +\begin{flushright} +\vspace*{-6cm} +\includegraphics{tkplot} +\end{flushright} + +\newpage +\stitle{Visualizing graphs} +\verb+rglplot+ Needs the \verb+rgl+ package. +\begin{Myverb} +> \command{co <- layout.kamada.kawai(g, dim=3)} +> \command{rglplot(g, vertex.size=5, } +\command{ vertex.label=NA, } +\command{ layout=co)} +\end{Myverb} +\begin{flushright} +\vspace*{-6cm} +\enlargethispage{3cm} +\includegraphics{rglplot} +\end{flushright} + +\newpage +\stitle{Working with a somewhat bigger graph} +\begin{Myverb} +> \command{vertices <- read.csv("http://cneurocvs.rmki.kfki.hu/igraph/judicial.csv")} +> \command{edges <- read.table("http://cneurocvs.rmki.kfki.hu/igraph/allcites.txt")} +> \command{jg <- graph.data.frame(edges, vertices=vertices, dir=TRUE)} +> \command{summary(jg)} +\slshape Vertices: 30288 +\slshape Edges: 216738 +\slshape Directed: TRUE +\slshape No graph attributes. +\slshape Vertex attributes: name, usid, parties, year, overruled, overruling, +\slshape oxford, liihc, indeg, outdeg, hub, hubrank, auth, authrank, between, incent. +\slshape No edge attributes. +\end{Myverb} + +\newpage +\stitle{Working with a somewhat bigger graph} +\begin{Myverb} +> \command{is.connected(jg)}\comment{ # Is it connected?} +\slshape [1] FALSE\pause + +> \command{no.clusters(jg)}\comment{ # How many components?} +\slshape [1] 4881\pause + +> \command{table(clusters(jg)$csize)}\comment{ # How big are these?} + +\slshape 1 3 4 25389 +\slshape 4871 8 1 1 \pause + +> \command{max(degree(jg, mode="in"))}\comment{ # Vertex degree} +\slshape [1] 248 +> \command{max(degree(jg, mode="out"))} +\slshape [1] 195 +> \command{max(degree(jg, mode="all"))} +\slshape [1] 313 +\end{Myverb} +%$ + +\newpage +\stitle{Working with a somewhat bigger graph} +\begin{Myverb} +\comment{# In-degree distribution} +> \command{plot(degree.distribution(jg, mode="in"), log="xy")} +\end{Myverb} +\begin{center} +\includegraphics[width=0.7\textwidth]{indd} +\end{center} + +\newpage +\stitle{Working with a somewhat bigger graph} +\begin{Myverb} +\comment{# Out-degree distribution} +\command{plot(degree.distribution(jg, mode="out"), log="xy")} +\end{Myverb} +\begin{center} +\includegraphics[width=0.7\textwidth]{outdd} +\end{center} + +\newpage +\stitle{Working with a somewhat bigger graph} +\begin{Myverb} +\comment{# Taking the largest component} +> \command{cl <- clusters(jg)} +> \command{jg2 <- subgraph(jg, which(cl$membership == which.max(cl$csize)-1)-1)} +> \command{summary(jg2)} +\slshape Vertices: 25389 +\slshape Edges: 216718 +\slshape Directed: TRUE +\slshape No graph attributes. +\slshape Vertex attributes: name, usid, parties, year, overruled, overruling, +\slshape oxford, liihc, indeg, outdeg, hub, hubrank, auth, authrank, +\slshape between, incent. +\slshape No edge attributes. +\end{Myverb} + +\newpage +\stitle{Working with a somewhat bigger graph} +\begin{Myverb} +> \command{graph.density(jg2)}\comment{ # Density} +\slshape [1] 0.0003362180\pause + +> \command{transitivity(jg2)}\comment{ # Transitivity} +\slshape [1] 0.1260031\pause + +\comment{# Transitivity of a random graph of the same size} +> \command{g <- erdos.renyi.game(vcount(jg2), ecount(jg2), type="gnm")} +> \command{transitivity(g)} +\slshape [1] 0.00064649\pause + +\comment{# Transitivity of a random graph with the same degrees} +> \command{g2 <- degree.sequence.game(degree(jg2,mode="all"), method="vl")} +> \command{transitivity(g2)} +\slshape [1] 0.004107072 +\end{Myverb} + +\newpage +\stitle{Community structure detection} +\begin{Myverb} +> \command{fc <- fastgreedy.community(simplify(as.undirected(jg2)))} +> \command{memb <- community.to.membership(jg2, } +\command{ fc$merges,} +\command{ which.max(fc$modularity)) } +> \command{lay <- layout.drl(jg2)} +> \command{jg3 <- graph.empty(n=vcount(jg2))} +> \command{colbar <- rainbow(5)} +> \command{col <- colbar[memb$membership+1] } +> \command{col[is.na(col)] <- "grey"} +> \command{plot(jg3, layout=lay, vertex.size=1, } +\command{ vertex.label=NA, asp=FALSE,} +\command{ vertex.color=col, } +\command{ vertex.frame.color=col)} +\end{Myverb} +%$ +\begin{flushright} +\vspace*{-9.5cm} +\includegraphics{jgplot} +\end{flushright} + +\newpage +\stitle{Functionality, what can be calculated?} +\renewcommand{\arraystretch}{1.7} +\begin{tabularx}{\textwidth}{l|X} +Fast (millions) & creating graphs (most of the time) \bull + structural modification (add/delete edges/vertices) \bull + subgraph \bull simplify \bull graph.decompose \bull + degree \bull clusters \bull graph.density \bull is.simple, + is.loop, is.multiple \bull articulation points and biconnected + components \bull ARPACK stuff: page.rank, hub.score, + authority.score, eigenvector centrality \bull transitivity \bull Burt's + constraint \bull dyad \& triad census, graph motifs \bull + $k$-cores \bull MST \bull reciprocity \bull modularity \bull + closeness and (edge) betweenness \it{estimation} \bull shortest + paths from one source \bull generating $G_{n,p}$ and $G_{n,m}$ + graphs \bull generating PA graphs with various PA exponents + \bull topological sort \\ +\hline +Slow (10000) & closeness \bull diameter \bull betweenness \bull all-pairs + shortest paths, average path length \bull most layout + generators \bull \\ +\hline +Very slow (100) & cliques \bull cohesive blocks \bull edge/vertex + connectivity \bull maximum flows and minimum cuts \bull + power centrality \bull alpha centrality \bull (sub)graph isomorphism\\ +\end{tabularx} + +\newpage +\stitle{Connection to other network/graph software} +\begin{itemize} +\item \texttt{graph} package: \texttt{igraph.to.graphNEL}, + \texttt{igraph.from.graphNEL}. \pause +\item Sparse matrices (\texttt{Matrix} package), + \texttt{get.adjacency} and \texttt{graph.adjacency} supports them. \pause +\item \texttt{sna} and \texttt{network} R packages. Currently throught + adjacency matrices. Use namespaces! \pause +\item Pajek. \texttt{.net} file format is supported. \pause +\item Visone. Use GraphML format. \pause +\item Cytoscape. Use GML format. \pause +\item GraphViz. igraph can write \texttt{.dot} files. \pause +\item In general. The \texttt{GraphML} and \texttt{GML} file formats + are fully supported, many programs can read/write these. +\end{itemize} + +\newpage +\stitle{Acknowledgements} +\begin{center} +\setlength{\parskip}{30pt} +\vfill +Tam\'as Nepusz\par\vfil +Peter McMahan, the BLISS, Walktrap, Spinglass, DrL projects\par\vfil +All the people who contributed code, sent bug reports, suggestions\par\vfil +The R project\par\vfil +\end{center} +\vfill\mbox{}\vfill + +\end{document} diff --git a/doc/presentations/user2008/er.svg.gz b/doc/presentations/user2008/er.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..8cf0d5b0258ab9d5e9205dbc8adc19f503946834 GIT binary patch literal 5776 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 RsIPaAV&5RMb>^|K{s(_&Z(aZZ literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/g1.svg.gz b/doc/presentations/user2008/g1.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..15ea30a767333da0c0330e2a57e9bc6507133a2d GIT binary patch literal 2783 zcmV<53Ly0#iwFpy6r4x^17|TVb9QF{KjN zS7*I5w(quA=Z9CjtBbS#KmY#cYU+Rf``>=Ld3Vvl%B!2R{_^(r`t;=F?(WXq1#>uF zoOo;P39RVLlGFG5-PLbP3ti6ngnrw1ce_`&muGzp{`$7>F1Ndj%iH?+ZnwSr$Km~1 zZ#&xohJSi}OxMGqUTt6Bz>f<&--kCZ5Bu%_OJ45}cW3>(-OcXhe%p7uS7-gj{=@a9 zU0HnT`o-;KcXRt;zdh@3ZjXoGwx`|I;cEN5?_gzr0!vPcw8x5dzc2Qe&RJuF^Fz1t zgPF#mTX&;L>Ul6RjQAMMgzKWQxd4IRTzB3?Ti&r|Oap#&hJLKvXnam6s3P7Bjy|p{ z7R*={MH465&zkbHo=*UHX%GQ*(PXrc@(BCz&TBzN6UzG0jH0!bk03Tr@xE#r`ZCQqKpCf`HW zT@s@URCNG3MX!c5wbmNVMTUrKM}Mf^N3{Nw==G0@-fUvA*$O;NC*L&8!UL$3MzY)d zN!+x-REom}ovqmC?!sqRgx@_AYEZbZ7>d4JPDZgnbH$C~Ptk zvke5x)_k2xNHuZEaE#Sphg=<778Vb}{*%s2I3@c}6P_Jsi)J~`B7@|gSNv5e2IIn& zcOcbD$f%5#Gp^v&<(C^UHlMX(9||wU>jt(bS5{uhu>W?OuJ5Oo`hEeS(T_1x{kV z^{l55u0TR+tdi$Uv~FGyZmKlVYHY11$}>?ct5B3Cl$%+#O0x)+U)I)1wQ3j`Y&~KQ zTsIok3Kia$1XkqXwaO)6qb{9nR|QsDThGuWn!6g1SS7fuR;N+rh_bt@evVuoklcl6 zD~H#d6mxGD(iDl}A^IsiF+8dyyOOF!;eUE~63#GXaZWYlgg; zS3;x#%Mp{S{TjA#{)Q3z3feZV6Zg)Jh8=hY1;Pr05KkbeeFMrG# z95_O_J$bhH@#*sp7q1T2FV1(jA5J^>yqlBB2^|T~xk44s`nSjZk3W#Bp7sSlzQs*p z`Tw@Z!+mNhXCYZEc}2%pTdnrHtL^!VYdG@Z?bWLp{Oe(Nbq_C^*(cBF|EG1i(t(jT zyW8z?zk?qD+UH#_4#KOija-27Crd7}>?qF;y7;L2{?Y2w3bFyNC>DR9#CA+`#T zypG7JOIZpC)Ib<49W^Ljf-wzwXCgRoSxeB_5jPO(n57f&NrvVx6N()iG^M#qgY@A5 zL$6nQY~WaI1xPMX$u#0z+9hvN!gCNDWmp}s`@sV-%#s0ip58+kCrli_y9C^h&6&t{ zq!^xKXhML!e8jVYENm&X8;-g%Qgc0w|g!}NjDwYoTG&-gRy5@u-067r|sDxdSKa~oLr-@BY z1W!|}pi-&LxKTy~w1KUxhWNlXbFiMaVtito0mD(H7Wt)w!;Z${n#f)_vklc4IVCfd^z>Y~Z?s zW?rgmgOLMW;lL(SfqH)h`#Q=hsrWBG^yc)uZYM0iZ%Q;Fiwb`??#7vGYBQIZTfxmW z4hAjmDw-3WudR}HICUoTC~!8)kgsGmaJGMgo2RNwa;DmtovqGSRx~A)kR^q`8+WVZ zBQ90Xjd4{nDsryInlZUb#s-u1mB8{$8Uohbmn`lm_ zZ<>fC>1s+J_Rrtk)8-J*X2h%Yi8db`&p4lV??8Lw@Ps$1;V<1g7QJC2MZNQ1YV%=9 lZ%myLZ@lB@-8?Nh95*l5>TPrN*UdZl@qg%ukn>kB001-`M3w*m literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/g2.svg.gz b/doc/presentations/user2008/g2.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..666ead7d5f6fc46cca0dfb2d2943d93f6c94393e GIT binary patch literal 4653 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 ZZ5Wrm+j4b(JiIS|{U3a=uK;&O007`Ej{X1u literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/g4.svg.gz b/doc/presentations/user2008/g4.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..fe184b854a77ca95f8a24f23b2196b22b081494e GIT binary patch literal 3871 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/g5.svg.gz b/doc/presentations/user2008/g5.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..48ec768b0781ecbdb297dcee77b6a067aa8e4945 GIT binary patch literal 6043 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/g6.svg.gz b/doc/presentations/user2008/g6.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..a225871ae46204b92dae2fc10770e2e131e1d5a8 GIT binary patch literal 3321 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 NzX0H9x-zpM001FV<_Q1* literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/g8.svg.gz b/doc/presentations/user2008/g8.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..eaf75593cf6009d4e974ccc41718d3006f5469d4 GIT binary patch literal 2533 zcmV8%CUap@m+Q-HH;_GtWHjAr^_GbBfcdO4dK6LkG zd)wS^pO^EC_I|rr|1m#rR_oRLbKAhmb^=Q#dAFBtYyN!Oe>I)zop*g`&P=cHbJ_%D zY`J%;3thVJ=*p_JJSZO=uADYTb$ZB~IF&9yVZMtAkuM7tmDe3T$(U6kUpb{sI!CMY zAr#U*Jf!J)LX1`3n(;0b7FB+#AV^c*0j?s^9fyqQB9V&4K}8!4%K@K>sutUx9Di#@ z!X0g;vp@xdGnq1{3`t9{SP5D{r!oeck>u$cqE7CojVEadXS%e%YluD{gG%fUG~F1J0a-+bUTb4amY** z?Rr&xFAj!#Xc-f40z^$S}@BQs_2mHNlq&U=GlYWlmc%fT^NOzK~qb0Qwd$8n^}sO6-!Wr$8$>N=AtrjA4Q;NrTgH z;27;B1{KtDsEX_(bxNSo8in!9DOb=H9=s6|h>0x+)8DM4%dGD3+8d>LoUpVSpQ_xOyJo zbM@SDb;@2+l?sk28BGKz8yBp2Siw@sfwc(L6IODQr07yvR~D94FBV2rjV!EMP?;D{ z0E&rOuEGV2`wol~gS@U+c65*83sOk;QXg^cVK|AGE-DgVd##4A?2MT1l^FvVY4%nP zr9|*55iW{g+n61+nB2XR-<`#xULIh?YGzWRncovoP*U8MU`C-#3MvU@I{?PA9f+c` z#4oxJimN6fBt6CTW1;o{Pe)jNjf2J)I_S>0XvVen-hCZbbZM9cPScB=f2dq6!CzXk zEEHYZ7oDD?9s|}bU(Uw*j*lGhKb zs}cPBda>HUGiUn4r}Y1$EU$PVlhfr5Oe%z#Bg6Ci*i9#4WF>m1kHrXRSn0ET+dRu9ej zfD9$IorZ#3g!+s;YDCpRJ&Ra&8UY~*&{=4 zfT)mS8`vQfi3aggV90jBkd6V!wHm7W)F~iG;5v?Qhzn6h`ad{72Hzk>2+NQ_eS{{? zWFN8+x6^gRS-nkx)6o3T{_jS*rW;qt zRmeI5$6h&)Z`4vGtMAj$Vzl4;5u@+8Ry9d5B~b3(7g5gY#|oUpINI;sh_Nc1M-`d} zA?yQn-%vkUa1`a?J>QK;E!I&7+7cT*SXj!{$8euax7q-Kl#veZ`EEpNdQC5C>b3~G zrKs^e%yArQah>Nzvuc-uCf7f(`@0dXGma-~y#XfDJOjKNIEpb@*%8=i=)U_jdYjd| zRCo#ohxY%oIi))~r@Yyp7iZ?7(GPln*7%UD^SJxre%_?NkUqcN%&#xnpX85>aZ+tv zNNrmhSX&*S^S+1xp(SKsz*m)XV_9oT-SvrXTtG-!BZ zzI{5|gkBB3WyZawI`VDL4(V)cZ1dPFD;#1#mpa6^J-o|84+H(xKxP~QmQ5w`+aBI$ vsYgR2q|7;_Xt4S>Jve5OTYSHceN{SmpueIjM_0e@AK~NwC~JIuLNNdU3boj} literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/gmplogo2.png b/doc/presentations/user2008/gmplogo2.png new file mode 100644 index 0000000000000000000000000000000000000000..6f229b12ef6f88ce30acb71c74964dc8baa51965 GIT binary patch literal 3817 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/hash_small.png b/doc/presentations/user2008/hash_small.png new file mode 100644 index 0000000000000000000000000000000000000000..4abf18e70259a2ec0f1cc64476d22d4d8bf2b770 GIT binary patch literal 23169 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 c2+T)PT;p}&R0t?&-|KU%Jc!2kdN literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/hist3d_2lights.jpg b/doc/presentations/user2008/hist3d_2lights.jpg new file mode 100644 index 0000000000000000000000000000000000000000..488b6638bd0c92c3bbda8a00f0b3c332c85faf66 GIT binary patch literal 15293 zcmdsdWk4L?vge?KTY}pFgS)#1C%C)2Lk1rbAZV~b13`iX*Wm8%?k)-L5C~+sZ}0!^ zd;9M0-naX0Pk%Ud`gB*-vFfg0)t|*bn*ba|Sp``D92@`u_i_RLtN`8skP(rPkr0uQ zk&uBvWE3L`WOvMR&C8_?J+7q9E ziqF#h7y0pQ*sE z*EZNs-GRep;`dDr!B}KlR~VfA$}79MLD?GBZ8dg&GO@}1W3GiiG3p!q0(3a-Qxsk= zJM#GFI|di!4}b(Tlq$A?)PjaqS`(OVwtdUq4QoC-pN#~Uy}TGoSANGl$8$+cgGa|x45B+?(*Ykwu+R0X;O zYd^YF*UtEB%$M^V>9Mj}p7OmxwBRx7A{I~|v`Tm{>K~h)xr;?Eb0eCa^sa`-hsz~Oecl%T@%)3g#f+~#ccSsx0hqQbHaF4NGnvgJxf zG<%t8wz!sU3iGE%xsXPaQ>fUPej8MF*wE#HD9E-kCOFNjI0F0@!Nq6@v=}|Z@JRDkd$H}i{Cl6TPZojWEj$$yJkIQ&u9Z-E*%h-jn;O$+ zyC;ogSrA#KTFPMY#$Skp72`>XKqO^rZWU>c?%m5eNBy&q5l6!~r86xVY$zS)n$w7R zfOm9OK(iK#y~0q3$@+RPm!e>)&}|RZ({9L3$1-fz@3q#kE5BZ(M#;rIa4PZU6q7c4 zmE|w%{uzWAy!I zc)Z?ydU292V~-lty{S+Oz}gIi>&G^_Je;n@-4yk}{Jm~l)| zuQ0%3*?CiZ`}z-nec>{1S*dxX($0hb%!=~-y+w;|T)yl24{@pY7P%wc%^p7=T9%CJ z*7l+ss7}U)doScjopqMg?m{T4K}F|eM+bPTs@`3{S$SutmAU|%$vduLxF*eoSS#_i^T3bKeO~M&^6y8@9vPpa@&`05%Q98S+Y|@p!D~jTpLWIf z)u1oq^Qy#`e?JC#M14vx8qmlqI&?cZ)GSkIuC2ryd_QLPxBJH?p!h$4FI^9{&tp0} z=f#|}5P7c~#uq+%goZCY@gX+~Pw6Md1~2=Z7I>KW$Qu&ZT_C4EN4+vzsFw&av-B&| z&Z)t`LZKsziYYq8v#%#4Y-kU`uSKzfU7>bTO6;pn3TC5)_K}>zayN!f$BWGi@1}>= z9<;kMQ-hJ z-D>WA;n+(Lv}Nha(Z`ubL#f=xjDjAjGL*T6F&zvyzY7uCT?@Bd8Vgzyz%cV#lpW?q zsr;(}DUq%U!!z-@C1?>-TYruH`MFk&qfqpNaqx0=!sWVGnBIGaMTU_HFZU1Jjh9{H zSnbDu06Oz$jo+rlgql05I|vGDzwkvE^g$^^eG9a_Fx`;(rQB$MRK5xA`K*o+n8e=C zx+y@xuc=URw2cA3!H$>rjS{Bi-&*iT4B;^h=`MD1ko8LvlSN!1C+qzI46SMY0W|&r z6!~e10FGP4qVyDqZ=ZBj>mIR~1F5z$4TCK(-xtcT@1e{561!wr$PWk7cn3X4l|44B zG&~GGkHxq^<#rO6>&Z9Rq3V4I9f>hw#E{ z;~a5=p&H!7)waRHiV3%;ms%bEErZd7-1UXTzB?fJ*tLCqx36ZGofO(XNp#FjZa*Q% zvG4itoCqf9W+#UEYmj(1?L!SGg)4TZ+U8A;TKd<%_>Vddu)OPo&faU*mYb}Z&8?`n z2N}s%uO8Yng>@0b<D3S!(&b!R~h1e8$XKmQ|pXs!my1 zTET?`!(=lyVSIA_BZs6=E<>8C1$JFtSsbeV&ujR#Xz zm7^_}d(w;Zrt72e2ICh#i@xs}=T@2w{QdwWcG^^s!ZrPm2DKYLHqH{rU7)W3xLd^6ng7gDADVeKmKU&m$b9@66F!NGZ>Gg}~0 z;WOT|@NeTPA5ZL?fRZ2AnDFj8*3mmoTB%ROJ1-%mJ<;WNnfF~7DGRCkC62}3%Cy1d zcXqCNTW!7@GySR3{PcHK-av(;XzTs$cUuzH7Qaj1nU&(LMACSnu1y>rXx1?s+dTL8 zkN!-VpTG5g%nwuDidRt*el@DyoJ{Wiuo!q>sOz$)t|>7)U}*l(``mo0jIB=YJa9z# zaQIU(tBtPbu1`+e)}fd#E5ZN*6;=TDW3gQ6NgOq9hNK6cj+) z#IhAPHeuAh4<8nN&&-edE3R-$qwX1}Z5vbJtj@O2Si#^s?ZA!vET@ie}j{Y+i=2XHa+s92+s-zu-Vh25oEUW z^Ii-3UJ*)IbD}e>@bS31B@Nygv9{pBYG=!`oSAi*f6&-=@tHb#YkZsEB*aH1xk3GY z_g4L>uPB-QAJnd0+v@^GjT}Zrb-bhk+<(2&^_TsHUiL`YtC5)(5IPTy+h$b5HH1*c zs#;U78boT`{6$ITCL|RwY0)w&QfnmT{_7RfncW5zB-`>nO0(8AzImehw+s_JvrFE^ z&~e`5Cv_&1zbFPC4UMD%#g7Gy(ux{M(*JTLV|+ro%l7^FJ+7ix%q5}zt$0L%gpBiS zprB~ddv^y~rGHS!Q7?>()shMrA{8|v{`IP~?XRg(nQE6h5*xU9U4&BJ95+B;6L-og zUF`Q4^>s=?Q6rh@Kd*qrsX&}`eMd_Ff!>UHTLc1aYpQ$=2!x}U2pbIf)o1<((9DkR zCw=b!ZDq?N8cO1vA1qh0!qJne4Gv7750LAw_i!iZATQ;u8}*)G&u1#O$0$m~1r1c> z6oB5DeFgM@)7k3TuhT%m`|Aw*2@<)wUe_KRdfUovZuw>(JSPy%(jj;%Jx}$)gfbyh z=DaL)HMX2R$zjj|kw@0G<~7PS@3;QG%j7g#3UjEDY{CV)PGs=I#}eaQZthuO`RA4P z4}k^iaut09F#@jY>4BF7#`y_WOgSjao{ey0ZVCb<)YFm>ekl3qmYg237JI~?zz{L; zvjr*;n5>|O>q25?wcdkBOjNmVwE6C`+_%@pvh7^BtcC6!KUVPv5^rHq@?OmTU+CoJ zuv3%D0$)LA)`j*J#5e8ttDu{Q-|S+Am6^tk{hJ^NiicF#Jy&Qr=bik5fRMe_O{STWYs^zbfnDmeR_91Ge z=(mSI!}Ip6V(LFA%-2c(>?j76I2xEFoYR#S>+6elQR6pd(s1EMhpx){#b#MVNq`qw zUCRP0x5f9|U%n?@+Na>>$H|VAh9{A^^!AqemhE<`g!ygasR|2--6{w3j|(8>77=u)Vp0+Ns^gRKq1eF z?V#y*=qHMf_uK8kM8^lGhzU|wzAAH0@x)S_T+z|H2MO1u#t=8x{-ZyDC+R(-J&UpP3sbDyMEQ};upV>LK#(lGV2UJXa z-)&~TrjEqBlp849zqMuwP{9w5{sjQWcQ{{f5{WXpEF3M7aFe<7&ISXan+ ze=9EYY3kaUU1i6FAXRn7sodIeNv#!!iIXDI%{^YogEF@P-*KHNhIR*C^p|50JZ|il8*K6kY^~_<`Z>*I*QXplvhq#_! zg2*c-9NPN!xl&a>^YgD1@D1;_tb1)ODYKR~b&A*MJ}{_=db8Zq{XNv%oHhA2=e@5z zBhpq&a{|N(NA^wi4+(L-F&SySDfQP(44rBmfXmn0%>FU-y8g^Ocq`uape%23*!pNm z%wWoXI3g@dB6EQC)zleSJh^=ls4Dl7EwF8+;|u=S1V%KZz++a_{2|`*GF-)Mq-hQ!A4s3ox6;F3COfXv#g;z6 zo1lI@qGy$MXfJYnz~!z^chM>hkNZR=a{}rt>W}xcGJ3|`x~BvJE|E5tL39AbgOq^C zC&XV?XhUu_WiyVkG4p=_iP*TSGKTkiPUYL?rgY_B(}OLMl$_*FmAFgSOBT=c7HdXc z0S1VBqx`OlW*qM@zc+<3`~iefvS*im6LGB6N7UzPe|@Sm?rX+D|GW{rtH){gdZ}>8Ok3DQEkUrz4$Ie|^|)q1COFrnU-DnmC#mr&}q4 zF~3yF=Fkk?&nefFbheryW<5ZZ*CurN5yaY(H3iF(zS_lfjvcL|ppDZek!_>RzYSY= zO!}+}zBV3HIU5KMEY`fl=QE|NP zKYpJ6Uo1~;!E?*E)eA~I6GPJ#qh->;$0p}pyl{#bfWS(|=B~%3n0DL=sY>(Q{gGZP zvfzqpg0Os^-K>bapNNNFG_t}5^DdqTDU7H`vo>DiKuu@YKsz4FWT_3j&I^nxc?cd7 zO><>lb1km&Imp9zPC5AWv3@B!vWfq|spg`syXKIv-rH}L33gYlWXm*`v+Joc=g_X)#Umc_v|-kY`4GJWnv0f_FBvY zCPZv2u#*)CmZM>%eFdv`E^ak%b*Qp$lVi`t*Crx>DPDX}va}?)`~o~?~7*(o(~9PqZle#yB2|G zyst#3yb}@O-`WYnE0BG-5D)cQV8zn%<;B+-p&itrkZXR?ToT`f$!F^xDOK3;llC^> zk?MAX8Nq248gzFsDozZ;VRJK%5_=*;S7tsBK?ZC`-oOY53L1s-j;O<()YqJ9xJi=x zl61FnNyg+9#2t;W-^5J^wqD*|Zi>#CWX225C&3=aAZv%0D@W=^U9T2H2a#K#Pv3&dp`eQvdMP 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/igraph0.svg.gz b/doc/presentations/user2008/igraph0.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..b480f6bb57a5f14e6092cbd9e9ce49b64cc33380 GIT binary patch literal 7724 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 YR0#1?N6(x&@n;-jUErA?Gt#U72MS{GR{#J2 literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/igraph1.svg.gz b/doc/presentations/user2008/igraph1.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..25d5963f59db102628519fd03bf49db43d706401 GIT binary patch literal 7741 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 PD;ci7zyOO1EX@A_KD2^G literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/igraph2.svg.gz b/doc/presentations/user2008/igraph2.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..a64cf5b9c102088db6f9ae9d38921f38940c3233 GIT binary patch literal 8518 zcmYkBWmH^E)2@RL?l8CqcN^S-JHg$8JHg#D!3pjb++72~gZm8b7Tklw$Mc>)=T}wt zzV`0Bt83L-y^A6m01zC>l=}c@Ywc#{V#CGe?rHtu)JV^rM!+p_!$iW~xTIehpktH? zJq!CtT92Kn3l_mxQN?v=DB9|V8iubxp^PFXXNu*iU$KNnC|Q2Iy8r%o z(dvvDJ(K3vl~J3zKo~O}t=^tpV|BgvayuCG^60$tY~Hk_lG-?8-po?Vo?03G`{kCM z;`NI#`C8T3NK1BX_hf6HOpr<7x)Oc--rql}E2le7b`M8dH&OlUQThVjkb-kd)uZQkcmG0QAve#P1A~>XLBhx6p*(ISjvQnBU=&H^Z67yz0{Slyr35%%s9pjN9##P@%z{VJ}XNB z9|r#TB-{3Ocwdi~g$IYH!$(OC{eTzE$#XHs)4PY0N6E`SmFySy%=1(Vp8^19hvbR2 z9%ThCu`A=+bMZ;B#5a4T5GfylH6>`{!`=eq@p@mQQTVrsD!EwfV6oukm8+3caxR0k z8k5U|hcTP&dV9|P)nj3I9Bw+0boI#g>AvZu`niQQ?KVBxhEzZceEUVN^S-j97p&*g zI>TSFSMYn}C|EH3E>P1!H1%_J>23MsaLdN+JdY~=?Q<+~pWpLoR^sYkyCOWr7qRQy zt1i)qnIqC8k%K!s6tA^mH1l8jQ|AW?IQyTdzE1~DMB^=B1ZgnIy4u2DaOefGE##~? zw^SZl2dqt1o;qWC)8F#&MlKL0m7bzR9$%dW;j5nAy}ADOa&BA-G+~IWNp@Oe)i$Z^ z9_PP~kDgVEB8Kc(J|U~{?dae!Cf)QU;@_stq$lmZq{liPsm53y+OWuf+!&hyea0)t zwgKI7zvcRZpt?{Kqz+`SEUORW&L5dK~gQJ{y~;`)ogY(EFu{p>~vg&y6?h zrD>@~CvU8h!sgbxJC1p$%RGK1???~GeBQNg%$jnCsL-CRwUGmMSV-EF5l96eM}tQ^ zMc)4XY=GQpzLmc270KO#y*#7XkNs}5FH9D$N3H_ZUw3Su#whZ<&ew89Ub0(C#i;cD z<;FSP`tLqG!HdP-%DUgkx$oxT(oIV1sgA_yrL*nFzn!_1`^@u@r!rgvergy@GvltH7FIziyGo;He+fO_6f}Ru~qJjcoGB?zx@*ThK_hrNyS`wbY zSWqEY`;=tk(`O`5&wcxmD9kwj$1cvTFEM!`uh@+OGJ;^%RHjP6gl99CoK)qcKf(DU z(sUm&Tdu-+Uvz>yd_&gP@)p=61p!x9a_g^+7RdLZvvU8eaa!tVG23B~v6xF~?u$7R zsHU%&`c?mVJgaEU$D*hVVJX3vP1^dSoxTX}E$^AN-&y^q0aa@1EnM|tO-6Yp1P_GW z0*K@YN#2Q%J{5X3qhW6284J){rlC)G&~T-g)aZGec3U+dmyVz5qr4-8KT}&Z*o_uK z--SqWt!%fD&oE@dppa^}@RNPsyotnB__>IU zE91m*VNO++JF0GloO6b@T${+#jYq}BildaG0@b>S9sH~b(m{7PCvij{(pv-Y4!+65# zj?wL$;BeZBVO1Q}4<`5K@^~#R=++}W_u0MuTg37w0Muz~J8g=5I`72Wpp2X>Joe!j zblXz6QV4Zj^89_%kz`Tt7Z>^%R-DdnbWmi)bo4rYKcm6QChI02{^W1rJsX0h8G}t2 zh%PftG;%HxR#=iitD<(jC~R0u0Z0X0paitT8cNwl>v+VDBQ6RQwmgFaG9{5A_J`1U zV~4Z#B!HbM$;R-D>0PDe7x0nQBCUYSwCeP2Da~W8v=Sh;S)4WOs(E?$FW>$wrRXzO zb}I3I?D5x94~F11ie_-Edb84x+BE*;)z|Ivf;Jb{&(lHS_FhtTe4pgM8U=p@;eCtNwP+Y>ED% zuDI^i|JIT<1v8jy=w>Ujo**;+_KQT`ir%!<;WiXj-5aLfwg8X|^zrN{05Lj(C=0g` z8qTKrXZGb8uOqLa=Ij7k8Deae;B!!xu7`4o^|798HWr_77R2T+P+r>WAHvfs!@7Am z^O=A(ipN)--X=YpcIZw@zA%uZpEtdbaDSjp5f`b?rT3LGwuvAU>Vu2r2HRTob6`~l zCp0UZn!8Hq6RUb-gF6WmN`Uu43(sg1dm zj+C7xFKHrERKeHiXbJ9Lwj6 zitwV+wY(D~_K|$!IXEU@X=4V zT6j&;)hqu{rNGM7e7!~AWwi1vreo~O*anyJ2_B1PD}W!_7X^yzY)sLoJkg`Hu^gHB zRaB7R6ec<>*i5I9PdD)rZCW@^|8y()@pW4>j%5=zM{52&*?}2ZZ%A1) z6l%R6+4ngX&kzS?3`YQO<|Nx4mXZ2i8Ax7|u_F}&b2z(aWUW-RUlZ_LZa+CZflM`#KHj`sN!p(l1 zg^DAV)|6jsDpkGp7RX9s8rcH+oeuM-$GEQ{a+t;IN4GFH9&|x@Zjo!Axpvkg@ii`* z?(;|w8SmJ5tFZEMmP{iBZlz-x_g_#H+^_ti=O}4TovWmdeJZnoQ@>8VR<@f5k-qE1 z+@<`r$-yd7F0ZiB8!K8hY-vHGPa8zFB>`JVxs++Sgf5YM#YFf@hmW3M?}a+ukS4y-*ePI zsTQT*0RDFCC!Us<=;m=FlhNmVXeP@;(cqC+>Cyiak_cEpbn3Kn0$79jn!ai;gwrhKSHC6n%e1U zSO=2kZku6v7|MfD*!G{0=Uq4hD*}iH2^z}*t9()hx;Lwb{ECM5vF9vLkZWlJ5w)35 z_K>g+n`#=MvsbYPtcZa8AO5?q{xG^*@WseVP4TH%2IxJb`Ff|qj8WoWWohv2iTEPg z#HZwUI(vG0W`UM{F-;*RRYt1`32s6n@G&_IAO@F~d1(`Ok_0LA{y>LTsBliRb|q8& z{#*$lczRVg(>8i(mXQxCj;3ObK3cnIkfeq_o_`%DF1lirWV+Gpx|12+)r{K=U6g+u zZ7;1okq3N^SqW%5p#6rWac?7%B-K{?^l)#xA+Mhs(Yj|_StX$TdAz6zK){&Z8@fd& zp^(_T;_c~{lS>8_n6Z{;xl}t|anOOoLck(kPOLJx;ci*1xE2g>ihgaf(Y2X}B_BDw zS>mfhAIhK2HbA3HKoeElf#;0;25Wv6jI(LgKG;Py6u-S39BcKxnIv{Un|E^CvkG9w z83+RJNi|O7ui5nJ9HHSWZUT2C;i3dDSiQ;H&P~tTj4E2Hm8+zMM$La7`tlZF(d9Qh z+T_>DHqIn{6-QM?!Xsp&H+2gQhi4{Ni>X2@u>s$NevQtoT5&AY1;OzAEanNkH;vwu z(R1L1v*RE7j%#qy14+uP)3%JoiKvw2Z%HuKLKO+?Ez#F8Q)_KSr{+^}j0L(Y{#0ns zvf;Ad2wt)*GA@kG%^*cG!f~%4n$*GQh2v4mhZd~Rr7V;yhZdBE_M;i%o1ZFw&ldOo zf;MAlB3@jU8dSN_n?sq%t1_K;LC-UcdXEI}#MPu{qF(#M(+y@K*aUNX2g6UgC} zSmU>K+VOs?Sp2}&+U|75XLS)x;=N?x9XSz^0d_U z?l{qnP6+&cw*h!R>HPBc2+xs^*H(013_9%Mv#YW-BsD(Xx1rmBr8w+5IKVOxrdc)W zflLeIZA?FfO^}R899jfhV88m(jJX-cG8lvE)&(0TvtL*o>!uE5{5p+JT&C;ds*wRhBK{ZKwoU=&8u`H`}NhaiCX;) zhWx&KeA1I!hkg}wflzE!D`>yx55!lyW=l=~g%_imoPO@%U*}qwEl-EZl=TXv&`>q? zGp|cm78bd0=G5pAr@QIxW!~P`V+nt@$u-~=Xn7%O$0j7`2 zITKnK8T9fq0%Z@Q9+N-zs{&kFGDQzx28UF~9z1`Z+!!2OmEP*-)usEhr`(^>9jIm1 zXIX50nMBlEGYH6G$G5Opr3v?3A!YUv>X;)i&>AYQD|1S8ZQmXUTr61J7RIQrsZzxi(-qG#M*EPE`uhbHh zBUV;EpOyD=+zO7+uo9jI={X1NEk|~rc2x26U!NiIXNivAN~gWG=dcq5EnwQ}CK55E zC-(mVD{eMCdTu1Hn_tx%8k)TS1|avi*$wRS_4U`Cn9paDZmVmk?CwXHlo5ga8MHKb z1zAO?spu88>la+d?3SbGY=o3$k#r9aEU6Cp@oOhE4Ws0Y91iH0dEe!%QSYxSGlJcK7+o=y?Vhj7cX?UMIqPsk$Chp*GE*sn%KlCIEVy_=YFt#0J4c!v zWHQ%FuMOYeKM!wDSKB==)N(s z=eH8zN;Z8_Cxc`m8wcp8d5s?XwIGLz?Zk7H(`X%%A%bXSaPf_cR{4R}JIr_?&EvU} za{h2JC?Ut0=o52ArFe^qX`$znu8cN|pA)SEJbJyd2*)M~ z5aBSW`tqka@(lmw_J&DYJ#hoS^O>XBeGN;PJs6224i?);`_cs$c~I=`(*tc3`z(0y zg_|jSA$4*OrWX50AG6}YRj_@e+*ZmvKsm6)8ljUliEo7IIsJW>B#haW5J~wUN-EON z*@FqThOTcT7klqv`v>(VkNNY+5ifqbv-vOP;a-B-7XdU-H~=i!`WRu^MN{)L!Vnt+ zp*Td2VW{9tqB6aq7Mt7|=|BO*`!{KJzeS?!B}e{SB7 zcDCPm?NhkF%kU&!?}cTP{+gf^zozdN*=rF)H$`4#~DzP1vw$`ho0eA>s)9^MT^OyKC?EY!sLQv>lfn1hN z&AXCUe|_YbLCsq{Kj5B3DTEqcGWL=`9ab7(N^Ir=LWsih0K|RYuEJU_O?)Gn;4xz5wwhDQB>zsQ1C8w?N5gPMZ1f2wO|QcK&2|tN(1p(g?2_c&QA-^#4k;49ml<@RzeK6x0 z4`UOtF~sikbeogCfp-6kXU0fPJ*#G>vat@AsNrYOag}y<7t51EH50NRJtNY|c14le z@6M7aFi|SJ8t&d(xgPAMvb2tocH2jkjGx<#@La4WKvF8sZ?{gReDkYZ#wq63dyilgJvn;7VR^d_w z8@7~|=Q49MC3U}>DTr)FB*hhtTTgJDyLq#j_)uY3X`a!GqfR&EOOb-Y%FP=$wR1ZK zNLBE&5LypoTtL5=_=LEii4k?Fq2xR(!g*cbo>Xi^B+WsLUe@=3l}3?;8oY;C1z#w< zC58i(&(wlsxfI>MrCBeM2Zgh~);jGpm3W`EEHlFl#|V0WR1_~oa8 zdBb}e$bDo-yHj0SRPZoiC}`03M{TddqX;)V_o-Gb!h1uptJ&9O*2XF#FR?B-hsAoEp6nH5#0?-wfz|;QTw(tCE4}|5Y~TbO||&`5txTY ztTWD`Eth9Kz&+3pck$vm0|~}QEr?)Q&y9cE&cw^Jdr}Z{i`3bcyl^q- zDgYs@KhZEgR=wNzgki6_$Y;YO)`90QTJ1tzUUwZ_3alCr^HVvQWGr)fH zyNE@)T`aisku}!ip^PV!WO>iagg+P(1n+70#{$d|U7x0eG4!9KjtJhamr5T5hQZw| z$_}waVOeoraaDDR{99@PU{lSThxZH9M-1_02Lwfe!l>LgCh|vRxnmvwu04p62us78 zz>sfo(3igheu#bsofjYIG3`!!bzIK_vFC%UMDE$n*4~#~mTDUWguylZ+f?j%moT{; zk7j`PPEi04+(qp%xBjPVIjnX&?nA&gi~ru21(9Yq`)x)eHUHa0t@rv}XKi9Hwh_-L zeG#$v9udM~|CV3Sw`eFROj=Sm^?oDzuCODB`kwp@5at(}JOv6XxH8?1@bxoKo-#?t zBLA=>v8D$Q1NtD$2CwS5bPmZ~J9C_|w3%2G-SGpx2v+Mj`!-GApZp%uJ$H6F8=q?h zl?kCz0cf20i4*?kyl*2X;zKw#B6w#~@uK>e=*!H>ls;n%q(79q+xTX4fKm z$KRd*;s2E03jwJYK`7=BkTgaJwE)SY_JJf<=sF*Oc{C|W**(lGi?}-(TAQ{$vE{O= zcQG;3NkkeagxZZO_&+@WVW3~<;>BtP(kE{#x?mgrc$}L2$0ty>}wA z%jWM=dKW}w|6Ajl^3J!*&)(%s$(JJ>{MDMP?R@jhtK#Hza?%QotL=Kz3B$eXysqKf zy5no3fCG{LihFo}XwkA+-UjJ5>9>;rez3Fx4llUNY|na^DgQC`ux06grhI)@dWET`&D-o%!32_3G3umu)pVfuyJ6j|>)T$heRXm`B!!DMLh>J0;2$s`d>l{70+F$!X(MDGL?6*?X!{X|y}MZjT{y zg;U0G=(-d(fHH3aE2kbv^#PK=ojOWM<$|{h)Ey2y-GPqz6xnNx=P&Ii)9YY$L{!Pf zqB|MsXNOMP5iD=cp@P7AWyu$NX!I2~)rom>Zzu!v&CZZY$AadY)N z9X{&4DdjoDiKO)%!~1*p1%>fSu2f2*paw^dozSeh?gi~qjm@cqE`Ji?r_;#@%g zO*Fhuuz!U(`a1^NRt-TESOgs7>^pN=_`ND``)>#PEjx3Ao_=(dK8k5r3gbS%q$mE8 zL-5PP8FkBu+%~dsbz*4!^sEz)}xorqlTSI0ZY)%a_|g?d?o zkLj~>~j?DnX1!n5nqQ1ZsCJFfDu!~D~}K$6l2y$Mf& z9ARU}*K^wjHlrgko$Eb_?`=~y%&l2B>q2z<y@HkcE~5w5doTi<2%_W z%Z)W>MOdVFX_?EAMEp<`4`r}x^;OqdzJS@XJqr%x6*MyT23LwsGLzpgrStM5MWd29 zOi!J)p;_LiXlKkp27kMfsDUCTa@k(&ep$&ntRZ9_)3NMvhu!c~l~q5jukNjLlIPfN zB(*(Kiw-1fYvrWzalE3vOI=KyiC(gbfkq4+Nbz=z0xR^iP#wI8$c0H$#=#ZI^Bg?< z<~(*C5>Ep!GFsfl*$fK_ zc`Z`$KI}ehe`528^4qlKwQIx6Jb6h8BX}blApQ2G=uG-5X6%kdQI0yAg<>tvTLKx9 zPowB)XsaolznCeu{OqpT^{m0B(@0eDxR#>|IUdtRNSqI9sJ@}dYgvgK)zN>Q>$iMq zH;m_B8S(YIZ8Cno@Bd%7_k2^lJsYz7|F=auUswmv2?ei$aJ3gBnl$S+X?F}Q(eX9m z69MawWbHp86L47&hu5;#(p*vI)(wiWiQB=ik9^YL3~Cir>MLq?;?K0SblIcOA^b_a z5iw!u$%$E`ot;q=I>YZn_sQql#!B5j_3Mp+PRz65^E2akVFP`p=ATIj&Na=Ouaery z6w^_=HM-)?%gF~Ba#?Y@0i!lh-=Lk9DvHIZ=YR literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/igraph3.svg.gz b/doc/presentations/user2008/igraph3.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..bf8c6ed200e0644cd05cae4d732e8709465f5924 GIT binary patch literal 9370 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/indd.svg.gz b/doc/presentations/user2008/indd.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..516ea1ac2955459c02fb908d49229f95547f96d8 GIT binary patch literal 9157 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%0A*DdriMG<{`6r1lF_@`~x%DMC=i}Lu$Q4Nc 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/logo125.png b/doc/presentations/user2008/logo125.png new file mode 100644 index 0000000000000000000000000000000000000000..c13cb9af7ad76fca7b77435360edc9b3e2bd1582 GIT binary patch literal 1627 zcmbu9`#aNp7{|Y*jU+`Sa+;xt$5XAO=FDcKCFfIeSc{sG@I-maG&MGDiuxvpwA7G9 zqB8OzTYXy&QBENea)_9g*z?Wx{0Gnbdf)f^x?cBveXh^vmk+~*=&)9Pi#z}TYaI#p zu5kXY1UVVFBjp;L;CSKU?2eQE79^DdQV5blpcDe6ASeaNQV@XYZw3p54MHFpf+3j< zkueY%fG`*c1N=7uL68ia43aS*1~xzjmlt6G3j<@gV=!b0LtdU^$N&a}ukio%B2Z)s zpRziV;By*I!gAq-CoO-&*hrh>a4x?b5Fs`Y0RSQIXm8^l`tkG4?%*u5^~rV0@)aAv z75bgV;az(w1{QN8>5ATgn#e)^2!3y@#&n!wD|E#=lelE0q)_l{T42m7_R{?hgnDJY z@1<(Gm%IfYHAm>R)bw69W`;FVy-L16UyXXene%-}zfTjiR`D|p&Hz3q-1!|RZ+~TM z<}I4U7BPZIJeIl0imEDfYh+(!JLGcwH{qhR%RF!U+o!)0F1)uBM_ZNU-18G9Sgfo6 zW<(txxm?-HEY0^`Zza1&#J2U+8G<4*C*CHhNqgx2Ki`xu?qHeHhZ3b3#2Otein0Fd zGpYe2WYDpP6>TdlGfcCqz4h0v2Wzx&M7O%(_+jxQgLlk9G;5%@pLD4@fRT*}I&Ngc z!UeFZ&-9uoltpv&Xy(LDoropMb+>F4Gg26*gi}u0T;S3^Y4SRRpdLM}%B!JaZZwt_ zKO*=UeeWYuw`3klZHN_hqEp(fp7-=;U7cLob?dywtp1WoKAo%~)U2JW3LR?c9t$R5 zbN@EsWaCvV8-bl%O_HO=QU~E7$6#mxx!Q^O{^O)@r{KN*qc4S8nV*-)(>Vcl+<35W-6%F{^z*#Z z?4pF(f^b@B;_T?qXh$ioI8v&rkO&YukIMunMs z0j#YB_5185nm$Z)T-?1deLPu%GOQ2lv+$r?>>& zyOjO692`kEC%fCo+~%kdTP;_k4T+>zgJPe)J4ezleUsyQCQhWPB-Lus+*cMAJ&rod z(@JzNnP>CzbCxW0J5hVSyE%D!?ETnQTRg!2^m#t-aUFddqad(=FNvWF@0~S$qG?F{ zBjLcBIS+zbv)%QLW8PdzML9b4kW8B)k{2^&OI}r>gfzTwYqHzMMX=+_t~{1@7?r4c zMfa&}T$`yvYpI+cYY4P-T=nu`o2ejWRFM7(mH5-svIkw~ZMeAGa0lT|a*3y9+*QTO z;Chse*sR~gb?CI-^c1mblR9$LSUL5b1Y{dGdnXa*cBS6!r%amZ5J*=?wVTy2X-WLb z_B!r<3lAhau94|Lu|9TmZCL!0HKM%bYv8YDY2eEI`$uJQZ+z*_;_Nv>kDzvY`Z*81 zg%GwRmWegchGFH$Am-8(uYB0T&UH5vfUbh$Mj6t*A$nUoa= Tgf0db;0?eLN3<`o4W#`G=Kk~m literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/matrix.jpg b/doc/presentations/user2008/matrix.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c14a4553c36efa6b18adabbfc126641ccf961f03 GIT binary patch literal 41619 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

    sMm_ zY)?@!;Mqvl+*a89@@|R}ps|7347{!V-p)dBfW9m0JLD{5ZqfTFa|D@b5@^jIjTef= z(9%|Cb+f8nmwbVQTTKmDYE}X09q-f#`dl3jsAop3+fO#i_0vV`*1*>qE8vtvs1@>= zwdT~RFBHlYNpfr&5qdJazo>ec5h#Q*q;zuIzP~bbWuYx{@$M zn_%#uOOs*6o#JJYf#${VHN8cH)>G$cCu?^77@&W-m=9?HHuc;jyJ;{3PMw{BtL_a) zYZvGNxC6_Mn|DvNpu)AE+$SCyEs)NgeZuA2ih5V=xazw?+r=L%PI`kfW^YUV$y TedLouFBqHC$#(DV-@N%BYU6K| literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/pause.sty b/doc/presentations/user2008/pause.sty new file mode 100644 index 0000000..3f31153 --- /dev/null +++ b/doc/presentations/user2008/pause.sty @@ -0,0 +1,185 @@ +%% pause.sty 18 Jun 2002 +%%----------------------------------------------------------- +%% History: +%% The initial version was created 11 Jun 1999 +%% Updated to allow options 10 Mar 2000 +%% Restructured after tests 03 Apr 2000 +%% Added procesing for random builds 07 May 2000 +%% Supply test for new enough pdftex 09 May 2000 +%% No need for pdftex 14 any more 31 May 2000 +%% Added commands for highligthing etc. 12 Jun 2000 +%% Added options ignore, nomarkers and dvipdfm +%% runs with vlatex and dvipdfm 10 Nov 2001 +%% Update comment section 13 Jun 2002 +%% Make catcodes safe for pdfliteral 18 Jun 2002 +%% +%% Because we need a color definition this can be used +%% primarily with LaTeX. Thus we can also rely on \@ifnextchar +%% and LaTeX option processing. +%% +%% insert small colored chunks to mark spots in the pdf file, +%% where a break should be made during display. +%% This method needs postprocessing by another program. +%% But one can preview the formatted text, where stops are indicated +%% as small colored chunks. These will vanish during postprocessing. +%% +%% Take care: because we have to insert pdf comments starting with +%% the letter '%' the catcode of '%' will be changed temporarily. + +%% must define all optional variants to support creating a file +%% without pdf elements + + +%% Options +\newif\ifpause@ignore \pause@ignorefalse +\newif\ifpause@nomarkers \pause@nomarkersfalse +\newif\ifpause@dvipdfm \pause@dvipdfmfalse + +\DeclareOption{ignore}{\pause@ignoretrue} +\DeclareOption{nomarkers}{\pause@nomarkerstrue} +\DeclareOption{dvipdfm}{\pause@dvipdfmtrue} + +\ProcessOptions + + +%% warning if ignore and nomarkers +\ifpause@ignore + \ifpause@nomarkers + \message{Warning, options ignore and nomarkers used together} + \fi +\fi + +%% We need to make sure that , and : are not active characters while +%% we read these macro definitions. +%% Save their current catcode settings now and restore them at the end +\def\pppp@x#1{\catcode`\noexpand#1=\the\catcode`#1\relax}% +\xdef\pppp@restoreCatcodes{\relax + \pppp@x\:\relax + \pppp@x\,\relax +} +\catcode`\,=12\relax +\catcode`\:=12\relax + +\def\pauseDissolve{\pause[trans='Dissolve']} +\def\pauseReplace{\pause[trans='Replace']} +\def\pauseHBlinds{\pause[trans='Blinds,H']} +\def\pauseVBlinds{\pause[trans='Blinds,V']} +\def\pauseHOSplit{\pause[trans='Split,H,O']} +\def\pauseHISplit{\pause[trans='Split,H,I']} +\def\pauseVOSplit{\pause[trans='Split,V,O']} +\def\pauseVISplit{\pause[trans='Split,V,I']} +\def\pauseOBox{\pause[trans='Box,O']} +\def\pauseIBox{\pause[trans='Box,I']} +\def\pauseWipe#1{\pause[trans='Wipe,#1']} +\def\pauseGlitter#1{\pause[trans='Glitter,#1']} +\def\pause{\@ifnextchar [{\pppp@pause}{\pppp@pause[]}} + + + +%% dvipdfm support +\ifpause@dvipdfm + {\catcode`\^^M=12 + \gdef\pdfliteral#1{\special{pdf: content + #1 + }}} + + \message{Using color settings for dvipdfm} + + \RequirePackage[dvipdfm]{color} + + %% This is for the support of the dvipdfm color philosophy + \def\pppp@gettail #1 #2:{#2} + \def\pppp@gethead #1 #2:{#1} + \def\pppp@tail #1{\expandafter\pppp@gettail #1:} + \def\pppp@tailtail #1{\expandafter\pppp@gettail\pppp@gettail #1::} + \def\pppp@head #1{\expandafter\pppp@gethead #1:} + \def\pppp@removebrackets[#1]{#1} + + + % maps dvipdfm color arguments to pdf color commands + \def\pppp@dvipdfmmap #1{\expandafter\pppp@grayorother#1:} + \def\pppp@dvipdfmmapper#1{\csname dvipdfmmapel@#1\endcsname} + \def\pppp@dvipdfmmapvalue#1{\expandafter\def\csname dvipdfmmapel@#1\endcsname} + + \def\pppp@grayorother#1#2:{\pppp@dvipdfmmapper{#1}[#1#2]} + \def\pppp@rgborcmyk #1 #2 #3 #4 #5 #6:{\pppp@dvipdfmmapper{#6}[#2 #3 #4 #5]} + + % for the latest dvipdfm.def-File + \pppp@dvipdfmmapvalue{c}[#1]{\pppp@tail{#1} k \pppp@tail{#1} K} + \pppp@dvipdfmmapvalue{r}[#1]{\pppp@tail{#1} rg \pppp@tail{#1} RG} + \pppp@dvipdfmmapvalue{g}[#1]{\pppp@tail{#1} g \pppp@tail{#1} G} + + % for dvipdfm.def with bg-error + \pppp@dvipdfmmapvalue{b}[#1]{\pppp@tail{#1} g \pppp@tail{#1} G} + + % support of the older format ([...],...) + \pppp@dvipdfmmapvalue{0}[#1]{#1 g #1 G} + \pppp@dvipdfmmapvalue{1}[#1]{#1 g #1 G} + \pppp@dvipdfmmapvalue{[}[#1]{\expandafter\pppp@removebrackets[\expandafter\pppp@rgborcmyk #1 :} + \pppp@dvipdfmmapvalue{}[#1]{#1rg #1RG} + \pppp@dvipdfmmapvalue{ }[#1]{#1 k #1 K} + + \def\pppp@colortostring #1{\expandafter\pppp@dvipdfmmap\csname\string\color@#1\endcsname} +\else + \RequirePackage{color} + \def\pppp@colortostring #1{\csname\string\color@#1\endcsname} +\fi + + +\definecolor{pp4red}{rgb}{0.9,0.5,0} + +%% We need to make sure that our argument parameters do not contain +%% active characters when we are called. This means we better set the +%% catcodes for some common delimiters in our arguments. Let's hope +%% that nobody is going to change the letters and digits, too. +\gdef\pppp@catcodes{% + \catcode`\+12\relax + \catcode`\-12\relax + \catcode`\:12\relax + \catcode`\=12\relax + } +%% now we need a section where % can be written to pdf files. +%% thus make ! a comment character +{\catcode`\%=11\catcode`\!=14 +\gdef\pppp@marker#1{! + \ifpause@ignore + \relax + \else + \pdfliteral{%pause}! + \ifpause@nomarkers + \relax + \else + \color{pp4red}\vrule width 2truemm height 5truemm\hss ! the marker + \fi + \pdfliteral{%esuap #1}! + \fi} + \gdef\pppp@pause[#1]{! + \ifvmode + \vbox to 0pt{\vss\hsize0pt\noindent\hbox to 0pt{\pppp@marker{#1}}}\else!hmode or mmode + \null\hbox to 0pt{\pppp@marker{#1}}\null! + !\null\hbox to 0pt{\pppp@marker{#1}}\null + \fi} + + + \ifpause@ignore + !! define dummies, if \pause should be ignored + \gdef\pauselevel#1{\relax}! + \gdef\pausecolors#1#2#3{}! + \gdef\pausecolorreset{\relax} + \gdef\pausebuild{\relax} + \gdef\pausehighlight{\relax} + \else + \gdef\pauselevel{\bgroup\pppp@catcodes\pppp@pauselevel}! + \gdef\pppp@pauselevel#1{\pdfliteral{%pauselevel #1}\egroup}! + \gdef\pausecolors#1#2#3{! + \pdfliteral{%pausecolor \pppp@colortostring{#1}\space \pppp@colortostring{#2}\space \pppp@colortostring{#3}}}! + \gdef\pausecolorreset{! + \pdfliteral{%pausecolor reset}}! + \gdef\pausebuild{! + \pdfliteral{%pausecolor appear}}! + \gdef\pausehighlight{! + \pdfliteral{%pausecolor highlight}}! + \fi +} + +\pppp@restoreCatcodes diff --git a/doc/presentations/user2008/plot.svg.gz b/doc/presentations/user2008/plot.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..01c733bc39f3356c3a7a51b6227fff555b5d114e GIT binary patch literal 20190 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 R{-*stLg4XZ9i=cF`43v-?FIk< literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/rglplot.png b/doc/presentations/user2008/rglplot.png new file mode 100644 index 0000000000000000000000000000000000000000..ca4855ee92b77eab7dbf09e81e0939eab9afc508 GIT binary patch literal 37054 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/schema.svg.gz b/doc/presentations/user2008/schema.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..1119be95444e906c1de1da8802b0288bf8976b68 GIT binary patch literal 2141 zcmV-j2%`5NiwFqr#+pa~19M|&Wo=tNjxevZ=IS6t{ z5Ws1R%LQ#e1r)iWn4`D?xw0hx^_^Y5Da!E+?Oky?oGi}F&d$!`w==x??y$+VdsbE% zFBUpBk*={K=1Eqh3;nlGKN`NSRU#^qDCY%R=mpomyM1@_7sJqgC|M*}qU|%W)_yKN zS8=pu+WWN-+t9N1`@NYpM3a}Pb*UK!tgPpFZqnK4zOIPdnSt8a8-9bn_u346@1%#W{&snn2!D8Z4s-9a5!Y(n8Nl7i*s2#Fw zcf2a$@iMBwOl!?I%!;e1OfqZF%g>c151FN9v|Ssv1%lSLWEI33f(c;+t5^I!tB`l1 zQ63GfjMr=vnXP*}YxquVcj9+;5Ui-p0(T62qu6qpIr7UW@kcvu;A@pJaEvH-Ouq%@ zO~O`{6sjF2pTJ{|;sLj_WeZBTESA|>OApkFqYU3zl-4oUM&8~2egM~Mp@tpxJu7l- zL?Gkf51N&ESm=~Ks;Du-;ANI((ig5%D;N@R)WW$<+AU~ zlJChctmqF4dqL0_dbAU~BbIFSZ#dEL`Cr55GrB5q%)zM~g1O51K7{sbMFps{krfr? z>^)$OdnLav`>yEhE--P;5?_!eY+6NxfAya$E&3c)}1qkei_!11qd7x1yJ z7YE#Rrcr?tgR`A0!;!(j#B>^he3s0InG`vb6X69?pK*%dt+=lJWk9o3im-rY>G4n? zlhmP*KOhw3)uDL9+QV;Cd!F}|gu-t_;X^3Ut0VD-R~>$vtIoq;NhItx5)u<%5r|J^ z78PmE{=O4;uMfqnwwDIO^T{+Aj_G;I{cu(Iz6wG8OGT&Q{x<_scLl02On*KIH{-oDFn4qYx2h@XY1NwVWf&okqrA%!MU@e(n%Qfz zo#GkGB|kL$IyrAPOhic}qCWLn3QTE!JEg(QAAbDsV^^bb9R3pq)4m9#K(vH`aiMpW zEN8zsl$8;Q+iU|VAP4-v!c-1L&@o2Ba?tCQ)@otWRpb8bJC7vZWO9-9tAL{ZS#Hvm zoh-J-(6$Z-=YXPY4yvBgiNEq_$?}DMCljQb=X!5KAG?KN7mQLVr1$Pq&+UQ%HmIL@ zBw%X56q$izyYkxfOt@woOfOqkr$YoysfTHgBSUDE>=Npd+Su*TNWmiZK&euXk!w;r zz_OhlP-#zvM%eTM-=^oN^BOwp!xYygwuTpRv|71NJt8!?5aKf^+wuEz3U&~~bINBp zr{8TRtDm?gaw+boQOIoS!_a_T`^2hlbRkHNF6vRT0L&o^^O|xW$qP%2XnR$GBNEun zM7(Zk(l{f^v$P0fz!X+ClpCJN(4p5MJ;c_R$$xsC) z0J~1zSsS~GHd%fQzlW3M*G8MbMtzVii?dZ0tG7tCNbk%|9;$a@0H5+*Q+W?p^o;;g z(HH1!80G`f1X8Z2HbDb=cMX2BHwt8-QPP&i294LV~Qx@^4NZbLHfII4fny1cd=&27AKgzQamw0n-gocJm zNQ8?ZmY*RWreifJb^PI=KMPmXHhtt0A4XpS!S>+GyBvc2K z-<7in(%;G_VB71Pt$OUJ@%ZvAB%ejB8L)Fu@TCCo=qjQ1ORkc6NPY(VPH-IN7kn0y zTM0U=)cbi{ZBXTBl$>k#}Yt=pf*=W(>{a2kSsiqo?#-9L_E&5IXe za{4IJP^55Xf=@8gKdl)g{|C*WL-1>xLC`_JoMsTLalSQ!|7$3DRWk@7^xmWy1VzCs zn!%5|X)AQ)%iuG T&5eA?dHe1^CrJVFBPIX9Y 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 G8t^}ac%t6` literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/tkplot.png b/doc/presentations/user2008/tkplot.png new file mode 100644 index 0000000000000000000000000000000000000000..eaab78c0fd3ab5e892accb9a9ea6dd9cfab0c78e GIT binary patch literal 14302 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/transitivity.svg.gz b/doc/presentations/user2008/transitivity.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..a63198c71806c0c0b38f59d79cb8425ce094ce7d GIT binary patch literal 1434 zcmV;L1!ejliwFqf5}Zf?19Wm>ZgXjLX?AIJc`kEyX8`S2TW{Mo6n@XI(3F=JvnW!c zuCZMVD+(0ofMM&f$AXb)o2@Jg5*^3+^*f|)maQ}$umXLUgTUsw@tp5FJffCw9`}Xy z!0SfHa%B^QZHt$g%tg6f*?)a}=ft+vC|2gIkR@N)rL^CyZj>VBs{UQn5#8 z>Iyl|xq2957j&cLw^^kP;90@{VBQN*BM)`PH{i!n$(8%|$V3e!;EKCpQWY-DC zCEN3+Vi|ATUT*x4B3HYW?NJX=FAgFj+;OqpDIh(;C=9|*D)N;L*?VTur;-jGf|1{< z`uOK68XDE4ohD&&nduZ)wyIg#2p~|X6Y9YlLdt`x4(bzsRJ?2>L2+l58nt#CWcC^Y zEOWkTbfC5hy`n||sv&j-$yGelz0;bWrKe6B+^MPGGGI+gu0B0Ny;f=kUWiCca00){ ztl_COB+wP`g=cM^R(3=w!U?7E%R#%e99fI40BrooLzqS$1#>^ZD2gNh!WGastDC?f zcMV{!#>o5{Fwp$u4}u>@An#ejyI2+5!jUUvy|QmN=F=(eWu5c7SB2&?QAub^AbV^I z%i7k^ma^c`3mM?9AsSQ%k@JbJQ^@O#0PR5cEe~^-|Sc}k8mO`l}}Rcb&m8> zkpbgQoS+zo5xsKDbYUJ)7*HHuDxvESdY(ClQYe@i)#H`>p{}(-!A>0TGhaP@%}yRQ zQY(AIih+p1^(acH#OY2rq4=ua&Z0l=Nph87XQ6xg8I?w|Z<;+>dvC%C^^oc$sG@%)&iWYq1BG^cejaW*_wFpqLoz z13Lz7!`8eVsBdQM}iibY+>4ZkK3RhI>}W((S7>tL+6&1-s|P%Ko64 z8&2M!-PZC@?Pbn8ZtW4f3D;&U>O#8-S-=DKl|#H3;fPRw;gHBjp-)Ntx`(=AYWK__ zV|6x+fJU7P5N)99^fsrAVSJ|-PIppLa@VL@e&OjYHlJ=e>Zj8LcJ=(&x39L==Y#|v zBKkoyqlQlMu!sE6Bk{Gm25-+tL8RSh)Y{uavDS}!Lq5M*Bnpu6S`6W>U#ub4VsNvt zMf4wlYE<89KNJ6HNMV*Zf)X70G!D5FF7WH2(I%Yz0Li}q@3)fk0=4sv(KnM%g5Y~J z>sOA%JZA|-`iA8FPuPouMLz;zNz^77EQsG?>_0=?d6Ff(%(NNShk~d4ftNDRpGnd@ zTT`!J7NX=Z)zkZfQs=VIQj{q?UU=P0&7u(SmC|08Gq`)yH9NIAJc~oyW?|cn1R5kn zA&nC219VNt_`)X%3UGkw96QN83Zq4Mv6=5jh+^VHk9rYGs7Hz3EesG1f|$T=G4cow z{8`$6TtvS|Gm!r}b$?;`=Tf=ZG+d=$IVjy6in?AH=fnp~_eIw>SI7H|e}9BV!Mj#( z0T-^+yhMT_T&8&az^@pWa9M4{6Hgk!5Ck`awRFaj6or2_2B@lrbubE%QnmDr=!_-Y4m oPR``ByI<_TezSCq?;5^g|Mr%BQKO7PO0K(nYWB>pF literal 0 HcmV?d00001 diff --git a/doc/presentations/wien08/3dplot.png b/doc/presentations/wien08/3dplot.png new file mode 100644 index 0000000000000000000000000000000000000000..3dadddeab5b8b5af6b624e5f7bd7ded4e512fedb GIT binary patch literal 289931 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�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 E0RWUtQUCw| literal 0 HcmV?d00001 diff --git a/doc/presentations/wien08/adjlist.pdf b/doc/presentations/wien08/adjlist.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c38af061f89eacb8070fe0073749eec6f2f84286 GIT binary patch literal 28504 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%0D4+`Bks&y}D(_uC)}liErQ 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

    93t{z;p$79}ojZi5U$h=k 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 + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/presentations/wien08/ercomps.pdf b/doc/presentations/wien08/ercomps.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6cefb361969f1f82c7ee67caf561de4a7c32d7cc GIT binary patch literal 23975 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 z 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 OKl3kBipxtNBm94KTh@^P literal 0 HcmV?d00001 diff --git a/doc/presentations/wien08/ex-close.pdf b/doc/presentations/wien08/ex-close.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ee3cd2c690938db3d9abdf28c7d3e9f5b6303316 GIT binary patch literal 31192 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/wien08/ex-deg.pdf b/doc/presentations/wien08/ex-deg.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ee637dc12613bc66fd17e3329878e1a78c9b73a6 GIT binary patch literal 30605 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 BWP$(y literal 0 HcmV?d00001 diff --git a/doc/presentations/wien08/ex-ev.pdf b/doc/presentations/wien08/ex-ev.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4903fdd8c0e830347f57e392e8cc17d830cb7848 GIT binary patch literal 31123 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 UJZ46A4t5{~Gr5S2C?eed0S{ju1^@s6 literal 0 HcmV?d00001 diff --git a/doc/presentations/wien08/example.graph.Rdata b/doc/presentations/wien08/example.graph.Rdata new file mode 100644 index 0000000000000000000000000000000000000000..2284f00cfe24e34fd404ab1af465d1e5ed07d585 GIT binary patch literal 362 zcmV-w0hRtAiwFP!000001C^6MPs1<}#oZ)?Hb^K^J1}%Ws6q%K1cjXd=!ZtfZ7nhLnV&(A*FzjNZ3$#fh|BO!z*ylvC^rW<&s=?D?`tY$*^ebcdK z*A65jfr#qFRoJ<~)mLqjF4LIlfa%@_9sX1InI1G1oG^}8G{bSj$L;yZTicJ<&Ff_I z?%d(uIDg%r-v{o2zFl%idPI6mdO~_idPaIqIwrkn=X6}7X1?PG7tY5z@Pik1fg7CQ zL7m_RALnm!?g$p(TKjI`0Wa#o`M8J6_Wd0{Uk|EJh8P-2J1>;qmUUgtVhWc-_t8OCo_s&kl$mW3qKyQ}7^l=&+L!h1Q>svs9U z=uBhXqsrYx_#}%$SLC|S%D6XG*5KZfxKKb~3#hq8Yk_GltBNfObymvHcUrdi26Y=w IPUHjt0B%~g0{{R3 literal 0 HcmV?d00001 diff --git a/doc/presentations/wien08/example.pdf b/doc/presentations/wien08/example.pdf new file mode 100644 index 0000000000000000000000000000000000000000..477e726bb92c179ac19af41a404eb84f7f3777ca GIT binary patch literal 29572 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 E0~Ux)7XSbN literal 0 HcmV?d00001 diff --git a/doc/presentations/wien08/frplots.pdf b/doc/presentations/wien08/frplots.pdf new file mode 100644 index 0000000000000000000000000000000000000000..aadfd8ae02aec78a50356b0e0e6a02cfd701a08c GIT binary patch literal 17288 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

    xpE%y?M__a*9 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/wien08/karate.net b/doc/presentations/wien08/karate.net new file mode 100644 index 0000000..591670e --- /dev/null +++ b/doc/presentations/wien08/karate.net @@ -0,0 +1,80 @@ +*vertices 34 +*edges +1 32 +1 22 +1 20 +1 18 +1 14 +1 13 +1 12 +1 11 +1 9 +1 8 +1 7 +1 6 +1 5 +1 4 +1 3 +1 2 +2 31 +2 22 +2 20 +2 18 +2 14 +2 8 +2 4 +2 3 +3 14 +3 9 +3 10 +3 33 +3 29 +3 28 +3 8 +3 4 +4 14 +4 13 +4 8 +5 11 +5 7 +6 17 +6 11 +6 7 +7 17 +9 34 +9 33 +9 33 +10 34 +14 34 +15 34 +15 33 +16 34 +16 33 +19 34 +19 33 +20 34 +21 34 +21 33 +23 34 +23 33 +24 30 +24 34 +24 33 +24 28 +24 26 +25 32 +25 28 +25 26 +26 32 +27 34 +27 30 +28 34 +29 34 +29 32 +30 34 +30 33 +31 34 +31 33 +32 34 +32 33 +33 34 diff --git a/doc/presentations/wien08/lesmis.txt b/doc/presentations/wien08/lesmis.txt new file mode 100644 index 0000000..60ca3af --- /dev/null +++ b/doc/presentations/wien08/lesmis.txt @@ -0,0 +1,254 @@ +Myriel Napoleon +Myriel MlleBaptistine +Myriel MmeMagloire +MlleBaptistine MmeMagloire +Myriel CountessDeLo +Myriel Geborand +Myriel Champtercier +Myriel Cravatte +Myriel Count +Myriel OldMan +Labarre Valjean +MmeMagloire Valjean +MlleBaptistine Valjean +Myriel Valjean +Valjean Marguerite +Valjean MmeDeR +Valjean Isabeau +Valjean Gervais +Tholomyes Listolier +Tholomyes Fameuil +Listolier Fameuil +Tholomyes Blacheville +Listolier Blacheville +Fameuil Blacheville +Tholomyes Favourite +Listolier Favourite +Fameuil Favourite +Blacheville Favourite +Tholomyes Dahlia +Listolier Dahlia +Fameuil Dahlia +Blacheville Dahlia +Favourite Dahlia +Tholomyes Zephine +Listolier Zephine +Fameuil Zephine +Blacheville Zephine +Favourite Zephine +Dahlia Zephine +Tholomyes Fantine +Listolier Fantine +Fameuil Fantine +Blacheville Fantine +Favourite Fantine +Dahlia Fantine +Zephine Fantine +Marguerite Fantine +Valjean Fantine +Fantine MmeThenardier +Valjean MmeThenardier +MmeThenardier Thenardier +Fantine Thenardier +Valjean Thenardier +MmeThenardier Cosette +Valjean Cosette +Tholomyes Cosette +Thenardier Cosette +Valjean Javert +Fantine Javert +Thenardier Javert +MmeThenardier Javert +Cosette Javert +Valjean Fauchelevent +Javert Fauchelevent +Fantine Bamatabois +Javert Bamatabois +Valjean Bamatabois +Fantine Perpetue +Perpetue Simplice +Valjean Simplice +Fantine Simplice +Javert Simplice +Valjean Scaufflaire +Valjean Woman1 +Javert Woman1 +Valjean Judge +Bamatabois Judge +Valjean Champmathieu +Judge Champmathieu +Bamatabois Champmathieu +Judge Brevet +Champmathieu Brevet +Valjean Brevet +Bamatabois Brevet +Judge Chenildieu +Champmathieu Chenildieu +Brevet Chenildieu +Valjean Chenildieu +Bamatabois Chenildieu +Judge Cochepaille +Champmathieu Cochepaille +Brevet Cochepaille +Chenildieu Cochepaille +Valjean Cochepaille +Bamatabois Cochepaille +Thenardier Pontmercy +Thenardier Boulatruelle +MmeThenardier Eponine +Thenardier Eponine +Eponine Anzelma +Thenardier Anzelma +MmeThenardier Anzelma +Valjean Woman2 +Cosette Woman2 +Javert Woman2 +Fauchelevent MotherInnocent +Valjean MotherInnocent +Fauchelevent Gribier +Jondrette MmeBurgon +MmeBurgon Gavroche +Thenardier Gavroche +Javert Gavroche +Valjean Gavroche +Cosette Gillenormand +Valjean Gillenormand +Gillenormand Magnon +MmeThenardier Magnon +Gillenormand MlleGillenormand +Cosette MlleGillenormand +Valjean MlleGillenormand +MlleGillenormand MmePontmercy +Pontmercy MmePontmercy +MlleGillenormand MlleVaubois +MlleGillenormand LtGillenormand +Gillenormand LtGillenormand +Cosette LtGillenormand +MlleGillenormand Marius +Gillenormand Marius +Pontmercy Marius +LtGillenormand Marius +Cosette Marius +Valjean Marius +Tholomyes Marius +Thenardier Marius +Eponine Marius +Gavroche Marius +Gillenormand BaronessT +Marius BaronessT +Marius Mabeuf +Eponine Mabeuf +Gavroche Mabeuf +Marius Enjolras +Gavroche Enjolras +Javert Enjolras +Mabeuf Enjolras +Valjean Enjolras +Enjolras Combeferre +Marius Combeferre +Gavroche Combeferre +Mabeuf Combeferre +Gavroche Prouvaire +Enjolras Prouvaire +Combeferre Prouvaire +Gavroche Feuilly +Enjolras Feuilly +Prouvaire Feuilly +Combeferre Feuilly +Mabeuf Feuilly +Marius Feuilly +Marius Courfeyrac +Enjolras Courfeyrac +Combeferre Courfeyrac +Gavroche Courfeyrac +Mabeuf Courfeyrac +Eponine Courfeyrac +Feuilly Courfeyrac +Prouvaire Courfeyrac +Combeferre Bahorel +Gavroche Bahorel +Courfeyrac Bahorel +Mabeuf Bahorel +Enjolras Bahorel +Feuilly Bahorel +Prouvaire Bahorel +Marius Bahorel +Marius Bossuet +Courfeyrac Bossuet +Gavroche Bossuet +Bahorel Bossuet +Enjolras Bossuet +Feuilly Bossuet +Prouvaire Bossuet +Combeferre Bossuet +Mabeuf Bossuet +Valjean Bossuet +Bahorel Joly +Bossuet Joly +Gavroche Joly +Courfeyrac Joly +Enjolras Joly +Feuilly Joly +Prouvaire Joly +Combeferre Joly +Mabeuf Joly +Marius Joly +Bossuet Grantaire +Enjolras Grantaire +Combeferre Grantaire +Courfeyrac Grantaire +Joly Grantaire +Gavroche Grantaire +Bahorel Grantaire +Feuilly Grantaire +Prouvaire Grantaire +Mabeuf MotherPlutarch +Thenardier Gueulemer +Valjean Gueulemer +MmeThenardier Gueulemer +Javert Gueulemer +Gavroche Gueulemer +Eponine Gueulemer +Thenardier Babet +Gueulemer Babet +Valjean Babet +MmeThenardier Babet +Javert Babet +Gavroche Babet +Eponine Babet +Thenardier Claquesous +Babet Claquesous +Gueulemer Claquesous +Valjean Claquesous +MmeThenardier Claquesous +Javert Claquesous +Eponine Claquesous +Enjolras Claquesous +Javert Montparnasse +Babet Montparnasse +Gueulemer Montparnasse +Claquesous Montparnasse +Valjean Montparnasse +Gavroche Montparnasse +Eponine Montparnasse +Thenardier Montparnasse +Cosette Toussaint +Javert Toussaint +Valjean Toussaint +Gavroche Child1 +Gavroche Child2 +Child1 Child2 +Babet Brujon +Gueulemer Brujon +Thenardier Brujon +Gavroche Brujon +Eponine Brujon +Claquesous Brujon +Montparnasse Brujon +Bossuet MmeHucheloup +Joly MmeHucheloup +Grantaire MmeHucheloup +Bahorel MmeHucheloup +Courfeyrac MmeHucheloup +Gavroche MmeHucheloup +Enjolras MmeHucheloup diff --git a/doc/presentations/wien08/presentation.tex b/doc/presentations/wien08/presentation.tex new file mode 100644 index 0000000..af6c293 --- /dev/null +++ b/doc/presentations/wien08/presentation.tex @@ -0,0 +1,649 @@ +\documentclass[landscape,fleqno]{foils} + +\usepackage{ae} +%\usepackage{hyperref} +%\usepackage{thumbpdf} +\usepackage{graphicx} +\usepackage{color} +\usepackage[left=1cm,right=1cm,top=2cm,bottom=2cm]{geometry} +% \usepackage[display]{texpower} +%\usepackage{psfrag} +\usepackage{ragged2e} +\usepackage{amstext} +\usepackage{xspace} +\usepackage{fancyvrb} +\usepackage{amsmath} +\usepackage{url} +\usepackage{pause} + +\newcommand{\figfigure}[2]{% + \begin{psfrags}% + \input #2.eps_t% + \includegraphics[width=#1]{#2.eps}% + \end{psfrags}% +} + +\newcommand{\stitle}[1]{{\color{blue}\Large #1\par\vspace*{10pt}\hrule}} +\newcommand{\cstitle}[1]{{\centering\color{blue}\Large #1\par\vspace*{10pt}\hrule}} + +\setlength{\columnsep}{0.5cm} +\setlength{\columnseprule}{0.4pt} + +\renewcommand{\emph}[1]{\textcolor{red}{\bf #1}} + +\newcommand{\igraph}{\texttt{{igraph}}\xspace} + +\DefineVerbatimEnvironment{Myverb}{Verbatim} +{gobble=2,numbers=left,numbersep=5mm,frame=lines,fontsize=\small} + +\newenvironment{narrow}[2]{% + \begin{list}{}{% + \setlength{\topsep}{0pt}% + \setlength{\leftmargin}{#1}% + \setlength{\rightmargin}{#2}% + \setlength{\listparindent}{\parindent}% + \setlength{\itemindent}{\parindent}% + \setlength{\parsep}{\parskip}}% + \item[]}{\end{list}} + +\begin{document} + +\RaggedRight +% \color{white} +% \pagecolor{black} +\fvset{fontsize=\small} +\fvset{commandchars=\\\{\}} +\definecolor{grey}{gray}{0.75} +\fvset{frame=single, numbers=left, rulecolor=\color{grey}} + +\MyLogo{\color{black}Practical statistical network analysis -- WU Wien} + +\thispagestyle{empty} +\vspace*{1cm} +{\centering +\hrule +\Large +\vspace*{1cm} +{\bf Practical statistical network analysis\\ (with \emph{R} and \igraph)} +\vspace*{1cm} +\par +\hrule +\par +\vspace*{2cm} +\normalsize G\'abor Cs\'ardi\\ +\small \verb+csardi@rmki.kfki.hu+ +\par +\vspace*{1.5cm} +Department of Biophysics, +KFKI Research Institute for Nuclear and Particle Physics of the\\ +Hungarian Academy of Sciences, Budapest, Hungary\\[15pt] +Currently at \\Department of Medical Genetics, \\ +University of Lausanne, Lausanne, Switzerland\\ +} + +\newpage +\stitle{What is a network (or graph)?} +\includegraphics[width=0.5\textwidth]{frplots} + +\newpage +\cstitle{What is a network (or graph)?} +\begin{center} +\includegraphics[width=0.6\textwidth]{ercomps} +\end{center} + +\newpage +\cstitle{What is a network (or graph)?} +\begin{center} +\includegraphics[width=0.8\textwidth]{3dplot} +\end{center} + +\newpage +\cstitle{What is a graph?} +\begin{itemize} +\item Binary relation (=\emph{edges}) between elements of a set + (=\emph{vertices}). \pause\\[-15pt] +\item E.g. + \begin{align} + \text{vertices} & =\{A,B,C,D,E\} \nonumber\\ + \text{edges} & =( \{A,B\},\{A,C\},\{B,C\}, \{C,E\} ). \nonumber + \end{align} \pause +\item It is ``better'' to draw it: + \begin{center} + \includegraphics[width=0.25\textwidth]{small1} \pause + \includegraphics[width=0.25\textwidth]{small2} + \end{center} +\end{itemize} + +\newpage +\cstitle{Undirected and directed graphs} +\begin{itemize} +\item If the pairs are unordered, then the graph is undirected: + + \begin{minipage}{0.7\textwidth} + \begin{align} + \text{vertices} & =\{A,B,C,D,E\} \nonumber\\ + \text{edges} & =( \{A,B\},\{A,C\},\{B,C\},\{C,E\} ). \nonumber + \end{align} + \end{minipage}\begin{minipage}{0.3\textwidth} + \includegraphics[width=0.8\textwidth]{small3} + \end{minipage}\pause + +\item Otherwise it is directed: + + \begin{minipage}{0.7\textwidth} + \begin{align} + \text{vertices} & =\{A,B,C,D,E\} \nonumber\\ + \text{edges} & =( (A,B),(A,C),(B,C),(C,E) ). \nonumber + \end{align} + \end{minipage}\begin{minipage}{0.3\textwidth} + \includegraphics[width=0.8\textwidth]{small4} + \end{minipage} +\end{itemize} + +\newpage +\stitle{The \igraph ``package''} + +\begin{narrow}{0cm}{10cm} +\begin{itemize} +\item For classic graph theory and network science. +\item Core functionality is implemented as a C library. +\item High level interfaces from \emph{R} and \emph{Python}. +\item GNU GPL. +\item \url{http://igraph.sf.net} +\end{itemize} +\includegraphics[width=0.45\textwidth]{homepage} +\end{narrow} + +\newpage +\stitle{Vertex and edge ids} +\begin{narrow}{0cm}{12cm} +\begin{itemize} +\item Vertices are always numbered from zero (!). +\item Numbering is continual, form 0 to $|V|-1$. \pause +\item We have to ``translate'' vertex names to ids: +\begin{align} + V & =\{A,B,C,D,E\} \nonumber\\ + E & =( (A,B),(A,C),(B,C),(C,E) ). \nonumber\\ + A & =0, B=1, C=2, D=3, E=4. \nonumber +\end{align} \pause +\begin{Myverb} + > g <- graph( c(0,1, 0,2, 1,2, 2,4), n=5 ) +\end{Myverb} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Creating \igraph graphs} +% Giving the edges +% igraph objects +% is.igraph, is.directed, vcount, ecount, summary +\begin{narrow}{0cm}{12cm} +\begin{itemize} +\item \igraph objects \pause +\item \verb+print()+, \verb+summary()+, \verb+is.igraph()+ \pause +\item \verb+is.directed()+, \verb+vcount()+, \verb+ecount()+ +\end{itemize} +\begin{Myverb} + > g <- graph( c(0,1, 0,2, 1,2, 2,4), n=5 ) + > g + Vertices: 5 + Edges: 4 + Directed: TRUE + Edges: + + [0] 0 -> 1 + [1] 0 -> 2 + [2] 1 -> 2 + [3] 2 -> 4 +\end{Myverb} +\end{narrow} + +\newpage +\stitle{Visualization} +\begin{narrow}{0cm}{12cm} +\begin{Myverb} + > g <- graph.tree(40, 4) + > plot(g) + > plot(g, layout=layout.circle) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # Force directed layouts + > plot(g, layout=layout.fruchterman.reingold) + > plot(g, layout=layout.graphopt) + > plot(g, layout=layout.kamada.kawai) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # Interactive + > tkplot(g, layout=layout.kamada.kawai) + > l <- layout=layout.kamada.kawai(g) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # 3D + > rglplot(g, layout=l) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # Visual properties + > plot(g, layout=l, vertex.color="cyan") +\end{Myverb} +\end{narrow} + +\newpage +\stitle{Simple graphs} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item \igraph can handle multi-graphs: + \begin{align} + V & =\{A,B,C,D,E\} \nonumber\\ + E & =( (AB),(AB),(AC),(BC),(CE) ). \nonumber + \end{align} + \begin{Myverb} + > g <- graph( c(0,1,0,1, 0,2, 1,2, 3,4), n=5 ) + > g + Vertices: 5 + Edges: 5 + Directed: TRUE + Edges: + + [0] 0 -> 1 + [1] 0 -> 1 + [2] 0 -> 2 + [3] 1 -> 2 + [4] 3 -> 4 + \end{Myverb} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Simple graphs} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item \igraph can handle loop-edges: + \begin{align} + V & =\{A,B,C,D,E\} \nonumber\\ + E & =( (AA),(AB),(AC),(BC),(CE) ). \nonumber + \end{align} + \begin{Myverb} + > g <- graph( c(0,0,0,1, 0,2, 1,2, 3,4), n=5 ) + > g + Vertices: 5 + Edges: 5 + Directed: TRUE + Edges: + + [0] 0 -> 0 + [1] 0 -> 1 + [2] 0 -> 2 + [3] 1 -> 2 + [4] 3 -> 4 + \end{Myverb} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Creating (more) \igraph graphs} +\begin{narrow}{0cm}{15cm} +\begin{Myverb} + > el <- scan("lesmis.txt") + > el <- matrix(el, byrow=TRUE, nc=2) + > gmis <- graph.edgelist(el, dir=FALSE) + > summary(gmis) +\end{Myverb} +\end{narrow} + +\newpage +\stitle{Naming vertices} +\begin{narrow}{0cm}{15cm} +\begin{Myverb} + > V(gmis)$name + > g <- graph.ring(10) + > V(g)$name <- sample(letters, vcount(g)) +\end{Myverb} +\end{narrow} +% From edge list, data frame +% graph.edgelist +% graph.data.frame + +\newpage +\stitle{Creating (more) \igraph graphs} +\begin{narrow}{0cm}{15cm} +\begin{Myverb} + # A simple undirected graph + > g <- graph.formula( Alice-Bob-Cecil-Alice, + Daniel-Cecil-Eugene, Cecil-Gordon ) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # Another undirected graph, ":" notation + > g2 <- graph.formula( Alice-Bob:Cecil:Daniel, + Cecil:Daniel-Eugene:Gordon ) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # A directed graph + > g3 <- graph.formula( Alice +-+ Bob --+ Cecil + +-- Daniel, Eugene --+ Gordon:Helen ) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # A graph with isolate vertices + > g4 <- graph.formula( Alice -- Bob -- Daniel, + Cecil:Gordon, Helen ) +\end{Myverb} +\vspace*{-2cm} \pause +\begin{Myverb} + # "Arrows" can be arbitrarily long + > g5 <- graph.formula( Alice +---------+ Bob ) +\end{Myverb} +\end{narrow} +% Formula notation + +\newpage +\stitle{Vertex/Edge sets, attributes} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item Assigning attributes: + \texttt{set/get.graph/vertex/edge.attribute}. \pause\vspace*{-0.8cm} +\item \verb+V(g)+ and \verb+E(g)+. \pause +\item Smart indexing, e.g. \verb+V(g)[color=="white"]+ \pause +\item Easy access of attributes: + \begin{Myverb} + > g <- erdos.renyi.game(100, 1/100) + > V(g)$color <- sample( c("red", "black"), + vcount(g), rep=TRUE) + > E(g)$color <- "grey" + > red <- V(g)[ color == "red" ] + > bl <- V(g)[ color == "black" ] + > E(g)[ red %--% red ]$color <- "red" + > E(g)[ bl %--% bl ]$color <- "black" + > plot(g, vertex.size=5, layout= + layout.fruchterman.reingold) +\end{Myverb} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Creating (even) more graphs} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item E.g. from \verb+.csv+ files. +\end{itemize} +\begin{Myverb} + > traits <- read.csv("traits.csv", head=F) + > relations <- read.csv("relations.csv", head=F) + > orgnet <- graph.data.frame(relations) + + > traits[,1] <- sapply(strsplit(as.character + (traits[,1]), split=" "), "[[", 1) + > idx <- match(V(orgnet)$name, traits[,1]) + > V(orgnet)$gender <- as.character(traits[,3][idx]) + > V(orgnet)$age <- traits[,2][idx] + + > igraph.par("print.vertex.attributes", TRUE) + > orgnet +\end{Myverb} +% $ +\end{narrow} + +\newpage +\stitle{Creating (even) more graphs} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item From the web, e.g. Pajek files. +\end{itemize} +\begin{Myverb} + > karate <- read.graph("http://cneurocvs.rmki.kfki.hu/igraph/karate.net", + format="pajek") + > summary(karate) + Vertices: 34 + Edges: 78 + Directed: FALSE + No graph attributes. + No vertex attributes. + No edge attributes. +\end{Myverb} +\end{narrow} + +\newpage +\stitle{Graph representation} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item There is no best format, everything depends on + what kind of questions one wants to ask. +\begin{center} +\includegraphics[width=0.45\textwidth]{example} +\end{center} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Graph representation} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item Adjacency matrix. Good for questions like: is 'Alice' connected + to 'Bob'? +\begin{center} + \includegraphics[width=0.45\textwidth]{adjacency} +\end{center} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Graph representation} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item Edge list. Not really good for anything. +\begin{center} + \includegraphics{edgelist} +\end{center} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Graph representation} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item Adjacency lists. GQ: who are the neighbors of 'Alice'? +\begin{center} + \includegraphics{adjlist} +\end{center} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Graph representation} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item \igraph. Flat data structures, indexed edge lists. Easy to + handle, good for many kind of questions. +\begin{center} + \includegraphics[width=0.45\textwidth]{igraph} +\end{center} +\end{itemize} +\end{narrow} +% Adjacency matrix, edge list, adjacency lists, +% igraph + +\newpage +\stitle{Centrality in networks} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item degree +\begin{center} +\includegraphics[width=0.45\textwidth]{ex-deg} +\end{center} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Centrality in networks} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item closeness + \[ C_v = \frac{|V|-1}{\sum_{i\ne v} d_{vi}} \] +\begin{center} +\includegraphics[width=0.45\textwidth]{ex-close} +\end{center} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Centrality in networks} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item betweenness + \[ B_v= \sum_{i\ne j, i\ne v, j\ne v} g_{ivj}/g_{ij} \] +\begin{center} +\includegraphics[width=0.45\textwidth]{ex-betw} +\end{center} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Centrality in networks} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item eigenvector centrality +\[ E_v = \frac{1}{\lambda} \sum_{i=1}^{|V|} A_{iv} E_i, + \quad Ax=\lambda x \] +\begin{center} +\vspace*{-1cm} +\includegraphics[width=0.45\textwidth]{ex-ev} +\end{center} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Centrality in networks} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item page rank +\[ E_v = \frac{1-d}{|V|} + d \sum_{i=1}^{|V|} A_{iv} E_i \] +\begin{center} +\vspace*{-1cm} +\includegraphics[width=0.45\textwidth]{ex-pagerank} +\end{center} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Community structure in networks} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item Organizing things, clustering items to see the structure. +\begin{center} +\includegraphics[width=0.45\textwidth]{commstr}\\[15pt] +{\tiny M. E. J. Newman, PNAS, 103, 8577--8582\par } +\end{center} +\end{itemize} +\end{narrow} + +\newpage +\stitle{Community structure in networks} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item How to define what is modular? Many proposed definitions, here + is a popular one: + \[ Q = \frac1{2|E|}\sum_{vw} [A_{vw} - p_{vw} ]\delta(c_v,c_w). \] \pause +\item Random graph null model: + \[ p_{vw} = p = \frac{1}{|V|(|V|-1)} \] \pause +\item Degree sequence based null model: + \[ p_{vw} = \frac{k_vk_w}{2|E|} \] +\end{itemize} +\end{narrow} + +% modularity, clustering, organizing things +% fast greedy +% betweenness +% spinglass + +\newpage +\stitle{Cohesive blocks} +(Based on `Structural Cohesion and Embeddedness: a Hierarchical +Concept of Social Groups' by J.Moody and D.White, Americal +Sociological Review, 68, 103--127, 2003) + +\begin{quotation} +Definition 1: A collectivity is structurally cohesive to the extent +that the social relations of its members hold it together. +\end{quotation} \pause\vspace*{-0.8cm} + +\begin{quotation} +Definition 2: A group is structurally cohesive to the extent that +multiple independent relational paths among all pairs of members hold +it together. +\end{quotation} \pause\vspace*{-0.8cm} + +\begin{itemize} +\item Vertex-independent paths and vertex connectivity. \pause\vspace*{-0.8cm} +\item Vertex connectivity and network flows. +\end{itemize} + +\newpage +\stitle{Cohesive blocks} +\vspace*{-3cm} +{\centering +\includegraphics[width=0.9\textwidth]{groups}\\ +} + +\newpage +\stitle{Cohesive blocks} +\includegraphics[width=0.6\textwidth]{cblocks}\\ + +\newpage +\stitle{Rapid prototyping} +\begin{narrow}{0cm}{15cm} +Weighted transitivity +\[ c(i)=\frac{\mathbf{A}^3_{ii}}{(\mathbf{A1A})_{ii}} \] \pause +\[ c_w(i)=\frac{\mathbf{W}^3_{ii}}{(\mathbf{WW_{\text{max}}W})_{ii}} \] \pause +\begin{Myverb} + wtrans <- function(g) \{ + W <- get.adjacency(g, attr="weight") + WM <- matrix(max(W), nrow(W), ncol(W)) + diag(WM) <- 0 + diag( W %*% W %*% W ) / + diag( W %*% WM %*% W) + \} +\end{Myverb} +\end{narrow} + +\newpage +\stitle{Rapid prototyping} +\begin{narrow}{0cm}{15cm} +Clique percolation (Palla et al., Nature 435, 814, 2005) +\includegraphics[width=0.4\textwidth]{vicsek} +\end{narrow} + +\newpage +\stitle{\ldots and the rest} +\begin{narrow}{0cm}{15cm} +\begin{itemize} +\item Cliques and independent vertex sets. +\item Network flows. +\item Motifs, i.e. dyad and triad census. +\item Random graph generators. +\item Graph isomorphism. +\item Vertex similarity measures, topological sorting, + spanning trees, graph components, K-cores, transitivity or + clustering coefficient. +\item etc. +\item C-level: rich data type library. +\end{itemize} +\end{narrow} + +\newpage +\cstitle{Acknowledgement} + +\begin{center} +\vfill +Tam\'as Nepusz\par\vfil +All the people who contributed code, sent bug reports, suggestions\par\vfil +The R project\par\vfil +Hungarian Academy of Sciences\par\vfil +The OSS community in general\par\vfill +\end{center} + +\end{document} diff --git a/doc/presentations/wien08/relations.csv b/doc/presentations/wien08/relations.csv new file mode 100644 index 0000000..789555f --- /dev/null +++ b/doc/presentations/wien08/relations.csv @@ -0,0 +1,34 @@ +Bob,Alice,N,4,4 +Cecil,Bob,N,5,5 +Cecil,Alice,Y,5,5 +David,Alice,N,3,4 +David,Bob,N,4,2 +Esmeralda,Alice,Y,4,3 +Frank,Alice,N,3,2 +Frank,Esmeralda,N,4,4 +Gabi,Bob,Y,5,5 +Gabi,Alice,N,3,0 +Helen,Alice,N,4,1 +Iris,Cecil,N,0,1 +Iris,Bob,Y,4,3 +Iris,Esmeralda,N,5,5 +James,Alice,N,3,2 +James,Bob,Y,4,5 +James,Gabi,Y,5,5 +Alice,Bob,N,4,4 +Bob,Cecil,N,5,5 +Alice,Cecil,Y,5,5 +Alice,David,N,0,3 +Bob,David,N,4,1 +Alice,Esmeralda,Y,3,1 +Alice,Frank,N,3,3 +Esmeralda,Frank,N,5,4 +Bob,Gabi,Y,4,5 +Alice,Gabi,N,0,1 +Alice,Helen,N,4,3 +Cecil,Iris,N,3,2 +Bob,Iris,Y,3,2 +Esmeralda,Iris,N,5,5 +Alice,James,N,0,3 +Bob,James,Y,4,3 +Gabi,James,Y,5,4 diff --git a/doc/presentations/wien08/small1.pdf b/doc/presentations/wien08/small1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d4ce86b30607e9ed43664f3e2111007f8b050c9a GIT binary patch literal 26230 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/wien08/small2.pdf b/doc/presentations/wien08/small2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7b210e9d24d57da14f3c24d2d9be4f87bd37417e GIT binary patch literal 26229 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 literal 0 HcmV?d00001 diff --git a/doc/presentations/wien08/traits.csv b/doc/presentations/wien08/traits.csv new file mode 100644 index 0000000..5527e99 --- /dev/null +++ b/doc/presentations/wien08/traits.csv @@ -0,0 +1,10 @@ +Alice Anderson,48,F +Bob Bradford,33,M +Cecil Connor,45,F +David Daugher,34,M +Esmeralda Escobar,21,F +Frank Finley,36,M +Gabi Garbo,44,F +Helen Hunt,40,F +Iris Irving,25,F +James Jones,47,M diff --git a/doc/progress.xxml b/doc/progress.xxml new file mode 100644 index 0000000..5a6f5c0 --- /dev/null +++ b/doc/progress.xxml @@ -0,0 +1,38 @@ + + +]> + +

    +Progress handlers + +
    + +
    + +
    Setting up progress handlers + + + +
    + +
    Invoking the progress handler + + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    diff --git a/doc/random.xxml b/doc/random.xxml new file mode 100644 index 0000000..766330c --- /dev/null +++ b/doc/random.xxml @@ -0,0 +1,51 @@ + + +]> + + +Random numbers + + + +
    The default random number generator + + +
    + +
    Creating random number generators + + + + + + +
    + +
    Generating random numbers + + + + + + + +
    + +
    Supported random number generators + +By default igraph uses the MT19937 generator. Prior to igraph version +0.6, the generator supplied by the standard C library was used. This +means the GLIBC2 generator on GNU libc 2 systems, and maybe the RAND +generator on others. + + + + +
    + + + +
    + diff --git a/doc/scg.xxml b/doc/scg.xxml new file mode 100644 index 0000000..b22a537 --- /dev/null +++ b/doc/scg.xxml @@ -0,0 +1,23 @@ + + +]> + + +Spectral Coarse Graining + +
    Introduction + +
    + +
    SCG functions + + + + + + +
    + +
    diff --git a/doc/separators.xxml b/doc/separators.xxml new file mode 100644 index 0000000..9afffe7 --- /dev/null +++ b/doc/separators.xxml @@ -0,0 +1,15 @@ + + +]> + + +Vertex separators + + + + + + + diff --git a/doc/sitemap_gen.py b/doc/sitemap_gen.py new file mode 100755 index 0000000..e270a39 --- /dev/null +++ b/doc/sitemap_gen.py @@ -0,0 +1,2094 @@ +#!/usr/bin/env python +# +# Copyright (c) 2004, 2005 Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# * Neither the name of Google nor the names of its contributors may +# be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# +# The sitemap_gen.py script is written in Python 2.2 and released to +# the open source community for continuous improvements under the BSD +# 2.0 new license, which can be found at: +# +# http://www.opensource.org/licenses/bsd-license.php +# + +__usage__ = \ +"""A simple script to automatically produce sitemaps for a webserver, +in the Google Sitemap Protocol (GSP). + +Usage: python sitemap_gen.py --config=config.xml [--help] [--testing] + --config=config.xml, specifies config file location + --help, displays usage message + --testing, specified when user is experimenting +""" + +# Please be careful that all syntax used in this file can be parsed on +# Python 1.5 -- this version check is not evaluated until after the +# entire file has been parsed. +import sys +if sys.hexversion < 0x02020000: + print 'This script requires Python 2.2 or later.' + print 'Currently run with version: %s' % sys.version + sys.exit(1) + +import fnmatch +import glob +import gzip +import md5 +import os +import re +import stat +import time +import types +import urllib +import urlparse +import xml.sax + +# True and False were introduced in Python2.2.2 +try: + testTrue=True + del testTrue +except NameError: + True=1 + False=0 + +# Text encodings +ENC_ASCII = 'ASCII' +ENC_UTF8 = 'UTF-8' +ENC_IDNA = 'IDNA' +ENC_ASCII_LIST = ['ASCII', 'US-ASCII', 'US', 'IBM367', 'CP367', 'ISO646-US' + 'ISO_646.IRV:1991', 'ISO-IR-6', 'ANSI_X3.4-1968', + 'ANSI_X3.4-1986', 'CPASCII' ] +ENC_DEFAULT_LIST = ['ISO-8859-1', 'ISO-8859-2', 'ISO-8859-5'] + +# Available Sitemap types +SITEMAP_TYPES = ['web', 'mobile', 'news'] + +# General Sitemap tags +GENERAL_SITEMAP_TAGS = ['loc', 'changefreq', 'priority', 'lastmod'] + +# News specific tags +NEWS_SPECIFIC_TAGS = ['keywords', 'publication_date', 'stock_tickers'] + +# News Sitemap tags +NEWS_SITEMAP_TAGS = GENERAL_SITEMAP_TAGS + NEWS_SPECIFIC_TAGS + +# Maximum number of urls in each sitemap, before next Sitemap is created +MAXURLS_PER_SITEMAP = 50000 + +# Suffix on a Sitemap index file +SITEINDEX_SUFFIX = '_index.xml' + +# Regular expressions tried for extracting URLs from access logs. +ACCESSLOG_CLF_PATTERN = re.compile( + r'.+\s+"([^\s]+)\s+([^\s]+)\s+HTTP/\d+\.\d+"\s+200\s+.*' + ) + +# Match patterns for lastmod attributes +DATE_PATTERNS = map(re.compile, [ + r'^\d\d\d\d$', + r'^\d\d\d\d-\d\d$', + r'^\d\d\d\d-\d\d-\d\d$', + r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\dZ$', + r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\d[+-]\d\d:\d\d$', + r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z$', + r'^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?[+-]\d\d:\d\d$', + ]) + +# Match patterns for changefreq attributes +CHANGEFREQ_PATTERNS = [ + 'always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never' + ] + +# XML formats +GENERAL_SITEINDEX_HEADER = \ + '\n' \ + '\n' + +NEWS_SITEINDEX_HEADER = \ + '\n' \ + '\n' + +SITEINDEX_FOOTER = '\n' +SITEINDEX_ENTRY = \ + ' \n' \ + ' %(loc)s\n' \ + ' %(lastmod)s\n' \ + ' \n' +GENERAL_SITEMAP_HEADER = \ + '\n' \ + '\n' + +NEWS_SITEMAP_HEADER = \ + '\n' \ + '\n' + +SITEMAP_FOOTER = '\n' +SITEURL_XML_PREFIX = ' \n' +SITEURL_XML_SUFFIX = ' \n' + +NEWS_TAG_XML_PREFIX = ' \n' +NEWS_TAG_XML_SUFFIX = ' \n' + +# Search engines to notify with the updated sitemaps +# +# This list is very non-obvious in what's going on. Here's the gist: +# Each item in the list is a 6-tuple of items. The first 5 are "almost" +# the same as the input arguments to urlparse.urlunsplit(): +# 0 - schema +# 1 - netloc +# 2 - path +# 3 - query <-- EXCEPTION: specify a query map rather than a string +# 4 - fragment +# Additionally, add item 5: +# 5 - query attribute that should be set to the new Sitemap URL +# Clear as mud, I know. +NOTIFICATION_SITES = [ + ('http', 'www.google.com', 'webmasters/sitemaps/ping', {}, '', 'sitemap'), + ] + + +class Error(Exception): + """ + Base exception class. In this module we tend not to use our own exception + types for very much, but they come in very handy on XML parsing with SAX. + """ + pass +#end class Error + + +class SchemaError(Error): + """Failure to process an XML file according to the schema we know.""" + pass +#end class SchemeError + + +class Encoder: + """ + Manages wide-character/narrow-character conversions for just about all + text that flows into or out of the script. + + You should always use this class for string coercion, as opposed to + letting Python handle coercions automatically. Reason: Python + usually assumes ASCII (7-bit) as a default narrow character encoding, + which is not the kind of data we generally deal with. + + General high-level methodologies used in sitemap_gen: + + [PATHS] + File system paths may be wide or narrow, depending on platform. + This works fine, just be aware of it and be very careful to not + mix them. That is, if you have to pass several file path arguments + into a library call, make sure they are all narrow or all wide. + This class has MaybeNarrowPath() which should be called on every + file system path you deal with. + + [URLS] + URL locations are stored in Narrow form, already escaped. This has the + benefit of keeping escaping and encoding as close as possible to the format + we read them in. The downside is we may end up with URLs that have + intermingled encodings -- the root path may be encoded in one way + while the filename is encoded in another. This is obviously wrong, but + it should hopefully be an issue hit by very few users. The workaround + from the user level (assuming they notice) is to specify a default_encoding + parameter in their config file. + + [OTHER] + Other text, such as attributes of the URL class, configuration options, + etc, are generally stored in Unicode for simplicity. + """ + + def __init__(self): + self._user = None # User-specified default encoding + self._learned = [] # Learned default encodings + self._widefiles = False # File system can be wide + + # Can the file system be Unicode? + try: + self._widefiles = os.path.supports_unicode_filenames + except AttributeError: + try: + self._widefiles = sys.getwindowsversion() == os.VER_PLATFORM_WIN32_NT + except AttributeError: + pass + + # Try to guess a working default + try: + encoding = sys.getfilesystemencoding() + if encoding and not (encoding.upper() in ENC_ASCII_LIST): + self._learned = [ encoding ] + except AttributeError: + pass + + if not self._learned: + encoding = sys.getdefaultencoding() + if encoding and not (encoding.upper() in ENC_ASCII_LIST): + self._learned = [ encoding ] + + # If we had no guesses, start with some European defaults + if not self._learned: + self._learned = ENC_DEFAULT_LIST + #end def __init__ + + def SetUserEncoding(self, encoding): + self._user = encoding + #end def SetUserEncoding + + def NarrowText(self, text, encoding): + """ Narrow a piece of arbitrary text """ + if type(text) != types.UnicodeType: + return text + + # Try the passed in preference + if encoding: + try: + result = text.encode(encoding) + if not encoding in self._learned: + self._learned.append(encoding) + return result + except UnicodeError: + pass + except LookupError: + output.Warn('Unknown encoding: %s' % encoding) + + # Try the user preference + if self._user: + try: + return text.encode(self._user) + except UnicodeError: + pass + except LookupError: + temp = self._user + self._user = None + output.Warn('Unknown default_encoding: %s' % temp) + + # Look through learned defaults, knock any failing ones out of the list + while self._learned: + try: + return text.encode(self._learned[0]) + except: + del self._learned[0] + + # When all other defaults are exhausted, use UTF-8 + try: + return text.encode(ENC_UTF8) + except UnicodeError: + pass + + # Something is seriously wrong if we get to here + return text.encode(ENC_ASCII, 'ignore') + #end def NarrowText + + def MaybeNarrowPath(self, text): + """ Paths may be allowed to stay wide """ + if self._widefiles: + return text + return self.NarrowText(text, None) + #end def MaybeNarrowPath + + def WidenText(self, text, encoding): + """ Widen a piece of arbitrary text """ + if type(text) != types.StringType: + return text + + # Try the passed in preference + if encoding: + try: + result = unicode(text, encoding) + if not encoding in self._learned: + self._learned.append(encoding) + return result + except UnicodeError: + pass + except LookupError: + output.Warn('Unknown encoding: %s' % encoding) + + # Try the user preference + if self._user: + try: + return unicode(text, self._user) + except UnicodeError: + pass + except LookupError: + temp = self._user + self._user = None + output.Warn('Unknown default_encoding: %s' % temp) + + # Look through learned defaults, knock any failing ones out of the list + while self._learned: + try: + return unicode(text, self._learned[0]) + except: + del self._learned[0] + + # When all other defaults are exhausted, use UTF-8 + try: + return unicode(text, ENC_UTF8) + except UnicodeError: + pass + + # Getting here means it wasn't UTF-8 and we had no working default. + # We really don't have anything "right" we can do anymore. + output.Warn('Unrecognized encoding in text: %s' % text) + if not self._user: + output.Warn('You may need to set a default_encoding in your ' + 'configuration file.') + return text.decode(ENC_ASCII, 'ignore') + #end def WidenText +#end class Encoder +encoder = Encoder() + + +class Output: + """ + Exposes logging functionality, and tracks how many errors + we have thus output. + + Logging levels should be used as thus: + Fatal -- extremely sparingly + Error -- config errors, entire blocks of user 'intention' lost + Warn -- individual URLs lost + Log(,0) -- Un-suppressable text that's not an error + Log(,1) -- touched files, major actions + Log(,2) -- parsing notes, filtered or duplicated URLs + Log(,3) -- each accepted URL + """ + + def __init__(self): + self.num_errors = 0 # Count of errors + self.num_warns = 0 # Count of warnings + + self._errors_shown = {} # Shown errors + self._warns_shown = {} # Shown warnings + self._verbose = 0 # Level of verbosity + #end def __init__ + + def Log(self, text, level): + """ Output a blurb of diagnostic text, if the verbose level allows it """ + if text: + text = encoder.NarrowText(text, None) + if self._verbose >= level: + print text + #end def Log + + def Warn(self, text): + """ Output and count a warning. Suppress duplicate warnings. """ + if text: + text = encoder.NarrowText(text, None) + hash = md5.new(text).digest() + if not self._warns_shown.has_key(hash): + self._warns_shown[hash] = 1 + print '[WARNING] ' + text + else: + self.Log('(suppressed) [WARNING] ' + text, 3) + self.num_warns = self.num_warns + 1 + #end def Warn + + def Error(self, text): + """ Output and count an error. Suppress duplicate errors. """ + if text: + text = encoder.NarrowText(text, None) + hash = md5.new(text).digest() + if not self._errors_shown.has_key(hash): + self._errors_shown[hash] = 1 + print '[ERROR] ' + text + else: + self.Log('(suppressed) [ERROR] ' + text, 3) + self.num_errors = self.num_errors + 1 + #end def Error + + def Fatal(self, text): + """ Output an error and terminate the program. """ + if text: + text = encoder.NarrowText(text, None) + print '[FATAL] ' + text + else: + print 'Fatal error.' + sys.exit(1) + #end def Fatal + + def SetVerbose(self, level): + """ Sets the verbose level. """ + try: + if type(level) != types.IntType: + level = int(level) + if (level >= 0) and (level <= 3): + self._verbose = level + return + except ValueError: + pass + self.Error('Verbose level (%s) must be between 0 and 3 inclusive.' % level) + #end def SetVerbose +#end class Output +output = Output() + + +class URL(object): + """ URL is a smart structure grouping together the properties we + care about for a single web reference. """ + __slots__ = 'loc', 'lastmod', 'changefreq', 'priority' + + def __init__(self): + self.loc = None # URL -- in Narrow characters + self.lastmod = None # ISO8601 timestamp of last modify + self.changefreq = None # Text term for update frequency + self.priority = None # Float between 0 and 1 (inc) + #end def __init__ + + def __cmp__(self, other): + if self.loc < other.loc: + return -1 + if self.loc > other.loc: + return 1 + return 0 + #end def __cmp__ + + def TrySetAttribute(self, attribute, value): + """ Attempt to set the attribute to the value, with a pretty try + block around it. """ + if attribute == 'loc': + self.loc = self.Canonicalize(value) + else: + try: + setattr(self, attribute, value) + except AttributeError: + output.Warn('Unknown URL attribute: %s' % attribute) + #end def TrySetAttribute + + def IsAbsolute(loc): + """ Decide if the URL is absolute or not """ + if not loc: + return False + narrow = encoder.NarrowText(loc, None) + (scheme, netloc, path, query, frag) = urlparse.urlsplit(narrow) + if (not scheme) or (not netloc): + return False + return True + #end def IsAbsolute + IsAbsolute = staticmethod(IsAbsolute) + + def Canonicalize(loc): + """ Do encoding and canonicalization on a URL string """ + if not loc: + return loc + + # Let the encoder try to narrow it + narrow = encoder.NarrowText(loc, None) + + # Escape components individually + (scheme, netloc, path, query, frag) = urlparse.urlsplit(narrow) + unr = '-._~' + sub = '!$&\'()*+,;=' + netloc = urllib.quote(netloc, unr + sub + '%:@/[]') + path = urllib.quote(path, unr + sub + '%:@/') + query = urllib.quote(query, unr + sub + '%:@/?') + frag = urllib.quote(frag, unr + sub + '%:@/?') + + # Try built-in IDNA encoding on the netloc + try: + (ignore, widenetloc, ignore, ignore, ignore) = urlparse.urlsplit(loc) + for c in widenetloc: + if c >= unichr(128): + netloc = widenetloc.encode(ENC_IDNA) + netloc = urllib.quote(netloc, unr + sub + '%:@/[]') + break + except UnicodeError: + # urlsplit must have failed, based on implementation differences in the + # library. There is not much we can do here, except ignore it. + pass + except LookupError: + output.Warn('An International Domain Name (IDN) is being used, but this ' + 'version of Python does not have support for IDNA encoding. ' + ' (IDNA support was introduced in Python 2.3) The encoding ' + 'we have used instead is wrong and will probably not yield ' + 'valid URLs.') + bad_netloc = False + if '%' in netloc: + bad_netloc = True + + # Put it all back together + narrow = urlparse.urlunsplit((scheme, netloc, path, query, frag)) + + # I let '%' through. Fix any that aren't pre-existing escapes. + HEXDIG = '0123456789abcdefABCDEF' + list = narrow.split('%') + narrow = list[0] + del list[0] + for item in list: + if (len(item) >= 2) and (item[0] in HEXDIG) and (item[1] in HEXDIG): + narrow = narrow + '%' + item + else: + narrow = narrow + '%25' + item + + # Issue a warning if this is a bad URL + if bad_netloc: + output.Warn('Invalid characters in the host or domain portion of a URL: ' + + narrow) + + return narrow + #end def Canonicalize + Canonicalize = staticmethod(Canonicalize) + + def VerifyDate(self, date, metatag): + """Verify the date format is valid""" + match = False + if date: + date = date.upper() + for pattern in DATE_PATTERNS: + match = pattern.match(date) + if match: + return True + if not match: + output.Warn('The value for %s does not appear to be in ISO8601 ' + 'format on URL: %s' % (metatag, self.loc)) + return False + #end of VerifyDate + + def Validate(self, base_url, allow_fragment): + """ Verify the data in this URL is well-formed, and override if not. """ + assert type(base_url) == types.StringType + + # Test (and normalize) the ref + if not self.loc: + output.Warn('Empty URL') + return False + if allow_fragment: + self.loc = urlparse.urljoin(base_url, self.loc) + if not self.loc.startswith(base_url): + output.Warn('Discarded URL for not starting with the base_url: %s' % + self.loc) + self.loc = None + return False + + # Test the lastmod + if self.lastmod: + if not self.VerifyDate(self.lastmod, "lastmod"): + self.lastmod = None + + # Test the changefreq + if self.changefreq: + match = False + self.changefreq = self.changefreq.lower() + for pattern in CHANGEFREQ_PATTERNS: + if self.changefreq == pattern: + match = True + break + if not match: + output.Warn('Changefreq "%s" is not a valid change frequency on URL ' + ': %s' % (self.changefreq, self.loc)) + self.changefreq = None + + # Test the priority + if self.priority: + priority = -1.0 + try: + priority = float(self.priority) + except ValueError: + pass + if (priority < 0.0) or (priority > 1.0): + output.Warn('Priority "%s" is not a number between 0 and 1 inclusive ' + 'on URL: %s' % (self.priority, self.loc)) + self.priority = None + + return True + #end def Validate + + def MakeHash(self): + """ Provides a uniform way of hashing URLs """ + if not self.loc: + return None + if self.loc.endswith('/'): + return md5.new(self.loc[:-1]).digest() + return md5.new(self.loc).digest() + #end def MakeHash + + def Log(self, prefix='URL', level=3): + """ Dump the contents, empty or not, to the log. """ + out = prefix + ':' + + for attribute in self.__slots__: + value = getattr(self, attribute) + if not value: + value = '' + out = out + (' %s=[%s]' % (attribute, value)) + + output.Log('%s' % encoder.NarrowText(out, None), level) + #end def Log + + def WriteXML(self, file): + """ Dump non-empty contents to the output file, in XML format. """ + if not self.loc: + return + out = SITEURL_XML_PREFIX + + for attribute in self.__slots__: + value = getattr(self, attribute) + if value: + if type(value) == types.UnicodeType: + value = encoder.NarrowText(value, None) + elif type(value) != types.StringType: + value = str(value) + value = xml.sax.saxutils.escape(value) + out = out + (' <%s>%s\n' % (attribute, value, attribute)) + + out = out + SITEURL_XML_SUFFIX + file.write(out) + #end def WriteXML +#end class URL + +class NewsURL(URL): + """ NewsURL is a subclass of URL with News-Sitemap specific properties. """ + __slots__ = 'loc', 'lastmod', 'changefreq', 'priority', 'publication_date', \ + 'keywords', 'stock_tickers' + + def __init__(self): + URL.__init__(self) + self.publication_date = None # ISO8601 timestamp of publication date + self.keywords = None # Text keywords + self.stock_tickers = None # Text stock + #end def __init__ + + def Validate(self, base_url, allow_fragment): + """ Verify the data in this News URL is well-formed, and override if not. """ + assert type(base_url) == types.StringType + + if not URL.Validate(self, base_url, allow_fragment): + return False + + if not URL.VerifyDate(self, self.publication_date, "publication_date"): + self.publication_date = None + + return True + #end def Validate + + def WriteXML(self, file): + """ Dump non-empty contents to the output file, in XML format. """ + if not self.loc: + return + out = SITEURL_XML_PREFIX + + # printed_news_tag indicates if news-specific metatags are present + printed_news_tag = False + for attribute in self.__slots__: + value = getattr(self, attribute) + if value: + if type(value) == types.UnicodeType: + value = encoder.NarrowText(value, None) + elif type(value) != types.StringType: + value = str(value) + value = xml.sax.saxutils.escape(value) + if attribute in NEWS_SPECIFIC_TAGS: + if not printed_news_tag: + printed_news_tag = True + out = out + NEWS_TAG_XML_PREFIX + out = out + (' %s\n' % (attribute, value, attribute)) + else: + out = out + (' <%s>%s\n' % (attribute, value, attribute)) + + if printed_news_tag: + out = out + NEWS_TAG_XML_SUFFIX + out = out + SITEURL_XML_SUFFIX + file.write(out) + #end def WriteXML +#end class NewsURL + + +class Filter: + """ + A filter on the stream of URLs we find. A filter is, in essence, + a wildcard applied to the stream. You can think of this as an + operator that returns a tri-state when given a URL: + + True -- this URL is to be included in the sitemap + None -- this URL is undecided + False -- this URL is to be dropped from the sitemap + """ + + def __init__(self, attributes): + self._wildcard = None # Pattern for wildcard match + self._regexp = None # Pattern for regexp match + self._pass = False # "Drop" filter vs. "Pass" filter + + if not ValidateAttributes('FILTER', attributes, + ('pattern', 'type', 'action')): + return + + # Check error count on the way in + num_errors = output.num_errors + + # Fetch the attributes + pattern = attributes.get('pattern') + type = attributes.get('type', 'wildcard') + action = attributes.get('action', 'drop') + if type: + type = type.lower() + if action: + action = action.lower() + + # Verify the attributes + if not pattern: + output.Error('On a filter you must specify a "pattern" to match') + elif (not type) or ((type != 'wildcard') and (type != 'regexp')): + output.Error('On a filter you must specify either \'type="wildcard"\' ' + 'or \'type="regexp"\'') + elif (action != 'pass') and (action != 'drop'): + output.Error('If you specify a filter action, it must be either ' + '\'action="pass"\' or \'action="drop"\'') + + # Set the rule + if action == 'drop': + self._pass = False + elif action == 'pass': + self._pass = True + + if type == 'wildcard': + self._wildcard = pattern + elif type == 'regexp': + try: + self._regexp = re.compile(pattern) + except re.error: + output.Error('Bad regular expression: %s' % pattern) + + # Log the final results iff we didn't add any errors + if num_errors == output.num_errors: + output.Log('Filter: %s any URL that matches %s "%s"' % + (action, type, pattern), 2) + #end def __init__ + + def Apply(self, url): + """ Process the URL, as above. """ + if (not url) or (not url.loc): + return None + + if self._wildcard: + if fnmatch.fnmatchcase(url.loc, self._wildcard): + return self._pass + return None + + if self._regexp: + if self._regexp.search(url.loc): + return self._pass + return None + + assert False # unreachable + #end def Apply +#end class Filter + + +class InputURL: + """ + Each Input class knows how to yield a set of URLs from a data source. + + This one handles a single URL, manually specified in the config file. + """ + + def __init__(self, attributes): + self._url = None # The lonely URL + + if not ValidateAttributes('URL', attributes, + ('href', 'lastmod', 'changefreq', 'priority')): + return + + url = URL() + for attr in attributes.keys(): + if attr == 'href': + url.TrySetAttribute('loc', attributes[attr]) + else: + url.TrySetAttribute(attr, attributes[attr]) + + if not url.loc: + output.Error('Url entries must have an href attribute.') + return + + self._url = url + output.Log('Input: From URL "%s"' % self._url.loc, 2) + #end def __init__ + + def ProduceURLs(self, consumer): + """ Produces URLs from our data source, hands them in to the consumer. """ + if self._url: + consumer(self._url, True) + #end def ProduceURLs +#end class InputURL + + +class InputURLList: + """ + Each Input class knows how to yield a set of URLs from a data source. + + This one handles a text file with a list of URLs + """ + + def __init__(self, attributes): + self._path = None # The file path + self._encoding = None # Encoding of that file + + if not ValidateAttributes('URLLIST', attributes, ('path', 'encoding')): + return + + self._path = attributes.get('path') + self._encoding = attributes.get('encoding', ENC_UTF8) + if self._path: + self._path = encoder.MaybeNarrowPath(self._path) + if os.path.isfile(self._path): + output.Log('Input: From URLLIST "%s"' % self._path, 2) + else: + output.Error('Can not locate file: %s' % self._path) + self._path = None + else: + output.Error('Urllist entries must have a "path" attribute.') + #end def __init__ + + def ProduceURLs(self, consumer): + """ Produces URLs from our data source, hands them in to the consumer. """ + + # Open the file + (frame, file) = OpenFileForRead(self._path, 'URLLIST') + if not file: + return + + # Iterate lines + linenum = 0 + for line in file.readlines(): + linenum = linenum + 1 + + # Strip comments and empty lines + if self._encoding: + line = encoder.WidenText(line, self._encoding) + line = line.strip() + if (not line) or line[0] == '#': + continue + + # Split the line on space + url = URL() + cols = line.split(' ') + for i in range(0,len(cols)): + cols[i] = cols[i].strip() + url.TrySetAttribute('loc', cols[0]) + + # Extract attributes from the other columns + for i in range(1,len(cols)): + if cols[i]: + try: + (attr_name, attr_val) = cols[i].split('=', 1) + url.TrySetAttribute(attr_name, attr_val) + except ValueError: + output.Warn('Line %d: Unable to parse attribute: %s' % + (linenum, cols[i])) + + # Pass it on + consumer(url, False) + + file.close() + if frame: + frame.close() + #end def ProduceURLs +#end class InputURLList + + +class InputNewsURLList: + """ + Each Input class knows how to yield a set of URLs from a data source. + + This one handles a text file with a list of News URLs and their metadata + """ + + def __init__(self, attributes): + self._path = None # The file path + self._encoding = None # Encoding of that file + self._tag_order = [] # Order of URL metadata + + if not ValidateAttributes('URLLIST', attributes, ('path', 'encoding', \ + 'tag_order')): + return + + self._path = attributes.get('path') + self._encoding = attributes.get('encoding', ENC_UTF8) + self._tag_order = attributes.get('tag_order') + + if self._path: + self._path = encoder.MaybeNarrowPath(self._path) + if os.path.isfile(self._path): + output.Log('Input: From URLLIST "%s"' % self._path, 2) + else: + output.Error('Can not locate file: %s' % self._path) + self._path = None + else: + output.Error('Urllist entries must have a "path" attribute.') + + # parse tag_order into an array + # tag_order_ascii created for more readable logging + tag_order_ascii = [] + if self._tag_order: + self._tag_order = self._tag_order.split(",") + for i in range(0, len(self._tag_order)): + element = self._tag_order[i].strip().lower() + self._tag_order[i]= element + tag_order_ascii.append(element.encode('ascii')) + output.Log('Input: From URLLIST tag order is "%s"' % tag_order_ascii, 0) + else: + output.Error('News Urllist configuration file must contain tag_order ' + 'to define Sitemap metatags.') + + # verify all tag_order inputs are valid + tag_order_dict = {} + for tag in self._tag_order: + tag_order_dict[tag] = "" + if not ValidateAttributes('URLLIST', tag_order_dict, \ + NEWS_SITEMAP_TAGS): + return + + # loc tag must be present + loc_tag = False + for tag in self._tag_order: + if tag == 'loc': + loc_tag = True + break + if not loc_tag: + output.Error('News Urllist tag_order in configuration file ' + 'does not contain "loc" value: %s' % tag_order_ascii) + #end def __init__ + + def ProduceURLs(self, consumer): + """ Produces URLs from our data source, hands them in to the consumer. """ + + # Open the file + (frame, file) = OpenFileForRead(self._path, 'URLLIST') + if not file: + return + + # Iterate lines + linenum = 0 + for line in file.readlines(): + linenum = linenum + 1 + + # Strip comments and empty lines + if self._encoding: + line = encoder.WidenText(line, self._encoding) + line = line.strip() + if (not line) or line[0] == '#': + continue + + # Split the line on tabs + url = NewsURL() + cols = line.split('\t') + for i in range(0,len(cols)): + cols[i] = cols[i].strip() + + for i in range(0,len(cols)): + if cols[i]: + attr_value = cols[i] + if i < len(self._tag_order): + attr_name = self._tag_order[i] + try: + url.TrySetAttribute(attr_name, attr_value) + except ValueError: + output.Warn('Line %d: Unable to parse attribute: %s' % + (linenum, cols[i])) + + # Pass it on + consumer(url, False) + + file.close() + if frame: + frame.close() + #end def ProduceURLs +#end class InputNewsURLList + + +class InputDirectory: + """ + Each Input class knows how to yield a set of URLs from a data source. + + This one handles a directory that acts as base for walking the filesystem. + """ + + def __init__(self, attributes, base_url): + self._path = None # The directory + self._url = None # The URL equivalent + self._default_file = None + self._remove_empty_directories = False + + if not ValidateAttributes('DIRECTORY', attributes, ('path', 'url', + 'default_file', 'remove_empty_directories')): + return + + # Prep the path -- it MUST end in a sep + path = attributes.get('path') + if not path: + output.Error('Directory entries must have both "path" and "url" ' + 'attributes') + return + path = encoder.MaybeNarrowPath(path) + if not path.endswith(os.sep): + path = path + os.sep + if not os.path.isdir(path): + output.Error('Can not locate directory: %s' % path) + return + + # Prep the URL -- it MUST end in a sep + url = attributes.get('url') + if not url: + output.Error('Directory entries must have both "path" and "url" ' + 'attributes') + return + url = URL.Canonicalize(url) + if not url.endswith('/'): + url = url + '/' + if not url.startswith(base_url): + url = urlparse.urljoin(base_url, url) + if not url.startswith(base_url): + output.Error('The directory URL "%s" is not relative to the ' + 'base_url: %s' % (url, base_url)) + return + + # Prep the default file -- it MUST be just a filename + file = attributes.get('default_file') + if file: + file = encoder.MaybeNarrowPath(file) + if os.sep in file: + output.Error('The default_file "%s" can not include path information.' + % file) + file = None + + # Prep the remove_empty_directories -- default is false + remove_empty_directories = attributes.get('remove_empty_directories') + if remove_empty_directories: + if (remove_empty_directories == '1') or \ + (remove_empty_directories.lower() == 'true'): + remove_empty_directories = True + elif (remove_empty_directories == '0') or \ + (remove_empty_directories.lower() == 'false'): + remove_empty_directories = False + # otherwise the user set a non-default value + else: + output.Error('Configuration file remove_empty_directories ' + 'value is not recognized. Value must be true or false.') + return + else: + remove_empty_directories = False + + self._path = path + self._url = url + self._default_file = file + self._remove_empty_directories = remove_empty_directories + + if file: + output.Log('Input: From DIRECTORY "%s" (%s) with default file "%s"' + % (path, url, file), 2) + else: + output.Log('Input: From DIRECTORY "%s" (%s) with no default file' + % (path, url), 2) + #end def __init__ + + + def ProduceURLs(self, consumer): + """ Produces URLs from our data source, hands them in to the consumer. """ + if not self._path: + return + + root_path = self._path + root_URL = self._url + root_file = self._default_file + remove_empty_directories = self._remove_empty_directories + + def HasReadPermissions(path): + """ Verifies a given path has read permissions. """ + stat_info = os.stat(path) + mode = stat_info[stat.ST_MODE] + if mode & stat.S_IREAD: + return True + else: + return None + + def PerFile(dirpath, name): + """ + Called once per file. + Note that 'name' will occasionally be None -- for a directory itself + """ + # Pull a timestamp + url = URL() + isdir = False + try: + if name: + path = os.path.join(dirpath, name) + else: + path = dirpath + isdir = os.path.isdir(path) + time = None + if isdir and root_file: + file = os.path.join(path, root_file) + try: + time = os.stat(file)[stat.ST_MTIME]; + except OSError: + pass + if not time: + time = os.stat(path)[stat.ST_MTIME]; + url.lastmod = TimestampISO8601(time) + except OSError: + pass + except ValueError: + pass + + # Build a URL + middle = dirpath[len(root_path):] + if os.sep != '/': + middle = middle.replace(os.sep, '/') + if middle: + middle = middle + '/' + if name: + middle = middle + name + if isdir: + middle = middle + '/' + url.TrySetAttribute('loc', root_URL + encoder.WidenText(middle, None)) + + # Suppress default files. (All the way down here so we can log it.) + if name and (root_file == name): + url.Log(prefix='IGNORED (default file)', level=2) + return + + # Suppress directories when remove_empty_directories="true" + try: + if isdir: + if HasReadPermissions(path): + if remove_empty_directories == 'true' and \ + len(os.listdir(path)) == 0: + output.Log('IGNORED empty directory %s' % str(path), level=1) + return + elif path == self._path: + output.Error('IGNORED configuration file directory input %s due ' + 'to file permissions' % self._path) + else: + output.Log('IGNORED files within directory %s due to file ' + 'permissions' % str(path), level=0) + except OSError: + pass + except ValueError: + pass + + consumer(url, False) + #end def PerFile + + def PerDirectory(ignore, dirpath, namelist): + """ + Called once per directory with a list of all the contained files/dirs. + """ + ignore = ignore # Avoid warnings of an unused parameter + + if not dirpath.startswith(root_path): + output.Warn('Unable to decide what the root path is for directory: ' + '%s' % dirpath) + return + + for name in namelist: + PerFile(dirpath, name) + #end def PerDirectory + + output.Log('Walking DIRECTORY "%s"' % self._path, 1) + PerFile(self._path, None) + os.path.walk(self._path, PerDirectory, None) + #end def ProduceURLs +#end class InputDirectory + + +class InputAccessLog: + """ + Each Input class knows how to yield a set of URLs from a data source. + + This one handles access logs. It's non-trivial in that we want to + auto-detect log files in the Common Logfile Format (as used by Apache, + for instance) and the Extended Log File Format (as used by IIS, for + instance). + """ + + def __init__(self, attributes): + self._path = None # The file path + self._encoding = None # Encoding of that file + self._is_elf = False # Extended Log File Format? + self._is_clf = False # Common Logfile Format? + self._elf_status = -1 # ELF field: '200' + self._elf_method = -1 # ELF field: 'HEAD' + self._elf_uri = -1 # ELF field: '/foo?bar=1' + self._elf_urifrag1 = -1 # ELF field: '/foo' + self._elf_urifrag2 = -1 # ELF field: 'bar=1' + + if not ValidateAttributes('ACCESSLOG', attributes, ('path', 'encoding')): + return + + self._path = attributes.get('path') + self._encoding = attributes.get('encoding', ENC_UTF8) + if self._path: + self._path = encoder.MaybeNarrowPath(self._path) + if os.path.isfile(self._path): + output.Log('Input: From ACCESSLOG "%s"' % self._path, 2) + else: + output.Error('Can not locate file: %s' % self._path) + self._path = None + else: + output.Error('Accesslog entries must have a "path" attribute.') + #end def __init__ + + def RecognizeELFLine(self, line): + """ Recognize the Fields directive that heads an ELF file """ + if not line.startswith('#Fields:'): + return False + fields = line.split(' ') + del fields[0] + for i in range(0, len(fields)): + field = fields[i].strip() + if field == 'sc-status': + self._elf_status = i + elif field == 'cs-method': + self._elf_method = i + elif field == 'cs-uri': + self._elf_uri = i + elif field == 'cs-uri-stem': + self._elf_urifrag1 = i + elif field == 'cs-uri-query': + self._elf_urifrag2 = i + output.Log('Recognized an Extended Log File Format file.', 2) + return True + #end def RecognizeELFLine + + def GetELFLine(self, line): + """ Fetch the requested URL from an ELF line """ + fields = line.split(' ') + count = len(fields) + + # Verify status was Ok + if self._elf_status >= 0: + if self._elf_status >= count: + return None + if not fields[self._elf_status].strip() == '200': + return None + + # Verify method was HEAD or GET + if self._elf_method >= 0: + if self._elf_method >= count: + return None + if not fields[self._elf_method].strip() in ('HEAD', 'GET'): + return None + + # Pull the full URL if we can + if self._elf_uri >= 0: + if self._elf_uri >= count: + return None + url = fields[self._elf_uri].strip() + if url != '-': + return url + + # Put together a fragmentary URL + if self._elf_urifrag1 >= 0: + if self._elf_urifrag1 >= count or self._elf_urifrag2 >= count: + return None + urlfrag1 = fields[self._elf_urifrag1].strip() + urlfrag2 = None + if self._elf_urifrag2 >= 0: + urlfrag2 = fields[self._elf_urifrag2] + if urlfrag1 and (urlfrag1 != '-'): + if urlfrag2 and (urlfrag2 != '-'): + urlfrag1 = urlfrag1 + '?' + urlfrag2 + return urlfrag1 + + return None + #end def GetELFLine + + def RecognizeCLFLine(self, line): + """ Try to tokenize a logfile line according to CLF pattern and see if + it works. """ + match = ACCESSLOG_CLF_PATTERN.match(line) + recognize = match and (match.group(1) in ('HEAD', 'GET')) + if recognize: + output.Log('Recognized a Common Logfile Format file.', 2) + return recognize + #end def RecognizeCLFLine + + def GetCLFLine(self, line): + """ Fetch the requested URL from a CLF line """ + match = ACCESSLOG_CLF_PATTERN.match(line) + if match: + request = match.group(1) + if request in ('HEAD', 'GET'): + return match.group(2) + return None + #end def GetCLFLine + + def ProduceURLs(self, consumer): + """ Produces URLs from our data source, hands them in to the consumer. """ + + # Open the file + (frame, file) = OpenFileForRead(self._path, 'ACCESSLOG') + if not file: + return + + # Iterate lines + for line in file.readlines(): + if self._encoding: + line = encoder.WidenText(line, self._encoding) + line = line.strip() + + # If we don't know the format yet, try them both + if (not self._is_clf) and (not self._is_elf): + self._is_elf = self.RecognizeELFLine(line) + self._is_clf = self.RecognizeCLFLine(line) + + # Digest the line + match = None + if self._is_elf: + match = self.GetELFLine(line) + elif self._is_clf: + match = self.GetCLFLine(line) + if not match: + continue + + # Pass it on + url = URL() + url.TrySetAttribute('loc', match) + consumer(url, True) + + file.close() + if frame: + frame.close() + #end def ProduceURLs +#end class InputAccessLog + + +class FilePathGenerator: + """ + This class generates filenames in a series, upon request. + You can request any iteration number at any time, you don't + have to go in order. + + Example of iterations for '/path/foo.xml.gz': + 0 --> /path/foo.xml.gz + 1 --> /path/foo1.xml.gz + 2 --> /path/foo2.xml.gz + _index.xml --> /path/foo_index.xml + """ + + def __init__(self): + self.is_gzip = False # Is this a GZIP file? + + self._path = None # '/path/' + self._prefix = None # 'foo' + self._suffix = None # '.xml.gz' + #end def __init__ + + def Preload(self, path): + """ Splits up a path into forms ready for recombination. """ + path = encoder.MaybeNarrowPath(path) + + # Get down to a base name + path = os.path.normpath(path) + base = os.path.basename(path).lower() + if not base: + output.Error('Couldn\'t parse the file path: %s' % path) + return False + lenbase = len(base) + + # Recognize extension + lensuffix = 0 + compare_suffix = ['.xml', '.xml.gz', '.gz'] + for suffix in compare_suffix: + if base.endswith(suffix): + lensuffix = len(suffix) + break + if not lensuffix: + output.Error('The path "%s" doesn\'t end in a supported file ' + 'extension.' % path) + return False + self.is_gzip = suffix.endswith('.gz') + + # Split the original path + lenpath = len(path) + self._path = path[:lenpath-lenbase] + self._prefix = path[lenpath-lenbase:lenpath-lensuffix] + self._suffix = path[lenpath-lensuffix:] + + return True + #end def Preload + + def GeneratePath(self, instance): + """ Generates the iterations, as described above. """ + prefix = self._path + self._prefix + if type(instance) == types.IntType: + if instance: + return '%s%d%s' % (prefix, instance, self._suffix) + return prefix + self._suffix + return prefix + instance + #end def GeneratePath + + def GenerateURL(self, instance, root_url): + """ Generates iterations, but as a URL instead of a path. """ + prefix = root_url + self._prefix + retval = None + if type(instance) == types.IntType: + if instance: + retval = '%s%d%s' % (prefix, instance, self._suffix) + else: + retval = prefix + self._suffix + else: + retval = prefix + instance + return URL.Canonicalize(retval) + #end def GenerateURL + + def GenerateWildURL(self, root_url): + """ Generates a wildcard that should match all our iterations """ + prefix = URL.Canonicalize(root_url + self._prefix) + temp = URL.Canonicalize(prefix + self._suffix) + suffix = temp[len(prefix):] + return prefix + '*' + suffix + #end def GenerateURL +#end class FilePathGenerator + + +class PerURLStatistics: + """ Keep track of some simple per-URL statistics, like file extension. """ + + def __init__(self): + self._extensions = {} # Count of extension instances + #end def __init__ + + def Consume(self, url): + """ Log some stats for the URL. At the moment, that means extension. """ + if url and url.loc: + (scheme, netloc, path, query, frag) = urlparse.urlsplit(url.loc) + if not path: + return + + # Recognize directories + if path.endswith('/'): + if self._extensions.has_key('/'): + self._extensions['/'] = self._extensions['/'] + 1 + else: + self._extensions['/'] = 1 + return + + # Strip to a filename + i = path.rfind('/') + if i >= 0: + assert i < len(path) + path = path[i:] + + # Find extension + i = path.rfind('.') + if i > 0: + assert i < len(path) + ext = path[i:].lower() + if self._extensions.has_key(ext): + self._extensions[ext] = self._extensions[ext] + 1 + else: + self._extensions[ext] = 1 + else: + if self._extensions.has_key('(no extension)'): + self._extensions['(no extension)'] = self._extensions[ + '(no extension)'] + 1 + else: + self._extensions['(no extension)'] = 1 + #end def Consume + + def Log(self): + """ Dump out stats to the output. """ + if len(self._extensions): + output.Log('Count of file extensions on URLs:', 1) + set = self._extensions.keys() + set.sort() + for ext in set: + output.Log(' %7d %s' % (self._extensions[ext], ext), 1) + #end def Log + +class Sitemap(xml.sax.handler.ContentHandler): + """ + This is the big workhorse class that processes your inputs and spits + out sitemap files. It is built as a SAX handler for set up purposes. + That is, it processes an XML stream to bring itself up. + """ + + def __init__(self, suppress_notify): + xml.sax.handler.ContentHandler.__init__(self) + self._filters = [] # Filter objects + self._inputs = [] # Input objects + self._urls = {} # Maps URLs to count of dups + self._set = [] # Current set of URLs + self._filegen = None # Path generator for output files + self._wildurl1 = None # Sitemap URLs to filter out + self._wildurl2 = None # Sitemap URLs to filter out + self._sitemaps = 0 # Number of output files + # We init _dup_max to 2 so the default priority is 0.5 instead of 1.0 + self._dup_max = 2 # Max number of duplicate URLs + self._stat = PerURLStatistics() # Some simple stats + self._in_site = False # SAX: are we in a Site node? + self._in_Site_ever = False # SAX: were we ever in a Site? + + self._default_enc = None # Best encoding to try on URLs + self._base_url = None # Prefix to all valid URLs + self._store_into = None # Output filepath + self._sitemap_type = None # Sitemap type (web, mobile or news) + self._suppress = suppress_notify # Suppress notify of servers + #end def __init__ + + def ValidateBasicConfig(self): + """ Verifies (and cleans up) the basic user-configurable options. """ + all_good = True + + if self._default_enc: + encoder.SetUserEncoding(self._default_enc) + + # Canonicalize the base_url + if all_good and not self._base_url: + output.Error('A site needs a "base_url" attribute.') + all_good = False + if all_good and not URL.IsAbsolute(self._base_url): + output.Error('The "base_url" must be absolute, not relative: %s' % + self._base_url) + all_good = False + if all_good: + self._base_url = URL.Canonicalize(self._base_url) + if not self._base_url.endswith('/'): + self._base_url = self._base_url + '/' + output.Log('BaseURL is set to: %s' % self._base_url, 2) + + # Load store_into into a generator + if all_good: + if self._store_into: + self._filegen = FilePathGenerator() + if not self._filegen.Preload(self._store_into): + all_good = False + else: + output.Error('A site needs a "store_into" attribute.') + all_good = False + + # Ask the generator for patterns on what its output will look like + if all_good: + self._wildurl1 = self._filegen.GenerateWildURL(self._base_url) + self._wildurl2 = self._filegen.GenerateURL(SITEINDEX_SUFFIX, + self._base_url) + + # Unify various forms of False + if all_good: + if self._suppress: + if (type(self._suppress) == types.StringType) or (type(self._suppress) + == types.UnicodeType): + if (self._suppress == '0') or (self._suppress.lower() == 'false'): + self._suppress = False + + # Clean up the sitemap_type + if all_good: + match = False + # If sitemap_type is not specified, default to web sitemap + if not self._sitemap_type: + self._sitemap_type = 'web' + else: + self._sitemap_type = self._sitemap_type.lower() + for pattern in SITEMAP_TYPES: + if self._sitemap_type == pattern: + match = True + break + if not match: + output.Error('The "sitemap_type" value must be "web", "mobile" ' + 'or "news": %s' % self._sitemap_type) + all_good = False + output.Log('The Sitemap type is %s Sitemap.' % \ + self._sitemap_type.upper(), 0) + + # Done + if not all_good: + output.Log('See "example_config.xml" for more information.', 0) + return all_good + #end def ValidateBasicConfig + + def Generate(self): + """ Run over all the Inputs and ask them to Produce """ + # Run the inputs + for input in self._inputs: + input.ProduceURLs(self.ConsumeURL) + + # Do last flushes + if len(self._set): + self.FlushSet() + if not self._sitemaps: + output.Warn('No URLs were recorded, writing an empty sitemap.') + self.FlushSet() + + # Write an index as needed + if self._sitemaps > 1: + self.WriteIndex() + + # Notify + self.NotifySearch() + + # Dump stats + self._stat.Log() + #end def Generate + + def ConsumeURL(self, url, allow_fragment): + """ + All per-URL processing comes together here, regardless of Input. + Here we run filters, remove duplicates, spill to disk as needed, etc. + + """ + if not url: + return + + # Validate + if not url.Validate(self._base_url, allow_fragment): + return + + # Run filters + accept = None + for filter in self._filters: + accept = filter.Apply(url) + if accept != None: + break + if not (accept or (accept == None)): + url.Log(prefix='FILTERED', level=2) + return + + # Ignore our out output URLs + if fnmatch.fnmatchcase(url.loc, self._wildurl1) or fnmatch.fnmatchcase( + url.loc, self._wildurl2): + url.Log(prefix='IGNORED (output file)', level=2) + return + + # Note the sighting + hash = url.MakeHash() + if self._urls.has_key(hash): + dup = self._urls[hash] + if dup > 0: + dup = dup + 1 + self._urls[hash] = dup + if self._dup_max < dup: + self._dup_max = dup + url.Log(prefix='DUPLICATE') + return + + # Acceptance -- add to set + self._urls[hash] = 1 + self._set.append(url) + self._stat.Consume(url) + url.Log() + + # Flush the set if needed + if len(self._set) >= MAXURLS_PER_SITEMAP: + self.FlushSet() + #end def ConsumeURL + + def FlushSet(self): + """ + Flush the current set of URLs to the output. This is a little + slow because we like to sort them all and normalize the priorities + before dumping. + """ + + # Determine what Sitemap header to use (News or General) + if self._sitemap_type == 'news': + sitemap_header = NEWS_SITEMAP_HEADER + else: + sitemap_header = GENERAL_SITEMAP_HEADER + + # Sort and normalize + output.Log('Sorting and normalizing collected URLs.', 1) + self._set.sort() + for url in self._set: + hash = url.MakeHash() + dup = self._urls[hash] + if dup > 0: + self._urls[hash] = -1 + if not url.priority: + url.priority = '%.4f' % (float(dup) / float(self._dup_max)) + + # Get the filename we're going to write to + filename = self._filegen.GeneratePath(self._sitemaps) + if not filename: + output.Fatal('Unexpected: Couldn\'t generate output filename.') + self._sitemaps = self._sitemaps + 1 + output.Log('Writing Sitemap file "%s" with %d URLs' % + (filename, len(self._set)), 1) + + # Write to it + frame = None + file = None + + try: + if self._filegen.is_gzip: + basename = os.path.basename(filename); + frame = open(filename, 'wb') + file = gzip.GzipFile(fileobj=frame, filename=basename, mode='wt') + else: + file = open(filename, 'wt') + + file.write(sitemap_header) + for url in self._set: + url.WriteXML(file) + file.write(SITEMAP_FOOTER) + + file.close() + if frame: + frame.close() + + frame = None + file = None + except IOError: + output.Fatal('Couldn\'t write out to file: %s' % filename) + os.chmod(filename, 0644) + + # Flush + self._set = [] + #end def FlushSet + + def WriteIndex(self): + """ Write the master index of all Sitemap files """ + # Make a filename + filename = self._filegen.GeneratePath(SITEINDEX_SUFFIX) + if not filename: + output.Fatal('Unexpected: Couldn\'t generate output index filename.') + output.Log('Writing index file "%s" with %d Sitemaps' % + (filename, self._sitemaps), 1) + + # Determine what Sitemap index header to use (News or General) + if self._sitemap_type == 'news': + sitemap_index_header = NEWS_SITEMAP_HEADER + else: + sitemap__index_header = GENERAL_SITEMAP_HEADER + + # Make a lastmod time + lastmod = TimestampISO8601(time.time()) + + # Write to it + try: + fd = open(filename, 'wt') + fd.write(sitemap_index_header) + + for mapnumber in range(0,self._sitemaps): + # Write the entry + mapurl = self._filegen.GenerateURL(mapnumber, self._base_url) + mapattributes = { 'loc' : mapurl, 'lastmod' : lastmod } + fd.write(SITEINDEX_ENTRY % mapattributes) + + fd.write(SITEINDEX_FOOTER) + + fd.close() + fd = None + except IOError: + output.Fatal('Couldn\'t write out to file: %s' % filename) + os.chmod(filename, 0644) + #end def WriteIndex + + def NotifySearch(self): + """ Send notification of the new Sitemap(s) to the search engines. """ + if self._suppress: + output.Log('Search engine notification is suppressed.', 1) + return + + output.Log('Notifying search engines.', 1) + + # Override the urllib's opener class with one that doesn't ignore 404s + class ExceptionURLopener(urllib.FancyURLopener): + def http_error_default(self, url, fp, errcode, errmsg, headers): + output.Log('HTTP error %d: %s' % (errcode, errmsg), 2) + raise IOError + #end def http_error_default + #end class ExceptionURLOpener + old_opener = urllib._urlopener + urllib._urlopener = ExceptionURLopener() + + # Build the URL we want to send in + if self._sitemaps > 1: + url = self._filegen.GenerateURL(SITEINDEX_SUFFIX, self._base_url) + else: + url = self._filegen.GenerateURL(0, self._base_url) + + # Test if we can hit it ourselves + try: + u = urllib.urlopen(url) + u.close() + except IOError: + output.Error('When attempting to access our generated Sitemap at the ' + 'following URL:\n %s\n we failed to read it. Please ' + 'verify the store_into path you specified in\n' + ' your configuration file is web-accessable. Consult ' + 'the FAQ for more\n information.' % url) + output.Warn('Proceeding to notify with an unverifyable URL.') + + # Cycle through notifications + # To understand this, see the comment near the NOTIFICATION_SITES comment + for ping in NOTIFICATION_SITES: + query_map = ping[3] + query_attr = ping[5] + query_map[query_attr] = url + query = urllib.urlencode(query_map) + notify = urlparse.urlunsplit((ping[0], ping[1], ping[2], query, ping[4])) + + # Send the notification + output.Log('Notifying: %s' % ping[1], 0) + output.Log('Notification URL: %s' % notify, 2) + try: + u = urllib.urlopen(notify) + u.read() + u.close() + except IOError: + output.Warn('Cannot contact: %s' % ping[1]) + + if old_opener: + urllib._urlopener = old_opener + #end def NotifySearch + + def startElement(self, tag, attributes): + """ SAX processing, called per node in the config stream. """ + if tag == 'site': + if self._in_site: + output.Error('Can not nest Site entries in the configuration.') + else: + self._in_site = True + + if not ValidateAttributes('SITE', attributes, + ('verbose', 'default_encoding', 'base_url', 'store_into', + 'suppress_search_engine_notify', 'sitemap_type')): + return + + verbose = attributes.get('verbose', 0) + if verbose: + output.SetVerbose(verbose) + + self._default_enc = attributes.get('default_encoding') + self._base_url = attributes.get('base_url') + self._store_into = attributes.get('store_into') + self._sitemap_type= attributes.get('sitemap_type') + if not self._suppress: + self._suppress = attributes.get('suppress_search_engine_notify', + False) + self.ValidateBasicConfig() + elif tag == 'filter': + self._filters.append(Filter(attributes)) + + elif tag == 'url': + print type(attributes) + self._inputs.append(InputURL(attributes)) + + elif tag == 'urllist': + for attributeset in ExpandPathAttribute(attributes, 'path'): + if self._sitemap_type == 'news': + self._inputs.append(InputNewsURLList(attributeset)) + else: + self._inputs.append(InputURLList(attributeset)) + + elif tag == 'directory': + self._inputs.append(InputDirectory(attributes, self._base_url)) + + elif tag == 'accesslog': + for attributeset in ExpandPathAttribute(attributes, 'path'): + self._inputs.append(InputAccessLog(attributeset)) + else: + output.Error('Unrecognized tag in the configuration: %s' % tag) + #end def startElement + + def endElement(self, tag): + """ SAX processing, called per node in the config stream. """ + if tag == 'site': + assert self._in_site + self._in_site = False + self._in_site_ever = True + #end def endElement + + def endDocument(self): + """ End of SAX, verify we can proceed. """ + if not self._in_site_ever: + output.Error('The configuration must specify a "site" element.') + else: + if not self._inputs: + output.Warn('There were no inputs to generate a sitemap from.') + #end def endDocument +#end class Sitemap + + +def ValidateAttributes(tag, attributes, goodattributes): + """ Makes sure 'attributes' does not contain any attribute not + listed in 'goodattributes' """ + all_good = True + for attr in attributes.keys(): + if not attr in goodattributes: + output.Error('Unknown %s attribute: %s' % (tag, attr)) + all_good = False + return all_good +#end def ValidateAttributes + +def ExpandPathAttribute(src, attrib): + """ Given a dictionary of attributes, return a list of dictionaries + with all the same attributes except for the one named attrib. + That one, we treat as a file path and expand into all its possible + variations. """ + # Do the path expansion. On any error, just return the source dictionary. + path = src.get(attrib) + if not path: + return [src] + path = encoder.MaybeNarrowPath(path); + pathlist = glob.glob(path) + if not pathlist: + return [src] + + # If this isn't actually a dictionary, make it one + if type(src) != types.DictionaryType: + tmp = {} + for key in src.keys(): + tmp[key] = src[key] + src = tmp + # Create N new dictionaries + retval = [] + for path in pathlist: + dst = src.copy() + dst[attrib] = path + retval.append(dst) + + return retval +#end def ExpandPathAttribute + +def OpenFileForRead(path, logtext): + """ Opens a text file, be it GZip or plain """ + + frame = None + file = None + + if not path: + return (frame, file) + + try: + if path.endswith('.gz'): + frame = open(path, 'rb') + file = gzip.GzipFile(fileobj=frame, mode='rt') + else: + file = open(path, 'rt') + + if logtext: + output.Log('Opened %s file: %s' % (logtext, path), 1) + else: + output.Log('Opened file: %s' % path, 1) + except IOError: + output.Error('Can not open file: %s' % path) + + return (frame, file) +#end def OpenFileForRead + +def TimestampISO8601(t): + """Seconds since epoch (1970-01-01) --> ISO 8601 time string.""" + return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(t)) +#end def TimestampISO8601 + +def CreateSitemapFromFile(configpath, suppress_notify): + """ Sets up a new Sitemap object from the specified configuration file. """ + + # Remember error count on the way in + num_errors = output.num_errors + + # Rev up SAX to parse the config + sitemap = Sitemap(suppress_notify) + try: + output.Log('Reading configuration file: %s' % configpath, 0) + xml.sax.parse(configpath, sitemap) + except IOError: + output.Error('Cannot read configuration file: %s' % configpath) + except xml.sax._exceptions.SAXParseException, e: + output.Error('XML error in the config file (line %d, column %d): %s' % + (e._linenum, e._colnum, e.getMessage())) + except xml.sax._exceptions.SAXReaderNotAvailable: + output.Error('Some installs of Python 2.2 did not include complete support' + ' for XML.\n Please try upgrading your version of Python' + ' and re-running the script.') + + # If we added any errors, return no sitemap + if num_errors == output.num_errors: + return sitemap + return None +#end def CreateSitemapFromFile + +def ProcessCommandFlags(args): + """ + Parse command line flags per specified usage, pick off key, value pairs + All flags of type "--key=value" will be processed as __flags[key] = value, + "--option" will be processed as __flags[option] = option + """ + + flags = {} + rkeyval = '--(?P\S*)[=](?P\S*)' # --key=val + roption = '--(?P
    This is basically the same as os, but this time + * for the incoming edges. + * + * For undirected graph, the same edge list is stored, ie. an + * undirected edge is stored only once, and for checking whether there + * is an undirected edge from \c v1 to \c v2 one + * should search for both \c from=v1, \c to=v2 and + * \c from=v2, \c to=v1. + * + * The storage requirements for a graph with \c |V| vertices + * and \c |E| edges is \c O(|E|+|V|). + */ +typedef struct igraph_s { + igraph_integer_t n; + igraph_bool_t directed; + igraph_vector_t from; + igraph_vector_t to; + igraph_vector_t oi; + igraph_vector_t ii; + igraph_vector_t os; + igraph_vector_t is; + void *attr; +} igraph_t; + +__END_DECLS + +#endif diff --git a/include/igraph_decls.h b/include/igraph_decls.h new file mode 100644 index 0000000..1971469 --- /dev/null +++ b/include/igraph_decls.h @@ -0,0 +1,26 @@ +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus + #define __BEGIN_DECLS extern "C" { + #define __END_DECLS } +#else + #define __BEGIN_DECLS /* empty */ + #define __END_DECLS /* empty */ +#endif + +#undef DECLDIR +#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64) + #if defined (__MINGW32__) || defined (__CYGWIN32__) + #define DECLDIR /**/ + #else + #ifdef IGRAPH_EXPORTS + #define DECLDIR __declspec(dllexport) + #elif defined(IGRAPH_STATIC) + #define DECLDIR /**/ + #else + #define DECLDIR __declspec(dllimport) + #endif + #endif +#else + #define DECLDIR /**/ +#endif diff --git a/include/igraph_dqueue.h b/include/igraph_dqueue.h new file mode 100644 index 0000000..dc1d7bd --- /dev/null +++ b/include/igraph_dqueue.h @@ -0,0 +1,73 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_DQUEUE_H +#define IGRAPH_DQUEUE_H + +#include "igraph_types.h" +#include "igraph_decls.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* double ended queue, very useful */ +/* -------------------------------------------------- */ + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_dqueue_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_LONG +#include "igraph_pmt.h" +#include "igraph_dqueue_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_LONG + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "igraph_dqueue_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "igraph_dqueue_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_INT +#include "igraph_pmt.h" +#include "igraph_dqueue_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define IGRAPH_DQUEUE_NULL { 0,0,0,0 } +#define IGRAPH_DQUEUE_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_dqueue_init(v, size)); \ + IGRAPH_FINALLY(igraph_dqueue_destroy, v); } while (0) + +__END_DECLS + +#endif diff --git a/include/igraph_dqueue_pmt.h b/include/igraph_dqueue_pmt.h new file mode 100644 index 0000000..81aecf6 --- /dev/null +++ b/include/igraph_dqueue_pmt.h @@ -0,0 +1,49 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/** + * Double ended queue data type. + * \ingroup internal + */ + +typedef struct TYPE(igraph_dqueue) { + BASE *begin; + BASE *end; + BASE *stor_begin; + BASE *stor_end; +} TYPE(igraph_dqueue); + +DECLDIR int FUNCTION(igraph_dqueue, init) (TYPE(igraph_dqueue)* q, long int size); +DECLDIR void FUNCTION(igraph_dqueue, destroy) (TYPE(igraph_dqueue)* q); +DECLDIR igraph_bool_t FUNCTION(igraph_dqueue, empty) (const TYPE(igraph_dqueue)* q); +DECLDIR void FUNCTION(igraph_dqueue, clear) (TYPE(igraph_dqueue)* q); +DECLDIR igraph_bool_t FUNCTION(igraph_dqueue, full) (TYPE(igraph_dqueue)* q); +DECLDIR long int FUNCTION(igraph_dqueue, size) (const TYPE(igraph_dqueue)* q); +DECLDIR BASE FUNCTION(igraph_dqueue, pop) (TYPE(igraph_dqueue)* q); +DECLDIR BASE FUNCTION(igraph_dqueue, pop_back)(TYPE(igraph_dqueue)* q); +DECLDIR BASE FUNCTION(igraph_dqueue, head) (const TYPE(igraph_dqueue)* q); +DECLDIR BASE FUNCTION(igraph_dqueue, back) (const TYPE(igraph_dqueue)* q); +DECLDIR int FUNCTION(igraph_dqueue, push) (TYPE(igraph_dqueue)* q, BASE elem); +int FUNCTION(igraph_dqueue, print)(const TYPE(igraph_dqueue)* q); +int FUNCTION(igraph_dqueue, fprint)(const TYPE(igraph_dqueue)* q, FILE *file); +DECLDIR BASE FUNCTION(igraph_dqueue, e)(const TYPE(igraph_dqueue) *q, long int idx); diff --git a/include/igraph_eigen.h b/include/igraph_eigen.h new file mode 100644 index 0000000..3c1459d --- /dev/null +++ b/include/igraph_eigen.h @@ -0,0 +1,112 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_arpack.h" +#include "igraph_lapack.h" +#include "igraph_sparsemat.h" + +#ifndef IGRAPH_EIGEN_H +#define IGRAPH_EIGEN_H + +#include "igraph_decls.h" + +__BEGIN_DECLS + +typedef enum { IGRAPH_EIGEN_AUTO = 0, + IGRAPH_EIGEN_LAPACK, + IGRAPH_EIGEN_ARPACK, + IGRAPH_EIGEN_COMP_AUTO, + IGRAPH_EIGEN_COMP_LAPACK, + IGRAPH_EIGEN_COMP_ARPACK + } igraph_eigen_algorithm_t; + +typedef enum { IGRAPH_EIGEN_LM = 0, + IGRAPH_EIGEN_SM, /* 1 */ + IGRAPH_EIGEN_LA, /* 2 */ + IGRAPH_EIGEN_SA, /* 3 */ + IGRAPH_EIGEN_BE, /* 4 */ + IGRAPH_EIGEN_LR, /* 5 */ + IGRAPH_EIGEN_SR, /* 6 */ + IGRAPH_EIGEN_LI, /* 7 */ + IGRAPH_EIGEN_SI, /* 8 */ + IGRAPH_EIGEN_ALL, /* 9 */ + IGRAPH_EIGEN_INTERVAL, /* 10 */ + IGRAPH_EIGEN_SELECT + } /* 11 */ +igraph_eigen_which_position_t; + +typedef struct igraph_eigen_which_t { + igraph_eigen_which_position_t pos; + int howmany; + int il, iu; + igraph_real_t vl, vu; + int vestimate; + igraph_lapack_dgeevx_balance_t balance; +} igraph_eigen_which_t; + +DECLDIR int igraph_eigen_matrix_symmetric(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, int n, + void *extra, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors); + +DECLDIR int igraph_eigen_matrix(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, int n, + void *extra, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors); + +DECLDIR int igraph_eigen_adjacency(const igraph_t *graph, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_vector_complex_t *cmplxvalues, + igraph_matrix_complex_t *cmplxvectors); + +DECLDIR int igraph_eigen_laplacian(const igraph_t *graph, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_vector_complex_t *cmplxvalues, + igraph_matrix_complex_t *cmplxvectors); + + +__END_DECLS + +#endif diff --git a/include/igraph_embedding.h b/include/igraph_embedding.h new file mode 100644 index 0000000..e5822ed --- /dev/null +++ b/include/igraph_embedding.h @@ -0,0 +1,69 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_EMBEDDING_H +#define IGRAPH_EMBEDDING_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_arpack.h" +#include "igraph_eigen.h" +#include "igraph_constants.h" + +__BEGIN_DECLS + +DECLDIR int igraph_adjacency_spectral_embedding(const igraph_t *graph, + igraph_integer_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + const igraph_vector_t *cvec, + igraph_arpack_options_t *options); + +typedef enum { + IGRAPH_EMBEDDING_D_A = 0, + IGRAPH_EMBEDDING_I_DAD, + IGRAPH_EMBEDDING_DAD, + IGRAPH_EMBEDDING_OAP +} igraph_laplacian_spectral_embedding_type_t; + +DECLDIR int igraph_laplacian_spectral_embedding(const igraph_t *graph, + igraph_integer_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_neimode_t degmode, + igraph_laplacian_spectral_embedding_type_t type, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + igraph_arpack_options_t *options); + +DECLDIR int igraph_dim_select(const igraph_vector_t *sv, igraph_integer_t *dim); + +__END_DECLS + +#endif diff --git a/include/igraph_epidemics.h b/include/igraph_epidemics.h new file mode 100644 index 0000000..445c250 --- /dev/null +++ b/include/igraph_epidemics.h @@ -0,0 +1,66 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_EPIDEMICS_H +#define IGRAPH_EPIDEMICS_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" + +__BEGIN_DECLS + +/** + * \struct igraph_sir_t + * + * Data structure to store the results of one simulation + * of the SIR (susceptible-infected-recovered) model on a graph. + * + * It has the following members. They are all (real or integer) + * vectors, and they are of the same length. + * + * \member times A vector, the times of the events are stored here. + * \member no_s An integer vector, the number of susceptibles in + * each time step is stored here. + * \member no_i An integer vector, the number of infected individuals + * at each time step, is stored here. + * \member no_r An integer vector, the number of recovered individuals + * is stored here at each time step. + */ + +typedef struct igraph_sir_t { + igraph_vector_t times; + igraph_vector_int_t no_s, no_i, no_r; +} igraph_sir_t; + +DECLDIR int igraph_sir_init(igraph_sir_t *sir); +DECLDIR void igraph_sir_destroy(igraph_sir_t *sir); + +DECLDIR int igraph_sir(const igraph_t *graph, igraph_real_t beta, + igraph_real_t gamma, igraph_integer_t no_sim, + igraph_vector_ptr_t *result); + +__END_DECLS + +#endif diff --git a/include/igraph_error.h b/include/igraph_error.h new file mode 100644 index 0000000..58f1cec --- /dev/null +++ b/include/igraph_error.h @@ -0,0 +1,720 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_ERROR_H +#define IGRAPH_ERROR_H + +#include + +#include "igraph_decls.h" + +__BEGIN_DECLS + +/* This file contains the igraph error handling. + * Most bits are taken literally from the GSL library (with the GSL_ + * prefix renamed to IGRAPH_), as I couldn't find a better way to do + * them. */ + +/** + * \section errorhandlingbasics Error handling basics + * + * \a igraph functions can run into various problems preventing them + * from normal operation. The user might have supplied invalid arguments, + * e.g. a non-square matrix when a square-matrix was expected, or the program + * has run out of memory while some more memory allocation is required, etc. + * + * + * By default \a igraph aborts the program when it runs into an + * error. While this behavior might be good enough for smaller programs, + * it is without doubt avoidable in larger projects. Please read further + * if your project requires more sophisticated error handling. You can + * safely skip the rest of this chapter otherwise. + * + */ + +/** + * \section errorhandlers Error handlers + * + * + * If \a igraph runs into an error - an invalid argument was supplied + * to a function, or we've ran out of memory - the control is + * transferred to the \emb error handler \eme function. + * + * The default error handler is \ref igraph_error_handler_abort which + * prints an error message and aborts the program. + * + * + * The \ref igraph_set_error_handler() function can be used to set a new + * error handler function of type \ref igraph_error_handler_t; see the + * documentation of this type for details. + * + * + * There are two other predefined error handler functions, + * \ref igraph_error_handler_ignore and \ref igraph_error_handler_printignore. + * These deallocate the temporarily allocated memory (more about this + * later) and return with the error code. The latter also prints an + * error message. If you use these error handlers you need to take + * care about possible errors yourself by checking the return value of + * (almost) every non-void \a igraph function. + * + * Independently of the error handler installed, all functions in the + * library do their best to leave their arguments + * \em semantically unchanged if an error + * happens. By semantically we mean that the implementation of an + * object supplied as an argument might change, but its + * \quote meaning \endquote in most cases does not. The rare occasions + * when this rule is violated are documented in this manual. + * + */ + +/** + * \section errorcodes Error codes + * + * Every \a igraph function which can fail return a + * single integer error code. Some functions are very simple and + * cannot run into any error, these may return other types, or + * \type void as well. The error codes are defined by the + * \ref igraph_error_type_t enumeration. + * + */ + +/** + * \section writing_error_handlers Writing error handlers + * + * + * The contents of the rest of this chapter might be useful only + * for those who want to create an interface to \a igraph from another + * language. Most readers can safely skip to the next chapter. + * + * + * + * You can write and install error handlers simply by defining a + * function of type \ref igraph_error_handler_t and calling + * \ref igraph_set_error_handler(). This feature is useful for interface + * writers, as \a igraph will have the chance to + * signal errors the appropriate way, e.g. the R interface defines an + * error handler which calls the error() + * function, as required by R, while the Python interface has an error + * handler which raises an exception according to the Python way. + * + * + * If you want to write an error handler, your error handler should + * call \ref IGRAPH_FINALLY_FREE() to deallocate all temporary memory to + * prevent memory leaks. + * + */ + +/** + * \section error_handling_internals Error handling internals + * + * + * If an error happens, the functions in the library call the + * \ref IGRAPH_ERROR macro with a textual description of the error and an + * \a igraph error code. This macro calls (through the \ref + * igraph_error() function) the installed error handler. Another useful + * macro is \ref IGRAPH_CHECK(). This checks the return value of its + * argument, which is normally a function call, and calls \ref + * IGRAPH_ERROR if it is not \c IGRAPH_SUCCESS. + * + */ + +/** + * \section deallocating_memory Deallocating memory + * + * + * If a function runs into an error (and the program is not aborted) + * the error handler should deallocate all temporary memory. This is + * done by storing the address and the destroy function of all temporary + * objects in a stack. The \ref IGRAPH_FINALLY function declares an object as + * temporary by placing its address in the stack. If an \a igraph function returns + * with success it calls \ref IGRAPH_FINALLY_CLEAN() with the + * number of objects to remove from the stack. If an error happens + * however, the error handler should call \ref IGRAPH_FINALLY_FREE() to + * deallocate each object added to the stack. This means that the + * temporary objects allocated in the calling function (and etc.) will + * be freed as well. + * + */ + +/** + * \section writing_functions_error_handling Writing \a igraph functions with + * proper error handling + * + * + * There are some simple rules to keep in order to have functions + * behaving well in erroneous situations. First, check the arguments + * of the functions and call \ref IGRAPH_ERROR if they are invalid. Second, + * call \ref IGRAPH_FINALLY on each dynamically allocated object and call + * \ref IGRAPH_FINALLY_CLEAN() with the proper argument before returning. Third, use + * \ref IGRAPH_CHECK on all \a igraph function calls which can generate errors. + * + * + * The size of the stack used for this bookkeeping is fixed, and + * small. If you want to allocate several objects, write a destroy + * function which can deallocate all of these. See the + * adjlist.c file in the + * \a igraph source for an example. + * + * + * For some functions these mechanisms are simply not flexible + * enough. These functions should define their own error handlers and + * restore the error handler before they return. + * + */ + +/** + * \section error_handling_threads Error handling and threads + * + * + * It is likely that the \a igraph error handling + * method is \em not thread-safe, mainly because of + * the static global stack which is used to store the address of the + * temporarily allocated objects. This issue might be addressed in a + * later version of \a igraph. + * + */ + +/** + * \typedef igraph_error_handler_t + * \brief Type of error handler functions. + * + * This is the type of the error handler functions. + * \param reason Textual description of the error. + * \param file The source file in which the error is noticed. + * \param line The number of the line in the source file which triggered + * the error + * \param igraph_errno The \a igraph error code. + */ + +typedef void igraph_error_handler_t (const char * reason, const char * file, + int line, int igraph_errno); + +/** + * \var igraph_error_handler_abort + * \brief Abort program in case of error. + * + * The default error handler, prints an error message and aborts the + * program. + */ + +extern igraph_error_handler_t igraph_error_handler_abort; + +/** + * \var igraph_error_handler_ignore + * \brief Ignore errors. + * + * This error handler frees the temporarily allocated memory and returns + * with the error code. + */ + +extern igraph_error_handler_t igraph_error_handler_ignore; + +/** + * \var igraph_error_handler_printignore + * \brief Print and ignore errors. + * + * Frees temporarily allocated memory, prints an error message to the + * standard error and returns with the error code. + */ + +extern igraph_error_handler_t igraph_error_handler_printignore; + +/** + * \function igraph_set_error_handler + * \brief Set a new error handler. + * + * Installs a new error handler. If called with 0, it installs the + * default error handler (which is currently + * \ref igraph_error_handler_abort). + * \param new_handler The error handler function to install. + * \return The old error handler function. This should be saved and + * restored if \p new_handler is not needed any + * more. + */ + +DECLDIR igraph_error_handler_t* igraph_set_error_handler(igraph_error_handler_t* new_handler); + +/** + * \typedef igraph_error_type_t + * \brief Error code type. + * These are the possible values returned by \a igraph functions. + * Note that these are interesting only if you defined an error handler + * with \ref igraph_set_error_handler(). Otherwise the program is aborted + * and the function causing the error never returns. + * + * \enumval IGRAPH_SUCCESS The function successfully completed its task. + * \enumval IGRAPH_FAILURE Something went wrong. You'll almost never + * meet this error as normally more specific error codes are used. + * \enumval IGRAPH_ENOMEM There wasn't enough memory to allocate + * on the heap. + * \enumval IGRAPH_PARSEERROR A parse error was found in a file. + * \enumval IGRAPH_EINVAL A parameter's value is invalid. E.g. negative + * number was specified as the number of vertices. + * \enumval IGRAPH_EXISTS A graph/vertex/edge attribute is already + * installed with the given name. + * \enumval IGRAPH_EINVEVECTOR Invalid vector of vertex ids. A vertex id + * is either negative or bigger than the number of vertices minus one. + * \enumval IGRAPH_EINVVID Invalid vertex id, negative or too big. + * \enumval IGRAPH_NONSQUARE A non-square matrix was received while a + * square matrix was expected. + * \enumval IGRAPH_EINVMODE Invalid mode parameter. + * \enumval IGRAPH_EFILE A file operation failed. E.g. a file doesn't exist, + * or the user has no rights to open it. + * \enumval IGRAPH_UNIMPLEMENTED Attempted to call an unimplemented or + * disabled (at compile-time) function. + * \enumval IGRAPH_DIVERGED A numeric algorithm failed to converge. + * \enumval IGRAPH_ARPACK_PROD Matrix-vector product failed. + * \enumval IGRAPH_ARPACK_NPOS N must be positive. + * \enumval IGRAPH_ARPACK_NEVNPOS NEV must be positive. + * \enumval IGRAPH_ARPACK_NCVSMALL NCV must be bigger. + * \enumval IGRAPH_ARPACK_NONPOSI Maximum number of iterations should be positive. + * \enumval IGRAPH_ARPACK_WHICHINV Invalid WHICH parameter. + * \enumval IGRAPH_ARPACK_BMATINV Invalid BMAT parameter. + * \enumval IGRAPH_ARPACK_WORKLSMALL WORKL is too small. + * \enumval IGRAPH_ARPACK_TRIDERR LAPACK error in tridiagonal eigenvalue calculation. + * \enumval IGRAPH_ARPACK_ZEROSTART Starting vector is zero. + * \enumval IGRAPH_ARPACK_MODEINV MODE is invalid. + * \enumval IGRAPH_ARPACK_MODEBMAT MODE and BMAT are not compatible. + * \enumval IGRAPH_ARPACK_ISHIFT ISHIFT must be 0 or 1. + * \enumval IGRAPH_ARPACK_NEVBE NEV and WHICH='BE' are incompatible. + * \enumval IGRAPH_ARPACK_NOFACT Could not build an Arnoldi factorization. + * \enumval IGRAPH_ARPACK_FAILED No eigenvalues to sufficient accuracy. + * \enumval IGRAPH_ARPACK_HOWMNY HOWMNY is invalid. + * \enumval IGRAPH_ARPACK_HOWMNYS HOWMNY='S' is not implemented. + * \enumval IGRAPH_ARPACK_EVDIFF Different number of converged Ritz values. + * \enumval IGRAPH_ARPACK_SHUR Error from calculation of a real Schur form. + * \enumval IGRAPH_ARPACK_LAPACK LAPACK (dtrevc) error for calculating eigenvectors. + * \enumval IGRAPH_ARPACK_UNKNOWN Unknown ARPACK error. + * \enumval IGRAPH_ENEGLOOP Negative loop detected while calculating shortest paths. + * \enumval IGRAPH_EINTERNAL Internal error, likely a bug in igraph. + * \enumval IGRAPH_EDIVZERO Big integer division by zero. + * \enumval IGARPH_GLP_EBOUND GLPK error (GLP_EBOUND). + * \enumval IGARPH_GLP_EROOT GLPK error (GLP_EROOT). + * \enumval IGARPH_GLP_ENOPFS GLPK error (GLP_ENOPFS). + * \enumval IGARPH_GLP_ENODFS GLPK error (GLP_ENODFS). + * \enumval IGARPH_GLP_EFAIL GLPK error (GLP_EFAIL). + * \enumval IGARPH_GLP_EMIPGAP GLPK error (GLP_EMIPGAP). + * \enumval IGARPH_GLP_ETMLIM GLPK error (GLP_ETMLIM). + * \enumval IGARPH_GLP_ESTOP GLPK error (GLP_ESTOP). + * \enumval IGRAPH_EATTRIBUTES Attribute handler error. The user is not + * expected to find this; it is signalled if some igraph function is + * not using the attribute handler interface properly. + * \enumval IGRAPH_EATTRCOMBINE Unimplemented attribute combination + * method for the given attribute type. + * \enumval IGRAPH_ELAPACK A LAPACK call resulted an error. + * \enumval IGRAPH_EDRL Internal error in the DrL layout generator. + * \enumval IGRAPH_EOVERFLOW Integer or double overflow. + * \enumval IGRAPH_EGLP Internal GLPK error. + * \enumval IGRAPH_CPUTIME CPU time exceeded. + * \enumval IGRAPH_EUNDERFLOW Integer or double underflow. + * \enumval IGRAPH_ERWSTUCK Random walk got stuck. + */ + +typedef enum { + IGRAPH_SUCCESS = 0, + IGRAPH_FAILURE = 1, + IGRAPH_ENOMEM = 2, + IGRAPH_PARSEERROR = 3, + IGRAPH_EINVAL = 4, + IGRAPH_EXISTS = 5, + IGRAPH_EINVEVECTOR = 6, + IGRAPH_EINVVID = 7, + IGRAPH_NONSQUARE = 8, + IGRAPH_EINVMODE = 9, + IGRAPH_EFILE = 10, + IGRAPH_UNIMPLEMENTED = 12, + IGRAPH_INTERRUPTED = 13, + IGRAPH_DIVERGED = 14, + IGRAPH_ARPACK_PROD = 15, + IGRAPH_ARPACK_NPOS = 16, + IGRAPH_ARPACK_NEVNPOS = 17, + IGRAPH_ARPACK_NCVSMALL = 18, + IGRAPH_ARPACK_NONPOSI = 19, + IGRAPH_ARPACK_WHICHINV = 20, + IGRAPH_ARPACK_BMATINV = 21, + IGRAPH_ARPACK_WORKLSMALL = 22, + IGRAPH_ARPACK_TRIDERR = 23, + IGRAPH_ARPACK_ZEROSTART = 24, + IGRAPH_ARPACK_MODEINV = 25, + IGRAPH_ARPACK_MODEBMAT = 26, + IGRAPH_ARPACK_ISHIFT = 27, + IGRAPH_ARPACK_NEVBE = 28, + IGRAPH_ARPACK_NOFACT = 29, + IGRAPH_ARPACK_FAILED = 30, + IGRAPH_ARPACK_HOWMNY = 31, + IGRAPH_ARPACK_HOWMNYS = 32, + IGRAPH_ARPACK_EVDIFF = 33, + IGRAPH_ARPACK_SHUR = 34, + IGRAPH_ARPACK_LAPACK = 35, + IGRAPH_ARPACK_UNKNOWN = 36, + IGRAPH_ENEGLOOP = 37, + IGRAPH_EINTERNAL = 38, + IGRAPH_ARPACK_MAXIT = 39, + IGRAPH_ARPACK_NOSHIFT = 40, + IGRAPH_ARPACK_REORDER = 41, + IGRAPH_EDIVZERO = 42, + IGRAPH_GLP_EBOUND = 43, + IGRAPH_GLP_EROOT = 44, + IGRAPH_GLP_ENOPFS = 45, + IGRAPH_GLP_ENODFS = 46, + IGRAPH_GLP_EFAIL = 47, + IGRAPH_GLP_EMIPGAP = 48, + IGRAPH_GLP_ETMLIM = 49, + IGRAPH_GLP_ESTOP = 50, + IGRAPH_EATTRIBUTES = 51, + IGRAPH_EATTRCOMBINE = 52, + IGRAPH_ELAPACK = 53, + IGRAPH_EDRL = 54, + IGRAPH_EOVERFLOW = 55, + IGRAPH_EGLP = 56, + IGRAPH_CPUTIME = 57, + IGRAPH_EUNDERFLOW = 58, + IGRAPH_ERWSTUCK = 59, + IGRAPH_STOP = 60, /* undocumented, used internally; signals a request to stop in functions like igraph_i_maximal_cliques_bk */ +} igraph_error_type_t; +/* Each enum value above must have a corresponding error string in + * igraph_i_error_strings[] in igraph_error.c */ + +/** + * \define IGRAPH_ERROR + * \brief Trigger an error. + * + * \a igraph functions usually use this macro when they notice an error. + * It calls + * \ref igraph_error() with the proper parameters and if that returns + * the macro returns the "calling" function as well, with the error + * code. If for some (suspicious) reason you want to call the error + * handler without returning from the current function, call + * \ref igraph_error() directly. + * \param reason Textual description of the error. This should be + * something more descriptive than the text associated with the error + * code. E.g. if the error code is \c IGRAPH_EINVAL, + * its associated text (see \ref igraph_strerror()) is "Invalid + * value" and this string should explain which parameter was invalid + * and maybe why. + * \param igraph_errno The \a igraph error code. + */ + +#define IGRAPH_ERROR(reason,igraph_errno) \ + do { \ + igraph_error (reason, __FILE__, __LINE__, igraph_errno) ; \ + return igraph_errno ; \ + } while (0) + +/** + * \function igraph_error + * \brief Trigger an error. + * + * \a igraph functions usually call this function (most often via the + * \ref IGRAPH_ERROR macro) if they notice an error. + * It calls the currently installed error handler function with the + * supplied arguments. + * + * \param reason Textual description of the error. + * \param file The source file in which the error was noticed. + * \param line The number of line in the source file which triggered the + * error. + * \param igraph_errno The \a igraph error code. + * \return the error code (if it returns) + * + * \sa igraph_errorf(). + */ + +DECLDIR int igraph_error(const char *reason, const char *file, int line, + int igraph_errno); + +/** + * \function igraph_errorf + * \brief Trigger an error, printf-like version. + * + * \param reason Textual description of the error, interpreted as + * a \c printf format string. + * \param file The source file in which the error was noticed. + * \param line The line in the source file which triggered the error. + * \param igraph_errno The \a igraph error code. + * \param ... Additional parameters, the values to substitute into the + * format string. + * + * \sa igraph_error(). + */ + +DECLDIR int igraph_errorf(const char *reason, const char *file, int line, + int igraph_errno, ...); + +DECLDIR int igraph_errorvf(const char *reason, const char *file, int line, + int igraph_errno, va_list ap); + +/** + * \function igraph_strerror + * \brief Textual description of an error. + * + * This is a simple utility function, it gives a short general textual + * description for an \a igraph error code. + * + * \param igraph_errno The \a igraph error code. + * \return pointer to the textual description of the error code. + */ + +DECLDIR const char* igraph_strerror(const int igraph_errno); + +#define IGRAPH_ERROR_SELECT_2(a,b) ((a) != IGRAPH_SUCCESS ? (a) : ((b) != IGRAPH_SUCCESS ? (b) : IGRAPH_SUCCESS)) +#define IGRAPH_ERROR_SELECT_3(a,b,c) ((a) != IGRAPH_SUCCESS ? (a) : IGRAPH_ERROR_SELECT_2(b,c)) +#define IGRAPH_ERROR_SELECT_4(a,b,c,d) ((a) != IGRAPH_SUCCESS ? (a) : IGRAPH_ERROR_SELECT_3(b,c,d)) +#define IGRAPH_ERROR_SELECT_5(a,b,c,d,e) ((a) != IGRAPH_SUCCESS ? (a) : IGRAPH_ERROR_SELECT_4(b,c,d,e)) + +/* Now comes the more convenient error handling macro arsenal. + * Ideas taken from exception.{h,c} by Laurent Deniau see + * http://cern.ch/Laurent.Deniau/html/oopc/oopc.html#Exceptions for more + * information. We don't use the exception handling code though. */ + +struct igraph_i_protectedPtr { + int all; + void *ptr; + void (*func)(void*); +}; + +typedef void igraph_finally_func_t (void*); + +DECLDIR void IGRAPH_FINALLY_REAL(void (*func)(void*), void* ptr); + +/** + * \function IGRAPH_FINALLY_CLEAN + * \brief Signal clean deallocation of objects. + * + * Removes the specified number of objects from the stack of + * temporarily allocated objects. Most often this is called just + * before returning from a function. + * \param num The number of objects to remove from the bookkeeping + * stack. + */ + +DECLDIR void IGRAPH_FINALLY_CLEAN(int num); + +/** + * \function IGRAPH_FINALLY_FREE + * \brief Deallocate all registered objects. + * + * Calls the destroy function for all objects in the stack of + * temporarily allocated objects. This is usually called only from an + * error handler. It is \em not appropriate to use it + * instead of destroying each unneeded object of a function, as it + * destroys the temporary objects of the caller function (and so on) + * as well. + */ + +DECLDIR void IGRAPH_FINALLY_FREE(void); + +/** + * \function IGRAPH_FINALLY_STACK_SIZE + * \brief Returns the number of registered objects. + * + * Returns the number of objects in the stack of temporarily allocated + * objects. This function is handy if you write an own igraph routine and + * you want to make sure it handles errors properly. A properly written + * igraph routine should not leave pointers to temporarily allocated objects + * in the finally stack, because otherwise an \ref IGRAPH_FINALLY_FREE call + * in another igraph function would result in freeing these objects as well + * (and this is really hard to debug, since the error will be not in that + * function that shows erroneous behaviour). Therefore, it is advised to + * write your own test cases and examine \ref IGRAPH_FINALLY_STACK_SIZE + * before and after your test cases - the numbers should be equal. + */ +DECLDIR int IGRAPH_FINALLY_STACK_SIZE(void); + +/** + * \define IGRAPH_FINALLY_STACK_EMPTY + * \brief Returns true if there are no registered objects, false otherwise. + * + * This is just a shorthand notation for checking that + * \ref IGRAPH_FINALLY_STACK_SIZE is zero. + */ +#define IGRAPH_FINALLY_STACK_EMPTY (IGRAPH_FINALLY_STACK_SIZE() == 0) + +/** + * \define IGRAPH_FINALLY + * \brief Register an object for deallocation. + * \param func The address of the function which is normally called to + * destroy the object. + * \param ptr Pointer to the object itself. + * + * This macro places the address of an object, together with the + * address of its destructor in a stack. This stack is used if an + * error happens to deallocate temporarily allocated objects to + * prevent memory leaks. + */ + +#define IGRAPH_FINALLY(func,ptr) \ + IGRAPH_FINALLY_REAL((igraph_finally_func_t*)(func), (ptr)) + +#if !defined(GCC_VERSION_MAJOR) && defined(__GNUC__) + #define GCC_VERSION_MAJOR __GNUC__ +#endif + +#if defined(GCC_VERSION_MAJOR) && (GCC_VERSION_MAJOR >= 3) + #define IGRAPH_UNLIKELY(a) __builtin_expect((a), 0) + #define IGRAPH_LIKELY(a) __builtin_expect((a), 1) +#else + #define IGRAPH_UNLIKELY(a) a + #define IGRAPH_LIKELY(a) a +#endif + +/** + * \define IGRAPH_CHECK + * \brief Check the return value of a function call. + * + * \param a An expression, usually a function call. + * + * Executes the expression and checks its value. If this is not + * \c IGRAPH_SUCCESS, it calls \ref IGRAPH_ERROR with + * the value as the error code. Here is an example usage: + * \verbatim IGRAPH_CHECK(vector_push_back(&v, 100)); \endverbatim + * + * There is only one reason to use this macro when writing + * \a igraph functions. If the user installs an error handler which + * returns to the auxiliary calling code (like \ref + * igraph_error_handler_ignore and \ref + * igraph_error_handler_printignore), and the \a igraph function + * signalling the error is called from another \a igraph function + * then we need to make sure that the error is propagated back to + * the auxiliary (i.e. non-igraph) calling function. This is achieved + * by using IGRAPH_CHECK on every \a igraph + * call which can return an error code. + */ + +#define IGRAPH_CHECK(a) do { \ + int igraph_i_ret=(a); \ + if (IGRAPH_UNLIKELY(igraph_i_ret != 0)) {\ + IGRAPH_ERROR("", igraph_i_ret); \ + } } while (0) + + +/** + * \section about_igraph_warnings Warning messages + * + * + * Igraph also supports warning messages in addition to error + * messages. Warning messages typically do not terminate the + * program, but they are usually crucial to the user. + * + * + * + * Igraph warning are handled similarly to errors. There is a + * separate warning handler function that is called whenever + * an igraph function triggers a warning. This handler can be + * set by the \ref igraph_set_warning_handler() function. There are + * two predefined simple warning handlers, + * \ref igraph_warning_handler_ignore() and + * \ref igraph_warning_handler_print(), the latter being the default. + * + * + * + * To trigger a warning, igraph functions typically use the + * \ref IGRAPH_WARNING() macro, the \ref igraph_warning() function, + * or if more flexibility is needed, \ref igraph_warningf(). + * + */ + +/** + * \typedef igraph_warning_handler_t + * Type of igraph warning handler functions + * + * Currently it is defined to have the same type as + * \ref igraph_error_handler_t, although the last (error code) + * argument is not used. + */ + +typedef igraph_error_handler_t igraph_warning_handler_t; + +/** + * \function igraph_set_warning_handler + * Install a warning handler + * + * Install the supplied warning handler function. + * \param new_handler The new warning handler function to install. + * Supply a null pointer here to uninstall the current + * warning handler, without installing a new one. + * \return The current warning handler function. + */ + +DECLDIR igraph_warning_handler_t* igraph_set_warning_handler(igraph_warning_handler_t* new_handler); + +extern igraph_warning_handler_t igraph_warning_handler_ignore; +extern igraph_warning_handler_t igraph_warning_handler_print; + +/** + * \function igraph_warning + * Trigger a warning + * + * Call this function if you want to trigger a warning from within + * a function that uses igraph. + * \param reason Textual description of the warning. + * \param file The source file in which the warning was noticed. + * \param line The number of line in the source file which triggered the + * warning. + * \param igraph_errno Warnings could have potentially error codes as well, + * but this is currently not used in igraph. + * \return The supplied error code. + */ + +DECLDIR int igraph_warning(const char *reason, const char *file, int line, + int igraph_errno); + +/** + * \function igraph_warningf + * Trigger a warning, more flexible printf-like syntax + * + * This function is similar to \ref igraph_warning(), but + * uses a printf-like syntax. It substitutes the additional arguments + * into the \p reason template string and calls \ref igraph_warning(). + * \param reason Textual description of the warning, a template string + * with the same syntax as the standard printf C library function. + * \param file The source file in which the warning was noticed. + * \param line The number of line in the source file which triggered the + * warning. + * \param igraph_errno Warnings could have potentially error codes as well, + * but this is currently not used in igraph. + * \param ... The additional arguments to be substituted into the + * template string. + * \return The supplied error code. + */ + +DECLDIR int igraph_warningf(const char *reason, const char *file, int line, + int igraph_errno, ...); + +/** + * \define IGRAPH_WARNING + * Trigger a warning. + * + * This is the usual way of triggering a warning from an igraph + * function. It calls \ref igraph_warning(). + * \param reason The warning message. + */ + +#define IGRAPH_WARNING(reason) \ + do { \ + igraph_warning(reason, __FILE__, __LINE__, -1); \ + } while (0) + +__END_DECLS + +#endif diff --git a/include/igraph_flow.h b/include/igraph_flow.h new file mode 100644 index 0000000..cc98718 --- /dev/null +++ b/include/igraph_flow.h @@ -0,0 +1,169 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_FLOW_H +#define IGRAPH_FLOW_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_types.h" +#include "igraph_datatype.h" +#include "igraph_vector_ptr.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* MAximum flows, minimum cuts & such */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_maxflow_stats_t + * A simple data type to return some statistics from the + * push-relabel maximum flow solver. + * + * \param nopush The number of push operations performed. + * \param norelabel The number of relabel operarions performed. + * \param nogap The number of times the gap heuristics was used. + * \param nogapnodes The total number of vertices that were + * omitted form further calculations because of the gap + * heuristics. + * \param nobfs The number of times the reverse BFS was run to + * assign good values to the height function. This includes + * an initial run before the whole algorithm, so it is always + * at least one. + */ + +typedef struct { + int nopush, norelabel, nogap, nogapnodes, nobfs; +} igraph_maxflow_stats_t; + +DECLDIR int igraph_maxflow(const igraph_t *graph, igraph_real_t *value, + igraph_vector_t *flow, igraph_vector_t *cut, + igraph_vector_t *partition, igraph_vector_t *partition2, + igraph_integer_t source, igraph_integer_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats); +DECLDIR int igraph_maxflow_value(const igraph_t *graph, igraph_real_t *value, + igraph_integer_t source, igraph_integer_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats); + +DECLDIR int igraph_st_mincut(const igraph_t *graph, igraph_real_t *value, + igraph_vector_t *cut, igraph_vector_t *partition, + igraph_vector_t *partition2, + igraph_integer_t source, igraph_integer_t target, + const igraph_vector_t *capacity); +DECLDIR int igraph_st_mincut_value(const igraph_t *graph, igraph_real_t *res, + igraph_integer_t source, igraph_integer_t target, + const igraph_vector_t *capacity); + +DECLDIR int igraph_mincut_value(const igraph_t *graph, igraph_real_t *res, + const igraph_vector_t *capacity); +DECLDIR int igraph_mincut(const igraph_t *graph, + igraph_real_t *value, + igraph_vector_t *partition, + igraph_vector_t *partition2, + igraph_vector_t *cut, + const igraph_vector_t *capacity); + +DECLDIR int igraph_st_vertex_connectivity(const igraph_t *graph, + igraph_integer_t *res, + igraph_integer_t source, + igraph_integer_t target, + igraph_vconn_nei_t neighbors); +DECLDIR int igraph_vertex_connectivity(const igraph_t *graph, igraph_integer_t *res, + igraph_bool_t checks); + +DECLDIR int igraph_st_edge_connectivity(const igraph_t *graph, igraph_integer_t *res, + igraph_integer_t source, + igraph_integer_t target); +DECLDIR int igraph_edge_connectivity(const igraph_t *graph, igraph_integer_t *res, + igraph_bool_t checks); + +DECLDIR int igraph_edge_disjoint_paths(const igraph_t *graph, igraph_integer_t *res, + igraph_integer_t source, + igraph_integer_t target); +DECLDIR int igraph_vertex_disjoint_paths(const igraph_t *graph, igraph_integer_t *res, + igraph_integer_t source, + igraph_integer_t target); + +DECLDIR int igraph_adhesion(const igraph_t *graph, igraph_integer_t *res, + igraph_bool_t checks); +DECLDIR int igraph_cohesion(const igraph_t *graph, igraph_integer_t *res, + igraph_bool_t checks); + +/* s-t cut listing related stuff */ + +DECLDIR int igraph_even_tarjan_reduction(const igraph_t *graph, igraph_t *graphbar, + igraph_vector_t *capacity); + +DECLDIR int igraph_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + igraph_vector_t *residual_capacity, + const igraph_vector_t *flow); +int igraph_i_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + igraph_vector_t *residual_capacity, + const igraph_vector_t *flow, + igraph_vector_t *tmp); + +int igraph_i_reverse_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + const igraph_vector_t *flow, + igraph_vector_t *tmp); +DECLDIR int igraph_reverse_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + const igraph_vector_t *flow); + +DECLDIR int igraph_dominator_tree(const igraph_t *graph, + igraph_integer_t root, + igraph_vector_t *dom, + igraph_t *domtree, + igraph_vector_t *leftout, + igraph_neimode_t mode); + +DECLDIR int igraph_all_st_cuts(const igraph_t *graph, + igraph_vector_ptr_t *cuts, + igraph_vector_ptr_t *partition1s, + igraph_integer_t source, + igraph_integer_t target); + +DECLDIR int igraph_all_st_mincuts(const igraph_t *graph, igraph_real_t *value, + igraph_vector_ptr_t *cuts, + igraph_vector_ptr_t *partition1s, + igraph_integer_t source, + igraph_integer_t target, + const igraph_vector_t *capacity); + +DECLDIR int igraph_gomory_hu_tree(const igraph_t *graph, + igraph_t *tree, + igraph_vector_t *flows, + const igraph_vector_t *capacity); + +__END_DECLS + +#endif diff --git a/include/igraph_foreign.h b/include/igraph_foreign.h new file mode 100644 index 0000000..c81c5e0 --- /dev/null +++ b/include/igraph_foreign.h @@ -0,0 +1,85 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_FOREIGN_H +#define IGRAPH_FOREIGN_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_types.h" +#include "igraph_strvector.h" + +#include + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Read and write foreign formats */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_read_graph_edgelist(igraph_t *graph, FILE *instream, + igraph_integer_t n, igraph_bool_t directed); +DECLDIR int igraph_read_graph_ncol(igraph_t *graph, FILE *instream, + igraph_strvector_t *predefnames, igraph_bool_t names, + igraph_add_weights_t weights, igraph_bool_t directed); +DECLDIR int igraph_read_graph_lgl(igraph_t *graph, FILE *instream, + igraph_bool_t names, igraph_add_weights_t weights, + igraph_bool_t directed); +DECLDIR int igraph_read_graph_pajek(igraph_t *graph, FILE *instream); +DECLDIR int igraph_read_graph_graphml(igraph_t *graph, FILE *instream, + int index); +DECLDIR int igraph_read_graph_dimacs(igraph_t *graph, FILE *instream, + igraph_strvector_t *problem, + igraph_vector_t *label, + igraph_integer_t *source, + igraph_integer_t *target, + igraph_vector_t *capacity, + igraph_bool_t directed); +DECLDIR int igraph_read_graph_graphdb(igraph_t *graph, FILE *instream, + igraph_bool_t directed); +DECLDIR int igraph_read_graph_gml(igraph_t *graph, FILE *instream); +DECLDIR int igraph_read_graph_dl(igraph_t *graph, FILE *instream, + igraph_bool_t directed); + +DECLDIR int igraph_write_graph_edgelist(const igraph_t *graph, FILE *outstream); +DECLDIR int igraph_write_graph_ncol(const igraph_t *graph, FILE *outstream, + const char *names, const char *weights); +DECLDIR int igraph_write_graph_lgl(const igraph_t *graph, FILE *outstream, + const char *names, const char *weights, + igraph_bool_t isolates); +DECLDIR int igraph_write_graph_graphml(const igraph_t *graph, FILE *outstream, + igraph_bool_t prefixattr); +DECLDIR int igraph_write_graph_pajek(const igraph_t *graph, FILE *outstream); +DECLDIR int igraph_write_graph_dimacs(const igraph_t *graph, FILE *outstream, + long int source, long int target, + const igraph_vector_t *capacity); +DECLDIR int igraph_write_graph_gml(const igraph_t *graph, FILE *outstream, + const igraph_vector_t *id, const char *creator); +DECLDIR int igraph_write_graph_dot(const igraph_t *graph, FILE *outstream); +DECLDIR int igraph_write_graph_leda(const igraph_t *graph, FILE *outstream, + const char* vertex_attr_name, const char* edge_attr_name); + +__END_DECLS + +#endif diff --git a/include/igraph_games.h b/include/igraph_games.h new file mode 100644 index 0000000..a2cfa7d --- /dev/null +++ b/include/igraph_games.h @@ -0,0 +1,227 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_GAMES_H +#define IGRAPH_GAMES_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_types.h" +#include "igraph_matrix.h" +#include "igraph_vector.h" +#include "igraph_datatype.h" +#include "igraph_vector_ptr.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Constructors, games (=stochastic) */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_barabasi_game(igraph_t *graph, igraph_integer_t n, + igraph_real_t power, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + igraph_barabasi_algorithm_t algo, + const igraph_t *start_from); +DECLDIR int igraph_nonlinear_barabasi_game(igraph_t *graph, igraph_integer_t n, + igraph_real_t power, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t zeroappeal, + igraph_bool_t directed); +DECLDIR int igraph_erdos_renyi_game(igraph_t *graph, igraph_erdos_renyi_t type, + igraph_integer_t n, igraph_real_t p, + igraph_bool_t directed, igraph_bool_t loops); +DECLDIR int igraph_erdos_renyi_game_gnp(igraph_t *graph, igraph_integer_t n, igraph_real_t p, + igraph_bool_t directed, igraph_bool_t loops); +DECLDIR int igraph_erdos_renyi_game_gnm(igraph_t *graph, igraph_integer_t n, igraph_real_t m, + igraph_bool_t directed, igraph_bool_t loops); +DECLDIR int igraph_degree_sequence_game(igraph_t *graph, const igraph_vector_t *out_deg, + const igraph_vector_t *in_deg, + igraph_degseq_t method); +DECLDIR int igraph_growing_random_game(igraph_t *graph, igraph_integer_t n, + igraph_integer_t m, igraph_bool_t directed, igraph_bool_t citation); +DECLDIR int igraph_barabasi_aging_game(igraph_t *graph, + igraph_integer_t nodes, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t pa_exp, + igraph_real_t aging_exp, + igraph_integer_t aging_bin, + igraph_real_t zero_deg_appeal, + igraph_real_t zero_age_appeal, + igraph_real_t deg_coef, + igraph_real_t age_coef, + igraph_bool_t directed); +DECLDIR int igraph_recent_degree_game(igraph_t *graph, igraph_integer_t n, + igraph_real_t power, + igraph_integer_t window, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t zero_appeal, + igraph_bool_t directed); +DECLDIR int igraph_recent_degree_aging_game(igraph_t *graph, + igraph_integer_t nodes, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t pa_exp, + igraph_real_t aging_exp, + igraph_integer_t aging_bin, + igraph_integer_t window, + igraph_real_t zero_appeal, + igraph_bool_t directed); +DECLDIR int igraph_callaway_traits_game (igraph_t *graph, igraph_integer_t nodes, + igraph_integer_t types, igraph_integer_t edges_per_step, + igraph_vector_t *type_dist, + igraph_matrix_t *pref_matrix, + igraph_bool_t directed); +DECLDIR int igraph_establishment_game(igraph_t *graph, igraph_integer_t nodes, + igraph_integer_t types, igraph_integer_t k, + igraph_vector_t *type_dist, + igraph_matrix_t *pref_matrix, + igraph_bool_t directed); +DECLDIR int igraph_grg_game(igraph_t *graph, igraph_integer_t nodes, + igraph_real_t radius, igraph_bool_t torus, + igraph_vector_t *x, igraph_vector_t *y); +DECLDIR int igraph_preference_game(igraph_t *graph, igraph_integer_t nodes, + igraph_integer_t types, + const igraph_vector_t *type_dist, + igraph_bool_t fixed_sizes, + const igraph_matrix_t *pref_matrix, + igraph_vector_t *node_type_vec, + igraph_bool_t directed, igraph_bool_t loops); +DECLDIR int igraph_asymmetric_preference_game(igraph_t *graph, igraph_integer_t nodes, + igraph_integer_t types, + igraph_matrix_t *type_dist_matrix, + igraph_matrix_t *pref_matrix, + igraph_vector_t *node_type_in_vec, + igraph_vector_t *node_type_out_vec, + igraph_bool_t loops); + +DECLDIR int igraph_rewire_edges(igraph_t *graph, igraph_real_t prob, + igraph_bool_t loops, igraph_bool_t multiple); +DECLDIR int igraph_rewire_directed_edges(igraph_t *graph, igraph_real_t prob, + igraph_bool_t loops, igraph_neimode_t mode); + +DECLDIR int igraph_watts_strogatz_game(igraph_t *graph, igraph_integer_t dim, + igraph_integer_t size, igraph_integer_t nei, + igraph_real_t p, igraph_bool_t loops, + igraph_bool_t multiple); + +DECLDIR int igraph_lastcit_game(igraph_t *graph, + igraph_integer_t nodes, igraph_integer_t edges_per_node, + igraph_integer_t agebins, + const igraph_vector_t *preference, igraph_bool_t directed); + +DECLDIR int igraph_cited_type_game(igraph_t *graph, igraph_integer_t nodes, + const igraph_vector_t *types, + const igraph_vector_t *pref, + igraph_integer_t edges_per_step, + igraph_bool_t directed); + +DECLDIR int igraph_citing_cited_type_game(igraph_t *graph, igraph_integer_t nodes, + const igraph_vector_t *types, + const igraph_matrix_t *pref, + igraph_integer_t edges_per_step, + igraph_bool_t directed); + +DECLDIR int igraph_forest_fire_game(igraph_t *graph, igraph_integer_t nodes, + igraph_real_t fw_prob, igraph_real_t bw_factor, + igraph_integer_t ambs, igraph_bool_t directed); + + +DECLDIR int igraph_simple_interconnected_islands_game( + igraph_t *graph, + igraph_integer_t islands_n, + igraph_integer_t islands_size, + igraph_real_t islands_pin, + igraph_integer_t n_inter); + +DECLDIR int igraph_static_fitness_game(igraph_t *graph, igraph_integer_t no_of_edges, + igraph_vector_t* fitness_out, igraph_vector_t* fitness_in, + igraph_bool_t loops, igraph_bool_t multiple); + +DECLDIR int igraph_static_power_law_game(igraph_t *graph, + igraph_integer_t no_of_nodes, igraph_integer_t no_of_edges, + igraph_real_t exponent_out, igraph_real_t exponent_in, + igraph_bool_t loops, igraph_bool_t multiple, + igraph_bool_t finite_size_correction); + +DECLDIR int igraph_k_regular_game(igraph_t *graph, + igraph_integer_t no_of_nodes, igraph_integer_t k, + igraph_bool_t directed, igraph_bool_t multiple); + +DECLDIR int igraph_sbm_game(igraph_t *graph, igraph_integer_t n, + const igraph_matrix_t *pref_matrix, + const igraph_vector_int_t *block_sizes, + igraph_bool_t directed, igraph_bool_t loops); + +DECLDIR int igraph_hsbm_game(igraph_t *graph, igraph_integer_t n, + igraph_integer_t m, const igraph_vector_t *rho, + const igraph_matrix_t *C, igraph_real_t p); + +DECLDIR int igraph_hsbm_list_game(igraph_t *graph, igraph_integer_t n, + const igraph_vector_int_t *mlist, + const igraph_vector_ptr_t *rholist, + const igraph_vector_ptr_t *Clist, + igraph_real_t p); + +DECLDIR int igraph_correlated_game(const igraph_t *old_graph, igraph_t *new_graph, + igraph_real_t corr, igraph_real_t p, + const igraph_vector_t *permutation); + +DECLDIR int igraph_correlated_pair_game(igraph_t *graph1, igraph_t *graph2, + int n, igraph_real_t corr, igraph_real_t p, + igraph_bool_t directed, + const igraph_vector_t *permutation); + +DECLDIR int igraph_tree_game(igraph_t *graph, igraph_integer_t n, igraph_bool_t directed, + igraph_random_tree_t method); + +DECLDIR int igraph_dot_product_game(igraph_t *graph, const igraph_matrix_t *vecs, + igraph_bool_t directed); + +DECLDIR int igraph_sample_sphere_surface(igraph_integer_t dim, igraph_integer_t n, + igraph_real_t radius, + igraph_bool_t positive, + igraph_matrix_t *res); + +DECLDIR int igraph_sample_sphere_volume(igraph_integer_t dim, igraph_integer_t n, + igraph_real_t radius, + igraph_bool_t positive, + igraph_matrix_t *res); + +DECLDIR int igraph_sample_dirichlet(igraph_integer_t n, const igraph_vector_t *alpha, + igraph_matrix_t *res); + +__END_DECLS + +#endif diff --git a/include/igraph_graphlets.h b/include/igraph_graphlets.h new file mode 100644 index 0000000..68fe67d --- /dev/null +++ b/include/igraph_graphlets.h @@ -0,0 +1,52 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_GRAPHLETS_H +#define IGRAPH_GRAPHLETS_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_vector_ptr.h" +#include "igraph_interface.h" + +__BEGIN_DECLS + +DECLDIR int igraph_graphlets_candidate_basis(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_ptr_t *cliques, + igraph_vector_t *thresholds); + +DECLDIR int igraph_graphlets_project(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_vector_ptr_t *cliques, + igraph_vector_t *Mu, igraph_bool_t startMu, + int niter); + +DECLDIR int igraph_graphlets(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_ptr_t *cliques, + igraph_vector_t *Mu, int niter); + +__END_DECLS + +#endif diff --git a/include/igraph_heap.h b/include/igraph_heap.h new file mode 100644 index 0000000..3729226 --- /dev/null +++ b/include/igraph_heap.h @@ -0,0 +1,83 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_HEAP_H +#define IGRAPH_HEAP_H + +#include "igraph_decls.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Heap */ +/* -------------------------------------------------- */ + +/** + * Heap data type. + * \ingroup internal + */ + +#define BASE_IGRAPH_REAL +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_IGRAPH_REAL + +#define BASE_LONG +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_LONG + +#define BASE_CHAR +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "igraph_heap_pmt.h" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_CHAR + +#define IGRAPH_HEAP_NULL { 0,0,0 } + +__END_DECLS + +#endif diff --git a/include/igraph_heap_pmt.h b/include/igraph_heap_pmt.h new file mode 100644 index 0000000..f4b2856 --- /dev/null +++ b/include/igraph_heap_pmt.h @@ -0,0 +1,45 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +typedef struct TYPE(igraph_heap) { + BASE* stor_begin; + BASE* stor_end; + BASE* end; + int destroy; +} TYPE(igraph_heap); + +DECLDIR int FUNCTION(igraph_heap, init)(TYPE(igraph_heap)* h, long int size); +DECLDIR int FUNCTION(igraph_heap, init_array)(TYPE(igraph_heap) *t, BASE* data, long int len); +DECLDIR void FUNCTION(igraph_heap, destroy)(TYPE(igraph_heap)* h); +DECLDIR igraph_bool_t FUNCTION(igraph_heap, empty)(TYPE(igraph_heap)* h); +DECLDIR int FUNCTION(igraph_heap, push)(TYPE(igraph_heap)* h, BASE elem); +DECLDIR BASE FUNCTION(igraph_heap, top)(TYPE(igraph_heap)* h); +DECLDIR BASE FUNCTION(igraph_heap, delete_top)(TYPE(igraph_heap)* h); +DECLDIR long int FUNCTION(igraph_heap, size)(TYPE(igraph_heap)* h); +DECLDIR int FUNCTION(igraph_heap, reserve)(TYPE(igraph_heap)* h, long int size); + +void FUNCTION(igraph_heap, i_build)(BASE* arr, long int size, long int head); +void FUNCTION(igraph_heap, i_shift_up)(BASE* arr, long int size, long int elem); +void FUNCTION(igraph_heap, i_sink)(BASE* arr, long int size, long int head); +void FUNCTION(igraph_heap, i_switch)(BASE* arr, long int e1, long int e2); + diff --git a/include/igraph_hrg.h b/include/igraph_hrg.h new file mode 100644 index 0000000..357f054 --- /dev/null +++ b/include/igraph_hrg.h @@ -0,0 +1,114 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_HRG_H +#define IGRAPH_HRG_H + +#include "igraph_decls.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" +#include "igraph_datatype.h" + +__BEGIN_DECLS + +/** + * \struct igraph_hrg_t + * Data structure to store a hierarchical random graph + * + * A hierarchical random graph (HRG) can be given as a binary tree, + * where the internal vertices are labeled with real numbers. + * + * Note that you don't necessarily have to know this + * internal representation for using the HRG functions, just pass the + * HRG objects created by one igraph function, to another igraph + * function. + * + * + * It has the following members: + * \member left Vector that contains the left children of the internal + * tree vertices. The first vertex is always the root vertex, so + * the first element of the vector is the left child of the root + * vertex. Internal vertices are denoted with negative numbers, + * starting from -1 and going down, i.e. the root vertex is + * -1. Leaf vertices are denoted by non-negative number, starting + * from zero and up. + * \member right Vector that contains the right children of the + * vertices, with the same encoding as the \c left vector. + * \member prob The connection probabilities attached to the internal + * vertices, the first number belongs to the root vertex + * (i.e. internal vertex -1), the second to internal vertex -2, + * etc. + * \member edges The number of edges in the subtree below the given + * internal vertex. + * \member vertices The number of vertices in the subtree below the + * given internal vertex, including itself. + */ + +typedef struct igraph_hrg_t { + igraph_vector_t left, right, prob, edges, vertices; +} igraph_hrg_t; + +DECLDIR int igraph_hrg_init(igraph_hrg_t *hrg, int n); +DECLDIR void igraph_hrg_destroy(igraph_hrg_t *hrg); +DECLDIR int igraph_hrg_size(const igraph_hrg_t *hrg); +DECLDIR int igraph_hrg_resize(igraph_hrg_t *hrg, int newsize); + +DECLDIR int igraph_hrg_fit(const igraph_t *graph, + igraph_hrg_t *hrg, + igraph_bool_t start, + int steps); + +DECLDIR int igraph_hrg_sample(const igraph_t *graph, + igraph_t *sample, + igraph_vector_ptr_t *samples, + igraph_hrg_t *hrg, + igraph_bool_t start); + +DECLDIR int igraph_hrg_game(igraph_t *graph, + const igraph_hrg_t *hrg); + +DECLDIR int igraph_hrg_dendrogram(igraph_t *graph, + const igraph_hrg_t *hrg); + +DECLDIR int igraph_hrg_consensus(const igraph_t *graph, + igraph_vector_t *parents, + igraph_vector_t *weights, + igraph_hrg_t *hrg, + igraph_bool_t start, + int num_samples); + +DECLDIR int igraph_hrg_predict(const igraph_t *graph, + igraph_vector_t *edges, + igraph_vector_t *prob, + igraph_hrg_t *hrg, + igraph_bool_t start, + int num_samples, + int num_bins); + +DECLDIR int igraph_hrg_create(igraph_hrg_t *hrg, + const igraph_t *graph, + const igraph_vector_t *prob); + +__END_DECLS + +#endif /* IGRAPH_HRG_H */ diff --git a/include/igraph_interface.h b/include/igraph_interface.h new file mode 100644 index 0000000..8ea31b6 --- /dev/null +++ b/include/igraph_interface.h @@ -0,0 +1,86 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_INTERFACE_H +#define IGRAPH_INTERFACE_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_datatype.h" +#include "igraph_iterators.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Interface */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_empty(igraph_t *graph, igraph_integer_t n, igraph_bool_t directed); +DECLDIR int igraph_empty_attrs(igraph_t *graph, igraph_integer_t n, igraph_bool_t directed, void *attr); +DECLDIR void igraph_destroy(igraph_t *graph); +DECLDIR int igraph_copy(igraph_t *to, const igraph_t *from); +DECLDIR int igraph_add_edges(igraph_t *graph, const igraph_vector_t *edges, + void *attr); +DECLDIR int igraph_add_vertices(igraph_t *graph, igraph_integer_t nv, + void *attr); +DECLDIR int igraph_delete_edges(igraph_t *graph, igraph_es_t edges); +DECLDIR int igraph_delete_vertices(igraph_t *graph, const igraph_vs_t vertices); +DECLDIR int igraph_delete_vertices_idx(igraph_t *graph, const igraph_vs_t vertices, + igraph_vector_t *idx, + igraph_vector_t *invidx); +DECLDIR igraph_integer_t igraph_vcount(const igraph_t *graph); +DECLDIR igraph_integer_t igraph_ecount(const igraph_t *graph); +DECLDIR int igraph_neighbors(const igraph_t *graph, igraph_vector_t *neis, igraph_integer_t vid, + igraph_neimode_t mode); +DECLDIR igraph_bool_t igraph_is_directed(const igraph_t *graph); +DECLDIR int igraph_degree(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_neimode_t mode, + igraph_bool_t loops); +DECLDIR int igraph_edge(const igraph_t *graph, igraph_integer_t eid, + igraph_integer_t *from, igraph_integer_t *to); +DECLDIR int igraph_edges(const igraph_t *graph, igraph_es_t eids, + igraph_vector_t *edges); +DECLDIR int igraph_get_eid(const igraph_t *graph, igraph_integer_t *eid, + igraph_integer_t from, igraph_integer_t to, + igraph_bool_t directed, igraph_bool_t error); +DECLDIR int igraph_get_eids(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *pairs, + const igraph_vector_t *path, + igraph_bool_t directed, igraph_bool_t error); +DECLDIR int igraph_get_eids_multi(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *pairs, + const igraph_vector_t *path, + igraph_bool_t directed, igraph_bool_t error); +DECLDIR int igraph_adjacent(const igraph_t *graph, igraph_vector_t *eids, igraph_integer_t vid, + igraph_neimode_t mode); /* deprecated */ +DECLDIR int igraph_incident(const igraph_t *graph, igraph_vector_t *eids, igraph_integer_t vid, + igraph_neimode_t mode); + +#define IGRAPH_FROM(g,e) ((igraph_integer_t)(VECTOR((g)->from)[(long int)(e)])) +#define IGRAPH_TO(g,e) ((igraph_integer_t)(VECTOR((g)->to) [(long int)(e)])) +#define IGRAPH_OTHER(g,e,v) \ + ((igraph_integer_t)(IGRAPH_TO(g,(e))==(v) ? IGRAPH_FROM((g),(e)) : IGRAPH_TO((g),(e)))) + +__END_DECLS + +#endif diff --git a/include/igraph_interrupt.h b/include/igraph_interrupt.h new file mode 100644 index 0000000..38a3102 --- /dev/null +++ b/include/igraph_interrupt.h @@ -0,0 +1,128 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_INTERRUPT_H +#define IGRAPH_INTERRUPT_H + +#include "igraph_error.h" +#include "igraph_decls.h" + +__BEGIN_DECLS + +/* This file contains the igraph interruption handling. */ + +/** + * \section interrupthandlers Interruption handlers + * + * + * \a igraph is designed to be embeddable into several higher level + * languages (R and Python interfaces are included in the original + * package). Since most higher level languages consider internal \a igraph + * calls as atomic, interruption requests (like Ctrl-C in Python) must + * be handled differently depending on the environment \a igraph embeds + * into. + * + * An \emb interruption handler \eme is a function which is called regularly + * by \a igraph during long calculations. A typical usage of the interruption + * handler is to check whether the user tried to interrupt the calculation + * and return an appropriate value to signal this condition. For example, + * in R, one must call an internal R function regularly to check for + * interruption requests, and the \a igraph interruption handler is the + * perfect place to do that. + * + * If you are using the plain C interface of \a igraph or if you are + * allowed to replace the operating system's interruption handler (like + * SIGINT in Un*x systems), these calls are not of much use to you. + * + * The default interruption handler is empty. + * The \ref igraph_set_interruption_handler() function can be used to set a + * new interruption handler function of type + * \ref igraph_interruption_handler_t, see the + * documentation of this type for details. + * + */ + +/** + * \section writing_interruption_handlers Writing interruption handlers + * + * + * You can write and install interruption handlers simply by defining a + * function of type \ref igraph_interruption_handler_t and calling + * \ref igraph_set_interruption_handler(). This feature is useful for + * interface writers, because usually this is the only way to allow handling + * of Ctrl-C and similar keypresses properly. + * + * + * Your interruption handler will be called regularly during long operations + * (so it is not guaranteed to be called during operations which tend to be + * short, like adding single edges). An interruption handler accepts no + * parameters and must return \c IGRAPH_SUCCESS if the calculation should go on. All + * other return values are considered to be a request for interruption, + * and the caller function would return a special error code, \c IGRAPH_INTERRUPTED. + * It is up to your error handler function to handle this error properly. + * + */ + +/** + * \section writing_functions_interruption_handling Writing \a igraph functions with + * proper interruption handling + * + * + * There is practically a simple rule that should be obeyed when writing + * \a igraph functions. If the calculation is expected to take a long time + * in large graphs (a simple rule of thumb is to assume this for every + * function with a time complexity of at least O(n^2)), call + * \ref IGRAPH_ALLOW_INTERRUPTION in regular intervals like every 10th + * iteration or so. + * + */ + +/** + * \typedef igraph_interruption_handler_t + * + * This is the type of the interruption handler functions. + * + * \param data reserved for possible future use + * \return \c IGRAPH_SUCCESS if the calculation should go on, anything else otherwise. + */ + +typedef int igraph_interruption_handler_t (void* data); + +/** + * \function igraph_allow_interruption + * + * This is the function which is called (usually via the + * \ref IGRAPH_INTERRUPTION macro) if \a igraph is checking for interruption + * requests. + * + * \param data reserved for possible future use, now it is always \c NULL + * \return \c IGRAPH_SUCCESS if the calculation should go on, anything else otherwise. + */ + +DECLDIR int igraph_allow_interruption(void* data); + +DECLDIR igraph_interruption_handler_t * igraph_set_interruption_handler (igraph_interruption_handler_t * new_handler); + +__END_DECLS + +#endif diff --git a/include/igraph_iterators.h b/include/igraph_iterators.h new file mode 100644 index 0000000..d04df89 --- /dev/null +++ b/include/igraph_iterators.h @@ -0,0 +1,401 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_ITERATORS_H +#define IGRAPH_ITERATORS_H + +#include "igraph_decls.h" +#include "igraph_constants.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Vertex selectors */ +/* -------------------------------------------------- */ + +#define IGRAPH_VS_ALL 0 +#define IGRAPH_VS_ADJ 1 +#define IGRAPH_VS_NONE 2 +#define IGRAPH_VS_1 3 +#define IGRAPH_VS_VECTORPTR 4 +#define IGRAPH_VS_VECTOR 5 +#define IGRAPH_VS_SEQ 6 +#define IGRAPH_VS_NONADJ 7 + +typedef struct igraph_vs_t { + int type; + union { + igraph_integer_t vid; /* single vertex */ + const igraph_vector_t *vecptr; /* vector of vertices */ + struct { + igraph_integer_t vid; + igraph_neimode_t mode; + } adj; /* adjacent vertices */ + struct { + igraph_integer_t from; + igraph_integer_t to; + } seq; /* sequence of vertices from:to */ + } data; +} igraph_vs_t; + +DECLDIR int igraph_vs_all(igraph_vs_t *vs); +DECLDIR igraph_vs_t igraph_vss_all(void); + +DECLDIR int igraph_vs_adj(igraph_vs_t *vs, + igraph_integer_t vid, igraph_neimode_t mode); +DECLDIR igraph_vs_t igraph_vss_adj(igraph_integer_t vid, igraph_neimode_t mode); + +DECLDIR int igraph_vs_nonadj(igraph_vs_t *vs, igraph_integer_t vid, + igraph_neimode_t mode); + +DECLDIR int igraph_vs_none(igraph_vs_t *vs); +DECLDIR igraph_vs_t igraph_vss_none(void); + +DECLDIR int igraph_vs_1(igraph_vs_t *vs, igraph_integer_t vid); +DECLDIR igraph_vs_t igraph_vss_1(igraph_integer_t vid); + +DECLDIR int igraph_vs_vector(igraph_vs_t *vs, + const igraph_vector_t *v); +DECLDIR igraph_vs_t igraph_vss_vector(const igraph_vector_t *v); + +DECLDIR int igraph_vs_vector_small(igraph_vs_t *vs, ...); + +DECLDIR int igraph_vs_vector_copy(igraph_vs_t *vs, + const igraph_vector_t *v); + +DECLDIR int igraph_vs_seq(igraph_vs_t *vs, igraph_integer_t from, igraph_integer_t to); +DECLDIR igraph_vs_t igraph_vss_seq(igraph_integer_t from, igraph_integer_t to); + +DECLDIR void igraph_vs_destroy(igraph_vs_t *vs); + +DECLDIR igraph_bool_t igraph_vs_is_all(const igraph_vs_t *vs); + +DECLDIR int igraph_vs_copy(igraph_vs_t* dest, const igraph_vs_t* src); + +DECLDIR int igraph_vs_as_vector(const igraph_t *graph, igraph_vs_t vs, + igraph_vector_t *v); +DECLDIR int igraph_vs_size(const igraph_t *graph, const igraph_vs_t *vs, + igraph_integer_t *result); +DECLDIR int igraph_vs_type(const igraph_vs_t *vs); + +/* -------------------------------------------------- */ +/* Vertex iterators */ +/* -------------------------------------------------- */ + +#define IGRAPH_VIT_SEQ 0 +#define IGRAPH_VIT_VECTOR 1 +#define IGRAPH_VIT_VECTORPTR 2 + +typedef struct igraph_vit_t { + int type; + long int pos; + long int start; + long int end; + const igraph_vector_t *vec; +} igraph_vit_t; + +/** + * \section IGRAPH_VIT Stepping over the vertices + * + * After creating an iterator with \ref igraph_vit_create(), it + * points to the first vertex in the vertex determined by the vertex + * selector (if there is any). The \ref IGRAPH_VIT_NEXT() macro steps + * to the next vertex, \ref IGRAPH_VIT_END() checks whether there are + * more vertices to visit, \ref IGRAPH_VIT_SIZE() gives the total size + * of the vertices visited so far and to be visited. \ref + * IGRAPH_VIT_RESET() resets the iterator, it will point to the first + * vertex again. Finally \ref IGRAPH_VIT_GET() gives the current vertex + * pointed to by the iterator (call this only if \ref IGRAPH_VIT_END() + * is false). + * + * + * Here is an example on how to step over the neighbors of vertex 0: + * + * igraph_vs_t vs; + * igraph_vit_t vit; + * ... + * igraph_vs_adj(&vs, 0, IGRAPH_ALL); + * igraph_vit_create(&graph, vs, &vit); + * while (!IGRAPH_VIT_END(vit)) { + * printf(" %li", (long int) IGRAPH_VIT_GET(vit)); + * IGRAPH_VIT_NEXT(vit); + * } + * printf("\n"); + * ... + * igraph_vit_destroy(&vit); + * igraph_vs_destroy(&vs); + * + * + */ + +/** + * \define IGRAPH_VIT_NEXT + * \brief Next vertex. + * + * Steps the iterator to the next vertex. Only call this function if + * \ref IGRAPH_VIT_END() returns false. + * \param vit The vertex iterator to step. + * + * Time complexity: O(1). + */ +#define IGRAPH_VIT_NEXT(vit) (++((vit).pos)) +/** + * \define IGRAPH_VIT_END + * \brief Are we at the end? + * + * Checks whether there are more vertices to step to. + * \param vit The vertex iterator to check. + * \return Logical value, if true there are no more vertices to step + * to. + * + * Time complexity: O(1). + */ +#define IGRAPH_VIT_END(vit) ((vit).pos >= (vit).end) +/** + * \define IGRAPH_VIT_SIZE + * \brief Size of a vertex iterator. + * + * Gives the number of vertices in a vertex iterator. + * \param vit The vertex iterator. + * \return The number of vertices. + * + * Time complexity: O(1). + */ +#define IGRAPH_VIT_SIZE(vit) ((vit).end - (vit).start) +/** + * \define IGRAPH_VIT_RESET + * \brief Reset a vertex iterator. + * + * Resets a vertex iterator. After calling this macro the iterator + * will point to the first vertex. + * \param vit The vertex iterator. + * + * Time complexity: O(1). + */ +#define IGRAPH_VIT_RESET(vit) ((vit).pos = (vit).start) +/** + * \define IGRAPH_VIT_GET + * \brief Query the current position. + * + * Gives the vertex id of the current vertex pointed to by the + * iterator. + * \param vit The vertex iterator. + * \return The vertex id of the current vertex. + * + * Time complexity: O(1). + */ +#define IGRAPH_VIT_GET(vit) \ + ((igraph_integer_t)(((vit).type == IGRAPH_VIT_SEQ) ? (vit).pos : \ + VECTOR(*(vit).vec)[(vit).pos])) + +DECLDIR int igraph_vit_create(const igraph_t *graph, + igraph_vs_t vs, igraph_vit_t *vit); +DECLDIR void igraph_vit_destroy(const igraph_vit_t *vit); + +DECLDIR int igraph_vit_as_vector(const igraph_vit_t *vit, igraph_vector_t *v); + +/* -------------------------------------------------- */ +/* Edge Selectors */ +/* -------------------------------------------------- */ + +#define IGRAPH_ES_ALL 0 +#define IGRAPH_ES_ALLFROM 1 +#define IGRAPH_ES_ALLTO 2 +#define IGRAPH_ES_INCIDENT 3 +#define IGRAPH_ES_NONE 4 +#define IGRAPH_ES_1 5 +#define IGRAPH_ES_VECTORPTR 6 +#define IGRAPH_ES_VECTOR 7 +#define IGRAPH_ES_SEQ 8 +#define IGRAPH_ES_PAIRS 9 +#define IGRAPH_ES_PATH 10 +#define IGRAPH_ES_MULTIPAIRS 11 + +typedef struct igraph_es_t { + int type; + union { + igraph_integer_t vid; + igraph_integer_t eid; + const igraph_vector_t *vecptr; + struct { + igraph_integer_t vid; + igraph_neimode_t mode; + } incident; + struct { + igraph_integer_t from; + igraph_integer_t to; + } seq; + struct { + const igraph_vector_t *ptr; + igraph_bool_t mode; + } path; + } data; +} igraph_es_t; + +DECLDIR int igraph_es_all(igraph_es_t *es, + igraph_edgeorder_type_t order); +DECLDIR igraph_es_t igraph_ess_all(igraph_edgeorder_type_t order); + +DECLDIR int igraph_es_adj(igraph_es_t *es, + igraph_integer_t vid, igraph_neimode_t mode); /* deprecated */ +DECLDIR int igraph_es_incident(igraph_es_t *es, + igraph_integer_t vid, igraph_neimode_t mode); + +DECLDIR int igraph_es_none(igraph_es_t *es); +DECLDIR igraph_es_t igraph_ess_none(void); + +DECLDIR int igraph_es_1(igraph_es_t *es, igraph_integer_t eid); +DECLDIR igraph_es_t igraph_ess_1(igraph_integer_t eid); + +DECLDIR int igraph_es_vector(igraph_es_t *es, + const igraph_vector_t *v); +DECLDIR igraph_es_t igraph_ess_vector(const igraph_vector_t *v); + +DECLDIR int igraph_es_fromto(igraph_es_t *es, + igraph_vs_t from, igraph_vs_t to); + +DECLDIR int igraph_es_seq(igraph_es_t *es, igraph_integer_t from, igraph_integer_t to); +DECLDIR igraph_es_t igraph_ess_seq(igraph_integer_t from, igraph_integer_t to); + +DECLDIR int igraph_es_vector_copy(igraph_es_t *es, const igraph_vector_t *v); + +DECLDIR int igraph_es_pairs(igraph_es_t *es, const igraph_vector_t *v, + igraph_bool_t directed); +DECLDIR int igraph_es_pairs_small(igraph_es_t *es, igraph_bool_t directed, ...); + +DECLDIR int igraph_es_multipairs(igraph_es_t *es, const igraph_vector_t *v, + igraph_bool_t directed); + +DECLDIR int igraph_es_path(igraph_es_t *es, const igraph_vector_t *v, + igraph_bool_t directed); +DECLDIR int igraph_es_path_small(igraph_es_t *es, igraph_bool_t directed, ...); + +DECLDIR void igraph_es_destroy(igraph_es_t *es); + +DECLDIR igraph_bool_t igraph_es_is_all(const igraph_es_t *es); + +DECLDIR int igraph_es_copy(igraph_es_t* dest, const igraph_es_t* src); + +DECLDIR int igraph_es_as_vector(const igraph_t *graph, igraph_es_t es, + igraph_vector_t *v); +DECLDIR int igraph_es_size(const igraph_t *graph, const igraph_es_t *es, + igraph_integer_t *result); +DECLDIR int igraph_es_type(const igraph_es_t *es); + + +/* -------------------------------------------------- */ +/* Edge Iterators */ +/* -------------------------------------------------- */ + +#define IGRAPH_EIT_SEQ 0 +#define IGRAPH_EIT_VECTOR 1 +#define IGRAPH_EIT_VECTORPTR 2 + +typedef struct igraph_eit_t { + int type; + long int pos; + long int start; + long int end; + const igraph_vector_t *vec; +} igraph_eit_t; + +/** + * \section IGRAPH_EIT Stepping over the edges + * + * Just like for vertex iterators, macros are provided for + * stepping over a sequence of edges: \ref IGRAPH_EIT_NEXT() goes to + * the next edge, \ref IGRAPH_EIT_END() checks whether there are more + * edges to visit, \ref IGRAPH_EIT_SIZE() gives the number of edges in + * the edge sequence, \ref IGRAPH_EIT_RESET() resets the iterator to + * the first edge and \ref IGRAPH_EIT_GET() returns the id of the + * current edge. + */ + +/** + * \define IGRAPH_EIT_NEXT + * \brief Next edge. + * + * Steps the iterator to the next edge. Call this function only if + * \ref IGRAPH_EIT_END() returns false. + * \param eit The edge iterator to step. + * + * Time complexity: O(1). + */ +#define IGRAPH_EIT_NEXT(eit) (++((eit).pos)) +/** + * \define IGRAPH_EIT_END + * \brief Are we at the end? + * + * Checks whether there are more edges to step to. + * \param wit The edge iterator to check. + * \return Logical value, if true there are no more edges + * to step to. + * + * Time complexity: O(1). + */ +#define IGRAPH_EIT_END(eit) ((eit).pos >= (eit).end) +/** + * \define IGRAPH_EIT_SIZE + * \brief Number of edges in the iterator. + * + * Gives the number of edges in an edge iterator. + * \param eit The edge iterator. + * \return The number of edges. + * + * Time complexity: O(1). + */ +#define IGRAPH_EIT_SIZE(eit) ((eit).end - (eit).start) +/** + * \define IGRAPH_EIT_RESET + * \brief Reset an edge iterator. + * + * Resets an edge iterator. After calling this macro the iterator will + * point to the first edge. + * \param eit The edge iterator. + * + * Time complexity: O(1). + */ +#define IGRAPH_EIT_RESET(eit) ((eit).pos = (eit).start) +/** + * \define IGRAPH_EIT_GET + * \brief Query an edge iterator. + * + * Gives the edge id of the current edge pointed to by an iterator. + * \param eit The edge iterator. + * \return The id of the current edge. + * + * Time complexity: O(1). + */ +#define IGRAPH_EIT_GET(eit) \ + (igraph_integer_t)((((eit).type == IGRAPH_EIT_SEQ) ? (eit).pos : \ + VECTOR(*(eit).vec)[(eit).pos])) + +DECLDIR int igraph_eit_create(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit); +DECLDIR void igraph_eit_destroy(const igraph_eit_t *eit); + +DECLDIR int igraph_eit_as_vector(const igraph_eit_t *eit, igraph_vector_t *v); + +__END_DECLS + +#endif diff --git a/include/igraph_lapack.h b/include/igraph_lapack.h new file mode 100644 index 0000000..e3e6d53 --- /dev/null +++ b/include/igraph_lapack.h @@ -0,0 +1,114 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_LAPACK_H +#define IGRAPH_LAPACK_H + +#include "igraph_vector.h" +#include "igraph_matrix.h" +#include "igraph_decls.h" + +__BEGIN_DECLS + +/** + * \section about_lapack LAPACK interface in igraph + * + * + * LAPACK is written in Fortran90 and provides routines for solving + * systems of simultaneous linear equations, least-squares solutions + * of linear systems of equations, eigenvalue problems, and singular + * value problems. The associated matrix factorizations (LU, Cholesky, + * QR, SVD, Schur, generalized Schur) are also provided, as are + * related computations such as reordering of the Schur factorizations + * and estimating condition numbers. Dense and banded matrices are + * handled, but not general sparse matrices. In all areas, similar + * functionality is provided for real and complex matrices, in both + * single and double precision. + * + * + * + * igraph provides an interface to a very limited set of LAPACK + * functions, using the regular igraph data structures. + * + * + * + * See more about LAPACK at http://www.netlib.org/lapack/ + * + */ + +DECLDIR int igraph_lapack_dgetrf(igraph_matrix_t *a, igraph_vector_int_t *ipiv, + int *info); +DECLDIR int igraph_lapack_dgetrs(igraph_bool_t transpose, const igraph_matrix_t *a, + igraph_vector_int_t *ipiv, igraph_matrix_t *b); +DECLDIR int igraph_lapack_dgesv(igraph_matrix_t *a, igraph_vector_int_t *ipiv, + igraph_matrix_t *b, int *info); + +typedef enum { IGRAPH_LAPACK_DSYEV_ALL, + IGRAPH_LAPACK_DSYEV_INTERVAL, + IGRAPH_LAPACK_DSYEV_SELECT + } igraph_lapack_dsyev_which_t; + +DECLDIR int igraph_lapack_dsyevr(const igraph_matrix_t *A, + igraph_lapack_dsyev_which_t which, + igraph_real_t vl, igraph_real_t vu, int vestimate, + int il, int iu, igraph_real_t abstol, + igraph_vector_t *values, igraph_matrix_t *vectors, + igraph_vector_int_t *support); + +/* TODO: should we use complex vectors/matrices? */ + +DECLDIR int igraph_lapack_dgeev(const igraph_matrix_t *A, + igraph_vector_t *valuesreal, + igraph_vector_t *valuesimag, + igraph_matrix_t *vectorsleft, + igraph_matrix_t *vectorsright, int *info); + +typedef enum { IGRAPH_LAPACK_DGEEVX_BALANCE_NONE = 0, + IGRAPH_LAPACK_DGEEVX_BALANCE_PERM, + IGRAPH_LAPACK_DGEEVX_BALANCE_SCALE, + IGRAPH_LAPACK_DGEEVX_BALANCE_BOTH + } +igraph_lapack_dgeevx_balance_t; + +DECLDIR int igraph_lapack_dgeevx(igraph_lapack_dgeevx_balance_t balance, + const igraph_matrix_t *A, + igraph_vector_t *valuesreal, + igraph_vector_t *valuesimag, + igraph_matrix_t *vectorsleft, + igraph_matrix_t *vectorsright, + int *ilo, int *ihi, igraph_vector_t *scale, + igraph_real_t *abnrm, + igraph_vector_t *rconde, + igraph_vector_t *rcondv, + int *info); + +DECLDIR int igraph_lapack_dgehrd(const igraph_matrix_t *A, + int ilo, int ihi, + igraph_matrix_t *result); + +DECLDIR int igraph_lapack_ddot(const igraph_vector_t *v1, const igraph_vector_t *v2, + igraph_real_t *res); + +__END_DECLS + +#endif diff --git a/include/igraph_layout.h b/include/igraph_layout.h new file mode 100644 index 0000000..4705e03 --- /dev/null +++ b/include/igraph_layout.h @@ -0,0 +1,250 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_LAYOUT_H +#define IGRAPH_LAYOUT_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" +#include "igraph_matrix.h" +#include "igraph_datatype.h" +#include "igraph_arpack.h" +#include "igraph_iterators.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Layouts */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_layout_random(const igraph_t *graph, igraph_matrix_t *res); +DECLDIR int igraph_layout_circle(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t order); +DECLDIR int igraph_layout_star(const igraph_t *graph, igraph_matrix_t *res, + igraph_integer_t center, const igraph_vector_t *order); +DECLDIR int igraph_layout_grid(const igraph_t *graph, igraph_matrix_t *res, long int width); +DECLDIR int igraph_layout_fruchterman_reingold(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_integer_t niter, + igraph_real_t start_temp, + igraph_layout_grid_t grid, + const igraph_vector_t *weight, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy); + +DECLDIR int igraph_layout_kamada_kawai(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_integer_t maxiter, + igraph_real_t epsilon, igraph_real_t kkconst, + const igraph_vector_t *weights, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy); + +DECLDIR int igraph_layout_springs(const igraph_t *graph, igraph_matrix_t *res, + igraph_real_t mass, igraph_real_t equil, igraph_real_t k, + igraph_real_t repeqdis, igraph_real_t kfr, igraph_bool_t repulse); +DECLDIR int igraph_layout_lgl(const igraph_t *graph, igraph_matrix_t *res, + igraph_integer_t maxiter, igraph_real_t maxdelta, + igraph_real_t area, igraph_real_t coolexp, + igraph_real_t repulserad, igraph_real_t cellsize, igraph_integer_t root); +DECLDIR int igraph_layout_reingold_tilford(const igraph_t *graph, igraph_matrix_t *res, + igraph_neimode_t mode, + const igraph_vector_t *roots, + const igraph_vector_t *rootlevel); +DECLDIR int igraph_layout_reingold_tilford_circular(const igraph_t *graph, + igraph_matrix_t *res, + igraph_neimode_t mode, + const igraph_vector_t *roots, + const igraph_vector_t *rootlevel); +DECLDIR int igraph_layout_sugiyama(const igraph_t *graph, igraph_matrix_t *res, + igraph_t *extd_graph, igraph_vector_t *extd_to_orig_eids, + const igraph_vector_t* layers, igraph_real_t hgap, + igraph_real_t vgap, long int maxiter, const igraph_vector_t *weights); + +DECLDIR int igraph_layout_random_3d(const igraph_t *graph, igraph_matrix_t *res); +DECLDIR int igraph_layout_sphere(const igraph_t *graph, igraph_matrix_t *res); +DECLDIR int igraph_layout_grid_3d(const igraph_t *graph, igraph_matrix_t *res, + long int width, long int height); +DECLDIR int igraph_layout_fruchterman_reingold_3d(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_integer_t niter, + igraph_real_t start_temp, + const igraph_vector_t *weight, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy, + const igraph_vector_t *minz, + const igraph_vector_t *maxz); + +DECLDIR int igraph_layout_kamada_kawai_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_integer_t maxiter, + igraph_real_t epsilon, igraph_real_t kkconst, + const igraph_vector_t *weights, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy, + const igraph_vector_t *minz, const igraph_vector_t *maxz); + +DECLDIR int igraph_layout_graphopt(const igraph_t *graph, + igraph_matrix_t *res, igraph_integer_t niter, + igraph_real_t node_charge, igraph_real_t node_mass, + igraph_real_t spring_length, + igraph_real_t spring_constant, + igraph_real_t max_sa_movement, + igraph_bool_t use_seed); + +DECLDIR int igraph_layout_mds(const igraph_t *graph, igraph_matrix_t *res, + const igraph_matrix_t *dist, long int dim, + igraph_arpack_options_t *options); + +DECLDIR int igraph_layout_bipartite(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_matrix_t *res, igraph_real_t hgap, + igraph_real_t vgap, long int maxiter); + +/** + * \struct igraph_layout_drl_options_t + * Parameters for the DrL layout generator + * + * \member edge_cut The edge cutting parameter. + * Edge cutting is done in the late stages of the + * algorithm in order to achieve less dense layouts. Edges are cut + * if there is a lot of stress on them (a large value in the + * objective function sum). The edge cutting parameter is a value + * between 0 and 1 with 0 representing no edge cutting and 1 + * representing maximal edge cutting. The default value is 32/40. + * \member init_iterations Number of iterations, initial phase. + * \member init_temperature Start temperature, initial phase. + * \member init_attraction Attraction, initial phase. + * \member init_damping_mult Damping factor, initial phase. + * \member liquid_iterations Number of iterations in the liquid phase. + * \member liquid_temperature Start temperature in the liquid phase. + * \member liquid_attraction Attraction in the liquid phase. + * \member liquid_damping_mult Multiplicatie damping factor, liquid phase. + * \member expansion_iterations Number of iterations in the expansion phase. + * \member expansion_temperature Start temperature in the expansion phase. + * \member expansion_attraction Attraction, expansion phase. + * \member expansion_damping_mult Damping factor, expansion phase. + * \member cooldown_iterations Number of iterations in the cooldown phase. + * \member cooldown_temperature Start temperature in the cooldown phase. + * \member cooldown_attraction Attraction in the cooldown phase. + * \member cooldown_damping_mult Damping fact int the cooldown phase. + * \member crunch_iterations Number of iterations in the crunch phase. + * \member crunch_temperature Start temperature in the crunch phase. + * \member crunch_attraction Attraction in the crunch phase. + * \member crunch_damping_mult Damping factor in the crunch phase. + * \member simmer_iterations Number of iterations in the simmer phase. + * \member simmer_temperature Start temperature in te simmer phase. + * \member simmer_attraction Attraction in the simmer phase. + * \member simmer_damping_mult Multiplicative damping factor in the simmer phase. + */ + +typedef struct igraph_layout_drl_options_t { + igraph_real_t edge_cut; + igraph_integer_t init_iterations; + igraph_real_t init_temperature; + igraph_real_t init_attraction; + igraph_real_t init_damping_mult; + igraph_integer_t liquid_iterations; + igraph_real_t liquid_temperature; + igraph_real_t liquid_attraction; + igraph_real_t liquid_damping_mult; + igraph_integer_t expansion_iterations; + igraph_real_t expansion_temperature; + igraph_real_t expansion_attraction; + igraph_real_t expansion_damping_mult; + igraph_integer_t cooldown_iterations; + igraph_real_t cooldown_temperature; + igraph_real_t cooldown_attraction; + igraph_real_t cooldown_damping_mult; + igraph_integer_t crunch_iterations; + igraph_real_t crunch_temperature; + igraph_real_t crunch_attraction; + igraph_real_t crunch_damping_mult; + igraph_integer_t simmer_iterations; + igraph_real_t simmer_temperature; + igraph_real_t simmer_attraction; + igraph_real_t simmer_damping_mult; +} igraph_layout_drl_options_t; + +/** + * \typedef igraph_layout_drl_default_t + * Predefined parameter templates for the DrL layout generator + * + * These constants can be used to initialize a set of DrL parameters. + * These can then be modified according to the user's needs. + * \enumval IGRAPH_LAYOUT_DRL_DEFAULT The deafult parameters. + * \enumval IGRAPH_LAYOUT_DRL_COARSEN Slightly modified parameters to + * get a coarser layout. + * \enumval IGRAPH_LAYOUT_DRL_COARSEST An even coarser layout. + * \enumval IGRAPH_LAYOUT_DRL_REFINE Refine an already calculated layout. + * \enumval IGRAPH_LAYOUT_DRL_FINAL Finalize an already refined layout. + */ + +typedef enum { IGRAPH_LAYOUT_DRL_DEFAULT = 0, + IGRAPH_LAYOUT_DRL_COARSEN, + IGRAPH_LAYOUT_DRL_COARSEST, + IGRAPH_LAYOUT_DRL_REFINE, + IGRAPH_LAYOUT_DRL_FINAL + } igraph_layout_drl_default_t; + +DECLDIR int igraph_layout_drl_options_init(igraph_layout_drl_options_t *options, + igraph_layout_drl_default_t templ); +DECLDIR int igraph_layout_drl(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_layout_drl_options_t *options, + const igraph_vector_t *weights, + const igraph_vector_bool_t *fixed); + +DECLDIR int igraph_layout_drl_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_layout_drl_options_t *options, + const igraph_vector_t *weights, + const igraph_vector_bool_t *fixed); + +DECLDIR int igraph_layout_merge_dla(igraph_vector_ptr_t *graphs, + igraph_vector_ptr_t *coords, + igraph_matrix_t *res); + +DECLDIR int igraph_layout_gem(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_integer_t maxiter, + igraph_real_t temp_max, igraph_real_t temp_min, + igraph_real_t temp_init); + +DECLDIR int igraph_layout_davidson_harel(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_integer_t maxiter, + igraph_integer_t fineiter, igraph_real_t cool_fact, + igraph_real_t weight_node_dist, igraph_real_t weight_border, + igraph_real_t weight_edge_lengths, + igraph_real_t weight_edge_crossings, + igraph_real_t weight_node_edge_dist); + +__END_DECLS + +#endif diff --git a/include/igraph_lsap.h b/include/igraph_lsap.h new file mode 100644 index 0000000..9529831 --- /dev/null +++ b/include/igraph_lsap.h @@ -0,0 +1,16 @@ + +#ifndef IGRAPH_LSAP_H +#define IGRAPH_LSAP_H + +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" + +__BEGIN_DECLS + +int igraph_solve_lsap(igraph_matrix_t *c, igraph_integer_t n, + igraph_vector_int_t *p); + +__END_DECLS + +#endif diff --git a/include/igraph_matching.h b/include/igraph_matching.h new file mode 100644 index 0000000..f7f5978 --- /dev/null +++ b/include/igraph_matching.h @@ -0,0 +1,56 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2012 Tamas Nepusz + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_MATCHING_H +#define IGRAPH_MATCHING_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Matchings in graphs */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_is_matching(const igraph_t* graph, + const igraph_vector_bool_t* types, const igraph_vector_long_t* matching, + igraph_bool_t* result); +DECLDIR int igraph_is_maximal_matching(const igraph_t* graph, + const igraph_vector_bool_t* types, const igraph_vector_long_t* matching, + igraph_bool_t* result); + +DECLDIR int igraph_maximum_bipartite_matching(const igraph_t* graph, + const igraph_vector_bool_t* types, igraph_integer_t* matching_size, + igraph_real_t* matching_weight, igraph_vector_long_t* matching, + const igraph_vector_t* weights, igraph_real_t eps); + +DECLDIR int igraph_maximum_matching(const igraph_t* graph, igraph_integer_t* matching_size, + igraph_real_t* matching_weight, igraph_vector_long_t* matching, + const igraph_vector_t* weights); + +__END_DECLS + +#endif diff --git a/include/igraph_matrix.h b/include/igraph_matrix.h new file mode 100644 index 0000000..a4cd675 --- /dev/null +++ b/include/igraph_matrix.h @@ -0,0 +1,100 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_MATRIX_H +#define IGRAPH_MATRIX_H + +#include "igraph_decls.h" +#include "igraph_vector.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Matrix, very similar to vector */ +/* -------------------------------------------------- */ + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_matrix_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_INT +#include "igraph_pmt.h" +#include "igraph_matrix_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_LONG +#include "igraph_pmt.h" +#include "igraph_matrix_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_LONG + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "igraph_matrix_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "igraph_matrix_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_COMPLEX +#include "igraph_pmt.h" +#include "igraph_matrix_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_COMPLEX + +#define IGRAPH_MATRIX_NULL { IGRAPH_VECTOR_NULL, 0, 0 } +#define IGRAPH_MATRIX_INIT_FINALLY(m, nr, nc) \ + do { IGRAPH_CHECK(igraph_matrix_init(m, nr, nc)); \ + IGRAPH_FINALLY(igraph_matrix_destroy, m); } while (0) + +/** + * \ingroup matrix + * \define MATRIX + * \brief Accessing an element of a matrix. + * + * Note that there are no range checks right now. + * This functionality might be redefined as a proper function later. + * \param m The matrix object. + * \param i The index of the row, starting with zero. + * \param j The index of the column, starting with zero. + * + * Time complexity: O(1). + */ +#define MATRIX(m,i,j) ((m).data.stor_begin[(m).nrow*(j)+(i)]) + +igraph_bool_t igraph_matrix_all_e_tol(const igraph_matrix_t *lhs, + const igraph_matrix_t *rhs, + igraph_real_t tol); + +int igraph_matrix_zapsmall(igraph_matrix_t *m, igraph_real_t tol); + +__END_DECLS + +#endif diff --git a/include/igraph_matrix_pmt.h b/include/igraph_matrix_pmt.h new file mode 100644 index 0000000..6c38d6d --- /dev/null +++ b/include/igraph_matrix_pmt.h @@ -0,0 +1,243 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +typedef struct TYPE(igraph_matrix) { + TYPE(igraph_vector) data; + long int nrow, ncol; +} TYPE(igraph_matrix); + +/*---------------*/ +/* Allocation */ +/*---------------*/ + +DECLDIR int FUNCTION(igraph_matrix, init)(TYPE(igraph_matrix) *m, + long int nrow, long int ncol); +DECLDIR int FUNCTION(igraph_matrix, copy)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from); +DECLDIR void FUNCTION(igraph_matrix, destroy)(TYPE(igraph_matrix) *m); +DECLDIR long int FUNCTION(igraph_matrix, capacity)(const TYPE(igraph_matrix) *m); + +/*--------------------*/ +/* Accessing elements */ +/*--------------------*/ + +/* MATRIX */ +DECLDIR BASE FUNCTION(igraph_matrix, e)(const TYPE(igraph_matrix) *m, + long int row, long int col); +BASE* FUNCTION(igraph_matrix, e_ptr)(const TYPE(igraph_matrix) *m, + long int row, long int col); +DECLDIR void FUNCTION(igraph_matrix, set)(TYPE(igraph_matrix)* m, long int row, long int col, + BASE value); + +/*------------------------------*/ +/* Initializing matrix elements */ +/*------------------------------*/ + +DECLDIR void FUNCTION(igraph_matrix, null)(TYPE(igraph_matrix) *m); +DECLDIR void FUNCTION(igraph_matrix, fill)(TYPE(igraph_matrix) *m, BASE e); + +/*-----------------------*/ +/* Matrix views */ +/*-----------------------*/ + +const TYPE(igraph_matrix) *FUNCTION(igraph_matrix, view)(const TYPE(igraph_matrix) *m, + const BASE *data, + long int nrow, + long int ncol); + +/*------------------*/ +/* Copying matrices */ +/*------------------*/ + +DECLDIR void FUNCTION(igraph_matrix, copy_to)(const TYPE(igraph_matrix) *m, BASE *to); +DECLDIR int FUNCTION(igraph_matrix, update)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from); +DECLDIR int FUNCTION(igraph_matrix, rbind)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from); +DECLDIR int FUNCTION(igraph_matrix, cbind)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from); +DECLDIR int FUNCTION(igraph_matrix, swap)(TYPE(igraph_matrix) *m1, TYPE(igraph_matrix) *m2); + +/*--------------------------*/ +/* Copying rows and columns */ +/*--------------------------*/ + +DECLDIR int FUNCTION(igraph_matrix, get_row)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res, long int index); +DECLDIR int FUNCTION(igraph_matrix, get_col)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res, long int index); +DECLDIR int FUNCTION(igraph_matrix, set_row)(TYPE(igraph_matrix) *m, + const TYPE(igraph_vector) *v, long int index); +DECLDIR int FUNCTION(igraph_matrix, set_col)(TYPE(igraph_matrix) *m, + const TYPE(igraph_vector) *v, long int index); +DECLDIR int FUNCTION(igraph_matrix, select_rows)(const TYPE(igraph_matrix) *m, + TYPE(igraph_matrix) *res, + const igraph_vector_t *rows); +DECLDIR int FUNCTION(igraph_matrix, select_cols)(const TYPE(igraph_matrix) *m, + TYPE(igraph_matrix) *res, + const igraph_vector_t *cols); +DECLDIR int FUNCTION(igraph_matrix, select_rows_cols)(const TYPE(igraph_matrix) *m, + TYPE(igraph_matrix) *res, + const igraph_vector_t *rows, + const igraph_vector_t *cols); + +/*-----------------------------*/ +/* Exchanging rows and columns */ +/*-----------------------------*/ + +DECLDIR int FUNCTION(igraph_matrix, swap_rows)(TYPE(igraph_matrix) *m, + long int i, long int j); +DECLDIR int FUNCTION(igraph_matrix, swap_cols)(TYPE(igraph_matrix) *m, + long int i, long int j); +DECLDIR int FUNCTION(igraph_matrix, swap_rowcol)(TYPE(igraph_matrix) *m, + long int i, long int j); +DECLDIR int FUNCTION(igraph_matrix, transpose)(TYPE(igraph_matrix) *m); + +/*-----------------------------*/ +/* Matrix operations */ +/*-----------------------------*/ + +DECLDIR int FUNCTION(igraph_matrix, add)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); +DECLDIR int FUNCTION(igraph_matrix, sub)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); +DECLDIR int FUNCTION(igraph_matrix, mul_elements)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); +DECLDIR int FUNCTION(igraph_matrix, div_elements)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); +DECLDIR void FUNCTION(igraph_matrix, scale)(TYPE(igraph_matrix) *m, BASE by); +DECLDIR void FUNCTION(igraph_matrix, add_constant)(TYPE(igraph_matrix) *m, BASE plus); + +/*-----------------------------*/ +/* Finding minimum and maximum */ +/*-----------------------------*/ + +DECLDIR igraph_real_t FUNCTION(igraph_matrix, min)(const TYPE(igraph_matrix) *m); +DECLDIR igraph_real_t FUNCTION(igraph_matrix, max)(const TYPE(igraph_matrix) *m); +DECLDIR int FUNCTION(igraph_matrix, which_min)(const TYPE(igraph_matrix) *m, + long int *i, long int *j); +DECLDIR int FUNCTION(igraph_matrix, which_max)(const TYPE(igraph_matrix) *m, + long int *i, long int *j); +DECLDIR int FUNCTION(igraph_matrix, minmax)(const TYPE(igraph_matrix) *m, + BASE *min, BASE *max); +DECLDIR int FUNCTION(igraph_matrix, which_minmax)(const TYPE(igraph_matrix) *m, + long int *imin, long int *jmin, + long int *imax, long int *jmax); + +/*------------------------------*/ +/* Comparison */ +/*------------------------------*/ + +DECLDIR igraph_bool_t FUNCTION(igraph_matrix, all_e)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs); +DECLDIR igraph_bool_t FUNCTION(igraph_matrix, all_l)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs); +DECLDIR igraph_bool_t FUNCTION(igraph_matrix, all_g)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs); +DECLDIR igraph_bool_t FUNCTION(igraph_matrix, all_le)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs); +DECLDIR igraph_bool_t FUNCTION(igraph_matrix, all_ge)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs); + +/*-------------------*/ +/* Matrix properties */ +/*-------------------*/ + +DECLDIR igraph_bool_t FUNCTION(igraph_matrix, isnull)(const TYPE(igraph_matrix) *m); +DECLDIR igraph_bool_t FUNCTION(igraph_matrix, empty)(const TYPE(igraph_matrix) *m); +DECLDIR long int FUNCTION(igraph_matrix, size)(const TYPE(igraph_matrix) *m); +DECLDIR long int FUNCTION(igraph_matrix, nrow)(const TYPE(igraph_matrix) *m); +DECLDIR long int FUNCTION(igraph_matrix, ncol)(const TYPE(igraph_matrix) *m); +DECLDIR igraph_bool_t FUNCTION(igraph_matrix, is_symmetric)(const TYPE(igraph_matrix) *m); +DECLDIR BASE FUNCTION(igraph_matrix, sum)(const TYPE(igraph_matrix) *m); +DECLDIR BASE FUNCTION(igraph_matrix, prod)(const TYPE(igraph_matrix) *m); +DECLDIR int FUNCTION(igraph_matrix, rowsum)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res); +DECLDIR int FUNCTION(igraph_matrix, colsum)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res); +DECLDIR igraph_bool_t FUNCTION(igraph_matrix, is_equal)(const TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); +DECLDIR igraph_real_t FUNCTION(igraph_matrix, maxdifference)(const TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2); + +/*------------------------*/ +/* Searching for elements */ +/*------------------------*/ + +DECLDIR igraph_bool_t FUNCTION(igraph_matrix, contains)(const TYPE(igraph_matrix) *m, + BASE e); +DECLDIR igraph_bool_t FUNCTION(igraph_matrix, search)(const TYPE(igraph_matrix) *m, + long int from, BASE what, + long int *pos, + long int *row, long int *col); + +/*------------------------*/ +/* Resizing operations */ +/*------------------------*/ + +DECLDIR int FUNCTION(igraph_matrix, resize)(TYPE(igraph_matrix) *m, + long int nrow, long int ncol); +DECLDIR int FUNCTION(igraph_matrix, resize_min)(TYPE(igraph_matrix) *m); +DECLDIR int FUNCTION(igraph_matrix, add_cols)(TYPE(igraph_matrix) *m, long int n); +DECLDIR int FUNCTION(igraph_matrix, add_rows)(TYPE(igraph_matrix) *m, long int n); +DECLDIR int FUNCTION(igraph_matrix, remove_col)(TYPE(igraph_matrix) *m, long int col); +DECLDIR int FUNCTION(igraph_matrix, remove_row)(TYPE(igraph_matrix) *m, long int row); + +/*------------------------*/ +/* Print as text */ +/*------------------------*/ + +int FUNCTION(igraph_matrix, print)(const TYPE(igraph_matrix) *m); +int FUNCTION(igraph_matrix, printf)(const TYPE(igraph_matrix) *m, + const char *format); +int FUNCTION(igraph_matrix, fprint)(const TYPE(igraph_matrix) *m, + FILE *file); + +#ifdef BASE_COMPLEX + +int igraph_matrix_complex_real(const igraph_matrix_complex_t *v, + igraph_matrix_t *real); +int igraph_matrix_complex_imag(const igraph_matrix_complex_t *v, + igraph_matrix_t *imag); +int igraph_matrix_complex_realimag(const igraph_matrix_complex_t *v, + igraph_matrix_t *real, + igraph_matrix_t *imag); +int igraph_matrix_complex_create(igraph_matrix_complex_t *v, + const igraph_matrix_t *real, + const igraph_matrix_t *imag); +int igraph_matrix_complex_create_polar(igraph_matrix_complex_t *v, + const igraph_matrix_t *r, + const igraph_matrix_t *theta); + +#endif + +/* ----------------------------------------------------------------------------*/ +/* For internal use only, may be removed, rewritten ... */ +/* ----------------------------------------------------------------------------*/ + +int FUNCTION(igraph_matrix, permdelete_rows)(TYPE(igraph_matrix) *m, + long int *index, long int nremove); +int FUNCTION(igraph_matrix, delete_rows_neg)(TYPE(igraph_matrix) *m, + const igraph_vector_t *neg, + long int nremove); + diff --git a/include/igraph_memory.h b/include/igraph_memory.h new file mode 100644 index 0000000..01f58f3 --- /dev/null +++ b/include/igraph_memory.h @@ -0,0 +1,47 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_MEMORY_H +#define IGRAPH_MEMORY_H + +#include +#include "igraph_decls.h" + +__BEGIN_DECLS + +#define igraph_Calloc(n,t) (t*) calloc( (size_t)(n), sizeof(t) ) +#define igraph_Realloc(p,n,t) (t*) realloc((void*)(p), (size_t)((n)*sizeof(t))) +#define igraph_Free(p) (free( (void *)(p) ), (p) = NULL) + +/* #ifndef IGRAPH_NO_CALLOC */ +/* # define Calloc igraph_Calloc */ +/* # define Realloc igraph_Realloc */ +/* # define Free igraph_Free */ +/* #endif */ + +DECLDIR int igraph_free(void *p); +DECLDIR void *igraph_malloc(size_t n); + +__END_DECLS + +#endif diff --git a/include/igraph_microscopic_update.h b/include/igraph_microscopic_update.h new file mode 100644 index 0000000..c9bc45f --- /dev/null +++ b/include/igraph_microscopic_update.h @@ -0,0 +1,60 @@ +/* -*- mode: C -*- */ +/* + Microscopic update rules for dealing with agent-level strategy revision. + Copyright (C) 2011 Minh Van Nguyen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#ifndef IGRAPH_MICROSCOPIC_UPDATE_H +#define IGRAPH_MICROSCOPIC_UPDATE_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_iterators.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +__BEGIN_DECLS + +DECLDIR int igraph_deterministic_optimal_imitation(const igraph_t *graph, + igraph_integer_t vid, + igraph_optimal_t optimality, + const igraph_vector_t *quantities, + igraph_vector_t *strategies, + igraph_neimode_t mode); +DECLDIR int igraph_moran_process(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_t *quantities, + igraph_vector_t *strategies, + igraph_neimode_t mode); +DECLDIR int igraph_roulette_wheel_imitation(const igraph_t *graph, + igraph_integer_t vid, + igraph_bool_t islocal, + const igraph_vector_t *quantities, + igraph_vector_t *strategies, + igraph_neimode_t mode); +DECLDIR int igraph_stochastic_imitation(const igraph_t *graph, + igraph_integer_t vid, + igraph_imitate_algorithm_t algo, + const igraph_vector_t *quantities, + igraph_vector_t *strategies, + igraph_neimode_t mode); + +__END_DECLS + +#endif diff --git a/include/igraph_mixing.h b/include/igraph_mixing.h new file mode 100644 index 0000000..037e90a --- /dev/null +++ b/include/igraph_mixing.h @@ -0,0 +1,51 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_MIXING_H +#define IGRAPH_MIXING_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_datatype.h" +#include "igraph_vector.h" + +__BEGIN_DECLS + +DECLDIR int igraph_assortativity_nominal(const igraph_t *graph, + const igraph_vector_t *types, + igraph_real_t *res, + igraph_bool_t directed); + +DECLDIR int igraph_assortativity(const igraph_t *graph, + const igraph_vector_t *types1, + const igraph_vector_t *types2, + igraph_real_t *res, + igraph_bool_t directed); + +DECLDIR int igraph_assortativity_degree(const igraph_t *graph, + igraph_real_t *res, + igraph_bool_t directed); + +__END_DECLS + +#endif diff --git a/include/igraph_motifs.h b/include/igraph_motifs.h new file mode 100644 index 0000000..f18b314 --- /dev/null +++ b/include/igraph_motifs.h @@ -0,0 +1,97 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_MOTIFS_H +#define IGRAPH_MOTIFS_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_datatype.h" +#include "igraph_iterators.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Graph motifs */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_motifs_handler_t + * Callback type for \c igraph_motifs_randesu_callback + * + * \ref igraph_motifs_randesu_callback() calls a specified callback + * function whenever a new motif is found during a motif search. This + * callback function must be of type \c igraph_motifs_handler_t. It has + * the following arguments: + * \param graph The graph that that algorithm is working on. Of course + * this must not be modified. + * \param vids The IDs of the vertices in the motif that has just been + * found. This vector is owned by the motif search algorithm, so do not + * modify or destroy it; make a copy of it if you need it later. + * \param isoclass The isomorphism class of the motif that has just been + * found. Use \ref igraph_isoclass or \ref igraph_isoclass_subgraph to find + * out which isomorphism class belongs to a given motif. + * \param extra The extra argument that was passed to \ref + * igraph_motifs_randesu_callback(). + * \return A logical value, if TRUE (=non-zero), that is interpreted + * as a request to stop the motif search and return to the caller. + * + * \sa \ref igraph_motifs_randesu_callback() + */ + +typedef igraph_bool_t igraph_motifs_handler_t(const igraph_t *graph, + igraph_vector_t *vids, + int isoclass, + void* extra); + +DECLDIR int igraph_motifs_randesu(const igraph_t *graph, igraph_vector_t *hist, + int size, const igraph_vector_t *cut_prob); + +DECLDIR int igraph_motifs_randesu_callback(const igraph_t *graph, int size, + const igraph_vector_t *cut_prob, + igraph_motifs_handler_t *callback, + void* extra); + +DECLDIR int igraph_motifs_randesu_estimate(const igraph_t *graph, igraph_integer_t *est, + int size, const igraph_vector_t *cut_prob, + igraph_integer_t sample_size, + const igraph_vector_t *sample); +DECLDIR int igraph_motifs_randesu_no(const igraph_t *graph, igraph_integer_t *no, + int size, const igraph_vector_t *cut_prob); + +DECLDIR int igraph_dyad_census(const igraph_t *graph, igraph_integer_t *mut, + igraph_integer_t *asym, igraph_integer_t *null); +DECLDIR int igraph_triad_census(const igraph_t *igraph, igraph_vector_t *res); +DECLDIR int igraph_triad_census_24(const igraph_t *graph, igraph_real_t *res2, + igraph_real_t *res4); + +DECLDIR int igraph_adjacent_triangles(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids); + +DECLDIR int igraph_list_triangles(const igraph_t *graph, + igraph_vector_int_t *res); + +__END_DECLS + +#endif diff --git a/include/igraph_neighborhood.h b/include/igraph_neighborhood.h new file mode 100644 index 0000000..06501bb --- /dev/null +++ b/include/igraph_neighborhood.h @@ -0,0 +1,47 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_NEIGHBORHOOD_H +#define IGRAPH_NEIGHBORHOOD_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_iterators.h" +#include "igraph_vector_ptr.h" + +__BEGIN_DECLS + +DECLDIR int igraph_neighborhood_size(const igraph_t *graph, igraph_vector_t *res, + igraph_vs_t vids, igraph_integer_t order, + igraph_neimode_t mode, igraph_integer_t mindist); +DECLDIR int igraph_neighborhood(const igraph_t *graph, igraph_vector_ptr_t *res, + igraph_vs_t vids, igraph_integer_t order, + igraph_neimode_t mode, igraph_integer_t mindist); +DECLDIR int igraph_neighborhood_graphs(const igraph_t *graph, igraph_vector_ptr_t *res, + igraph_vs_t vids, igraph_integer_t order, + igraph_neimode_t mode, + igraph_integer_t mindist); + +__END_DECLS + +#endif diff --git a/include/igraph_nongraph.h b/include/igraph_nongraph.h new file mode 100644 index 0000000..d1b460c --- /dev/null +++ b/include/igraph_nongraph.h @@ -0,0 +1,93 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_NONGRAPH_H +#define IGRAPH_NONGRAPH_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_matrix.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Other, not graph related */ +/* -------------------------------------------------- */ + +/** + * \struct igraph_plfit_result_t + * \brief Result of fitting a power-law distribution to a vector + * + * This data structure contains the result of \ref igraph_power_law_fit(), + * which tries to fit a power-law distribution to a vector of numbers. The + * structure contains the following members: + * + * \member continuous Whether the fitted power-law distribution was continuous + * or discrete. + * \member alpha The exponent of the fitted power-law distribution. + * \member xmin The minimum value from which the power-law distribution was + * fitted. In other words, only the values larger than \c xmin + * were used from the input vector. + * \member L The log-likelihood of the fitted parameters; in other words, + * the probability of observing the input vector given the + * parameters. + * \member D The test statistic of a Kolmogorov-Smirnov test that compares + * the fitted distribution with the input vector. Smaller scores + * denote better fit. + * \member p The p-value of the Kolmogorov-Smirnov test. Small p-values + * (less than 0.05) indicate that the test rejected the hypothesis + * that the original data could have been drawn from the fitted + * power-law distribution. + */ +typedef struct igraph_plfit_result_t { + igraph_bool_t continuous; + double alpha; + double xmin; + double L; + double D; + double p; +} igraph_plfit_result_t; + +DECLDIR int igraph_running_mean(const igraph_vector_t *data, igraph_vector_t *res, + igraph_integer_t binwidth); +DECLDIR int igraph_fisher_yates_shuffle(igraph_vector_t *seq); +DECLDIR int igraph_random_sample(igraph_vector_t *res, igraph_real_t l, igraph_real_t h, + igraph_integer_t length); +DECLDIR int igraph_convex_hull(const igraph_matrix_t *data, igraph_vector_t *resverts, + igraph_matrix_t *rescoords); +DECLDIR int igraph_zeroin(igraph_real_t *ax, igraph_real_t *bx, + igraph_real_t (*f)(igraph_real_t x, void *info), + void *info, igraph_real_t *Tol, int *Maxit, igraph_real_t *res); +DECLDIR int igraph_bfgs(igraph_vector_t *b, igraph_real_t *Fmin, + igraph_scalar_function_t fminfn, igraph_vector_function_t fmingr, + int maxit, int trace, + igraph_real_t abstol, igraph_real_t reltol, int nREPORT, void *ex, + igraph_integer_t *fncount, igraph_integer_t *grcount); +DECLDIR int igraph_power_law_fit(const igraph_vector_t* vector, igraph_plfit_result_t* result, + igraph_real_t xmin, igraph_bool_t force_continuous); + +__END_DECLS + +#endif diff --git a/include/igraph_operators.h b/include/igraph_operators.h new file mode 100644 index 0000000..244b8fe --- /dev/null +++ b/include/igraph_operators.h @@ -0,0 +1,63 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_OPERATORS_H +#define IGRAPH_OPERATORS_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_types.h" +#include "igraph_datatype.h" +#include "igraph_vector_ptr.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Graph operators */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_disjoint_union(igraph_t *res, + const igraph_t *left, const igraph_t *right); +DECLDIR int igraph_disjoint_union_many(igraph_t *res, + const igraph_vector_ptr_t *graphs); +DECLDIR int igraph_union(igraph_t *res, const igraph_t *left, const igraph_t *right, + igraph_vector_t *edge_map1, igraph_vector_t *edge_map2); +DECLDIR int igraph_union_many(igraph_t *res, const igraph_vector_ptr_t *graphs, + igraph_vector_ptr_t *edgemaps); +DECLDIR int igraph_intersection(igraph_t *res, + const igraph_t *left, const igraph_t *right, + igraph_vector_t *edge_map1, + igraph_vector_t *edge_map2); +DECLDIR int igraph_intersection_many(igraph_t *res, + const igraph_vector_ptr_t *graphs, + igraph_vector_ptr_t *edgemaps); +DECLDIR int igraph_difference(igraph_t *res, + const igraph_t *orig, const igraph_t *sub); +DECLDIR int igraph_complementer(igraph_t *res, const igraph_t *graph, + igraph_bool_t loops); +DECLDIR int igraph_compose(igraph_t *res, const igraph_t *g1, const igraph_t *g2, + igraph_vector_t *edge_map1, igraph_vector_t *edge_map2); + +__END_DECLS + +#endif diff --git a/include/igraph_paths.h b/include/igraph_paths.h new file mode 100644 index 0000000..f5fed98 --- /dev/null +++ b/include/igraph_paths.h @@ -0,0 +1,146 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_PATHS_H +#define IGRAPH_PATHS_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" +#include "igraph_matrix.h" +#include "igraph_iterators.h" + +__BEGIN_DECLS + +DECLDIR int igraph_diameter(const igraph_t *graph, igraph_integer_t *res, + igraph_integer_t *from, igraph_integer_t *to, + igraph_vector_t *path, + igraph_bool_t directed, igraph_bool_t unconn); +DECLDIR int igraph_diameter_dijkstra(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *pres, + igraph_integer_t *pfrom, + igraph_integer_t *pto, + igraph_vector_t *path, + igraph_bool_t directed, + igraph_bool_t unconn); + +DECLDIR int igraph_shortest_paths(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t from, const igraph_vs_t to, + igraph_neimode_t mode); +DECLDIR int igraph_get_shortest_paths(const igraph_t *graph, + igraph_vector_ptr_t *vertices, + igraph_vector_ptr_t *edges, + igraph_integer_t from, const igraph_vs_t to, + igraph_neimode_t mode, + igraph_vector_long_t *predecessors, + igraph_vector_long_t *inbound_edges); +DECLDIR int igraph_get_shortest_path(const igraph_t *graph, + igraph_vector_t *vertices, + igraph_vector_t *edges, + igraph_integer_t from, + igraph_integer_t to, + igraph_neimode_t mode); + +DECLDIR int igraph_get_all_shortest_paths(const igraph_t *graph, + igraph_vector_ptr_t *res, + igraph_vector_t *nrgeo, + igraph_integer_t from, const igraph_vs_t to, + igraph_neimode_t mode); +DECLDIR int igraph_shortest_paths_dijkstra(const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); +DECLDIR int igraph_shortest_paths_bellman_ford(const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); +DECLDIR int igraph_get_shortest_paths_dijkstra(const igraph_t *graph, + igraph_vector_ptr_t *vertices, + igraph_vector_ptr_t *edges, + igraph_integer_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_long_t *predecessors, + igraph_vector_long_t *inbound_edges); +DECLDIR int igraph_get_shortest_path_dijkstra(const igraph_t *graph, + igraph_vector_t *vertices, + igraph_vector_t *edges, + igraph_integer_t from, + igraph_integer_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); +DECLDIR int igraph_get_all_shortest_paths_dijkstra(const igraph_t *graph, + igraph_vector_ptr_t *res, + igraph_vector_t *nrgeo, + igraph_integer_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode); +DECLDIR int igraph_shortest_paths_johnson(const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights); + +DECLDIR int igraph_average_path_length(const igraph_t *graph, igraph_real_t *res, + igraph_bool_t directed, igraph_bool_t unconn); +DECLDIR int igraph_path_length_hist(const igraph_t *graph, igraph_vector_t *res, + igraph_real_t *unconnected, igraph_bool_t directed); + +DECLDIR int igraph_eccentricity(const igraph_t *graph, + igraph_vector_t *res, + igraph_vs_t vids, + igraph_neimode_t mode); + +DECLDIR int igraph_radius(const igraph_t *graph, igraph_real_t *radius, + igraph_neimode_t mode); + +DECLDIR int igraph_get_all_simple_paths(const igraph_t *graph, + igraph_vector_int_t *res, + igraph_integer_t from, + const igraph_vs_t to, + igraph_integer_t cutoff, + igraph_neimode_t mode); + +DECLDIR int igraph_random_walk(const igraph_t *graph, igraph_vector_t *walk, + igraph_integer_t start, igraph_neimode_t mode, + igraph_integer_t steps, + igraph_random_walk_stuck_t stuck); + +DECLDIR int igraph_random_edge_walk(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_t *edgewalk, + igraph_integer_t start, igraph_neimode_t mode, + igraph_integer_t steps, + igraph_random_walk_stuck_t stuck); + +__END_DECLS + +#endif diff --git a/include/igraph_pmt.h b/include/igraph_pmt.h new file mode 100644 index 0000000..33d424a --- /dev/null +++ b/include/igraph_pmt.h @@ -0,0 +1,150 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#define CONCAT2x(a,b) a ## _ ## b +#define CONCAT2(a,b) CONCAT2x(a,b) +#define CONCAT3x(a,b,c) a ## _ ## b ## _ ## c +#define CONCAT3(a,b,c) CONCAT3x(a,b,c) +#define CONCAT4x(a,b,c,d) a ## _ ## b ## _ ## c ## _ ## d +#define CONCAT4(a,b,c,d) CONCAT4x(a,b,c,d) + +#if defined(BASE_IGRAPH_REAL) + #define BASE igraph_real_t + #define SHORT + #define OUT_FORMAT "%G" + #define PRINTFUNC(val) igraph_real_printf(val) + #define FPRINTFUNC(file, val) igraph_real_fprintf(file, val) + #define ZERO 0.0 + #define ONE 1.0 + #define MULTIPLICITY 1 + +#elif defined(BASE_FLOAT) + #define BASE float + #define SHORT float + #define OUT_FORMAT "%f" + #define ZERO 0.0F + #define ONE 1.0F + #define MULTIPLICITY 1 + +#elif defined(BASE_LONG) + #define BASE long + #define SHORT long + #define OUT_FORMAT "%ld" + #define ZERO 0L + #define ONE 1L + #define MULTIPLICITY 1 + +#elif defined(BASE_CHAR) + #define BASE char + #define SHORT char + #define OUT_FORMAT "%d" + #define ZERO 0 + #define ONE 1 + #define MULTIPLICITY 1 + +#elif defined(BASE_BOOL) + #define BASE igraph_bool_t + #define SHORT bool + #define OUT_FORMAT "%d" + #define ZERO 0 + #define ONE 1 + #define MULTIPLICITY 1 + +#elif defined(BASE_INT) + #define BASE int + #define SHORT int + #define OUT_FORMAT "%d" + #define ZERO 0 + #define ONE 1 + #define MULTIPLICITY 1 + +#elif defined(BASE_LIMB) + #define BASE limb_t + #define SHORT limb + #define ZERO 0 + #define ONE 1 + #define MULTIPLICITY 1 + #define UNSIGNED 1 + +#elif defined(BASE_PTR) + #define BASE void* + #define SHORT ptr + #define ZERO 0 + #define MULTIPLICITY 1 + +#elif defined(BASE_COMPLEX) + #undef complex + #define BASE igraph_complex_t + #define SHORT complex + #define ZERO igraph_complex(0,0) + #define ONE {{1.0,0.0}} + #define MULTIPLICITY 2 + #define NOTORDERED 1 + #define NOABS 1 + #define SUM(a,b,c) ((a) = igraph_complex_add((b),(c))) + #define DIFF(a,b,c) ((a) = igraph_complex_sub((b),(c))) + #define PROD(a,b,c) ((a) = igraph_complex_mul((b),(c))) + #define DIV(a,b,c) ((a) = igraph_complex_div((b),(c))) + #define EQ(a,b) IGRAPH_COMPLEX_EQ((a),(b)) + #define SQ(a) IGRAPH_REAL(igraph_complex_mul((a),(a))) + +#else + #error unknown BASE_ directive +#endif + +#if defined(BASE_IGRAPH_REAL) + #define FUNCTION(dir,name) CONCAT2(dir,name) + #define TYPE(dir) CONCAT2(dir,t) +#elif defined(BASE_BOOL) + /* Special case because stdbool.h defines bool as a macro to _Bool which would + * screw things up */ + #define FUNCTION(a,c) CONCAT3x(a,bool,c) + #define TYPE(dir) CONCAT3x(dir,bool,t) +#else + #define FUNCTION(a,c) CONCAT3(a,SHORT,c) + #define TYPE(dir) CONCAT3(dir,SHORT,t) +#endif + +#if defined(HEAP_TYPE_MIN) + #define HEAPMORE < + #define HEAPMOREEQ <= + #define HEAPLESS > + #define HEAPLESSEQ >= + #undef FUNCTION + #undef TYPE + #if defined(BASE_IGRAPH_REAL) + #define FUNCTION(dir,name) CONCAT3(dir,min,name) + #define TYPE(dir) CONCAT3(dir,min,t) + #else + #define FUNCTION(a,c) CONCAT4(a,min,SHORT,c) + #define TYPE(dir) CONCAT4(dir,min,SHORT,t) + #endif +#endif + +#if defined(HEAP_TYPE_MAX) + #define HEAPMORE > + #define HEAPMOREEQ >= + #define HEAPLESS < + #define HEAPLESSEQ <= +#endif + diff --git a/include/igraph_pmt_off.h b/include/igraph_pmt_off.h new file mode 100644 index 0000000..95a957e --- /dev/null +++ b/include/igraph_pmt_off.h @@ -0,0 +1,158 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifdef ATOMIC + #undef ATOMIC +#endif + +#ifdef ATOMIC_IO + #undef ATOMIC_IO +#endif + +#ifdef BASE + #undef BASE +#endif + +#ifdef BASE_EPSILON + #undef BASE_EPSILON +#endif + +#ifdef CONCAT2 + #undef CONCAT2 +#endif + +#ifdef CONCAT2x + #undef CONCAT2x +#endif + +#ifdef CONCAT3 + #undef CONCAT3 +#endif + +#ifdef CONCAT3x + #undef CONCAT3x +#endif + +#ifdef CONCAT4 + #undef CONCAT4 +#endif + +#ifdef CONCAT4x + #undef CONCAT4x +#endif + +#ifdef FP + #undef FP +#endif + +#ifdef FUNCTION + #undef FUNCTION +#endif + +#ifdef IN_FORMAT + #undef IN_FORMAT +#endif + +#ifdef MULTIPLICITY + #undef MULTIPLICITY +#endif + +#ifdef ONE + #undef ONE +#endif + +#ifdef OUT_FORMAT + #undef OUT_FORMAT +#endif + +#ifdef SHORT + #undef SHORT +#endif + +#ifdef TYPE + #undef TYPE +#endif + +#ifdef ZERO + #undef ZERO +#endif + +#ifdef HEAPMORE + #undef HEAPMORE +#endif + +#ifdef HEAPLESS + #undef HEAPLESS +#endif + +#ifdef HEAPMOREEQ + #undef HEAPMOREEQ +#endif + +#ifdef HEAPLESSEQ + #undef HEAPLESSEQ +#endif + +#ifdef SUM + #undef SUM +#endif + +#ifdef SQ + #undef SQ +#endif + +#ifdef PROD + #undef PROD +#endif + +#ifdef NOTORDERED + #undef NOTORDERED +#endif + +#ifdef EQ + #undef EQ +#endif + +#ifdef DIFF + #undef DIFF +#endif + +#ifdef DIV + #undef DIV +#endif + +#ifdef NOABS + #undef NOABS +#endif + +#ifdef PRINTFUNC + #undef PRINTFUNC +#endif + +#ifdef FPRINTFUNC + #undef PRINTFUNC +#endif + +#ifdef UNSIGNED + #undef UNSIGNED +#endif diff --git a/include/igraph_progress.h b/include/igraph_progress.h new file mode 100644 index 0000000..74bc593 --- /dev/null +++ b/include/igraph_progress.h @@ -0,0 +1,183 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_PROGRESS_H +#define IGRAPH_PROGRESS_H + +#include "igraph_decls.h" +#include "igraph_types.h" + +__BEGIN_DECLS + +/** + * \section about_progress_handlers About progress handlers + * + * It is often useful to report the progress of some long + * calculation, to allow the user to follow the computation and + * guess the total running time. A couple of igraph functions + * support this at the time of writing, hopefully more will support it + * in the future. + * + * + * + * To see the progress of a computation, the user has to install a + * progress handler, as there is none installed by default. + * If an igraph function supports progress reporting, then it + * calls the installed progress handler periodically, and passes a + * percentage value to it, the percentage of computation already + * performed. To install a progress handler, you need to call + * \ref igraph_set_progress_handler(). Currently there is a single + * pre-defined progress handler, called \ref + * igraph_progress_handler_stderr(). + * + */ + +/** + * \section writing_progress_handlers Writing progress handlers + * + * + * To write a new progress handler, one needs to create a function of + * type \ref igraph_progress_handler_t. The new progress handler + * can then be installed with the \ref igraph_set_progress_handler() + * function. + * + * + * + * One can assume that the first progress handler call from a + * calculation will be call with zero as the \p percentage argument, + * and the last call from a function will have 100 as the \p + * percentage argument. Note, however, that if an error happens in the + * middle of a computation, then the 100 percent call might be + * omitted. + * + */ + +/** + * \section igraph_functions_with_progress Writing igraph functions with progress reporting + * + * + * If you want to write a function that uses igraph and supports + * progress reporting, you need to include \ref igraph_progress() + * calls in your function, usually via the \ref IGRAPH_PROGRESS() + * macro. + * + * + * + * It is good practice to always include a call to \ref + * igraph_progress() with a zero \p percentage argument, before the + * computation; and another call with 100 \p percentage value + * after the computation is completed. + * + * + * + * It is also good practice \em not to call \ref igraph_progress() too + * often, as this would slow down the computation. It might not be + * worth to support progress reporting in functions with linear or + * log-linear time complexity, as these are fast, even with a large + * amount of data. For functions with quadratic or higher time + * complexity make sure that the time complexity of the progress + * reporting is constant or at least linear. In practice this means + * having at most O(n) progress checks and at most 100 \reg + * igraph_progress() calls. + * + */ + +/** + * \section progress_and_threads Multi-threaded programs + * + * + * In multi-threaded programs, each thread has its own progress + * handler, if thread-local storage is supported and igraph is + * thread-safe. See the \ref IGRAPH_THREAD_SAFE macro for checking + * whether an igraph build is thread-safe. + * + */ + +/* -------------------------------------------------- */ +/* Progress handlers */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_progress_handler_t + * \brief Type of progress handler functions + * + * This is the type of the igraph progress handler functions. + * There is currently one such predefined function, + * \ref igraph_progress_handler_stderr(), but the user can + * write and set up more sophisticated ones. + * \param message A string describing the function or algorithm + * that is reporting the progress. Current igraph functions + * always use the name \p message argument if reporting from the + * same function. + * \param percent Numeric, the percentage that was completed by the + * algorithm or function. + * \param data User-defined data. Current igraph functions that + * report progress pass a null pointer here. Users can + * write their own progress handlers and functions with progress + * reporting, and then pass some meaningfull context here. + * \return If the return value of the progress handler is not + * IGRAPH_SUCCESS (=0), then \ref igraph_progress() returns the + * error code \c IGRAPH_INTERRUPTED. The \ref IGRAPH_PROGRESS() + * macro frees all memory and finishes the igraph function with + * error code \c IGRAPH_INTERRUPTED in this case. + */ + +typedef int igraph_progress_handler_t(const char *message, igraph_real_t percent, + void *data); + +extern igraph_progress_handler_t igraph_progress_handler_stderr; + +DECLDIR igraph_progress_handler_t * igraph_set_progress_handler(igraph_progress_handler_t new_handler); + +DECLDIR int igraph_progress(const char *message, igraph_real_t percent, void *data); + +DECLDIR int igraph_progressf(const char *message, igraph_real_t percent, void *data, + ...); + +/** + * \define IGRAPH_PROGRESS + * \brief Report progress. + * + * The standard way to report progress from an igraph function + * \param message A string, a textual message that references the + * calculation under progress. + * \param percent Numeric scalar, the percentage that is complete. + * \param data User-defined data, this can be used in user-defined + * progress handler functions, from user-written igraph functions. + * \return If the progress handler returns with \c IGRAPH_INTERRUPTED, + * then this macro frees up the igraph allocated memory for + * temporary data and returns to the caller with \c + * IGRAPH_INTERRUPTED. + */ + +#define IGRAPH_PROGRESS(message, percent, data) \ + do { \ + if (igraph_progress((message), (percent), (data)) != IGRAPH_SUCCESS) { \ + IGRAPH_FINALLY_FREE(); \ + return IGRAPH_INTERRUPTED; \ + } \ + } while (0) + +__END_DECLS + +#endif diff --git a/include/igraph_psumtree.h b/include/igraph_psumtree.h new file mode 100644 index 0000000..23d7e69 --- /dev/null +++ b/include/igraph_psumtree.h @@ -0,0 +1,58 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_PSUMTREE_H +#define IGRAPH_PSUMTREE_H + +#include "igraph_decls.h" +#include "igraph_vector.h" + +__BEGIN_DECLS + +/* + * Defines a partial prefix sum tree which is handy for drawing random numbers + * from a dynamic discrete distribution. The first part (0,...,offset - 1) of + * the vector v contains the prefixes of the values contained in the latter part + * (offset, offset + size - 1) of vector v. + */ + +typedef struct { + igraph_vector_t v; + long int size; + long int offset; +} igraph_psumtree_t; + +DECLDIR int igraph_psumtree_init(igraph_psumtree_t *t, long int size); +DECLDIR void igraph_psumtree_reset(igraph_psumtree_t *t); +DECLDIR void igraph_psumtree_destroy(igraph_psumtree_t *t); +DECLDIR igraph_real_t igraph_psumtree_get(const igraph_psumtree_t *t, long int idx); +DECLDIR long int igraph_psumtree_size(const igraph_psumtree_t *t); +DECLDIR int igraph_psumtree_search(const igraph_psumtree_t *t, long int *idx, + igraph_real_t elem); +DECLDIR int igraph_psumtree_update(igraph_psumtree_t *t, long int idx, + igraph_real_t new_value); +DECLDIR igraph_real_t igraph_psumtree_sum(const igraph_psumtree_t *t); + +__END_DECLS + +#endif diff --git a/include/igraph_qsort.h b/include/igraph_qsort.h new file mode 100644 index 0000000..09a26c9 --- /dev/null +++ b/include/igraph_qsort.h @@ -0,0 +1,40 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA 02139, USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_QSORT_H +#define IGRAPH_QSORT_H + +#include "igraph_decls.h" + +#include + +__BEGIN_DECLS + +DECLDIR void igraph_qsort(void *base, size_t nel, size_t width, + int (*compar)(const void *, const void *)); +DECLDIR void igraph_qsort_r(void *base, size_t nel, size_t width, void *thunk, + int (*compar)(void *, const void *, const void *)); + +__END_DECLS + +#endif diff --git a/include/igraph_random.h b/include/igraph_random.h new file mode 100644 index 0000000..2713c8d --- /dev/null +++ b/include/igraph_random.h @@ -0,0 +1,133 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef REST_RANDOM_H +#define REST_RANDOM_H + +#include "igraph_decls.h" + +__BEGIN_DECLS + +#include +#include + +#include "igraph_types.h" +#include "igraph_vector.h" + +/* The new RNG interface is (somewhat) modelled based on the GSL */ + +typedef struct igraph_rng_type_t { + const char *name; + unsigned long int min; + unsigned long int max; + int (*init)(void **state); + void (*destroy)(void *state); + int (*seed)(void *state, unsigned long int seed); + unsigned long int (*get)(void *state); + igraph_real_t (*get_real)(void *state); + igraph_real_t (*get_norm)(void *state); + igraph_real_t (*get_geom)(void *state, igraph_real_t p); + igraph_real_t (*get_binom)(void *state, long int n, igraph_real_t p); + igraph_real_t (*get_exp)(void *state, igraph_real_t rate); + igraph_real_t (*get_gamma)(void *state, igraph_real_t shape, + igraph_real_t scale); +} igraph_rng_type_t; + +typedef struct igraph_rng_t { + const igraph_rng_type_t *type; + void *state; + int def; +} igraph_rng_t; + +/* --------------------------------- */ + +DECLDIR int igraph_rng_init(igraph_rng_t *rng, const igraph_rng_type_t *type); +DECLDIR void igraph_rng_destroy(igraph_rng_t *rng); + +DECLDIR int igraph_rng_seed(igraph_rng_t *rng, unsigned long int seed); +DECLDIR unsigned long int igraph_rng_max(igraph_rng_t *rng); +DECLDIR unsigned long int igraph_rng_min(igraph_rng_t *rng); +DECLDIR const char *igraph_rng_name(igraph_rng_t *rng); + +DECLDIR long int igraph_rng_get_integer(igraph_rng_t *rng, + long int l, long int h); +DECLDIR igraph_real_t igraph_rng_get_normal(igraph_rng_t *rng, + igraph_real_t m, igraph_real_t s); +DECLDIR igraph_real_t igraph_rng_get_unif(igraph_rng_t *rng, + igraph_real_t l, igraph_real_t h); +DECLDIR igraph_real_t igraph_rng_get_unif01(igraph_rng_t *rng); +DECLDIR igraph_real_t igraph_rng_get_geom(igraph_rng_t *rng, igraph_real_t p); +DECLDIR igraph_real_t igraph_rng_get_binom(igraph_rng_t *rng, long int n, + igraph_real_t p); +DECLDIR igraph_real_t igraph_rng_get_exp(igraph_rng_t *rng, igraph_real_t rate); +DECLDIR unsigned long int igraph_rng_get_int31(igraph_rng_t *rng); +DECLDIR igraph_real_t igraph_rng_get_gamma(igraph_rng_t *rng, igraph_real_t shape, + igraph_real_t scale); +DECLDIR int igraph_rng_get_dirichlet(igraph_rng_t *rng, + const igraph_vector_t *alpha, + igraph_vector_t *result); + +/* --------------------------------- */ + +extern const igraph_rng_type_t igraph_rngtype_glibc2; +extern const igraph_rng_type_t igraph_rngtype_rand; +extern const igraph_rng_type_t igraph_rngtype_mt19937; + +DECLDIR igraph_rng_t *igraph_rng_default(void); +DECLDIR void igraph_rng_set_default(igraph_rng_t *rng); + +/* --------------------------------- */ + +#ifdef USING_R + +void GetRNGstate(void); +void PutRNGstate(void); +#define RNG_BEGIN() GetRNGstate() +#define RNG_END() PutRNGstate() + +double Rf_dnorm4(double x, double mu, double sigma, int give_log); +#define igraph_dnorm Rf_dnorm4 + +#else + +#define RNG_BEGIN() if (igraph_rng_default()->def==1) { \ + igraph_rng_seed(igraph_rng_default(), time(0)); \ + igraph_rng_default()->def=2; \ + } +#define RNG_END() /* do nothing */ + +DECLDIR double igraph_dnorm(double x, double mu, double sigma, int give_log); + +#endif + +#define RNG_INTEGER(l,h) (igraph_rng_get_integer(igraph_rng_default(),(l),(h))) +#define RNG_NORMAL(m,s) (igraph_rng_get_normal(igraph_rng_default(),(m),(s))) +#define RNG_UNIF(l,h) (igraph_rng_get_unif(igraph_rng_default(),(l),(h))) +#define RNG_UNIF01() (igraph_rng_get_unif01(igraph_rng_default())) +#define RNG_GEOM(p) (igraph_rng_get_geom(igraph_rng_default(),(p))) +#define RNG_BINOM(n,p) (igraph_rng_get_binom(igraph_rng_default(),(n),(p))) +#define RNG_INT31() (igraph_rng_get_int31(igraph_rng_default())) + +__END_DECLS + +#endif diff --git a/include/igraph_scan.h b/include/igraph_scan.h new file mode 100644 index 0000000..1ac04c8 --- /dev/null +++ b/include/igraph_scan.h @@ -0,0 +1,69 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_SCAN_H +#define IGRAPH_SCAN_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_arpack.h" +#include "igraph_constants.h" +#include "igraph_vector_ptr.h" + +__BEGIN_DECLS + +DECLDIR int igraph_local_scan_0(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *weights, igraph_neimode_t mode); + +DECLDIR int igraph_local_scan_0_them(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weigths_them, + igraph_neimode_t mode); + +DECLDIR int igraph_local_scan_1_ecount(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode); + +DECLDIR int igraph_local_scan_1_ecount_them(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode); + +DECLDIR int igraph_local_scan_k_ecount(const igraph_t *graph, int k, + igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode); + +DECLDIR int igraph_local_scan_k_ecount_them(const igraph_t *us, const igraph_t *them, + int k, igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode); + +DECLDIR int igraph_local_scan_neighborhood_ecount(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights, + const igraph_vector_ptr_t *neighborhoods); + +__END_DECLS + +#endif diff --git a/include/igraph_scg.h b/include/igraph_scg.h new file mode 100644 index 0000000..dd5f5c4 --- /dev/null +++ b/include/igraph_scg.h @@ -0,0 +1,142 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_SCG_H +#define IGRAPH_SCG_H + +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" +#include "igraph_sparsemat.h" + +__BEGIN_DECLS + +typedef enum { IGRAPH_SCG_SYMMETRIC = 1, IGRAPH_SCG_LAPLACIAN = 2, + IGRAPH_SCG_STOCHASTIC = 3 + } igraph_scg_matrix_t; + +typedef enum { IGRAPH_SCG_OPTIMUM = 1, IGRAPH_SCG_INTERV_KM = 2, + IGRAPH_SCG_INTERV = 3, IGRAPH_SCG_EXACT = 4 + } +igraph_scg_algorithm_t; + +typedef enum { IGRAPH_SCG_NORM_ROW = 1, IGRAPH_SCG_NORM_COL = 2 } +igraph_scg_norm_t; + +typedef enum { IGRAPH_SCG_DIRECTION_DEFAULT = 1, + IGRAPH_SCG_DIRECTION_LEFT = 2, + IGRAPH_SCG_DIRECTION_RIGHT = 3 + } igraph_scg_direction_t; + +int igraph_scg_grouping(const igraph_matrix_t *V, + igraph_vector_t *groups, + igraph_integer_t nt, + const igraph_vector_t *nt_vec, + igraph_scg_matrix_t mtype, + igraph_scg_algorithm_t algo, + const igraph_vector_t *p, + igraph_integer_t maxiter); + +int igraph_scg_semiprojectors(const igraph_vector_t *groups, + igraph_scg_matrix_t mtype, + igraph_matrix_t *L, + igraph_matrix_t *R, + igraph_sparsemat_t *Lsparse, + igraph_sparsemat_t *Rsparse, + const igraph_vector_t *p, + igraph_scg_norm_t norm); + +int igraph_scg_norm_eps(const igraph_matrix_t *V, + const igraph_vector_t *groups, + igraph_vector_t *eps, + igraph_scg_matrix_t mtype, + const igraph_vector_t *p, + igraph_scg_norm_t norm); + +int igraph_scg_adjacency(const igraph_t *graph, + const igraph_matrix_t *matrix, + const igraph_sparsemat_t *sparsemat, + const igraph_vector_t *ev, + igraph_integer_t nt, + const igraph_vector_t *nt_vec, + igraph_scg_algorithm_t algo, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_vector_t *groups, + igraph_bool_t use_arpack, + igraph_integer_t maxiter, + igraph_t *scg_graph, + igraph_matrix_t *scg_matrix, + igraph_sparsemat_t *scg_sparsemat, + igraph_matrix_t *L, + igraph_matrix_t *R, + igraph_sparsemat_t *Lsparse, + igraph_sparsemat_t *Rsparse); + +int igraph_scg_stochastic(const igraph_t *graph, + const igraph_matrix_t *matrix, + const igraph_sparsemat_t *sparsemat, + const igraph_vector_t *ev, + igraph_integer_t nt, + const igraph_vector_t *nt_vec, + igraph_scg_algorithm_t algo, + igraph_scg_norm_t norm, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors, + igraph_vector_t *groups, + igraph_vector_t *p, + igraph_bool_t use_arpack, + igraph_integer_t maxiter, + igraph_t *scg_graph, + igraph_matrix_t *scg_matrix, + igraph_sparsemat_t *scg_sparsemat, + igraph_matrix_t *L, + igraph_matrix_t *R, + igraph_sparsemat_t *Lsparse, + igraph_sparsemat_t *Rsparse); + +int igraph_scg_laplacian(const igraph_t *graph, + const igraph_matrix_t *matrix, + const igraph_sparsemat_t *sparsemat, + const igraph_vector_t *ev, + igraph_integer_t nt, + const igraph_vector_t *nt_vec, + igraph_scg_algorithm_t algo, + igraph_scg_norm_t norm, + igraph_scg_direction_t direction, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors, + igraph_vector_t *groups, + igraph_bool_t use_arpack, + igraph_integer_t maxiter, + igraph_t *scg_graph, + igraph_matrix_t *scg_matrix, + igraph_sparsemat_t *scg_sparsemat, + igraph_matrix_t *L, + igraph_matrix_t *R, + igraph_sparsemat_t *Lsparse, + igraph_sparsemat_t *Rsparse); + +__END_DECLS + +#endif diff --git a/include/igraph_separators.h b/include/igraph_separators.h new file mode 100644 index 0000000..74e051e --- /dev/null +++ b/include/igraph_separators.h @@ -0,0 +1,53 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_SEPARATORS_H +#define IGRAPH_SEPARATORS_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" +#include "igraph_datatype.h" +#include "igraph_iterators.h" + +__BEGIN_DECLS + +DECLDIR int igraph_is_separator(const igraph_t *graph, + const igraph_vs_t candidate, + igraph_bool_t *res); + +DECLDIR int igraph_all_minimal_st_separators(const igraph_t *graph, + igraph_vector_ptr_t *separators); + +DECLDIR int igraph_is_minimal_separator(const igraph_t *graph, + const igraph_vs_t candidate, + igraph_bool_t *res); + +DECLDIR int igraph_minimum_size_separators(const igraph_t *graph, + igraph_vector_ptr_t *separators); + +__END_DECLS + +#endif diff --git a/include/igraph_sparsemat.h b/include/igraph_sparsemat.h new file mode 100644 index 0000000..fa7ccaf --- /dev/null +++ b/include/igraph_sparsemat.h @@ -0,0 +1,287 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_SPARSEMAT_H +#define IGRAPH_SPARSEMAT_H + +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_datatype.h" +#include "igraph_arpack.h" + +#include + +__BEGIN_DECLS + +struct cs_di_sparse; +struct cs_di_symbolic; +struct cs_di_numeric; + +typedef struct { + struct cs_di_sparse *cs; +} igraph_sparsemat_t; + +typedef struct { + struct cs_di_symbolic *symbolic; +} igraph_sparsemat_symbolic_t; + +typedef struct { + struct cs_di_numeric *numeric; +} igraph_sparsemat_numeric_t; + +typedef enum { IGRAPH_SPARSEMAT_TRIPLET, + IGRAPH_SPARSEMAT_CC + } igraph_sparsemat_type_t; + +typedef struct { + igraph_sparsemat_t *mat; + int pos; + int col; +} igraph_sparsemat_iterator_t; + +int igraph_sparsemat_init(igraph_sparsemat_t *A, int rows, int cols, int nzmax); +int igraph_sparsemat_copy(igraph_sparsemat_t *to, + const igraph_sparsemat_t *from); +void igraph_sparsemat_destroy(igraph_sparsemat_t *A); +int igraph_sparsemat_realloc(igraph_sparsemat_t *A, int nzmax); + +long int igraph_sparsemat_nrow(const igraph_sparsemat_t *A); +long int igraph_sparsemat_ncol(const igraph_sparsemat_t *B); +igraph_sparsemat_type_t igraph_sparsemat_type(const igraph_sparsemat_t *A); +igraph_bool_t igraph_sparsemat_is_triplet(const igraph_sparsemat_t *A); +igraph_bool_t igraph_sparsemat_is_cc(const igraph_sparsemat_t *A); + +int igraph_sparsemat_permute(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res); + +int igraph_sparsemat_index(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res, + igraph_real_t *constres); + +int igraph_sparsemat_entry(igraph_sparsemat_t *A, int row, int col, + igraph_real_t elem); +int igraph_sparsemat_compress(const igraph_sparsemat_t *A, + igraph_sparsemat_t *res); +int igraph_sparsemat_transpose(const igraph_sparsemat_t *A, + igraph_sparsemat_t *res, int values); +igraph_bool_t igraph_sparsemat_is_symmetric(const igraph_sparsemat_t *A); +int igraph_sparsemat_dupl(igraph_sparsemat_t *A); +int igraph_sparsemat_fkeep(igraph_sparsemat_t *A, + int (*fkeep)(int, int, igraph_real_t, void*), + void *other); +int igraph_sparsemat_dropzeros(igraph_sparsemat_t *A); +int igraph_sparsemat_droptol(igraph_sparsemat_t *A, igraph_real_t tol); +int igraph_sparsemat_multiply(const igraph_sparsemat_t *A, + const igraph_sparsemat_t *B, + igraph_sparsemat_t *res); +int igraph_sparsemat_add(const igraph_sparsemat_t *A, + const igraph_sparsemat_t *B, + igraph_real_t alpha, + igraph_real_t beta, + igraph_sparsemat_t *res); +int igraph_sparsemat_gaxpy(const igraph_sparsemat_t *A, + const igraph_vector_t *x, + igraph_vector_t *res); + +int igraph_sparsemat_lsolve(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res); +int igraph_sparsemat_ltsolve(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res); +int igraph_sparsemat_usolve(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res); +int igraph_sparsemat_utsolve(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res); + +int igraph_sparsemat_cholsol(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res, + int order); + +int igraph_sparsemat_lusol(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res, + int order, + igraph_real_t tol); + +int igraph_sparsemat_print(const igraph_sparsemat_t *A, + FILE *outstream); + +int igraph_sparsemat_eye(igraph_sparsemat_t *A, int n, int nzmax, + igraph_real_t value, + igraph_bool_t compress); + +int igraph_sparsemat_diag(igraph_sparsemat_t *A, int nzmax, + const igraph_vector_t *values, + igraph_bool_t compress); + +int igraph_sparsemat(igraph_t *graph, const igraph_sparsemat_t *A, + igraph_bool_t directed); + +int igraph_weighted_sparsemat(igraph_t *graph, const igraph_sparsemat_t *A, + igraph_bool_t directed, const char *attr, + igraph_bool_t loops); + +int igraph_get_sparsemat(const igraph_t *graph, igraph_sparsemat_t *res); + +int igraph_matrix_as_sparsemat(igraph_sparsemat_t *res, + const igraph_matrix_t *mat, + igraph_real_t tol); + +int igraph_sparsemat_as_matrix(igraph_matrix_t *res, + const igraph_sparsemat_t *spmat); + +typedef enum { IGRAPH_SPARSEMAT_SOLVE_LU, + IGRAPH_SPARSEMAT_SOLVE_QR + } igraph_sparsemat_solve_t; + +int igraph_sparsemat_arpack_rssolve(const igraph_sparsemat_t *A, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_sparsemat_solve_t solvemethod); + +int igraph_sparsemat_arpack_rnsolve(const igraph_sparsemat_t *A, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_matrix_t *values, + igraph_matrix_t *vectors); + +int igraph_sparsemat_lu(const igraph_sparsemat_t *A, + const igraph_sparsemat_symbolic_t *dis, + igraph_sparsemat_numeric_t *din, double tol); + +int igraph_sparsemat_qr(const igraph_sparsemat_t *A, + const igraph_sparsemat_symbolic_t *dis, + igraph_sparsemat_numeric_t *din); + +int igraph_sparsemat_luresol(const igraph_sparsemat_symbolic_t *dis, + const igraph_sparsemat_numeric_t *din, + const igraph_vector_t *b, + igraph_vector_t *res); + +int igraph_sparsemat_qrresol(const igraph_sparsemat_symbolic_t *dis, + const igraph_sparsemat_numeric_t *din, + const igraph_vector_t *b, + igraph_vector_t *res); + +int igraph_sparsemat_symbqr(long int order, const igraph_sparsemat_t *A, + igraph_sparsemat_symbolic_t *dis); + +int igraph_sparsemat_symblu(long int order, const igraph_sparsemat_t *A, + igraph_sparsemat_symbolic_t *dis); + + +void igraph_sparsemat_symbolic_destroy(igraph_sparsemat_symbolic_t *dis); +void igraph_sparsemat_numeric_destroy(igraph_sparsemat_numeric_t *din); + +igraph_real_t igraph_sparsemat_max(igraph_sparsemat_t *A); +igraph_real_t igraph_sparsemat_min(igraph_sparsemat_t *A); +int igraph_sparsemat_minmax(igraph_sparsemat_t *A, + igraph_real_t *min, igraph_real_t *max); + +long int igraph_sparsemat_count_nonzero(igraph_sparsemat_t *A); +long int igraph_sparsemat_count_nonzerotol(igraph_sparsemat_t *A, + igraph_real_t tol); +int igraph_sparsemat_rowsums(const igraph_sparsemat_t *A, + igraph_vector_t *res); +int igraph_sparsemat_colsums(const igraph_sparsemat_t *A, + igraph_vector_t *res); + +int igraph_sparsemat_rowmins(igraph_sparsemat_t *A, + igraph_vector_t *res); +int igraph_sparsemat_colmins(igraph_sparsemat_t *A, + igraph_vector_t *res); + +int igraph_sparsemat_rowmaxs(igraph_sparsemat_t *A, + igraph_vector_t *res); +int igraph_sparsemat_colmaxs(igraph_sparsemat_t *A, + igraph_vector_t *res); + +int igraph_sparsemat_which_min_rows(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos); +int igraph_sparsemat_which_min_cols(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos); + +int igraph_sparsemat_scale(igraph_sparsemat_t *A, igraph_real_t by); + + +int igraph_sparsemat_add_rows(igraph_sparsemat_t *A, long int n); +int igraph_sparsemat_add_cols(igraph_sparsemat_t *A, long int n); +int igraph_sparsemat_resize(igraph_sparsemat_t *A, long int nrow, + long int ncol, int nzmax); +int igraph_sparsemat_nonzero_storage(const igraph_sparsemat_t *A); +int igraph_sparsemat_getelements(const igraph_sparsemat_t *A, + igraph_vector_int_t *i, + igraph_vector_int_t *j, + igraph_vector_t *x); +int igraph_sparsemat_getelements_sorted(const igraph_sparsemat_t *A, + igraph_vector_int_t *i, + igraph_vector_int_t *j, + igraph_vector_t *x); +int igraph_sparsemat_scale_rows(igraph_sparsemat_t *A, + const igraph_vector_t *fact); +int igraph_sparsemat_scale_cols(igraph_sparsemat_t *A, + const igraph_vector_t *fact); +int igraph_sparsemat_multiply_by_dense(const igraph_sparsemat_t *A, + const igraph_matrix_t *B, + igraph_matrix_t *res); +int igraph_sparsemat_dense_multiply(const igraph_matrix_t *A, + const igraph_sparsemat_t *B, + igraph_matrix_t *res); + +int igraph_i_sparsemat_view(igraph_sparsemat_t *A, int nzmax, int m, int n, + int *p, int *i, double *x, int nz); + +int igraph_sparsemat_sort(const igraph_sparsemat_t *A, + igraph_sparsemat_t *sorted); + +int igraph_sparsemat_nzmax(const igraph_sparsemat_t *A); + +int igraph_sparsemat_neg(igraph_sparsemat_t *A); + +int igraph_sparsemat_iterator_init(igraph_sparsemat_iterator_t *it, + igraph_sparsemat_t *sparsemat); +int igraph_sparsemat_iterator_reset(igraph_sparsemat_iterator_t *it); +igraph_bool_t +igraph_sparsemat_iterator_end(const igraph_sparsemat_iterator_t *it); +int igraph_sparsemat_iterator_row(const igraph_sparsemat_iterator_t *it); +int igraph_sparsemat_iterator_col(const igraph_sparsemat_iterator_t *it); +int igraph_sparsemat_iterator_idx(const igraph_sparsemat_iterator_t *it); +igraph_real_t +igraph_sparsemat_iterator_get(const igraph_sparsemat_iterator_t *it); +int igraph_sparsemat_iterator_next(igraph_sparsemat_iterator_t *it); + +__END_DECLS + +#endif diff --git a/include/igraph_spmatrix.h b/include/igraph_spmatrix.h new file mode 100644 index 0000000..ee6c589 --- /dev/null +++ b/include/igraph_spmatrix.h @@ -0,0 +1,114 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_SPMATRIX_H +#define IGRAPH_SPMATRIX_H + +#include "igraph_decls.h" +#include "igraph_vector.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Sparse matrix */ +/* -------------------------------------------------- */ + +/** + * \section about_igraph_spmatrix_t_objects About \type igraph_spmatrix_t objects + * + * The \type igraph_spmatrix_t type stores a sparse matrix with the + * assumption that the number of nonzero elements in the matrix scales + * linearly with the row or column count of the matrix (so most of the + * elements are zero). Of course it can store an arbitrary real matrix, + * but if most of the elements are nonzero, one should use \type igraph_matrix_t + * instead. + * + * The elements are stored in column compressed format, so the elements + * in the same column are stored adjacent in the computer's memory. The storage + * requirement for a sparse matrix is O(n) where n is the number of nonzero + * elements. Actually it can be a bit larger, see the documentation of + * the vector type for an explanation. + */ +typedef struct s_spmatrix { + igraph_vector_t ridx, cidx, data; + long int nrow, ncol; +} igraph_spmatrix_t; + +#define IGRAPH_SPMATRIX_INIT_FINALLY(m, nr, nc) \ + do { IGRAPH_CHECK(igraph_spmatrix_init(m, nr, nc)); \ + IGRAPH_FINALLY(igraph_spmatrix_destroy, m); } while (0) + +DECLDIR int igraph_spmatrix_init(igraph_spmatrix_t *m, long int nrow, long int ncol); +DECLDIR void igraph_spmatrix_destroy(igraph_spmatrix_t *m); +DECLDIR int igraph_spmatrix_resize(igraph_spmatrix_t *m, long int nrow, long int ncol); +DECLDIR igraph_real_t igraph_spmatrix_e(const igraph_spmatrix_t *m, long int row, long int col); +DECLDIR int igraph_spmatrix_set(igraph_spmatrix_t *m, long int row, long int col, + igraph_real_t value); +DECLDIR int igraph_spmatrix_add_e(igraph_spmatrix_t *m, long int row, long int col, + igraph_real_t value); +DECLDIR int igraph_spmatrix_add_col_values(igraph_spmatrix_t *m, long int to, long int from); +DECLDIR long int igraph_spmatrix_count_nonzero(const igraph_spmatrix_t *m); +DECLDIR long int igraph_spmatrix_size(const igraph_spmatrix_t *m); +DECLDIR long int igraph_spmatrix_nrow(const igraph_spmatrix_t *m); +DECLDIR long int igraph_spmatrix_ncol(const igraph_spmatrix_t *m); +DECLDIR int igraph_spmatrix_copy_to(const igraph_spmatrix_t *m, igraph_real_t *to); +DECLDIR int igraph_spmatrix_null(igraph_spmatrix_t *m); +DECLDIR int igraph_spmatrix_add_cols(igraph_spmatrix_t *m, long int n); +DECLDIR int igraph_spmatrix_add_rows(igraph_spmatrix_t *m, long int n); +DECLDIR int igraph_spmatrix_clear_col(igraph_spmatrix_t *m, long int col); +DECLDIR int igraph_spmatrix_clear_row(igraph_spmatrix_t *m, long int row); +DECLDIR int igraph_spmatrix_copy(igraph_spmatrix_t *to, const igraph_spmatrix_t *from); +DECLDIR igraph_real_t igraph_spmatrix_max_nonzero(const igraph_spmatrix_t *m, + igraph_real_t *ridx, igraph_real_t *cidx); +DECLDIR igraph_real_t igraph_spmatrix_max(const igraph_spmatrix_t *m, + igraph_real_t *ridx, igraph_real_t *cidx); +DECLDIR void igraph_spmatrix_scale(igraph_spmatrix_t *m, igraph_real_t by); +DECLDIR int igraph_spmatrix_colsums(const igraph_spmatrix_t *m, igraph_vector_t *res); +DECLDIR int igraph_spmatrix_rowsums(const igraph_spmatrix_t *m, igraph_vector_t *res); + +DECLDIR int igraph_spmatrix_print(const igraph_spmatrix_t *matrix); +DECLDIR int igraph_spmatrix_fprint(const igraph_spmatrix_t *matrix, FILE* file); + +DECLDIR int igraph_i_spmatrix_get_col_nonzero_indices(const igraph_spmatrix_t *m, + igraph_vector_t *res, long int col); +DECLDIR int igraph_i_spmatrix_clear_row_fast(igraph_spmatrix_t *m, long int row); +DECLDIR int igraph_i_spmatrix_cleanup(igraph_spmatrix_t *m); + + +typedef struct s_spmatrix_iter { + const igraph_spmatrix_t *m; /* pointer to the matrix we are iterating over */ + long int pos; /* internal index into the data vector */ + long int ri; /* row index */ + long int ci; /* column index */ + igraph_real_t value; /* value at the given cell */ +} igraph_spmatrix_iter_t; + +DECLDIR int igraph_spmatrix_iter_create(igraph_spmatrix_iter_t *mit, const igraph_spmatrix_t *m); +DECLDIR int igraph_spmatrix_iter_reset(igraph_spmatrix_iter_t *mit); +DECLDIR int igraph_spmatrix_iter_next(igraph_spmatrix_iter_t *mit); +DECLDIR igraph_bool_t igraph_spmatrix_iter_end(igraph_spmatrix_iter_t *mit); +DECLDIR void igraph_spmatrix_iter_destroy(igraph_spmatrix_iter_t *mit); + +__END_DECLS + +#endif diff --git a/include/igraph_stack.h b/include/igraph_stack.h new file mode 100644 index 0000000..c845735 --- /dev/null +++ b/include/igraph_stack.h @@ -0,0 +1,79 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_STACK_H +#define IGRAPH_STACK_H + +#include "igraph_decls.h" +#include "igraph_types.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Plain stack */ +/* -------------------------------------------------- */ + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_stack_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_LONG +#include "igraph_pmt.h" +#include "igraph_stack_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_LONG + +#define BASE_INT +#include "igraph_pmt.h" +#include "igraph_stack_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "igraph_stack_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "igraph_stack_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_PTR +#include "igraph_pmt.h" +#include "igraph_stack_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_PTR + +#define IGRAPH_STACK_NULL { 0,0,0 } + +void igraph_stack_ptr_free_all(igraph_stack_ptr_t* s); +void igraph_stack_ptr_destroy_all(igraph_stack_ptr_t* s); + +__END_DECLS + +#endif diff --git a/include/igraph_stack_pmt.h b/include/igraph_stack_pmt.h new file mode 100644 index 0000000..9c84a9f --- /dev/null +++ b/include/igraph_stack_pmt.h @@ -0,0 +1,47 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +/** + * Stack data type. + * \ingroup internal + */ + +typedef struct TYPE(igraph_stack) { + BASE* stor_begin; + BASE* stor_end; + BASE* end; +} TYPE(igraph_stack); + +DECLDIR int FUNCTION(igraph_stack, init)(TYPE(igraph_stack)* s, long int size); +DECLDIR void FUNCTION(igraph_stack, destroy)(TYPE(igraph_stack)* s); +DECLDIR int FUNCTION(igraph_stack, reserve)(TYPE(igraph_stack)* s, long int size); +DECLDIR igraph_bool_t FUNCTION(igraph_stack, empty)(TYPE(igraph_stack)* s); +DECLDIR long int FUNCTION(igraph_stack, size)(const TYPE(igraph_stack)* s); +DECLDIR void FUNCTION(igraph_stack, clear)(TYPE(igraph_stack)* s); +DECLDIR int FUNCTION(igraph_stack, push)(TYPE(igraph_stack)* s, BASE elem); +DECLDIR BASE FUNCTION(igraph_stack, pop)(TYPE(igraph_stack)* s); +DECLDIR BASE FUNCTION(igraph_stack, top)(const TYPE(igraph_stack)* s); +DECLDIR int FUNCTION(igraph_stack, print)(const TYPE(igraph_stack)* s); +DECLDIR int FUNCTION(igraph_stack, fprint)(const TYPE(igraph_stack)* s, FILE *file); diff --git a/include/igraph_statusbar.h b/include/igraph_statusbar.h new file mode 100644 index 0000000..b3ead54 --- /dev/null +++ b/include/igraph_statusbar.h @@ -0,0 +1,126 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_STATUSBAR_H +#define IGRAPH_STATUSBAR_H + +#include "igraph_decls.h" + +__BEGIN_DECLS + +/** + * \section about_status_handlers Status reporting + * + * + * In addition to the possibility of reporting the progress of an + * igraph computation via \ref igraph_progress(), it is also possible + * to report simple status messages from within igraph functions, + * without having to judge how much of the computation was performed + * already. For this one needs to install a status handler function. + * + * + * + * Status handler functions must be of type \ref igraph_status_handler_t + * and they can be install by a call to \ref igraph_set_status_handler(). + * Currently there is a simple predefined status handler function, + * called \ref igraph_status_handler_stderr(), but the user can define + * new ones. + * + * + * + * Igraph functions report their status via a call to the + * \ref IGRAPH_STATUS() or the \ref IGRAPH_STATUSF() macro. + * + */ + +/** + * \typedef igraph_status_handler_t + * + * The type of the igraph status handler functions + * \param message The status message. + * \param data Additional context, with user-defined semantics. + * Existing igraph functions pass a null pointer here. + */ + +typedef int igraph_status_handler_t(const char *message, void *data); + +extern igraph_status_handler_t igraph_status_handler_stderr; + +DECLDIR igraph_status_handler_t * igraph_set_status_handler(igraph_status_handler_t new_handler); + +DECLDIR int igraph_status(const char *message, void *data); + +/** + * \define IGRAPH_STATUS + * Report the status of an igraph function. + * + * Typically this function is called only a handful of times from + * an igraph function. E.g. if an algorithm has three major + * steps, then it is logical to call it three times, to + * signal the three major steps. + * \param message The status message. + * \param data Additional context, with user-defined semantics. + * Existing igraph functions pass a null pointer here. + * \return If the status handler returns with a value other than + * \c IGRAPH_SUCCESS, then the function that called this + * macro returns as well, with error code + * \c IGRAPH_INTERRUPTED. + */ + +#define IGRAPH_STATUS(message, data) \ + do { \ + if (igraph_status((message), (data)) != IGRAPH_SUCCESS) { \ + IGRAPH_FINALLY_FREE(); \ + return IGRAPH_INTERRUPTED; \ + } \ + } while (0) + +DECLDIR int igraph_statusf(const char *message, void *data, ...); + +/** + * \define IGRAPH_STATUSF + * Report the status from an igraph function + * + * This is the more flexible version of \ref IGRAPH_STATUS(), + * having a printf-like syntax. As this macro takes variable + * number of arguments, they must be all supplied as a single + * argument, enclosed in parentheses. Then \ref igraph_statusf() + * is called with the given arguments. + * \param args The arguments to pass to \ref igraph_statusf(). + * \return If the status handler returns with a value other than + * \c IGRAPH_SUCCESS, then the function that called this + * macro returns as well, with error code + * \c IGRAPH_INTERRUPTED. + */ + +#define IGRAPH_STATUSF(args) \ + do { \ + if (igraph_statusf args != IGRAPH_SUCCESS) { \ + IGRAPH_FINALLY_FREE(); \ + return IGRAPH_INTERRUPTED; \ + } \ + } while (0) + +__END_DECLS + +#endif diff --git a/include/igraph_structural.h b/include/igraph_structural.h new file mode 100644 index 0000000..4ff1ac9 --- /dev/null +++ b/include/igraph_structural.h @@ -0,0 +1,151 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_STRUCTURAL_H +#define IGRAPH_STRUCTURAL_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" +#include "igraph_datatype.h" +#include "igraph_iterators.h" +#include "igraph_attributes.h" +#include "igraph_sparsemat.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Basic query functions */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_are_connected(const igraph_t *graph, igraph_integer_t v1, igraph_integer_t v2, igraph_bool_t *res); + +/* -------------------------------------------------- */ +/* Structural properties */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_minimum_spanning_tree(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *weights); +DECLDIR int igraph_minimum_spanning_tree_unweighted(const igraph_t *graph, + igraph_t *mst); +DECLDIR int igraph_minimum_spanning_tree_prim(const igraph_t *graph, igraph_t *mst, + const igraph_vector_t *weights); +DECLDIR int igraph_random_spanning_tree(const igraph_t *graph, igraph_vector_t *res, + igraph_integer_t vid); + +DECLDIR int igraph_subcomponent(const igraph_t *graph, igraph_vector_t *res, igraph_real_t vid, + igraph_neimode_t mode); +DECLDIR int igraph_rewire(igraph_t *graph, igraph_integer_t n, igraph_rewiring_t mode); +DECLDIR int igraph_subgraph(const igraph_t *graph, igraph_t *res, + const igraph_vs_t vids); +DECLDIR int igraph_induced_subgraph_map(const igraph_t *graph, igraph_t *res, + const igraph_vs_t vids, + igraph_subgraph_implementation_t impl, + igraph_vector_t *map, + igraph_vector_t *invmap); +DECLDIR int igraph_induced_subgraph(const igraph_t *graph, igraph_t *res, + const igraph_vs_t vids, igraph_subgraph_implementation_t impl); +DECLDIR int igraph_subgraph_edges(const igraph_t *graph, igraph_t *res, + const igraph_es_t eids, igraph_bool_t delete_vertices); +DECLDIR int igraph_simplify(igraph_t *graph, igraph_bool_t multiple, + igraph_bool_t loops, + const igraph_attribute_combination_t *edge_comb); +DECLDIR int igraph_reciprocity(const igraph_t *graph, igraph_real_t *res, + igraph_bool_t ignore_loops, + igraph_reciprocity_t mode); + +DECLDIR int igraph_maxdegree(const igraph_t *graph, igraph_integer_t *res, + igraph_vs_t vids, igraph_neimode_t mode, + igraph_bool_t loops); +DECLDIR int igraph_density(const igraph_t *graph, igraph_real_t *res, + igraph_bool_t loops); + +DECLDIR int igraph_has_loop(const igraph_t *graph, igraph_bool_t *res); +DECLDIR int igraph_is_loop(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es); +DECLDIR int igraph_is_simple(const igraph_t *graph, igraph_bool_t *res); +DECLDIR int igraph_has_multiple(const igraph_t *graph, igraph_bool_t *res); +DECLDIR int igraph_is_multiple(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es); +DECLDIR int igraph_count_multiple(const igraph_t *graph, igraph_vector_t *res, igraph_es_t es); +DECLDIR int igraph_is_tree(const igraph_t *graph, igraph_bool_t *res, igraph_integer_t *root, igraph_neimode_t mode); +DECLDIR int igraph_girth(const igraph_t *graph, igraph_integer_t *girth, + igraph_vector_t *circle); +DECLDIR int igraph_add_edge(igraph_t *graph, igraph_integer_t from, igraph_integer_t to); + +DECLDIR int igraph_unfold_tree(const igraph_t *graph, igraph_t *tree, + igraph_neimode_t mode, const igraph_vector_t *roots, + igraph_vector_t *vertex_index); + +DECLDIR int igraph_is_mutual(igraph_t *graph, igraph_vector_bool_t *res, igraph_es_t es); + +DECLDIR int igraph_maximum_cardinality_search(const igraph_t *graph, + igraph_vector_t *alpha, + igraph_vector_t *alpham1); +DECLDIR int igraph_is_chordal(const igraph_t *graph, + const igraph_vector_t *alpha, + const igraph_vector_t *alpham1, + igraph_bool_t *chordal, + igraph_vector_t *fill_in, + igraph_t *newgraph); +DECLDIR int igraph_avg_nearest_neighbor_degree(const igraph_t *graph, + igraph_vs_t vids, + igraph_neimode_t mode, + igraph_neimode_t neighbor_degree_mode, + igraph_vector_t *knn, + igraph_vector_t *knnk, + const igraph_vector_t *weights); +DECLDIR int igraph_contract_vertices(igraph_t *graph, + const igraph_vector_t *mapping, + const igraph_attribute_combination_t + *vertex_comb); + +DECLDIR int igraph_feedback_arc_set(const igraph_t *graph, igraph_vector_t *result, + const igraph_vector_t *weights, igraph_fas_algorithm_t algo); + +DECLDIR int igraph_diversity(igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, const igraph_vs_t vs); + +/* -------------------------------------------------- */ +/* Spectral Properties */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_laplacian(const igraph_t *graph, igraph_matrix_t *res, + igraph_sparsemat_t *sparseres, + igraph_bool_t normalized, + const igraph_vector_t *weights); + +/* -------------------------------------------------- */ +/* Internal functions, may change any time */ +/* -------------------------------------------------- */ + +int igraph_i_feedback_arc_set_undirected(const igraph_t *graph, igraph_vector_t *result, + const igraph_vector_t *weights, igraph_vector_t *layering); +int igraph_i_feedback_arc_set_eades(const igraph_t *graph, igraph_vector_t *result, + const igraph_vector_t *weights, igraph_vector_t *layering); + +__END_DECLS + +#endif diff --git a/include/igraph_strvector.h b/include/igraph_strvector.h new file mode 100644 index 0000000..a108d63 --- /dev/null +++ b/include/igraph_strvector.h @@ -0,0 +1,97 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_STRVECTOR_H +#define IGRAPH_STRVECTOR_H + +#include "igraph_decls.h" +#include "igraph_vector.h" + +__BEGIN_DECLS + +/** + * Vector of strings + * \ingroup internal + */ + +typedef struct s_igraph_strvector { + char **data; + long int len; +} igraph_strvector_t; + +/** + * \define STR + * Indexing string vectors + * + * This is a macro which allows to query the elements of a string vector in + * simpler way than \ref igraph_strvector_get(). Note this macro cannot be + * used to set an element, for that use \ref igraph_strvector_set(). + * \param sv The string vector + * \param i The the index of the element. + * \return The element at position \p i. + * + * Time complexity: O(1). + */ +#define STR(sv,i) ((const char *)((sv).data[(i)])) + +#define IGRAPH_STRVECTOR_NULL { 0,0 } +#define IGRAPH_STRVECTOR_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_strvector_init(v, size)); \ + IGRAPH_FINALLY( (igraph_finally_func_t*) igraph_strvector_destroy, v); } while (0) + +DECLDIR int igraph_strvector_init(igraph_strvector_t *sv, long int len); +DECLDIR void igraph_strvector_destroy(igraph_strvector_t *sv); +DECLDIR long int igraph_strvector_size(const igraph_strvector_t *sv); +DECLDIR void igraph_strvector_get(const igraph_strvector_t *sv, + long int idx, char **value); +DECLDIR int igraph_strvector_set(igraph_strvector_t *sv, long int idx, + const char *value); +DECLDIR int igraph_strvector_set2(igraph_strvector_t *sv, long int idx, + const char *value, int len); +DECLDIR void igraph_strvector_clear(igraph_strvector_t *sv); +DECLDIR void igraph_strvector_remove_section(igraph_strvector_t *v, long int from, + long int to); +DECLDIR void igraph_strvector_remove(igraph_strvector_t *v, long int elem); +DECLDIR void igraph_strvector_move_interval(igraph_strvector_t *v, long int begin, + long int end, long int to); +DECLDIR int igraph_strvector_copy(igraph_strvector_t *to, + const igraph_strvector_t *from); +DECLDIR int igraph_strvector_append(igraph_strvector_t *to, + const igraph_strvector_t *from); +DECLDIR int igraph_strvector_resize(igraph_strvector_t* v, long int newsize); +DECLDIR int igraph_strvector_add(igraph_strvector_t *v, const char *value); +DECLDIR void igraph_strvector_permdelete(igraph_strvector_t *v, const igraph_vector_t *index, + long int nremove); +DECLDIR void igraph_strvector_remove_negidx(igraph_strvector_t *v, const igraph_vector_t *neg, + long int nremove); +DECLDIR int igraph_strvector_print(const igraph_strvector_t *v, FILE *file, + const char *sep); + +DECLDIR int igraph_strvector_index(const igraph_strvector_t *v, + igraph_strvector_t *newv, + const igraph_vector_t *idx); + + +__END_DECLS + +#endif diff --git a/include/igraph_threading.h.in b/include/igraph_threading.h.in new file mode 100644 index 0000000..de20e09 --- /dev/null +++ b/include/igraph_threading.h.in @@ -0,0 +1,43 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_THREADING_H +#define IGRAPH_THREADING_H + +#include "igraph_decls.h" + +__BEGIN_DECLS + +/** + * \define IGRAPH_THREAD_SAFE + * + * Macro that is defined to be 1 if the current build of the + * igraph library is thread-safe, and 0 if it is not. + */ + +#define IGRAPH_THREAD_SAFE @HAVE_TLS@ + +__END_DECLS + +#endif + diff --git a/include/igraph_topology.h b/include/igraph_topology.h new file mode 100644 index 0000000..b935d79 --- /dev/null +++ b/include/igraph_topology.h @@ -0,0 +1,292 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_TOPOLOGY_H +#define IGRAPH_TOPOLOGY_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_types.h" +#include "igraph_vector_ptr.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Degree sequences */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_is_degree_sequence(const igraph_vector_t *out_degrees, + const igraph_vector_t *in_degrees, igraph_bool_t *res); +DECLDIR int igraph_is_graphical_degree_sequence(const igraph_vector_t *out_degrees, + const igraph_vector_t *in_degrees, igraph_bool_t *res); + +/* -------------------------------------------------- */ +/* Directed acyclic graphs */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_topological_sorting(const igraph_t *graph, igraph_vector_t *res, + igraph_neimode_t mode); +DECLDIR int igraph_is_dag(const igraph_t *graph, igraph_bool_t *res); +DECLDIR int igraph_transitive_closure_dag(const igraph_t *graph, + igraph_t *closure); + +/* -------------------------------------------------- */ +/* Graph isomorphisms */ +/* -------------------------------------------------- */ + +/* Common functions */ +DECLDIR int igraph_permute_vertices(const igraph_t *graph, igraph_t *res, + const igraph_vector_t *permutation); + +DECLDIR int igraph_simplify_and_colorize( + const igraph_t *graph, igraph_t *res, + igraph_vector_int_t *vertex_color, igraph_vector_int_t *edge_color); + +/* Generic interface */ +DECLDIR int igraph_isomorphic(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso); +DECLDIR int igraph_subisomorphic(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso); + +/* LAD */ +DECLDIR int igraph_subisomorphic_lad(const igraph_t *pattern, const igraph_t *target, + igraph_vector_ptr_t *domains, + igraph_bool_t *iso, igraph_vector_t *map, + igraph_vector_ptr_t *maps, + igraph_bool_t induced, int time_limit); + +/* VF2 family*/ +/** + * \typedef igraph_isohandler_t + * Callback type, called when an isomorphism was found + * + * See the details at the documentation of \ref + * igraph_isomorphic_function_vf2(). + * \param map12 The mapping from the first graph to the second. + * \param map21 The mapping from the second graph to the first, the + * inverse of \p map12 basically. + * \param arg This extra argument was passed to \ref + * igraph_isomorphic_function_vf2() when it was called. + * \return Boolean, whether to continue with the isomorphism search. + */ + + +typedef igraph_bool_t igraph_isohandler_t(const igraph_vector_t *map12, + const igraph_vector_t *map21, void *arg); + +/** + * \typedef igraph_isocompat_t + * Callback type, called to check whether two vertices or edges are compatible + * + * VF2 (subgraph) isomorphism functions can be restricted by defining + * relations on the vertices and/or edges of the graphs, and then checking + * whether the vertices (edges) match according to these relations. + * + * This feature is implemented by two callbacks, one for + * vertices, one for edges. Every time igraph tries to match a vertex (edge) + * of the first (sub)graph to a vertex of the second graph, the vertex + * (edge) compatibility callback is called. The callback returns a + * logical value, giving whether the two vertices match. + * + * Both callback functions are of type \c igraph_isocompat_t. + * \param graph1 The first graph. + * \param graph2 The second graph. + * \param g1_num The id of a vertex or edge in the first graph. + * \param g2_num The id of a vertex or edge in the second graph. + * \param arg Extra argument to pass to the callback functions. + * \return Logical scalar, whether vertex (or edge) \p g1_num in \p graph1 + * is compatible with vertex (or edge) \p g2_num in \p graph2. + */ + +typedef igraph_bool_t igraph_isocompat_t(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_integer_t g1_num, + const igraph_integer_t g2_num, + void *arg); + +DECLDIR int igraph_isomorphic_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_bool_t *iso, + igraph_vector_t *map12, + igraph_vector_t *map21, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +DECLDIR int igraph_isomorphic_function_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_t *map12, igraph_vector_t *map21, + igraph_isohandler_t *isohandler_fn, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +DECLDIR int igraph_count_isomorphisms_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_integer_t *count, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +DECLDIR int igraph_get_isomorphisms_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_ptr_t *maps, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); + +DECLDIR int igraph_subisomorphic_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_bool_t *iso, + igraph_vector_t *map12, + igraph_vector_t *map21, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +DECLDIR int igraph_subisomorphic_function_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_t *map12, + igraph_vector_t *map21, + igraph_isohandler_t *isohandler_fn, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +DECLDIR int igraph_count_subisomorphisms_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_integer_t *count, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); +DECLDIR int igraph_get_subisomorphisms_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_ptr_t *maps, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg); + +/* BLISS family */ +/** + * \struct igraph_bliss_info_t + * Information about a BLISS run + * + * Some secondary information found by the BLISS algorithm is stored + * here. It is useful if you wany to study the internal working of the + * algorithm. + * \member nof_nodes The number of nodes in the search tree. + * \member nof_leaf_nodes The number of leaf nodes in the search tree. + * \member nof_bad_nodes Number of bad nodes. + * \member nof_canupdates Number of canrep updates. + * \member nof_generators Number of generators of the automorphism group. + * \member max_level Maximum level. + * \member group_size The size of the automorphism group of the graph, + * given as a string. It should be deallocated via + * \ref igraph_free() if not needed any more. + * + * See http://www.tcs.hut.fi/Software/bliss/index.html + * for details about the algorithm and these parameters. + */ +typedef struct igraph_bliss_info_t { + unsigned long nof_nodes; + unsigned long nof_leaf_nodes; + unsigned long nof_bad_nodes; + unsigned long nof_canupdates; + unsigned long nof_generators; + unsigned long max_level; + char *group_size; +} igraph_bliss_info_t; + +/** + * \typedef igraph_bliss_sh_t + * Splitting heuristics for BLISS + * + * \enumval IGRAPH_BLISS_F First non-singleton cell. + * \enumval IGRAPH_BLISS_FL First largest non-singleton cell. + * \enumval IGRAPH_BLISS_FS First smallest non-singleton cell. + * \enumval IGRAPH_BLISS_FM First maximally non-trivially connected + * non-singleton cell. + * \enumval IGRAPH_BLISS_FLM Largest maximally non-trivially connected + * non-singleton cell. + * \enumval IGRAPH_BLISS_FSM Smallest maximally non-trivially + * connected non-singletion cell. + */ + +typedef enum { IGRAPH_BLISS_F = 0, IGRAPH_BLISS_FL, + IGRAPH_BLISS_FS, IGRAPH_BLISS_FM, + IGRAPH_BLISS_FLM, IGRAPH_BLISS_FSM + } igraph_bliss_sh_t; + +DECLDIR int igraph_canonical_permutation(const igraph_t *graph, const igraph_vector_int_t *colors, igraph_vector_t *labeling, + igraph_bliss_sh_t sh, igraph_bliss_info_t *info); +DECLDIR int igraph_isomorphic_bliss(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *colors1, const igraph_vector_int_t *colors2, + igraph_bool_t *iso, igraph_vector_t *map12, + igraph_vector_t *map21, + igraph_bliss_sh_t sh, + igraph_bliss_info_t *info1, igraph_bliss_info_t *info2); + +DECLDIR int igraph_automorphisms(const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_bliss_sh_t sh, igraph_bliss_info_t *info); + +DECLDIR int igraph_automorphism_group(const igraph_t *graph, const igraph_vector_int_t *colors, igraph_vector_ptr_t *generators, + igraph_bliss_sh_t sh, igraph_bliss_info_t *info); + +/* Functions for 3-4 graphs */ +DECLDIR int igraph_isomorphic_34(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso); +DECLDIR int igraph_isoclass(const igraph_t *graph, igraph_integer_t *isoclass); +DECLDIR int igraph_isoclass_subgraph(const igraph_t *graph, igraph_vector_t *vids, + igraph_integer_t *isoclass); +DECLDIR int igraph_isoclass_create(igraph_t *graph, igraph_integer_t size, + igraph_integer_t number, igraph_bool_t directed); + + + + +__END_DECLS + +#endif diff --git a/include/igraph_transitivity.h b/include/igraph_transitivity.h new file mode 100644 index 0000000..687d216 --- /dev/null +++ b/include/igraph_transitivity.h @@ -0,0 +1,64 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_TRANSITIVITY_H +#define IGRAPH_TRANSITIVITY_H + +#include "igraph_decls.h" +#include "igraph_datatype.h" +#include "igraph_constants.h" +#include "igraph_iterators.h" + +__BEGIN_DECLS + +DECLDIR int igraph_transitivity_undirected(const igraph_t *graph, + igraph_real_t *res, + igraph_transitivity_mode_t mode); +DECLDIR int igraph_transitivity_local_undirected(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode); +DECLDIR int igraph_transitivity_local_undirected1(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode); +DECLDIR int igraph_transitivity_local_undirected2(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode); +DECLDIR int igraph_transitivity_local_undirected4(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode); +DECLDIR int igraph_transitivity_avglocal_undirected(const igraph_t *graph, + igraph_real_t *res, + igraph_transitivity_mode_t mode); +DECLDIR int igraph_transitivity_barrat(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + const igraph_vector_t *weights, + const igraph_transitivity_mode_t mode); + +__END_DECLS + +#endif diff --git a/include/igraph_types.h b/include/igraph_types.h new file mode 100644 index 0000000..a1ee9d8 --- /dev/null +++ b/include/igraph_types.h @@ -0,0 +1,91 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_TYPES_H +#define IGRAPH_TYPES_H + +#include "igraph_decls.h" + +__BEGIN_DECLS + +#ifndef _GNU_SOURCE + #define _GNU_SOURCE 1 +#endif + +#include "igraph_error.h" +#include +#include +#include + +/* This is to eliminate gcc warnings about unused parameters */ +#define IGRAPH_UNUSED(x) (void)(x) + +typedef int igraph_integer_t; +typedef double igraph_real_t; +typedef int igraph_bool_t; + +/* Replacements for printf that print doubles in the same way on all platforms + * (even for NaN and infinities) */ +DECLDIR int igraph_real_printf(igraph_real_t val); +DECLDIR int igraph_real_fprintf(FILE *file, igraph_real_t val); +DECLDIR int igraph_real_snprintf(char* str, size_t size, igraph_real_t val); + +/* Replacements for printf that print doubles in the same way on all platforms + * (even for NaN and infinities) with the largest possible precision */ +DECLDIR int igraph_real_printf_precise(igraph_real_t val); +DECLDIR int igraph_real_fprintf_precise(FILE *file, igraph_real_t val); +DECLDIR int igraph_real_snprintf_precise(char* str, size_t size, igraph_real_t val); + +/* igraph_i_fdiv is needed here instead of in igraph_math.h because + * some constants use it */ +double igraph_i_fdiv(const double a, const double b); + +#if defined(INFINITY) + #define IGRAPH_INFINITY INFINITY + #define IGRAPH_POSINFINITY INFINITY + #define IGRAPH_NEGINFINITY (-INFINITY) +#else + #define IGRAPH_INFINITY (igraph_i_fdiv(1.0, 0.0)) + #define IGRAPH_POSINFINITY (igraph_i_fdiv(1.0, 0.0)) + #define IGRAPH_NEGINFINITY (igraph_i_fdiv(-1.0, 0.0)) +#endif + +DECLDIR int igraph_finite(double x); +#define IGRAPH_FINITE(x) igraph_finite(x) + +DECLDIR int igraph_is_nan(double x); +DECLDIR int igraph_is_inf(double x); +DECLDIR int igraph_is_posinf(double x); +DECLDIR int igraph_is_neginf(double x); + +#if defined(NAN) + #define IGRAPH_NAN NAN +#elif defined(INFINITY) + #define IGRAPH_NAN (INFINITY/INFINITY) +#else + #define IGRAPH_NAN (igraph_i_fdiv(0.0, 0.0)) +#endif + +__END_DECLS + +#endif diff --git a/include/igraph_vector.h b/include/igraph_vector.h new file mode 100644 index 0000000..463e9a5 --- /dev/null +++ b/include/igraph_vector.h @@ -0,0 +1,176 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_VECTOR_H +#define IGRAPH_VECTOR_H + +#include "igraph_decls.h" +#include "igraph_types.h" +#include "igraph_complex.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Flexible vector */ +/* -------------------------------------------------- */ + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_FLOAT +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_FLOAT + +#define BASE_LONG +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_LONG + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_INT +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_COMPLEX +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_pmt_off.h" +#undef BASE_COMPLEX + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_FLOAT +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_FLOAT + +#define BASE_LONG +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_LONG + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_INT +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_COMPLEX +#include "igraph_pmt.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_COMPLEX + +/* -------------------------------------------------- */ +/* Helper macros */ +/* -------------------------------------------------- */ + +#ifndef IGRAPH_VECTOR_NULL + #define IGRAPH_VECTOR_NULL { 0,0,0 } +#endif + +#ifndef IGRAPH_VECTOR_INIT_FINALLY +#define IGRAPH_VECTOR_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_destroy, v); } while (0) +#endif +#ifndef IGRAPH_VECTOR_BOOL_INIT_FINALLY +#define IGRAPH_VECTOR_BOOL_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_bool_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_bool_destroy, v); } while (0) +#endif +#ifndef IGRAPH_VECTOR_INT_INIT_FINALLY +#define IGRAPH_VECTOR_INT_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_int_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_int_destroy, v); } while (0) +#endif +#ifndef IGRAPH_VECTOR_LONG_INIT_FINALLY +#define IGRAPH_VECTOR_LONG_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_long_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_long_destroy, v); } while (0) +#endif + +/* -------------------------------------------------- */ +/* Type-specific vector functions */ +/* -------------------------------------------------- */ + +DECLDIR int igraph_vector_floor(const igraph_vector_t *from, igraph_vector_long_t *to); +DECLDIR int igraph_vector_round(const igraph_vector_t *from, igraph_vector_long_t *to); + +DECLDIR igraph_bool_t igraph_vector_e_tol(const igraph_vector_t *lhs, + const igraph_vector_t *rhs, + igraph_real_t tol); + +DECLDIR int igraph_vector_zapsmall(igraph_vector_t *v, igraph_real_t tol); + +/* These are for internal use only */ +int igraph_vector_order(const igraph_vector_t* v, const igraph_vector_t *v2, + igraph_vector_t* res, igraph_real_t maxval); +int igraph_vector_order1(const igraph_vector_t* v, + igraph_vector_t* res, igraph_real_t maxval); +int igraph_vector_order1_int(const igraph_vector_t* v, + igraph_vector_int_t* res, igraph_real_t maxval); +int igraph_vector_order2(igraph_vector_t *v); +int igraph_vector_rank(const igraph_vector_t *v, igraph_vector_t *res, + long int nodes); + +__END_DECLS + +#endif diff --git a/include/igraph_vector_pmt.h b/include/igraph_vector_pmt.h new file mode 100644 index 0000000..045c8e4 --- /dev/null +++ b/include/igraph_vector_pmt.h @@ -0,0 +1,265 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/*--------------------*/ +/* Allocation */ +/*--------------------*/ + +DECLDIR int FUNCTION(igraph_vector, init)(TYPE(igraph_vector)* v, long int size); +DECLDIR int FUNCTION(igraph_vector, init_copy)(TYPE(igraph_vector)* v, + const BASE* data, long int length); +DECLDIR int FUNCTION(igraph_vector, init_seq)(TYPE(igraph_vector)*v, BASE from, BASE to); +DECLDIR int FUNCTION(igraph_vector, copy)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from); +DECLDIR void FUNCTION(igraph_vector, destroy)(TYPE(igraph_vector)* v); + +DECLDIR long int FUNCTION(igraph_vector, capacity)(const TYPE(igraph_vector)*v); + +/*--------------------*/ +/* Accessing elements */ +/*--------------------*/ + +#ifndef VECTOR +/** + * \ingroup vector + * \define VECTOR + * \brief Accessing an element of a vector. + * + * Usage: + * \verbatim VECTOR(v)[0] \endverbatim + * to access the first element of the vector, you can also use this in + * assignments, like: + * \verbatim VECTOR(v)[10]=5; \endverbatim + * + * Note that there are no range checks right now. + * This functionality might be redefined later as a real function + * instead of a #define. + * \param v The vector object. + * + * Time complexity: O(1). + */ +#define VECTOR(v) ((v).stor_begin) +#endif + +DECLDIR BASE FUNCTION(igraph_vector, e)(const TYPE(igraph_vector)* v, long int pos); +BASE* FUNCTION(igraph_vector, e_ptr)(const TYPE(igraph_vector)* v, long int pos); +DECLDIR void FUNCTION(igraph_vector, set)(TYPE(igraph_vector)* v, long int pos, BASE value); +DECLDIR BASE FUNCTION(igraph_vector, tail)(const TYPE(igraph_vector) *v); + +/*-----------------------*/ +/* Initializing elements */ +/*-----------------------*/ + +DECLDIR void FUNCTION(igraph_vector, null)(TYPE(igraph_vector)* v); +DECLDIR void FUNCTION(igraph_vector, fill)(TYPE(igraph_vector)* v, BASE e); + +/*-----------------------*/ +/* Vector views */ +/*-----------------------*/ + +DECLDIR const TYPE(igraph_vector) *FUNCTION(igraph_vector, view)(const TYPE(igraph_vector) *v, + const BASE *data, + long int length); + +/*-----------------------*/ +/* Copying vectors */ +/*-----------------------*/ + +DECLDIR void FUNCTION(igraph_vector, copy_to)(const TYPE(igraph_vector) *v, BASE* to); +DECLDIR int FUNCTION(igraph_vector, update)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from); +DECLDIR int FUNCTION(igraph_vector, append)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from); +DECLDIR int FUNCTION(igraph_vector, swap)(TYPE(igraph_vector) *v1, TYPE(igraph_vector) *v2); + +/*-----------------------*/ +/* Exchanging elements */ +/*-----------------------*/ + +DECLDIR int FUNCTION(igraph_vector, swap_elements)(TYPE(igraph_vector) *v, + long int i, long int j); +DECLDIR int FUNCTION(igraph_vector, reverse)(TYPE(igraph_vector) *v); +DECLDIR int FUNCTION(igraph_vector, shuffle)(TYPE(igraph_vector) *v); + +/*-----------------------*/ +/* Vector operations */ +/*-----------------------*/ + +DECLDIR void FUNCTION(igraph_vector, add_constant)(TYPE(igraph_vector) *v, BASE plus); +DECLDIR void FUNCTION(igraph_vector, scale)(TYPE(igraph_vector) *v, BASE by); +DECLDIR int FUNCTION(igraph_vector, add)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2); +DECLDIR int FUNCTION(igraph_vector, sub)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2); +DECLDIR int FUNCTION(igraph_vector, mul)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2); +DECLDIR int FUNCTION(igraph_vector, div)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2); +DECLDIR int FUNCTION(igraph_vector, cumsum)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from); + +#ifndef NOABS + DECLDIR int FUNCTION(igraph_vector, abs)(TYPE(igraph_vector) *v); +#endif + +/*------------------------------*/ +/* Comparison */ +/*------------------------------*/ + +DECLDIR igraph_bool_t FUNCTION(igraph_vector, all_e)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); +DECLDIR igraph_bool_t FUNCTION(igraph_vector, all_l)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); +DECLDIR igraph_bool_t FUNCTION(igraph_vector, all_g)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); +DECLDIR igraph_bool_t FUNCTION(igraph_vector, all_le)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); +DECLDIR igraph_bool_t FUNCTION(igraph_vector, all_ge)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); + +/*------------------------------*/ +/* Finding minimum and maximum */ +/*------------------------------*/ + +DECLDIR BASE FUNCTION(igraph_vector, min)(const TYPE(igraph_vector)* v); +DECLDIR BASE FUNCTION(igraph_vector, max)(const TYPE(igraph_vector)* v); +DECLDIR long int FUNCTION(igraph_vector, which_min)(const TYPE(igraph_vector)* v); +DECLDIR long int FUNCTION(igraph_vector, which_max)(const TYPE(igraph_vector)* v); +DECLDIR int FUNCTION(igraph_vector, minmax)(const TYPE(igraph_vector) *v, + BASE *min, BASE *max); +DECLDIR int FUNCTION(igraph_vector, which_minmax)(const TYPE(igraph_vector) *v, + long int *which_min, long int *which_max); + +/*-------------------*/ +/* Vector properties */ +/*-------------------*/ + +DECLDIR igraph_bool_t FUNCTION(igraph_vector, empty) (const TYPE(igraph_vector)* v); +DECLDIR long int FUNCTION(igraph_vector, size) (const TYPE(igraph_vector)* v); +DECLDIR igraph_bool_t FUNCTION(igraph_vector, isnull)(const TYPE(igraph_vector) *v); +DECLDIR BASE FUNCTION(igraph_vector, sum)(const TYPE(igraph_vector) *v); +DECLDIR igraph_real_t FUNCTION(igraph_vector, sumsq)(const TYPE(igraph_vector) *v); +DECLDIR BASE FUNCTION(igraph_vector, prod)(const TYPE(igraph_vector) *v); +DECLDIR igraph_bool_t FUNCTION(igraph_vector, isininterval)(const TYPE(igraph_vector) *v, + BASE low, BASE high); +DECLDIR igraph_bool_t FUNCTION(igraph_vector, any_smaller)(const TYPE(igraph_vector) *v, + BASE limit); +DECLDIR igraph_bool_t FUNCTION(igraph_vector, is_equal)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs); +DECLDIR igraph_real_t FUNCTION(igraph_vector, maxdifference)(const TYPE(igraph_vector) *m1, + const TYPE(igraph_vector) *m2); + +/*------------------------*/ +/* Searching for elements */ +/*------------------------*/ + +DECLDIR igraph_bool_t FUNCTION(igraph_vector, contains)(const TYPE(igraph_vector) *v, BASE e); +DECLDIR igraph_bool_t FUNCTION(igraph_vector, search)(const TYPE(igraph_vector) *v, + long int from, BASE what, + long int *pos); +DECLDIR igraph_bool_t FUNCTION(igraph_vector, binsearch)(const TYPE(igraph_vector) *v, + BASE what, long int *pos); +DECLDIR igraph_bool_t FUNCTION(igraph_vector, binsearch2)(const TYPE(igraph_vector) *v, + BASE what); + +/*------------------------*/ +/* Resizing operations */ +/*------------------------*/ + +DECLDIR void FUNCTION(igraph_vector, clear)(TYPE(igraph_vector)* v); +DECLDIR int FUNCTION(igraph_vector, resize)(TYPE(igraph_vector)* v, long int newsize); +DECLDIR int FUNCTION(igraph_vector, resize_min)(TYPE(igraph_vector)*v); +DECLDIR int FUNCTION(igraph_vector, reserve)(TYPE(igraph_vector)* v, long int size); +DECLDIR int FUNCTION(igraph_vector, push_back)(TYPE(igraph_vector)* v, BASE e); +DECLDIR BASE FUNCTION(igraph_vector, pop_back)(TYPE(igraph_vector)* v); +DECLDIR int FUNCTION(igraph_vector, insert)(TYPE(igraph_vector) *v, long int pos, BASE value); +DECLDIR void FUNCTION(igraph_vector, remove)(TYPE(igraph_vector) *v, long int elem); +DECLDIR void FUNCTION(igraph_vector, remove_section)(TYPE(igraph_vector) *v, + long int from, long int to); + +/*-----------*/ +/* Sorting */ +/*-----------*/ + +DECLDIR void FUNCTION(igraph_vector, sort)(TYPE(igraph_vector) *v); +DECLDIR long int FUNCTION(igraph_vector, qsort_ind)(TYPE(igraph_vector) *v, + igraph_vector_t *inds, igraph_bool_t descending); + +/*-----------*/ +/* Printing */ +/*-----------*/ + +int FUNCTION(igraph_vector, print)(const TYPE(igraph_vector) *v); +int FUNCTION(igraph_vector, printf)(const TYPE(igraph_vector) *v, + const char *format); +int FUNCTION(igraph_vector, fprint)(const TYPE(igraph_vector) *v, FILE *file); + +#ifdef BASE_COMPLEX + +DECLDIR int igraph_vector_complex_real(const igraph_vector_complex_t *v, + igraph_vector_t *real); +DECLDIR int igraph_vector_complex_imag(const igraph_vector_complex_t *v, + igraph_vector_t *imag); +DECLDIR int igraph_vector_complex_realimag(const igraph_vector_complex_t *v, + igraph_vector_t *real, + igraph_vector_t *imag); +DECLDIR int igraph_vector_complex_create(igraph_vector_complex_t *v, + const igraph_vector_t *real, + const igraph_vector_t *imag); +DECLDIR int igraph_vector_complex_create_polar(igraph_vector_complex_t *v, + const igraph_vector_t *r, + const igraph_vector_t *theta); + +#endif + +/* ----------------------------------------------------------------------------*/ +/* For internal use only, may be removed, rewritten ... */ +/* ----------------------------------------------------------------------------*/ + +int FUNCTION(igraph_vector, init_real)(TYPE(igraph_vector)*v, int no, ...); +int FUNCTION(igraph_vector, init_int)(TYPE(igraph_vector)*v, int no, ...); +int FUNCTION(igraph_vector, init_real_end)(TYPE(igraph_vector)*v, BASE endmark, ...); +int FUNCTION(igraph_vector, init_int_end)(TYPE(igraph_vector)*v, int endmark, ...); + +int FUNCTION(igraph_vector, move_interval)(TYPE(igraph_vector) *v, + long int begin, long int end, long int to); +int FUNCTION(igraph_vector, move_interval2)(TYPE(igraph_vector) *v, + long int begin, long int end, long int to); +void FUNCTION(igraph_vector, permdelete)(TYPE(igraph_vector) *v, + const igraph_vector_t *index, + long int nremove); +int FUNCTION(igraph_vector, filter_smaller)(TYPE(igraph_vector) *v, BASE elem); +int FUNCTION(igraph_vector, get_interval)(const TYPE(igraph_vector) *v, + TYPE(igraph_vector) *res, + long int from, long int to); +int FUNCTION(igraph_vector, difference_sorted)(const TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2, TYPE(igraph_vector) *result); +int FUNCTION(igraph_vector, intersect_sorted)(const TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2, TYPE(igraph_vector) *result); + +int FUNCTION(igraph_vector, index)(const TYPE(igraph_vector) *v, + TYPE(igraph_vector) *newv, + const igraph_vector_t *idx); + +int FUNCTION(igraph_vector, index_int)(TYPE(igraph_vector) *v, + const igraph_vector_int_t *idx); diff --git a/include/igraph_vector_ptr.h b/include/igraph_vector_ptr.h new file mode 100644 index 0000000..eb8d551 --- /dev/null +++ b/include/igraph_vector_ptr.h @@ -0,0 +1,100 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_VECTOR_PTR_H +#define IGRAPH_VECTOR_PTR_H + +#include "igraph_decls.h" +#include "igraph_vector.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Flexible vector, storing pointers */ +/* -------------------------------------------------- */ + +/** + * Vector, storing pointers efficiently + * \ingroup internal + * + */ +typedef struct s_vector_ptr { + void** stor_begin; + void** stor_end; + void** end; + igraph_finally_func_t* item_destructor; +} igraph_vector_ptr_t; + +#define IGRAPH_VECTOR_PTR_NULL { 0,0,0,0 } +#define IGRAPH_VECTOR_PTR_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_vector_ptr_init(v, size)); \ + IGRAPH_FINALLY(igraph_vector_ptr_destroy, v); } while (0) + +DECLDIR int igraph_vector_ptr_init (igraph_vector_ptr_t* v, long int size); +DECLDIR int igraph_vector_ptr_init_copy (igraph_vector_ptr_t* v, void** data, long int length); +DECLDIR const igraph_vector_ptr_t *igraph_vector_ptr_view (const igraph_vector_ptr_t *v, + void *const *data, long int length); +DECLDIR void igraph_vector_ptr_destroy (igraph_vector_ptr_t* v); +DECLDIR void igraph_vector_ptr_free_all (igraph_vector_ptr_t* v); +DECLDIR void igraph_vector_ptr_destroy_all (igraph_vector_ptr_t* v); +DECLDIR int igraph_vector_ptr_reserve (igraph_vector_ptr_t* v, long int size); +DECLDIR igraph_bool_t igraph_vector_ptr_empty (const igraph_vector_ptr_t* v); +DECLDIR long int igraph_vector_ptr_size (const igraph_vector_ptr_t* v); +DECLDIR void igraph_vector_ptr_clear (igraph_vector_ptr_t* v); +DECLDIR void igraph_vector_ptr_null (igraph_vector_ptr_t* v); +DECLDIR int igraph_vector_ptr_push_back (igraph_vector_ptr_t* v, void* e); +DECLDIR int igraph_vector_ptr_append (igraph_vector_ptr_t *to, + const igraph_vector_ptr_t *from); +DECLDIR void *igraph_vector_ptr_pop_back (igraph_vector_ptr_t *v); +DECLDIR int igraph_vector_ptr_insert(igraph_vector_ptr_t *v, long int pos, void* e); +DECLDIR void* igraph_vector_ptr_e (const igraph_vector_ptr_t* v, long int pos); +DECLDIR void igraph_vector_ptr_set (igraph_vector_ptr_t* v, long int pos, void* value); +DECLDIR int igraph_vector_ptr_resize(igraph_vector_ptr_t* v, long int newsize); +DECLDIR void igraph_vector_ptr_copy_to(const igraph_vector_ptr_t *v, void** to); +DECLDIR int igraph_vector_ptr_copy(igraph_vector_ptr_t *to, const igraph_vector_ptr_t *from); +DECLDIR void igraph_vector_ptr_remove(igraph_vector_ptr_t *v, long int pos); +DECLDIR void igraph_vector_ptr_sort(igraph_vector_ptr_t *v, int(*compar)(const void*, const void*)); +DECLDIR int igraph_vector_ptr_index_int(igraph_vector_ptr_t *v, + const igraph_vector_int_t *idx); + +DECLDIR igraph_finally_func_t* igraph_vector_ptr_get_item_destructor(const igraph_vector_ptr_t *v); +DECLDIR igraph_finally_func_t* igraph_vector_ptr_set_item_destructor(igraph_vector_ptr_t *v, + igraph_finally_func_t *func); + +/** + * \define IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR + * \brief Sets the item destructor for this pointer vector (macro version). + * + * This macro is expanded to \ref igraph_vector_ptr_set_item_destructor(), the + * only difference is that the second argument is automatically cast to an + * \c igraph_finally_func_t*. The cast is necessary in most cases as the + * destructor functions we use (such as \ref igraph_vector_destroy()) take a + * pointer to some concrete igraph data type, while \c igraph_finally_func_t + * expects \c void* + */ +#define IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(v, func) \ + igraph_vector_ptr_set_item_destructor((v), (igraph_finally_func_t*)(func)) + +__END_DECLS + +#endif diff --git a/include/igraph_vector_type.h b/include/igraph_vector_type.h new file mode 100644 index 0000000..84990f6 --- /dev/null +++ b/include/igraph_vector_type.h @@ -0,0 +1,34 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/** + * Vector, dealing with arrays efficiently. + * \ingroup types + */ + +typedef struct TYPE(igraph_vector) { + BASE* stor_begin; + BASE* stor_end; + BASE* end; +} TYPE(igraph_vector); + diff --git a/include/igraph_version.h.in b/include/igraph_version.h.in new file mode 100644 index 0000000..919df53 --- /dev/null +++ b/include/igraph_version.h.in @@ -0,0 +1,46 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_VERSION_H +#define IGRAPH_VERSION_H + +#include "igraph_decls.h" + +__BEGIN_DECLS + +#define IGRAPH_VERSION "@PACKAGE_VERSION@" +#define IGRAPH_VERSION_MAJOR @PACKAGE_VERSION_MAJOR@ +#define IGRAPH_VERSION_MINOR @PACKAGE_VERSION_MINOR@ +#define IGRAPH_VERSION_PATCH @PACKAGE_VERSION_PATCH@ +#define IGRAPH_VERSION_PRERELEASE "@PACKAGE_VERSION_PRERELEASE@" + +int igraph_version(const char **version_string, + int *major, + int *minor, + int *subminor); + +__END_DECLS + +#endif + + diff --git a/include/igraph_visitor.h b/include/igraph_visitor.h new file mode 100644 index 0000000..e4c9ec8 --- /dev/null +++ b/include/igraph_visitor.h @@ -0,0 +1,132 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_VISITOR_H +#define IGRAPH_VISITOR_H + +#include "igraph_decls.h" +#include "igraph_constants.h" +#include "igraph_types.h" +#include "igraph_datatype.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Visitor-like functions */ +/* -------------------------------------------------- */ + +/** + * \typedef igraph_bfshandler_t + * Callback type for BFS function + * + * \ref igraph_bfs() is able to call a callback function, whenever a + * new vertex is found, while doing the breadth-first search. This + * callback function must be of type \c igraph_bfshandler_t. It has + * the following arguments: + * \param graph The graph that that algorithm is working on. Of course + * this must not be modified. + * \param vid The id of the vertex just found by the breadth-first + * search. + * \param pred The id of the previous vertex visited. It is -1 if + * there is no previous vertex, because the current vertex is the root + * is a search tree. + * \param succ The id of the next vertex that will be visited. It is + * -1 if there is no next vertex, because the current vertex is the + * last one in a search tree. + * \param rank The rank of the current vertex, it starts with zero. + * \param dist The distance (number of hops) of the current vertex + * from the root of the current search tree. + * \param extra The extra argument that was passed to \ref + * igraph_bfs(). + * \return A logical value, if TRUE (=non-zero), that is interpreted + * as a request to stop the BFS and return to the caller. If a BFS + * is terminated like this, then all elements of the result vectors + * that were not yet calculated at the point of the termination + * contain NaN. + * + * \sa \ref igraph_bfs() + */ + +typedef igraph_bool_t igraph_bfshandler_t(const igraph_t *graph, + igraph_integer_t vid, + igraph_integer_t pred, + igraph_integer_t succ, + igraph_integer_t rank, + igraph_integer_t dist, + void *extra); + +DECLDIR int igraph_bfs(const igraph_t *graph, + igraph_integer_t root, const igraph_vector_t *roots, + igraph_neimode_t mode, igraph_bool_t unreachable, + const igraph_vector_t *restricted, + igraph_vector_t *order, igraph_vector_t *rank, + igraph_vector_t *father, + igraph_vector_t *pred, igraph_vector_t *succ, + igraph_vector_t *dist, igraph_bfshandler_t *callback, + void *extra); + +int igraph_i_bfs(igraph_t *graph, igraph_integer_t vid, igraph_neimode_t mode, + igraph_vector_t *vids, igraph_vector_t *layers, + igraph_vector_t *parents); + +/** + * \function igraph_dfshandler_t + * Callback type for the DFS function + * + * \ref igraph_dfs() is able to call a callback function, whenever a + * new vertex is discovered, and/or whenever a subtree is + * completed. These callbacks must be of type \c + * igraph_dfshandler_t. They have the following arguments: + * \param graph The graph that that algorithm is working on. Of course + * this must not be modified. + * \param vid The id of the vertex just found by the depth-first + * search. + * \param dist The distance (number of hops) of the current vertex + * from the root of the current search tree. + * \param extra The extra argument that was passed to \ref + * igraph_dfs(). + * \return A logical value, if TRUE (=non-zero), that is interpreted + * as a request to stop the DFS and return to the caller. If a DFS + * is terminated like this, then all elements of the result vectors + * that were not yet calculated at the point of the termination + * contain NaN. + * + * \sa \ref igraph_dfs() + */ + +typedef igraph_bool_t igraph_dfshandler_t(const igraph_t *graph, + igraph_integer_t vid, + igraph_integer_t dist, + void *extra); + +DECLDIR int igraph_dfs(const igraph_t *graph, igraph_integer_t root, + igraph_neimode_t mode, igraph_bool_t unreachable, + igraph_vector_t *order, + igraph_vector_t *order_out, igraph_vector_t *father, + igraph_vector_t *dist, igraph_dfshandler_t *in_callback, + igraph_dfshandler_t *out_callback, + void *extra); + +__END_DECLS + +#endif diff --git a/interfaces/R/README b/interfaces/R/README new file mode 100644 index 0000000..9da2c32 --- /dev/null +++ b/interfaces/R/README @@ -0,0 +1,2 @@ +The source code of the igraph R package has moved to +https://github.com/igraph/rigraph diff --git a/interfaces/functions.def b/interfaces/functions.def new file mode 100644 index 0000000..79a33fa --- /dev/null +++ b/interfaces/functions.def @@ -0,0 +1,2100 @@ +####################################### +# The basic interface +####################################### + +igraph_empty: + PARAMS: OUT GRAPH graph, INTEGER n=0, BOOLEAN directed=True + IGNORE: RR + +igraph_add_edges: + PARAMS: INOUT GRAPH graph, VECTOR edges, ATTRIBUTES attr + NAME-R: add_edges + IGNORE: RR, RC + +igraph_add_vertices: + PARAMS: INOUT GRAPH graph, INTEGER nv, ATTRIBUTES attr + NAME-R: add.vertices + IGNORE: RR, RC, RNamespace + +igraph_delete_edges: + PARAMS: INOUT GRAPH graph, EDGESET edges + NAME-R: delete.edges + DEPS: edges ON graph + IGNORE: RR, RC, RNamespace + +igraph_delete_vertices: + PARAMS: INOUT GRAPH graph, VERTEXSET vertices + NAME-R: delete.vertices + IGNORE: RR, RC, RNamespace + +igraph_vcount: + PARAMS: GRAPH graph + NAME-R: gorder + RETURN: INTEGER + +igraph_ecount: + PARAMS: GRAPH graph + NAME-R: ecount + RETURN: INTEGER + IGNORE: RR, RC, RNamespace + +igraph_neighbors: + PARAMS: GRAPH graph, OUT VECTOR neis, INTEGER vid, NEIMODE mode=ALL + NAME-R: neighbors + IGNORE: RR, RC, RNamespace + +igraph_is_directed: + PARAMS: GRAPH graph + NAME-R: is_directed + RETURN: BOOLEAN + IGNORE: RR, RC, RNamespace + +igraph_degree: + PARAMS: GRAPH graph, OUT VECTOR res, VERTEXSET vids=ALL, NEIMODE mode=ALL, \ + BOOLEAN loops + NAME-R: degree + IGNORE: RR, RC, RNamespace + +igraph_edge: + PARAMS: GRAPH graph, INTEGER eid, OUT INTEGERPTR from, OUT INTEGERPTR to + NAME-R: get.edge + IGNORE: RR, RC, RNamespace + +igraph_edges: + PARAMS: GRAPH graph, EDGESET eids, OUT VECTOR edges + NAME-R: ends + DEPS: eids ON graph + IGNORE: RR, RC, RNamespace + +igraph_get_eid: + PARAMS: GRAPH graph, OUT INTEGERPTR eid, INTEGER from, \ + INTEGER to, BOOLEAN directed=True, BOOLEAN error=True + IGNORE: RR, RC, RNamespace + +igraph_get_eids: + PARAMS: GRAPH graph, OUT VECTOR eids, VECTOR pairs, \ + BOOLEAN directed=True, BOOLEAN error=True + IGNORE: RR, RC, RNamespace + +igraph_incident: + PARAMS: GRAPH graph, OUT VECTOR eids, INTEGER vid, NEIMODE mode=ALL + IGNORE: RR, RC, RNamespace + +####################################### +# Constructors, deterministic +####################################### + +igraph_create: + PARAMS: OUT GRAPH graph, VECTOR edges, INTEGER n=0, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_adjacency: + PARAMS: OUT GRAPH graph, MATRIX adjmatrix, ADJACENCYMODE mode=DIRECTED + IGNORE: RR, RC, RNamespace + +igraph_weighted_adjacency: + PARAMS: OUT GRAPH graph, MATRIX adjmatrix, \ + ADJACENCYMODE mode=DIRECTED, CSTRING attr="weight", \ + BOOLEAN loops + IGNORE: RR, RC, RNamespace + +igraph_star: + PARAMS: OUT GRAPH graph, INTEGER n, STARMODE mode=OUT, INTEGER center=0 + IGNORE: RR, RC, RNamespace + +igraph_lattice: + PARAMS: OUT GRAPH graph, VECTOR dimvector, INTEGER nei=1, \ + BOOLEAN directed=False, BOOLEAN mutual=False, BOOLEAN circular=False + IGNORE: RR, RC, RNamespace + +igraph_ring: + PARAMS: OUT GRAPH graph, INTEGER n, BOOLEAN directed=False, BOOLEAN mutual=False, \ + BOOLEAN circular=True + IGNORE: RR, RC, RNamespace + +igraph_tree: + PARAMS: OUT GRAPH graph, INTEGER n, INTEGER children=2, TREEMODE type=OUT + IGNORE: RR, RC, RNamespace + +igraph_full: + PARAMS: OUT GRAPH graph, INTEGER n, BOOLEAN directed=False, BOOLEAN loops=False + IGNORE: RR, RC, RNamespace + +igraph_full_citation: + PARAMS: OUT GRAPH graph, INTEGER n, BOOLEAN directed=True + GATTR-R: name IS Full citation graph + IGNORE: RR + +igraph_atlas: + PARAMS: OUT GRAPH graph, INT number=0 + IGNORE: RR, RC, RNamespace + +igraph_extended_chordal_ring: + PARAMS: OUT GRAPH graph, INTEGER nodes, MATRIX W, BOOLEAN directed=False + IGNORE: RR + +igraph_connect_neighborhood: + PARAMS: INOUT GRAPH graph, INTEGER order=2, NEIMODE mode=ALL + IGNORE: RR, RC, RNamespace + +igraph_linegraph: + PARAMS: GRAPH graph, OUT GRAPH linegraph + IGNORE: RR, RC, RNamespace + +igraph_de_bruijn: + PARAMS: OUT GRAPH graph, INTEGER m, INTEGER n + IGNORE: RR, RC, RNamespace + +igraph_kautz: + PARAMS: OUT GRAPH graph, INTEGER m, INTEGER n + IGNORE: RR, RC, RNamespace + +igraph_famous: + PARAMS: OUT GRAPH graph, CSTRING name="" + IGNORE: RR, RC, RNamespace + +igraph_lcf_vector: + PARAMS: OUT GRAPH graph, INTEGER n, VECTOR shifts, INTEGER repeats=1 + NAME-R: graph_from_lcf + GATTR-R: name IS LCF graph + +igraph_adjlist: + PARAMS: OUT GRAPH graph, ADJLIST adjlist, NEIMODE mode=OUT, \ + BOOLEAN duplicate=True + NAME-R: graph_from_adj_list + +igraph_full_bipartite: + PARAMS: OUT GRAPH graph, OUT VECTOR_BOOL_OR_0 types, INTEGER n1, \ + INTEGER n2, BOOLEAN directed=False, NEIMODE mode=ALL + IGNORE: RR + +####################################### +# Constructors, games +####################################### + +igraph_barabasi_game: + PARAMS: OUT GRAPH graph, INTEGER n, INTEGER m=1, REAL power=1.0, \ + VECTOR_OR_0 outseq, BOOLEAN outpref=False, REAL A=1.0, \ + BOOLEAN directed=True, BARABASI_ALGORITHM algo=BAG, \ + GRAPH_OR_0 start_from=0 + IGNORE: RR, RC, RNamespace + +igraph_erdos_renyi_game_gnp: + PARAMS: OUT GRAPH graph, INTEGER n, REAL p, BOOLEAN directed=False, BOOLEAN loops=False + IGNORE: RR, RC, RNamespace + +igraph_erdos_renyi_game_gnm: + PARAMS: OUT GRAPH graph, INTEGER n, REAL m, BOOLEAN directed=False, BOOLEAN loops=False + IGNORE: RR, RC, RNamespace + +igraph_degree_sequence_game: + PARAMS: OUT GRAPH graph, VECTOR out_deg, VECTOR_OR_0 in_deg, \ + DEGSEQMODE method=SIMPLE + IGNORE: RR, RC, RNamespace + +igraph_growing_random_game: + PARAMS: OUT GRAPH graph, INTEGER n, INTEGER m=1, BOOLEAN directed=False, \ + BOOLEAN citation=False + IGNORE: RR, RC, RNamespace + +igraph_barabasi_aging_game: + PARAMS: OUT GRAPH graph, INTEGER nodes, INTEGER m=1, VECTOR_OR_0 outseq, \ + BOOLEAN outpref=False, REAL pa_exp=1.0, REAL aging_exp=0.0, INTEGER aging_bin=1, \ + REAL zero_deg_appeal=1.0, REAL zero_age_appeal=0.0, REAL deg_coef=1.0, \ + REAL age_coef=1.0, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_recent_degree_game: + PARAMS: OUT GRAPH graph, INTEGER n, REAL power=1.0, INTEGER window=1, \ + INTEGER m=1, VECTOR_OR_0 outseq, BOOLEAN outpref=False, \ + REAL zero_appeal=1.0, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_recent_degree_aging_game: + PARAMS: OUT GRAPH graph, INTEGER nodes, INTEGER m=1, VECTOR_OR_0 outseq, \ + BOOLEAN outpref=False, REAL pa_exp=1.0, REAL aging_exp=0.0, INTEGER aging_bin=1, \ + INTEGER window=1, REAL zero_appeal=1.0, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_callaway_traits_game: + PARAMS: OUT GRAPH graph, INTEGER nodes, INTEGER types, \ + INTEGER edges_per_step=1, VECTOR type_dist, MATRIX pref_matrix, \ + BOOLEAN directed=False + IGNORE: RR, RC, RNamespace + +igraph_establishment_game: + PARAMS: OUT GRAPH graph, INTEGER nodes, INTEGER types, INTEGER k=1, \ + VECTOR type_dist, MATRIX pref_matrix, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_grg_game: + PARAMS: OUT GRAPH graph, INTEGER nodes, REAL radius, BOOLEAN torus=False, VECTOR_OR_0 x, VECTOR_OR_0 y + IGNORE: RR, RC, RNamespace + +igraph_preference_game: + PARAMS: OUT GRAPH graph, INTEGER nodes, INTEGER types, \ + VECTOR type_dist, BOOLEAN fixed_sizes=False, \ + MATRIX pref_matrix, OUT VECTOR node_type_vec, \ + BOOLEAN directed=False, BOOLEAN loops=False + IGNORE: RR, RC, RNamespace + +igraph_asymmetric_preference_game: + PARAMS: OUT GRAPH graph, INTEGER nodes, INTEGER types, \ + MATRIX type_dist_matrix, MATRIX pref_matrix, \ + OUT VECTOR node_type_in_vec, OUT VECTOR node_type_out_vec, \ + BOOLEAN loops=False + IGNORE: RR, RC, RNamespace + +igraph_rewire_edges: + PARAMS: INOUT GRAPH graph, REAL prob, BOOLEAN loops=False + IGNORE: RR, RC, RNamespace + +igraph_watts_strogatz_game: + PARAMS: OUT GRAPH graph, INTEGER dim, INTEGER size, INTEGER nei, \ + REAL p, BOOLEAN loops=False, BOOLEAN multiple=False + IGNORE: RR, RC, RNamespace + +igraph_lastcit_game: + PARAMS: OUT GRAPH graph, INTEGER nodes, INTEGER edges_per_node=1, \ + INTEGER agebins=1, VECTOR preference, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_cited_type_game: + PARAMS: OUT GRAPH graph, INTEGER nodes, VECTOR types, VECTOR pref, \ + INTEGER edges_per_step=1, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_citing_cited_type_game: + PARAMS: OUT GRAPH graph, INTEGER nodes, VECTOR types, MATRIX pref, \ + INTEGER edges_per_step=1, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_forest_fire_game: + PARAMS: OUT GRAPH graph, INTEGER nodes, REAL fw_prob, REAL bw_factor=1, \ + INTEGER ambs=1, BOOLEAN directed=True + NAME-R: sample_forestfire + GATTR-R: name IS Forest fire model + GATTR-PARAM-R: fw_prob, bw_factor, ambs + FLAGS: PROGRESS + +igraph_simple_interconnected_islands_game: + PARAMS: OUT GRAPH graph, INTEGER islands_n, INTEGER islands_size, \ + REAL islands_pin, INTEGER n_inter + NAME-R: sample_islands + GATTR-R: name IS Interconnected islands model + GATTR-PARAM-R: islands_n, islands_size, islands_pin, n_inter + IGNORE: RC + +igraph_static_fitness_game: + PARAMS: OUT GRAPH graph, INTEGER no_of_edges, VECTOR fitness_out, \ + VECTOR_OR_0 fitness_in=NULL, BOOLEAN loops=False, \ + BOOLEAN multiple=False + NAME-R: sample_fitness + GATTR-R: name IS Static fitness model + GATTR-PARAM-R: loops, multiple + FLAGS: PROGRESS + +igraph_static_power_law_game: + PARAMS: OUT GRAPH graph, INTEGER no_of_nodes, INTEGER no_of_edges, \ + REAL exponent_out, REAL exponent_in=-1, \ + BOOLEAN loops=False, BOOLEAN multiple=False, \ + BOOLEAN finite_size_correction=True + NAME-R: sample_fitness_pl + GATTR-R: name IS Static power law model + GATTR-PARAM-R: exponent_out, exponent_in, loops, multiple, \ + finite_size_correction + FLAGS: PROGRESS + +igraph_k_regular_game: + PARAMS: OUT GRAPH graph, INTEGER no_of_nodes, INTEGER k, \ + BOOLEAN directed=False, BOOLEAN multiple=False + NAME-R: sample_k_regular + GATTR-R: name IS k-regular graph + GATTR-PARAM-R: k + +igraph_sbm_game: + PARAMS: OUT GRAPH graph, INTEGER n, MATRIX pref_matrix, \ + VECTOR_INT block_sizes, BOOLEAN directed=False, \ + BOOLEAN loops=False + NAME-R: sample_sbm + GATTR-R: name IS Stochastic block-model + GATTR-PARAM-R: loops + +igraph_hsbm_game: + PARAMS: OUT GRAPH graph, INTEGER n, INTEGER m, \ + VECTOR rho, MATRIX C, REAL p + NAME-R: hsbm.1.game + GATTR-R: name IS Hierarchical stochastic block model + GATTR-PARAM-R: m, rho, C, p + IGNORE: RNamespace + INTERNAL: True + +igraph_hsbm_list_game: + PARAMS: OUT GRAPH graph, INTEGER n, VECTOR_INT mlist, \ + VECTORLIST rholist, MATRIXLIST Clist, REAL p + NAME-R: hsbm.list.game + GATTR-R: name IS Hierarchical stochastic block model + GATTR-PARAM-R: p + IGNORE: RNamespace + INTERNAL: True + +igraph_correlated_game: + PARAMS: GRAPH old_graph, OUT GRAPH new_graph, \ + REAL corr, REAL p=old.graph$p, VECTORM1_OR_0 permutation=NULL + NAME-R: sample_correlated_gnp + GATTR-R: name IS Correlated random graph + GATTR-PARAM-R: corr, p + DEFAULT-R: p=old.graph$p + +igraph_correlated_pair_game: + PARAMS: OUT GRAPH graph1, OUT GRAPH graph2, INT n, REAL corr, \ + REAL p, BOOLEAN directed=False, \ + VECTORM1_OR_0 permutation=NULL + NAME-R: sample_correlated_gnp_pair + +igraph_dot_product_game: + PARAMS: OUT GRAPH graph, MATRIX vecs, BOOLEAN directed=False + NAME-R: sample_dot_product + +igraph_sample_sphere_surface: + PARAMS: INTEGER dim, INTEGER n=1, REAL radius=1, \ + BOOLEAN positive=True, OUT MATRIX res + NAME-R: sample_sphere_surface + +igraph_sample_sphere_volume: + PARAMS: INTEGER dim, INTEGER n=1, REAL radius=1, \ + BOOLEAN positive=True, OUT MATRIX res + NAME-R: sample_sphere_volume + +igraph_sample_dirichlet: + PARAMS: INTEGER n, VECTOR alpha, OUT MATRIX res + NAME-R: sample_dirichlet + +####################################### +# Basic query functions +####################################### + +igraph_are_connected: + PARAMS: GRAPH graph, INTEGER v1, INTEGER v2, OUT BOOLEANPTR res + IGNORE: RR, RC, RNamespace + +####################################### +# Structural properties +####################################### + +igraph_diameter: + PARAMS: GRAPH graph, OUT INTEGERPTR res, OUT INTEGERPTR from, \ + OUT INTEGERPTR to, OUT VECTOR_OR_0 path, BOOLEAN directed=True, \ + BOOLEAN unconnected=True + IGNORE: RR, RC, RNamespace + +igraph_diameter_dijkstra: + PARAMS: GRAPH graph, EDGEWEIGHTS weights=NULL, \ + OUT INTEGERPTR res, OUT INTEGERPTR from, \ + OUT INTEGERPTR to, OUT VECTOR_OR_0 path, BOOLEAN directed=True, \ + BOOLEAN unconnected=True + DEPS: weights ON graph + IGNORE: RR, RC, RNamespace + +igraph_minimum_spanning_tree: + PARAMS: GRAPH graph, OUT VECTOR res, EDGEWEIGHTS weights=NULL + DEPS: weights ON graph + IGNORE: RR, RC, RNamespace + +igraph_minimum_spanning_tree_unweighted: + PARAMS: GRAPH graph, OUT GRAPH mst + IGNORE: RR, RC, RNamespace + +igraph_minimum_spanning_tree_prim: + PARAMS: GRAPH graph, OUT GRAPH mst, VECTOR weights + IGNORE: RR, RC, RNamespace + +igraph_closeness: + PARAMS: GRAPH graph, OUT VECTOR res, VERTEXSET vids=ALL, \ + NEIMODE mode=OUT, EDGEWEIGHTS weights=NULL, \ + BOOLEAN normalized=False + DEPS: vids ON graph, weights ON graph + NAME-R: closeness + IGNORE: RR + +igraph_closeness_estimate: + PARAMS: GRAPH graph, OUT VERTEXINDEX res, VERTEXSET vids=ALL, \ + NEIMODE mode=OUT, REAL cutoff, EDGEWEIGHTS weights=NULL, \ + BOOLEAN normalized=False + DEPS: vids ON graph, weights ON graph, res ON graph vids + NAME-R: estimate_closeness + IGNORE: RR + +igraph_shortest_paths: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEXSET from=ALL, \ + VERTEXSET to=ALL, NEIMODE mode=OUT + IGNORE: RR, RC, RNamespace + +igraph_get_shortest_paths: + PARAMS: GRAPH graph, OUT VERTEXSETLIST_OR_0 vertices, + OUT EDGESETLIST_OR_0 edges, VERTEX from, VERTEXSET to=ALL, \ + NEIMODE mode=OUT, OUT VECTOR_LONG_OR_0 predecessors=0, \ + OUT VECTOR_LONG_OR_0 inbound_edges=0 + DEPS: vertices ON graph, edges ON graph, from ON graph, to ON graph + IGNORE: RR, RC, RNamespace + +igraph_get_all_shortest_paths: + PARAMS: GRAPH graph, OUT VERTEXSETLIST res, OUT VECTOR nrgeo, \ + VERTEX from, VERTEXSET to, NEIMODE mode=OUT + DEPS: res ON graph, from ON graph, to ON graph + IGNORE: RR, RNamespace + +igraph_shortest_paths_dijkstra: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEXSET from=ALL, \ + VERTEXSET to=ALL, EDGEWEIGHTS weights, NEIMODE mode=OUT + IGNORE: RR, RC, RNamespace + +igraph_get_shortest_paths_dijkstra: + PARAMS: GRAPH graph, OUT VERTEXSETLIST_OR_0 vertices, \ + OUT EDGESETLIST_OR_0 edges, VERTEX from, VERTEXSET to=ALL, \ + EDGEWEIGHTS weights=NULL, NEIMODE mode=OUT, \ + OUT VECTOR_LONG_OR_0 predecessors=0, \ + OUT VECTOR_LONG_OR_0 inbound_edges=0 + DEPS: vertices ON graph, edges ON graph, from ON graph, to ON graph,\ + weights ON graph + IGNORE: RR, RC, RNamespace + +igraph_get_all_shortest_paths_dijkstra: + PARAMS: GRAPH graph, OUT VERTEXSETLIST res, OUT VECTOR nrgeo, \ + VERTEX from, VERTEXSET to=ALL, EDGEWEIGHTS weights, \ + NEIMODE mode=OUT + DEPS: weights ON graph, to ON graph, res ON graph, from ON graph + IGNORE: RR, RNamespace + +igraph_shortest_paths_bellman_ford: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEXSET from=ALL, \ + VERTEXSET to=ALL, EDGEWEIGHTS weights, NEIMODE mode=OUT + DEPS: from ON graph, to ON graph, weights ON graph + IGNORE: RR, RC, RNamespace + +igraph_shortest_paths_johnson: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEXSET from=ALL, \ + VERTEXSET to=ALL, EDGEWEIGHTS weights + IGNORE: RR, RC, RNamespace + +igraph_get_all_simple_paths: + PARAMS: GRAPH graph, OUT VERTEXSET_INT res, VERTEX from, \ + VERTEXSET to=ALL, INTEGER cutoff=-1, NEIMODE mode=OUT + DEPS: from ON graph, to ON graph, res ON graph + NAME-R: all_simple_paths + PP-R: get.all.simple.paths.pp + IGNORE: RR + +igraph_subcomponent: + PARAMS: GRAPH graph, OUT VERTEXSET res, VERTEX vid, NEIMODE mode=ALL + IGNORE: RR, RC, RNamespace + +igraph_betweenness: + PARAMS: GRAPH graph, OUT VECTOR res, VERTEXSET vids=ALL, \ + BOOLEAN directed=True, EDGEWEIGHTS weights=NULL, \ + BOOLEAN nobigint=True + IGNORE: RR, RC, RNamespace + DEPS: weights ON graph + FLAGS: PROGRESS + +igraph_betweenness_estimate: + PARAMS: GRAPH graph, OUT VERTEXINDEX res, VERTEXSET vids=ALL, \ + BOOLEAN directed=True, REAL cutoff, EDGEWEIGHTS weights=NULL, \ + BOOLEAN nobigint=True + DEPS: vids ON graph, weights ON graph, res ON graph vids + NAME-R: estimate_betweenness + FLAGS: PROGRESS + IGNORE: RR + +igraph_edge_betweenness: + PARAMS: GRAPH graph, OUT VECTOR res, BOOLEAN directed=True, \ + EDGEWEIGHTS weights=NULL + DEPS: weights ON graph + NAME-R: edge_betweenness + IGNORE: RR + +igraph_edge_betweenness_estimate: + PARAMS: GRAPH graph, OUT VECTOR res, BOOLEAN directed=True, \ + REAL cutoff, EDGEWEIGHTS weights=NULL + DEPS: weights ON graph + NAME-R: estimate_edge_betweenness + IGNORE: RR + +igraph_pagerank_old: + PARAMS: GRAPH graph, OUT VERTEXINDEX res, VERTEXSET vids=ALL, \ + BOOLEAN directed=True, INTEGER niter=1000, REAL eps=0.001, \ + REAL damping=0.85, BOOLEAN old=False + DEPS: vids ON graph, res ON graph vids + NAME-R: page_rank_old + +igraph_pagerank: + PARAMS: GRAPH graph, PAGERANKALGO algo=PRPACK, \ + OUT VERTEXINDEX vector, OUT REALPTR value, \ + VERTEXSET vids=ALL, BOOLEAN directed=True, \ + REAL damping=0.85, EDGEWEIGHTS weights=NULL, \ + INOUT PAGERANKOPT options=NULL + DEPS: vids ON graph, weights ON graph, vector ON graph vids \ + options ON algo + NAME-R: page_rank + IGNORE: RR, RC, RNamespace + +igraph_personalized_pagerank: + PARAMS: GRAPH graph, PAGERANKALGO algo=PRPACK, \ + OUT VERTEXINDEX vector, OUT REALPTR value, \ + VERTEXSET vids=ALL, BOOLEAN directed=True, \ + REAL damping=0.85, VECTOR_OR_0 personalized=NULL, \ + EDGEWEIGHTS weights=NULL, \ + INOUT PAGERANKOPT options=NULL + DEPS: vids ON graph, weights ON graph, vector ON graph vids, \ + options ON algo + NAME-R: page_rank + +igraph_rewire: + PARAMS: INOUT GRAPH rewire, INTEGER n, REWIRINGMODE mode=SIMPLE + IGNORE: RR, RC, RNamespace + +igraph_induced_subgraph: + PARAMS: GRAPH graph, OUT GRAPH res, VERTEXSET vids, SUBGRAPH_IMPL impl=AUTO + DEPS: vids ON graph + NAME-R: induced_subgraph + IGNORE: RR + +igraph_subgraph: + PARAMS: GRAPH graph, OUT GRAPH res, VERTEXSET vids + IGNORE: RR, RC, RNamespace + +igraph_subgraph_edges: + PARAMS: GRAPH graph, OUT GRAPH res, EDGESET eids, BOOLEAN delete_vertices=True + DEPS: eids ON graph + NAME-R: subgraph.edges + IGNORE: RR + +igraph_average_path_length: + PARAMS: GRAPH graph, OUT REALPTR res, BOOLEAN directed=True, BOOLEAN unconn=True + IGNORE: RR, RC, RNamespace + +igraph_path_length_hist: + PARAMS: GRAPH graph, OUT VECTOR res, OUT REALPTR unconnected, \ + BOOLEAN directed=True + NAME-R: distance_table + FLAGS: PROGRESS + +igraph_simplify: + PARAMS: INOUT GRAPH graph, BOOLEAN remove_multiple=True, \ + BOOLEAN remove_loops=True, \ + EDGE_ATTRIBUTE_COMBINATION edge_attr_comb=Default + NAME-R: simplify + +igraph_transitivity_undirected: + PARAMS: GRAPH graph, OUT REALPTR res, TRANSITIVITYMODE mode=NAN + IGNORE: RR, RC, RNamespace + +igraph_transitivity_local_undirected: + PARAMS: GRAPH graph, OUT VECTOR res, VERTEXSET vids=ALL, TRANSITIVITYMODE mode=NAN + IGNORE: RR, RC, RNamespace + +igraph_transitivity_avglocal_undirected: + PARAMS: GRAPH graph, OUT REALPTR res, TRANSITIVITYMODE mode=NAN + IGNORE: RR, RC, RNamespace + +igraph_transitivity_barrat: + PARAMS: GRAPH graph, OUT VECTOR res, VERTEXSET vids=ALL, \ + EDGEWEIGHTS weights=NULL, TRANSITIVITYMODE mode=NAN + DEPENDS: weights ON graph + IGNORE: RR, RC, RNamespace + +igraph_reciprocity: + PARAMS: GRAPH graph, OUT REALPTR res, BOOLEAN ignore_loops=True, \ + RECIP mode=Default + IGNORE: RR, RC, RNamespace + +igraph_constraint: + PARAMS: GRAPH graph, OUT VECTOR res, VERTEXSET vids=ALL, VECTOR_OR_0 weights + IGNORE: RR, RC, RNamespace + +igraph_maxdegree: + PARAMS: GRAPH graph, OUT INTEGERPTR res, VERTEXSET vids=ALL, NEIMODE mode=ALL, \ + BOOLEAN loops=True + IGNORE: RR, RC, RNamespace + +igraph_density: + PARAMS: GRAPH graph, OUT REALPTR res, BOOLEAN loops=False + IGNORE: RR, RC, RNamespace + +igraph_neighborhood_size: + PARAMS: GRAPH graph, OUT VECTOR res, VERTEXSET vids, INTEGER order, \ + NEIMODE mode=ALL, INTEGER mindist=0 + DEPS: vids ON graph + IGNORE: RR, RC, RNamespace + +igraph_neighborhood: + PARAMS: GRAPH graph, OUT VERTEXSETLIST res, \ + VERTEXSET vids, INTEGER order, \ + NEIMODE mode=ALL, INTEGER mindist=0 + DEPS res ON graph, vids ON graph + IGNORE: RR, RC, RNamespace + +igraph_neighborhood_graphs: + PARAMS: GRAPH graph, OUT GRAPHLIST res, VERTEXSET vids, \ + INTEGER order, \ + NEIMODE mode=ALL, INTEGER mindist=0 + DEPS: vids ON graph + IGNORE: RR, RC, RNamespace + +igraph_topological_sorting: + PARAMS: GRAPH graph, OUT VECTOR res, NEIMODE mode=OUT + IGNORE: RR, RC, RNamespace + +igraph_is_loop: + PARAMS: GRAPH graph, OUT VECTOR_BOOL res, EDGESET es=ALL + DEPS: es ON graph + IGNORE: RR, RC, RNamespace + +igraph_is_dag: + PARAMS: GRAPH graph, OUT BOOLEANPTR res + NAME-R: is_dag + +igraph_is_simple: + PARAMS: GRAPH graph, OUT BOOLEANPTR res + NAME-R: is_simple + +igraph_is_multiple: + PARAMS: GRAPH graph, OUT VECTOR_BOOL res, EDGESET es=ALL + DEPS: es ON graph + IGNORE: RR, RC, RNamespace + +igraph_has_multiple: + PARAMS: GRAPH graph, OUT BOOLEANPTR res + NAME-R: any_multiple + +igraph_count_multiple: + PARAMS: GRAPH graph, OUT VECTOR res, EDGESET es=ALL + DEPS: es ON graph + IGNORE: RR, RC, RNamespace + +igraph_girth: + PARAMS: GRAPH graph, OUT INTEGERPTR girth, OUT VERTEXSET circle + IGNORE: RR, RC, RNamespace + +igraph_add_edge: + PARAMS: INOUT GRAPH graph, INTEGER from, INTEGER to + IGNORE: RR, RC, RNamespace + +igraph_eigenvector_centrality: + PARAMS: GRAPH graph, OUT VERTEXINDEX vector, OUT REALPTR value, \ + BOOLEAN directed=False, BOOLEAN scale=True, \ + EDGEWEIGHTS weights=NULL, \ + INOUT ARPACKOPT options=arpack_defaults + DEPS: weights ON graph, vector ON graph + DEFAULT-R: options=arpack_defaults + NAME-R: eigen_centrality + +igraph_hub_score: + PARAMS: GRAPH graph, OUT VERTEXINDEX vector, OUT REALPTR value, \ + BOOLEAN scale=True, EDGEWEIGHTS weights=NULL, \ + INOUT ARPACKOPT options=arpack_defaults + DEFAULT-R: options=arpack_defaults + DEPS: weights ON graph, vector ON graph + NAME-R: hub_score + +igraph_authority_score: + PARAMS: GRAPH graph, OUT VERTEXINDEX vector, OUT REALPTR value, \ + BOOLEAN scale=True, EDGEWEIGHTS weights=NULL, \ + INOUT ARPACKOPT options=arpack_defaults + DEFAULT-R: options=arpack_defaults + DEPS: weights ON graph, vector ON graph + NAME-R: authority_score + +igraph_arpack_rssolve: + PARAMS: ARPFUNC fun, EXTRA extra, INOUT ARPACKOPT options=arpack_defaults, \ + NULL storage, OUT VECTOR_OR_0 values, \ + OUT MATRIX_OR_0 vectors + DEFAULT-R: options=arpack_defaults + IGNORE: RR, RC, RNamespace + +igraph_arpack_rnsolve: + PARAMS: ARPFUNC fun, EXTRA extra, INOUT ARPACKOPT options=arpack_defaults, \ + NULL storage, OUT VECTOR_OR_0 values, \ + OUT MATRIX_OR_0 vectors + DEFAULT-R: options=arpack_defaults + IGNORE: RR, RC, RNamespace + +igraph_arpack_unpack_complex: + PARAMS: INOUT MATRIX vectors, INOUT MATRIX values, INTEGER nev + NAME-R: arpack.unpack.complex + IGNORE: RNamespace, RR + +igraph_unfold_tree: + PARAMS: GRAPH graph, OUT GRAPH tree, NEIMODE mode=ALL, VECTOR roots,\ + OUT VECTORM1_OR_0 vertex_index + NAME-R: unfold_tree + IGNORE: RR + +igraph_is_mutual: + PARAMS: GRAPH graph, OUT VECTOR_BOOL res, EDGESET es=ALL + DEPS: es ON graph + NAME-R: which_mutual + +igraph_maximum_cardinality_search: + PARAMS: GRAPH graph, OUT VERTEXSET alpha, OUT VECTORM1_OR_0 alpham1 + DEPS: alpha ON graph, alpham1 ON graph + NAME-R: max_cardinality + +igraph_is_chordal: + PARAMS: GRAPH graph, VECTORM1_OR_0 alpha=NULL, VECTORM1_OR_0 alpham1=NULL, \ + OUT BOOLEANPTR_OR_0 chordal, OUT VECTORM1_OR_0 fillin, \ + OUT GRAPH_OR_0 newgraph + NAME-R: is_chordal + IGNORE: RR, RC + +igraph_avg_nearest_neighbor_degree: + PARAMS: GRAPH graph, VERTEXSET vids=ALL, \ + NEIMODE mode=ALL, NEIMODE neighbor_degree_mode=ALL, \ + OUT VERTEXINDEX_OR_0 knn, OUT VECTOR_OR_0 knnk, \ + EDGEWEIGHTS weights=NULL + DEPS: vids ON graph, weights ON graph, knn ON graph vids + NAME-R: knn + +igraph_strength: + PARAMS: GRAPH graph, OUT VERTEXINDEX res, VERTEXSET vids=ALL, \ + NEIMODE mode=ALL, BOOLEAN loops=True, EDGEWEIGHTS weights=NULL + DEPS: vids ON graph, weights ON graph, res ON graph vids + NAME-R: strength + +igraph_centralization: + PARAMS: VECTOR scores, REAL theoretical_max=0, BOOLEAN normalized=True + RETURN: REAL + NAME-R: centralize + +igraph_centralization_degree: + PARAMS: GRAPH graph, OUT VECTOR res, \ + NEIMODE mode=ALL, BOOLEAN loops=True, \ + OUT REALPTR centralization, OUT REALPTR theoretical_max, \ + BOOLEAN normalized=True + NAME-R: centr_degree + +igraph_centralization_degree_tmax: + PARAMS: GRAPH_OR_0 graph=NULL, INTEGER nodes=0, NEIMODE mode=ALL, \ + BOOLEAN loops=False, OUT REALPTR res + NAME-R: centr_degree_tmax + +igraph_centralization_betweenness: + PARAMS: GRAPH graph, OUT VECTOR res, \ + BOOLEAN directed=True, BOOLEAN nobigint=True, \ + OUT REALPTR centralization, \ + OUT REALPTR theoretical_max, \ + BOOLEAN normalized=True + NAME-R: centr_betw + +igraph_centralization_betweenness_tmax: + PARAMS: GRAPH_OR_0 graph=NULL, INTEGER nodes=0, \ + BOOLEAN directed=True, OUT REALPTR res + NAME-R: centr_betw_tmax + +igraph_centralization_closeness: + PARAMS: GRAPH graph, OUT VECTOR res, \ + NEIMODE mode=OUT, OUT REALPTR centralization, \ + OUT REALPTR theoretical_max, \ + BOOLEAN normalized=True + NAME-R: centr_clo + +igraph_centralization_closeness_tmax: + PARAMS: GRAPH_OR_0 graph=NULL, INTEGER nodes=0, \ + NEIMODE mode=OUT, OUT REALPTR res + NAME-R: centr_clo_tmax + +igraph_centralization_eigenvector_centrality: + PARAMS: GRAPH graph, OUT VECTOR vector, OUT REALPTR value, \ + BOOLEAN directed=False, BOOLEAN scale=True, \ + INOUT ARPACKOPT options=arpack_defaults, \ + OUT REALPTR centralization, OUT REALPTR theoretical_max,\ + BOOLEAN normalized=True + NAME-R: centr_eigen + +igraph_centralization_eigenvector_centrality_tmax: + PARAMS: GRAPH_OR_0 graph=NULL, INTEGER nodes=0, \ + BOOLEAN directed=False, BOOLEAN scale=True, \ + OUT REALPTR res + NAME-R: centr_eigen_tmax + +igraph_assortativity_nominal: + PARAMS: GRAPH graph, VECTORM1 types, OUT REALPTR res, \ + BOOLEAN directed=True + NAME-R: assortativity_nominal + +igraph_assortativity: + PARAMS: GRAPH graph, VECTOR types1, VECTOR_OR_0 types2=NULL, \ + OUT REALPTR res, BOOLEAN directed=True + NAME-R: assortativity + +igraph_assortativity_degree: + PARAMS: GRAPH graph, OUT REALPTR res, BOOLEAN directed=True + NAME-R: assortativity_degree + +igraph_contract_vertices: + PARAMS: INOUT GRAPH graph, VECTORM1 mapping, \ + VERTEX_ATTRIBUTE_COMBINATION vertex_attr_comb=Default + NAME-R: contract + +igraph_eccentricity: + PARAMS: GRAPH graph, OUT VERTEXINDEX res, VERTEXSET vids=ALL, \ + NEIMODE mode=ALL + DEPS: vids ON graph, res ON graph vids + NAME-R: eccentricity + +igraph_radius: + PARAMS: GRAPH graph, OUT REALPTR radius, NEIMODE mode=ALL + NAME-R: radius + +igraph_diversity: + PARAMS: GRAPH graph, EDGEWEIGHTS weights=NULL, OUT VERTEXINDEX res, \ + VERTEXSET vids=ALL + DEPS: weights ON graph, vids ON graph, res ON graph vids + NAME-R: diversity + +igraph_random_walk: + PARAMS: GRAPH graph, OUT VERTEXSET walk, VERTEX start, NEIMODE mode = OUT, \ + INTEGER steps, RWSTUCK stuck=RETURN + DEPS: start ON graph, walk ON graph + NAME-R: random_walk + IGNORE: RR + +####################################### +# Degree sequences +####################################### + +igraph_is_degree_sequence: + PARAMS: VECTOR out_deg, VECTOR_OR_0 in_deg=NULL, OUT BOOLEANPTR res + NAME-R: is_degseq + +igraph_is_graphical_degree_sequence: + PARAMS: VECTOR out_deg, VECTOR_OR_0 in_deg=NULL, OUT BOOLEANPTR res + NAME-R: is_graphical + +####################################### +# Visitors +####################################### + +igraph_bfs: + PARAMS: GRAPH graph, INTEGER root, VECTOR_OR_0 roots, \ + NEIMODE mode=OUT, BOOLEAN unreachable, \ + VECTOR_OR_0 restricted, \ + OUT VECTOR_OR_0 order, OUT VECTOR_OR_0 rank, \ + OUT VECTOR_OR_0 father, \ + OUT VECTOR_OR_0 pred, OUT VECTOR_OR_0 succ, \ + OUT VECTOR_OR_0 dist, BFS_FUNC callback, NULL extra + IGNORE: RR, RC + +igraph_dfs: + PARAMS: GRAPH graph, INTEGER root, NEIMODE mode=OUT, \ + BOOLEAN unreachable, \ + OUT VECTOR_OR_0 order, OUT VECTOR_OR_0 order_out, \ + OUT VECTOR_OR_0 dist, DFS_FUNC in_callback, \ + DFS_FUNC out_callback, NULL extra + IGNORE: RR, RC + +####################################### +# Bipartite graphs +####################################### + +igraph_bipartite_projection_size: + PARAMS: GRAPH graph, BIPARTITE_TYPES types=NULL, \ + OUT INTEGERPTR vcount1, OUT INTEGERPTR ecount1, \ + OUT INTEGERPTR vcount2, OUT INTEGERPTR ecount2 + DEPS: types ON graph + NAME-R: bipartite_projection_size + +igraph_bipartite_projection: + PARAMS: GRAPH graph, BIPARTITE_TYPES types=NULL, \ + OUT GRAPH proj1, OUT GRAPH proj2, \ + OUT VECTOR_OR_0 multiplicity1, \ + OUT VECTOR_OR_0 multiplicity2, INTEGER probe1=-1 + DEPS: types ON graph + NAME-R: bipartite.projection + IGNORE: RR, RC + +igraph_create_bipartite: + PARAMS: OUT GRAPH graph, IN VECTOR_BOOL types, \ + VECTORM1 edges, BOOLEAN directed=False + NAME-R: make_bipartite_graph + IGNORE: RR + +igraph_incidence: + PARAMS: OUT GRAPH graph, OUT VECTOR_BOOL types, MATRIX incidence, \ + BOOLEAN directed=False, NEIMODE mode=ALL, \ + BOOLEAN multiple=False + IGNORE: RR + +igraph_get_incidence: + PARAMS: GRAPH graph, BIPARTITE_TYPES types=NULL, OUT MATRIX res, \ + OUT VECTOR_OR_0 row_ids, OUT VECTOR_OR_0 col_ids + DEPS: types ON graph + NAME-R: as_incidence_matrix + IGNORE: RR + +igraph_is_bipartite: + PARAMS: GRAPH graph, OUT BOOLEANPTR res, OUT VECTOR_BOOL_OR_0 type + NAME-R: bipartite_mapping + +igraph_bipartite_game_gnp: + PARAMS: OUT GRAPH graph, OUT VECTOR_BOOL_OR_0 types, \ + INTEGER n1, INTEGER n2, REAL p, BOOLEAN directed, \ + NEIMODE mode + IGNORE: RR, RNamespace + +igraph_bipartite_game_gnm: + PARAMS: OUT GRAPH graph, OUT VECTOR_BOOL_OR_0 types, \ + INTEGER n1, INTEGER n2, INTEGER m, BOOLEAN directed, \ + NEIMODE mode + IGNORE: RR, RNamespace + +####################################### +# Spectral properties +####################################### + +igraph_laplacian: + PARAMS: GRAPH graph, OUT MATRIX_OR_0 res, \ + OUT SPARSEMAT_OR_0 sparseres, BOOLEAN normalized=False, \ + EDGEWEIGHTS weights=NULL + DEPS: weights ON graph + IGNORE: RR, RC + NAME-R: laplacian_matrix + +####################################### +# Components +####################################### + +igraph_clusters: + PARAMS: GRAPH graph, OUT VECTOR membership, OUT VECTOR csize, \ + OUT INTEGERPTR no, CONNECTEDNESS mode=WEAK + NAME-R: clusters + IGNORE: RR + +igraph_is_connected: + PARAMS: GRAPH graph, OUT BOOLEANPTR res, CONNECTEDNESS mode=WEAK + IGNORE: RR, RC, RNamespace + +igraph_decompose: + PARAMS: GRAPH graph, OUT GRAPHLIST components, CONNECTEDNESS mode=WEAK, \ + LONGINT maxcompno=-1, LONGINT minelements=1 + IGNORE: RR, RC, RNamespace + +igraph_articulation_points: + PARAMS: GRAPH graph, OUT VERTEXSET res + DEPS: res ON graph + NAME-R: articulation_points + +igraph_biconnected_components: + PARAMS: GRAPH graph, OUT INTEGERPTR no, \ + OUT EDGESETLIST_OR_0 tree_edges, \ + OUT EDGESETLIST_OR_0 component_edges, \ + OUT VERTEXSETLIST_OR_0 components, \ + OUT VERTEXSET articulation_points + DEPS: tree_edges ON graph, component_edges ON graph, \ + components ON graph, articulation_points ON graph + NAME-R: biconnected_components + +####################################### +# Cliques +####################################### + +igraph_cliques: + PARAMS: GRAPH graph, OUT VERTEXSETLIST res, INTEGER min_size=0, \ + INTEGER max_size=0 + DEPS: res ON graph + IGNORE: RR, RC, RNamespace + +igraph_largest_cliques: + PARAMS: GRAPH graph, OUT VERTEXSETLIST res + DEPS: res ON graph + IGNORE: RR, RC, RNamespace + +igraph_maximal_cliques: + PARAMS: GRAPH graph, OUT VERTEXSETLIST res + DEPS: res ON graph + IGNORE: RR, RC, RNamespace + +igraph_maximal_cliques_count: + PARAMS: GRAPH graph, OUT INTEGERPTR no, INTEGER min_size=0, \ + INTEGER max_size=0 + NAME-R: count_max_cliques + IGNORE: RR, RC + +igraph_maximal_cliques_file: + PARAMS: GRAPH graph, OUTFILE res, INTEGER min_size=0, \ + INTEGER max_size=0 + NAME-R: maximal.cliques.file + IGNORE: RR, RC, RNamespace + +igraph_clique_number: + PARAMS: GRAPH graph, OUT INTEGERPTR no + IGNORE: RR, RC, RNamespace + +igraph_independent_vertex_sets: + PARAMS: GRAPH graph, OUT VERTEXSETLIST res, INTEGER min_size=0, \ + INTEGER max_size=0 + DEPS: res ON graph + IGNORE: RR, RC, RNamespace + +igraph_largest_independent_vertex_sets: + PARAMS: GRAPH graph, OUT VERTEXSETLIST res + DEPS: res ON graph + IGNORE: RR, RC, RNamespace + +igraph_maximal_independent_vertex_sets: + PARAMS: GRAPH graph, OUT VERTEXSETLIST res + DEPS: res ON graph + IGNORE: RR, RC, RNamespace + +igraph_independence_number: + PARAMS: GRAPH graph, OUT INTEGERPTR no + IGNORE: RR, RC, RNamespace + +####################################### +# Layouts +####################################### + +igraph_layout_random: + PARAMS: GRAPH graph, OUT MATRIX res + IGNORE: RR, RC, RNamespace + +igraph_layout_circle: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEXSET order=ALL + IGNORE: RR, RC, RNamespace + +igraph_layout_star: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEX center=V(graph)[1], \ + VECTORM1_OR_0 order=NULL + DEPS: center ON graph + NAME-R: layout_as_star + IGNORE: RR + +igraph_layout_grid: + PARAMS: GRAPH graph, OUT MATRIX res, INTEGER width=0 + NAME-R: layout.grid + IGNORE: RR + +igraph_layout_grid_3d: + PARAMS: GRAPH graph, OUT MATRIX res, INTEGER width=0, INTEGER height=0 + NAME-R: layout.grid.3d + IGNORE: RR + +igraph_layout_fruchterman_reingold: + PARAMS: GRAPH graph, INOUT MATRIX coords=NULL, \ + BOOLEAN use_seed=False, INTEGER niter=500, \ + REAL start_temp=sqrt(vcount(graph)), \ + LAYOUT_GRID grid=AUTO, EDGEWEIGHTS weights=NULL, \ + VECTOR_OR_0 minx=NULL, VECTOR_OR_0 maxx=NULL, \ + VECTOR_OR_0 miny=NULL, VECTOR_OR_0 maxy=NULL, \ + DEPRECATED coolexp, DEPRECATED maxdelta, DEPRECATED area, \ + DEPRECATED repulserad + NAME-R: layout.fruchterman.reingold + DEPS: weights ON graph + FLAGS: PROGRESS + IGNORE: RR, RC + +igraph_layout_kamada_kawai: + PARAMS: GRAPH graph, INOUT MATRIX coords, BOOLEAN use_seed=False, \ + INTEGER maxiter=500, REAL epsilon=0.0, \ + REAL kkconst=vcount(graph), EDGEWEIGHTS weights=NULL, \ + VECTOR_OR_0 minx=NULL, VECTOR_OR_0 maxx=NULL, \ + VECTOR_OR_0 miny=NULL, VECTOR_OR_0 maxy=NULL + NAME-R: layout.kamada.kawai + DEPS: weights ON graph + IGNORE: RR, RC + +igraph_layout_lgl: + PARAMS: GRAPH graph, OUT MATRIX res, INTEGER maxiter=150, REAL maxdelta=VCOUNT(graph), \ + REAL area=VCOUNT(graph)^2, REAL coolexp=1.5, REAL repulserad=VCOUNT(graph)^3, REAL cellsize=VCOUNT(graph), \ + INTEGER root=-1 + IGNORE: RR, RC, RNamespace + +igraph_layout_reingold_tilford: + PARAMS: GRAPH graph, OUT MATRIX res, NEIMODE mode=OUT, VECTOR roots + IGNORE: RR, RC, RNamespace + +igraph_layout_reingold_tilford_circular: + PARAMS: GRAPH graph, OUT MATRIX res, NEIMODE mode=OUT, VECTOR roots + IGNORE: RR, RC, RNamespace + +igraph_layout_random_3d: + PARAMS: GRAPH graph, OUT MATRIX res + IGNORE: RR, RC, RNamespace + +igraph_layout_sphere: + PARAMS: GRAPH graph, OUT MATRIX res + IGNORE: RR, RC, RNamespace + +igraph_layout_fruchterman_reingold_3d: + PARAMS: GRAPH graph, INOUT MATRIX coords=NULL, \ + BOOLEAN use_seed=False, INTEGER niter=500, \ + REAL start_temp=sqrt(vcount(graph)), \ + EDGEWEIGHTS weights=NULL, \ + VECTOR_OR_0 minx=NULL, VECTOR_OR_0 maxx=NULL, \ + VECTOR_OR_0 miny=NULL, VECTOR_OR_0 maxy=NULL, \ + VECTOR_OR_0 minz=NULL, VECTOR_OR_0 maxz=NULL, \ + DEPRECATED coolexp, DEPRECATED maxdelta, DEPRECATED area, \ + DEPRECATED repulserad + DEPS: weights ON graph + FLAGS: PROGRESS + IGNORE: RR, RC, RNamespace + +igraph_layout_kamada_kawai_3d: + PARAMS: GRAPH graph, INOUT MATRIX coords, BOOLEAN use_seed=False, \ + INTEGER maxiter=500, REAL epsilon=0.0, \ + REAL kkconst=vcount(graph), EDGEWEIGHTS weights=NULL, \ + VECTOR_OR_0 minx=NULL, VECTOR_OR_0 maxx=NULL, \ + VECTOR_OR_0 miny=NULL, VECTOR_OR_0 maxy=NULL, \ + VECTOR_OR_0 minz=NULL, VECTOR_OR_0 maxz=NULL + DEPS: weights ON graph + IGNORE: RR, RC, RNamespace + +igraph_layout_graphopt: + PARAMS: GRAPH graph, OUT MATRIX res, INTEGER niter=500, \ + REAL node_charge=0.001, REAL node_mass=30, \ + INTEGER spring_length=0, REAL spring_constant=1, \ + REAL max_sa_movement=5, NULL use_seed + NAME-R: layout.graphopt + FLAGS: PROGRESS + IGNORE: RR, RC + +igraph_layout_drl: + PARAMS: GRAPH graph, INOUT MATRIX res, BOOLEAN use_seed=False, \ + DRL_OPTIONS options=drl_defaults$default, VECTOR_OR_0 weights=NULL, \ + VECTOR_BOOL_OR_0 fixed=NULL + DEFAULT-R: options=drl_defaults$default + NAME-R: layout.drl + IGNORE: RR + +igraph_layout_drl_3d: + PARAMS: GRAPH graph, INOUT MATRIX res, BOOLEAN use_seed=False, \ + DRL_OPTIONS options=drl_defaults$default, VECTOR_OR_0 weights=NULL, \ + VECTOR_BOOL_OR_0 fixed=NULL + DEFAULT-R: options=drl_defaults$default + NAME-R: layout.drl + IGNORE: RR + +igraph_layout_merge_dla: + PARAMS: GRAPHLIST graphs, MATRIXLIST coords, OUT MATRIX res + IGNORE: RR, RC, RNamespace + +igraph_layout_sugiyama: + PARAMS: GRAPH graph, OUT MATRIX res, OUT GRAPH_OR_0 extd_graph, \ + OUT VECTORM1_OR_0 extd_to_orig_eids, \ + VECTORM1_OR_0 layers=NULL, \ + REAL hgap=1, REAL vgap=1, INTEGER maxiter=100, \ + EDGEWEIGHTS weights=NULL + DEPS: weights ON graph + NAME-R: layout.sugiyama + IGNORE: RR + +igraph_layout_mds: + PARAMS: GRAPH graph, OUT MATRIX res, MATRIX_OR_0 dist=NULL, \ + INTEGER dim=2, NULL options + IGNORE: RR + NAME-R: layout.mds + +igraph_layout_bipartite: + PARAMS: GRAPH graph, BIPARTITE_TYPES types=NULL, OUT MATRIX res, \ + REAL hgap=1, REAL vgap=1, INTEGER maxiter=100 + DEPS: types ON graph + NAME-R: layout_as_bipartite + IGNORE: RR + +igraph_layout_gem: + PARAMS: GRAPH graph, INOUT MATRIX res=matrix(), \ + BOOLEAN use_seed=False, \ + INTEGER maxiter=40*vcount(graph)^2, \ + REAL temp_max=vcount(graph), \ + REAL temp_min=1/10, REAL temp_init=sqrt(vcount(graph)) + NAME-R: layout.gem + IGNORE: RR + +igraph_layout_davidson_harel: + PARAMS: GRAPH graph, INOUT MATRIX res=matrix(), \ + BOOLEAN use_seed=False, INTEGER maxiter=10, \ + INTEGER fineiter=FINEITER, REAL cool_fact=0.75, \ + REAL weight_node_dist=1.0, REAL weight_border=0.0, \ + REAL weight_edge_lengths=ELENW, \ + REAL weight_edge_crossings=ECROSSW, \ + REAL weight_node_edge_dist=NEDISTW + NAME-R: layout.davidson.harel + IGNORE: RR + +####################################### +# Cocitation and other similarity measures +####################################### + +igraph_cocitation: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEXSET vids=ALL + IGNORE: RR, RC, RNamespace + +igraph_bibcoupling: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEXSET vids=ALL + IGNORE: RR, RC, RNamespace + +igraph_similarity_jaccard: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEXSET vids=ALL, NEIMODE mode=ALL, \ + BOOLEAN loops=False + DEPS: vids ON graph res, mode ON vids + NAME-R: similarity.jaccard + +igraph_similarity_dice: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEXSET vids=ALL, NEIMODE mode=ALL, \ + BOOLEAN loops=False + DEPS: vids ON graph + NAME-R: similarity.dice + +igraph_similarity_inverse_log_weighted: + PARAMS: GRAPH graph, OUT MATRIX res, VERTEXSET vids=ALL, NEIMODE mode=ALL + DEPS: vids ON graph + NAME-R: similarity.invlogweighted + +####################################### +# Community structure +####################################### + +igraph_compare_communities: + PARAMS: VECTOR comm1, VECTOR comm2, OUT REALPTR res, \ + COMMCMP method=VI + IGNORE: RR, RNamespace + +igraph_community_spinglass: + PARAMS: GRAPH graph, VECTOR_OR_0 weights, OUT REALPTR modularity, \ + OUT REALPTR temperature, OUT VECTOR membership, OUT VECTOR csize, \ + INTEGER spins=25, BOOLEAN parupdate=False, REAL starttemp=1, REAL stoptemp=0.01, \ + REAL coolfact=0.99, SPINCOMMUPDATE update_rule=CONFIG, REAL gamma=1.0, \ + REAL gamma_minus=0, REAL d_p=0, REAL d_n=0 + IGNORE: RR, RC, RNamespace + +igraph_community_spinglass_single: + PARAMS: GRAPH graph, VECTOR_OR_0 weights, INTEGER vertex, OUT VECTOR community, \ + OUT REALPTR cohesion, OUT REALPTR adhesion, \ + OUT INTEGERPTR inner_links, OUT INTEGERPTR outer_links, \ + INTEGER spins=25, SPINCOMMUPDATE update_rule=CONFIG, REAL gamma=1.0 + IGNORE: RR, RC, RNamespace + +igraph_community_walktrap: + PARAMS: GRAPH graph, VECTOR weights, INT steps=4, OUT MATRIX merges, \ + OUT VECTOR modularity, OUT VECTOR membership + IGNORE: RR, RC, RNamespace + +igraph_community_edge_betweenness: + PARAMS: GRAPH graph, OUT VECTOR result, OUT VECTOR edge_betweenness, \ + OUT MATRIX merges, OUT VECTOR bridges, \ + OUT VECTOR_OR_0 modularity, OUT VECTOR_OR_0 membership, \ + BOOLEAN directed=True, EDGEWEIGHTS weights=NULL + DEPS: weights ON graph + IGNORE: RR, RC, RNamespace + +igraph_community_eb_get_merges: + PARAMS: GRAPH graph, VECTOR edges, EDGEWEIGHTS weights=NULL, \ + OUT MATRIX merges, OUT VECTOR bridges, \ + OUT VECTOR_OR_0 modularity, OUT VECTOR_OR_0 membership + IGNORE: RR, RC, RNamespace + +igraph_community_fastgreedy: + PARAMS: GRAPH graph, VECTOR_OR_0 weights, OUT MATRIX merges, OUT VECTOR modularity, OUT VECTOR_OR_0 membership + IGNORE: RR, RC, RNamespace + +igraph_community_to_membership: + PARAMS: GRAPH graph, MATRIX merges, INTEGER steps, \ + OUT VECTOR membership, OUT VECTOR csize + IGNORE: RR, RC, RNamespace + +igraph_le_community_to_membership: + PARAMS: MATRIX merges, INTEGER steps, INOUT VECTOR membership, \ + OUT VECTOR_OR_0 csize + NAME-R: community.le.to.membership + IGNORE: RR, RNamespace, RC + +igraph_modularity: + PARAMS: GRAPH graph, VECTOR membership, OUT REALPTR modularity, \ + IN VECTOR_OR_0 weights=NULL + NAME-R: modularity.igraph + IGNORE: RNamespace, RR + +igraph_modularity_matrix: + PARAMS: GRAPH graph, OUT MATRIX modmat, \ + EDGEWEIGHTS weights=NULL + DEPS: weights ON graph + NAME-R: modularity_matrix + IGNORE: RR + +igraph_reindex_membership: + PARAMS: INOUT VECTOR membership, OUT VECTOR_OR_0 new_to_old + NAME-R: reindex.membership + IGNORE: RNamespace, RR, RC + +igraph_community_leading_eigenvector: + PARAMS: GRAPH graph, EDGEWEIGHTS weights=NULL, \ + OUT MATRIX merges, OUT VECTOR membership, \ + INTEGER steps=-1, \ + INOUT ARPACKOPT options=arpack_defaults, \ + OUT REALPTR modularity, BOOLEAN start=False, \ + OUT VECTOR_OR_0 eigenvalues, \ + OUT VECTOR_OR_0 eigenvectors, \ + OUT VECTOR_OR_0 history, \ + LEVCFUNC callback, EXTRA callback_extra + DEFAULT-R: options=arpack_defaults + CLASS-R: igraph.eigenc + NAME-R: cluster_leading_eigen + IGNORE: RR, RC + +igraph_community_fluid_communities: + PARAMS: GRAPH graph, INTEGER no_of_communities, \ + OUT VECTOR membership, OUT REALPTR modularity + NAME-R: cluster_fluid_com + IGNORE: RR + +igraph_community_label_propagation: + PARAMS: GRAPH graph, OUT VECTOR membership, EDGEWEIGHTS weights=NULL, \ + VECTOR_OR_0 initial=NULL, VECTOR_BOOL_OR_0 fixed=NULL, \ + OUT REALPTR modularity + DEPS: weights ON graph + NAME-R: cluster_label_prop + IGNORE: RR + +igraph_community_multilevel: + PARAMS: GRAPH graph, EDGEWEIGHTS weights=NULL, \ + OUT VECTOR membership, \ + OUT MATRIX_OR_0 memberships, OUT VECTOR_OR_0 modularity + DEPS: weights ON graph + NAME-R: cluster_louvain + IGNORE: RR + +igraph_community_optimal_modularity: + PARAMS: GRAPH graph, OUT REALPTR modularity, \ + OUT VECTOR_OR_0 membership, \ + EDGEWEIGHTS weights=NULL + NAME-R: cluster_optimal + IGNORE: RR + +igraph_split_join_distance: + PARAMS: VECTOR comm1, VECTOR comm2, OUT INTEGERPTR distance12, \ + OUT INTEGERPTR distance21 + NAME-R: split.join.distance + IGNORE: RR, RNamespace + +igraph_hrg_fit: + PARAMS: GRAPH graph, INOUT HRG hrg=Default, BOOLEAN start=False, \ + INT steps=0 + NAME-R: fit_hrg + CLASS-R: igraphHRG + IGNORE: RR + +igraph_hrg_game: + PARAMS: OUT GRAPH graph, HRG hrg + NAME-R: sample_hrg + GATTR-R: name IS Hierarchical random graph model + +igraph_hrg_dendrogram: + PARAMS: OUT GRAPH graph, HRG hrg + NAME-R: hrg_tree + +igraph_hrg_consensus: + PARAMS: GRAPH graph, OUT VECTOR parents, OUT VECTOR weights, \ + INOUT HRG hrg=Default, BOOLEAN start=False, \ + INT num_samples=10000 + NAME-R: consensus_tree + +igraph_hrg_predict: + PARAMS: GRAPH graph, OUT VERTEXSET edges, OUT VECTOR prob, \ + INOUT HRG hrg=Default, BOOLEAN start=False, \ + INT num_samples=10000, INT num_bins=25 + NAME-R: predict_edges + IGNORE: RR + +igraph_hrg_create: + PARAMS: OUT HRG hrg, GRAPH graph, VECTOR prob + NAME-R: hrg + DEPS: prob ON graph + CLASS-R: igraphHRG + +igraph_community_infomap: + PARAMS: GRAPH graph, EDGEWEIGHTS e_weights=NULL, \ + VERTEXWEIGHTS v_weights=NULL, INT nb_trials=10, \ + OUT VECTOR membership, OUT REALPTR codelength + NAME-R: cluster_infomap + DEPS: e_weights ON graph, v_weights ON graph + IGNORE: RR + +igraph_graphlets: + PARAMS: GRAPH graph, EDGEWEIGHTS weights=NULL, \ + OUT VERTEXSETLIST cliques, OUT VECTOR Mu, INT niter=1000 + NAME-R: graphlets + DEPS: weights ON graph, cliques ON graph + IGNORE: RC + +igraph_graphlets_candidate_basis: + PARAMS: GRAPH graph, EDGEWEIGHTS weights=NULL, \ + OUT VERTEXSETLIST cliques, OUT VECTOR thresholds + NAME-R: graphlets.candidate.basis + DEPS: weights ON graph + IGNORE: RR, RC + +igraph_graphlets_project: + PARAMS: GRAPH graph, EDGEWEIGHTS weights=NULL, \ + VERTEXSETLIST cliques, INOUT VECTOR Muc, \ + BOOLEAN startMu=False, INT niter=1000 + NAME-R: graphlets.project + DEPS: weights ON graph + IGNORE: RR, RC + +####################################### +# Conversion +####################################### + +igraph_get_adjacency: + PARAMS: GRAPH graph, OUT MATRIX res, GETADJACENCY type=BOTH, \ + BOOLEAN eids=False + IGNORE: RR, RC, RNamespace + +igraph_get_edgelist: + PARAMS: GRAPH graph, OUT VECTOR res, BOOLEAN bycol=False + IGNORE: RR, RC, RNamespace + +igraph_to_directed: + PARAMS: INOUT GRAPH graph, TODIRECTED flags=MUTUAL + IGNORE: RR, RC, RNamespace + +igraph_to_undirected: + PARAMS: INOUT GRAPH graph, TOUNDIRECTED mode=COLLAPSE, \ + EDGE_ATTRIBUTE_COMBINATION edge_attr_comb=Default + NAME-R: as.undirected + IGNORE: RR + +igraph_get_stochastic: + PARAMS: GRAPH graph, OUT MATRIX res, BOOLEAN column_wise=False + NAME-R: stochastic_matrix + IGNORE: RR + +igraph_get_stochastic_sparsemat: + PARAMS: GRAPH graph, OUT SPARSEMATPTR sparsemat, \ + BOOLEAN column_wise=False + IGNORE: RR, RNamespace + +####################################### +# Read and write foreign formats +####################################### + +igraph_read_graph_edgelist: + PARAMS: OUT GRAPH graph, INFILE instream, INTEGER n=0, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_read_graph_ncol: + PARAMS: OUT GRAPH graph, INFILE instream, STRVECTOR_OR_0 predefnames, \ + BOOLEAN names=True, ADD_WEIGHTS weights=True, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_read_graph_lgl: + PARAMS: OUT GRAPH graph, INFILE instream, BOOLEAN names=True, \ + ADD_WEIGHTS weights=True, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_read_graph_pajek: + PARAMS: OUT GRAPH graph, INFILE instream + IGNORE: RR, RC, RNamespace + +igraph_read_graph_graphml: + PARAMS: OUT GRAPH graph, INFILE instream, INT index=0 + IGNORE: RR, RC, RNamespace + +igraph_read_graph_dimacs: + PARAMS: OUT GRAPH graph, INFILE instream, OUT INTEGERPTR source, \ + OUT INTEGERPTR target, OUT VECTOR capacity, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_read_graph_graphdb: + PARAMS: OUT GRAPH graph, INFILE instream, BOOLEAN directed=False + IGNORE: RR, RC, RNamespace + +igraph_read_graph_gml: + PARAMS: OUT GRAPH graph, INFILE instream + IGNORE: RR, RC, RNamespace + +igraph_read_graph_dl: + PARAMS: OUT GRAPH graph, INFILE instream, BOOLEAN directed=True + IGNORE: RR, RC, RNamespace + +igraph_write_graph_edgelist: + PARAMS: GRAPH graph, OUTFILE outstream + IGNORE: RR, RC, RNamespace + +igraph_write_graph_ncol: + PARAMS: GRAPH graph, OUTFILE outstream, CSTRING names="name", CSTRING weights="weight" + IGNORE: RR, RC, RNamespace + +igraph_write_graph_lgl: + PARAMS: GRAPH graph, OUTFILE outstream, CSTRING names="name", CSTRING weights="weight", \ + BOOLEAN isolates=True + IGNORE: RR, RC, RNamespace + +igraph_write_graph_leda: + PARAMS: GRAPH graph, OUTFILE outstream, CSTRING names="name", CSTRING weights="weight" + IGNORE: RR, RC, RNamespace + +igraph_write_graph_graphml: + PARAMS: GRAPH graph, OUTFILE outstream, BOOLEAN prefixattr=True + IGNORE: RR, RC, RNamespace + +igraph_write_graph_pajek: + PARAMS: GRAPH graph, OUTFILE outstream + IGNORE: RR, RC, RNamespace + +igraph_write_graph_dimacs: + PARAMS: GRAPH graph, OUTFILE outstream, LONGINT source=0, LONGINT target=0, \ + VECTOR capacity + IGNORE: RR, RC, RNamespace + +igraph_write_graph_gml: + PARAMS: GRAPH graph, OUTFILE outstream, VECTOR id, CSTRING creator="igraph" + IGNORE: RR, RC, RNamespace + +igraph_write_graph_dot: + PARAMS: GRAPH graph, OUTFILE outstream + IGNORE: RR, RC, RNamespace + +####################################### +# Motifs +####################################### + +igraph_motifs_randesu: + PARAMS: GRAPH graph, OUT VECTOR hist, INT size=3, VECTOR cut_prob + IGNORE: RR, RC, RNamespace + +igraph_motifs_randesu_estimate: + PARAMS: GRAPH graph, OUT INTEGERPTR est, INT size=3, VECTOR cut_prob, \ + INTEGER sample_size, VECTOR_OR_0 sample + IGNORE: RR, RC, RNamespace + +igraph_motifs_randesu_no: + PARAMS: GRAPH graph, OUT INTEGERPTR no, INT size=3, VECTOR cut_prob + IGNORE: RR, RC, RNamespace + +igraph_dyad_census: + PARAMS: GRAPH graph, OUT INTEGERPTR mut, OUT INTEGERPTR asym, OUT INTEGERPTR null + NAME-R: dyad_census + RETURN: ERROR + +igraph_triad_census: + PARAMS: GRAPH graph, OUT VECTOR res + NAME-R: triad_census + RETURN: ERROR + +igraph_adjacent_triangles: + PARAMS: GRAPH graph, OUT VECTOR res, VERTEXSET vids=ALL + DEPS: vids ON graph + NAME-R: count_triangles + +igraph_local_scan_0: + PARAMS: GRAPH graph, OUT VECTOR res, EDGEWEIGHTS weights=NULL, \ + NEIMODE mode=OUT + DEPS: weights ON graph + IGNORE: RR, RNamespace + +igraph_local_scan_0_them: + PARAMS: GRAPH us, GRAPH them, OUT VECTOR res, \ + EDGEWEIGHTS weights_them=NULL, NEIMODE mode=OUT + DEPS: weights_us ON us, weights_them ON them + IGNORE: RR, RNamespace + +igraph_local_scan_1_ecount: + PARAMS: GRAPH graph, OUT VECTOR res, EDGEWEIGHTS weights=NULL, \ + NEIMODE mode=OUT + DEPS: weights ON graph + IGNORE: RR, RNamespace + +igraph_local_scan_1_ecount_them: + PARAMS: GRAPH us, GRAPH them, OUT VECTOR res, \ + EDGEWEIGHTS weights_them=NULL, NEIMODE mode=OUT + DEPS: weigths_them ON them + IGNORE: RR, RNamespace + +igraph_local_scan_k_ecount: + PARAMS: GRAPH graph, INT k, OUT VECTOR res, EDGEWEIGHTS weights=NULL, \ + NEIMODE mode=OUT + DEPS: weights ON graph + IGNORE: RR, RNamespace + +igraph_local_scan_k_ecount_them: + PARAMS: GRAPH us, GRAPH them, INT k, OUT VECTOR res, \ + EDGEWEIGHTS weights_them=NULL, NEIMODE mode=OUT + DEPS: weights_them ON them + IGNORE: RR, RNamespace + +igraph_local_scan_neighborhood_ecount: + PARAMS: GRAPH graph, OUT VECTOR res, EDGEWEIGHTS weights=NULL, \ + VERTEXSETLIST_INT neighborhoods + DEPS: weights ON graph + IGNORE: RR, RNamespace + +igraph_list_triangles: + PARAMS: GRAPH graph, OUT VERTEXSET_INT res + DEPS: res ON graph + NAME-R: triangles + +####################################### +# Graph operators +####################################### + +igraph_disjoint_union: + PARAMS: OUT GRAPH res, GRAPH left, GRAPH right, \ + OUT EDGESET edge_map_left, OUT EDGESET edge_map_right + DEPS: edge_map_left ON left, edge_map_right ON right + IGNORE: RR, RC, RNamespace + +igraph_disjoint_union_many: + PARAMS: OUT GRAPH res, GRAPHLIST graphs, OUT VECTORLIST edgemaps + IGNORE: RR, RC, RNamespace + +igraph_union: + PARAMS: OUT GRAPH res, GRAPH left, GRAPH right + IGNORE: RR, RC, RNamespace + +igraph_union_many: + PARAMS: OUT GRAPH res, GRAPHLIST graphs + IGNORE: RR, RC, RNamespace + +igraph_intersection: + PARAMS: OUT GRAPH res, GRAPH left, GRAPH right + IGNORE: RR, RC, RNamespace + +igraph_intersection_many: + PARAMS: OUT GRAPH res, GRAPHLIST graphs + IGNORE: RR, RC, RNamespace + +igraph_difference: + PARAMS: OUT GRAPH res, GRAPH orig, GRAPH sub + IGNORE: RR, RC, RNamespace + +igraph_complementer: + PARAMS: OUT GRAPH res, GRAPH graph, BOOLEAN loops=False + IGNORE: RR, RC, RNamespace + +igraph_compose: + PARAMS: OUT GRAPH res, GRAPH g1, GRAPH g2 + IGNORE: RR, RC, RNamespace + +####################################### +# Maximum flows, minimum cuts +####################################### + +igraph_maxflow: + PARAMS: GRAPH graph, OUT REALPTR value, OUT VECTOR_OR_0 flow, \ + OUT VECTORM1_OR_0 cut, OUT VERTEXSET_OR_0 partition1, \ + OUT VERTEXSET_OR_0 partition2, VERTEX source, VERTEX target, \ + EDGECAPACITY capacity=NULL, OUT MAXFLOW_STATS stats + DEPS: capacity ON graph, source ON graph, target ON graph, \ + partition1 ON graph, partition2 ON graph, flow ON graph, \ + cut ON graph + NAME-R: max_flow + +igraph_maxflow_value: + PARAMS: GRAPH graph, OUT REALPTR value, VERTEX source, VERTEX target, \ + VECTOR_OR_0 capacity, OUT MAXFLOW_STATS stats + DEPS: source ON graph, target ON graph + IGNORE: RR, RC, RNamespace + +igraph_mincut_value: + PARAMS: GRAPH graph, OUT REALPTR res, VECTOR_OR_0 capacity + IGNORE: RR, RC, RNamespace + +igraph_st_mincut_value: + PARAMS: GRAPH graph, OUT REALPTR res, VERTEX source, VERTEX target, \ + VECTOR_OR_0 capacity + DEPS: source ON graph, target ON graph + IGNORE: RR, RC, RNamespace + +igraph_mincut: + PARAMS: GRAPH graph, OUT REALPTR value, OUT VECTORM1 partition1, \ + OUT VECTORM1 partition2, OUT VECTORM1 cut, VECTOR_OR_0 capacity + IGNORE: RR, RC, RNamespace + +igraph_st_vertex_connectivity: + PARAMS: GRAPH graph, OUT INTEGERPTR res, VERTEX source, VERTEX target, \ + VCONNNEI neighbors=NUMBER_OF_NODES + DEPS: source ON graph, target ON graph + IGNORE: RR, RC, RNamespace + +igraph_vertex_connectivity: + PARAMS: GRAPH graph, OUT INTEGERPTR res, BOOLEAN checks=True + IGNORE: RR, RC, RNamespace + +igraph_st_edge_connectivity: + PARAMS: GRAPH graph, OUT INTEGERPTR res, VERTEX source, VERTEX target + DEPS: source ON graph, target ON graph + IGNORE: RR, RC, RNamespace + +igraph_edge_connectivity: + PARAMS: GRAPH graph, OUT INTEGERPTR res, BOOLEAN checks=True + IGNORE: RR, RC, RNamespace + +igraph_edge_disjoint_paths: + PARAMS: GRAPH graph, OUT INTEGERPTR res, VERTEX source, VERTEX target + DEPS: source ON graph, target ON graph + IGNORE: RR, RC, RNamespace + +igraph_vertex_disjoint_paths: + PARAMS: GRAPH graph, OUT INTEGERPTR res, VERTEX source, VERTEX target + DEPS: source ON graph, target ON graph + IGNORE: RR, RC, RNamespace + +igraph_adhesion: + PARAMS: GRAPH graph, OUT INTEGERPTR res, BOOLEAN checks=True + IGNORE: RR, RC, RNamespace + +igraph_cohesion: + PARAMS: GRAPH graph, OUT INTEGERPTR res, BOOLEAN checks=True + IGNORE: RR, RC, RNamespace + +####################################### +# Listing s-t cuts, separators +####################################### + +igraph_dominator_tree: + PARAMS: GRAPH graph, VERTEX root, OUT VERTEXSET dom, \ + OUT GRAPH_OR_0 domtree, OUT VERTEXSET leftout, \ + INOUTMODE mode=OUT + DEPS: root ON graph, dom ON graph, leftout ON graph + NAME-R: dominator_tree + +igraph_all_st_cuts: + PARAMS: GRAPH graph, OUT EDGESETLIST_OR_0 cuts, \ + OUT VERTEXSETLIST_OR_0 partition1s, \ + VERTEX source, VERTEX target + DEPS: source ON graph, target ON graph, cuts ON graph, \ + partition1s ON graph + NAME-R: st_cuts + +igraph_all_st_mincuts: + PARAMS: GRAPH graph, OUT REALPTR value, \ + OUT EDGESETLIST_OR_0 cuts, \ + OUT VERTEXSETLIST_OR_0 partition1s, \ + VERTEX source, VERTEX target, EDGEWEIGHTS capacity=NULL + DEPS: capacity ON graph, source ON graph, target ON graph, \ + cuts ON graph, partition1s ON graph + NAME-R: st_min_cuts + +igraph_is_separator: + PARAMS: GRAPH graph, VERTEXSET candidate, OUT BOOLEANPTR res + DEPS: candidate ON graph + NAME-R: is_separator + +igraph_is_minimal_separator: + PARAMS: GRAPH graph, VERTEXSET candidate, OUT BOOLEANPTR res + DEPS: candidate ON graph + NAME-R: is_min_separator + +igraph_all_minimal_st_separators: + PARAMS: GRAPH graph, OUT VERTEXSETLIST separators + DEPS: separators ON graph + NAME-R: min_st_separators + +igraph_minimum_size_separators: + PARAMS: GRAPH graph, OUT VERTEXSETLIST separators + DEPS: separators ON graph + NAME-R: min_separators + +igraph_cohesive_blocks: + PARAMS: GRAPH graph, OUT VERTEXSETLIST blocks, \ + OUT VECTOR cohesion, OUT VECTORM1 parent, \ + OUT GRAPH blockTree + DEPS: blocks ON graph + NAME-R: cohesive.blocks + CLASS-R: cohesiveBlocks + IGNORE: RR, RC + +####################################### +# K-Cores +####################################### + +igraph_coreness: + PARAMS: GRAPH graph, OUT VECTOR cores, NEIMODE mode=ALL + IGNORE: RR, RC, RNamespace + +####################################### +# Graph isomorphism +####################################### + +igraph_isoclass: + PARAMS: GRAPH graph, OUT INTEGERPTR isoclass + NAME-R: graph.isoclass + +igraph_isomorphic: + PARAMS: GRAPH graph1, GRAPH graph2, OUT BOOLEANPTR iso + +igraph_isoclass_subgraph: + PARAMS: GRAPH graph, VECTOR vids, OUT INTEGERPTR isoclass + DEPS: vids ON graph + IGNORE: RR + +igraph_isoclass_create: + PARAMS: OUT GRAPH graph, INTEGER size, INTEGER number, BOOLEAN directed=True + NAME-R: graph_from_isomorphism_class + +igraph_isomorphic_vf2: + PARAMS: GRAPH graph1, GRAPH graph2, \ + VERTEX_COLOR vertex_color1, VERTEX_COLOR vertex_color2, \ + EDGE_COLOR edge_color1, EDGE_COLOR edge_color2, \ + OUT BOOLEANPTR iso, \ + OUT VECTORM1_OR_0 map12, OUT VECTORM1_OR_0 map21, \ + NULL node_compat_fn, NULL edge_compat_fn, \ + NULL extra + DEPS: vertex_color1 ON graph1, vertex_color2 ON graph2, \ + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_count_isomorphisms_vf2: + PARAMS: GRAPH graph1, GRAPH graph2, \ + VERTEX_COLOR vertex_color1, VERTEX_COLOR vertex_color2, \ + EDGE_COLOR edge_color1, EDGE_COLOR edge_color2, \ + OUT INTEGERPTR count, NULL node_compat_fn, \ + NULL edge_compat_fn, NULL extra + DEPS: vertex_color1 ON graph1, vertex_color2 ON graph2, \ + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_get_isomorphisms_vf2: + PARAMS: GRAPH graph1, GRAPH graph2, \ + VERTEX_COLOR vertex_color1, VERTEX_COLOR vertex_color2, \ + EDGE_COLOR edge_color1, EDGE_COLOR edge_color2, \ + OUT VECTORLIST maps, NULL node_compat_fn, \ + NULL edge_compat_fn, NULL extra + DEPS: vertex_color1 ON graph1, vertex_color2 ON graph2, \ + edge_color1 ON graph1, edge_color2 ON graph2 + IGNORE: RR + +igraph_subisomorphic_vf2: + PARAMS: GRAPH graph1, GRAPH graph2, \ + VERTEX_COLOR vertex_color1, VERTEX_COLOR vertex_color2, \ + EDGE_COLOR edge_color1, EDGE_COLOR edge_color2, \ + OUT BOOLEANPTR iso, \ + OUT VECTORM1_OR_0 map12, OUT VECTORM1_OR_0 map21, \ + NULL node_compat_fn, NULL edge_compat_fn, \ + NULL extra + DEPS: vertex_color1 ON graph1, vertex_color2 ON graph2, \ + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_count_subisomorphisms_vf2: + PARAMS: GRAPH graph1, GRAPH graph2, \ + VERTEX_COLOR vertex_color1, VERTEX_COLOR vertex_color2, \ + EDGE_COLOR edge_color1, EDGE_COLOR edge_color2, \ + OUT INTEGERPTR count, NULL node_compat_fn, \ + NULL edge_compat_fn, NULL extra + DEPS: vertex_color1 ON graph1, vertex_color2 ON graph2, \ + edge_color1 ON graph1, edge_color2 ON graph2 + +igraph_get_subisomorphisms_vf2: + PARAMS: GRAPH graph1, GRAPH graph2, \ + VERTEX_COLOR vertex_color1, VERTEX_COLOR vertex_color2, \ + EDGE_COLOR edge_color1, EDGE_COLOR edge_color2, \ + OUT VECTORLIST maps, NULL node_compat_fn, \ + NULL edge_compat_fn, NULL extra + DEPS: vertex_color1 ON graph1, vertex_color2 ON graph2, \ + edge_color1 ON graph1, edge_color2 ON graph2 + IGNORE: RR + +igraph_isomorphic_34: + PARAMS: GRAPH graph1, GRAPH graph2, OUT BOOLEANPTR iso + +igraph_canonical_permutation: + PARAMS: GRAPH graph, NULL colors, OUT VECTORM1 labeling, BLISSSH sh="fm", OUT BLISSINFO info + NAME-R: canonical_permutation + +igraph_permute_vertices: + PARAMS: GRAPH graph, OUT GRAPH res, VECTORM1 permutation + NAME-R: permute + +igraph_isomorphic_bliss: + PARAMS: GRAPH graph1, GRAPH graph2, NULL colors1, NULL colors2, \ + OUT BOOLEANPTR iso, OUT VECTORM1_OR_0 map12, \ + OUT VECTORM1_OR_0 map21, BLISSSH sh="fm", \ + OUT BLISSINFO info1, OUT BLISSINFO info2 + +igraph_automorphisms: + PARAMS: GRAPH graph, NULL colors, BLISSSH sh="fm", OUT BLISSINFO info + NAME-R: automorphisms + +igraph_subisomorphic_lad: + PARAMS: GRAPH pattern, GRAPH target, VERTEXSETLIST_OR_0 domains, \ + OUT BOOLEANPTR_OR_0 iso, OUT VECTOR_OR_0 map, \ + OUT VECTORLIST_OR_0 maps, BOOLEAN induced, INT time_limit + IGNORE: RR, RC + +####################################### +# SCG +####################################### + +igraph_scg_grouping: + PARAMS: MATRIX V, OUT VECTORM1 groups, INTEGER nt, \ + VECTOR_OR_0 nt_vec, SCGMAT mtype=Default, \ + SCGALGO algo=Default, VECTOR_OR_0 p=NULL, \ + INTEGER maxiter=100 + NAME-R: scg_group + IGNORE: RR + +igraph_scg_semiprojectors: + PARAMS: VECTORM1 groups, SCGMAT mtype=Default, \ + OUT MATRIX_OR_0 L, OUT MATRIX_OR_0 R, \ + OUT SPARSEMATPTR_OR_0 Lsparse, OUT SPARSEMATPTR_OR_0 Rsparse, \ + VECTOR_OR_0 p=NULL, SCGNORM norm=Default + NAME-R: scg_semi_proj + IGNORE: RR, RC + +igraph_scg_norm_eps: + PARAMS: MATRIX V, VECTORM1 groups, OUT VECTOR eps, \ + SCGMAT mtype=Default, VECTOR_OR_0 p=NULL, \ + SCGNORM norm=Default + NAME-R: scg_eps + +igraph_scg_adjacency: + PARAMS: GRAPH_OR_0 graph, MATRIX_OR_0 matrix, \ + SPARSEMAT_OR_0 sparsemat, VECTOR ev, \ + INTEGER nt, VECTOR_OR_0 ntvec, \ + SCGALGO algo, INOUT VECTOR_OR_0 values, \ + INOUT MATRIX_OR_0 vectors, INOUT VECTORM1_OR_0 groups, \ + BOOLEAN use_arpack=False, INTEGER maxiter, \ + OUT GRAPH_OR_0 scg_graph, OUT MATRIX_OR_0 scg_matrix, \ + OUT SPARSEMAT_OR_0 scg_sparsemat, OUT MATRIX_OR_0 L, \ + OUT MATRIX_OR_0 R, OUT SPARSEMATPTR_OR_0 Lsparse, \ + OUT SPARSEMATPTR_OR_0 Rsparse + IGNORE: RR, RC, RNamespace + +igraph_scg_stochastic: + PARAMS: GRAPH_OR_0 graph, MATRIX_OR_0 matrix, \ + SPARSEMAT_OR_0 sparsemat, VECTOR ev, \ + INTEGER nt, VECTOR_OR_0 nt_vec, \ + SCGALGO algo, SCGNORM norm=Default, \ + INOUT CVECTOR_OR_0 values, INOUT CMATRIX_OR_0 vectors, \ + INOUT VECTORM1_OR_0 groups, INOUT VECTOR_OR_0 p, \ + BOOLEAN use_arpack=False, INTEGER maxiter, \ + OUT GRAPH_OR_0 scg_graph, OUT MATRIX_OR_0 scg_matrix, \ + OUT SPARSEMAT_OR_0 scg_sparsemat, OUT MATRIX_OR_0 L, \ + OUT MATRIX_OR_0 R, OUT SPARSEMATPTR_OR_0 Lsparse, \ + OUT SPARSEMATPTR_OR_0 Rsparse + IGNORE: RR, RC, RNamespace + +igraph_scg_laplacian: + PARAMS: GRAPH_OR_0 graph, MATRIX_OR_0 matrix, \ + SPARSEMAT_OR_0 sparsemat, VECTOR ev, \ + INTEGER nt, VECTOR_OR_0 nt_vec, \ + SCGALGO algo, SCGNORM norm=Default, \ + SCGDIR direction=Default, INOUT CVECTOR_OR_0 values, \ + INOUT CMATRIX_OR_0 vectors, INOUT VECTORM1_OR_0 groups, \ + BOOLEAN use_arpack=False, INTEGER maxiter, \ + OUT GRAPH_OR_0 scg_graph, OUT MATRIX_OR_0 scg_matrix, \ + OUT SPARSEMAT_OR_0 scg_sparsemat, OUT MATRIX_OR_0 L, \ + OUT MATRIX_OR_0 R, OUT SPARSEMATPTR_OR_0 Lsparse, \ + OUT SPARSEMATPTR_OR_0 Rsparse + IGNORE: RR, RC, RNamespace + + +####################################### +# Matching +####################################### + +igraph_is_matching: + PARAMS: GRAPH graph, BIPARTITE_TYPES_OR_0 types=NULL, \ + VECTOR_LONG_M1 matching, OUT BOOLEANPTR res + DEPS: types ON graph, matching ON graph + NAME-R: is_matching + IGNORE: RR + +igraph_is_maximal_matching: + PARAMS: GRAPH graph, BIPARTITE_TYPES_OR_0 types=NULL, \ + VECTOR_LONG_M1 matching, OUT BOOLEANPTR res + DEPS: types ON graph + NAME-R: is_max_matching + IGNORE: RR + +igraph_maximum_bipartite_matching: + PARAMS: GRAPH graph, BIPARTITE_TYPES_OR_0 types=NULL, \ + OUT INTEGERPTR_OR_0 matching_size, \ + OUT REALPTR_OR_0 matching_weight, \ + OUT VECTOR_LONG_M1 matching, \ + EDGEWEIGHTS weights=NULL, REAL eps=.Machine$double.eps + DEPS: types ON graph, weights ON graph + NAME-R: max_bipartite_match + IGNORE: RR + +####################################### +# Embedding +####################################### + +igraph_adjacency_spectral_embedding: + PARAMS: GRAPH graph, INTEGER no, EDGEWEIGHTS weights=NULL, \ + EIGENWHICHPOS which=ASE, BOOLEAN scaled=True, OUT MATRIX X, \ + OUT MATRIX_OR_0 Y, OUT VECTOR_OR_0 D, \ + VECTOR cvec=AsmDefaultCvec, \ + INOUT ARPACKOPT options=igraph.arpack.default + NAME-R: embed_adjacency_matrix + DEPS: weights ON graph, cvec ON graph + IGNORE: RC + +igraph_laplacian_spectral_embedding: + PARAMS: GRAPH graph, INTEGER no, EDGEWEIGHTS weights=NULL, \ + EIGENWHICHPOS which=ASE, NEIMODE degmode=OUT, \ + LSETYPE type=Default, BOOLEAN scaled=True, OUT MATRIX X, \ + OUT MATRIX_OR_0 Y, OUT VECTOR_OR_0 D, \ + INOUT ARPACKOPT options=igraph.arpack.default + NAME-R: embed_laplacian_matrix + DEPS: weights ON graph, type ON graph + IGNORE: RC + +####################################### +# Eigensolvers +####################################### + +igraph_eigen_adjacency: + PARAMS: GRAPH graph, EIGENALGO algorithm=ARPACK, \ + EIGENWHICH which=Default, \ + INOUT ARPACKOPT options=arpack_defaults, \ + NULL storage, OUT VECTOR values, OUT MATRIX vectors, \ + NULL cmplxvalues, NULL cmplxvectors + NAME-R: spectrum + +####################################### +# Fitting power laws +####################################### + +igraph_power_law_fit: + PARAMS: VECTOR data, OUT PLFIT res, REAL xmin=-1, \ + BOOLEAN force_continuous=False + NAME-R: power.law.fit.new + IGNORE: RNamespace, RR + +####################################### +# Dynamics, on networks +####################################### + +igraph_sir: + PARAMS: GRAPH graph, REAL beta, REAL gamma, INTEGER no_sim=100, \ + OUT SIRLIST res + NAME-R: sir + CLASS-R: sir + +####################################### +# Other, not graph related +####################################### + +igraph_running_mean: + PARAMS: VECTOR data, OUT VECTOR res, INTEGER binwidth + IGNORE: RR, RC, RNamespace + +igraph_random_sample: + PARAMS: OUT VECTOR res, INTEGER l, INTEGER h, INTEGER length + IGNORE: RR, RC, RNamespace + +igraph_convex_hull: + PARAMS: MATRIX data, OUT VECTOR resverts, OUT MATRIX rescoords + NAME-R: convex_hull + +igraph_dim_select: + PARAMS: VECTOR sv, OUT INTEGERPTR dim + NAME-R: dim_select + +####################################### +# Other, (yet) undocumented functions +####################################### +igraph_convergence_degree: + PARAMS: GRAPH graph, OUT VECTOR result, OUT VECTOR in, OUT VECTOR out + IGNORE: RR, RC, RNamespace diff --git a/interfaces/java/COPYING b/interfaces/java/COPYING new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/interfaces/java/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/interfaces/java/README b/interfaces/java/README new file mode 100644 index 0000000..d3b1481 --- /dev/null +++ b/interfaces/java/README @@ -0,0 +1,72 @@ +igraph library Java interface +============================= + +Preface +------- + +**ATTENTION**: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. `addEdges`) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +Requirements +------------ + +In order to compile the Java interface from scratch, you'll need the +following: + +* A recent C compiler + +* A recent Java SDK (>= 1.5) + +* Python 2.4 or later if you want to use the interface generator called + Stimulus (found in tools/stimulus.py in the igraph distribution) to + generate `src/Graph.java` and `src/net_sf_igraph_Graph.c` + +* A GNU-compatible environment (e.g. Cygwin or MinGW on Windows, + practically anything on Linux, XCode Developer Tools on Mac OS X) + +* The `Apache Ant build tool `_ for Java + +* `ant-contrib.jar` and `cpptasks.jar` somewhere in your Ant path. + You can get these from http://ant-contrib.sourceforge.net. + +* JUnit 4 if you want to run the unit tests. You can get it from + http://junit.org. Put the downloaded JAR file in the `lib` + subdirectory and rename it to `junit.jar`. + + +Compilation steps +----------------- + +First, compile and install the C core of igraph (see the corresponding +[documentation](http://igraph.org). After that, +change to `interfaces/java` and type `ant build`. +This should generate two files in the `dist/` subdirectory: + +* `igraph-0.6.jar` (substitute the appropriate version number of course). + This contains the platform-independent compiled class files. Put it + wherever you want in your classpath. + +* `igraph-java-wrapper.dll` on Windows, `libigraph-java-wrapper.so` on + Linux or `libigraph-java-wrapper.jnilib` on Mac OS X. This contains the + platform-dependent parts. Put it somewhere in the Java library path + so Java can find it. (The library path can be determined by + `System.out.println(System.getProperty("java.library.path"))` from + Java. If you want to keep it elsewhere, you can alter the library + path by passing `-Djava.library.path=whatever` to the Java + interpreter when executing an igraph-based program. + +`ant` might fail stating that igraph is not installed on your +system. In that case, you must specify explicitly where igraph is +to be found by tweaking the appropriate properties in `build.xml`. + +Bugs, suggestions +----------------- + +Drop me a mail at if something sucks. Contact +me also if you find this a great idea and want to complete it for +the benefit of the whole open source community :) diff --git a/interfaces/java/build.xml b/interfaces/java/build.xml new file mode 100644 index 0000000..870db90 --- /dev/null +++ b/interfaces/java/build.xml @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interfaces/java/etc/enums/Connectedness.properties b/interfaces/java/etc/enums/Connectedness.properties new file mode 100644 index 0000000..b15e21d --- /dev/null +++ b/interfaces/java/etc/enums/Connectedness.properties @@ -0,0 +1,3 @@ +@ctype@=igraph_connectedness_t +@javatype@=Connectedness +@values@=WEAK(1), STRONG(2) diff --git a/interfaces/java/etc/enums/NeighborMode.properties b/interfaces/java/etc/enums/NeighborMode.properties new file mode 100644 index 0000000..ebca431 --- /dev/null +++ b/interfaces/java/etc/enums/NeighborMode.properties @@ -0,0 +1,3 @@ +@ctype@=igraph_neimode_t +@javatype@=NeighborMode +@values@=OUT(1), IN(2), ALL(3), TOTAL(3) diff --git a/interfaces/java/etc/enums/StarMode.properties b/interfaces/java/etc/enums/StarMode.properties new file mode 100644 index 0000000..ada25a8 --- /dev/null +++ b/interfaces/java/etc/enums/StarMode.properties @@ -0,0 +1,3 @@ +@ctype@=igraph_star_mode_t +@javatype@=StarMode +@values@=OUT(0), IN(1), UNDIRECTED(2), MUTUAL(3) diff --git a/interfaces/java/src/c/config.h.in b/interfaces/java/src/c/config.h.in new file mode 100644 index 0000000..dbfaaf9 --- /dev/null +++ b/interfaces/java/src/c/config.h.in @@ -0,0 +1 @@ +#define JAVA_PACKAGE_PREFIX "@JAVA_PACKAGE_SLASH@" diff --git a/interfaces/java/src/c/conversion.c b/interfaces/java/src/c/conversion.c new file mode 100644 index 0000000..75114ba --- /dev/null +++ b/interfaces/java/src/c/conversion.c @@ -0,0 +1,105 @@ +/* + IGraph library Java interface. + Copyright (C) 2007-2009 Tamas Nepusz + MTA RMKI, Konkoly-Thege Miklos st. 29-33, Budapest 1121, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#include "conversion.h" + +extern jfieldID net_sf_igraph_Graph_handle_fid; +extern jmethodID net_sf_igraph_Graph_constructor_mid; + +/***** Conversion between jobject and igraph_t */ + +/* + * Converts a Java jobject to an igraph_t* if appropriate by casting + * its private handle field to an igraph_t* + * + * @return: 0 if everything was OK, 1 otherwise + */ +int Java_jobject_to_igraph(JNIEnv *env, jobject jobj, igraph_t** gptr) { + *gptr = (igraph_t*)(uintptr_t)((*env)->GetLongField(env, jobj, net_sf_igraph_Graph_handle_fid)); + return (*gptr == 0 ? 1 : 0); +} + +/* + * Converts an igraph_t* to a new Java Graph object + * @return: the new Java Graph object or NULL if there was an error + */ +jobject Java_igraph_to_new_jobject(JNIEnv *env, igraph_t* gptr, jclass cls) { + /* Construct the object */ + jobject result; + result = (*env)->NewObject(env, cls, net_sf_igraph_Graph_constructor_mid, gptr); + return result; +} + +/***** Conversion between jdoubleArray and igraph_vector_t */ + +/** + * Converts a Java double[] to an igraph_vector_t* object. + * The igraph_vector_t* that's passed in must be uninitialized. + * @return: zero if everything was OK, an igraph error code otherwise + */ +int Java_jdoubleArray_to_igraph_vector(JNIEnv *env, jdoubleArray array, igraph_vector_t* vector) { + jsize i, n = (*env)->GetArrayLength(env, array); + jdouble* elements = (*env)->GetDoubleArrayElements(env, array, 0); + + IGRAPH_CHECK(igraph_vector_init(vector, n)); + for (i=0; i < n; i++) + VECTOR(*vector)[i] = elements[i]; + + (*env)->ReleaseDoubleArrayElements(env, array, elements, JNI_ABORT); + + return 0; +} + +/** + * Converts an igraph_vector_t* to a new Java double[] object + * @return: the new Java double array or NULL if there was an error + */ +jdoubleArray Java_igraph_vector_to_new_jdoubleArray(JNIEnv *env, igraph_vector_t* vector) { + long i, n; + jdoubleArray result; + jdouble* elements; + + n = igraph_vector_size(vector); + result = (*env)->NewDoubleArray(env, n); + elements = (*env)->GetDoubleArrayElements(env, result, 0); + + for (i=0; i < n; i++) { + elements[i] = VECTOR(*vector)[i]; + } + + (*env)->ReleaseDoubleArrayElements(env, result, elements, 0); + + return result; +} + diff --git a/interfaces/java/src/c/conversion.h b/interfaces/java/src/c/conversion.h new file mode 100644 index 0000000..92a0be8 --- /dev/null +++ b/interfaces/java/src/c/conversion.h @@ -0,0 +1,59 @@ +/* + IGraph library Java interface. + Copyright (C) 2007-2009 Tamas Nepusz + MTA RMKI, Konkoly-Thege Miklos st. 29-33, Budapest 1121, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#ifndef _Included_net_sf_igraph_conversion +#define _Included_net_sf_igraph_conversion + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/************************ CONVERSION ROUTINES **************************/ + +/* Conversion between jobject and igraph_t */ +int Java_jobject_to_igraph(JNIEnv *env, jobject jobj, igraph_t** gptr); +jobject Java_igraph_to_new_jobject(JNIEnv *env, igraph_t* gptr, jclass cls); + +/* Conversion between jdoubleArray and igraph_vector_t */ +int Java_jdoubleArray_to_igraph_vector(JNIEnv *env, jdoubleArray array, igraph_vector_t* vector); +jdoubleArray Java_igraph_vector_to_new_jdoubleArray(JNIEnv *env, igraph_vector_t* vector); + +#ifdef __cplusplus +} +#endif +#endif + diff --git a/interfaces/java/src/c/jni_utils.c b/interfaces/java/src/c/jni_utils.c new file mode 100644 index 0000000..590d37c --- /dev/null +++ b/interfaces/java/src/c/jni_utils.c @@ -0,0 +1,134 @@ +/* + IGraph library Java interface. + Copyright (C) 2007 Tamas Nepusz + MTA RMKI, Konkoly-Thege Miklos st. 29-33, Budapest 1121, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#include "jni_utils.h" +#include "config.h" +#include +#include /* strlen */ + +/************************** STATIC VARIABLES ***************************/ + +static JavaVM *jvm; + +/*********************** INITIALIZER FUNCTION **************************/ + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *this_jvm, void* reserved) { + JNIEnv *env; + + jvm = this_jvm; + if ((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2)) return JNI_ERR; + + if (Java_net_sf_igraph_Graph_OnLoad(env) == JNI_ERR) return JNI_ERR; + + if (Java_net_sf_igraph_Connectedness_OnLoad(env) == JNI_ERR) return JNI_ERR; + if (Java_net_sf_igraph_NeighborMode_OnLoad(env) == JNI_ERR) return JNI_ERR; + if (Java_net_sf_igraph_StarMode_OnLoad(env) == JNI_ERR) return JNI_ERR; + + if (Java_net_sf_igraph_VertexSet_OnLoad(env) == JNI_ERR) return JNI_ERR; + + return JNI_VERSION_1_2; +} + +JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *this_jvm, void* reserved) { + JNIEnv *env; + + if ((*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2)) return; + + Java_net_sf_igraph_Graph_OnUnload(env); + + Java_net_sf_igraph_Connectedness_OnUnload(env); + Java_net_sf_igraph_NeighborMode_OnUnload(env); + Java_net_sf_igraph_StarMode_OnUnload(env); + + Java_net_sf_igraph_VertexSet_OnUnload(env); +} + +/************************ AUXILIARY FUNCTIONS **************************/ + +/* + * Returns the environment of the current thread using the cached JVM + */ +JNIEnv *JNU_GetEnv() { + JNIEnv *env; + (*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2); + return env; +} + +/* + * Throws an exception by exception class name + * Adapted from http://java.sun.com/docs/books/jni/html/exceptions.html#26050 + */ +void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg) { + jclass cls = (*env)->FindClass(env, name); + if (cls != 0) + (*env)->ThrowNew(env, cls, msg); + (*env)->DeleteLocalRef(env, cls); +} + +/********** THINGS TO DO BEFORE ENTERING & AFTER LEAVING C LAYER ***********/ + +static igraph_error_handler_t *Java_igraph_old_error_handler; +static igraph_warning_handler_t *Java_igraph_old_warning_handler; + +void Java_igraph_error_handler(const char *reason, const char *file, + int line, int igraph_errno) { + JNIEnv *env = JNU_GetEnv(); + char msg[8192], *p; + IGRAPH_FINALLY_FREE(); + + if ((*env)->ExceptionCheck(env)) { + /* We already have an exception, keep that and return */ + return; + } + + if (strlen(reason) > 2 && reason[0] == '_' && reason[1] == '_') { + /* Special case: throwing a Java exception by name */ + JNU_ThrowByName(env, reason+2, ""); + } else { + snprintf(msg, 8192, "%s, %s at %s:%i", reason, igraph_strerror(igraph_errno), + file, line); + JNU_ThrowByName(env, JAVA_PACKAGE_PREFIX "/CoreException", msg); + } +} + +void Java_igraph_before() { + Java_igraph_old_error_handler=igraph_set_error_handler(Java_igraph_error_handler); + Java_igraph_old_warning_handler=igraph_set_warning_handler(0); +} + +void Java_igraph_after() { + igraph_set_error_handler(Java_igraph_old_error_handler); + igraph_set_warning_handler(Java_igraph_old_warning_handler); +} + diff --git a/interfaces/java/src/c/jni_utils.h b/interfaces/java/src/c/jni_utils.h new file mode 100644 index 0000000..93718e1 --- /dev/null +++ b/interfaces/java/src/c/jni_utils.h @@ -0,0 +1,68 @@ +/* + IGraph library Java interface. + Copyright (C) 2007 Tamas Nepusz + MTA RMKI, Konkoly-Thege Miklos st. 29-33, Budapest 1121, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#ifndef _Included_net_sf_igraph_jni_utils +#define _Included_net_sf_igraph_jni_utils + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "config.h" + +/*********************** INITIALIZER FUNCTION **************************/ + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *this_jvm, void* reserved); +JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *this_jvm, void* reserved); + +/************************ AUXILIARY FUNCTIONS **************************/ + +/// Returns the environment of the current thread using the cached JVM +JNIEnv *JNU_GetEnv(); + +/// Throws an exception by exception class name +void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg); + +/********** THINGS TO DO BEFORE ENTERING & AFTER LEAVING C LAYER ***********/ + +void Java_igraph_error_handler(const char *reason, const char *file, int line, int igraph_errno); +void Java_igraph_before(); +void Java_igraph_after(); + +#ifdef __cplusplus +} +#endif +#endif + diff --git a/interfaces/java/src/c/net_sf_igraph_Graph.c.in b/interfaces/java/src/c/net_sf_igraph_Graph.c.in new file mode 100644 index 0000000..01d2239 --- /dev/null +++ b/interfaces/java/src/c/net_sf_igraph_Graph.c.in @@ -0,0 +1,84 @@ +/* + IGraph library Java interface. + Copyright (C) 2006-2012 Tamas Nepusz + Pázmány Péter sétány 1/a, 1117 Budapest, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#include "net_sf_igraph_Graph.h" +#include +#include "config.h" +#include "conversion.h" +#include "jni_utils.h" + +/************************** STATIC VARIABLES ***************************/ + +jclass net_sf_igraph_Graph_class; +jfieldID net_sf_igraph_Graph_handle_fid; +jmethodID net_sf_igraph_Graph_constructor_mid; + +/*********************** INITIALIZER FUNCTION **************************/ + +jint Java_net_sf_igraph_Graph_OnLoad(JNIEnv *env) { + jclass cls; + + cls = (*env)->FindClass(env, JAVA_PACKAGE_PREFIX "/Graph"); + if (cls == 0) return JNI_ERR; + + net_sf_igraph_Graph_class = (*env)->NewWeakGlobalRef(env, cls); + if (net_sf_igraph_Graph_class == 0) return JNI_ERR; + + net_sf_igraph_Graph_handle_fid = (*env)->GetFieldID(env, cls, "handle", "J"); + if (net_sf_igraph_Graph_handle_fid == 0) return JNI_ERR; + + net_sf_igraph_Graph_constructor_mid = (*env)->GetMethodID(env, cls, "", "(J)V"); + if (net_sf_igraph_Graph_constructor_mid == 0) return JNI_ERR; +} + +void Java_net_sf_igraph_Graph_OnUnload(JNIEnv *env) { + (*env)->DeleteWeakGlobalRef(env, net_sf_igraph_Graph_class); + net_sf_igraph_Graph_class = 0; +} + +/************************ DESTRUCTOR ROUTINES **************************/ + +/* + * Frees the underlying igraph_t object + */ +void Java_net_sf_igraph_Graph_destroy(JNIEnv *env, jobject jobj) { + igraph_t *g; + if (Java_jobject_to_igraph(env, jobj, &g)) return; + if (g == 0) return; + igraph_destroy(g); + free(g); +} + +/*********** THE REST OF THIS FILE IS GENERATED BY stimulus.py *************/ + diff --git a/interfaces/java/src/c/net_sf_igraph_VertexSet.c b/interfaces/java/src/c/net_sf_igraph_VertexSet.c new file mode 100644 index 0000000..265888b --- /dev/null +++ b/interfaces/java/src/c/net_sf_igraph_VertexSet.c @@ -0,0 +1,130 @@ +/* + IGraph library Java interface. + Copyright (C) 2007 Tamas Nepusz + MTA RMKI, Konkoly-Thege Miklos st. 29-33, Budapest 1121, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#include "net_sf_igraph_VertexSet.h" + +/************************** STATIC VARIABLES ***************************/ + +static jclass net_sf_igraph_VertexSet_class; +static jmethodID net_sf_igraph_VertexSet_getIdArray_mid; +static jmethodID net_sf_igraph_VertexSet_getTypeHint_mid; + +/************************ CONVERSION ROUTINES **************************/ + +/* + * Initializes the locally cached field IDs + */ +jint Java_net_sf_igraph_VertexSet_OnLoad(JNIEnv *env) { + jclass cls; + + cls = (*env)->FindClass(env, JAVA_PACKAGE_PREFIX "/VertexSet"); + if (cls == 0) return JNI_ERR; + net_sf_igraph_VertexSet_class = (*env)->NewWeakGlobalRef(env, cls); + if (net_sf_igraph_VertexSet_class == 0) return JNI_ERR; + + net_sf_igraph_VertexSet_getIdArray_mid = + (*env)->GetMethodID(env, cls, "getIdArray", "()[J"); + if (net_sf_igraph_VertexSet_getIdArray_mid == 0) return JNI_ERR; + + net_sf_igraph_VertexSet_getTypeHint_mid = + (*env)->GetMethodID(env, cls, "getTypeHint", "()I"); + if (net_sf_igraph_VertexSet_getTypeHint_mid == 0) return JNI_ERR; + + return JNI_OK; +} + +/* + * Releases the weak references held + */ +void Java_net_sf_igraph_VertexSet_OnUnload(JNIEnv *env) { + /* + (*env)->DeleteWeakGlobalRef(env, net_sf_igraph_VertexSet_class); + net_sf_igraph_VertexSet_class = 0; + */ +} + +/* + * Converts a Java VertexSet to an igraph_vs_t + * @return: zero if everything went fine, 1 if a null pointer was passed + */ +int Java_net_sf_igraph_VertexSet_to_igraph_vs(JNIEnv *env, jobject jobj, igraph_vs_t *result) { + jint typeHint; + jobject idArray; + + if (jobj == 0) { + IGRAPH_CHECK(igraph_vs_all(result)); + return IGRAPH_SUCCESS; + } + + typeHint = (*env)->CallIntMethod(env, jobj, net_sf_igraph_VertexSet_getTypeHint_mid); + if (typeHint != 1 && typeHint != 2) { + IGRAPH_CHECK(igraph_vs_all(result)); + return IGRAPH_SUCCESS; + } + + idArray = (*env)->CallObjectMethod(env, jobj, net_sf_igraph_VertexSet_getIdArray_mid); + if ((*env)->ExceptionCheck(env)) { + return IGRAPH_EINVAL; + } + + if (typeHint == 1) { + /* Single vertex */ + jlong id[1]; + (*env)->GetLongArrayRegion(env, idArray, 0, 1, id); + IGRAPH_CHECK(igraph_vs_1(result, (igraph_integer_t)id[0])); + } else if (typeHint == 2) { + /* List of vertices */ + jlong* ids; + igraph_vector_t vec; + long i, n; + + ids = (*env)->GetLongArrayElements(env, idArray, 0); + n = (*env)->GetArrayLength(env, idArray); + + IGRAPH_VECTOR_INIT_FINALLY(&vec, n); + for (i = 0; i < n; i++) + VECTOR(vec)[i] = ids[i]; + IGRAPH_CHECK(igraph_vs_vector_copy(result, &vec)); + igraph_vector_destroy(&vec); + IGRAPH_FINALLY_CLEAN(1); + + (*env)->ReleaseLongArrayElements(env, idArray, ids, JNI_ABORT); + } + + (*env)->DeleteLocalRef(env, idArray); + + return IGRAPH_SUCCESS; +} + + diff --git a/interfaces/java/src/c/net_sf_igraph_VertexSet.h b/interfaces/java/src/c/net_sf_igraph_VertexSet.h new file mode 100644 index 0000000..ebf75b1 --- /dev/null +++ b/interfaces/java/src/c/net_sf_igraph_VertexSet.h @@ -0,0 +1,53 @@ +/* + IGraph library Java interface. + Copyright (C) 2007 Tamas Nepusz + MTA RMKI, Konkoly-Thege Miklos st. 29-33, Budapest 1121, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#ifndef _Included_net_sf_igraph_VertexSet +#define _Included_net_sf_igraph_VertexSet +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include "config.h" + +jint Java_net_sf_igraph_VertexSet_OnLoad(JNIEnv *env); +void Java_net_sf_igraph_VertexSet_OnUnload(JNIEnv *env); +int Java_net_sf_igraph_VertexSet_to_igraph_vs(JNIEnv *env, jobject jobj, igraph_vs_t *result); + +#ifdef __cplusplus +} +#endif +#endif + diff --git a/interfaces/java/src/c/net_sf_igraph_enum.pmt b/interfaces/java/src/c/net_sf_igraph_enum.pmt new file mode 100644 index 0000000..81dc5aa --- /dev/null +++ b/interfaces/java/src/c/net_sf_igraph_enum.pmt @@ -0,0 +1,44 @@ +/* -*- mode: C -*- */ +/* + IGraph library Java interface. + Copyright (C) 2006-2012 Tamas Nepusz + Pázmány Péter sétány 1/a, 1117 Budapest, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#include +#include "config.h" +#include +#include "net_sf_igraph_pmt.h" + +jint FUNCTION(OnLoad)(JNIEnv *env); +void FUNCTION(OnUnload)(JNIEnv *env); +int FUNCTION(to_igraph)(JNIEnv *env, jobject jobj, C_TYPE *result); + diff --git a/interfaces/java/src/c/net_sf_igraph_enum_impl.pmt b/interfaces/java/src/c/net_sf_igraph_enum_impl.pmt new file mode 100644 index 0000000..eb4e44a --- /dev/null +++ b/interfaces/java/src/c/net_sf_igraph_enum_impl.pmt @@ -0,0 +1,82 @@ +/* + IGraph library Java interface. + Copyright (C) 2006-2012 Tamas Nepusz + Pázmány Péter sétány 1/a, 1117 Budapest, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#include "net_sf_igraph_pmt.h" + +/************************** STATIC VARIABLES ***************************/ + +#define CLASSVAR CONCAT3(igraph, JAVA_TYPE, class) +#define FIELDVAR CONCAT3(igraph, JAVA_TYPE, numericValue_fid) + +static jclass CLASSVAR; +static jfieldID FIELDVAR; + +/************************ CONVERSION ROUTINES **************************/ + +/* + * Initializes the locally cached field IDs + */ +jint FUNCTION(OnLoad)(JNIEnv *env) { + jclass cls; + + cls = (*env)->FindClass(env, JAVA_PACKAGE_PREFIX "/" JAVA_TYPE_STRING); + if (cls == 0) return JNI_ERR; + CLASSVAR = (*env)->NewWeakGlobalRef(env, cls); + if (CLASSVAR == 0) return JNI_ERR; + FIELDVAR = (*env)->GetFieldID(env, cls, "numericValue", "I"); + if (FIELDVAR == 0) return JNI_ERR; + + return JNI_OK; +} + +/* + * Releases the weak references held + */ +void FUNCTION(OnUnload)(JNIEnv *env) { + (*env)->DeleteWeakGlobalRef(env, CLASSVAR); + CLASSVAR = 0; +} + +/* + * Converts a Java enum type to an igraph enum type + * @return: zero if everything went fine, 1 if a null pointer was passed + */ +int FUNCTION(to_igraph)(JNIEnv *env, jobject jobj, C_TYPE *result) { + if (jobj == 0) + return 1; + *result = (*env)->GetIntField(env, jobj, FIELDVAR); + return 0; +} + + diff --git a/interfaces/java/src/c/net_sf_igraph_enums.c b/interfaces/java/src/c/net_sf_igraph_enums.c new file mode 100644 index 0000000..96eeaf5 --- /dev/null +++ b/interfaces/java/src/c/net_sf_igraph_enums.c @@ -0,0 +1,60 @@ +/* + IGraph library Java interface. + Copyright (C) 2007 Tamas Nepusz + MTA RMKI, Konkoly-Thege Miklos st. 29-33, Budapest 1121, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#include "net_sf_igraph_enums.h" + +#define JAVA_TYPE Connectedness +#define JAVA_TYPE_STRING "Connectedness" +#define C_TYPE igraph_connectedness_t +#include "net_sf_igraph_enum_impl.pmt" +#undef JAVA_TYPE_STRING +#undef JAVA_TYPE +#undef C_TYPE + +#define JAVA_TYPE NeighborMode +#define JAVA_TYPE_STRING "NeighborMode" +#define C_TYPE igraph_neimode_t +#include "net_sf_igraph_enum_impl.pmt" +#undef JAVA_TYPE_STRING +#undef JAVA_TYPE +#undef C_TYPE + +#define JAVA_TYPE StarMode +#define JAVA_TYPE_STRING "StarMode" +#define C_TYPE igraph_star_mode_t +#include "net_sf_igraph_enum_impl.pmt" +#undef JAVA_TYPE_STRING +#undef JAVA_TYPE +#undef C_TYPE + diff --git a/interfaces/java/src/c/net_sf_igraph_enums.h b/interfaces/java/src/c/net_sf_igraph_enums.h new file mode 100644 index 0000000..a6af2b4 --- /dev/null +++ b/interfaces/java/src/c/net_sf_igraph_enums.h @@ -0,0 +1,45 @@ +/* + IGraph library Java interface. + Copyright (C) 2007 Tamas Nepusz + MTA RMKI, Konkoly-Thege Miklos st. 29-33, Budapest 1121, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#define JAVA_TYPE NeighborMode +#define C_TYPE igraph_neimode_t +#include "net_sf_igraph_enum.pmt" +#undef JAVA_TYPE +#undef C_TYPE + +#define JAVA_TYPE StarMode +#define C_TYPE igraph_star_mode_t +#include "net_sf_igraph_enum.pmt" +#undef JAVA_TYPE +#undef C_TYPE diff --git a/interfaces/java/src/c/net_sf_igraph_pmt.h b/interfaces/java/src/c/net_sf_igraph_pmt.h new file mode 100644 index 0000000..05d5a18 --- /dev/null +++ b/interfaces/java/src/c/net_sf_igraph_pmt.h @@ -0,0 +1,50 @@ +/* + IGraph library Java interface. + Copyright (C) 2007 Tamas Nepusz + MTA RMKI, Konkoly-Thege Miklos st. 29-33, Budapest 1121, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +#ifndef CONCAT2 +# define CONCAT2x(a,b) a ## _ ## b +# define CONCAT2(a,b) CONCAT2x(a,b) +#endif + +#ifndef CONCAT3 +# define CONCAT3x(a,b,c) a ## _ ## b ## _ ## c +# define CONCAT3(a,b,c) CONCAT3x(a,b,c) +#endif + +#ifndef CONCAT4 +# define CONCAT4x(a,b,c,d) a ## _ ## b ## _ ## c ## _ ## d +# define CONCAT4(a,b,c,d) CONCAT4x(a,b,c) +#endif + +#define FUNCTION(a) CONCAT3(Java_net_sf_igraph, JAVA_TYPE, a) diff --git a/interfaces/java/src/java/CoreException.java b/interfaces/java/src/java/CoreException.java new file mode 100644 index 0000000..a30be06 --- /dev/null +++ b/interfaces/java/src/java/CoreException.java @@ -0,0 +1,40 @@ +/* + IGraph library Java interface. + Copyright (C) 2006-2012 Tamas Nepusz + Pázmány Péter sétány 1/a, 1117 Budapest, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +package net.sf.igraph; + +public class CoreException extends Exception { + public CoreException() { super(); } + public CoreException(String message) { super(message); } +} diff --git a/interfaces/java/src/java/GenericEnum.java.in b/interfaces/java/src/java/GenericEnum.java.in new file mode 100644 index 0000000..77c19d3 --- /dev/null +++ b/interfaces/java/src/java/GenericEnum.java.in @@ -0,0 +1,45 @@ +/* + IGraph library Java interface. + Copyright (C) 2006-2012 Tamas Nepusz + Pázmány Péter sétány 1/a, 1117 Budapest, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +package net.sf.igraph; + +/// Proxy class for @ctype@ +public enum @javatype@ { + @values@; + + private final int numericValue; + + @javatype@(int numericValue) { this.numericValue = numericValue; } + public int intValue() { return this.numericValue; } +} diff --git a/interfaces/java/src/java/Graph.java.in b/interfaces/java/src/java/Graph.java.in new file mode 100644 index 0000000..ed4a309 --- /dev/null +++ b/interfaces/java/src/java/Graph.java.in @@ -0,0 +1,59 @@ +/* + IGraph library Java interface. + Copyright (C) 2006-2012 Tamas Nepusz + Pázmány Péter sétány 1/a, 1117 Budapest, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +package net.sf.igraph; + +public class Graph { + private long handle=0; // handle to the underlying igraph_t object + + private Graph(long handle) { this.handle = handle; } + private native void destroy(); + + @Override protected void finalize() throws Throwable { + if (handle != 0) destroy(); + super.finalize(); + } + + public static void test() { + System.out.println("OK"); + } + + /* stimulus.py generated part starts here */ + %STIMULUS% + /* stimulus.py generated part ends here */ + + static { + System.loadLibrary("igraph-java-wrapper"); + } +} diff --git a/interfaces/java/src/java/VertexSet.java b/interfaces/java/src/java/VertexSet.java new file mode 100644 index 0000000..fb1f2e8 --- /dev/null +++ b/interfaces/java/src/java/VertexSet.java @@ -0,0 +1,186 @@ +/* + IGraph library Java interface. + Copyright (C) 2006-2012 Tamas Nepusz + Pázmány Péter sétány 1/a, 1117 Budapest, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +package net.sf.igraph; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; +import net.sf.igraph.util.LongRangeIterator; + +/// Class representing a vertex (sub)set of a graph +public class VertexSet implements Iterable { + /// List of vertex IDs in this vertex (sub)set + protected List ids = null; + + /// Graph object we are attached to (if any) + protected Graph graph = null; + + /// Static instance variable that refers to a vertex set with all the vertices in a graph + static final VertexSet ALL = new VertexSet(); + + /// Constructor that creates a VertexSet referring to all the vertices with no attached graph + public VertexSet() {} + + /// Constructor that creates a VertexSet referring to all the vertices attached to a given graph + public VertexSet(Graph graph) { + this.graph = graph; + } + + /// Constructor that creates a VertexSet referring to a single vertex attached to a given graph + public VertexSet(Long vertexID, Graph graph) { + this(graph); + this.ids = new Vector(1); + this.ids.add(vertexID); + } + + /// Constructor that creates a VertexSet referring to a single vertex with no attached graph + public VertexSet(Long vertexID) { + this(vertexID, null); + } + + /// Constructor that creates a VertexSet referring to a single vertex attached to a given graph + public VertexSet(Integer vertexID, Graph graph) { + this(Long.valueOf(vertexID), graph); + } + + /// Constructor that creates a VertexSet referring to a single vertex with no attached graph + public VertexSet(Integer vertexID) { + this(vertexID, null); + } + + /// Constructor that creates a VertexSet referring to a single vertex attached to a given graph + public VertexSet(long vertexID, Graph graph) { + this(Long.valueOf(vertexID), graph); + } + + /// Constructor that creates a VertexSet referring to a single vertex with no attached graph + public VertexSet(long vertexID) { + this(vertexID, null); + } + + /// Constructor that creates a VertexSet referring to a predefined set of vertices attached to a graph + public VertexSet(Collection vertexIDs, Graph graph) { + this(graph); + this.ids = new Vector(vertexIDs); + } + + /// Constructor that creates a VertexSet referring to a predefined set of vertices with no attached graph + public VertexSet(Collection vertexIDs) { + this(vertexIDs, null); + } + + /// Constructor that creates a VertexSet referring to a predefined set of vertices attached to a graph + public VertexSet(long[] vertexIDs, Graph graph) { + this(graph); + this.ids = new Vector(vertexIDs.length); + for (int i = 0; i < vertexIDs.length; i++) + this.ids.add(Long.valueOf(vertexIDs[i])); + } + + /// Constructor that creates a VertexSet referring to a predefined set of vertices with no attached graph + public VertexSet(long[] vertexIDs) { + this(vertexIDs, null); + } + + /// Returns an iterator for the vertex IDs (may change in the future!) + public Iterator iterator() { + if (ids == null) { + /* No IDs specified. If we are assigned to a graph, get the vertex + * count and iterate over all the vertices */ + if (graph == null) + throw new UnsupportedOperationException("VertexSet is not assigned to a graph"); + + return new LongRangeIterator(0L, graph.vcount()); + } + + return this.ids.iterator(); + } + + /// Returns an array for the vertex IDs + public long[] getIdArray() { + if (this.ids == null) { + /* No IDs specified. If we are assigned to a graph, get the vertex + * count and return all the IDs */ + if (graph == null) + throw new UnsupportedOperationException("VertexSet is not assigned to a graph"); + + long[] result = new long[(int)graph.vcount()]; + for (int i = 0; i < result.length; i++) + result[i] = i; + + return result; + } + + long[] result = new long[this.ids.size()]; + for (int i = 0; i < result.length; i++) + result[i] = this.ids.get(i); + return result; + } + + /** + * Returns the type hint of this vertex set. + * + * This variable tells which igraph_vs_t constructor should be used in the C core + * of igraph. It is a non-negative integer with the following meanings: + * + * - 0 = igraph_vs_all + * - 1 = igraph_vs_1 + * - 2 = igraph_vs_vector + * + * Other igraph_vs_t constructors are not supported yet. + */ + public int getTypeHint() { + if (ids == null) + return 0; + + if (ids.size() == 1) + return 1; + + return 2; + } + + /// Get the graph we are attached to + public Graph getGraph() { return this.graph; } + + /// Set the graph we are attached to + public void setGraph(Graph graph) { this.graph = graph; } + + /// Set the graph we are attached to + public void attach(Graph graph) { setGraph(graph); } + + /// Detach from the graph we are attached to + public void detach() { this.graph = null; } +} diff --git a/interfaces/java/src/java/util/LongRangeIterator.java b/interfaces/java/src/java/util/LongRangeIterator.java new file mode 100644 index 0000000..e903b6c --- /dev/null +++ b/interfaces/java/src/java/util/LongRangeIterator.java @@ -0,0 +1,95 @@ +/* + IGraph library Java interface. + Copyright (C) 2006-2012 Tamas Nepusz + Pázmány Péter sétány 1/a, 1117 Budapest, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +package net.sf.igraph.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Iterator generating numbers in increasing order from min to max with a given step size. + * + * The interval is inclusive from the left (i.e. min will be included) but exclusive from + * the right (i.e. max will not be included). + */ +public class LongRangeIterator implements Iterator { + /// The next value to be returned + private long nextValue; + + /// The maximum value + private final long max; + + /// The step size + private final long step; + + /// Constructor that creates a range iterator in [min..max) with a given step size + public LongRangeIterator(long min, long max, long step) { + if (min > max) { + throw new IllegalArgumentException("min must be <= max"); + } + this.nextValue = min; + this.max = max; + this.step = step; + } + + /// Constructor that creates a range iterator in [min..max) with step size 1 + public LongRangeIterator(long min, long max) { + this(min, max, 1); + } + + /// Constructor that creates an unlimited range iterator starting from min with step size 1 + public LongRangeIterator(long min) { + this(min, Long.MAX_VALUE, 1); + } + + /// Checks whether there are more elements left + public boolean hasNext() { + return nextValue < max; + } + + /// Returns the next element + public Long next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Long result = Long.valueOf(nextValue); + nextValue = nextValue + step; + return result; + } + + /// Removes the current element - not implemented of course + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/interfaces/java/src/tests/BasicGraphTests.java b/interfaces/java/src/tests/BasicGraphTests.java new file mode 100644 index 0000000..c37176c --- /dev/null +++ b/interfaces/java/src/tests/BasicGraphTests.java @@ -0,0 +1,158 @@ +/* + IGraph library Java interface. + Copyright (C) 2006-2012 Tamas Nepusz + Pázmány Péter sétány 1/a, 1117 Budapest, Hungary + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + +ATTENTION: This is a highly experimental, proof-of-concept Java interface. +Its main purpose was to convince me that it can be done in finite time :) +The interface is highly incomplete, at the time of writing even some +essential functions (e.g. addEdges) are missing. Since I don't use Java +intensively, chances are that this interface gets finished only if there +is substantial demand for it and/or someone takes the time to send patches +or finish it completely. + +*/ + +package net.sf.igraph; + +import org.junit.*; +import static org.junit.Assert.*; + +public class BasicGraphTests { + @Test + public void testEmptyGraph() { + Graph graph = Graph.Empty(10, false); + assertEquals("empty graph should have ten vertices", 10, graph.vcount()); + assertEquals("empty graph should have no edges", 0, graph.ecount()); + assertFalse(graph.isDirected()); + + graph = Graph.Empty(5, true); + assertEquals("empty graph should have five vertices", 5, graph.vcount()); + assertEquals("empty graph should have no edges", 0, graph.ecount()); + assertTrue(graph.isDirected()); + } + + @Test(expected=CoreException.class) + public void testEmptyGraphException() { + Graph graph = Graph.Empty(-1, true); + } + + @Test + public void testFullGraph() { + Graph graph = Graph.Full(10, false, false); + assertEquals("full graph should have 10 vertices", 10, graph.vcount()); + assertEquals("full graph should have 45 edges", 45, graph.ecount()); + + graph = Graph.Full(10, true, false); + assertEquals("full directed graph should have 10 vertices", 10, graph.vcount()); + assertEquals("full directed graph should have 90 edges", 90, graph.ecount()); + + graph = Graph.Full(10, false, true); + assertEquals("full graph with loops should have 10 vertices", 10, graph.vcount()); + assertEquals("full graph with loops should have 45 edges", 55, graph.ecount()); + } + + @Test(expected=CoreException.class) + public void testFullGraphException() { + Graph graph = Graph.Full(-1, true, true); + } + + @Test + public void testNeighbors() { + Graph graph = Graph.Full(10, false, false); + double[] neighbors = graph.neighbors(3, NeighborMode.OUT); + double[] expectedNeighbors = { 0, 1, 2, 4, 5, 6, 7, 8, 9 }; + + for (int i = 0; i < expectedNeighbors.length; i++) + assertEquals("neighbor list element "+i+" invalid for undirected full graph", + expectedNeighbors[i], neighbors[i], 0.0); + } + + @Test(expected=CoreException.class) + public void testNeighborsInvalidVertexID() { + Graph graph = Graph.Full(10, false, false); + double[] neighbors = graph.neighbors(-1, NeighborMode.OUT); + } + + @Test(expected=NullPointerException.class) + public void testNeighborsInvalidNeighborMode() { + Graph graph = Graph.Empty(3, false); + double[] neighbors = graph.neighbors(2, null); + } + + @Test + public void testGetEids() { + Graph graph = Graph.Full(10, false, false); + double[] pairs = {2, 7, 5, 1, 3, 4}; + double[] eids = graph.getEids(pairs, false); + double[] expectedEids = { 21, 12, 24 }; + + for (int i = 0; i < expectedEids.length; i++) + assertEquals("edge ID list element "+i+" invalid for undirected full graph", + expectedEids[i], eids[i], 0.0); + } + + @Test + public void testDegreeAll1() { + Graph graph = Graph.Full(10, false, true); + double[] expectedDegrees = { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 }; + double[] degrees = graph.degree(null, NeighborMode.OUT, false); + + for (int i = 0; i < expectedDegrees.length; i++) { + assertEquals("degree list element "+i+" invalid for undirected full graph (no loops)", + expectedDegrees[i], degrees[i], 0.0); + } + } + + @Test + public void testDegreeAll2() { + Graph graph = Graph.Full(10, false, true); + double[] expectedDegrees = { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 }; + double[] degrees = graph.degree(VertexSet.ALL, NeighborMode.OUT, false); + + for (int i = 0; i < expectedDegrees.length; i++) { + assertEquals("degree list element "+i+" invalid for undirected full graph (no loops)", + expectedDegrees[i], degrees[i], 0.0); + } + } + + @Test + public void testDegreeSingle() { + Graph graph = Graph.Full(10, false, true); + double[] expectedDegrees = { 9 }; + double[] degrees = graph.degree(new VertexSet(2), NeighborMode.OUT, false); + for (int i = 0; i < expectedDegrees.length; i++) + assertEquals("degree list element "+i+" invalid for undirected full graph (no loops)", + expectedDegrees[i], degrees[i], 0.0); + } + + @Test + public void testDegreeMultiple() { + Graph graph = Graph.Full(10, false, true); + long[] ids = { 2, 3, 7 }; + double[] expectedDegrees = { 9, 9, 9 }; + double[] degrees = graph.degree(new VertexSet(ids), NeighborMode.OUT, false); + for (int i = 0; i < expectedDegrees.length; i++) + assertEquals("degree list element "+i+" invalid for undirected full graph (no loops)", + expectedDegrees[i], degrees[i], 0.0); + } +}; diff --git a/interfaces/java/types-C.def b/interfaces/java/types-C.def new file mode 100644 index 0000000..0ded79c --- /dev/null +++ b/interfaces/java/types-C.def @@ -0,0 +1,232 @@ + +################################## +# GRAPH + +GRAPH: + CTYPE: igraph_t* + JAVATYPE: jobject + INCONV: + IN: if (Java_jobject_to_igraph(env, %I%, &%C%)) return 0; + OUT: %C% = (igraph_t*)calloc(1, sizeof(igraph_t)); + INOUT: if (Java_jobject_to_igraph(env, %I%, &%C%)) return 0; + OUTCONV: + OUT: %I% = Java_igraph_to_new_jobject(env, %C%, cls); + INOUT: /* No conversion needed for INOUT GRAPH */ + +################################## +# INTEGER + +INTEGER: + CTYPE: igraph_integer_t + JAVATYPE: jlong + INCONV: + IN: %C% = (igraph_integer_t)%I%; + OUTCONV: + OUT: %I% = (jlong)%C%; + +INTEGERPTR: + CTYPE: igraph_integer_t + JAVATYPE: jlong + CALL: &%C% + OUTCONV: + OUT: %I% = (jlong)%C%; + +INT: + CTYPE: int + JAVATYPE: jint + INCONV: + IN: %C% = (int)%I%; + +################################## +# REAL + +REAL: + CTYPE: igraph_real_t + JAVATYPE: jdouble + INCONV: + IN: %C% = (igraph_real_t)%I%; + OUTCONV: + OUT: %I% = (jdouble)%C%; + +REALPTR: + CTYPE: igraph_real_t + JAVATYPE: jdouble + CALL: &%C% + INCONV: + INOUT: %C% = (igraph_real_t)%I%; + OUTCONV: + OUT: %I% = (jdouble)%C%; + +################################## +# BOOLEAN + +BOOLEAN: + CTYPE: igraph_bool_t + JAVATYPE: jboolean + INCONV: + IN: %C% = (igraph_bool_t)%I%; + OUTCONV: + OUT: %I% = (%C% ? JNI_TRUE : JNI_FALSE); + +BOOLEANPTR: + CTYPE: igraph_bool_t + JAVATYPE: jboolean + CALL: &%C% + OUTCONV: + OUT: %I% = (%C% ? JNI_TRUE : JNI_FALSE); + +################################## +# ERROR, used as the return type usually + +ERROR: + CTYPE: int + +################################## +# ENUMS + +CONNECTEDNESS: + CTYPE: igraph_connectedness_t + JAVATYPE: jobject + INCONV: + IN: if (Java_net_sf_igraph_Connectedness_to_igraph(env, %I%, &%C%)) { \ + igraph_error("__java/lang/NullPointerException", __FILE__, __LINE__, IGRAPH_ENOMEM); \ + } + +NEIMODE: + CTYPE: igraph_neimode_t + JAVATYPE: jobject + INCONV: + IN: if (Java_net_sf_igraph_NeighborMode_to_igraph(env, %I%, &%C%)) { \ + igraph_error("__java/lang/NullPointerException", __FILE__, __LINE__, IGRAPH_ENOMEM); \ + } + +STARMODE: + CTYPE: igraph_star_mode_t + JAVATYPE: jobject + INCONV: + IN: if (Java_net_sf_igraph_StarMode_to_igraph(env, %I%, &%C%)) { \ + igraph_error("__java/lang/NullPointerException", __FILE__, __LINE__, IGRAPH_ENOMEM); \ + } + +################################## +# VECTOR of real values + +VECTOR: + CTYPE: igraph_vector_t + JAVATYPE: jdoubleArray + CALL: &%C% + INCONV: + IN: Java_jdoubleArray_to_igraph_vector(env, %I%, &%C%); \ + IGRAPH_FINALLY(igraph_vector_destroy, &%C%); + OUT: if (0 != igraph_vector_init(&%C%, 0)) { \ + igraph_error("", __FILE__, __LINE__, IGRAPH_ENOMEM); \ + } \ + IGRAPH_FINALLY(igraph_vector_destroy, &%C%); + OUTCONV: + IN: igraph_vector_destroy(&%C%); \ + IGRAPH_FINALLY_CLEAN(1); + OUT: %I% = Java_igraph_vector_to_new_jdoubleArray(env, &%C%); \ + igraph_vector_destroy(&%C%); \ + IGRAPH_FINALLY_CLEAN(1); + +VECTORLIST: + CTYPE: igraph_vector_ptr_t + CALL: &%C% + INCONV: + OUT: if (0 != igraph_vector_ptr_init(&%C%, 0)) { \ + igraph_error("", __FILE__, __LINE__, IGRAPH_ENOMEM); \ + } \ + IGRAPH_FINALLY(R_igraph_vectorlist_destroy, &%C%); + OUTCONV: + OUT: PROTECT(%I%=R_igraph_vectorlist_to_SEXP(&%C%)); \ + R_igraph_vectorlist_destroy(&%C%); \ + IGRAPH_FINALLY_CLEAN(1); + +################################## +# VECTOR that can be NULL +# 'OUT' arguments are always returned now, in the +# future we should have a boolean R argument for them which +# defines whether or not we want to return them, but this +# requires some stimulus development. + +VECTOR_OR_0: + CTYPE: igraph_vector_t + CALL: (isNull(%I%) ? 0 : &%C%) + INCONV: + IN: if (!isNull(%I%)) { R_SEXP_to_vector(%I%, &%C%); } + OUT: if (0 != igraph_vector_init(&%C%, 0)) { \ + igraph_error("", __FILE__, __LINE__, IGRAPH_ENOMEM); \ + } \ + IGRAPH_FINALLY(igraph_vector_destroy, &%C%); \ + %I%=NEW_NUMERIC(0); /* hack to have a non-NULL value */ + OUTCONV: + OUT: PROTECT(%I%=R_igraph_0orvector_to_SEXP(&%C%)); \ + igraph_vector_destroy(&%C%); \ + IGRAPH_FINALLY_CLEAN(1); + +# This is the same, some syntax would be needed to express it + +EDGEWEIGHTS: + CTYPE: igraph_vector_t + CALL: (isNull(%I%) ? 0 : &%C%) + INCONV: + IN: if (!isNull(%I%)) { R_SEXP_to_vector(%I%, &%C%); } + OUT: if (0 != igraph_vector_init(&%C%, 0)) { \ + igraph_error("", __FILE__, __LINE__, IGRAPH_ENOMEM); \ + } \ + IGRAPH_FINALLY(igraph_vector_destroy, &%C%); \ + %I%=NEW_NUMERIC(0); /* hack to have a non-NULL value */ + OUTCONV: + OUT: PROTECT(%I%=R_igraph_0orvector_to_SEXP(&%C%)); \ + igraph_vector_destroy(&%C%); \ + IGRAPH_FINALLY_CLEAN(1); + +################################## +# MATRIX of real values + +MATRIX: + CTYPE: igraph_matrix_t + CALL: &%C% + INCONV: + IN: R_SEXP_to_matrix(%I%, &%C%); + OUT: if (0 != igraph_matrix_init(&%C%, 0, 0)) { \ + igraph_error("", __FILE__, __LINE__, IGRAPH_ENOMEM); \ + } \ + IGRAPH_FINALLY(igraph_vector_destroy, &%C%); + OUTCONV: + OUT: PROTECT(%I%=R_igraph_matrix_to_SEXP(&%C%)); \ + igraph_matrix_destroy(&%C%); \ + IGRAPH_FINALLY_CLEAN(1); + +################################## +# Some vertices of a graph + +VERTEXSET: + CTYPE: igraph_vs_t + JAVATYPE: jobject + INCONV: + IN: if (0 != Java_net_sf_igraph_VertexSet_to_igraph_vs(env, %I%, &%C%)) { \ + igraph_error("", __FILE__, __LINE__, IGRAPH_ENOMEM); \ + } \ + IGRAPH_FINALLY(igraph_vs_destroy, &%C%); + OUTCONV: + IN: igraph_vs_destroy(&%C%); \ + IGRAPH_FINALLY_CLEAN(1); + +################################## +# NULL, this is supplied as an argument + +NULL: + CALL: 0 + HEADER: + +################################## +# Options to the ARPACK solver + +ARPACKOPT: + CTYPE: igraph_arpack_options_t + CALL: &%C% + INCONV: + INOUT: R_SEXP_to_igraph_arpack_options(%I%, &%C%); + OUTCONV: + INOUT: PROTECT(%I%=R_igraph_arpack_options_to_SEXP(&%C%)); diff --git a/interfaces/java/types-Java.def b/interfaces/java/types-Java.def new file mode 100644 index 0000000..d8413b3 --- /dev/null +++ b/interfaces/java/types-Java.def @@ -0,0 +1,61 @@ + +GRAPH: + JAVATYPE: Graph + +INTEGER: + JAVATYPE: long + +INTEGERPTR: + JAVATYPE: long + +INT: + JAVATYPE: int + +REAL: + JAVATYPE: double + +REALPTR: + JAVATYPE: double + +BOOLEAN: + JAVATYPE: boolean + +BOOLEANPTR: + JAVATYPE: boolean + +CSTRING: + JAVATYPE: String + +VECTOR: + JAVATYPE: double[] + +VECTOR_OR_0: + JAVATYPE: double[] + +MATRIX: + JAVATYPE: double[][] + +NEIMODE: + JAVATYPE: NeighborMode + +STARMODE: + JAVATYPE: StarMode + +CONNECTEDNESS: + JAVATYPE: Connectedness + +ATTRIBUTES: + JAVATYPE: Object + +VERTEXSET: + JAVATYPE: VertexSet + +TRANSITIVITYMODE: + +EDGEWEIGHTS: + +NULL: + +VECTORLIST: + +ARPACKOPT: diff --git a/interfaces/shell/Makefile.am b/interfaces/shell/Makefile.am new file mode 100644 index 0000000..b06aeb1 --- /dev/null +++ b/interfaces/shell/Makefile.am @@ -0,0 +1,23 @@ +bin_PROGRAMS = igraph + +STIMULUS = $(top_srcdir)/tools/stimulus.py +FUNCTIONS_DEF = $(top_srcdir)/interfaces/functions.def +TYPES_DEF = $(top_srcdir)/interfaces/shell/types.def + +interface.c: interface.c.in $(TYPES_DEF) $(STIMULUS) $(FUNCTIONS_DEF) + $(STIMULUS) -f $(FUNCTIONS_DEF) \ + -i $(top_srcdir)/interfaces/shell/interface.c.in \ + -o interface.c \ + -t $(TYPES_DEF) \ + -l Shell + +funcs.txt: interface.c.in $(STIMULUS) $(FUNCTIONS_DEF) + $(STIMULUS) -f $(FUNCTIONS_DEF) -o funcs.txt -l ShellLn + +igraph_CPPFLAGS = -I $(top_srcdir)/include/ +igraph_CFLAGS = -Wall -O2 +igraph_LDFLAGS = -L$(top_srcdir)/src/.libs -ligraph +igraph_SOURCES = interface.c + +install-exec-hook: funcs.txt + for i in `cat funcs.txt`; do ln -sf igraph $(bindir)/$$i; done diff --git a/interfaces/shell/interface.c.in b/interfaces/shell/interface.c.in new file mode 100644 index 0000000..cb66758 --- /dev/null +++ b/interfaces/shell/interface.c.in @@ -0,0 +1,448 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "igraph.h" + +/* ------------------------------------------------------------------------ */ +/* Conversion & other common functions first */ +/* ------------------------------------------------------------------------ */ + + +int shell_igraph_usage(int argc, char **argv) { + printf("Command line interface to igraph\n"); + return 0; +} + +int shell_skip_whitespace(FILE *fin) { + int c; + c=fgetc(fin); + while (isspace(c)) { + c=fgetc(fin); + } + if (c!=EOF) { ungetc(c, fin); } + return 0; +} + +FILE *shell_open_file(const char *where, const char *mode) { + static const char *input="input"; + static const char *output="output"; + FILE *f; + + if (!strcmp(where, "-")) { + if (!strcmp(mode, "r")) { + return stdin; + } else if (!strcmp(mode, "w")) { + return stdout; + } + } + + f=fopen(where, mode); + if (!f) { + fprintf(stderr, "Cannot open %s file: `%s'\n", + !strcmp(mode, "r") ? input : output, where); + exit(1); + } + return f; +} + +int shell_read_graph(igraph_t *graph, const char *where) { + FILE *fin=shell_open_file(where, "r"); + igraph_read_graph_graphml(graph, fin, 0); + fclose(fin); + return 0; +} + +int shell_write_graph(const igraph_t *graph, const char *where) { + FILE *fout=shell_open_file(where, "w"); + igraph_write_graph_graphml(graph, fout); + fclose(fout); + return 0; +} + +int shell_read_vector(igraph_vector_t *v, const char *where) { + FILE *fin=shell_open_file(where, "r"); + igraph_real_t n; + int ret=1; + igraph_vector_init(v, 0); + while (ret > 0) { + ret=fscanf(fin, "%lf", &n); + if (ret > 0) { + igraph_vector_push_back(v, n); + } + } + fclose(fin); + if (ret != EOF) { + fprintf(stderr, "Error reading vector from file: `%s'\n", where); + exit(1); + } + + return 0; +} + +int shell_write_a_vector(const igraph_vector_t *v, FILE *fout, const char *where) { + int ret=1; + long int i, n=igraph_vector_size(v); + if (n>0) { ret=fprintf(fout, "%g", VECTOR(*v)[0]); } + if (ret <= 0) { + fprintf(stderr, "Cannot write vector to `%s'\n", where); + fclose(fout); + exit(1); + } + for (i=1; ifprintf(fout, "%li %li\n", nrow, ncol)) { + fprintf(stderr, "Error writing matrix to file '%s'.\n", where); + fclose(fout); + exit(1); + } + + for (i=0; ifprintf(fout, "%g", MATRIX(*m, i, j))) { + fprintf(stderr, "Error writing matrix to file '%s'.\n", where); + fclose(fout); + exit(1); + } + } + fprintf(fout, "\n"); + } + fclose(fout); + + return 0; +} + +int shell_read_integer(igraph_integer_t *n, const char *where) { + long int nn; + int ret=sscanf(where, "%li", &nn); + if (ret == EOF || ret == 0) { + fprintf(stderr, "Error, cannot interpret '%s' as integer\n", where); + exit(1); + } + *n=nn; + return 0; +} + +int shell_write_integer(igraph_integer_t n, const char *where) { + FILE *fout=shell_open_file(where, "w"); + int ret=fprintf(fout, "%li\n", (long int)n); + if (ret <= 0) { + fprintf(stderr, "Cannot write integer to '%s'\n", where); + fclose(fout); + exit(1); + } + return 0; +} + +int shell_read_boolean(igraph_bool_t *b, const char *where) { + if (strlen(where)==0 || + where[0]=='0' || + where[0]=='F' || + where[0]=='f') { + *b=0; + } else { + *b=1; + } + return 0; +} + +int shell_write_boolean(igraph_bool_t b, const char *where) { + int bb= b==0 ? 0 : 1; + FILE *fout=shell_open_file(where, "w"); + int ret=fprintf(fout, "%i\n", bb); + if (ret <= 0) { + fprintf(stderr, "Cannot write integer to '%s'\n", where); + fclose(fout); + exit(1); + } + return 0; +} + +int shell_read_real(igraph_real_t *b, const char *where) { + int ret=sscanf(where, "%lf", b); + if (ret == EOF || ret == 0) { + fprintf(stderr, "Error, cannot interpret '%s' as real\n", where); + exit(1); + } + return 0; +} + +int shell_write_real(igraph_real_t b, const char *where) { + FILE *fout=shell_open_file(where, "w"); + int ret=fprintf(fout, "%g\n", (double)b); + if (ret <= 0) { + fprintf(stderr, "Cannot write real to `%s'\n", where); + fclose(fout); + exit(1); + } + return 0; +} + +int shell_read_enum(void* value, const char *where, ...) { + int result=-1, *p=value; + va_list args; + va_start(args, where); + + while (1) { + char *name=va_arg(args, char*); + int code; + if (name) { + code=va_arg(args, int); + if (!strcmp(optarg, name)) { + result=code; + break; + } + } else { + break; + } + } + + if (result==-1) { + fprintf(stderr, "Cannot interpret argument: '%s'.\n", where); + exit(1); + } + + *p=result; + + return 0; +} + +int shell_read_int(int *value, const char *where) { + long int li=strtol(where, 0, 10); + if (errno || liINT_MAX) { + fprintf(stderr, "Integer too small/big: '%s'.\n", where); + } + *value=li; + return 0; +} + +int shell_read_longint(long int *value, const char *where) { + long int li=strtol(where, 0, 10); + if (errno) { + fprintf(stderr, "Long integer too small/big: '%s'.\n", where); + } + *value=li; + return 0; +} + +int shell_read_file(FILE **file, const char *where, const char *mode) { + *file=fopen(where, mode); + if (!*file) { + fprintf(stderr, "Cannot open file '%s'\n", where); + } + return 0; +} + +int shell_read_matrixlist(igraph_vector_ptr_t *list, const char *where) { + FILE *fin=shell_open_file(where, "r"); + igraph_vector_ptr_init(list, 0); + shell_skip_whitespace(fin); + while (!feof(fin)) { + igraph_matrix_t *m=malloc(sizeof(igraph_matrix_t)); + shell_read_a_matrix(m, fin, where); + igraph_vector_ptr_push_back(list, m); + shell_skip_whitespace(fin); + } + fclose(fin); + return 0; +} + +int shell_read_graphlist(igraph_vector_ptr_t *list, const char *where) { + FILE *fin=shell_open_file(where, "r"); + igraph_vector_ptr_init(list, 0); + shell_skip_whitespace(fin); + while (!feof(fin)) { + igraph_t *g=malloc(sizeof(igraph_t)); + igraph_read_graph_graphml(g, fin, 0); + igraph_vector_ptr_push_back(list, g); + shell_skip_whitespace(fin); + } + fclose(fin); + return 0; +} + +int shell_read_strvector(igraph_strvector_t *str, const char *where) { + FILE *fin=shell_open_file(where, "r"); + char *buffer=calloc(1000, sizeof(char)); + size_t size; + int c; + igraph_strvector_init(str, 0); + while(1) { + getline(&buffer, &size, fin); + igraph_strvector_add(str, buffer); + c=getc(fin); + if (!feof(fin)) { + ungetc(c, fin); + } else { + break; + } + } + fclose(fin); + free(buffer); + + return 0; +} + +int shell_write_vectorlist(igraph_vector_ptr_t *v, const char *where) { + FILE *fout=shell_open_file(where, "w"); + long int i, n=igraph_vector_ptr_size(v); + for (i=0; i0) { ret=fprintf(fout, "%i", VECTOR(*v)[0]==0 ? 0 : 1); } + if (ret <= 0) { + fprintf(stderr, "Cannot write vector to `%s'\n", where); + fclose(fout); + exit(1); + } + for (i=1; i +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/m4/libtool.m4 b/m4/libtool.m4 new file mode 100644 index 0000000..a3bc337 --- /dev/null +++ b/m4/libtool.m4 @@ -0,0 +1,8369 @@ +# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- +# +# Copyright (C) 1996-2001, 2003-2015 Free Software Foundation, Inc. +# Written by Gordon Matzigkeit, 1996 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +m4_define([_LT_COPYING], [dnl +# Copyright (C) 2014 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program or library that is built +# using GNU Libtool, you may include this file under the same +# distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +]) + +# serial 58 LT_INIT + + +# LT_PREREQ(VERSION) +# ------------------ +# Complain and exit if this libtool version is less that VERSION. +m4_defun([LT_PREREQ], +[m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1, + [m4_default([$3], + [m4_fatal([Libtool version $1 or higher is required], + 63)])], + [$2])]) + + +# _LT_CHECK_BUILDDIR +# ------------------ +# Complain if the absolute build directory name contains unusual characters +m4_defun([_LT_CHECK_BUILDDIR], +[case `pwd` in + *\ * | *\ *) + AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;; +esac +]) + + +# LT_INIT([OPTIONS]) +# ------------------ +AC_DEFUN([LT_INIT], +[AC_PREREQ([2.62])dnl We use AC_PATH_PROGS_FEATURE_CHECK +AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +AC_BEFORE([$0], [LT_LANG])dnl +AC_BEFORE([$0], [LT_OUTPUT])dnl +AC_BEFORE([$0], [LTDL_INIT])dnl +m4_require([_LT_CHECK_BUILDDIR])dnl + +dnl Autoconf doesn't catch unexpanded LT_ macros by default: +m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl +m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl +dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4 +dnl unless we require an AC_DEFUNed macro: +AC_REQUIRE([LTOPTIONS_VERSION])dnl +AC_REQUIRE([LTSUGAR_VERSION])dnl +AC_REQUIRE([LTVERSION_VERSION])dnl +AC_REQUIRE([LTOBSOLETE_VERSION])dnl +m4_require([_LT_PROG_LTMAIN])dnl + +_LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}]) + +dnl Parse OPTIONS +_LT_SET_OPTIONS([$0], [$1]) + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS=$ltmain + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' +AC_SUBST(LIBTOOL)dnl + +_LT_SETUP + +# Only expand once: +m4_define([LT_INIT]) +])# LT_INIT + +# Old names: +AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT]) +AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PROG_LIBTOOL], []) +dnl AC_DEFUN([AM_PROG_LIBTOOL], []) + + +# _LT_PREPARE_CC_BASENAME +# ----------------------- +m4_defun([_LT_PREPARE_CC_BASENAME], [ +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in @S|@*""; do + case $cc_temp in + compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;; + distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} +])# _LT_PREPARE_CC_BASENAME + + +# _LT_CC_BASENAME(CC) +# ------------------- +# It would be clearer to call AC_REQUIREs from _LT_PREPARE_CC_BASENAME, +# but that macro is also expanded into generated libtool script, which +# arranges for $SED and $ECHO to be set by different means. +m4_defun([_LT_CC_BASENAME], +[m4_require([_LT_PREPARE_CC_BASENAME])dnl +AC_REQUIRE([_LT_DECL_SED])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl +func_cc_basename $1 +cc_basename=$func_cc_basename_result +]) + + +# _LT_FILEUTILS_DEFAULTS +# ---------------------- +# It is okay to use these file commands and assume they have been set +# sensibly after 'm4_require([_LT_FILEUTILS_DEFAULTS])'. +m4_defun([_LT_FILEUTILS_DEFAULTS], +[: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} +])# _LT_FILEUTILS_DEFAULTS + + +# _LT_SETUP +# --------- +m4_defun([_LT_SETUP], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl + +_LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl +dnl +_LT_DECL([], [host_alias], [0], [The host system])dnl +_LT_DECL([], [host], [0])dnl +_LT_DECL([], [host_os], [0])dnl +dnl +_LT_DECL([], [build_alias], [0], [The build system])dnl +_LT_DECL([], [build], [0])dnl +_LT_DECL([], [build_os], [0])dnl +dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +dnl +AC_REQUIRE([AC_PROG_LN_S])dnl +test -z "$LN_S" && LN_S="ln -s" +_LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl +dnl +AC_REQUIRE([LT_CMD_MAX_LEN])dnl +_LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl +_LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl +dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl +m4_require([_LT_CMD_RELOAD])dnl +m4_require([_LT_CHECK_MAGIC_METHOD])dnl +m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl +m4_require([_LT_CMD_OLD_ARCHIVE])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_WITH_SYSROOT])dnl +m4_require([_LT_CMD_TRUNCATE])dnl + +_LT_CONFIG_LIBTOOL_INIT([ +# See if we are running on zsh, and set the options that allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi +]) +if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + +_LT_CHECK_OBJDIR + +m4_require([_LT_TAG_COMPILER])dnl + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a '.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld=$lt_cv_prog_gnu_ld + +old_CC=$CC +old_CFLAGS=$CFLAGS + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +_LT_CC_BASENAME([$compiler]) + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + _LT_PATH_MAGIC + fi + ;; +esac + +# Use C for the default configuration in the libtool script +LT_SUPPORTED_TAG([CC]) +_LT_LANG_C_CONFIG +_LT_LANG_DEFAULT_CONFIG +_LT_CONFIG_COMMANDS +])# _LT_SETUP + + +# _LT_PREPARE_SED_QUOTE_VARS +# -------------------------- +# Define a few sed substitution that help us do robust quoting. +m4_defun([_LT_PREPARE_SED_QUOTE_VARS], +[# Backslashify metacharacters that are still active within +# double-quoted strings. +sed_quote_subst='s/\([["`$\\]]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\([["`\\]]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' +]) + +# _LT_PROG_LTMAIN +# --------------- +# Note that this code is called both from 'configure', and 'config.status' +# now that we use AC_CONFIG_COMMANDS to generate libtool. Notably, +# 'config.status' has no value for ac_aux_dir unless we are using Automake, +# so we pass a copy along to make sure it has a sensible value anyway. +m4_defun([_LT_PROG_LTMAIN], +[m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl +_LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir']) +ltmain=$ac_aux_dir/ltmain.sh +])# _LT_PROG_LTMAIN + + +## ------------------------------------- ## +## Accumulate code for creating libtool. ## +## ------------------------------------- ## + +# So that we can recreate a full libtool script including additional +# tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS +# in macros and then make a single call at the end using the 'libtool' +# label. + + +# _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS]) +# ---------------------------------------- +# Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL_INIT], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_INIT], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_INIT]) + + +# _LT_CONFIG_LIBTOOL([COMMANDS]) +# ------------------------------ +# Register COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS]) + + +# _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS]) +# ----------------------------------------------------- +m4_defun([_LT_CONFIG_SAVE_COMMANDS], +[_LT_CONFIG_LIBTOOL([$1]) +_LT_CONFIG_LIBTOOL_INIT([$2]) +]) + + +# _LT_FORMAT_COMMENT([COMMENT]) +# ----------------------------- +# Add leading comment marks to the start of each line, and a trailing +# full-stop to the whole comment if one is not present already. +m4_define([_LT_FORMAT_COMMENT], +[m4_ifval([$1], [ +m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])], + [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.]) +)]) + + + +## ------------------------ ## +## FIXME: Eliminate VARNAME ## +## ------------------------ ## + + +# _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?]) +# ------------------------------------------------------------------- +# CONFIGNAME is the name given to the value in the libtool script. +# VARNAME is the (base) name used in the configure script. +# VALUE may be 0, 1 or 2 for a computed quote escaped value based on +# VARNAME. Any other value will be used directly. +m4_define([_LT_DECL], +[lt_if_append_uniq([lt_decl_varnames], [$2], [, ], + [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name], + [m4_ifval([$1], [$1], [$2])]) + lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3]) + m4_ifval([$4], + [lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])]) + lt_dict_add_subkey([lt_decl_dict], [$2], + [tagged?], [m4_ifval([$5], [yes], [no])])]) +]) + + +# _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION]) +# -------------------------------------------------------- +m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])]) + + +# lt_decl_tag_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_tag_varnames], +[_lt_decl_filter([tagged?], [yes], $@)]) + + +# _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..]) +# --------------------------------------------------------- +m4_define([_lt_decl_filter], +[m4_case([$#], + [0], [m4_fatal([$0: too few arguments: $#])], + [1], [m4_fatal([$0: too few arguments: $#: $1])], + [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)], + [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)], + [lt_dict_filter([lt_decl_dict], $@)])[]dnl +]) + + +# lt_decl_quote_varnames([SEPARATOR], [VARNAME1...]) +# -------------------------------------------------- +m4_define([lt_decl_quote_varnames], +[_lt_decl_filter([value], [1], $@)]) + + +# lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_dquote_varnames], +[_lt_decl_filter([value], [2], $@)]) + + +# lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_varnames_tagged], +[m4_assert([$# <= 2])dnl +_$0(m4_quote(m4_default([$1], [[, ]])), + m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]), + m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))]) +m4_define([_lt_decl_varnames_tagged], +[m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])]) + + +# lt_decl_all_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_all_varnames], +[_$0(m4_quote(m4_default([$1], [[, ]])), + m4_if([$2], [], + m4_quote(lt_decl_varnames), + m4_quote(m4_shift($@))))[]dnl +]) +m4_define([_lt_decl_all_varnames], +[lt_join($@, lt_decl_varnames_tagged([$1], + lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl +]) + + +# _LT_CONFIG_STATUS_DECLARE([VARNAME]) +# ------------------------------------ +# Quote a variable value, and forward it to 'config.status' so that its +# declaration there will have the same value as in 'configure'. VARNAME +# must have a single quote delimited value for this to work. +m4_define([_LT_CONFIG_STATUS_DECLARE], +[$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`']) + + +# _LT_CONFIG_STATUS_DECLARATIONS +# ------------------------------ +# We delimit libtool config variables with single quotes, so when +# we write them to config.status, we have to be sure to quote all +# embedded single quotes properly. In configure, this macro expands +# each variable declared with _LT_DECL (and _LT_TAGDECL) into: +# +# ='`$ECHO "$" | $SED "$delay_single_quote_subst"`' +m4_defun([_LT_CONFIG_STATUS_DECLARATIONS], +[m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames), + [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAGS +# ---------------- +# Output comment and list of tags supported by the script +m4_defun([_LT_LIBTOOL_TAGS], +[_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl +available_tags='_LT_TAGS'dnl +]) + + +# _LT_LIBTOOL_DECLARE(VARNAME, [TAG]) +# ----------------------------------- +# Extract the dictionary values for VARNAME (optionally with TAG) and +# expand to a commented shell variable setting: +# +# # Some comment about what VAR is for. +# visible_name=$lt_internal_name +m4_define([_LT_LIBTOOL_DECLARE], +[_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], + [description])))[]dnl +m4_pushdef([_libtool_name], + m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl +m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])), + [0], [_libtool_name=[$]$1], + [1], [_libtool_name=$lt_[]$1], + [2], [_libtool_name=$lt_[]$1], + [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl +m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl +]) + + +# _LT_LIBTOOL_CONFIG_VARS +# ----------------------- +# Produce commented declarations of non-tagged libtool config variables +# suitable for insertion in the LIBTOOL CONFIG section of the 'libtool' +# script. Tagged libtool config variables (even for the LIBTOOL CONFIG +# section) are produced by _LT_LIBTOOL_TAG_VARS. +m4_defun([_LT_LIBTOOL_CONFIG_VARS], +[m4_foreach([_lt_var], + m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAG_VARS(TAG) +# ------------------------- +m4_define([_LT_LIBTOOL_TAG_VARS], +[m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])]) + + +# _LT_TAGVAR(VARNAME, [TAGNAME]) +# ------------------------------ +m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])]) + + +# _LT_CONFIG_COMMANDS +# ------------------- +# Send accumulated output to $CONFIG_STATUS. Thanks to the lists of +# variables for single and double quote escaping we saved from calls +# to _LT_DECL, we can put quote escaped variables declarations +# into 'config.status', and then the shell code to quote escape them in +# for loops in 'config.status'. Finally, any additional code accumulated +# from calls to _LT_CONFIG_LIBTOOL_INIT is expanded. +m4_defun([_LT_CONFIG_COMMANDS], +[AC_PROVIDE_IFELSE([LT_OUTPUT], + dnl If the libtool generation code has been placed in $CONFIG_LT, + dnl instead of duplicating it all over again into config.status, + dnl then we will have config.status run $CONFIG_LT later, so it + dnl needs to know what name is stored there: + [AC_CONFIG_COMMANDS([libtool], + [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])], + dnl If the libtool generation code is destined for config.status, + dnl expand the accumulated commands and init code now: + [AC_CONFIG_COMMANDS([libtool], + [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])]) +])#_LT_CONFIG_COMMANDS + + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT], +[ + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +_LT_CONFIG_STATUS_DECLARATIONS +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$[]1 +_LTECHO_EOF' +} + +# Quote evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_quote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_dquote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +_LT_OUTPUT_LIBTOOL_INIT +]) + +# _LT_GENERATED_FILE_INIT(FILE, [COMMENT]) +# ------------------------------------ +# Generate a child script FILE with all initialization necessary to +# reuse the environment learned by the parent script, and make the +# file executable. If COMMENT is supplied, it is inserted after the +# '#!' sequence but before initialization text begins. After this +# macro, additional text can be appended to FILE to form the body of +# the child script. The macro ends with non-zero status if the +# file could not be fully written (such as if the disk is full). +m4_ifdef([AS_INIT_GENERATED], +[m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])], +[m4_defun([_LT_GENERATED_FILE_INIT], +[m4_require([AS_PREPARE])]dnl +[m4_pushdef([AS_MESSAGE_LOG_FD])]dnl +[lt_write_fail=0 +cat >$1 <<_ASEOF || lt_write_fail=1 +#! $SHELL +# Generated by $as_me. +$2 +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$1 <<\_ASEOF || lt_write_fail=1 +AS_SHELL_SANITIZE +_AS_PREPARE +exec AS_MESSAGE_FD>&1 +_ASEOF +test 0 = "$lt_write_fail" && chmod +x $1[]dnl +m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT + +# LT_OUTPUT +# --------- +# This macro allows early generation of the libtool script (before +# AC_OUTPUT is called), incase it is used in configure for compilation +# tests. +AC_DEFUN([LT_OUTPUT], +[: ${CONFIG_LT=./config.lt} +AC_MSG_NOTICE([creating $CONFIG_LT]) +_LT_GENERATED_FILE_INIT(["$CONFIG_LT"], +[# Run this file to recreate a libtool stub with the current configuration.]) + +cat >>"$CONFIG_LT" <<\_LTEOF +lt_cl_silent=false +exec AS_MESSAGE_LOG_FD>>config.log +{ + echo + AS_BOX([Running $as_me.]) +} >&AS_MESSAGE_LOG_FD + +lt_cl_help="\ +'$as_me' creates a local libtool stub from the current configuration, +for use in further configure time tests before the real libtool is +generated. + +Usage: $[0] [[OPTIONS]] + + -h, --help print this help, then exit + -V, --version print version number, then exit + -q, --quiet do not print progress messages + -d, --debug don't remove temporary files + +Report bugs to ." + +lt_cl_version="\ +m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl +m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION]) +configured by $[0], generated by m4_PACKAGE_STRING. + +Copyright (C) 2011 Free Software Foundation, Inc. +This config.lt script is free software; the Free Software Foundation +gives unlimited permision to copy, distribute and modify it." + +while test 0 != $[#] +do + case $[1] in + --version | --v* | -V ) + echo "$lt_cl_version"; exit 0 ;; + --help | --h* | -h ) + echo "$lt_cl_help"; exit 0 ;; + --debug | --d* | -d ) + debug=: ;; + --quiet | --q* | --silent | --s* | -q ) + lt_cl_silent=: ;; + + -*) AC_MSG_ERROR([unrecognized option: $[1] +Try '$[0] --help' for more information.]) ;; + + *) AC_MSG_ERROR([unrecognized argument: $[1] +Try '$[0] --help' for more information.]) ;; + esac + shift +done + +if $lt_cl_silent; then + exec AS_MESSAGE_FD>/dev/null +fi +_LTEOF + +cat >>"$CONFIG_LT" <<_LTEOF +_LT_OUTPUT_LIBTOOL_COMMANDS_INIT +_LTEOF + +cat >>"$CONFIG_LT" <<\_LTEOF +AC_MSG_NOTICE([creating $ofile]) +_LT_OUTPUT_LIBTOOL_COMMANDS +AS_EXIT(0) +_LTEOF +chmod +x "$CONFIG_LT" + +# configure is writing to config.log, but config.lt does its own redirection, +# appending to config.log, which fails on DOS, as config.log is still kept +# open by configure. Here we exec the FD to /dev/null, effectively closing +# config.log, so it can be properly (re)opened and appended to by config.lt. +lt_cl_success=: +test yes = "$silent" && + lt_config_lt_args="$lt_config_lt_args --quiet" +exec AS_MESSAGE_LOG_FD>/dev/null +$SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false +exec AS_MESSAGE_LOG_FD>>config.log +$lt_cl_success || AS_EXIT(1) +])# LT_OUTPUT + + +# _LT_CONFIG(TAG) +# --------------- +# If TAG is the built-in tag, create an initial libtool script with a +# default configuration from the untagged config vars. Otherwise add code +# to config.status for appending the configuration named by TAG from the +# matching tagged config vars. +m4_defun([_LT_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_CONFIG_SAVE_COMMANDS([ + m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl + m4_if(_LT_TAG, [C], [ + # See if we are running on zsh, and set the options that allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST + fi + + cfgfile=${ofile}T + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL +# Generated automatically by $as_me ($PACKAGE) $VERSION +# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`: +# NOTE: Changes made to this file will be lost: look at ltmain.sh. + +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit, 1996 + +_LT_COPYING +_LT_LIBTOOL_TAGS + +# Configured defaults for sys_lib_dlsearch_path munging. +: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} + +# ### BEGIN LIBTOOL CONFIG +_LT_LIBTOOL_CONFIG_VARS +_LT_LIBTOOL_TAG_VARS +# ### END LIBTOOL CONFIG + +_LT_EOF + + cat <<'_LT_EOF' >> "$cfgfile" + +# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE + +_LT_PREPARE_MUNGE_PATH_LIST +_LT_PREPARE_CC_BASENAME + +# ### END FUNCTIONS SHARED WITH CONFIGURE + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + _LT_PROG_LTMAIN + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" +], +[cat <<_LT_EOF >> "$ofile" + +dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded +dnl in a comment (ie after a #). +# ### BEGIN LIBTOOL TAG CONFIG: $1 +_LT_LIBTOOL_TAG_VARS(_LT_TAG) +# ### END LIBTOOL TAG CONFIG: $1 +_LT_EOF +])dnl /m4_if +], +[m4_if([$1], [], [ + PACKAGE='$PACKAGE' + VERSION='$VERSION' + RM='$RM' + ofile='$ofile'], []) +])dnl /_LT_CONFIG_SAVE_COMMANDS +])# _LT_CONFIG + + +# LT_SUPPORTED_TAG(TAG) +# --------------------- +# Trace this macro to discover what tags are supported by the libtool +# --tag option, using: +# autoconf --trace 'LT_SUPPORTED_TAG:$1' +AC_DEFUN([LT_SUPPORTED_TAG], []) + + +# C support is built-in for now +m4_define([_LT_LANG_C_enabled], []) +m4_define([_LT_TAGS], []) + + +# LT_LANG(LANG) +# ------------- +# Enable libtool support for the given language if not already enabled. +AC_DEFUN([LT_LANG], +[AC_BEFORE([$0], [LT_OUTPUT])dnl +m4_case([$1], + [C], [_LT_LANG(C)], + [C++], [_LT_LANG(CXX)], + [Go], [_LT_LANG(GO)], + [Java], [_LT_LANG(GCJ)], + [Fortran 77], [_LT_LANG(F77)], + [Fortran], [_LT_LANG(FC)], + [Windows Resource], [_LT_LANG(RC)], + [m4_ifdef([_LT_LANG_]$1[_CONFIG], + [_LT_LANG($1)], + [m4_fatal([$0: unsupported language: "$1"])])])dnl +])# LT_LANG + + +# _LT_LANG(LANGNAME) +# ------------------ +m4_defun([_LT_LANG], +[m4_ifdef([_LT_LANG_]$1[_enabled], [], + [LT_SUPPORTED_TAG([$1])dnl + m4_append([_LT_TAGS], [$1 ])dnl + m4_define([_LT_LANG_]$1[_enabled], [])dnl + _LT_LANG_$1_CONFIG($1)])dnl +])# _LT_LANG + + +m4_ifndef([AC_PROG_GO], [ +############################################################ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_GO. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +############################################################ +m4_defun([AC_PROG_GO], +[AC_LANG_PUSH(Go)dnl +AC_ARG_VAR([GOC], [Go compiler command])dnl +AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl +_AC_ARG_VAR_LDFLAGS()dnl +AC_CHECK_TOOL(GOC, gccgo) +if test -z "$GOC"; then + if test -n "$ac_tool_prefix"; then + AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo]) + fi +fi +if test -z "$GOC"; then + AC_CHECK_PROG(GOC, gccgo, gccgo, false) +fi +])#m4_defun +])#m4_ifndef + + +# _LT_LANG_DEFAULT_CONFIG +# ----------------------- +m4_defun([_LT_LANG_DEFAULT_CONFIG], +[AC_PROVIDE_IFELSE([AC_PROG_CXX], + [LT_LANG(CXX)], + [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])]) + +AC_PROVIDE_IFELSE([AC_PROG_F77], + [LT_LANG(F77)], + [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])]) + +AC_PROVIDE_IFELSE([AC_PROG_FC], + [LT_LANG(FC)], + [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])]) + +dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal +dnl pulling things in needlessly. +AC_PROVIDE_IFELSE([AC_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([LT_PROG_GCJ], + [LT_LANG(GCJ)], + [m4_ifdef([AC_PROG_GCJ], + [m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([A][M_PROG_GCJ], + [m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([LT_PROG_GCJ], + [m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])]) + +AC_PROVIDE_IFELSE([AC_PROG_GO], + [LT_LANG(GO)], + [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])]) + +AC_PROVIDE_IFELSE([LT_PROG_RC], + [LT_LANG(RC)], + [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])]) +])# _LT_LANG_DEFAULT_CONFIG + +# Obsolete macros: +AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)]) +AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)]) +AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)]) +AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)]) +AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_CXX], []) +dnl AC_DEFUN([AC_LIBTOOL_F77], []) +dnl AC_DEFUN([AC_LIBTOOL_FC], []) +dnl AC_DEFUN([AC_LIBTOOL_GCJ], []) +dnl AC_DEFUN([AC_LIBTOOL_RC], []) + + +# _LT_TAG_COMPILER +# ---------------- +m4_defun([_LT_TAG_COMPILER], +[AC_REQUIRE([AC_PROG_CC])dnl + +_LT_DECL([LTCC], [CC], [1], [A C compiler])dnl +_LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl +_LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl +_LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC +])# _LT_TAG_COMPILER + + +# _LT_COMPILER_BOILERPLATE +# ------------------------ +# Check for compiler boilerplate output or warnings with +# the simple compiler test code. +m4_defun([_LT_COMPILER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* +])# _LT_COMPILER_BOILERPLATE + + +# _LT_LINKER_BOILERPLATE +# ---------------------- +# Check for linker boilerplate output or warnings with +# the simple link test code. +m4_defun([_LT_LINKER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* +])# _LT_LINKER_BOILERPLATE + +# _LT_REQUIRED_DARWIN_CHECKS +# ------------------------- +m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[ + case $host_os in + rhapsody* | darwin*) + AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:]) + AC_CHECK_TOOL([NMEDIT], [nmedit], [:]) + AC_CHECK_TOOL([LIPO], [lipo], [:]) + AC_CHECK_TOOL([OTOOL], [otool], [:]) + AC_CHECK_TOOL([OTOOL64], [otool64], [:]) + _LT_DECL([], [DSYMUTIL], [1], + [Tool to manipulate archived DWARF debug symbol files on Mac OS X]) + _LT_DECL([], [NMEDIT], [1], + [Tool to change global to local symbols on Mac OS X]) + _LT_DECL([], [LIPO], [1], + [Tool to manipulate fat objects and archives on Mac OS X]) + _LT_DECL([], [OTOOL], [1], + [ldd/readelf like tool for Mach-O binaries on Mac OS X]) + _LT_DECL([], [OTOOL64], [1], + [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4]) + + AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod], + [lt_cv_apple_cc_single_mod=no + if test -z "$LT_MULTI_MODULE"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + # If there is a non-empty error log, and "single_module" + # appears in it, assume the flag caused a linker warning + if test -s conftest.err && $GREP single_module conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + # Otherwise, if the output was created with a 0 exit code from + # the compiler, it worked. + elif test -f libconftest.dylib && test 0 = "$_lt_result"; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi]) + + AC_CACHE_CHECK([for -exported_symbols_list linker flag], + [lt_cv_ld_exported_symbols_list], + [lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [lt_cv_ld_exported_symbols_list=yes], + [lt_cv_ld_exported_symbols_list=no]) + LDFLAGS=$save_LDFLAGS + ]) + + AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load], + [lt_cv_ld_force_load=no + cat > conftest.c << _LT_EOF +int forced_loaded() { return 2;} +_LT_EOF + echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD + echo "$AR cru libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD + $AR cru libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD + echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD + $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD + cat > conftest.c << _LT_EOF +int main() { return 0;} +_LT_EOF + echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err + _lt_result=$? + if test -s conftest.err && $GREP force_load conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then + lt_cv_ld_force_load=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -f conftest.err libconftest.a conftest conftest.c + rm -rf conftest.dSYM + ]) + case $host_os in + rhapsody* | darwin1.[[012]]) + _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[[91]]*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + 10.[[012]][[,.]]*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test yes = "$lt_cv_apple_cc_single_mod"; then + _lt_dar_single_mod='$single_module' + fi + if test yes = "$lt_cv_ld_exported_symbols_list"; then + _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' + fi + if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac +]) + + +# _LT_DARWIN_LINKER_FEATURES([TAG]) +# --------------------------------- +# Checks for linker and compiler features on darwin +m4_defun([_LT_DARWIN_LINKER_FEATURES], +[ + m4_require([_LT_REQUIRED_DARWIN_CHECKS]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_automatic, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + if test yes = "$lt_cv_ld_force_load"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes], + [FC], [_LT_TAGVAR(compiler_needs_object, $1)=yes]) + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='' + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + _LT_TAGVAR(module_expsym_cmds, $1)="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + m4_if([$1], [CXX], +[ if test yes != "$lt_cv_apple_cc_single_mod"; then + _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" + fi +],[]) + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi +]) + +# _LT_SYS_MODULE_PATH_AIX([TAGNAME]) +# ---------------------------------- +# Links a minimal program and checks the executable +# for the system default hardcoded library path. In most cases, +# this is /usr/lib:/lib, but when the MPI compilers are used +# the location of the communication and MPI libs are included too. +# If we don't find anything, use the default library path according +# to the aix ld manual. +# Store the results from the different compilers for each TAGNAME. +# Allow to override them for all tags through lt_cv_aix_libpath. +m4_defun([_LT_SYS_MODULE_PATH_AIX], +[m4_require([_LT_DECL_SED])dnl +if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])], + [AC_LINK_IFELSE([AC_LANG_PROGRAM],[ + lt_aix_libpath_sed='[ + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }]' + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi],[]) + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=/usr/lib:/lib + fi + ]) + aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1]) +fi +])# _LT_SYS_MODULE_PATH_AIX + + +# _LT_SHELL_INIT(ARG) +# ------------------- +m4_define([_LT_SHELL_INIT], +[m4_divert_text([M4SH-INIT], [$1 +])])# _LT_SHELL_INIT + + + +# _LT_PROG_ECHO_BACKSLASH +# ----------------------- +# Find how we can fake an echo command that does not interpret backslash. +# In particular, with Autoconf 2.60 or later we add some code to the start +# of the generated configure script that will find a shell with a builtin +# printf (that we can use as an echo command). +m4_defun([_LT_PROG_ECHO_BACKSLASH], +[ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + +AC_MSG_CHECKING([how to print strings]) +# Test print first, because it will be a builtin if present. +if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ + test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='print -r --' +elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='printf %s\n' +else + # Use this function as a fallback that always works. + func_fallback_echo () + { + eval 'cat <<_LTECHO_EOF +$[]1 +_LTECHO_EOF' + } + ECHO='func_fallback_echo' +fi + +# func_echo_all arg... +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "$*" +} + +case $ECHO in + printf*) AC_MSG_RESULT([printf]) ;; + print*) AC_MSG_RESULT([print -r]) ;; + *) AC_MSG_RESULT([cat]) ;; +esac + +m4_ifdef([_AS_DETECT_SUGGESTED], +[_AS_DETECT_SUGGESTED([ + test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || ( + ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + PATH=/empty FPATH=/empty; export PATH FPATH + test "X`printf %s $ECHO`" = "X$ECHO" \ + || test "X`print -r -- $ECHO`" = "X$ECHO" )])]) + +_LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts]) +_LT_DECL([], [ECHO], [1], [An echo program that protects backslashes]) +])# _LT_PROG_ECHO_BACKSLASH + + +# _LT_WITH_SYSROOT +# ---------------- +AC_DEFUN([_LT_WITH_SYSROOT], +[AC_MSG_CHECKING([for sysroot]) +AC_ARG_WITH([sysroot], +[AS_HELP_STRING([--with-sysroot@<:@=DIR@:>@], + [Search for dependent libraries within DIR (or the compiler's sysroot + if not specified).])], +[], [with_sysroot=no]) + +dnl lt_sysroot will always be passed unquoted. We quote it here +dnl in case the user passed a directory name. +lt_sysroot= +case $with_sysroot in #( + yes) + if test yes = "$GCC"; then + lt_sysroot=`$CC --print-sysroot 2>/dev/null` + fi + ;; #( + /*) + lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` + ;; #( + no|'') + ;; #( + *) + AC_MSG_RESULT([$with_sysroot]) + AC_MSG_ERROR([The sysroot must be an absolute path.]) + ;; +esac + + AC_MSG_RESULT([${lt_sysroot:-no}]) +_LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl +[dependent libraries, and where our libraries should be installed.])]) + +# _LT_ENABLE_LOCK +# --------------- +m4_defun([_LT_ENABLE_LOCK], +[AC_ARG_ENABLE([libtool-lock], + [AS_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test no = "$enable_libtool_lock" || enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out what ABI is being produced by ac_compile, and set mode + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE=32 + ;; + *ELF-64*) + HPUX_IA64_MODE=64 + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + if test yes = "$lt_cv_prog_gnu_ld"; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +mips64*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + emul=elf + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + emul="${emul}32" + ;; + *64-bit*) + emul="${emul}64" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *MSB*) + emul="${emul}btsmip" + ;; + *LSB*) + emul="${emul}ltsmip" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *N32*) + emul="${emul}n32" + ;; + esac + LD="${LD-ld} -m $emul" + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. Note that the listed cases only cover the + # situations where additional linker options are needed (such as when + # doing 32-bit compilation for a host where ld defaults to 64-bit, or + # vice versa); the common cases where no linker options are needed do + # not appear in the list. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + case `/usr/bin/file conftest.o` in + *x86-64*) + LD="${LD-ld} -m elf32_x86_64" + ;; + *) + LD="${LD-ld} -m elf_i386" + ;; + esac + ;; + powerpc64le-*linux*) + LD="${LD-ld} -m elf32lppclinux" + ;; + powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + powerpcle-*linux*) + LD="${LD-ld} -m elf64lppc" + ;; + powerpc-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -belf" + AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, + [AC_LANG_PUSH(C) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) + AC_LANG_POP]) + if test yes != "$lt_cv_cc_needs_belf"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS=$SAVE_CFLAGS + fi + ;; +*-*solaris*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) + case $host in + i?86-*-solaris*|x86_64-*-solaris*) + LD="${LD-ld} -m elf_x86_64" + ;; + sparc*-*-solaris*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + # GNU ld 2.21 introduced _sol2 emulations. Use them if available. + if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then + LD=${LD-ld}_sol2 + fi + ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks=$enable_libtool_lock +])# _LT_ENABLE_LOCK + + +# _LT_PROG_AR +# ----------- +m4_defun([_LT_PROG_AR], +[AC_CHECK_TOOLS(AR, [ar], false) +: ${AR=ar} +: ${AR_FLAGS=cru} +_LT_DECL([], [AR], [1], [The archiver]) +_LT_DECL([], [AR_FLAGS], [1], [Flags to create an archive]) + +AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file], + [lt_cv_ar_at_file=no + AC_COMPILE_IFELSE([AC_LANG_PROGRAM], + [echo conftest.$ac_objext > conftest.lst + lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD' + AC_TRY_EVAL([lt_ar_try]) + if test 0 -eq "$ac_status"; then + # Ensure the archiver fails upon bogus file names. + rm -f conftest.$ac_objext libconftest.a + AC_TRY_EVAL([lt_ar_try]) + if test 0 -ne "$ac_status"; then + lt_cv_ar_at_file=@ + fi + fi + rm -f conftest.* libconftest.a + ]) + ]) + +if test no = "$lt_cv_ar_at_file"; then + archiver_list_spec= +else + archiver_list_spec=$lt_cv_ar_at_file +fi +_LT_DECL([], [archiver_list_spec], [1], + [How to feed a file listing to the archiver]) +])# _LT_PROG_AR + + +# _LT_CMD_OLD_ARCHIVE +# ------------------- +m4_defun([_LT_CMD_OLD_ARCHIVE], +[_LT_PROG_AR + +AC_CHECK_TOOL(STRIP, strip, :) +test -z "$STRIP" && STRIP=: +_LT_DECL([], [STRIP], [1], [A symbol stripping program]) + +AC_CHECK_TOOL(RANLIB, ranlib, :) +test -z "$RANLIB" && RANLIB=: +_LT_DECL([], [RANLIB], [1], + [Commands used to install an old-style archive]) + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + bitrig* | openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" +fi + +case $host_os in + darwin*) + lock_old_archive_extraction=yes ;; + *) + lock_old_archive_extraction=no ;; +esac +_LT_DECL([], [old_postinstall_cmds], [2]) +_LT_DECL([], [old_postuninstall_cmds], [2]) +_LT_TAGDECL([], [old_archive_cmds], [2], + [Commands used to build an old-style archive]) +_LT_DECL([], [lock_old_archive_extraction], [0], + [Whether to use a lock for old archive extraction]) +])# _LT_CMD_OLD_ARCHIVE + + +# _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------------------- +# Check whether the given compiler option works +AC_DEFUN([_LT_COMPILER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$3" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + fi + $RM conftest* +]) + +if test yes = "[$]$2"; then + m4_if([$5], , :, [$5]) +else + m4_if([$6], , :, [$6]) +fi +])# _LT_COMPILER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], []) + + +# _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------- +# Check whether the given linker option works +AC_DEFUN([_LT_LINKER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $3" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&AS_MESSAGE_LOG_FD + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + else + $2=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS +]) + +if test yes = "[$]$2"; then + m4_if([$4], , :, [$4]) +else + m4_if([$5], , :, [$5]) +fi +])# _LT_LINKER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], []) + + +# LT_CMD_MAX_LEN +#--------------- +AC_DEFUN([LT_CMD_MAX_LEN], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +# find the maximum length of command line arguments +AC_MSG_CHECKING([the maximum length of command line arguments]) +AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl + i=0 + teststring=ABCD + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + mint*) + # On MiNT this can take a long time and run out of memory. + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + os2*) + # The test takes a long time on OS/2. + lt_cv_sys_max_cmd_len=8192 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[ ]]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len" && \ + test undefined != "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test X`env echo "$teststring$teststring" 2>/dev/null` \ + = "X$teststring$teststring"; } >/dev/null 2>&1 && + test 17 != "$i" # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac +]) +if test -n "$lt_cv_sys_max_cmd_len"; then + AC_MSG_RESULT($lt_cv_sys_max_cmd_len) +else + AC_MSG_RESULT(none) +fi +max_cmd_len=$lt_cv_sys_max_cmd_len +_LT_DECL([], [max_cmd_len], [0], + [What is the maximum length of a command?]) +])# LT_CMD_MAX_LEN + +# Old name: +AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], []) + + +# _LT_HEADER_DLFCN +# ---------------- +m4_defun([_LT_HEADER_DLFCN], +[AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl +])# _LT_HEADER_DLFCN + + +# _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, +# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) +# ---------------------------------------------------------------- +m4_defun([_LT_TRY_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes = "$cross_compiling"; then : + [$4] +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +[#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +}] +_LT_EOF + if AC_TRY_EVAL(ac_link) && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) $1 ;; + x$lt_dlneed_uscore) $2 ;; + x$lt_dlunknown|x*) $3 ;; + esac + else : + # compilation failed + $3 + fi +fi +rm -fr conftest* +])# _LT_TRY_DLOPEN_SELF + + +# LT_SYS_DLOPEN_SELF +# ------------------ +AC_DEFUN([LT_SYS_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes != "$enable_dlopen"; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen=load_add_on + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen=LoadLibrary + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl],[ + lt_cv_dlopen=dyld + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ]) + ;; + + tpf*) + # Don't try to run any link tests for TPF. We know it's impossible + # because TPF is a cross-compiler, and we know how we open DSOs. + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + lt_cv_dlopen_self=no + ;; + + *) + AC_CHECK_FUNC([shl_load], + [lt_cv_dlopen=shl_load], + [AC_CHECK_LIB([dld], [shl_load], + [lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld], + [AC_CHECK_FUNC([dlopen], + [lt_cv_dlopen=dlopen], + [AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl], + [AC_CHECK_LIB([svld], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld], + [AC_CHECK_LIB([dld], [dld_link], + [lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld]) + ]) + ]) + ]) + ]) + ]) + ;; + esac + + if test no = "$lt_cv_dlopen"; then + enable_dlopen=no + else + enable_dlopen=yes + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS=$CPPFLAGS + test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS=$LDFLAGS + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS=$LIBS + LIBS="$lt_cv_dlopen_libs $LIBS" + + AC_CACHE_CHECK([whether a program can dlopen itself], + lt_cv_dlopen_self, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, + lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) + ]) + + if test yes = "$lt_cv_dlopen_self"; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + AC_CACHE_CHECK([whether a statically linked program can dlopen itself], + lt_cv_dlopen_self_static, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, + lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) + ]) + fi + + CPPFLAGS=$save_CPPFLAGS + LDFLAGS=$save_LDFLAGS + LIBS=$save_LIBS + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi +_LT_DECL([dlopen_support], [enable_dlopen], [0], + [Whether dlopen is supported]) +_LT_DECL([dlopen_self], [enable_dlopen_self], [0], + [Whether dlopen of programs is supported]) +_LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0], + [Whether dlopen of statically linked programs is supported]) +])# LT_SYS_DLOPEN_SELF + +# Old name: +AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], []) + + +# _LT_COMPILER_C_O([TAGNAME]) +# --------------------------- +# Check to see if options -c and -o are simultaneously supported by compiler. +# This macro does not hard code the compiler like AC_PROG_CC_C_O. +m4_defun([_LT_COMPILER_C_O], +[m4_require([_LT_DECL_SED])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + fi + fi + chmod u+w . 2>&AS_MESSAGE_LOG_FD + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* +]) +_LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1], + [Does compiler simultaneously support -c and -o options?]) +])# _LT_COMPILER_C_O + + +# _LT_COMPILER_FILE_LOCKS([TAGNAME]) +# ---------------------------------- +# Check to see if we can do hard links to lock some files if needed +m4_defun([_LT_COMPILER_FILE_LOCKS], +[m4_require([_LT_ENABLE_LOCK])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_COMPILER_C_O([$1]) + +hard_links=nottested +if test no = "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + AC_MSG_CHECKING([if we can lock with hard links]) + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + AC_MSG_RESULT([$hard_links]) + if test no = "$hard_links"; then + AC_MSG_WARN(['$CC' does not support '-c -o', so 'make -j' may be unsafe]) + need_locks=warn + fi +else + need_locks=no +fi +_LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?]) +])# _LT_COMPILER_FILE_LOCKS + + +# _LT_CHECK_OBJDIR +# ---------------- +m4_defun([_LT_CHECK_OBJDIR], +[AC_CACHE_CHECK([for objdir], [lt_cv_objdir], +[rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null]) +objdir=$lt_cv_objdir +_LT_DECL([], [objdir], [0], + [The name of the directory that contains temporary libtool files])dnl +m4_pattern_allow([LT_OBJDIR])dnl +AC_DEFINE_UNQUOTED([LT_OBJDIR], "$lt_cv_objdir/", + [Define to the sub-directory where libtool stores uninstalled libraries.]) +])# _LT_CHECK_OBJDIR + + +# _LT_LINKER_HARDCODE_LIBPATH([TAGNAME]) +# -------------------------------------- +# Check hardcoding attributes. +m4_defun([_LT_LINKER_HARDCODE_LIBPATH], +[AC_MSG_CHECKING([how to hardcode library paths into programs]) +_LT_TAGVAR(hardcode_action, $1)= +if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" || + test -n "$_LT_TAGVAR(runpath_var, $1)" || + test yes = "$_LT_TAGVAR(hardcode_automatic, $1)"; then + + # We can hardcode non-existent directories. + if test no != "$_LT_TAGVAR(hardcode_direct, $1)" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" && + test no != "$_LT_TAGVAR(hardcode_minus_L, $1)"; then + # Linking always hardcodes the temporary library directory. + _LT_TAGVAR(hardcode_action, $1)=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + _LT_TAGVAR(hardcode_action, $1)=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + _LT_TAGVAR(hardcode_action, $1)=unsupported +fi +AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)]) + +if test relink = "$_LT_TAGVAR(hardcode_action, $1)" || + test yes = "$_LT_TAGVAR(inherit_rpath, $1)"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi +_LT_TAGDECL([], [hardcode_action], [0], + [How to hardcode a shared library path into an executable]) +])# _LT_LINKER_HARDCODE_LIBPATH + + +# _LT_CMD_STRIPLIB +# ---------------- +m4_defun([_LT_CMD_STRIPLIB], +[m4_require([_LT_DECL_EGREP]) +striplib= +old_striplib= +AC_MSG_CHECKING([whether stripping libraries is possible]) +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + AC_MSG_RESULT([yes]) +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP"; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac +fi +_LT_DECL([], [old_striplib], [1], [Commands to strip libraries]) +_LT_DECL([], [striplib], [1]) +])# _LT_CMD_STRIPLIB + + +# _LT_PREPARE_MUNGE_PATH_LIST +# --------------------------- +# Make sure func_munge_path_list() is defined correctly. +m4_defun([_LT_PREPARE_MUNGE_PATH_LIST], +[[# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x@S|@2 in + x) + ;; + *:) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'` \@S|@@S|@1\" + ;; + x:*) + eval @S|@1=\"\@S|@@S|@1 `$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval @S|@1=\"\@S|@@S|@1\ `$ECHO @S|@2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval @S|@1=\"`$ECHO @S|@2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \@S|@@S|@1\" + ;; + *) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + esac +} +]])# _LT_PREPARE_PATH_LIST + + +# _LT_SYS_DYNAMIC_LINKER([TAG]) +# ----------------------------- +# PORTME Fill in your ld.so characteristics +m4_defun([_LT_SYS_DYNAMIC_LINKER], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_OBJDUMP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PREPARE_MUNGE_PATH_LIST])dnl +AC_MSG_CHECKING([dynamic linker characteristics]) +m4_if([$1], + [], [ +if test yes = "$GCC"; then + case $host_os in + darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; + *) lt_awk_arg='/^libraries:/' ;; + esac + case $host_os in + mingw* | cegcc*) lt_sed_strip_eq='s|=\([[A-Za-z]]:\)|\1|g' ;; + *) lt_sed_strip_eq='s|=/|/|g' ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` + case $lt_search_path_spec in + *\;*) + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` + ;; + *) + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` + ;; + esac + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary... + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + # ...but if some path component already ends with the multilib dir we assume + # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). + case "$lt_multi_os_dir; $lt_search_path_spec " in + "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) + lt_multi_os_dir= + ;; + esac + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" + elif test -n "$lt_multi_os_dir"; then + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' +BEGIN {RS = " "; FS = "/|\n";} { + lt_foo = ""; + lt_count = 0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo = "/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[[lt_foo]]++; } + if (lt_freq[[lt_foo]] == 1) { print lt_foo; } +}'` + # AWK program above erroneously prepends '/' to C:/dos/paths + # for these hosts. + case $host_os in + mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ + $SED 's|/\([[A-Za-z]]:\)|\1|g'` ;; + esac + sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi]) +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +AC_ARG_VAR([LT_SYS_LIBRARY_PATH], +[User-defined run-time library search path.]) + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[[4-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[[01]] | aix4.[[01]].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a[(]lib.so.V[)]' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)], lib.a[(]lib.so.V[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a[(]lib.so.V[)], lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[[45]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"]) + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"]) + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[[23]].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[[01]]* | freebsdelf3.[[01]]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \ + freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[[3-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath], + [lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \ + LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\"" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null], + [lt_cv_shlibpath_overrides_runpath=yes])]) + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + ]) + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +AC_MSG_RESULT([$dynamic_linker]) +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + +_LT_DECL([], [variables_saved_for_relink], [1], + [Variables whose values should be saved in libtool wrapper scripts and + restored at link time]) +_LT_DECL([], [need_lib_prefix], [0], + [Do we need the "lib" prefix for modules?]) +_LT_DECL([], [need_version], [0], [Do we need a version for libraries?]) +_LT_DECL([], [version_type], [0], [Library versioning type]) +_LT_DECL([], [runpath_var], [0], [Shared library runtime path variable]) +_LT_DECL([], [shlibpath_var], [0],[Shared library path variable]) +_LT_DECL([], [shlibpath_overrides_runpath], [0], + [Is shlibpath searched before the hard-coded library search path?]) +_LT_DECL([], [libname_spec], [1], [Format of library name prefix]) +_LT_DECL([], [library_names_spec], [1], + [[List of archive names. First name is the real one, the rest are links. + The last name is the one that the linker finds with -lNAME]]) +_LT_DECL([], [soname_spec], [1], + [[The coded name of the library, if different from the real name]]) +_LT_DECL([], [install_override_mode], [1], + [Permission mode override for installation of shared libraries]) +_LT_DECL([], [postinstall_cmds], [2], + [Command to use after installation of a shared archive]) +_LT_DECL([], [postuninstall_cmds], [2], + [Command to use after uninstallation of a shared archive]) +_LT_DECL([], [finish_cmds], [2], + [Commands used to finish a libtool library installation in a directory]) +_LT_DECL([], [finish_eval], [1], + [[As "finish_cmds", except a single script fragment to be evaled but + not shown]]) +_LT_DECL([], [hardcode_into_libs], [0], + [Whether we should hardcode library paths into libraries]) +_LT_DECL([], [sys_lib_search_path_spec], [2], + [Compile-time system search path for libraries]) +_LT_DECL([sys_lib_dlsearch_path_spec], [configure_time_dlsearch_path], [2], + [Detected run-time system search path for libraries]) +_LT_DECL([], [configure_time_lt_sys_library_path], [2], + [Explicit LT_SYS_LIBRARY_PATH set during ./configure time]) +])# _LT_SYS_DYNAMIC_LINKER + + +# _LT_PATH_TOOL_PREFIX(TOOL) +# -------------------------- +# find a file program that can recognize shared library +AC_DEFUN([_LT_PATH_TOOL_PREFIX], +[m4_require([_LT_DECL_EGREP])dnl +AC_MSG_CHECKING([for $1]) +AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, +[case $MAGIC_CMD in +[[\\/*] | ?:[\\/]*]) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR +dnl $ac_dummy forces splitting on constant user-supplied paths. +dnl POSIX.2 word splitting is done only on the output of word expansions, +dnl not every word. This closes a longstanding sh security hole. + ac_dummy="m4_if([$2], , $PATH, [$2])" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$1"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"$1" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac]) +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + AC_MSG_RESULT($MAGIC_CMD) +else + AC_MSG_RESULT(no) +fi +_LT_DECL([], [MAGIC_CMD], [0], + [Used to examine libraries when file_magic_cmd begins with "file"])dnl +])# _LT_PATH_TOOL_PREFIX + +# Old name: +AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], []) + + +# _LT_PATH_MAGIC +# -------------- +# find a file program that can recognize a shared library +m4_defun([_LT_PATH_MAGIC], +[_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) + else + MAGIC_CMD=: + fi +fi +])# _LT_PATH_MAGIC + + +# LT_PATH_LD +# ---------- +# find the pathname to the GNU or non-GNU linker +AC_DEFUN([LT_PATH_LD], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PROG_ECHO_BACKSLASH])dnl + +AC_ARG_WITH([gnu-ld], + [AS_HELP_STRING([--with-gnu-ld], + [assume the C compiler uses GNU ld @<:@default=no@:>@])], + [test no = "$withval" || with_gnu_ld=yes], + [with_gnu_ld=no])dnl + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(lt_cv_path_LD, +[if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &1 conftest.i +cat conftest.i conftest.i >conftest2.i +: ${lt_DD:=$DD} +AC_PATH_PROGS_FEATURE_CHECK([lt_DD], [dd], +[if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: +fi]) +rm -f conftest.i conftest2.i conftest.out]) +])# _LT_PATH_DD + + +# _LT_CMD_TRUNCATE +# ---------------- +# find command to truncate a binary pipe +m4_defun([_LT_CMD_TRUNCATE], +[m4_require([_LT_PATH_DD]) +AC_CACHE_CHECK([how to truncate binary pipes], [lt_cv_truncate_bin], +[printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +lt_cv_truncate_bin= +if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" +fi +rm -f conftest.i conftest2.i conftest.out +test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q"]) +_LT_DECL([lt_truncate_bin], [lt_cv_truncate_bin], [1], + [Command to truncate a binary pipe]) +])# _LT_CMD_TRUNCATE + + +# _LT_CHECK_MAGIC_METHOD +# ---------------------- +# how to check for library dependencies +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_MAGIC_METHOD], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +AC_CACHE_CHECK([how to recognize dependent libraries], +lt_cv_deplibs_check_method, +[lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# 'unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# that responds to the $file_magic_cmd with a given extended regex. +# If you have 'file' or equivalent on your system and you're not sure +# whether 'pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[[4-9]]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[[45]]*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + if ( file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + # Keep this pattern in sync with the one in func_win32_libid. + lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc*) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +haiku*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'] + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[[3-9]]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd* | bitrig*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +os2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac +]) + +file_magic_glob= +want_nocaseglob=no +if test "$build" = "$host"; then + case $host_os in + mingw* | pw32*) + if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then + want_nocaseglob=yes + else + file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"` + fi + ;; + esac +fi + +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + +_LT_DECL([], [deplibs_check_method], [1], + [Method to check whether dependent libraries are shared objects]) +_LT_DECL([], [file_magic_cmd], [1], + [Command to use when deplibs_check_method = "file_magic"]) +_LT_DECL([], [file_magic_glob], [1], + [How to find potential files when deplibs_check_method = "file_magic"]) +_LT_DECL([], [want_nocaseglob], [1], + [Find potential files using nocaseglob when deplibs_check_method = "file_magic"]) +])# _LT_CHECK_MAGIC_METHOD + + +# LT_PATH_NM +# ---------- +# find the pathname to a BSD- or MS-compatible name lister +AC_DEFUN([LT_PATH_NM], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM, +[if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM=$NM +else + lt_nm_to_check=${ac_tool_prefix}nm + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + tmp_nm=$ac_dir/$lt_tmp_nm + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the 'sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty + case $build_os in + mingw*) lt_bad_file=conftest.nm/nofile ;; + *) lt_bad_file=/dev/null ;; + esac + case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in + *$lt_bad_file* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break 2 + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break 2 + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS=$lt_save_ifs + done + : ${lt_cv_path_NM=no} +fi]) +if test no != "$lt_cv_path_NM"; then + NM=$lt_cv_path_NM +else + # Didn't find any BSD compatible name lister, look for dumpbin. + if test -n "$DUMPBIN"; then : + # Let the user override the test. + else + AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :) + case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in + *COFF*) + DUMPBIN="$DUMPBIN -symbols -headers" + ;; + *) + DUMPBIN=: + ;; + esac + fi + AC_SUBST([DUMPBIN]) + if test : != "$DUMPBIN"; then + NM=$DUMPBIN + fi +fi +test -z "$NM" && NM=nm +AC_SUBST([NM]) +_LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl + +AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface], + [lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD) + cat conftest.out >&AS_MESSAGE_LOG_FD + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest*]) +])# LT_PATH_NM + +# Old names: +AU_ALIAS([AM_PROG_NM], [LT_PATH_NM]) +AU_ALIAS([AC_PROG_NM], [LT_PATH_NM]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_PROG_NM], []) +dnl AC_DEFUN([AC_PROG_NM], []) + +# _LT_CHECK_SHAREDLIB_FROM_LINKLIB +# -------------------------------- +# how to determine the name of the shared library +# associated with a specific link library. +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +m4_require([_LT_DECL_DLLTOOL]) +AC_CACHE_CHECK([how to associate runtime and link libraries], +lt_cv_sharedlib_from_linklib_cmd, +[lt_cv_sharedlib_from_linklib_cmd='unknown' + +case $host_os in +cygwin* | mingw* | pw32* | cegcc*) + # two different shell functions defined in ltmain.sh; + # decide which one to use based on capabilities of $DLLTOOL + case `$DLLTOOL --help 2>&1` in + *--identify-strict*) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib + ;; + *) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback + ;; + esac + ;; +*) + # fallback: assume linklib IS sharedlib + lt_cv_sharedlib_from_linklib_cmd=$ECHO + ;; +esac +]) +sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd +test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO + +_LT_DECL([], [sharedlib_from_linklib_cmd], [1], + [Command to associate shared and link libraries]) +])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB + + +# _LT_PATH_MANIFEST_TOOL +# ---------------------- +# locate the manifest tool +m4_defun([_LT_PATH_MANIFEST_TOOL], +[AC_CHECK_TOOL(MANIFEST_TOOL, mt, :) +test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt +AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_mainfest_tool], + [lt_cv_path_mainfest_tool=no + echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD + $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out + cat conftest.err >&AS_MESSAGE_LOG_FD + if $GREP 'Manifest Tool' conftest.out > /dev/null; then + lt_cv_path_mainfest_tool=yes + fi + rm -f conftest*]) +if test yes != "$lt_cv_path_mainfest_tool"; then + MANIFEST_TOOL=: +fi +_LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl +])# _LT_PATH_MANIFEST_TOOL + + +# _LT_DLL_DEF_P([FILE]) +# --------------------- +# True iff FILE is a Windows DLL '.def' file. +# Keep in sync with func_dll_def_p in the libtool script +AC_DEFUN([_LT_DLL_DEF_P], +[dnl + test DEF = "`$SED -n dnl + -e '\''s/^[[ ]]*//'\'' dnl Strip leading whitespace + -e '\''/^\(;.*\)*$/d'\'' dnl Delete empty lines and comments + -e '\''s/^\(EXPORTS\|LIBRARY\)\([[ ]].*\)*$/DEF/p'\'' dnl + -e q dnl Only consider the first "real" line + $1`" dnl +])# _LT_DLL_DEF_P + + +# LT_LIB_M +# -------- +# check for math library +AC_DEFUN([LT_LIB_M], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +LIBM= +case $host in +*-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*) + # These system don't have libm, or don't need it + ;; +*-ncr-sysv4.3*) + AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM=-lmw) + AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") + ;; +*) + AC_CHECK_LIB(m, cos, LIBM=-lm) + ;; +esac +AC_SUBST([LIBM]) +])# LT_LIB_M + +# Old name: +AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_CHECK_LIBM], []) + + +# _LT_COMPILER_NO_RTTI([TAGNAME]) +# ------------------------------- +m4_defun([_LT_COMPILER_NO_RTTI], +[m4_require([_LT_TAG_COMPILER])dnl + +_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + +if test yes = "$GCC"; then + case $cc_basename in + nvcc*) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;; + *) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;; + esac + + _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], + lt_cv_prog_compiler_rtti_exceptions, + [-fno-rtti -fno-exceptions], [], + [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) +fi +_LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1], + [Compiler flag to turn off builtin functions]) +])# _LT_COMPILER_NO_RTTI + + +# _LT_CMD_GLOBAL_SYMBOLS +# ---------------------- +m4_defun([_LT_CMD_GLOBAL_SYMBOLS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([LT_PATH_NM])dnl +AC_REQUIRE([LT_PATH_LD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_TAG_COMPILER])dnl + +# Check for command to grab the raw symbol name followed by C symbol from nm. +AC_MSG_CHECKING([command to parse $NM output from $compiler object]) +AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], +[ +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[[BCDEGRST]]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[[BCDT]]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[[ABCDGISTW]]' + ;; +hpux*) + if test ia64 = "$host_cpu"; then + symcode='[[ABCDEGRST]]' + fi + ;; +irix* | nonstopux*) + symcode='[[BCDEGRST]]' + ;; +osf*) + symcode='[[BCDEGQRST]]' + ;; +solaris*) + symcode='[[BDRT]]' + ;; +sco3.2v5*) + symcode='[[DT]]' + ;; +sysv4.2uw2*) + symcode='[[DT]]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[[ABDT]]' + ;; +sysv4) + symcode='[[DFNSTU]]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[[ABCDGIRSTW]]' ;; +esac + +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Gets list of data symbols to import. + lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" + # Adjust the below global symbol transforms to fixup imported variables. + lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" + lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" + lt_c_name_lib_hook="\ + -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ + -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" +else + # Disable hooks by default. + lt_cv_sys_global_symbol_to_import= + lt_cdecl_hook= + lt_c_name_hook= + lt_c_name_lib_hook= +fi + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n"\ +$lt_cdecl_hook\ +" -e 's/^T .* \(.*\)$/extern int \1();/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ +$lt_c_name_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" + +# Transform an extracted symbol line into symbol name with lib prefix and +# symbol address. +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ +$lt_c_name_lib_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function, + # D for any global variable and I for any imported variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK ['"\ +" {last_section=section; section=\$ 3};"\ +" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ +" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ +" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ +" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ +" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx]" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if AC_TRY_EVAL(ac_compile); then + # Now try to grab the symbols. + nlist=conftest.nm + if AC_TRY_EVAL(NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT@&t@_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT@&t@_DLSYM_CONST +#else +# define LT@&t@_DLSYM_CONST const +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +LT@&t@_DLSYM_CONST struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[[]] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_globsym_save_LIBS=$LIBS + lt_globsym_save_CFLAGS=$CFLAGS + LIBS=conftstm.$ac_objext + CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" + if AC_TRY_EVAL(ac_link) && test -s conftest$ac_exeext; then + pipe_works=yes + fi + LIBS=$lt_globsym_save_LIBS + CFLAGS=$lt_globsym_save_CFLAGS + else + echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD + fi + else + echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test yes = "$pipe_works"; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done +]) +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + AC_MSG_RESULT(failed) +else + AC_MSG_RESULT(ok) +fi + +# Response file support. +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + nm_file_list_spec='@' +elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then + nm_file_list_spec='@' +fi + +_LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1], + [Take the output of nm and produce a listing of raw symbols and C names]) +_LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1], + [Transform the output of nm in a proper C declaration]) +_LT_DECL([global_symbol_to_import], [lt_cv_sys_global_symbol_to_import], [1], + [Transform the output of nm into a list of symbols to manually relocate]) +_LT_DECL([global_symbol_to_c_name_address], + [lt_cv_sys_global_symbol_to_c_name_address], [1], + [Transform the output of nm in a C name address pair]) +_LT_DECL([global_symbol_to_c_name_address_lib_prefix], + [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1], + [Transform the output of nm in a C name address pair when lib prefix is needed]) +_LT_DECL([nm_interface], [lt_cv_nm_interface], [1], + [The name lister interface]) +_LT_DECL([], [nm_file_list_spec], [1], + [Specify filename containing input files for $NM]) +]) # _LT_CMD_GLOBAL_SYMBOLS + + +# _LT_COMPILER_PIC([TAGNAME]) +# --------------------------- +m4_defun([_LT_COMPILER_PIC], +[m4_require([_LT_TAG_COMPILER])dnl +_LT_TAGVAR(lt_prog_compiler_wl, $1)= +_LT_TAGVAR(lt_prog_compiler_pic, $1)= +_LT_TAGVAR(lt_prog_compiler_static, $1)= + +m4_if([$1], [CXX], [ + # C++ specific cases for pic, static, wl, etc. + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + case $host_os in + aix[[4-9]]*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + dgux*) + case $cc_basename in + ec++*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + fi + ;; + aCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # KAI C++ Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + ecpc* ) + # old Intel C++ for x86_64, which still supported -KPIC. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + icpc* ) + # Intel C++, used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*) + # IBM XL 8.0, 9.0 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd*) + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + cxx*) + # Digital/Compaq C++ + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + lcc*) + # Lucid + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + *) + ;; + esac + ;; + vxworks*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +], +[ + if test yes = "$GCC"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + + case $cc_basename in + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker ' + if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)" + fi + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + case $cc_basename in + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + hpux9* | hpux10* | hpux11*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC (with -KPIC) is the default. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + # old Intel for x86_64, which still supported -KPIC. + ecc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared' + _LT_TAGVAR(lt_prog_compiler_static, $1)='--static' + ;; + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + ccc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All Alpha code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xl* | bgxl* | bgf* | mpixl*) + # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='' + ;; + *Sun\ F* | *Sun*Fortran*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + *Sun\ C*) + # Sun C 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + ;; + *Intel*\ [[CF]]*Compiler*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + *Portland\ Group*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + esac + ;; + + newsos6) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All OSF/1 code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + rdos*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + solaris*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';; + *) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';; + esac + ;; + + sunos4*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + unicos*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + + uts4*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +]) +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])" + ;; +esac + +AC_CACHE_CHECK([for $compiler option to produce PIC], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)]) +_LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1) + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works], + [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)], + [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [], + [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in + "" | " "*) ;; + *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;; + esac], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) +fi +_LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1], + [Additional compiler flags for building library objects]) + +_LT_TAGDECL([wl], [lt_prog_compiler_wl], [1], + [How to pass a linker flag through the compiler]) +# +# Check to make sure the static flag actually works. +# +wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\" +_LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works], + _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1), + $lt_tmp_static_flag, + [], + [_LT_TAGVAR(lt_prog_compiler_static, $1)=]) +_LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1], + [Compiler flag to prevent dynamic linking]) +])# _LT_COMPILER_PIC + + +# _LT_LINKER_SHLIBS([TAGNAME]) +# ---------------------------- +# See if the linker supports building shared libraries. +m4_defun([_LT_LINKER_SHLIBS], +[AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +m4_if([$1], [CXX], [ + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + case $host_os in + aix[[4-9]]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + _LT_TAGVAR(export_symbols_cmds, $1)=$ltdll_cmds + ;; + cygwin* | mingw* | cegcc*) + case $cc_basename in + cl*) + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + ;; + esac + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac +], [ + runpath_var= + _LT_TAGVAR(allow_undefined_flag, $1)= + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(archive_cmds, $1)= + _LT_TAGVAR(archive_expsym_cmds, $1)= + _LT_TAGVAR(compiler_needs_object, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(hardcode_automatic, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(hardcode_libdir_separator, $1)= + _LT_TAGVAR(hardcode_minus_L, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_TAGVAR(inherit_rpath, $1)=no + _LT_TAGVAR(link_all_deplibs, $1)=unknown + _LT_TAGVAR(module_cmds, $1)= + _LT_TAGVAR(module_expsym_cmds, $1)= + _LT_TAGVAR(old_archive_from_new_cmds, $1)= + _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)= + _LT_TAGVAR(thread_safe_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + _LT_TAGVAR(include_expsyms, $1)= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ' (' and ')$', so one must not match beginning or + # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', + # as well as any symbol that contains 'd'. + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. +dnl Note also adjust exclude_expsyms for C++ above. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test yes != "$GCC"; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd* | bitrig*) + with_gnu_ld=no + ;; + esac + + _LT_TAGVAR(ld_shlibs, $1)=yes + + # On some targets, GNU ld is compatible enough with the native linker + # that we're better off using the native interface for both. + lt_use_gnu_ld_interface=no + if test yes = "$with_gnu_ld"; then + case $host_os in + aix*) + # The AIX port of GNU ld has always aspired to compatibility + # with the native linker. However, as the warning in the GNU ld + # block says, versions before 2.19.5* couldn't really create working + # shared libraries, regardless of the interface used. + case `$LD -v 2>&1` in + *\ \(GNU\ Binutils\)\ 2.19.5*) ;; + *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;; + *\ \(GNU\ Binutils\)\ [[3-9]]*) ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + fi + + if test yes = "$lt_use_gnu_ld_interface"; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='$wl' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + supports_anon_versioning=no + case `$LD -v | $SED -e 's/([^)]\+)\s\+//' 2>&1` in + *GNU\ gold*) supports_anon_versioning=yes ;; + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[[3-9]]*) + # On AIX/PPC, the GNU linker is very broken + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.19, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to install binutils +*** 2.20 or above, or modify your PATH so that a non-GNU linker is found. +*** You will then need to restart the configuration process. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + tmp_diet=no + if test linux-dietlibc = "$host_os"; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test no = "$tmp_diet" + then + tmp_addflag=' $pic_flag' + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group f77 and f90 compilers + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + _LT_TAGVAR(whole_archive_flag_spec, $1)= + tmp_sharedflag='--shared' ;; + nagfor*) # NAGFOR 5.3 + tmp_sharedflag='-Wl,-shared' ;; + xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + tcc*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-rdynamic' + ;; + xlf* | bgf* | bgxlf* | mpixlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*) + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + sunos4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + + if test no = "$_LT_TAGVAR(ld_shlibs, $1)"; then + runpath_var= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + _LT_TAGVAR(hardcode_direct, $1)=unsupported + fi + ;; + + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then + aix_use_runtimelinking=yes + break + fi + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # traditional, no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GCC"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + ;; + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag="$shared_flag "'$wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared libraries. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + bsdi[[45]]*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + case $cc_basename in + cl*) + # Native MSVC + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols' + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # Assume MSVC wrapper + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + # FIXME: Should let the user specify the lib program. + _LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + esac + ;; + + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + dgux*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2.*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + hpux9*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + + hpux10*) + if test yes,no = "$GCC,$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + fi + ;; + + hpux11*) + if test yes,no = "$GCC,$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + m4_if($1, [], [ + # Older versions of the 11.00 compiler do not understand -b yet + # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) + _LT_LINKER_OPTION([if $CC understands -b], + _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'], + [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags']) + ;; + esac + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + # This should be the same for all languages, so no per-tag cache variable. + AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol], + [lt_cv_irix_exported_symbol], + [save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" + AC_LINK_IFELSE( + [AC_LANG_SOURCE( + [AC_LANG_CASE([C], [[int foo (void) { return 0; }]], + [C++], [[int foo (void) { return 0; }]], + [Fortran 77], [[ + subroutine foo + end]], + [Fortran], [[ + subroutine foo + end]])])], + [lt_cv_irix_exported_symbol=yes], + [lt_cv_irix_exported_symbol=no]) + LDFLAGS=$save_LDFLAGS]) + if test yes = "$lt_cv_irix_exported_symbol"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' + fi + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + linux*) + case $cc_basename in + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(ld_shlibs, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + _LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + newsos6) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *nto* | *qnx*) + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + fi + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + osf3*) + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + solaris*) + _LT_TAGVAR(no_undefined_flag, $1)=' -z defs' + if test yes = "$GCC"; then + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + _LT_TAGVAR(archive_cmds, $1)='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. GCC discards it without '$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test yes = "$GCC"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + fi + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + sunos4*) + if test sequent = "$host_vendor"; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4) + case $host_vendor in + sni) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' + _LT_TAGVAR(hardcode_direct, $1)=no + ;; + motorola) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4.3*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + _LT_TAGVAR(ld_shlibs, $1)=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + if test sni = "$host_vendor"; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Blargedynsym' + ;; + esac + fi + fi +]) +AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) +test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + +_LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld + +_LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl +_LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl +_LT_DECL([], [extract_expsyms_cmds], [2], + [The commands to extract the exported symbol list from a shared archive]) + +# +# Do we need to explicitly link libc? +# +case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in +x|xyes) + # Assume -lc should be added + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $_LT_TAGVAR(archive_cmds, $1) in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + AC_CACHE_CHECK([whether -lc should be explicitly linked in], + [lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1), + [$RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if AC_TRY_EVAL(ac_compile) 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) + pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1) + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1) + _LT_TAGVAR(allow_undefined_flag, $1)= + if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) + then + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no + else + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes + fi + _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + ]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1) + ;; + esac + fi + ;; +esac + +_LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0], + [Whether or not to add -lc for building shared libraries]) +_LT_TAGDECL([allow_libtool_libs_with_static_runtimes], + [enable_shared_with_static_runtimes], [0], + [Whether or not to disallow shared libs when runtime libs are static]) +_LT_TAGDECL([], [export_dynamic_flag_spec], [1], + [Compiler flag to allow reflexive dlopens]) +_LT_TAGDECL([], [whole_archive_flag_spec], [1], + [Compiler flag to generate shared objects directly from archives]) +_LT_TAGDECL([], [compiler_needs_object], [1], + [Whether the compiler copes with passing no objects directly]) +_LT_TAGDECL([], [old_archive_from_new_cmds], [2], + [Create an old-style archive from a shared archive]) +_LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2], + [Create a temporary old-style archive to link instead of a shared archive]) +_LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive]) +_LT_TAGDECL([], [archive_expsym_cmds], [2]) +_LT_TAGDECL([], [module_cmds], [2], + [Commands used to build a loadable module if different from building + a shared archive.]) +_LT_TAGDECL([], [module_expsym_cmds], [2]) +_LT_TAGDECL([], [with_gnu_ld], [1], + [Whether we are building with GNU ld or not]) +_LT_TAGDECL([], [allow_undefined_flag], [1], + [Flag that allows shared libraries with undefined symbols to be built]) +_LT_TAGDECL([], [no_undefined_flag], [1], + [Flag that enforces no undefined symbols]) +_LT_TAGDECL([], [hardcode_libdir_flag_spec], [1], + [Flag to hardcode $libdir into a binary during linking. + This must work even if $libdir does not exist]) +_LT_TAGDECL([], [hardcode_libdir_separator], [1], + [Whether we need a single "-rpath" flag with a separated argument]) +_LT_TAGDECL([], [hardcode_direct], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary]) +_LT_TAGDECL([], [hardcode_direct_absolute], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary and the resulting library dependency is + "absolute", i.e impossible to change by setting $shlibpath_var if the + library is relocated]) +_LT_TAGDECL([], [hardcode_minus_L], [0], + [Set to "yes" if using the -LDIR flag during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_shlibpath_var], [0], + [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_automatic], [0], + [Set to "yes" if building a shared library automatically hardcodes DIR + into the library and all subsequent libraries and executables linked + against it]) +_LT_TAGDECL([], [inherit_rpath], [0], + [Set to yes if linker adds runtime paths of dependent libraries + to runtime path list]) +_LT_TAGDECL([], [link_all_deplibs], [0], + [Whether libtool must link a program against all its dependency libraries]) +_LT_TAGDECL([], [always_export_symbols], [0], + [Set to "yes" if exported symbols are required]) +_LT_TAGDECL([], [export_symbols_cmds], [2], + [The commands to list exported symbols]) +_LT_TAGDECL([], [exclude_expsyms], [1], + [Symbols that should not be listed in the preloaded symbols]) +_LT_TAGDECL([], [include_expsyms], [1], + [Symbols that must always be exported]) +_LT_TAGDECL([], [prelink_cmds], [2], + [Commands necessary for linking programs (against libraries) with templates]) +_LT_TAGDECL([], [postlink_cmds], [2], + [Commands necessary for finishing linking programs]) +_LT_TAGDECL([], [file_list_spec], [1], + [Specify filename containing input files]) +dnl FIXME: Not yet implemented +dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1], +dnl [Compiler flag to generate thread safe objects]) +])# _LT_LINKER_SHLIBS + + +# _LT_LANG_C_CONFIG([TAG]) +# ------------------------ +# Ensure that the configuration variables for a C compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_C_CONFIG], +[m4_require([_LT_DECL_EGREP])dnl +lt_save_CC=$CC +AC_LANG_PUSH(C) + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + +_LT_TAG_COMPILER +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + LT_SYS_DLOPEN_SELF + _LT_CMD_STRIPLIB + + # Report what library types will actually be built + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_CONFIG($1) +fi +AC_LANG_POP +CC=$lt_save_CC +])# _LT_LANG_C_CONFIG + + +# _LT_LANG_CXX_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a C++ compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_CXX_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +if test -n "$CXX" && ( test no != "$CXX" && + ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || + (test g++ != "$CXX"))); then + AC_PROG_CXXCPP +else + _lt_caught_CXX_error=yes +fi + +AC_LANG_PUSH(C++) +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(compiler_needs_object, $1)=no +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the CXX compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_caught_CXX_error"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="int some_variable = 0;" + + # Code to be used in simple link tests + lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }' + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_CFLAGS=$CFLAGS + lt_save_LD=$LD + lt_save_GCC=$GCC + GCC=$GXX + lt_save_with_gnu_ld=$with_gnu_ld + lt_save_path_LD=$lt_cv_path_LD + if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx + else + $as_unset lt_cv_prog_gnu_ld + fi + if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX + else + $as_unset lt_cv_path_LD + fi + test -z "${LDCXX+set}" || LD=$LDCXX + CC=${CXX-"c++"} + CFLAGS=$CXXFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + # We don't want -fno-exception when compiling C++ code, so set the + # no_builtin_flag separately + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' + else + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + fi + + if test yes = "$GXX"; then + # Set up default GNU C++ configuration + + LT_PATH_LD + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test yes = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='$wl' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | + $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + GXX=no + with_gnu_ld=no + wlarc= + fi + + # PORTME: fill in a description of your system's C++ link characteristics + AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) + _LT_TAGVAR(ld_shlibs, $1)=yes + case $host_os in + aix3*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GXX"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag=$shared_flag' $wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to + # export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + # The "-G" linker flag allows undefined symbols. + _LT_TAGVAR(no_undefined_flag, $1)='-bernotok' + # Determine the default libpath from the value encoded in an empty + # executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared + # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + cygwin* | mingw* | pw32* | cegcc*) + case $GXX,$cc_basename in + ,cl* | no,cl*) + # Native MSVC + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + func_to_tool_file "$lt_outputfile"~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # g++ + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + freebsd2.*) + # C++ shared libraries reported to be fairly broken before + # switch to ELF + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + freebsd-elf*) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + hpux9*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + hpux10*|hpux11*) + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' + fi + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + esac + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc* | ecpc* ) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + case `$CC -V` in + *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*) + _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ + compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' + _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ + $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ + $RANLIB $oldlib' + _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 6 and above use weak symbols + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl--rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + ;; + cxx*) + # Compaq C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' + ;; + xl* | mpixl* | bgxl*) + # IBM XL 8.0 on PPC, with GNU ld + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + + lynxos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + m88k*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + + *nto* | *qnx*) + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + fi + output_verbose_link_cmd=func_echo_all + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + case $host in + osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;; + *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;; + esac + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + cxx*) + case $host in + osf3*) + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + ;; + *) + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ + $RM $lib.exp' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + case $host in + osf3*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + psos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(archive_cmds_need_lc,$1)=yes + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-z ${wl}defs' + if $CC --version | $GREP -v '^2\.7' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + else + # g++ 2.7 appears to require '-G' NOT '-shared' on this + # platform. + _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + fi + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $wl$libdir' + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~ + '"$_LT_TAGVAR(old_archive_cmds, $1)" + _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~ + '"$_LT_TAGVAR(reload_cmds, $1)" + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + vxworks*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) + test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + + _LT_TAGVAR(GCC, $1)=$GXX + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS + LDCXX=$LD + LD=$lt_save_LD + GCC=$lt_save_GCC + with_gnu_ld=$lt_save_with_gnu_ld + lt_cv_path_LDCXX=$lt_cv_path_LD + lt_cv_path_LD=$lt_save_path_LD + lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld + lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +fi # test yes != "$_lt_caught_CXX_error" + +AC_LANG_POP +])# _LT_LANG_CXX_CONFIG + + +# _LT_FUNC_STRIPNAME_CNF +# ---------------------- +# func_stripname_cnf prefix suffix name +# strip PREFIX and SUFFIX off of NAME. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +# +# This function is identical to the (non-XSI) version of func_stripname, +# except this one can be used by m4 code that may be executed by configure, +# rather than the libtool script. +m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl +AC_REQUIRE([_LT_DECL_SED]) +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH]) +func_stripname_cnf () +{ + case @S|@2 in + .*) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%\\\\@S|@2\$%%"`;; + *) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%@S|@2\$%%"`;; + esac +} # func_stripname_cnf +])# _LT_FUNC_STRIPNAME_CNF + + +# _LT_SYS_HIDDEN_LIBDEPS([TAGNAME]) +# --------------------------------- +# Figure out "hidden" library dependencies from verbose +# compiler output when linking a shared library. +# Parse the compiler output and extract the necessary +# objects, libraries and library flags. +m4_defun([_LT_SYS_HIDDEN_LIBDEPS], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl +# Dependencies to place before and after the object being linked: +_LT_TAGVAR(predep_objects, $1)= +_LT_TAGVAR(postdep_objects, $1)= +_LT_TAGVAR(predeps, $1)= +_LT_TAGVAR(postdeps, $1)= +_LT_TAGVAR(compiler_lib_search_path, $1)= + +dnl we can't use the lt_simple_compile_test_code here, +dnl because it contains code intended for an executable, +dnl not a library. It's possible we should let each +dnl tag define a new lt_????_link_test_code variable, +dnl but it's only used here... +m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF +int a; +void foo (void) { a = 0; } +_LT_EOF +], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF +class Foo +{ +public: + Foo (void) { a = 0; } +private: + int a; +}; +_LT_EOF +], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer*4 a + a=0 + return + end +_LT_EOF +], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer a + a=0 + return + end +_LT_EOF +], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF +public class foo { + private int a; + public void bar (void) { + a = 0; + } +}; +_LT_EOF +], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF +package foo +func foo() { +} +_LT_EOF +]) + +_lt_libdeps_save_CFLAGS=$CFLAGS +case "$CC $CFLAGS " in #( +*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; +*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; +*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; +esac + +dnl Parse the compiler output and extract the necessary +dnl objects, libraries and library flags. +if AC_TRY_EVAL(ac_compile); then + # Parse the compiler output and extract the necessary + # objects, libraries and library flags. + + # Sentinel used to keep track of whether or not we are before + # the conftest object file. + pre_test_object_deps_done=no + + for p in `eval "$output_verbose_link_cmd"`; do + case $prev$p in + + -L* | -R* | -l*) + # Some compilers place space between "-{L,R}" and the path. + # Remove the space. + if test x-L = "$p" || + test x-R = "$p"; then + prev=$p + continue + fi + + # Expand the sysroot to ease extracting the directories later. + if test -z "$prev"; then + case $p in + -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; + -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; + -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; + esac + fi + case $p in + =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; + esac + if test no = "$pre_test_object_deps_done"; then + case $prev in + -L | -R) + # Internal compiler library paths should come after those + # provided the user. The postdeps already come after the + # user supplied libs so there is no need to process them. + if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then + _LT_TAGVAR(compiler_lib_search_path, $1)=$prev$p + else + _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} $prev$p" + fi + ;; + # The "-l" case would never come before the object being + # linked, so don't bother handling this case. + esac + else + if test -z "$_LT_TAGVAR(postdeps, $1)"; then + _LT_TAGVAR(postdeps, $1)=$prev$p + else + _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} $prev$p" + fi + fi + prev= + ;; + + *.lto.$objext) ;; # Ignore GCC LTO objects + *.$objext) + # This assumes that the test object file only shows up + # once in the compiler output. + if test "$p" = "conftest.$objext"; then + pre_test_object_deps_done=yes + continue + fi + + if test no = "$pre_test_object_deps_done"; then + if test -z "$_LT_TAGVAR(predep_objects, $1)"; then + _LT_TAGVAR(predep_objects, $1)=$p + else + _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p" + fi + else + if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then + _LT_TAGVAR(postdep_objects, $1)=$p + else + _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p" + fi + fi + ;; + + *) ;; # Ignore the rest. + + esac + done + + # Clean up. + rm -f a.out a.exe +else + echo "libtool.m4: error: problem compiling $1 test program" +fi + +$RM -f confest.$objext +CFLAGS=$_lt_libdeps_save_CFLAGS + +# PORTME: override above test on systems where it is broken +m4_if([$1], [CXX], +[case $host_os in +interix[[3-9]]*) + # Interix 3.5 installs completely hosed .la files for C++, so rather than + # hack all around it, let's just trust "g++" to DTRT. + _LT_TAGVAR(predep_objects,$1)= + _LT_TAGVAR(postdep_objects,$1)= + _LT_TAGVAR(postdeps,$1)= + ;; +esac +]) + +case " $_LT_TAGVAR(postdeps, $1) " in +*" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;; +esac + _LT_TAGVAR(compiler_lib_search_dirs, $1)= +if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then + _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | $SED -e 's! -L! !g' -e 's!^ !!'` +fi +_LT_TAGDECL([], [compiler_lib_search_dirs], [1], + [The directories searched by this compiler when creating a shared library]) +_LT_TAGDECL([], [predep_objects], [1], + [Dependencies to place before and after the objects being linked to + create a shared library]) +_LT_TAGDECL([], [postdep_objects], [1]) +_LT_TAGDECL([], [predeps], [1]) +_LT_TAGDECL([], [postdeps], [1]) +_LT_TAGDECL([], [compiler_lib_search_path], [1], + [The library search path used internally by the compiler when linking + a shared library]) +])# _LT_SYS_HIDDEN_LIBDEPS + + +# _LT_LANG_F77_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a Fortran 77 compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_F77_CONFIG], +[AC_LANG_PUSH(Fortran 77) +if test -z "$F77" || test no = "$F77"; then + _lt_disable_F77=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for f77 test sources. +ac_ext=f + +# Object file extension for compiled f77 test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the F77 compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_F77"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${F77-"f77"} + CFLAGS=$FFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + GCC=$G77 + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$G77 + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_F77" + +AC_LANG_POP +])# _LT_LANG_F77_CONFIG + + +# _LT_LANG_FC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for a Fortran compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_FC_CONFIG], +[AC_LANG_PUSH(Fortran) + +if test -z "$FC" || test no = "$FC"; then + _lt_disable_FC=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for fc test sources. +ac_ext=${ac_fc_srcext-f} + +# Object file extension for compiled fc test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the FC compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_FC"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${FC-"f95"} + CFLAGS=$FCFLAGS + compiler=$CC + GCC=$ac_cv_fc_compiler_gnu + + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$ac_cv_fc_compiler_gnu + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_FC" + +AC_LANG_POP +])# _LT_LANG_FC_CONFIG + + +# _LT_LANG_GCJ_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Java Compiler compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GCJ_CONFIG], +[AC_REQUIRE([LT_PROG_GCJ])dnl +AC_LANG_SAVE + +# Source file extension for Java test sources. +ac_ext=java + +# Object file extension for compiled Java test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="class foo {}" + +# Code to be used in simple link tests +lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GCJ-"gcj"} +CFLAGS=$GCJFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# GCJ did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GCJ_CONFIG + + +# _LT_LANG_GO_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Go compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GO_CONFIG], +[AC_REQUIRE([LT_PROG_GO])dnl +AC_LANG_SAVE + +# Source file extension for Go test sources. +ac_ext=go + +# Object file extension for compiled Go test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="package main; func main() { }" + +# Code to be used in simple link tests +lt_simple_link_test_code='package main; func main() { }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GOC-"gccgo"} +CFLAGS=$GOFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# Go did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GO_CONFIG + + +# _LT_LANG_RC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for the Windows resource compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_RC_CONFIG], +[AC_REQUIRE([LT_PROG_RC])dnl +AC_LANG_SAVE + +# Source file extension for RC test sources. +ac_ext=rc + +# Object file extension for compiled RC test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }' + +# Code to be used in simple link tests +lt_simple_link_test_code=$lt_simple_compile_test_code + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC= +CC=${RC-"windres"} +CFLAGS= +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_CC_BASENAME([$compiler]) +_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + +if test -n "$compiler"; then + : + _LT_CONFIG($1) +fi + +GCC=$lt_save_GCC +AC_LANG_RESTORE +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_RC_CONFIG + + +# LT_PROG_GCJ +# ----------- +AC_DEFUN([LT_PROG_GCJ], +[m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ], + [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ], + [AC_CHECK_TOOL(GCJ, gcj,) + test set = "${GCJFLAGS+set}" || GCJFLAGS="-g -O2" + AC_SUBST(GCJFLAGS)])])[]dnl +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_GCJ], []) + + +# LT_PROG_GO +# ---------- +AC_DEFUN([LT_PROG_GO], +[AC_CHECK_TOOL(GOC, gccgo,) +]) + + +# LT_PROG_RC +# ---------- +AC_DEFUN([LT_PROG_RC], +[AC_CHECK_TOOL(RC, windres,) +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_RC], []) + + +# _LT_DECL_EGREP +# -------------- +# If we don't have a new enough Autoconf to choose the best grep +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_EGREP], +[AC_REQUIRE([AC_PROG_EGREP])dnl +AC_REQUIRE([AC_PROG_FGREP])dnl +test -z "$GREP" && GREP=grep +_LT_DECL([], [GREP], [1], [A grep program that handles long lines]) +_LT_DECL([], [EGREP], [1], [An ERE matcher]) +_LT_DECL([], [FGREP], [1], [A literal string matcher]) +dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too +AC_SUBST([GREP]) +]) + + +# _LT_DECL_OBJDUMP +# -------------- +# If we don't have a new enough Autoconf to choose the best objdump +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_OBJDUMP], +[AC_CHECK_TOOL(OBJDUMP, objdump, false) +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [An object symbol dumper]) +AC_SUBST([OBJDUMP]) +]) + +# _LT_DECL_DLLTOOL +# ---------------- +# Ensure DLLTOOL variable is set. +m4_defun([_LT_DECL_DLLTOOL], +[AC_CHECK_TOOL(DLLTOOL, dlltool, false) +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [1], [DLL creation program]) +AC_SUBST([DLLTOOL]) +]) + +# _LT_DECL_SED +# ------------ +# Check for a fully-functional sed program, that truncates +# as few characters as possible. Prefer GNU sed if found. +m4_defun([_LT_DECL_SED], +[AC_PROG_SED +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" +_LT_DECL([], [SED], [1], [A sed program that does not truncate output]) +_LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"], + [Sed that helps us avoid accidentally triggering echo(1) options like -n]) +])# _LT_DECL_SED + +m4_ifndef([AC_PROG_SED], [ +############################################################ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_SED. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +############################################################ + +m4_defun([AC_PROG_SED], +[AC_MSG_CHECKING([for a sed that does not truncate output]) +AC_CACHE_VAL(lt_cv_path_SED, +[# Loop through the user's path and test for sed and gsed. +# Then use that list of sed's as ones to test for truncation. +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for lt_ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then + lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" + fi + done + done +done +IFS=$as_save_IFS +lt_ac_max=0 +lt_ac_count=0 +# Add /usr/xpg4/bin/sed as it is typically found on Solaris +# along with /bin/sed that truncates output. +for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do + test ! -f "$lt_ac_sed" && continue + cat /dev/null > conftest.in + lt_ac_count=0 + echo $ECHO_N "0123456789$ECHO_C" >conftest.in + # Check for GNU sed and select it if it is found. + if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then + lt_cv_path_SED=$lt_ac_sed + break + fi + while true; do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo >>conftest.nl + $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break + cmp -s conftest.out conftest.nl || break + # 10000 chars as input seems more than enough + test 10 -lt "$lt_ac_count" && break + lt_ac_count=`expr $lt_ac_count + 1` + if test "$lt_ac_count" -gt "$lt_ac_max"; then + lt_ac_max=$lt_ac_count + lt_cv_path_SED=$lt_ac_sed + fi + done +done +]) +SED=$lt_cv_path_SED +AC_SUBST([SED]) +AC_MSG_RESULT([$SED]) +])#AC_PROG_SED +])#m4_ifndef + +# Old name: +AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_SED], []) + + +# _LT_CHECK_SHELL_FEATURES +# ------------------------ +# Find out whether the shell is Bourne or XSI compatible, +# or has some other useful features. +m4_defun([_LT_CHECK_SHELL_FEATURES], +[if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi +_LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac +_LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl +_LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl +])# _LT_CHECK_SHELL_FEATURES + + +# _LT_PATH_CONVERSION_FUNCTIONS +# ----------------------------- +# Determine what file name conversion functions should be used by +# func_to_host_file (and, implicitly, by func_to_host_path). These are needed +# for certain cross-compile configurations and native mingw. +m4_defun([_LT_PATH_CONVERSION_FUNCTIONS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_MSG_CHECKING([how to convert $build file names to $host format]) +AC_CACHE_VAL(lt_cv_to_host_file_cmd, +[case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 + ;; + esac + ;; + *-*-cygwin* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin + ;; + esac + ;; + * ) # unhandled hosts (and "normal" native builds) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; +esac +]) +to_host_file_cmd=$lt_cv_to_host_file_cmd +AC_MSG_RESULT([$lt_cv_to_host_file_cmd]) +_LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd], + [0], [convert $build file names to $host format])dnl + +AC_MSG_CHECKING([how to convert $build file names to toolchain format]) +AC_CACHE_VAL(lt_cv_to_tool_file_cmd, +[#assume ordinary cross tools, or native build. +lt_cv_to_tool_file_cmd=func_convert_file_noop +case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 + ;; + esac + ;; +esac +]) +to_tool_file_cmd=$lt_cv_to_tool_file_cmd +AC_MSG_RESULT([$lt_cv_to_tool_file_cmd]) +_LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd], + [0], [convert $build files to toolchain format])dnl +])# _LT_PATH_CONVERSION_FUNCTIONS diff --git a/optional/simpleraytracer/Color.cpp b/optional/simpleraytracer/Color.cpp new file mode 100755 index 0000000..3b31cf0 --- /dev/null +++ b/optional/simpleraytracer/Color.cpp @@ -0,0 +1,96 @@ +#include "Color.h" +#include "unit_limiter.h" + +namespace igraph { + +Color::Color() +{ +} + +Color::Color(double vRed, double vGreen, double vBlue, + double vTransparent) +{ + Red(vRed); + Green(vGreen); + Blue(vBlue); + Transparent(vTransparent); +} + +Color::~Color() +{ +} + +// returns multiplication of a scalar with this vector +Color Color::operator* (double vRhs) const +{ + return Color(mRed*vRhs, mGreen*vRhs, mBlue*vRhs, mTransparent); +} + +// returns the addition of this color with another color +Color Color::operator+ (const Color& vRhs) const +{ + double trans=Transparent() > vRhs.Transparent() ? Transparent() : + vRhs.Transparent(); + return Color(Red()+vRhs.Red(),Green()+vRhs.Green(),Blue()+vRhs.Blue(), + trans); +} + +void Color::Red(double vRed) +{ + mRed = unit_limiter(vRed); +} +double Color::Red() const +{ + return mRed; +} +void Color::Green(double vGreen) +{ + mGreen = unit_limiter(vGreen); + +} +double Color::Green() const +{ + return mGreen; +} +void Color::Blue(double vBlue) +{ + mBlue = unit_limiter(vBlue); +} +double Color::Blue() const +{ + return mBlue; +} + +void Color::Transparent(double vTransparent) +{ + mTransparent = unit_limiter(vTransparent); +} + +double Color::Transparent() const +{ + return mTransparent; +} + +unsigned char Color::RedByte() const +{ + return ByteValue(mRed); +} +unsigned char Color::GreenByte() const +{ + return ByteValue(mGreen); +} +unsigned char Color::BlueByte() const +{ + return ByteValue(mBlue); +} +unsigned char Color::TransparentByte() const +{ + return ByteValue(mTransparent); +} + +unsigned char Color::ByteValue(double vZeroToOne) const +{ + return (unsigned char)(vZeroToOne*255.0); +} + +} // namespace igraph diff --git a/optional/simpleraytracer/Color.h b/optional/simpleraytracer/Color.h new file mode 100755 index 0000000..8a10430 --- /dev/null +++ b/optional/simpleraytracer/Color.h @@ -0,0 +1,40 @@ +/** Color.h + */ + +#ifndef COLOR_H +#define COLOR_H + +namespace igraph { + +class Color +{ +public: + Color(); + Color(double vRed, double vGreen, double vBlue, + double vTransparent=1.0); + ~Color(); + + Color operator* (double vRhs) const; // returns multiplication of a scalar with a vector + Color operator+ (const Color& vRhs) const; // returns the addition of this color with another color + + void Red(double vRed); + double Red() const; + void Green(double vGreen); + double Green() const; + void Blue(double vBlue); + double Blue() const; + void Transparent(double vTransparent); + double Transparent() const; + + unsigned char RedByte() const; + unsigned char GreenByte() const; + unsigned char BlueByte() const; + unsigned char TransparentByte() const; +private: + unsigned char ByteValue(double vZeroToOne) const; + double mRed, mGreen, mBlue, mTransparent; +}; + +} // namespace igraph + +#endif diff --git a/optional/simpleraytracer/Light.cpp b/optional/simpleraytracer/Light.cpp new file mode 100755 index 0000000..d74cf0c --- /dev/null +++ b/optional/simpleraytracer/Light.cpp @@ -0,0 +1,46 @@ +#include "Light.h" +#include "unit_limiter.h" + +namespace igraph { + +Light::Light() : mLightPoint(0,0,0) +{ + mIntensity = 0.1; +} + +Light::Light(const Point& rLightPoint) : mLightPoint(rLightPoint) +{ + mIntensity = 0.1; +} + +Light::~Light() +{} + +const Point& Light::LightPoint() const +{ + return mLightPoint; +} +void Light::LightPoint(const Point& rLightPoint) +{ + mLightPoint = rLightPoint; +} +double Light::Intensity() const +{ + return mIntensity; +} +void Light::Intensity(double vIntensity) +{ + mIntensity = unit_limiter(vIntensity); +} + +const Color& Light::LightColor() const +{ + return mLightColor; +} + +void Light::LightColor(const Color& rLightColor) +{ + mLightColor = rLightColor; +} + +} // namespace igraph diff --git a/optional/simpleraytracer/Light.h b/optional/simpleraytracer/Light.h new file mode 100755 index 0000000..369b926 --- /dev/null +++ b/optional/simpleraytracer/Light.h @@ -0,0 +1,39 @@ +#ifndef LIGHT_H +#define LIGHT_H + +#include "Point.h" +#include "Color.h" + +#include +using namespace std; + +namespace igraph { + +class Light +{ +public: + Light(); // creates a light at the origin + Light(const Point& rLightPoint); + ~Light(); + + const Point& LightPoint() const; + void LightPoint(const Point& rLightPoint); + + double Intensity() const; + void Intensity(double vIntensity); + + const Color& LightColor() const; + void LightColor(const Color& rLightColor); + +private: + Point mLightPoint; + double mIntensity; // 0 to 1 + Color mLightColor; +}; + +typedef list LightList; +typedef list::iterator LightListIterator; + +} // namespace igraph + +#endif diff --git a/optional/simpleraytracer/Point.cpp b/optional/simpleraytracer/Point.cpp new file mode 100755 index 0000000..8ccbc27 --- /dev/null +++ b/optional/simpleraytracer/Point.cpp @@ -0,0 +1,106 @@ +#include "Point.h" +#include + +namespace igraph { + +Point::Point() +{ + X(0.0); + Y(0.0); + Z(0.0); + Name(0); +} + +Point::Point(double vX, double vY, double vZ, int vName) +{ + X(vX); + Y(vY); + Z(vZ); + Name(vName); +} + +Point::Point(double vX, double vY, double vZ) +{ + X(vX); + Y(vY); + Z(vZ); + Name(0); +} + +Point::~Point() +{} + +double Point::X() const +{ + return mX; +} + +void Point::X(double vX) +{ + mX = vX; +} + +double Point::Y() const +{ + return mY; +} + +void Point::Y(double vY) +{ + mY = vY; +} + +double Point::Z() const +{ + return mZ; +} + +void Point::Z(double vZ) +{ + mZ = vZ; +} + +int Point::Name() const +{ + return mName; +} + +void Point::Name(int vName) +{ + mName = vName; +} + +double Point::Distance(const Point& rPoint) const +{ + return sqrt( (rPoint.X() - mX)*(rPoint.X() - mX) + (rPoint.Y() - mY)*(rPoint.Y() - mY) + (rPoint.Z() - mZ)*(rPoint.Z() - mZ) ); +} + +bool Point::operator==(const Point& vRhs) const +{ + bool result = true; +/* + if ( mX + .001 <= vRhs.X() ) + result = false; + if ( mX - .001 >= vRhs.X() ) + result = false; + if ( mY + .001 <= vRhs.Y() ) + result = false; + if ( mY - .001 >= vRhs.Y() ) + result = false; + if ( mZ + .001 <= vRhs.Z() ) + result = false; + if ( mZ - .001 >= vRhs.Z() ) + result = false; +*/ + if ( mX != vRhs.X() ) + result = false; + if ( mY != vRhs.Y() ) + result = false; + if ( mZ != vRhs.Z() ) + result = false; + + + return result; +} + +} // namespace igraph diff --git a/optional/simpleraytracer/Point.h b/optional/simpleraytracer/Point.h new file mode 100755 index 0000000..647bc47 --- /dev/null +++ b/optional/simpleraytracer/Point.h @@ -0,0 +1,45 @@ +/** + this is a simple generic class representing a 3d point with a name. + it also defines the PointList type, which is a linked list of Points +*/ + +#ifndef POINT_H +#define POINT_H + +#include +using namespace std; + +namespace igraph { + +class Point +{ +public: + Point(); // creates a point at the origin with name 0 + Point(double vX, double vY, double vZ, int vName); + Point(double vX, double vY, double vZ); + ~Point(); + + double X() const; + void X(double vX); + double Y() const; + void Y(double vY); + double Z() const; + void Z(double vZ); + + int Name() const; + void Name(int vName); + double Distance(const Point& rPoint) const; + + bool operator==(const Point& vRhs) const; + +private: + double mX, mY, mZ; + int mName; +}; + +typedef list PointList; +typedef list::iterator PointListIterator; + +} // namespace igraph + +#endif diff --git a/optional/simpleraytracer/RIgraphRay.cpp b/optional/simpleraytracer/RIgraphRay.cpp new file mode 100644 index 0000000..5364f1a --- /dev/null +++ b/optional/simpleraytracer/RIgraphRay.cpp @@ -0,0 +1,94 @@ +/* -*- mode: C -*- */ +/* + IGraph library R interface. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph.h" +#include "igraph_error.h" + +#include "RayTracer.h" +#include "Sphere.h" + +#include "config.h" + +#include +#include +#include + +using namespace igraph; + +extern "C" { + +SEXP R_igraph_getsphere(SEXP pos, SEXP radius, SEXP color, SEXP bgcolor, + SEXP lightpos, SEXP lightcolor, SEXP width, + SEXP height) { + + /* All error checking is done at the R level */ + + int i; + double *spos=REAL(pos); + double *scolor=REAL(color); + double *svgcolor=REAL(bgcolor); + int no_lights=GET_LENGTH(lightpos); + RayTracer* p_ray_tracer; + Sphere * sphere; + int swidth=INTEGER(width)[0]; + int sheight=INTEGER(height)[0]; + int nopixels=swidth * sheight; + SEXP result, dim; + Image image; + + p_ray_tracer = new RayTracer(); + p_ray_tracer->EyePoint(Point(0,0,0)); + + for (i=0; iIntensity(1); + light->LightColor(Color(lcol[0], lcol[1], lcol[2])); + p_ray_tracer->AddLight(light); + } + + sphere = new Sphere(Point(spos[0], spos[1], spos[2]), REAL(radius)[0]); + sphere->ShapeColor(Color(scolor[0], scolor[1], scolor[2])); + p_ray_tracer->AddShape(sphere); + + PROTECT(result=NEW_NUMERIC(nopixels * 4)); + PROTECT(dim=NEW_INTEGER(3)); + INTEGER(dim)[0]=swidth; INTEGER(dim)[1]=sheight; INTEGER(dim)[2]=4; + SET_DIM(result, dim); + + image.width=swidth; + image.height=sheight; + image.red=REAL(result); + image.green=image.red + nopixels; + image.blue=image.green + nopixels; + image.trans=image.blue + nopixels; + + p_ray_tracer->RayTrace(image); + delete p_ray_tracer; + + UNPROTECT(2); + return result; +} + +} // extern C diff --git a/optional/simpleraytracer/Ray.cpp b/optional/simpleraytracer/Ray.cpp new file mode 100755 index 0000000..7f6744a --- /dev/null +++ b/optional/simpleraytracer/Ray.cpp @@ -0,0 +1,44 @@ +#include "Ray.h" + +namespace igraph { + +Ray::Ray() +{} + +Ray::~Ray() +{} + +Ray::Ray(const Point& rOrigin, const Vector& rDirection) +{ + Direction(rDirection); + Origin(rOrigin); + +} + +Ray::Ray(const Point& rOrigin, const Point& rEndPoint) +{ + Direction(Vector(rOrigin,rEndPoint)); + Origin(rOrigin); +} + +const Point& Ray::Origin() const +{ + return mOrigin; +} + +void Ray::Origin(Point vOrigin) +{ + mOrigin = vOrigin; +} + +const Vector& Ray::Direction() const +{ + return mDirection; +} + +void Ray::Direction(Vector vDirection) +{ + mDirection = vDirection; +} + +} // namespace igraph diff --git a/optional/simpleraytracer/Ray.h b/optional/simpleraytracer/Ray.h new file mode 100755 index 0000000..cd14ca1 --- /dev/null +++ b/optional/simpleraytracer/Ray.h @@ -0,0 +1,33 @@ +/** Ray.h + */ + +#ifndef RAY_H +#define RAY_H + +#include "RayVector.h" +#include "Point.h" + +namespace igraph { + +class Ray +{ +public: + Ray(); + Ray(const Point& rOrigin, const Vector& rDirection); + Ray(const Point& rOrigin, const Point& rEndPoint); + ~Ray(); + + void Origin(Point vPoint); + const Point& Origin() const; + + const Vector& Direction() const; + void Direction(Vector vDirection); + +private: + Vector mDirection; + Point mOrigin; +}; + +} // namespace igraph + +#endif diff --git a/optional/simpleraytracer/RayTracer.cpp b/optional/simpleraytracer/RayTracer.cpp new file mode 100755 index 0000000..27e3cc5 --- /dev/null +++ b/optional/simpleraytracer/RayTracer.cpp @@ -0,0 +1,266 @@ +#include "RayTracer.h" +#include "unit_limiter.h" +#include +#include + +namespace igraph { + +RayTracer::RayTracer() : mBackgroundColor(0,0,0,0), mAmbientColor(0,0,0), mEyePoint(0,0,0), mSpecularColor(1,1,1) +{ + // begin settings + mAmbientIntensity = .7; + mRecursionLimit = 700; + mAntiAliasDetail = 1; + // end settings + + mRecursions = 0; + + mpShapes = new ShapeList; + mpLights = new LightList; +} + +RayTracer::~RayTracer() +{ + ShapeListIterator iter1 = mpShapes->begin(); + while ( iter1 != mpShapes->end() ) + { + delete *iter1; + iter1++; + } + delete mpShapes; + + LightListIterator iter2 = mpLights->begin(); + while ( iter2 != mpLights->end() ) + { + delete *iter2; + iter2++; + } + + delete mpLights; + +} + +void RayTracer::RayTrace(Image &result) +{ + int mWidth=result.width; + int mHeight=result.height; + Ray eye_ray(mEyePoint,Vector(0,0,1)); + Color draw_color; + double i_inc, j_inc, anti_alias_i_inc, anti_alias_j_inc; // amount to increment the ray in each direction + double i, j, anti_alias_i, anti_alias_j; // the i and j values of the ray + int pixel_x, pixel_y, anti_alias_pixel_x, anti_alias_pixel_y; // the pixels being drawn + + double average_red_byte, average_green_byte, average_blue_byte, average_trans_byte; + int anti_alias_count; // the number of anti aliases (used in averaging) + int idx=0; + + i_inc = 2.0/(double)mWidth; + j_inc = 2.0/(double)mHeight; + anti_alias_i_inc = 1.0/(double)mAntiAliasDetail; + anti_alias_j_inc = 1.0/(double)mAntiAliasDetail; + + pixel_y = 0; + j = 1.0; + for (; pixel_y < mHeight; j -= j_inc, pixel_y++) + { + pixel_x = 0; + i = -1.0; + for (; pixel_x < mWidth; i += i_inc, pixel_x++) + { + anti_alias_pixel_y = 0; + anti_alias_j = 0.0; + average_red_byte = 0; + average_green_byte = 0; + average_blue_byte = 0; + average_trans_byte = 0; + anti_alias_count = 0; + for (; anti_alias_pixel_y < mAntiAliasDetail; anti_alias_j += anti_alias_j_inc, anti_alias_pixel_y++) + { + anti_alias_pixel_x = 0; + anti_alias_i = 0.0; + + for (; anti_alias_pixel_x < mAntiAliasDetail; anti_alias_i += anti_alias_i_inc, anti_alias_pixel_x++) + { + anti_alias_count++; + eye_ray.Direction( Vector(i+(anti_alias_i*i_inc),j+(anti_alias_j*j_inc),1.0) ); + draw_color = Render(eye_ray); + + average_red_byte = average_red_byte + ((double)draw_color.RedByte() - average_red_byte)/(double)anti_alias_count; + average_green_byte = average_green_byte + ((double)draw_color.GreenByte() - average_green_byte)/(double)anti_alias_count; + average_blue_byte = average_blue_byte + ((double)draw_color.BlueByte() - average_blue_byte)/(double)anti_alias_count; + average_trans_byte = average_trans_byte + ((double)draw_color.TransparentByte() - average_trans_byte)/(double)anti_alias_count; + } + } + + result.red [idx] = average_red_byte/255; + result.green[idx] = average_green_byte/255; + result.blue [idx] = average_blue_byte/255; + result.trans[idx] = average_trans_byte/255; + idx++; + } + } +} + +Color RayTracer::Render(const Ray& rRay, bool vIsReflecting, const Shape* pReflectingFrom ) +{ + mRecursions++; + Shape* closest_shape; + Point intersect_point; + Color result; + if (vIsReflecting) + closest_shape = QueryScene(rRay, intersect_point, vIsReflecting, pReflectingFrom); + else + closest_shape = QueryScene(rRay, intersect_point); + + if (closest_shape == NULL && !vIsReflecting) + { + mRecursions = 0; + return mBackgroundColor; + } + if (closest_shape == NULL && vIsReflecting) + { + mRecursions = 0; + return mAmbientColor*mAmbientIntensity; + } + if ( mRecursions > mRecursionLimit ) + { + mRecursions = 0; + return Color(0,0,0); // mAmbientColor*mAmbientIntensity; + } + result = closest_shape->ShapeColor()*Shade(closest_shape, intersect_point); + + Ray backwards_ray(intersect_point,rRay.Direction()*-1); + if ( closest_shape->DiffuseReflectivity() > 0.0 ) + result = result + (Render( closest_shape->Reflect(intersect_point,backwards_ray), true, closest_shape )*closest_shape->DiffuseReflectivity()); + + return (result + mSpecularColor); +} + +double RayTracer::Shade(const Shape* pShapeToShade, const Point& rPointOnShapeToShade) +{ + double intensity = mAmbientIntensity * pShapeToShade->AmbientReflectivity(); // the ambient intensity of the scene + Ray light_ray; // the ray that goes from the intersection point to the light sources + double dot_product; + Shape* closest_shape; // the shape closest from the intersection point to the light source + Point light_intersect; // the intersection point of the ray that goes from the intersection point to the light source + light_ray.Origin(rPointOnShapeToShade); // lightRay. org= object. intersect; + Ray light_ray_from_light; + + LightListIterator iter = mpLights->begin(); + mSpecularColor.Red(0); + mSpecularColor.Green(0); + mSpecularColor.Blue(0); + + while ( iter != mpLights->end() ) // foreach light in LightList do + { + + light_ray.Direction(Vector(rPointOnShapeToShade,(*iter)->LightPoint())); // lightRay. dir= light. dir + light_ray_from_light.Origin((*iter)->LightPoint()); + light_ray_from_light.Direction(Vector((*iter)->LightPoint(),rPointOnShapeToShade)); + + closest_shape = QueryScene(light_ray_from_light, light_intersect); + if ( closest_shape == NULL || (closest_shape == pShapeToShade && light_ray.Direction().Dot(pShapeToShade->Normal(rPointOnShapeToShade, light_ray_from_light.Origin() )) >= 0.0 ) ) //if (QueryScene( lightRay)= NIL) + { + Vector normal_vector = pShapeToShade->Normal(rPointOnShapeToShade, Point() ); + dot_product = normal_vector.Dot(light_ray.Direction().Normalize()); + dot_product *= (*iter)->Intensity(); + + if (dot_product < 0.0) + { + if (pShapeToShade->Type() == "Triangle") + dot_product = dot_product*-1.0; + else + dot_product = 0.0; + } + intensity = unit_limiter( intensity + dot_product ); + + if ( light_ray.Direction().Dot(pShapeToShade->Normal(rPointOnShapeToShade, light_ray_from_light.Origin() )) >= 0.0 ) + { + double specular = Specular(pShapeToShade, rPointOnShapeToShade, *iter); + mSpecularColor = mSpecularColor + Color(specular,specular,specular); + } + } + + iter++; + } + + return intensity; +} + +double RayTracer::Specular(const Shape* pShapeToShade, const Point& rPointOnShapeToShade, const Light* pLight) +{ + Ray reflected = pShapeToShade->Reflect(rPointOnShapeToShade,Ray(rPointOnShapeToShade, pLight->LightPoint())); + + Vector eye_vector(rPointOnShapeToShade, mEyePoint); + Vector reflected_vector = reflected.Direction().Normalize(); + eye_vector.NormalizeThis(); + double dot_product = eye_vector.Dot(reflected_vector); + + int n = pShapeToShade->SpecularSize(); + double specular_intensity = dot_product/(n - n*dot_product+ dot_product); + return unit_limiter(specular_intensity*pLight->Intensity()); +} + +Shape* RayTracer::QueryScene(const Ray& rRay, Point& rIntersectionPoint, bool vIsReflecting, const Shape* pReflectingFrom) +{ + Shape* closest_shape = NULL; + Point intersect_point; + double closest_distance; + double intersect_distance; + bool found_intersection = false; + + ShapeListIterator iter = mpShapes->begin(); + while ( iter != mpShapes->end() ) + { + if ( (*iter)->Intersect( rRay, intersect_point ) ) + { + intersect_distance = intersect_point.Distance(rRay.Origin()); + if ( !found_intersection && (*iter) != pReflectingFrom) + { + found_intersection = true; + rIntersectionPoint = intersect_point; + closest_shape = *iter; + closest_distance = intersect_distance; + } + else if ( intersect_distance < closest_distance && (*iter) != pReflectingFrom ) + { + rIntersectionPoint = intersect_point; + closest_shape = *iter; + closest_distance = intersect_distance; + } + } + iter++; + } + + return closest_shape; +} + +void RayTracer::AddShape(Shape* pShape) +{ + // should check if a shape with the same name already exists + mpShapes->push_back(pShape); +} +void RayTracer::AddLight(Light* pLight) +{ + // should check if a shape with the same name already exists + mpLights->push_back(pLight); +} + +void RayTracer::BackgroundColor(const Color& rBackgroundColor) +{ + mBackgroundColor = rBackgroundColor; +} +void RayTracer::EyePoint(const Point& rEyePoint) +{ + mEyePoint = rEyePoint; +} +void RayTracer::AmbientColor(const Color& rAmbientColor) +{ + mAmbientColor = rAmbientColor; +} +void RayTracer::AmbientIntensity(double vAmbientIntensity) +{ + mAmbientIntensity = unit_limiter(vAmbientIntensity); +} + +} // namespace igraph diff --git a/optional/simpleraytracer/RayTracer.h b/optional/simpleraytracer/RayTracer.h new file mode 100755 index 0000000..cc4830d --- /dev/null +++ b/optional/simpleraytracer/RayTracer.h @@ -0,0 +1,63 @@ +/** RayTraceCanvas.h + */ + +#ifndef RAY_TRACER_H +#define RAY_TRACER_H + + +#include +#include "Point.h" +#include "Shape.h" +#include "Color.h" +#include "Light.h" + +namespace igraph { + +class Image +{ + public: + int width, height; + double *red, *green, *blue, *trans; +}; + +class RayTracer +{ + +public: + RayTracer(); + ~RayTracer(); + + void RayTrace(Image &result); + + void AddShape(Shape* pShape); + void AddLight(Light* pLight); + + void BackgroundColor(const Color& rBackgroundColor); + void EyePoint(const Point& rEyePoint); + void AmbientColor(const Color& rAmbient); + void AmbientIntensity(double vAmbientIntensity); + +private: + + Color Render(const Ray& rRay, bool vIsReflecting = false, const Shape* pReflectingFrom = 0 ); // vEyeRay should be true if the ray we are tracing is a ray from the eye, otherwise it should be false + Shape* QueryScene(const Ray& rRay, Point& rIntersectionPoint, bool vIsReflecting = false, const Shape* pReflectingFrom = 0); + double Shade(const Shape* pShapeToShade, const Point& rPointOnShapeToShade); + double Specular(const Shape* pShapeToShade, const Point& rPointOnShapeToShade, const Light* pLight); + + Color mBackgroundColor; + Color mAmbientColor; + Point mEyePoint; + Color mSpecularColor; + double mAmbientIntensity; + + ShapeList* mpShapes; + LightList* mpLights; + + int mRecursions; + int mRecursionLimit; + int mAntiAliasDetail; +}; + +} // namespace igraph + +#endif diff --git a/optional/simpleraytracer/RayVector.cpp b/optional/simpleraytracer/RayVector.cpp new file mode 100755 index 0000000..76f21aa --- /dev/null +++ b/optional/simpleraytracer/RayVector.cpp @@ -0,0 +1,128 @@ +#include "RayVector.h" +#include + +namespace igraph { + +Vector::Vector() +{ + mI = mJ = mK = 0.0; +} + +Vector::Vector(const Point& vStartPoint, const Point& vEndPoint) +{ + mI = vEndPoint.X() - vStartPoint.X(); + mJ = vEndPoint.Y() - vStartPoint.Y(); + mK = vEndPoint.Z() - vStartPoint.Z(); +} + +Vector::Vector(double vI, double vJ, double vK) +{ + mI = vI; + mJ = vJ; + mK = vK; +} + +Vector::~Vector() +{} + +// returns a unit vector of this vector +Vector Vector::Normalize() const +{ + double magnitude = Magnitude(); + return Vector(mI/magnitude, mJ/magnitude, mK/magnitude); +} + +void Vector::NormalizeThis() +{ + *this = Normalize(); +} + +void Vector::ReverseDirection() +{ + *this = *this * -1.0; +} + +bool Vector::IsSameDirection(const Vector& rVector) const +{ + return ( this->Normalize().Dot(rVector.Normalize()) > 0.0 ); +} + + +void Vector::I(double vI) +{ + mI = vI; +} + +double Vector::I() const +{ + return mI; +} + +void Vector::J(double vJ) +{ + mJ = vJ; +} + +double Vector::J() const +{ + return mJ; +} +void Vector::K(double vK) +{ + mK = vK; +} + +double Vector::K() const +{ + return mK; +} + +// returns the dot product of this and rVector +double Vector::Dot(const Vector& rVector) const +{ + return mI*rVector.I() + mJ*rVector.J() + mK*rVector.K(); +} + +// returns the cross product of this and vVector +Vector Vector::Cross(const Vector& rVector) const +{ + return Vector(mJ*rVector.K() - rVector.J()*mK, -1.0*(mI*rVector.K() - rVector.I()*mK), mI*rVector.J() - rVector.I()*mJ); +} + +// returns the sum of this vector with another vector +Vector Vector::operator+ (Vector vRhs) const +{ + return Vector(mI + vRhs.I(), mJ + vRhs.J(), mK + vRhs.K()); +} + +// returns the sume of a vector and a Point +Point Vector::operator+ (Point vRhs) const +{ + return Point(mI + vRhs.X(), mJ + vRhs.Y(), mK + vRhs.Z()); +} + +// returns the difference of two vectors +Vector Vector::operator- (Vector vRhs) const +{ + return Vector(mI-vRhs.I(), mJ-vRhs.J(), mK-vRhs.K()); +} + +// returns multiplication of a scalar with this vector +Vector Vector::operator* (double vRhs) const +{ + return Vector(mI*vRhs, mJ*vRhs, mK*vRhs); +} + +// converts this vector to a point +Point Vector::ToPoint() const +{ + return Point(mI,mJ,mK); +} + +// returns the magnitude +double Vector::Magnitude() const +{ + return sqrt(mI*mI + mJ*mJ + mK*mK); +} + +} // namespace igraph diff --git a/optional/simpleraytracer/RayVector.h b/optional/simpleraytracer/RayVector.h new file mode 100755 index 0000000..7157ba9 --- /dev/null +++ b/optional/simpleraytracer/RayVector.h @@ -0,0 +1,49 @@ +/** Vector.h + */ + +#ifndef VECTOR_H +#define VECTOR_H + +#include "Point.h" + +namespace igraph { + +class Vector +{ +public: + Vector(); + Vector(const Point& vStartPoint, const Point& vEndPoint); + Vector(double vI, double vJ, double vK); + ~Vector(); + + Vector Normalize() const; // returns a unit vector of this vector + void NormalizeThis(); + void ReverseDirection(); + bool IsSameDirection(const Vector& rVector) const; + + void I(double vI); + double I() const; + void J(double vJ); + double J() const; + void K(double vK); + double K() const; + + double Dot(const Vector& rVector) const; // returns the dot product of this and rVector + Vector Cross(const Vector& rVector) const; // returns the cross product of this and rVector + Vector operator+ (Vector vRhs) const; // returns the sum of two vectors + Vector operator- (Vector vRhs) const; // returns the difference of two vectors + Point operator+ (Point vRhs) const; // returns the sum of a vector and a Point + Vector operator* (double vRhs) const; // returns multiplication of a scalar with a vector + Point ToPoint() const; // converts a vector to a point + + double Magnitude() const; + +private: + + + double mI, mJ, mK; +}; + +} // namespace igraph + +#endif diff --git a/optional/simpleraytracer/Shape.cpp b/optional/simpleraytracer/Shape.cpp new file mode 100755 index 0000000..3ca2b14 --- /dev/null +++ b/optional/simpleraytracer/Shape.cpp @@ -0,0 +1,106 @@ +#include "Shape.h" +#include "unit_limiter.h" + +namespace igraph { + +Shape::Shape() +{ + mName = 0; + mAmbientReflectivity = .6; + mSpecularReflectivity = 0; + mDiffuseReflectivity = 0; + mSpecularSize = 64; +} + +Shape::~Shape() +{} + +int Shape::Name() const +{ + return mName; +} + +void Shape::Name(int vName) +{ + mName = vName; +} + +const Color& Shape::ShapeColor() const +{ + return mShapeColor; +} + +void Shape::ShapeColor(const Color& rColor) +{ + mShapeColor = rColor; +} + +double Shape::AmbientReflectivity() const +{ + return mAmbientReflectivity; +} +double Shape::SpecularReflectivity() const +{ + return mSpecularReflectivity; +} +double Shape::DiffuseReflectivity() const +{ + return mDiffuseReflectivity; +} + +void Shape::AmbientReflectivity(double rReflectivity) +{ + mAmbientReflectivity = unit_limiter(rReflectivity); +} +void Shape::SpecularReflectivity(double rReflectivity) +{ + mSpecularReflectivity = unit_limiter(rReflectivity); +} +void Shape::DiffuseReflectivity(double rReflectivity) +{ + mDiffuseReflectivity = unit_limiter(rReflectivity); +} + +Ray Shape::Reflect(const Point& rReflectFrom, const Ray& rIncidentRay) const +{ + Ray result; // the reflected ray + Vector result_direction; // the reflected direction vector + Vector incident_unit = rIncidentRay.Direction().Normalize(); + Vector normal = this->Normal(rReflectFrom, rIncidentRay.Origin() ); + if ( !normal.IsSameDirection(incident_unit) ) + normal.ReverseDirection(); // we want the normal in the same direction of the incident ray. + + result.Origin(rReflectFrom); + result.Direction( normal*2.0*normal.Dot(incident_unit) - incident_unit ); +/* + if ( normal.Dot(rIncidentRay.Direction().Normalize()) < 0.0 ) + normal.ReverseDirection(); + + result.Origin(rReflectFrom); + result.Direction((normal*2.0) - rIncidentRay.Direction().Normalize()); +*/ + + return result; +} + +const string& Shape::Type() const +{ + return mType; +} + +void Shape::Type(const string& rType) +{ + mType = rType; +} + +int Shape::SpecularSize() const +{ + return mSpecularSize; +} + +void Shape::SpecularSize(int vSpecularSize) +{ + mSpecularSize = vSpecularSize; +} + +} // namespace igraph diff --git a/optional/simpleraytracer/Shape.h b/optional/simpleraytracer/Shape.h new file mode 100755 index 0000000..fc9c7c1 --- /dev/null +++ b/optional/simpleraytracer/Shape.h @@ -0,0 +1,65 @@ +/** Shape.h + */ + +#ifndef SHAPE_H +#define SHAPE_H + +#include +#include "Color.h" +#include "Ray.h" +#include "Point.h" + +#include +using namespace std; + +namespace igraph { + +class Shape +{ +public: + Shape(); + virtual ~Shape(); + + virtual bool Intersect(const Ray& rRay, Point& rIntersectPoint) const = 0; + virtual Vector Normal(const Point& rSurfacePoint, const Point& rOffSurface) const = 0; + // returns a normalized vector that is the normal of this shape from the surface point + // it also takes the rOffSurface point into account, for example: + // if rSurfacePoint is on top of a triangle, then the normal returned will be going up. + + Ray Reflect(const Point& rReflectFrom, const Ray& rRay) const; + + void Name(int vName); + int Name() const; + + const Color& ShapeColor() const; + void ShapeColor(const Color& rColor); + + double SpecularReflectivity() const; + void SpecularReflectivity(double rReflectivity); + double DiffuseReflectivity() const; + void DiffuseReflectivity(double rReflectivity); + double AmbientReflectivity() const; + void AmbientReflectivity(double rReflectivity); + + int SpecularSize() const; + void SpecularSize(int vSpecularSize); + + const string& Type() const; + void Type(const string& rType); + +private: + int mName; + string mType; + Color mShapeColor; + double mSpecularReflectivity; // from 0 to 1 + int mSpecularSize; // 1 to 64 + double mDiffuseReflectivity; // from 0 to 1 + double mAmbientReflectivity; // from 0 to 1 +}; + +typedef list ShapeList; +typedef list::iterator ShapeListIterator; + +} // namespace igraph + +#endif diff --git a/optional/simpleraytracer/Sphere.cpp b/optional/simpleraytracer/Sphere.cpp new file mode 100755 index 0000000..c590ad8 --- /dev/null +++ b/optional/simpleraytracer/Sphere.cpp @@ -0,0 +1,71 @@ +#include "Sphere.h" +#include + +namespace igraph { + +Sphere::Sphere() +{} + +Sphere::Sphere(Point vCenter, double vRadius) +{ + Type("Sphere"); + mCenter = vCenter; + mRadius = vRadius; +} + +Sphere::~Sphere() +{ +} + +bool Sphere::Intersect(const Ray& vRay, Point& vIntersectPoint) const +{ + double c; + Vector V; + Vector EO(vRay.Origin(), mCenter); + double v; + double disc; + double d; + Vector E(Point(0,0,0), vRay.Origin()); // E = vector from origin to ray origin + Vector P; + c = mCenter.Distance(vRay.Origin()); //c = distance from eye to center of sphere + V = vRay.Direction(); + V.NormalizeThis(); + v = EO.Dot(V); + double v2 = V.Dot(EO.Normalize()); + if (v2 >= 0.0) + { + disc = mRadius*mRadius - (EO.Dot(EO) - v*v); + if (disc <= 0) + return false; + else + { + d = sqrt(disc); + P = E + V*(v-d); + vIntersectPoint = P.ToPoint(); + return true; + } + } + else + return false; +} + +Vector Sphere::Normal(const Point& rSurfacePoint, const Point& rOffSurface) const +{ + // currently does not take rOffSurface point into account, + // it should check if this point is inside the sphere, if it is + // return a normal facing the center. + + Vector radius_vector (mCenter, rSurfacePoint); + return (radius_vector.Normalize()); +} + +double Sphere::Radius() const +{ + return mRadius; +} +const Point& Sphere::Center() const +{ + return mCenter; +} + +} // namespace igraph diff --git a/optional/simpleraytracer/Sphere.h b/optional/simpleraytracer/Sphere.h new file mode 100755 index 0000000..faff6da --- /dev/null +++ b/optional/simpleraytracer/Sphere.h @@ -0,0 +1,31 @@ +/** Sphere.h +*/ + +#ifndef SPHERE_H +#define SPHERE_H + +#include "Shape.h" + +namespace igraph { + +class Sphere : public Shape +{ +public: + Sphere(); + Sphere(Point vCenter, double vRadius); + ~Sphere(); + + virtual bool Intersect(const Ray& vRay, Point& vIntersectPoint) const; + virtual Vector Normal(const Point& rSurfacePoint, const Point& rOffSurface) const; + + double Radius() const; + const Point& Center() const; + +private: + Point mCenter; + double mRadius; +}; + +} // namespace igraph + +#endif diff --git a/optional/simpleraytracer/Triangle.cpp b/optional/simpleraytracer/Triangle.cpp new file mode 100755 index 0000000..7946c26 --- /dev/null +++ b/optional/simpleraytracer/Triangle.cpp @@ -0,0 +1,90 @@ +#include "Triangle.h" +#include + +namespace igraph { + +Triangle::Triangle() +{} + +Triangle::Triangle(const Point& rPoint1, const Point& rPoint2, const Point& rPoint3) +{ + Type("Triangle"); + mPoint1 = rPoint1; + mPoint2 = rPoint2; + mPoint3 = rPoint3; +} + +Triangle::~Triangle() +{ +} + +bool Triangle::Intersect(const Ray& vRay, Point& rIntersectPoint) const +{ + + Vector pointb_minus_pointa (mPoint1, mPoint2); + Vector pointb_minus_pointc (mPoint1, mPoint3); +/* + Vector plane_normal = pointb_minus_pointa.Cross(pointb_minus_pointc); + + // get the plane normal facing the right way: + Vector plane_normal_normalized = plane_normal.Normalize(); + Vector triangle_to_ray_origin = Vector(mPoint1, vRay.Origin() ); + triangle_to_ray_origin.NormalizeThis(); + if ( plane_normal_normalized.Dot(triangle_to_ray_origin) < 0.0 ) + { + plane_normal = plane_normal * -1.0; + plane_normal_normalized = plane_normal_normalized * -1.0; + } + + // check that the ray is actually facing the triangle + Vector ray_direction_normalized = vRay.Direction().Normalize(); + if ( plane_normal_normalized.Dot(ray_direction_normalized) > 0.0 ) + return false; +*/ + Vector plane_normal = this->Normal(mPoint1, vRay.Origin()); + Vector ray_direction_normalized = vRay.Direction().Normalize(); + if ( plane_normal.IsSameDirection(ray_direction_normalized) ) + return false; + + Vector b_minus_u (vRay.Origin(), mPoint2); + + double t = plane_normal.Dot(b_minus_u) / plane_normal.Dot(vRay.Direction()); + Point p = (vRay.Direction() * t) + vRay.Origin(); + + Vector p_minus_a (mPoint1, p); + Vector p_minus_b (mPoint2, p); + Vector p_minus_c (mPoint3, p); + Vector pointc_minus_pointb (mPoint2, mPoint3); + Vector pointa_minus_pointc (mPoint3, mPoint1); + + double test1 = (pointb_minus_pointa.Cross(p_minus_a)).Dot(plane_normal); + double test2 = (pointc_minus_pointb.Cross(p_minus_b)).Dot(plane_normal); + double test3 = (pointa_minus_pointc.Cross(p_minus_c)).Dot(plane_normal); + + if ((test1 > 0 && test2 > 0 && test3 > 0) || (test1 < 0 && test2 < 0 && test3 < 0)) + { + rIntersectPoint = p; + return true; + } + else + return false; +} + +Vector Triangle::Normal(const Point& rSurfacePoint, const Point& rOffSurface) const +{ + Vector pointb_minus_pointa (mPoint1, mPoint2); + Vector pointb_minus_pointc (mPoint1, mPoint3); + Vector plane_normal = pointb_minus_pointa.Cross(pointb_minus_pointc).Normalize(); + + // get the plane normal facing the right way: + Vector triangle_to_off_surface_point = Vector(mPoint1, rOffSurface ); + triangle_to_off_surface_point.NormalizeThis(); + if ( !plane_normal.IsSameDirection(triangle_to_off_surface_point) ) + { + plane_normal.ReverseDirection(); + } + + return plane_normal; +} + +} // namespace igraph diff --git a/optional/simpleraytracer/Triangle.h b/optional/simpleraytracer/Triangle.h new file mode 100755 index 0000000..198d241 --- /dev/null +++ b/optional/simpleraytracer/Triangle.h @@ -0,0 +1,27 @@ +/** Triangle.h +*/ + +#ifndef TRIANGLE_H +#define TRIANGLE_H + +#include "Shape.h" + +namespace igraph { + +class Triangle : public Shape +{ +public: + Triangle(); + Triangle(const Point& rPoint1, const Point& rPoint2, const Point& rPoint3); + ~Triangle(); + + virtual bool Intersect(const Ray& vRay, Point& vIntersectPoint) const; + virtual Vector Normal(const Point& rSurfacePoint, const Point& rOffSurface) const; + +private: + Point mPoint1, mPoint2, mPoint3; +}; + +} // namespace igraph + +#endif diff --git a/optional/simpleraytracer/unit_limiter.cpp b/optional/simpleraytracer/unit_limiter.cpp new file mode 100755 index 0000000..75a59da --- /dev/null +++ b/optional/simpleraytracer/unit_limiter.cpp @@ -0,0 +1,15 @@ +#include "unit_limiter.h" + +namespace igraph { + +double unit_limiter(double vUnitDouble) +{ + double result = vUnitDouble; + if (result < 0.0) + result = 0.0; + else if (result > 1.0) + result = 1.0; + return result; +} + +} // namespace igraph diff --git a/optional/simpleraytracer/unit_limiter.h b/optional/simpleraytracer/unit_limiter.h new file mode 100755 index 0000000..9c6f61b --- /dev/null +++ b/optional/simpleraytracer/unit_limiter.h @@ -0,0 +1,10 @@ +#ifndef ZERO_TO_ONE_H +#define ZERO_TO_ONE_H + +namespace igraph { + +double unit_limiter(double vUnitDouble); + +} // namespace igraph + +#endif diff --git a/src/AMD/Include/amd.h b/src/AMD/Include/amd.h new file mode 100644 index 0000000..a38fd31 --- /dev/null +++ b/src/AMD/Include/amd.h @@ -0,0 +1,411 @@ +/* ========================================================================= */ +/* === AMD: approximate minimum degree ordering =========================== */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD Version 2.2, Copyright (c) 2007 by Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* AMD finds a symmetric ordering P of a matrix A so that the Cholesky + * factorization of P*A*P' has fewer nonzeros and takes less work than the + * Cholesky factorization of A. If A is not symmetric, then it performs its + * ordering on the matrix A+A'. Two sets of user-callable routines are + * provided, one for int integers and the other for SuiteSparse_long integers. + * + * The method is based on the approximate minimum degree algorithm, discussed + * in Amestoy, Davis, and Duff, "An approximate degree ordering algorithm", + * SIAM Journal of Matrix Analysis and Applications, vol. 17, no. 4, pp. + * 886-905, 1996. This package can perform both the AMD ordering (with + * aggressive absorption), and the AMDBAR ordering (without aggressive + * absorption) discussed in the above paper. This package differs from the + * Fortran codes discussed in the paper: + * + * (1) it can ignore "dense" rows and columns, leading to faster run times + * (2) it computes the ordering of A+A' if A is not symmetric + * (3) it is followed by a depth-first post-ordering of the assembly tree + * (or supernodal elimination tree) + * + * For historical reasons, the Fortran versions, amd.f and amdbar.f, have + * been left (nearly) unchanged. They compute the identical ordering as + * described in the above paper. + */ + +#ifndef AMD_H +#define AMD_H + +/* make it easy for C++ programs to include AMD */ +#ifdef __cplusplus +extern "C" { +#endif + +/* get the definition of size_t: */ +#include + +#include "SuiteSparse_config.h" + +int amd_order /* returns AMD_OK, AMD_OK_BUT_JUMBLED, + * AMD_INVALID, or AMD_OUT_OF_MEMORY */ +( + int n, /* A is n-by-n. n must be >= 0. */ + const int Ap [ ], /* column pointers for A, of size n+1 */ + const int Ai [ ], /* row indices of A, of size nz = Ap [n] */ + int P [ ], /* output permutation, of size n */ + double Control [ ], /* input Control settings, of size AMD_CONTROL */ + double Info [ ] /* output Info statistics, of size AMD_INFO */ +) ; + +SuiteSparse_long amd_l_order /* see above for description of arguments */ +( + SuiteSparse_long n, + const SuiteSparse_long Ap [ ], + const SuiteSparse_long Ai [ ], + SuiteSparse_long P [ ], + double Control [ ], + double Info [ ] +) ; + +/* Input arguments (not modified): + * + * n: the matrix A is n-by-n. + * Ap: an int/SuiteSparse_long array of size n+1, containing column + * pointers of A. + * Ai: an int/SuiteSparse_long array of size nz, containing the row + * indices of A, where nz = Ap [n]. + * Control: a double array of size AMD_CONTROL, containing control + * parameters. Defaults are used if Control is NULL. + * + * Output arguments (not defined on input): + * + * P: an int/SuiteSparse_long array of size n, containing the output + * permutation. If row i is the kth pivot row, then P [k] = i. In + * MATLAB notation, the reordered matrix is A (P,P). + * Info: a double array of size AMD_INFO, containing statistical + * information. Ignored if Info is NULL. + * + * On input, the matrix A is stored in column-oriented form. The row indices + * of nonzero entries in column j are stored in Ai [Ap [j] ... Ap [j+1]-1]. + * + * If the row indices appear in ascending order in each column, and there + * are no duplicate entries, then amd_order is slightly more efficient in + * terms of time and memory usage. If this condition does not hold, a copy + * of the matrix is created (where these conditions do hold), and the copy is + * ordered. This feature is new to v2.0 (v1.2 and earlier required this + * condition to hold for the input matrix). + * + * Row indices must be in the range 0 to + * n-1. Ap [0] must be zero, and thus nz = Ap [n] is the number of nonzeros + * in A. The array Ap is of size n+1, and the array Ai is of size nz = Ap [n]. + * The matrix does not need to be symmetric, and the diagonal does not need to + * be present (if diagonal entries are present, they are ignored except for + * the output statistic Info [AMD_NZDIAG]). The arrays Ai and Ap are not + * modified. This form of the Ap and Ai arrays to represent the nonzero + * pattern of the matrix A is the same as that used internally by MATLAB. + * If you wish to use a more flexible input structure, please see the + * umfpack_*_triplet_to_col routines in the UMFPACK package, at + * http://www.suitesparse.com. + * + * Restrictions: n >= 0. Ap [0] = 0. Ap [j] <= Ap [j+1] for all j in the + * range 0 to n-1. nz = Ap [n] >= 0. Ai [0..nz-1] must be in the range 0 + * to n-1. Finally, Ai, Ap, and P must not be NULL. If any of these + * restrictions are not met, AMD returns AMD_INVALID. + * + * AMD returns: + * + * AMD_OK if the matrix is valid and sufficient memory can be allocated to + * perform the ordering. + * + * AMD_OUT_OF_MEMORY if not enough memory can be allocated. + * + * AMD_INVALID if the input arguments n, Ap, Ai are invalid, or if P is + * NULL. + * + * AMD_OK_BUT_JUMBLED if the matrix had unsorted columns, and/or duplicate + * entries, but was otherwise valid. + * + * The AMD routine first forms the pattern of the matrix A+A', and then + * computes a fill-reducing ordering, P. If P [k] = i, then row/column i of + * the original is the kth pivotal row. In MATLAB notation, the permuted + * matrix is A (P,P), except that 0-based indexing is used instead of the + * 1-based indexing in MATLAB. + * + * The Control array is used to set various parameters for AMD. If a NULL + * pointer is passed, default values are used. The Control array is not + * modified. + * + * Control [AMD_DENSE]: controls the threshold for "dense" rows/columns. + * A dense row/column in A+A' can cause AMD to spend a lot of time in + * ordering the matrix. If Control [AMD_DENSE] >= 0, rows/columns + * with more than Control [AMD_DENSE] * sqrt (n) entries are ignored + * during the ordering, and placed last in the output order. The + * default value of Control [AMD_DENSE] is 10. If negative, no + * rows/columns are treated as "dense". Rows/columns with 16 or + * fewer off-diagonal entries are never considered "dense". + * + * Control [AMD_AGGRESSIVE]: controls whether or not to use aggressive + * absorption, in which a prior element is absorbed into the current + * element if is a subset of the current element, even if it is not + * adjacent to the current pivot element (refer to Amestoy, Davis, + * & Duff, 1996, for more details). The default value is nonzero, + * which means to perform aggressive absorption. This nearly always + * leads to a better ordering (because the approximate degrees are + * more accurate) and a lower execution time. There are cases where + * it can lead to a slightly worse ordering, however. To turn it off, + * set Control [AMD_AGGRESSIVE] to 0. + * + * Control [2..4] are not used in the current version, but may be used in + * future versions. + * + * The Info array provides statistics about the ordering on output. If it is + * not present, the statistics are not returned. This is not an error + * condition. + * + * Info [AMD_STATUS]: the return value of AMD, either AMD_OK, + * AMD_OK_BUT_JUMBLED, AMD_OUT_OF_MEMORY, or AMD_INVALID. + * + * Info [AMD_N]: n, the size of the input matrix + * + * Info [AMD_NZ]: the number of nonzeros in A, nz = Ap [n] + * + * Info [AMD_SYMMETRY]: the symmetry of the matrix A. It is the number + * of "matched" off-diagonal entries divided by the total number of + * off-diagonal entries. An entry A(i,j) is matched if A(j,i) is also + * an entry, for any pair (i,j) for which i != j. In MATLAB notation, + * S = spones (A) ; + * B = tril (S, -1) + triu (S, 1) ; + * symmetry = nnz (B & B') / nnz (B) ; + * + * Info [AMD_NZDIAG]: the number of entries on the diagonal of A. + * + * Info [AMD_NZ_A_PLUS_AT]: the number of nonzeros in A+A', excluding the + * diagonal. If A is perfectly symmetric (Info [AMD_SYMMETRY] = 1) + * with a fully nonzero diagonal, then Info [AMD_NZ_A_PLUS_AT] = nz-n + * (the smallest possible value). If A is perfectly unsymmetric + * (Info [AMD_SYMMETRY] = 0, for an upper triangular matrix, for + * example) with no diagonal, then Info [AMD_NZ_A_PLUS_AT] = 2*nz + * (the largest possible value). + * + * Info [AMD_NDENSE]: the number of "dense" rows/columns of A+A' that were + * removed from A prior to ordering. These are placed last in the + * output order P. + * + * Info [AMD_MEMORY]: the amount of memory used by AMD, in bytes. In the + * current version, this is 1.2 * Info [AMD_NZ_A_PLUS_AT] + 9*n + * times the size of an integer. This is at most 2.4nz + 9n. This + * excludes the size of the input arguments Ai, Ap, and P, which have + * a total size of nz + 2*n + 1 integers. + * + * Info [AMD_NCMPA]: the number of garbage collections performed. + * + * Info [AMD_LNZ]: the number of nonzeros in L (excluding the diagonal). + * This is a slight upper bound because mass elimination is combined + * with the approximate degree update. It is a rough upper bound if + * there are many "dense" rows/columns. The rest of the statistics, + * below, are also slight or rough upper bounds, for the same reasons. + * The post-ordering of the assembly tree might also not exactly + * correspond to a true elimination tree postordering. + * + * Info [AMD_NDIV]: the number of divide operations for a subsequent LDL' + * or LU factorization of the permuted matrix A (P,P). + * + * Info [AMD_NMULTSUBS_LDL]: the number of multiply-subtract pairs for a + * subsequent LDL' factorization of A (P,P). + * + * Info [AMD_NMULTSUBS_LU]: the number of multiply-subtract pairs for a + * subsequent LU factorization of A (P,P), assuming that no numerical + * pivoting is required. + * + * Info [AMD_DMAX]: the maximum number of nonzeros in any column of L, + * including the diagonal. + * + * Info [14..19] are not used in the current version, but may be used in + * future versions. + */ + +/* ------------------------------------------------------------------------- */ +/* direct interface to AMD */ +/* ------------------------------------------------------------------------- */ + +/* amd_2 is the primary AMD ordering routine. It is not meant to be + * user-callable because of its restrictive inputs and because it destroys + * the user's input matrix. It does not check its inputs for errors, either. + * However, if you can work with these restrictions it can be faster than + * amd_order and use less memory (assuming that you can create your own copy + * of the matrix for AMD to destroy). Refer to AMD/Source/amd_2.c for a + * description of each parameter. */ + +void amd_2 +( + int n, + int Pe [ ], + int Iw [ ], + int Len [ ], + int iwlen, + int pfree, + int Nv [ ], + int Next [ ], + int Last [ ], + int Head [ ], + int Elen [ ], + int Degree [ ], + int W [ ], + double Control [ ], + double Info [ ] +) ; + +void amd_l2 +( + SuiteSparse_long n, + SuiteSparse_long Pe [ ], + SuiteSparse_long Iw [ ], + SuiteSparse_long Len [ ], + SuiteSparse_long iwlen, + SuiteSparse_long pfree, + SuiteSparse_long Nv [ ], + SuiteSparse_long Next [ ], + SuiteSparse_long Last [ ], + SuiteSparse_long Head [ ], + SuiteSparse_long Elen [ ], + SuiteSparse_long Degree [ ], + SuiteSparse_long W [ ], + double Control [ ], + double Info [ ] +) ; + +/* ------------------------------------------------------------------------- */ +/* amd_valid */ +/* ------------------------------------------------------------------------- */ + +/* Returns AMD_OK or AMD_OK_BUT_JUMBLED if the matrix is valid as input to + * amd_order; the latter is returned if the matrix has unsorted and/or + * duplicate row indices in one or more columns. Returns AMD_INVALID if the + * matrix cannot be passed to amd_order. For amd_order, the matrix must also + * be square. The first two arguments are the number of rows and the number + * of columns of the matrix. For its use in AMD, these must both equal n. + * + * NOTE: this routine returned TRUE/FALSE in v1.2 and earlier. + */ + +int amd_valid +( + int n_row, /* # of rows */ + int n_col, /* # of columns */ + const int Ap [ ], /* column pointers, of size n_col+1 */ + const int Ai [ ] /* row indices, of size Ap [n_col] */ +) ; + +SuiteSparse_long amd_l_valid +( + SuiteSparse_long n_row, + SuiteSparse_long n_col, + const SuiteSparse_long Ap [ ], + const SuiteSparse_long Ai [ ] +) ; + +/* ------------------------------------------------------------------------- */ +/* AMD memory manager and printf routines */ +/* ------------------------------------------------------------------------- */ + +/* The user can redefine these to change the malloc, free, and printf routines + * that AMD uses. */ + +#ifndef EXTERN +#define EXTERN extern +#endif + +EXTERN void *(*amd_malloc) (size_t) ; /* pointer to malloc */ +EXTERN void (*amd_free) (void *) ; /* pointer to free */ +EXTERN void *(*amd_realloc) (void *, size_t) ; /* pointer to realloc */ +EXTERN void *(*amd_calloc) (size_t, size_t) ; /* pointer to calloc */ +EXTERN int (*amd_printf) (const char *, ...) ; /* pointer to printf */ + +/* ------------------------------------------------------------------------- */ +/* AMD Control and Info arrays */ +/* ------------------------------------------------------------------------- */ + +/* amd_defaults: sets the default control settings */ +void amd_defaults (double Control [ ]) ; +void amd_l_defaults (double Control [ ]) ; + +/* amd_control: prints the control settings */ +void amd_control (double Control [ ]) ; +void amd_l_control (double Control [ ]) ; + +/* amd_info: prints the statistics */ +void amd_info (double Info [ ]) ; +void amd_l_info (double Info [ ]) ; + +#define AMD_CONTROL 5 /* size of Control array */ +#define AMD_INFO 20 /* size of Info array */ + +/* contents of Control */ +#define AMD_DENSE 0 /* "dense" if degree > Control [0] * sqrt (n) */ +#define AMD_AGGRESSIVE 1 /* do aggressive absorption if Control [1] != 0 */ + +/* default Control settings */ +#define AMD_DEFAULT_DENSE 10.0 /* default "dense" degree 10*sqrt(n) */ +#define AMD_DEFAULT_AGGRESSIVE 1 /* do aggressive absorption by default */ + +/* contents of Info */ +#define AMD_STATUS 0 /* return value of amd_order and amd_l_order */ +#define AMD_N 1 /* A is n-by-n */ +#define AMD_NZ 2 /* number of nonzeros in A */ +#define AMD_SYMMETRY 3 /* symmetry of pattern (1 is sym., 0 is unsym.) */ +#define AMD_NZDIAG 4 /* # of entries on diagonal */ +#define AMD_NZ_A_PLUS_AT 5 /* nz in A+A' */ +#define AMD_NDENSE 6 /* number of "dense" rows/columns in A */ +#define AMD_MEMORY 7 /* amount of memory used by AMD */ +#define AMD_NCMPA 8 /* number of garbage collections in AMD */ +#define AMD_LNZ 9 /* approx. nz in L, excluding the diagonal */ +#define AMD_NDIV 10 /* number of fl. point divides for LU and LDL' */ +#define AMD_NMULTSUBS_LDL 11 /* number of fl. point (*,-) pairs for LDL' */ +#define AMD_NMULTSUBS_LU 12 /* number of fl. point (*,-) pairs for LU */ +#define AMD_DMAX 13 /* max nz. in any column of L, incl. diagonal */ + +/* ------------------------------------------------------------------------- */ +/* return values of AMD */ +/* ------------------------------------------------------------------------- */ + +#define AMD_OK 0 /* success */ +#define AMD_OUT_OF_MEMORY -1 /* malloc failed, or problem too large */ +#define AMD_INVALID -2 /* input arguments are not valid */ +#define AMD_OK_BUT_JUMBLED 1 /* input matrix is OK for amd_order, but + * columns were not sorted, and/or duplicate entries were present. AMD had + * to do extra work before ordering the matrix. This is a warning, not an + * error. */ + +/* ========================================================================== */ +/* === AMD version ========================================================== */ +/* ========================================================================== */ + +/* AMD Version 1.2 and later include the following definitions. + * As an example, to test if the version you are using is 1.2 or later: + * + * #ifdef AMD_VERSION + * if (AMD_VERSION >= AMD_VERSION_CODE (1,2)) ... + * #endif + * + * This also works during compile-time: + * + * #if defined(AMD_VERSION) && (AMD_VERSION >= AMD_VERSION_CODE (1,2)) + * printf ("This is version 1.2 or later\n") ; + * #else + * printf ("This is an early version\n") ; + * #endif + * + * Versions 1.1 and earlier of AMD do not include a #define'd version number. + */ + +#define AMD_DATE "Jun 20, 2012" +#define AMD_VERSION_CODE(main,sub) ((main) * 1000 + (sub)) +#define AMD_MAIN_VERSION 2 +#define AMD_SUB_VERSION 3 +#define AMD_SUBSUB_VERSION 1 +#define AMD_VERSION AMD_VERSION_CODE(AMD_MAIN_VERSION,AMD_SUB_VERSION) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/AMD/Include/amd_internal.h b/src/AMD/Include/amd_internal.h new file mode 100644 index 0000000..c5f5493 --- /dev/null +++ b/src/AMD/Include/amd_internal.h @@ -0,0 +1,347 @@ +/* ========================================================================= */ +/* === amd_internal.h ====================================================== */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* This file is for internal use in AMD itself, and does not normally need to + * be included in user code (it is included in UMFPACK, however). All others + * should use amd.h instead. + * + * The following compile-time definitions affect how AMD is compiled. + * + * -DNPRINT + * + * Disable all printing. stdio.h will not be included. Printing can + * be re-enabled at run-time by setting the global pointer amd_printf + * to printf (or mexPrintf for a MATLAB mexFunction). + * + * -DNMALLOC + * + * No memory manager is defined at compile-time. You MUST define the + * function pointers amd_malloc, amd_free, amd_realloc, and + * amd_calloc at run-time for AMD to work properly. + */ + +/* ========================================================================= */ +/* === NDEBUG ============================================================== */ +/* ========================================================================= */ + +/* + * Turning on debugging takes some work (see below). If you do not edit this + * file, then debugging is always turned off, regardless of whether or not + * -DNDEBUG is specified in your compiler options. + * + * If AMD is being compiled as a mexFunction, then MATLAB_MEX_FILE is defined, + * and mxAssert is used instead of assert. If debugging is not enabled, no + * MATLAB include files or functions are used. Thus, the AMD library libamd.a + * can be safely used in either a stand-alone C program or in another + * mexFunction, without any change. + */ + +/* + AMD will be exceedingly slow when running in debug mode. The next three + lines ensure that debugging is turned off. +*/ +#ifndef NDEBUG +#define NDEBUG +#endif + +/* + To enable debugging, uncomment the following line: +#undef NDEBUG +*/ + +/* ------------------------------------------------------------------------- */ +/* ANSI include files */ +/* ------------------------------------------------------------------------- */ + +/* from stdlib.h: size_t, malloc, free, realloc, and calloc */ +#include + +#if !defined(NPRINT) || !defined(NDEBUG) +/* from stdio.h: printf. Not included if NPRINT is defined at compile time. + * fopen and fscanf are used when debugging. */ +#include +#endif + +/* from limits.h: INT_MAX and LONG_MAX */ +#include + +/* from math.h: sqrt */ +#include + +/* ------------------------------------------------------------------------- */ +/* MATLAB include files (only if being used in or via MATLAB) */ +/* ------------------------------------------------------------------------- */ + +#ifdef MATLAB_MEX_FILE +#include "matrix.h" +#include "mex.h" +#endif + +/* ------------------------------------------------------------------------- */ +/* basic definitions */ +/* ------------------------------------------------------------------------- */ + +#ifdef FLIP +#undef FLIP +#endif + +#ifdef MAX +#undef MAX +#endif + +#ifdef MIN +#undef MIN +#endif + +#ifdef EMPTY +#undef EMPTY +#endif + +#ifdef GLOBAL +#undef GLOBAL +#endif + +#ifdef PRIVATE +#undef PRIVATE +#endif + +/* FLIP is a "negation about -1", and is used to mark an integer i that is + * normally non-negative. FLIP (EMPTY) is EMPTY. FLIP of a number > EMPTY + * is negative, and FLIP of a number < EMTPY is positive. FLIP (FLIP (i)) = i + * for all integers i. UNFLIP (i) is >= EMPTY. */ +#define EMPTY (-1) +#define FLIP(i) (-(i)-2) +#define UNFLIP(i) ((i < EMPTY) ? FLIP (i) : (i)) + +/* for integer MAX/MIN, or for doubles when we don't care how NaN's behave: */ +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) + +/* logical expression of p implies q: */ +#define IMPLIES(p,q) (!(p) || (q)) + +/* Note that the IBM RS 6000 xlc predefines TRUE and FALSE in . */ +/* The Compaq Alpha also predefines TRUE and FALSE. */ +#ifdef TRUE +#undef TRUE +#endif +#ifdef FALSE +#undef FALSE +#endif + +#define TRUE (1) +#define FALSE (0) +#define PRIVATE static +#define GLOBAL +#define EMPTY (-1) + +/* Note that Linux's gcc 2.96 defines NULL as ((void *) 0), but other */ +/* compilers (even gcc 2.95.2 on Solaris) define NULL as 0 or (0). We */ +/* need to use the ANSI standard value of 0. */ +#ifdef NULL +#undef NULL +#endif + +#define NULL 0 + +/* largest value of size_t */ +#ifndef SIZE_T_MAX +#ifdef SIZE_MAX +/* C99 only */ +#define SIZE_T_MAX SIZE_MAX +#else +#define SIZE_T_MAX ((size_t) (-1)) +#endif +#endif + +/* ------------------------------------------------------------------------- */ +/* integer type for AMD: int or SuiteSparse_long */ +/* ------------------------------------------------------------------------- */ + +#include "amd.h" + +#if defined (DLONG) || defined (ZLONG) + +#define Int SuiteSparse_long +#define ID SuiteSparse_long_id +#define Int_MAX SuiteSparse_long_max + +#define AMD_order amd_l_order +#define AMD_defaults amd_l_defaults +#define AMD_control amd_l_control +#define AMD_info amd_l_info +#define AMD_1 amd_l1 +#define AMD_2 amd_l2 +#define AMD_valid amd_l_valid +#define AMD_aat amd_l_aat +#define AMD_postorder amd_l_postorder +#define AMD_post_tree amd_l_post_tree +#define AMD_dump amd_l_dump +#define AMD_debug amd_l_debug +#define AMD_debug_init amd_l_debug_init +#define AMD_preprocess amd_l_preprocess + +#else + +#define Int int +#define ID "%d" +#define Int_MAX INT_MAX + +#define AMD_order amd_order +#define AMD_defaults amd_defaults +#define AMD_control amd_control +#define AMD_info amd_info +#define AMD_1 amd_1 +#define AMD_2 amd_2 +#define AMD_valid amd_valid +#define AMD_aat amd_aat +#define AMD_postorder amd_postorder +#define AMD_post_tree amd_post_tree +#define AMD_dump amd_dump +#define AMD_debug amd_debug +#define AMD_debug_init amd_debug_init +#define AMD_preprocess amd_preprocess + +#endif + +/* ========================================================================= */ +/* === PRINTF macro ======================================================== */ +/* ========================================================================= */ + +/* All output goes through the PRINTF macro. */ +#define PRINTF(params) { if (amd_printf != NULL) (void) amd_printf params ; } + +/* ------------------------------------------------------------------------- */ +/* AMD routine definitions (not user-callable) */ +/* ------------------------------------------------------------------------- */ + +GLOBAL size_t AMD_aat +( + Int n, + const Int Ap [ ], + const Int Ai [ ], + Int Len [ ], + Int Tp [ ], + double Info [ ] +) ; + +GLOBAL void AMD_1 +( + Int n, + const Int Ap [ ], + const Int Ai [ ], + Int P [ ], + Int Pinv [ ], + Int Len [ ], + Int slen, + Int S [ ], + double Control [ ], + double Info [ ] +) ; + +GLOBAL void AMD_postorder +( + Int nn, + Int Parent [ ], + Int Npiv [ ], + Int Fsize [ ], + Int Order [ ], + Int Child [ ], + Int Sibling [ ], + Int Stack [ ] +) ; + +GLOBAL Int AMD_post_tree +( + Int root, + Int k, + Int Child [ ], + const Int Sibling [ ], + Int Order [ ], + Int Stack [ ] +#ifndef NDEBUG + , Int nn +#endif +) ; + +GLOBAL void AMD_preprocess +( + Int n, + const Int Ap [ ], + const Int Ai [ ], + Int Rp [ ], + Int Ri [ ], + Int W [ ], + Int Flag [ ] +) ; + +/* ------------------------------------------------------------------------- */ +/* debugging definitions */ +/* ------------------------------------------------------------------------- */ + +#ifndef NDEBUG + +/* from assert.h: assert macro */ +#include + +#ifndef EXTERN +#define EXTERN extern +#endif + +EXTERN Int AMD_debug ; + +GLOBAL void AMD_debug_init ( char *s ) ; + +GLOBAL void AMD_dump +( + Int n, + Int Pe [ ], + Int Iw [ ], + Int Len [ ], + Int iwlen, + Int pfree, + Int Nv [ ], + Int Next [ ], + Int Last [ ], + Int Head [ ], + Int Elen [ ], + Int Degree [ ], + Int W [ ], + Int nel +) ; + +#ifdef ASSERT +#undef ASSERT +#endif + +/* Use mxAssert if AMD is compiled into a mexFunction */ +#ifdef MATLAB_MEX_FILE +#define ASSERT(expression) (mxAssert ((expression), "")) +#else +#define ASSERT(expression) (assert (expression)) +#endif + +#define AMD_DEBUG0(params) { PRINTF (params) ; } +#define AMD_DEBUG1(params) { if (AMD_debug >= 1) PRINTF (params) ; } +#define AMD_DEBUG2(params) { if (AMD_debug >= 2) PRINTF (params) ; } +#define AMD_DEBUG3(params) { if (AMD_debug >= 3) PRINTF (params) ; } +#define AMD_DEBUG4(params) { if (AMD_debug >= 4) PRINTF (params) ; } + +#else + +/* no debugging */ +#define ASSERT(expression) +#define AMD_DEBUG0(params) +#define AMD_DEBUG1(params) +#define AMD_DEBUG2(params) +#define AMD_DEBUG3(params) +#define AMD_DEBUG4(params) + +#endif diff --git a/src/AMD/Makefile b/src/AMD/Makefile new file mode 100644 index 0000000..2db3476 --- /dev/null +++ b/src/AMD/Makefile @@ -0,0 +1,73 @@ +#------------------------------------------------------------------------------ +# AMD Makefile (for GNU Make or original make) +#------------------------------------------------------------------------------ + +VERSION = 2.3.1 + +default: all + +include ../SuiteSparse_config/SuiteSparse_config.mk + +demos: all + +# Compile all C code. Do not compile the FORTRAN versions. +all: + ( cd Lib ; $(MAKE) ) + ( cd Demo ; $(MAKE) ) + +# compile just the C-callable libraries (not Demos) +library: + ( cd Lib ; $(MAKE) ) + +# compile the FORTRAN libraries and demo programs (not compiled by "make all") +fortran: + ( cd Lib ; $(MAKE) fortran ) + ( cd Demo ; $(MAKE) fortran ) + +# compile a FORTRAN demo program that calls the C version of AMD +# (not compiled by "make all") +cross: + ( cd Demo ; $(MAKE) cross ) + +# remove object files, but keep the compiled programs and library archives +clean: + ( cd Lib ; $(MAKE) clean ) + ( cd Demo ; $(MAKE) clean ) + ( cd MATLAB ; $(RM) $(CLEAN) ) + ( cd Doc ; $(MAKE) clean ) + +# clean, and then remove compiled programs and library archives +purge: + ( cd Lib ; $(MAKE) purge ) + ( cd Demo ; $(MAKE) purge ) + ( cd MATLAB ; $(RM) $(CLEAN) ; $(RM) *.mex* ) + ( cd Doc ; $(MAKE) purge ) + +distclean: purge + +# create PDF documents for the original distribution +docs: + ( cd Doc ; $(MAKE) ) + +# get ready for distribution +dist: purge + ( cd Demo ; $(MAKE) dist ) + ( cd Doc ; $(MAKE) ) + +ccode: library + +lib: library + +# install AMD +install: + $(CP) Lib/libamd.a $(INSTALL_LIB)/libamd.$(VERSION).a + ( cd $(INSTALL_LIB) ; ln -sf libamd.$(VERSION).a libamd.a ) + $(CP) Include/amd.h $(INSTALL_INCLUDE) + chmod 644 $(INSTALL_LIB)/libamd* + chmod 644 $(INSTALL_INCLUDE)/amd.h + +# uninstall AMD +uninstall: + $(RM) $(INSTALL_LIB)/libamd*.a + $(RM) $(INSTALL_INCLUDE)/amd.h + diff --git a/src/AMD/README.txt b/src/AMD/README.txt new file mode 100644 index 0000000..2f0a4ff --- /dev/null +++ b/src/AMD/README.txt @@ -0,0 +1,213 @@ +AMD, Copyright (c) 2009-2012 by Timothy A. Davis (http://www.suitesparse.com), +Patrick R. Amestoy, and Iain S. Duff. All Rights Reserved. AMD is available +under alternate licences; contact T. Davis for details. + +AMD: a set of routines for permuting sparse matrices prior to + factorization. Includes a version in C, a version in Fortran, and a MATLAB + mexFunction. + +Requires SuiteSparse_config, in the ../SuiteSparse_config directory relative to +this directory. + +Quick start (Unix, or Windows with Cygwin): + + To compile, test, and install AMD, you may wish to first configure the + installation by editting the ../SuiteSparse_config/SuiteSparse_config.mk + file. Next, cd to this directory (AMD) and type "make" (or "make lib" if + you do not have MATLAB). To compile and run a demo program for the Fortran + version, type "make fortran". When done, type "make clean" to remove + unused *.o files (keeps the compiled libraries and demo programs). See the + User Guide (Doc/AMD_UserGuide.pdf), or + ../SuiteSparse_config/SuiteSparse_config.mk for more details. + +Quick start (for MATLAB users); + + To compile, test, and install the AMD mexFunction, cd to the + AMD/MATLAB directory and type amd_make at the MATLAB prompt. + +------------------------------------------------------------------------------- + +AMD License: + + Your use or distribution of AMD or any modified version of + AMD implies that you agree to this License. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + USA + + Permission is hereby granted to use or copy this program under the + terms of the GNU LGPL, provided that the Copyright, this License, + and the Availability of the original version is retained on all copies. + User documentation of any code that uses this code or any modified + version of this code must cite the Copyright, this License, the + Availability note, and "Used by permission." Permission to modify + the code and to distribute modified code is granted, provided the + Copyright, this License, and the Availability note are retained, + and a notice that the code was modified is included. + +Availability: + + http://www.suitesparse.com + +------------------------------------------------------------------------------- + +This is the AMD README file. It is a terse overview of AMD. +Refer to the User Guide (Doc/AMD_UserGuide.pdf) for how to install +and use AMD. + +Description: + + AMD is a set of routines for pre-ordering sparse matrices prior to Cholesky + or LU factorization, using the approximate minimum degree ordering + algorithm. Written in ANSI/ISO C with a MATLAB interface, and in + Fortran 77. + +Authors: + + Timothy A. Davis (DrTimothyAldenDavis@gmail.com) + Patrick R. Amestory, ENSEEIHT, Toulouse, France. + Iain S. Duff, Rutherford Appleton Laboratory, UK. + +Acknowledgements: + + This work was supported by the National Science Foundation, under + grants DMS-9504974, DMS-9803599, and CCR-0203270. + + Portions of this work were done while on sabbatical at Stanford University + and Lawrence Berkeley National Laboratory (with funding from the SciDAC + program). I would like to thank Gene Golub, Esmond Ng, and Horst Simon + for making this sabbatical possible. + +------------------------------------------------------------------------------- +Files and directories in the AMD distribution: +------------------------------------------------------------------------------- + + --------------------------------------------------------------------------- + Subdirectories of the AMD directory: + --------------------------------------------------------------------------- + + Doc documentation + Source primary source code + Include include file for use in your code that calls AMD + Demo demo programs. also serves as test of the AMD installation. + MATLAB AMD mexFunction for MATLAB, and supporting m-files + Lib where the compiled C-callable and Fortran-callable + AMD libraries placed. + + --------------------------------------------------------------------------- + Files in the AMD directory: + --------------------------------------------------------------------------- + + Makefile top-level Makefile for GNU make or original make. + Windows users would require Cygwin to use "make" + + README.txt this file + + --------------------------------------------------------------------------- + Doc directory: documentation + --------------------------------------------------------------------------- + + ChangeLog change log + License the AMD License + Makefile for creating the documentation + AMD_UserGuide.bib AMD User Guide (references) + AMD_UserGuide.tex AMD User Guide (LaTeX) + AMD_UserGuide.pdf AMD User Guide (PDF) + lesser.txt the GNU LGPL license + + --------------------------------------------------------------------------- + Source directory: + --------------------------------------------------------------------------- + + amd_order.c user-callable, primary AMD ordering routine + amd_control.c user-callable, prints the control parameters + amd_defaults.c user-callable, sets default control parameters + amd_info.c user-callable, prints the statistics from AMD + + amd_1.c non-user-callable, construct A+A' + amd_2.c user-callable, primary ordering kernel + (a C version of amd.f and amdbar.f, with + post-ordering added) + amd_aat.c non-user-callable, computes nnz (A+A') + amd_dump.c non-user-callable, debugging routines + amd_postorder.c non-user-callable, postorder + amd_post_tree.c non-user-callable, postorder just one tree + amd_valid.c non-user-callable, verifies a matrix + amd_preprocess.c non-user-callable, computes A', removes duplic + + amd.f user-callable Fortran 77 version + amdbar.f user-callable Fortran 77 version + + --------------------------------------------------------------------------- + Include directory: + --------------------------------------------------------------------------- + + amd.h include file for C programs that use AMD + amd_internal.h non-user-callable, include file for AMD + + --------------------------------------------------------------------------- + Demo directory: + --------------------------------------------------------------------------- + + Makefile for GNU make or original make + + amd_demo.c C demo program for AMD + amd_demo.out output of amd_demo.c + + amd_demo2.c C demo program for AMD, jumbled matrix + amd_demo2.out output of amd_demo2.c + + amd_l_demo.c C demo program for AMD (long integer version) + amd_l_demo.out output of amd_l_demo.c + + amd_simple.c simple C demo program for AMD + amd_simple.out output of amd_simple.c + + amd_f77demo.f Fortran 77 demo program for AMD + amd_f77demo.out output of amd_f77demo.f + + amd_f77simple.c simple Fortran 77 demo program for AMD + amd_f77simple.out output of amd_f77simple.f + + amd_f77cross.f Fortran 77 demo, calls the C version of AMD + amd_f77cross.out output of amd_f77cross.f + amd_f77wrapper.c Fortran-callable wrapper for C version of AMD + + --------------------------------------------------------------------------- + MATLAB directory: + --------------------------------------------------------------------------- + + GNUmakefile a nice Makefile, for GNU make + Makefile an ugly Unix Makefile (for older make's) + + Contents.m for "help amd2" listing of toolbox contents + + amd2.m MATLAB help file for AMD + amd_make.m MATLAB m-file for compiling AMD mexFunction + amd_install.m compile and install the AMD mexFunction + + amd_mex.c AMD mexFunction for MATLAB + + amd_demo.m MATLAB demo for AMD + amd_demo.m.out diary output of amd_demo.m + can_24.mat input file for AMD demo + + --------------------------------------------------------------------------- + Lib directory: libamd.a and libamdf77.a libraries placed here + --------------------------------------------------------------------------- + + GNUmakefile a nice Makefile, for GNU make + Makefile an ugly Unix Makefile (for older make's) + libamd.def AMD definitions for Windows diff --git a/src/AMD/Source/amd.f b/src/AMD/Source/amd.f new file mode 100644 index 0000000..ccfe3e8 --- /dev/null +++ b/src/AMD/Source/amd.f @@ -0,0 +1,1214 @@ +C----------------------------------------------------------------------- +C AMD: approximate minimum degree, with aggressive absorption +C----------------------------------------------------------------------- + + SUBROUTINE AMD + $ (N, PE, IW, LEN, IWLEN, PFREE, NV, NEXT, + $ LAST, HEAD, ELEN, DEGREE, NCMPA, W) + + INTEGER N, IWLEN, PFREE, NCMPA, IW (IWLEN), PE (N), + $ DEGREE (N), NV (N), NEXT (N), LAST (N), HEAD (N), + $ ELEN (N), W (N), LEN (N) + +C Given a representation of the nonzero pattern of a symmetric matrix, +C A, (excluding the diagonal) perform an approximate minimum +C (UMFPACK/MA38-style) degree ordering to compute a pivot order +C such that the introduction of nonzeros (fill-in) in the Cholesky +C factors A = LL^T are kept low. At each step, the pivot +C selected is the one with the minimum UMFPACK/MA38-style +C upper-bound on the external degree. +C +C Aggresive absorption is used to tighten the bound on the degree. + +C ********************************************************************** +C ***** CAUTION: ARGUMENTS ARE NOT CHECKED FOR ERRORS ON INPUT. ****** +C ********************************************************************** + +C References: +C +C [1] Timothy A. Davis and Iain Duff, "An unsymmetric-pattern +C multifrontal method for sparse LU factorization", SIAM J. +C Matrix Analysis and Applications, vol. 18, no. 1, pp. +C 140-158. Discusses UMFPACK / MA38, which first introduced +C the approximate minimum degree used by this routine. +C +C [2] Patrick Amestoy, Timothy A. Davis, and Iain S. Duff, "An +C approximate degree ordering algorithm," SIAM J. Matrix +C Analysis and Applications, vol. 17, no. 4, pp. 886-905, +C 1996. Discusses AMD, AMDBAR, and MC47B. +C +C [3] Alan George and Joseph Liu, "The evolution of the minimum +C degree ordering algorithm," SIAM Review, vol. 31, no. 1, +C pp. 1-19, 1989. We list below the features mentioned in +C that paper that this code includes: +C +C mass elimination: +C Yes. MA27 relied on supervariable detection for mass +C elimination. +C indistinguishable nodes: +C Yes (we call these "supervariables"). This was also in +C the MA27 code - although we modified the method of +C detecting them (the previous hash was the true degree, +C which we no longer keep track of). A supervariable is +C a set of rows with identical nonzero pattern. All +C variables in a supervariable are eliminated together. +C Each supervariable has as its numerical name that of +C one of its variables (its principal variable). +C quotient graph representation: +C Yes. We use the term "element" for the cliques formed +C during elimination. This was also in the MA27 code. +C The algorithm can operate in place, but it will work +C more efficiently if given some "elbow room." +C element absorption: +C Yes. This was also in the MA27 code. +C external degree: +C Yes. The MA27 code was based on the true degree. +C incomplete degree update and multiple elimination: +C No. This was not in MA27, either. Our method of +C degree update within MC47B/BD is element-based, not +C variable-based. It is thus not well-suited for use +C with incomplete degree update or multiple elimination. + +C----------------------------------------------------------------------- +C Authors, and Copyright (C) 1995 by: +C Timothy A. Davis, Patrick Amestoy, Iain S. Duff, & John K. Reid. +C +C Acknowledgements: +C This work (and the UMFPACK package) was supported by the +C National Science Foundation (ASC-9111263 and DMS-9223088). +C The UMFPACK/MA38 approximate degree update algorithm, the +C unsymmetric analog which forms the basis of MC47B/BD, was +C developed while Tim Davis was supported by CERFACS (Toulouse, +C France) in a post-doctoral position. +C +C Date: September, 1995 +C----------------------------------------------------------------------- + +C----------------------------------------------------------------------- +C INPUT ARGUMENTS (unaltered): +C----------------------------------------------------------------------- + +C n: The matrix order. +C +C Restriction: 1 .le. n .lt. (iovflo/2)-2, where iovflo is +C the largest positive integer that your computer can represent. + +C iwlen: The length of iw (1..iwlen). On input, the matrix is +C stored in iw (1..pfree-1). However, iw (1..iwlen) should be +C slightly larger than what is required to hold the matrix, at +C least iwlen .ge. pfree + n is recommended. Otherwise, +C excessive compressions will take place. +C *** We do not recommend running this algorithm with *** +C *** iwlen .lt. pfree + n. *** +C *** Better performance will be obtained if *** +C *** iwlen .ge. pfree + n *** +C *** or better yet *** +C *** iwlen .gt. 1.2 * pfree *** +C *** (where pfree is its value on input). *** +C The algorithm will not run at all if iwlen .lt. pfree-1. +C +C Restriction: iwlen .ge. pfree-1 + +C----------------------------------------------------------------------- +C INPUT/OUPUT ARGUMENTS: +C----------------------------------------------------------------------- + +C pe: On input, pe (i) is the index in iw of the start of row i, or +C zero if row i has no off-diagonal non-zeros. +C +C During execution, it is used for both supervariables and +C elements: +C +C * Principal supervariable i: index into iw of the +C description of supervariable i. A supervariable +C represents one or more rows of the matrix +C with identical nonzero pattern. +C * Non-principal supervariable i: if i has been absorbed +C into another supervariable j, then pe (i) = -j. +C That is, j has the same pattern as i. +C Note that j might later be absorbed into another +C supervariable j2, in which case pe (i) is still -j, +C and pe (j) = -j2. +C * Unabsorbed element e: the index into iw of the description +C of element e, if e has not yet been absorbed by a +C subsequent element. Element e is created when +C the supervariable of the same name is selected as +C the pivot. +C * Absorbed element e: if element e is absorbed into element +C e2, then pe (e) = -e2. This occurs when the pattern of +C e (that is, Le) is found to be a subset of the pattern +C of e2 (that is, Le2). If element e is "null" (it has +C no nonzeros outside its pivot block), then pe (e) = 0. +C +C On output, pe holds the assembly tree/forest, which implicitly +C represents a pivot order with identical fill-in as the actual +C order (via a depth-first search of the tree). +C +C On output: +C If nv (i) .gt. 0, then i represents a node in the assembly tree, +C and the parent of i is -pe (i), or zero if i is a root. +C If nv (i) = 0, then (i,-pe (i)) represents an edge in a +C subtree, the root of which is a node in the assembly tree. + +C pfree: On input the tail end of the array, iw (pfree..iwlen), +C is empty, and the matrix is stored in iw (1..pfree-1). +C During execution, additional data is placed in iw, and pfree +C is modified so that iw (pfree..iwlen) is always the unused part +C of iw. On output, pfree is set equal to the size of iw that +C would have been needed for no compressions to occur. If +C ncmpa is zero, then pfree (on output) is less than or equal to +C iwlen, and the space iw (pfree+1 ... iwlen) was not used. +C Otherwise, pfree (on output) is greater than iwlen, and all the +C memory in iw was used. + +C----------------------------------------------------------------------- +C INPUT/MODIFIED (undefined on output): +C----------------------------------------------------------------------- + +C len: On input, len (i) holds the number of entries in row i of the +C matrix, excluding the diagonal. The contents of len (1..n) +C are undefined on output. + +C iw: On input, iw (1..pfree-1) holds the description of each row i +C in the matrix. The matrix must be symmetric, and both upper +C and lower triangular parts must be present. The diagonal must +C not be present. Row i is held as follows: +C +C len (i): the length of the row i data structure +C iw (pe (i) ... pe (i) + len (i) - 1): +C the list of column indices for nonzeros +C in row i (simple supervariables), excluding +C the diagonal. All supervariables start with +C one row/column each (supervariable i is just +C row i). +C if len (i) is zero on input, then pe (i) is ignored +C on input. +C +C Note that the rows need not be in any particular order, +C and there may be empty space between the rows. +C +C During execution, the supervariable i experiences fill-in. +C This is represented by placing in i a list of the elements +C that cause fill-in in supervariable i: +C +C len (i): the length of supervariable i +C iw (pe (i) ... pe (i) + elen (i) - 1): +C the list of elements that contain i. This list +C is kept short by removing absorbed elements. +C iw (pe (i) + elen (i) ... pe (i) + len (i) - 1): +C the list of supervariables in i. This list +C is kept short by removing nonprincipal +C variables, and any entry j that is also +C contained in at least one of the elements +C (j in Le) in the list for i (e in row i). +C +C When supervariable i is selected as pivot, we create an +C element e of the same name (e=i): +C +C len (e): the length of element e +C iw (pe (e) ... pe (e) + len (e) - 1): +C the list of supervariables in element e. +C +C An element represents the fill-in that occurs when supervariable +C i is selected as pivot (which represents the selection of row i +C and all non-principal variables whose principal variable is i). +C We use the term Le to denote the set of all supervariables +C in element e. Absorbed supervariables and elements are pruned +C from these lists when computationally convenient. +C +C CAUTION: THE INPUT MATRIX IS OVERWRITTEN DURING COMPUTATION. +C The contents of iw are undefined on output. + +C----------------------------------------------------------------------- +C OUTPUT (need not be set on input): +C----------------------------------------------------------------------- + +C nv: During execution, abs (nv (i)) is equal to the number of rows +C that are represented by the principal supervariable i. If i is +C a nonprincipal variable, then nv (i) = 0. Initially, +C nv (i) = 1 for all i. nv (i) .lt. 0 signifies that i is a +C principal variable in the pattern Lme of the current pivot +C element me. On output, nv (e) holds the true degree of element +C e at the time it was created (including the diagonal part). + +C ncmpa: The number of times iw was compressed. If this is +C excessive, then the execution took longer than what could have +C been. To reduce ncmpa, try increasing iwlen to be 10% or 20% +C larger than the value of pfree on input (or at least +C iwlen .ge. pfree + n). The fastest performance will be +C obtained when ncmpa is returned as zero. If iwlen is set to +C the value returned by pfree on *output*, then no compressions +C will occur. + +C elen: See the description of iw above. At the start of execution, +C elen (i) is set to zero. During execution, elen (i) is the +C number of elements in the list for supervariable i. When e +C becomes an element, elen (e) = -nel is set, where nel is the +C current step of factorization. elen (i) = 0 is done when i +C becomes nonprincipal. +C +C For variables, elen (i) .ge. 0 holds until just before the +C permutation vectors are computed. For elements, +C elen (e) .lt. 0 holds. +C +C On output elen (1..n) holds the inverse permutation (the same +C as the 'INVP' argument in Sparspak). That is, if k = elen (i), +C then row i is the kth pivot row. Row i of A appears as the +C (elen(i))-th row in the permuted matrix, PAP^T. + +C last: In a degree list, last (i) is the supervariable preceding i, +C or zero if i is the head of the list. In a hash bucket, +C last (i) is the hash key for i. last (head (hash)) is also +C used as the head of a hash bucket if head (hash) contains a +C degree list (see head, below). +C +C On output, last (1..n) holds the permutation (the same as the +C 'PERM' argument in Sparspak). That is, if i = last (k), then +C row i is the kth pivot row. Row last (k) of A is the k-th row +C in the permuted matrix, PAP^T. + +C----------------------------------------------------------------------- +C LOCAL (not input or output - used only during execution): +C----------------------------------------------------------------------- + +C degree: If i is a supervariable, then degree (i) holds the +C current approximation of the external degree of row i (an upper +C bound). The external degree is the number of nonzeros in row i, +C minus abs (nv (i)) (the diagonal part). The bound is equal to +C the external degree if elen (i) is less than or equal to two. +C +C We also use the term "external degree" for elements e to refer +C to |Le \ Lme|. If e is an element, then degree (e) holds |Le|, +C which is the degree of the off-diagonal part of the element e +C (not including the diagonal part). + +C head: head is used for degree lists. head (deg) is the first +C supervariable in a degree list (all supervariables i in a +C degree list deg have the same approximate degree, namely, +C deg = degree (i)). If the list deg is empty then +C head (deg) = 0. +C +C During supervariable detection head (hash) also serves as a +C pointer to a hash bucket. +C If head (hash) .gt. 0, there is a degree list of degree hash. +C The hash bucket head pointer is last (head (hash)). +C If head (hash) = 0, then the degree list and hash bucket are +C both empty. +C If head (hash) .lt. 0, then the degree list is empty, and +C -head (hash) is the head of the hash bucket. +C After supervariable detection is complete, all hash buckets +C are empty, and the (last (head (hash)) = 0) condition is +C restored for the non-empty degree lists. + +C next: next (i) is the supervariable following i in a link list, or +C zero if i is the last in the list. Used for two kinds of +C lists: degree lists and hash buckets (a supervariable can be +C in only one kind of list at a time). + +C w: The flag array w determines the status of elements and +C variables, and the external degree of elements. +C +C for elements: +C if w (e) = 0, then the element e is absorbed +C if w (e) .ge. wflg, then w (e) - wflg is the size of +C the set |Le \ Lme|, in terms of nonzeros (the +C sum of abs (nv (i)) for each principal variable i that +C is both in the pattern of element e and NOT in the +C pattern of the current pivot element, me). +C if wflg .gt. w (e) .gt. 0, then e is not absorbed and has +C not yet been seen in the scan of the element lists in +C the computation of |Le\Lme| in loop 150 below. +C +C for variables: +C during supervariable detection, if w (j) .ne. wflg then j is +C not in the pattern of variable i +C +C The w array is initialized by setting w (i) = 1 for all i, +C and by setting wflg = 2. It is reinitialized if wflg becomes +C too large (to ensure that wflg+n does not cause integer +C overflow). + +C----------------------------------------------------------------------- +C LOCAL INTEGERS: +C----------------------------------------------------------------------- + + INTEGER DEG, DEGME, DEXT, DMAX, E, ELENME, ELN, HASH, HMOD, I, + $ ILAST, INEXT, J, JLAST, JNEXT, K, KNT1, KNT2, KNT3, + $ LENJ, LN, MAXMEM, ME, MEM, MINDEG, NEL, NEWMEM, + $ NLEFT, NVI, NVJ, NVPIV, SLENME, WE, WFLG, WNVI, X + +C deg: the degree of a variable or element +C degme: size, |Lme|, of the current element, me (= degree (me)) +C dext: external degree, |Le \ Lme|, of some element e +C dmax: largest |Le| seen so far +C e: an element +C elenme: the length, elen (me), of element list of pivotal var. +C eln: the length, elen (...), of an element list +C hash: the computed value of the hash function +C hmod: the hash function is computed modulo hmod = max (1,n-1) +C i: a supervariable +C ilast: the entry in a link list preceding i +C inext: the entry in a link list following i +C j: a supervariable +C jlast: the entry in a link list preceding j +C jnext: the entry in a link list, or path, following j +C k: the pivot order of an element or variable +C knt1: loop counter used during element construction +C knt2: loop counter used during element construction +C knt3: loop counter used during compression +C lenj: len (j) +C ln: length of a supervariable list +C maxmem: amount of memory needed for no compressions +C me: current supervariable being eliminated, and the +C current element created by eliminating that +C supervariable +C mem: memory in use assuming no compressions have occurred +C mindeg: current minimum degree +C nel: number of pivots selected so far +C newmem: amount of new memory needed for current pivot element +C nleft: n - nel, the number of nonpivotal rows/columns remaining +C nvi: the number of variables in a supervariable i (= nv (i)) +C nvj: the number of variables in a supervariable j (= nv (j)) +C nvpiv: number of pivots in current element +C slenme: number of variables in variable list of pivotal variable +C we: w (e) +C wflg: used for flagging the w array. See description of iw. +C wnvi: wflg - nv (i) +C x: either a supervariable or an element + +C----------------------------------------------------------------------- +C LOCAL POINTERS: +C----------------------------------------------------------------------- + + INTEGER P, P1, P2, P3, PDST, PEND, PJ, PME, PME1, PME2, PN, PSRC + +C Any parameter (pe (...) or pfree) or local variable +C starting with "p" (for Pointer) is an index into iw, +C and all indices into iw use variables starting with +C "p." The only exception to this rule is the iwlen +C input argument. + +C p: pointer into lots of things +C p1: pe (i) for some variable i (start of element list) +C p2: pe (i) + elen (i) - 1 for some var. i (end of el. list) +C p3: index of first supervariable in clean list +C pdst: destination pointer, for compression +C pend: end of memory to compress +C pj: pointer into an element or variable +C pme: pointer into the current element (pme1...pme2) +C pme1: the current element, me, is stored in iw (pme1...pme2) +C pme2: the end of the current element +C pn: pointer into a "clean" variable, also used to compress +C psrc: source pointer, for compression + +C----------------------------------------------------------------------- +C FUNCTIONS CALLED: +C----------------------------------------------------------------------- + + INTRINSIC MAX, MIN, MOD + +C======================================================================= +C INITIALIZATIONS +C======================================================================= + + WFLG = 2 + MINDEG = 1 + NCMPA = 0 + NEL = 0 + HMOD = MAX (1, N-1) + DMAX = 0 + MEM = PFREE - 1 + MAXMEM = MEM + ME = 0 + + DO 10 I = 1, N + LAST (I) = 0 + HEAD (I) = 0 + NV (I) = 1 + W (I) = 1 + ELEN (I) = 0 + DEGREE (I) = LEN (I) +10 CONTINUE + +C ---------------------------------------------------------------- +C initialize degree lists and eliminate rows with no off-diag. nz. +C ---------------------------------------------------------------- + + DO 20 I = 1, N + + DEG = DEGREE (I) + + IF (DEG .GT. 0) THEN + +C ---------------------------------------------------------- +C place i in the degree list corresponding to its degree +C ---------------------------------------------------------- + + INEXT = HEAD (DEG) + IF (INEXT .NE. 0) LAST (INEXT) = I + NEXT (I) = INEXT + HEAD (DEG) = I + + ELSE + +C ---------------------------------------------------------- +C we have a variable that can be eliminated at once because +C there is no off-diagonal non-zero in its row. +C ---------------------------------------------------------- + + NEL = NEL + 1 + ELEN (I) = -NEL + PE (I) = 0 + W (I) = 0 + + ENDIF + +20 CONTINUE + +C======================================================================= +C WHILE (selecting pivots) DO +C======================================================================= + +30 CONTINUE + IF (NEL .LT. N) THEN + +C======================================================================= +C GET PIVOT OF MINIMUM DEGREE +C======================================================================= + +C ------------------------------------------------------------- +C find next supervariable for elimination +C ------------------------------------------------------------- + + DO 40 DEG = MINDEG, N + ME = HEAD (DEG) + IF (ME .GT. 0) GOTO 50 +40 CONTINUE +50 CONTINUE + MINDEG = DEG + +C ------------------------------------------------------------- +C remove chosen variable from link list +C ------------------------------------------------------------- + + INEXT = NEXT (ME) + IF (INEXT .NE. 0) LAST (INEXT) = 0 + HEAD (DEG) = INEXT + +C ------------------------------------------------------------- +C me represents the elimination of pivots nel+1 to nel+nv(me). +C place me itself as the first in this set. It will be moved +C to the nel+nv(me) position when the permutation vectors are +C computed. +C ------------------------------------------------------------- + + ELENME = ELEN (ME) + ELEN (ME) = - (NEL + 1) + NVPIV = NV (ME) + NEL = NEL + NVPIV + +C======================================================================= +C CONSTRUCT NEW ELEMENT +C======================================================================= + +C ------------------------------------------------------------- +C At this point, me is the pivotal supervariable. It will be +C converted into the current element. Scan list of the +C pivotal supervariable, me, setting tree pointers and +C constructing new list of supervariables for the new element, +C me. p is a pointer to the current position in the old list. +C ------------------------------------------------------------- + +C flag the variable "me" as being in Lme by negating nv (me) + NV (ME) = -NVPIV + DEGME = 0 + + IF (ELENME .EQ. 0) THEN + +C ---------------------------------------------------------- +C construct the new element in place +C ---------------------------------------------------------- + + PME1 = PE (ME) + PME2 = PME1 - 1 + + DO 60 P = PME1, PME1 + LEN (ME) - 1 + I = IW (P) + NVI = NV (I) + IF (NVI .GT. 0) THEN + +C ---------------------------------------------------- +C i is a principal variable not yet placed in Lme. +C store i in new list +C ---------------------------------------------------- + + DEGME = DEGME + NVI +C flag i as being in Lme by negating nv (i) + NV (I) = -NVI + PME2 = PME2 + 1 + IW (PME2) = I + +C ---------------------------------------------------- +C remove variable i from degree list. +C ---------------------------------------------------- + + ILAST = LAST (I) + INEXT = NEXT (I) + IF (INEXT .NE. 0) LAST (INEXT) = ILAST + IF (ILAST .NE. 0) THEN + NEXT (ILAST) = INEXT + ELSE +C i is at the head of the degree list + HEAD (DEGREE (I)) = INEXT + ENDIF + + ENDIF +60 CONTINUE +C this element takes no new memory in iw: + NEWMEM = 0 + + ELSE + +C ---------------------------------------------------------- +C construct the new element in empty space, iw (pfree ...) +C ---------------------------------------------------------- + + P = PE (ME) + PME1 = PFREE + SLENME = LEN (ME) - ELENME + + DO 120 KNT1 = 1, ELENME + 1 + + IF (KNT1 .GT. ELENME) THEN +C search the supervariables in me. + E = ME + PJ = P + LN = SLENME + ELSE +C search the elements in me. + E = IW (P) + P = P + 1 + PJ = PE (E) + LN = LEN (E) + ENDIF + +C ------------------------------------------------------- +C search for different supervariables and add them to the +C new list, compressing when necessary. this loop is +C executed once for each element in the list and once for +C all the supervariables in the list. +C ------------------------------------------------------- + + DO 110 KNT2 = 1, LN + I = IW (PJ) + PJ = PJ + 1 + NVI = NV (I) + IF (NVI .GT. 0) THEN + +C ------------------------------------------------- +C compress iw, if necessary +C ------------------------------------------------- + + IF (PFREE .GT. IWLEN) THEN +C prepare for compressing iw by adjusting +C pointers and lengths so that the lists being +C searched in the inner and outer loops contain +C only the remaining entries. + + PE (ME) = P + LEN (ME) = LEN (ME) - KNT1 + IF (LEN (ME) .EQ. 0) THEN +C nothing left of supervariable me + PE (ME) = 0 + ENDIF + PE (E) = PJ + LEN (E) = LN - KNT2 + IF (LEN (E) .EQ. 0) THEN +C nothing left of element e + PE (E) = 0 + ENDIF + + NCMPA = NCMPA + 1 +C store first item in pe +C set first entry to -item + DO 70 J = 1, N + PN = PE (J) + IF (PN .GT. 0) THEN + PE (J) = IW (PN) + IW (PN) = -J + ENDIF +70 CONTINUE + +C psrc/pdst point to source/destination + PDST = 1 + PSRC = 1 + PEND = PME1 - 1 + +C while loop: +80 CONTINUE + IF (PSRC .LE. PEND) THEN +C search for next negative entry + J = -IW (PSRC) + PSRC = PSRC + 1 + IF (J .GT. 0) THEN + IW (PDST) = PE (J) + PE (J) = PDST + PDST = PDST + 1 +C copy from source to destination + LENJ = LEN (J) + DO 90 KNT3 = 0, LENJ - 2 + IW (PDST + KNT3) = IW (PSRC + KNT3) +90 CONTINUE + PDST = PDST + LENJ - 1 + PSRC = PSRC + LENJ - 1 + ENDIF + GOTO 80 + ENDIF + +C move the new partially-constructed element + P1 = PDST + DO 100 PSRC = PME1, PFREE - 1 + IW (PDST) = IW (PSRC) + PDST = PDST + 1 +100 CONTINUE + PME1 = P1 + PFREE = PDST + PJ = PE (E) + P = PE (ME) + ENDIF + +C ------------------------------------------------- +C i is a principal variable not yet placed in Lme +C store i in new list +C ------------------------------------------------- + + DEGME = DEGME + NVI +C flag i as being in Lme by negating nv (i) + NV (I) = -NVI + IW (PFREE) = I + PFREE = PFREE + 1 + +C ------------------------------------------------- +C remove variable i from degree link list +C ------------------------------------------------- + + ILAST = LAST (I) + INEXT = NEXT (I) + IF (INEXT .NE. 0) LAST (INEXT) = ILAST + IF (ILAST .NE. 0) THEN + NEXT (ILAST) = INEXT + ELSE +C i is at the head of the degree list + HEAD (DEGREE (I)) = INEXT + ENDIF + + ENDIF +110 CONTINUE + + IF (E .NE. ME) THEN +C set tree pointer and flag to indicate element e is +C absorbed into new element me (the parent of e is me) + PE (E) = -ME + W (E) = 0 + ENDIF +120 CONTINUE + + PME2 = PFREE - 1 +C this element takes newmem new memory in iw (possibly zero) + NEWMEM = PFREE - PME1 + MEM = MEM + NEWMEM + MAXMEM = MAX (MAXMEM, MEM) + ENDIF + +C ------------------------------------------------------------- +C me has now been converted into an element in iw (pme1..pme2) +C ------------------------------------------------------------- + +C degme holds the external degree of new element + DEGREE (ME) = DEGME + PE (ME) = PME1 + LEN (ME) = PME2 - PME1 + 1 + +C ------------------------------------------------------------- +C make sure that wflg is not too large. With the current +C value of wflg, wflg+n must not cause integer overflow +C ------------------------------------------------------------- + + IF (WFLG + N .LE. WFLG) THEN + DO 130 X = 1, N + IF (W (X) .NE. 0) W (X) = 1 +130 CONTINUE + WFLG = 2 + ENDIF + +C======================================================================= +C COMPUTE (w (e) - wflg) = |Le\Lme| FOR ALL ELEMENTS +C======================================================================= + +C ------------------------------------------------------------- +C Scan 1: compute the external degrees of previous elements +C with respect to the current element. That is: +C (w (e) - wflg) = |Le \ Lme| +C for each element e that appears in any supervariable in Lme. +C The notation Le refers to the pattern (list of +C supervariables) of a previous element e, where e is not yet +C absorbed, stored in iw (pe (e) + 1 ... pe (e) + iw (pe (e))). +C The notation Lme refers to the pattern of the current element +C (stored in iw (pme1..pme2)). If (w (e) - wflg) becomes +C zero, then the element e will be absorbed in scan 2. +C ------------------------------------------------------------- + + DO 150 PME = PME1, PME2 + I = IW (PME) + ELN = ELEN (I) + IF (ELN .GT. 0) THEN +C note that nv (i) has been negated to denote i in Lme: + NVI = -NV (I) + WNVI = WFLG - NVI + DO 140 P = PE (I), PE (I) + ELN - 1 + E = IW (P) + WE = W (E) + IF (WE .GE. WFLG) THEN +C unabsorbed element e has been seen in this loop + WE = WE - NVI + ELSE IF (WE .NE. 0) THEN +C e is an unabsorbed element +C this is the first we have seen e in all of Scan 1 + WE = DEGREE (E) + WNVI + ENDIF + W (E) = WE +140 CONTINUE + ENDIF +150 CONTINUE + +C======================================================================= +C DEGREE UPDATE AND ELEMENT ABSORPTION +C======================================================================= + +C ------------------------------------------------------------- +C Scan 2: for each i in Lme, sum up the degree of Lme (which +C is degme), plus the sum of the external degrees of each Le +C for the elements e appearing within i, plus the +C supervariables in i. Place i in hash list. +C ------------------------------------------------------------- + + DO 180 PME = PME1, PME2 + I = IW (PME) + P1 = PE (I) + P2 = P1 + ELEN (I) - 1 + PN = P1 + HASH = 0 + DEG = 0 + +C ---------------------------------------------------------- +C scan the element list associated with supervariable i +C ---------------------------------------------------------- + + DO 160 P = P1, P2 + E = IW (P) +C dext = | Le \ Lme | + DEXT = W (E) - WFLG + IF (DEXT .GT. 0) THEN + DEG = DEG + DEXT + IW (PN) = E + PN = PN + 1 + HASH = HASH + E + ELSE IF (DEXT .EQ. 0) THEN +C aggressive absorption: e is not adjacent to me, but +C the |Le \ Lme| is 0, so absorb it into me + PE (E) = -ME + W (E) = 0 + ELSE +C element e has already been absorbed, due to +C regular absorption, in do loop 120 above. Ignore it. + CONTINUE + ENDIF +160 CONTINUE + +C count the number of elements in i (including me): + ELEN (I) = PN - P1 + 1 + +C ---------------------------------------------------------- +C scan the supervariables in the list associated with i +C ---------------------------------------------------------- + + P3 = PN + DO 170 P = P2 + 1, P1 + LEN (I) - 1 + J = IW (P) + NVJ = NV (J) + IF (NVJ .GT. 0) THEN +C j is unabsorbed, and not in Lme. +C add to degree and add to new list + DEG = DEG + NVJ + IW (PN) = J + PN = PN + 1 + HASH = HASH + J + ENDIF +170 CONTINUE + +C ---------------------------------------------------------- +C update the degree and check for mass elimination +C ---------------------------------------------------------- + + IF (DEG .EQ. 0) THEN + +C ------------------------------------------------------- +C mass elimination +C ------------------------------------------------------- + +C There is nothing left of this node except for an +C edge to the current pivot element. elen (i) is 1, +C and there are no variables adjacent to node i. +C Absorb i into the current pivot element, me. + + PE (I) = -ME + NVI = -NV (I) + DEGME = DEGME - NVI + NVPIV = NVPIV + NVI + NEL = NEL + NVI + NV (I) = 0 + ELEN (I) = 0 + + ELSE + +C ------------------------------------------------------- +C update the upper-bound degree of i +C ------------------------------------------------------- + +C the following degree does not yet include the size +C of the current element, which is added later: + DEGREE (I) = MIN (DEGREE (I), DEG) + +C ------------------------------------------------------- +C add me to the list for i +C ------------------------------------------------------- + +C move first supervariable to end of list + IW (PN) = IW (P3) +C move first element to end of element part of list + IW (P3) = IW (P1) +C add new element to front of list. + IW (P1) = ME +C store the new length of the list in len (i) + LEN (I) = PN - P1 + 1 + +C ------------------------------------------------------- +C place in hash bucket. Save hash key of i in last (i). +C ------------------------------------------------------- + + HASH = MOD (HASH, HMOD) + 1 + J = HEAD (HASH) + IF (J .LE. 0) THEN +C the degree list is empty, hash head is -j + NEXT (I) = -J + HEAD (HASH) = -I + ELSE +C degree list is not empty +C use last (head (hash)) as hash head + NEXT (I) = LAST (J) + LAST (J) = I + ENDIF + LAST (I) = HASH + ENDIF +180 CONTINUE + + DEGREE (ME) = DEGME + +C ------------------------------------------------------------- +C Clear the counter array, w (...), by incrementing wflg. +C ------------------------------------------------------------- + + DMAX = MAX (DMAX, DEGME) + WFLG = WFLG + DMAX + +C make sure that wflg+n does not cause integer overflow + IF (WFLG + N .LE. WFLG) THEN + DO 190 X = 1, N + IF (W (X) .NE. 0) W (X) = 1 +190 CONTINUE + WFLG = 2 + ENDIF +C at this point, w (1..n) .lt. wflg holds + +C======================================================================= +C SUPERVARIABLE DETECTION +C======================================================================= + + DO 250 PME = PME1, PME2 + I = IW (PME) + IF (NV (I) .LT. 0) THEN +C i is a principal variable in Lme + +C ------------------------------------------------------- +C examine all hash buckets with 2 or more variables. We +C do this by examing all unique hash keys for super- +C variables in the pattern Lme of the current element, me +C ------------------------------------------------------- + + HASH = LAST (I) +C let i = head of hash bucket, and empty the hash bucket + J = HEAD (HASH) + IF (J .EQ. 0) GOTO 250 + IF (J .LT. 0) THEN +C degree list is empty + I = -J + HEAD (HASH) = 0 + ELSE +C degree list is not empty, restore last () of head + I = LAST (J) + LAST (J) = 0 + ENDIF + IF (I .EQ. 0) GOTO 250 + +C while loop: +200 CONTINUE + IF (NEXT (I) .NE. 0) THEN + +C ---------------------------------------------------- +C this bucket has one or more variables following i. +C scan all of them to see if i can absorb any entries +C that follow i in hash bucket. Scatter i into w. +C ---------------------------------------------------- + + LN = LEN (I) + ELN = ELEN (I) +C do not flag the first element in the list (me) + DO 210 P = PE (I) + 1, PE (I) + LN - 1 + W (IW (P)) = WFLG +210 CONTINUE + +C ---------------------------------------------------- +C scan every other entry j following i in bucket +C ---------------------------------------------------- + + JLAST = I + J = NEXT (I) + +C while loop: +220 CONTINUE + IF (J .NE. 0) THEN + +C ------------------------------------------------- +C check if j and i have identical nonzero pattern +C ------------------------------------------------- + + IF (LEN (J) .NE. LN) THEN +C i and j do not have same size data structure + GOTO 240 + ENDIF + IF (ELEN (J) .NE. ELN) THEN +C i and j do not have same number of adjacent el + GOTO 240 + ENDIF +C do not flag the first element in the list (me) + DO 230 P = PE (J) + 1, PE (J) + LN - 1 + IF (W (IW (P)) .NE. WFLG) THEN +C an entry (iw(p)) is in j but not in i + GOTO 240 + ENDIF +230 CONTINUE + +C ------------------------------------------------- +C found it! j can be absorbed into i +C ------------------------------------------------- + + PE (J) = -I +C both nv (i) and nv (j) are negated since they +C are in Lme, and the absolute values of each +C are the number of variables in i and j: + NV (I) = NV (I) + NV (J) + NV (J) = 0 + ELEN (J) = 0 +C delete j from hash bucket + J = NEXT (J) + NEXT (JLAST) = J + GOTO 220 + +C ------------------------------------------------- +240 CONTINUE +C j cannot be absorbed into i +C ------------------------------------------------- + + JLAST = J + J = NEXT (J) + GOTO 220 + ENDIF + +C ---------------------------------------------------- +C no more variables can be absorbed into i +C go to next i in bucket and clear flag array +C ---------------------------------------------------- + + WFLG = WFLG + 1 + I = NEXT (I) + IF (I .NE. 0) GOTO 200 + ENDIF + ENDIF +250 CONTINUE + +C======================================================================= +C RESTORE DEGREE LISTS AND REMOVE NONPRINCIPAL SUPERVAR. FROM ELEMENT +C======================================================================= + + P = PME1 + NLEFT = N - NEL + DO 260 PME = PME1, PME2 + I = IW (PME) + NVI = -NV (I) + IF (NVI .GT. 0) THEN +C i is a principal variable in Lme +C restore nv (i) to signify that i is principal + NV (I) = NVI + +C ------------------------------------------------------- +C compute the external degree (add size of current elem) +C ------------------------------------------------------- + + DEG = MIN (DEGREE (I) + DEGME - NVI, NLEFT - NVI) + +C ------------------------------------------------------- +C place the supervariable at the head of the degree list +C ------------------------------------------------------- + + INEXT = HEAD (DEG) + IF (INEXT .NE. 0) LAST (INEXT) = I + NEXT (I) = INEXT + LAST (I) = 0 + HEAD (DEG) = I + +C ------------------------------------------------------- +C save the new degree, and find the minimum degree +C ------------------------------------------------------- + + MINDEG = MIN (MINDEG, DEG) + DEGREE (I) = DEG + +C ------------------------------------------------------- +C place the supervariable in the element pattern +C ------------------------------------------------------- + + IW (P) = I + P = P + 1 + ENDIF +260 CONTINUE + +C======================================================================= +C FINALIZE THE NEW ELEMENT +C======================================================================= + + NV (ME) = NVPIV + DEGME +C nv (me) is now the degree of pivot (including diagonal part) +C save the length of the list for the new element me + LEN (ME) = P - PME1 + IF (LEN (ME) .EQ. 0) THEN +C there is nothing left of the current pivot element + PE (ME) = 0 + W (ME) = 0 + ENDIF + IF (NEWMEM .NE. 0) THEN +C element was not constructed in place: deallocate part +C of it (final size is less than or equal to newmem, +C since newly nonprincipal variables have been removed). + PFREE = P + MEM = MEM - NEWMEM + LEN (ME) + ENDIF + +C======================================================================= +C END WHILE (selecting pivots) + GOTO 30 + ENDIF +C======================================================================= + +C======================================================================= +C COMPUTE THE PERMUTATION VECTORS +C======================================================================= + +C ---------------------------------------------------------------- +C The time taken by the following code is O(n). At this +C point, elen (e) = -k has been done for all elements e, +C and elen (i) = 0 has been done for all nonprincipal +C variables i. At this point, there are no principal +C supervariables left, and all elements are absorbed. +C ---------------------------------------------------------------- + +C ---------------------------------------------------------------- +C compute the ordering of unordered nonprincipal variables +C ---------------------------------------------------------------- + + DO 290 I = 1, N + IF (ELEN (I) .EQ. 0) THEN + +C ---------------------------------------------------------- +C i is an un-ordered row. Traverse the tree from i until +C reaching an element, e. The element, e, was the +C principal supervariable of i and all nodes in the path +C from i to when e was selected as pivot. +C ---------------------------------------------------------- + + J = -PE (I) +C while (j is a variable) do: +270 CONTINUE + IF (ELEN (J) .GE. 0) THEN + J = -PE (J) + GOTO 270 + ENDIF + E = J + +C ---------------------------------------------------------- +C get the current pivot ordering of e +C ---------------------------------------------------------- + + K = -ELEN (E) + +C ---------------------------------------------------------- +C traverse the path again from i to e, and compress the +C path (all nodes point to e). Path compression allows +C this code to compute in O(n) time. Order the unordered +C nodes in the path, and place the element e at the end. +C ---------------------------------------------------------- + + J = I +C while (j is a variable) do: +280 CONTINUE + IF (ELEN (J) .GE. 0) THEN + JNEXT = -PE (J) + PE (J) = -E + IF (ELEN (J) .EQ. 0) THEN +C j is an unordered row + ELEN (J) = K + K = K + 1 + ENDIF + J = JNEXT + GOTO 280 + ENDIF +C leave elen (e) negative, so we know it is an element + ELEN (E) = -K + ENDIF +290 CONTINUE + +C ---------------------------------------------------------------- +C reset the inverse permutation (elen (1..n)) to be positive, +C and compute the permutation (last (1..n)). +C ---------------------------------------------------------------- + + DO 300 I = 1, N + K = ABS (ELEN (I)) + LAST (K) = I + ELEN (I) = K +300 CONTINUE + +C======================================================================= +C RETURN THE MEMORY USAGE IN IW +C======================================================================= + +C If maxmem is less than or equal to iwlen, then no compressions +C occurred, and iw (maxmem+1 ... iwlen) was unused. Otherwise +C compressions did occur, and iwlen would have had to have been +C greater than or equal to maxmem for no compressions to occur. +C Return the value of maxmem in the pfree argument. + + PFREE = MAXMEM + + RETURN + END + diff --git a/src/AMD/Source/amd_1.c b/src/AMD/Source/amd_1.c new file mode 100644 index 0000000..2be486e --- /dev/null +++ b/src/AMD/Source/amd_1.c @@ -0,0 +1,180 @@ +/* ========================================================================= */ +/* === AMD_1 =============================================================== */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* AMD_1: Construct A+A' for a sparse matrix A and perform the AMD ordering. + * + * The n-by-n sparse matrix A can be unsymmetric. It is stored in MATLAB-style + * compressed-column form, with sorted row indices in each column, and no + * duplicate entries. Diagonal entries may be present, but they are ignored. + * Row indices of column j of A are stored in Ai [Ap [j] ... Ap [j+1]-1]. + * Ap [0] must be zero, and nz = Ap [n] is the number of entries in A. The + * size of the matrix, n, must be greater than or equal to zero. + * + * This routine must be preceded by a call to AMD_aat, which computes the + * number of entries in each row/column in A+A', excluding the diagonal. + * Len [j], on input, is the number of entries in row/column j of A+A'. This + * routine constructs the matrix A+A' and then calls AMD_2. No error checking + * is performed (this was done in AMD_valid). + */ + +#include "amd_internal.h" + +GLOBAL void AMD_1 +( + Int n, /* n > 0 */ + const Int Ap [ ], /* input of size n+1, not modified */ + const Int Ai [ ], /* input of size nz = Ap [n], not modified */ + Int P [ ], /* size n output permutation */ + Int Pinv [ ], /* size n output inverse permutation */ + Int Len [ ], /* size n input, undefined on output */ + Int slen, /* slen >= sum (Len [0..n-1]) + 7n, + * ideally slen = 1.2 * sum (Len) + 8n */ + Int S [ ], /* size slen workspace */ + double Control [ ], /* input array of size AMD_CONTROL */ + double Info [ ] /* output array of size AMD_INFO */ +) +{ + Int i, j, k, p, pfree, iwlen, pj, p1, p2, pj2, *Iw, *Pe, *Nv, *Head, + *Elen, *Degree, *s, *W, *Sp, *Tp ; + + /* --------------------------------------------------------------------- */ + /* construct the matrix for AMD_2 */ + /* --------------------------------------------------------------------- */ + + ASSERT (n > 0) ; + + iwlen = slen - 6*n ; + s = S ; + Pe = s ; s += n ; + Nv = s ; s += n ; + Head = s ; s += n ; + Elen = s ; s += n ; + Degree = s ; s += n ; + W = s ; s += n ; + Iw = s ; s += iwlen ; + + ASSERT (AMD_valid (n, n, Ap, Ai) == AMD_OK) ; + + /* construct the pointers for A+A' */ + Sp = Nv ; /* use Nv and W as workspace for Sp and Tp [ */ + Tp = W ; + pfree = 0 ; + for (j = 0 ; j < n ; j++) + { + Pe [j] = pfree ; + Sp [j] = pfree ; + pfree += Len [j] ; + } + + /* Note that this restriction on iwlen is slightly more restrictive than + * what is strictly required in AMD_2. AMD_2 can operate with no elbow + * room at all, but it will be very slow. For better performance, at + * least size-n elbow room is enforced. */ + ASSERT (iwlen >= pfree + n) ; + +#ifndef NDEBUG + for (p = 0 ; p < iwlen ; p++) Iw [p] = EMPTY ; +#endif + + for (k = 0 ; k < n ; k++) + { + AMD_DEBUG1 (("Construct row/column k= "ID" of A+A'\n", k)) ; + p1 = Ap [k] ; + p2 = Ap [k+1] ; + + /* construct A+A' */ + for (p = p1 ; p < p2 ; ) + { + /* scan the upper triangular part of A */ + j = Ai [p] ; + ASSERT (j >= 0 && j < n) ; + if (j < k) + { + /* entry A (j,k) in the strictly upper triangular part */ + ASSERT (Sp [j] < (j == n-1 ? pfree : Pe [j+1])) ; + ASSERT (Sp [k] < (k == n-1 ? pfree : Pe [k+1])) ; + Iw [Sp [j]++] = k ; + Iw [Sp [k]++] = j ; + p++ ; + } + else if (j == k) + { + /* skip the diagonal */ + p++ ; + break ; + } + else /* j > k */ + { + /* first entry below the diagonal */ + break ; + } + /* scan lower triangular part of A, in column j until reaching + * row k. Start where last scan left off. */ + ASSERT (Ap [j] <= Tp [j] && Tp [j] <= Ap [j+1]) ; + pj2 = Ap [j+1] ; + for (pj = Tp [j] ; pj < pj2 ; ) + { + i = Ai [pj] ; + ASSERT (i >= 0 && i < n) ; + if (i < k) + { + /* A (i,j) is only in the lower part, not in upper */ + ASSERT (Sp [i] < (i == n-1 ? pfree : Pe [i+1])) ; + ASSERT (Sp [j] < (j == n-1 ? pfree : Pe [j+1])) ; + Iw [Sp [i]++] = j ; + Iw [Sp [j]++] = i ; + pj++ ; + } + else if (i == k) + { + /* entry A (k,j) in lower part and A (j,k) in upper */ + pj++ ; + break ; + } + else /* i > k */ + { + /* consider this entry later, when k advances to i */ + break ; + } + } + Tp [j] = pj ; + } + Tp [k] = p ; + } + + /* clean up, for remaining mismatched entries */ + for (j = 0 ; j < n ; j++) + { + for (pj = Tp [j] ; pj < Ap [j+1] ; pj++) + { + i = Ai [pj] ; + ASSERT (i >= 0 && i < n) ; + /* A (i,j) is only in the lower part, not in upper */ + ASSERT (Sp [i] < (i == n-1 ? pfree : Pe [i+1])) ; + ASSERT (Sp [j] < (j == n-1 ? pfree : Pe [j+1])) ; + Iw [Sp [i]++] = j ; + Iw [Sp [j]++] = i ; + } + } + +#ifndef NDEBUG + for (j = 0 ; j < n-1 ; j++) ASSERT (Sp [j] == Pe [j+1]) ; + ASSERT (Sp [n-1] == pfree) ; +#endif + + /* Tp and Sp no longer needed ] */ + + /* --------------------------------------------------------------------- */ + /* order the matrix */ + /* --------------------------------------------------------------------- */ + + AMD_2 (n, Pe, Iw, Len, iwlen, pfree, + Nv, Pinv, P, Head, Elen, Degree, W, Control, Info) ; +} diff --git a/src/AMD/Source/amd_2.c b/src/AMD/Source/amd_2.c new file mode 100644 index 0000000..f144722 --- /dev/null +++ b/src/AMD/Source/amd_2.c @@ -0,0 +1,1842 @@ +/* ========================================================================= */ +/* === AMD_2 =============================================================== */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* AMD_2: performs the AMD ordering on a symmetric sparse matrix A, followed + * by a postordering (via depth-first search) of the assembly tree using the + * AMD_postorder routine. + */ + +#include "amd_internal.h" + +/* ========================================================================= */ +/* === clear_flag ========================================================== */ +/* ========================================================================= */ + +static Int clear_flag (Int wflg, Int wbig, Int W [ ], Int n) +{ + Int x ; + if (wflg < 2 || wflg >= wbig) + { + for (x = 0 ; x < n ; x++) + { + if (W [x] != 0) W [x] = 1 ; + } + wflg = 2 ; + } + /* at this point, W [0..n-1] < wflg holds */ + return (wflg) ; +} + + +/* ========================================================================= */ +/* === AMD_2 =============================================================== */ +/* ========================================================================= */ + +GLOBAL void AMD_2 +( + Int n, /* A is n-by-n, where n > 0 */ + Int Pe [ ], /* Pe [0..n-1]: index in Iw of row i on input */ + Int Iw [ ], /* workspace of size iwlen. Iw [0..pfree-1] + * holds the matrix on input */ + Int Len [ ], /* Len [0..n-1]: length for row/column i on input */ + Int iwlen, /* length of Iw. iwlen >= pfree + n */ + Int pfree, /* Iw [pfree ... iwlen-1] is empty on input */ + + /* 7 size-n workspaces, not defined on input: */ + Int Nv [ ], /* the size of each supernode on output */ + Int Next [ ], /* the output inverse permutation */ + Int Last [ ], /* the output permutation */ + Int Head [ ], + Int Elen [ ], /* the size columns of L for each supernode */ + Int Degree [ ], + Int W [ ], + + /* control parameters and output statistics */ + double Control [ ], /* array of size AMD_CONTROL */ + double Info [ ] /* array of size AMD_INFO */ +) +{ + +/* + * Given a representation of the nonzero pattern of a symmetric matrix, A, + * (excluding the diagonal) perform an approximate minimum (UMFPACK/MA38-style) + * degree ordering to compute a pivot order such that the introduction of + * nonzeros (fill-in) in the Cholesky factors A = LL' is kept low. At each + * step, the pivot selected is the one with the minimum UMFAPACK/MA38-style + * upper-bound on the external degree. This routine can optionally perform + * aggresive absorption (as done by MC47B in the Harwell Subroutine + * Library). + * + * The approximate degree algorithm implemented here is the symmetric analog of + * the degree update algorithm in MA38 and UMFPACK (the Unsymmetric-pattern + * MultiFrontal PACKage, both by Davis and Duff). The routine is based on the + * MA27 minimum degree ordering algorithm by Iain Duff and John Reid. + * + * This routine is a translation of the original AMDBAR and MC47B routines, + * in Fortran, with the following modifications: + * + * (1) dense rows/columns are removed prior to ordering the matrix, and placed + * last in the output order. The presence of a dense row/column can + * increase the ordering time by up to O(n^2), unless they are removed + * prior to ordering. + * + * (2) the minimum degree ordering is followed by a postordering (depth-first + * search) of the assembly tree. Note that mass elimination (discussed + * below) combined with the approximate degree update can lead to the mass + * elimination of nodes with lower exact degree than the current pivot + * element. No additional fill-in is caused in the representation of the + * Schur complement. The mass-eliminated nodes merge with the current + * pivot element. They are ordered prior to the current pivot element. + * Because they can have lower exact degree than the current element, the + * merger of two or more of these nodes in the current pivot element can + * lead to a single element that is not a "fundamental supernode". The + * diagonal block can have zeros in it. Thus, the assembly tree used here + * is not guaranteed to be the precise supernodal elemination tree (with + * "funadmental" supernodes), and the postordering performed by this + * routine is not guaranteed to be a precise postordering of the + * elimination tree. + * + * (3) input parameters are added, to control aggressive absorption and the + * detection of "dense" rows/columns of A. + * + * (4) additional statistical information is returned, such as the number of + * nonzeros in L, and the flop counts for subsequent LDL' and LU + * factorizations. These are slight upper bounds, because of the mass + * elimination issue discussed above. + * + * (5) additional routines are added to interface this routine to MATLAB + * to provide a simple C-callable user-interface, to check inputs for + * errors, compute the symmetry of the pattern of A and the number of + * nonzeros in each row/column of A+A', to compute the pattern of A+A', + * to perform the assembly tree postordering, and to provide debugging + * ouput. Many of these functions are also provided by the Fortran + * Harwell Subroutine Library routine MC47A. + * + * (6) both int and SuiteSparse_long versions are provided. In the + * descriptions below and integer is and int or SuiteSparse_long depending + * on which version is being used. + + ********************************************************************** + ***** CAUTION: ARGUMENTS ARE NOT CHECKED FOR ERRORS ON INPUT. ****** + ********************************************************************** + ** If you want error checking, a more versatile input format, and a ** + ** simpler user interface, use amd_order or amd_l_order instead. ** + ** This routine is not meant to be user-callable. ** + ********************************************************************** + + * ---------------------------------------------------------------------------- + * References: + * ---------------------------------------------------------------------------- + * + * [1] Timothy A. Davis and Iain Duff, "An unsymmetric-pattern multifrontal + * method for sparse LU factorization", SIAM J. Matrix Analysis and + * Applications, vol. 18, no. 1, pp. 140-158. Discusses UMFPACK / MA38, + * which first introduced the approximate minimum degree used by this + * routine. + * + * [2] Patrick Amestoy, Timothy A. Davis, and Iain S. Duff, "An approximate + * minimum degree ordering algorithm," SIAM J. Matrix Analysis and + * Applications, vol. 17, no. 4, pp. 886-905, 1996. Discusses AMDBAR and + * MC47B, which are the Fortran versions of this routine. + * + * [3] Alan George and Joseph Liu, "The evolution of the minimum degree + * ordering algorithm," SIAM Review, vol. 31, no. 1, pp. 1-19, 1989. + * We list below the features mentioned in that paper that this code + * includes: + * + * mass elimination: + * Yes. MA27 relied on supervariable detection for mass elimination. + * + * indistinguishable nodes: + * Yes (we call these "supervariables"). This was also in the MA27 + * code - although we modified the method of detecting them (the + * previous hash was the true degree, which we no longer keep track + * of). A supervariable is a set of rows with identical nonzero + * pattern. All variables in a supervariable are eliminated together. + * Each supervariable has as its numerical name that of one of its + * variables (its principal variable). + * + * quotient graph representation: + * Yes. We use the term "element" for the cliques formed during + * elimination. This was also in the MA27 code. The algorithm can + * operate in place, but it will work more efficiently if given some + * "elbow room." + * + * element absorption: + * Yes. This was also in the MA27 code. + * + * external degree: + * Yes. The MA27 code was based on the true degree. + * + * incomplete degree update and multiple elimination: + * No. This was not in MA27, either. Our method of degree update + * within MC47B is element-based, not variable-based. It is thus + * not well-suited for use with incomplete degree update or multiple + * elimination. + * + * Authors, and Copyright (C) 2004 by: + * Timothy A. Davis, Patrick Amestoy, Iain S. Duff, John K. Reid. + * + * Acknowledgements: This work (and the UMFPACK package) was supported by the + * National Science Foundation (ASC-9111263, DMS-9223088, and CCR-0203270). + * The UMFPACK/MA38 approximate degree update algorithm, the unsymmetric analog + * which forms the basis of AMD, was developed while Tim Davis was supported by + * CERFACS (Toulouse, France) in a post-doctoral position. This C version, and + * the etree postorder, were written while Tim Davis was on sabbatical at + * Stanford University and Lawrence Berkeley National Laboratory. + + * ---------------------------------------------------------------------------- + * INPUT ARGUMENTS (unaltered): + * ---------------------------------------------------------------------------- + + * n: The matrix order. Restriction: n >= 1. + * + * iwlen: The size of the Iw array. On input, the matrix is stored in + * Iw [0..pfree-1]. However, Iw [0..iwlen-1] should be slightly larger + * than what is required to hold the matrix, at least iwlen >= pfree + n. + * Otherwise, excessive compressions will take place. The recommended + * value of iwlen is 1.2 * pfree + n, which is the value used in the + * user-callable interface to this routine (amd_order.c). The algorithm + * will not run at all if iwlen < pfree. Restriction: iwlen >= pfree + n. + * Note that this is slightly more restrictive than the actual minimum + * (iwlen >= pfree), but AMD_2 will be very slow with no elbow room. + * Thus, this routine enforces a bare minimum elbow room of size n. + * + * pfree: On input the tail end of the array, Iw [pfree..iwlen-1], is empty, + * and the matrix is stored in Iw [0..pfree-1]. During execution, + * additional data is placed in Iw, and pfree is modified so that + * Iw [pfree..iwlen-1] is always the unused part of Iw. + * + * Control: A double array of size AMD_CONTROL containing input parameters + * that affect how the ordering is computed. If NULL, then default + * settings are used. + * + * Control [AMD_DENSE] is used to determine whether or not a given input + * row is "dense". A row is "dense" if the number of entries in the row + * exceeds Control [AMD_DENSE] times sqrt (n), except that rows with 16 or + * fewer entries are never considered "dense". To turn off the detection + * of dense rows, set Control [AMD_DENSE] to a negative number, or to a + * number larger than sqrt (n). The default value of Control [AMD_DENSE] + * is AMD_DEFAULT_DENSE, which is defined in amd.h as 10. + * + * Control [AMD_AGGRESSIVE] is used to determine whether or not aggressive + * absorption is to be performed. If nonzero, then aggressive absorption + * is performed (this is the default). + + * ---------------------------------------------------------------------------- + * INPUT/OUPUT ARGUMENTS: + * ---------------------------------------------------------------------------- + * + * Pe: An integer array of size n. On input, Pe [i] is the index in Iw of + * the start of row i. Pe [i] is ignored if row i has no off-diagonal + * entries. Thus Pe [i] must be in the range 0 to pfree-1 for non-empty + * rows. + * + * During execution, it is used for both supervariables and elements: + * + * Principal supervariable i: index into Iw of the description of + * supervariable i. A supervariable represents one or more rows of + * the matrix with identical nonzero pattern. In this case, + * Pe [i] >= 0. + * + * Non-principal supervariable i: if i has been absorbed into another + * supervariable j, then Pe [i] = FLIP (j), where FLIP (j) is defined + * as (-(j)-2). Row j has the same pattern as row i. Note that j + * might later be absorbed into another supervariable j2, in which + * case Pe [i] is still FLIP (j), and Pe [j] = FLIP (j2) which is + * < EMPTY, where EMPTY is defined as (-1) in amd_internal.h. + * + * Unabsorbed element e: the index into Iw of the description of element + * e, if e has not yet been absorbed by a subsequent element. Element + * e is created when the supervariable of the same name is selected as + * the pivot. In this case, Pe [i] >= 0. + * + * Absorbed element e: if element e is absorbed into element e2, then + * Pe [e] = FLIP (e2). This occurs when the pattern of e (which we + * refer to as Le) is found to be a subset of the pattern of e2 (that + * is, Le2). In this case, Pe [i] < EMPTY. If element e is "null" + * (it has no nonzeros outside its pivot block), then Pe [e] = EMPTY, + * and e is the root of an assembly subtree (or the whole tree if + * there is just one such root). + * + * Dense variable i: if i is "dense", then Pe [i] = EMPTY. + * + * On output, Pe holds the assembly tree/forest, which implicitly + * represents a pivot order with identical fill-in as the actual order + * (via a depth-first search of the tree), as follows. If Nv [i] > 0, + * then i represents a node in the assembly tree, and the parent of i is + * Pe [i], or EMPTY if i is a root. If Nv [i] = 0, then (i, Pe [i]) + * represents an edge in a subtree, the root of which is a node in the + * assembly tree. Note that i refers to a row/column in the original + * matrix, not the permuted matrix. + * + * Info: A double array of size AMD_INFO. If present, (that is, not NULL), + * then statistics about the ordering are returned in the Info array. + * See amd.h for a description. + + * ---------------------------------------------------------------------------- + * INPUT/MODIFIED (undefined on output): + * ---------------------------------------------------------------------------- + * + * Len: An integer array of size n. On input, Len [i] holds the number of + * entries in row i of the matrix, excluding the diagonal. The contents + * of Len are undefined on output. + * + * Iw: An integer array of size iwlen. On input, Iw [0..pfree-1] holds the + * description of each row i in the matrix. The matrix must be symmetric, + * and both upper and lower triangular parts must be present. The + * diagonal must not be present. Row i is held as follows: + * + * Len [i]: the length of the row i data structure in the Iw array. + * Iw [Pe [i] ... Pe [i] + Len [i] - 1]: + * the list of column indices for nonzeros in row i (simple + * supervariables), excluding the diagonal. All supervariables + * start with one row/column each (supervariable i is just row i). + * If Len [i] is zero on input, then Pe [i] is ignored on input. + * + * Note that the rows need not be in any particular order, and there + * may be empty space between the rows. + * + * During execution, the supervariable i experiences fill-in. This is + * represented by placing in i a list of the elements that cause fill-in + * in supervariable i: + * + * Len [i]: the length of supervariable i in the Iw array. + * Iw [Pe [i] ... Pe [i] + Elen [i] - 1]: + * the list of elements that contain i. This list is kept short + * by removing absorbed elements. + * Iw [Pe [i] + Elen [i] ... Pe [i] + Len [i] - 1]: + * the list of supervariables in i. This list is kept short by + * removing nonprincipal variables, and any entry j that is also + * contained in at least one of the elements (j in Le) in the list + * for i (e in row i). + * + * When supervariable i is selected as pivot, we create an element e of + * the same name (e=i): + * + * Len [e]: the length of element e in the Iw array. + * Iw [Pe [e] ... Pe [e] + Len [e] - 1]: + * the list of supervariables in element e. + * + * An element represents the fill-in that occurs when supervariable i is + * selected as pivot (which represents the selection of row i and all + * non-principal variables whose principal variable is i). We use the + * term Le to denote the set of all supervariables in element e. Absorbed + * supervariables and elements are pruned from these lists when + * computationally convenient. + * + * CAUTION: THE INPUT MATRIX IS OVERWRITTEN DURING COMPUTATION. + * The contents of Iw are undefined on output. + + * ---------------------------------------------------------------------------- + * OUTPUT (need not be set on input): + * ---------------------------------------------------------------------------- + * + * Nv: An integer array of size n. During execution, ABS (Nv [i]) is equal to + * the number of rows that are represented by the principal supervariable + * i. If i is a nonprincipal or dense variable, then Nv [i] = 0. + * Initially, Nv [i] = 1 for all i. Nv [i] < 0 signifies that i is a + * principal variable in the pattern Lme of the current pivot element me. + * After element me is constructed, Nv [i] is set back to a positive + * value. + * + * On output, Nv [i] holds the number of pivots represented by super + * row/column i of the original matrix, or Nv [i] = 0 for non-principal + * rows/columns. Note that i refers to a row/column in the original + * matrix, not the permuted matrix. + * + * Elen: An integer array of size n. See the description of Iw above. At the + * start of execution, Elen [i] is set to zero for all rows i. During + * execution, Elen [i] is the number of elements in the list for + * supervariable i. When e becomes an element, Elen [e] = FLIP (esize) is + * set, where esize is the size of the element (the number of pivots, plus + * the number of nonpivotal entries). Thus Elen [e] < EMPTY. + * Elen (i) = EMPTY set when variable i becomes nonprincipal. + * + * For variables, Elen (i) >= EMPTY holds until just before the + * postordering and permutation vectors are computed. For elements, + * Elen [e] < EMPTY holds. + * + * On output, Elen [i] is the degree of the row/column in the Cholesky + * factorization of the permuted matrix, corresponding to the original row + * i, if i is a super row/column. It is equal to EMPTY if i is + * non-principal. Note that i refers to a row/column in the original + * matrix, not the permuted matrix. + * + * Note that the contents of Elen on output differ from the Fortran + * version (Elen holds the inverse permutation in the Fortran version, + * which is instead returned in the Next array in this C version, + * described below). + * + * Last: In a degree list, Last [i] is the supervariable preceding i, or EMPTY + * if i is the head of the list. In a hash bucket, Last [i] is the hash + * key for i. + * + * Last [Head [hash]] is also used as the head of a hash bucket if + * Head [hash] contains a degree list (see the description of Head, + * below). + * + * On output, Last [0..n-1] holds the permutation. That is, if + * i = Last [k], then row i is the kth pivot row (where k ranges from 0 to + * n-1). Row Last [k] of A is the kth row in the permuted matrix, PAP'. + * + * Next: Next [i] is the supervariable following i in a link list, or EMPTY if + * i is the last in the list. Used for two kinds of lists: degree lists + * and hash buckets (a supervariable can be in only one kind of list at a + * time). + * + * On output Next [0..n-1] holds the inverse permutation. That is, if + * k = Next [i], then row i is the kth pivot row. Row i of A appears as + * the (Next[i])-th row in the permuted matrix, PAP'. + * + * Note that the contents of Next on output differ from the Fortran + * version (Next is undefined on output in the Fortran version). + + * ---------------------------------------------------------------------------- + * LOCAL WORKSPACE (not input or output - used only during execution): + * ---------------------------------------------------------------------------- + * + * Degree: An integer array of size n. If i is a supervariable, then + * Degree [i] holds the current approximation of the external degree of + * row i (an upper bound). The external degree is the number of nonzeros + * in row i, minus ABS (Nv [i]), the diagonal part. The bound is equal to + * the exact external degree if Elen [i] is less than or equal to two. + * + * We also use the term "external degree" for elements e to refer to + * |Le \ Lme|. If e is an element, then Degree [e] is |Le|, which is the + * degree of the off-diagonal part of the element e (not including the + * diagonal part). + * + * Head: An integer array of size n. Head is used for degree lists. + * Head [deg] is the first supervariable in a degree list. All + * supervariables i in a degree list Head [deg] have the same approximate + * degree, namely, deg = Degree [i]. If the list Head [deg] is empty then + * Head [deg] = EMPTY. + * + * During supervariable detection Head [hash] also serves as a pointer to + * a hash bucket. If Head [hash] >= 0, there is a degree list of degree + * hash. The hash bucket head pointer is Last [Head [hash]]. If + * Head [hash] = EMPTY, then the degree list and hash bucket are both + * empty. If Head [hash] < EMPTY, then the degree list is empty, and + * FLIP (Head [hash]) is the head of the hash bucket. After supervariable + * detection is complete, all hash buckets are empty, and the + * (Last [Head [hash]] = EMPTY) condition is restored for the non-empty + * degree lists. + * + * W: An integer array of size n. The flag array W determines the status of + * elements and variables, and the external degree of elements. + * + * for elements: + * if W [e] = 0, then the element e is absorbed. + * if W [e] >= wflg, then W [e] - wflg is the size of the set + * |Le \ Lme|, in terms of nonzeros (the sum of ABS (Nv [i]) for + * each principal variable i that is both in the pattern of + * element e and NOT in the pattern of the current pivot element, + * me). + * if wflg > W [e] > 0, then e is not absorbed and has not yet been + * seen in the scan of the element lists in the computation of + * |Le\Lme| in Scan 1 below. + * + * for variables: + * during supervariable detection, if W [j] != wflg then j is + * not in the pattern of variable i. + * + * The W array is initialized by setting W [i] = 1 for all i, and by + * setting wflg = 2. It is reinitialized if wflg becomes too large (to + * ensure that wflg+n does not cause integer overflow). + + * ---------------------------------------------------------------------------- + * LOCAL INTEGERS: + * ---------------------------------------------------------------------------- + */ + + Int deg, degme, dext, lemax, e, elenme, eln, i, ilast, inext, j, + jlast, jnext, k, knt1, knt2, knt3, lenj, ln, me, mindeg, nel, nleft, + nvi, nvj, nvpiv, slenme, wbig, we, wflg, wnvi, ok, ndense, ncmpa, + dense, aggressive ; + + unsigned Int hash ; /* unsigned, so that hash % n is well defined.*/ + +/* + * deg: the degree of a variable or element + * degme: size, |Lme|, of the current element, me (= Degree [me]) + * dext: external degree, |Le \ Lme|, of some element e + * lemax: largest |Le| seen so far (called dmax in Fortran version) + * e: an element + * elenme: the length, Elen [me], of element list of pivotal variable + * eln: the length, Elen [...], of an element list + * hash: the computed value of the hash function + * i: a supervariable + * ilast: the entry in a link list preceding i + * inext: the entry in a link list following i + * j: a supervariable + * jlast: the entry in a link list preceding j + * jnext: the entry in a link list, or path, following j + * k: the pivot order of an element or variable + * knt1: loop counter used during element construction + * knt2: loop counter used during element construction + * knt3: loop counter used during compression + * lenj: Len [j] + * ln: length of a supervariable list + * me: current supervariable being eliminated, and the current + * element created by eliminating that supervariable + * mindeg: current minimum degree + * nel: number of pivots selected so far + * nleft: n - nel, the number of nonpivotal rows/columns remaining + * nvi: the number of variables in a supervariable i (= Nv [i]) + * nvj: the number of variables in a supervariable j (= Nv [j]) + * nvpiv: number of pivots in current element + * slenme: number of variables in variable list of pivotal variable + * wbig: = (INT_MAX - n) for the int version, (SuiteSparse_long_max - n) + * for the SuiteSparse_long version. wflg is not allowed to + * be >= wbig. + * we: W [e] + * wflg: used for flagging the W array. See description of Iw. + * wnvi: wflg - Nv [i] + * x: either a supervariable or an element + * + * ok: true if supervariable j can be absorbed into i + * ndense: number of "dense" rows/columns + * dense: rows/columns with initial degree > dense are considered "dense" + * aggressive: true if aggressive absorption is being performed + * ncmpa: number of garbage collections + + * ---------------------------------------------------------------------------- + * LOCAL DOUBLES, used for statistical output only (except for alpha): + * ---------------------------------------------------------------------------- + */ + + double f, r, ndiv, s, nms_lu, nms_ldl, dmax, alpha, lnz, lnzme ; + +/* + * f: nvpiv + * r: degme + nvpiv + * ndiv: number of divisions for LU or LDL' factorizations + * s: number of multiply-subtract pairs for LU factorization, for the + * current element me + * nms_lu number of multiply-subtract pairs for LU factorization + * nms_ldl number of multiply-subtract pairs for LDL' factorization + * dmax: the largest number of entries in any column of L, including the + * diagonal + * alpha: "dense" degree ratio + * lnz: the number of nonzeros in L (excluding the diagonal) + * lnzme: the number of nonzeros in L (excl. the diagonal) for the + * current element me + + * ---------------------------------------------------------------------------- + * LOCAL "POINTERS" (indices into the Iw array) + * ---------------------------------------------------------------------------- +*/ + + Int p, p1, p2, p3, p4, pdst, pend, pj, pme, pme1, pme2, pn, psrc ; + +/* + * Any parameter (Pe [...] or pfree) or local variable starting with "p" (for + * Pointer) is an index into Iw, and all indices into Iw use variables starting + * with "p." The only exception to this rule is the iwlen input argument. + * + * p: pointer into lots of things + * p1: Pe [i] for some variable i (start of element list) + * p2: Pe [i] + Elen [i] - 1 for some variable i + * p3: index of first supervariable in clean list + * p4: + * pdst: destination pointer, for compression + * pend: end of memory to compress + * pj: pointer into an element or variable + * pme: pointer into the current element (pme1...pme2) + * pme1: the current element, me, is stored in Iw [pme1...pme2] + * pme2: the end of the current element + * pn: pointer into a "clean" variable, also used to compress + * psrc: source pointer, for compression +*/ + +/* ========================================================================= */ +/* INITIALIZATIONS */ +/* ========================================================================= */ + + /* Note that this restriction on iwlen is slightly more restrictive than + * what is actually required in AMD_2. AMD_2 can operate with no elbow + * room at all, but it will be slow. For better performance, at least + * size-n elbow room is enforced. */ + ASSERT (iwlen >= pfree + n) ; + ASSERT (n > 0) ; + + /* initialize output statistics */ + lnz = 0 ; + ndiv = 0 ; + nms_lu = 0 ; + nms_ldl = 0 ; + dmax = 1 ; + me = EMPTY ; + + mindeg = 0 ; + ncmpa = 0 ; + nel = 0 ; + lemax = 0 ; + + /* get control parameters */ + if (Control != (double *) NULL) + { + alpha = Control [AMD_DENSE] ; + aggressive = (Control [AMD_AGGRESSIVE] != 0) ; + } + else + { + alpha = AMD_DEFAULT_DENSE ; + aggressive = AMD_DEFAULT_AGGRESSIVE ; + } + /* Note: if alpha is NaN, this is undefined: */ + if (alpha < 0) + { + /* only remove completely dense rows/columns */ + dense = n-2 ; + } + else + { + dense = alpha * sqrt ((double) n) ; + } + dense = MAX (16, dense) ; + dense = MIN (n, dense) ; + AMD_DEBUG1 (("\n\nAMD (debug), alpha %g, aggr. "ID"\n", + alpha, aggressive)) ; + + for (i = 0 ; i < n ; i++) + { + Last [i] = EMPTY ; + Head [i] = EMPTY ; + Next [i] = EMPTY ; + /* if separate Hhead array is used for hash buckets: * + Hhead [i] = EMPTY ; + */ + Nv [i] = 1 ; + W [i] = 1 ; + Elen [i] = 0 ; + Degree [i] = Len [i] ; + } + +#ifndef NDEBUG + AMD_DEBUG1 (("\n======Nel "ID" initial\n", nel)) ; + AMD_dump (n, Pe, Iw, Len, iwlen, pfree, Nv, Next, Last, + Head, Elen, Degree, W, -1) ; +#endif + + /* initialize wflg */ + wbig = Int_MAX - n ; + wflg = clear_flag (0, wbig, W, n) ; + + /* --------------------------------------------------------------------- */ + /* initialize degree lists and eliminate dense and empty rows */ + /* --------------------------------------------------------------------- */ + + ndense = 0 ; + + for (i = 0 ; i < n ; i++) + { + deg = Degree [i] ; + ASSERT (deg >= 0 && deg < n) ; + if (deg == 0) + { + + /* ------------------------------------------------------------- + * we have a variable that can be eliminated at once because + * there is no off-diagonal non-zero in its row. Note that + * Nv [i] = 1 for an empty variable i. It is treated just + * the same as an eliminated element i. + * ------------------------------------------------------------- */ + + Elen [i] = FLIP (1) ; + nel++ ; + Pe [i] = EMPTY ; + W [i] = 0 ; + + } + else if (deg > dense) + { + + /* ------------------------------------------------------------- + * Dense variables are not treated as elements, but as unordered, + * non-principal variables that have no parent. They do not take + * part in the postorder, since Nv [i] = 0. Note that the Fortran + * version does not have this option. + * ------------------------------------------------------------- */ + + AMD_DEBUG1 (("Dense node "ID" degree "ID"\n", i, deg)) ; + ndense++ ; + Nv [i] = 0 ; /* do not postorder this node */ + Elen [i] = EMPTY ; + nel++ ; + Pe [i] = EMPTY ; + + } + else + { + + /* ------------------------------------------------------------- + * place i in the degree list corresponding to its degree + * ------------------------------------------------------------- */ + + inext = Head [deg] ; + ASSERT (inext >= EMPTY && inext < n) ; + if (inext != EMPTY) Last [inext] = i ; + Next [i] = inext ; + Head [deg] = i ; + + } + } + +/* ========================================================================= */ +/* WHILE (selecting pivots) DO */ +/* ========================================================================= */ + + while (nel < n) + { + +#ifndef NDEBUG + AMD_DEBUG1 (("\n======Nel "ID"\n", nel)) ; + if (AMD_debug >= 2) + { + AMD_dump (n, Pe, Iw, Len, iwlen, pfree, Nv, Next, + Last, Head, Elen, Degree, W, nel) ; + } +#endif + +/* ========================================================================= */ +/* GET PIVOT OF MINIMUM DEGREE */ +/* ========================================================================= */ + + /* ----------------------------------------------------------------- */ + /* find next supervariable for elimination */ + /* ----------------------------------------------------------------- */ + + ASSERT (mindeg >= 0 && mindeg < n) ; + for (deg = mindeg ; deg < n ; deg++) + { + me = Head [deg] ; + if (me != EMPTY) break ; + } + mindeg = deg ; + ASSERT (me >= 0 && me < n) ; + AMD_DEBUG1 (("=================me: "ID"\n", me)) ; + + /* ----------------------------------------------------------------- */ + /* remove chosen variable from link list */ + /* ----------------------------------------------------------------- */ + + inext = Next [me] ; + ASSERT (inext >= EMPTY && inext < n) ; + if (inext != EMPTY) Last [inext] = EMPTY ; + Head [deg] = inext ; + + /* ----------------------------------------------------------------- */ + /* me represents the elimination of pivots nel to nel+Nv[me]-1. */ + /* place me itself as the first in this set. */ + /* ----------------------------------------------------------------- */ + + elenme = Elen [me] ; + nvpiv = Nv [me] ; + ASSERT (nvpiv > 0) ; + nel += nvpiv ; + +/* ========================================================================= */ +/* CONSTRUCT NEW ELEMENT */ +/* ========================================================================= */ + + /* ----------------------------------------------------------------- + * At this point, me is the pivotal supervariable. It will be + * converted into the current element. Scan list of the pivotal + * supervariable, me, setting tree pointers and constructing new list + * of supervariables for the new element, me. p is a pointer to the + * current position in the old list. + * ----------------------------------------------------------------- */ + + /* flag the variable "me" as being in Lme by negating Nv [me] */ + Nv [me] = -nvpiv ; + degme = 0 ; + ASSERT (Pe [me] >= 0 && Pe [me] < iwlen) ; + + if (elenme == 0) + { + + /* ------------------------------------------------------------- */ + /* construct the new element in place */ + /* ------------------------------------------------------------- */ + + pme1 = Pe [me] ; + pme2 = pme1 - 1 ; + + for (p = pme1 ; p <= pme1 + Len [me] - 1 ; p++) + { + i = Iw [p] ; + ASSERT (i >= 0 && i < n && Nv [i] >= 0) ; + nvi = Nv [i] ; + if (nvi > 0) + { + + /* ----------------------------------------------------- */ + /* i is a principal variable not yet placed in Lme. */ + /* store i in new list */ + /* ----------------------------------------------------- */ + + /* flag i as being in Lme by negating Nv [i] */ + degme += nvi ; + Nv [i] = -nvi ; + Iw [++pme2] = i ; + + /* ----------------------------------------------------- */ + /* remove variable i from degree list. */ + /* ----------------------------------------------------- */ + + ilast = Last [i] ; + inext = Next [i] ; + ASSERT (ilast >= EMPTY && ilast < n) ; + ASSERT (inext >= EMPTY && inext < n) ; + if (inext != EMPTY) Last [inext] = ilast ; + if (ilast != EMPTY) + { + Next [ilast] = inext ; + } + else + { + /* i is at the head of the degree list */ + ASSERT (Degree [i] >= 0 && Degree [i] < n) ; + Head [Degree [i]] = inext ; + } + } + } + } + else + { + + /* ------------------------------------------------------------- */ + /* construct the new element in empty space, Iw [pfree ...] */ + /* ------------------------------------------------------------- */ + + p = Pe [me] ; + pme1 = pfree ; + slenme = Len [me] - elenme ; + + for (knt1 = 1 ; knt1 <= elenme + 1 ; knt1++) + { + + if (knt1 > elenme) + { + /* search the supervariables in me. */ + e = me ; + pj = p ; + ln = slenme ; + AMD_DEBUG2 (("Search sv: "ID" "ID" "ID"\n", me,pj,ln)) ; + } + else + { + /* search the elements in me. */ + e = Iw [p++] ; + ASSERT (e >= 0 && e < n) ; + pj = Pe [e] ; + ln = Len [e] ; + AMD_DEBUG2 (("Search element e "ID" in me "ID"\n", e,me)) ; + ASSERT (Elen [e] < EMPTY && W [e] > 0 && pj >= 0) ; + } + ASSERT (ln >= 0 && (ln == 0 || (pj >= 0 && pj < iwlen))) ; + + /* --------------------------------------------------------- + * search for different supervariables and add them to the + * new list, compressing when necessary. this loop is + * executed once for each element in the list and once for + * all the supervariables in the list. + * --------------------------------------------------------- */ + + for (knt2 = 1 ; knt2 <= ln ; knt2++) + { + i = Iw [pj++] ; + ASSERT (i >= 0 && i < n && (i == me || Elen [i] >= EMPTY)); + nvi = Nv [i] ; + AMD_DEBUG2 ((": "ID" "ID" "ID" "ID"\n", + i, Elen [i], Nv [i], wflg)) ; + + if (nvi > 0) + { + + /* ------------------------------------------------- */ + /* compress Iw, if necessary */ + /* ------------------------------------------------- */ + + if (pfree >= iwlen) + { + + AMD_DEBUG1 (("GARBAGE COLLECTION\n")) ; + + /* prepare for compressing Iw by adjusting pointers + * and lengths so that the lists being searched in + * the inner and outer loops contain only the + * remaining entries. */ + + Pe [me] = p ; + Len [me] -= knt1 ; + /* check if nothing left of supervariable me */ + if (Len [me] == 0) Pe [me] = EMPTY ; + Pe [e] = pj ; + Len [e] = ln - knt2 ; + /* nothing left of element e */ + if (Len [e] == 0) Pe [e] = EMPTY ; + + ncmpa++ ; /* one more garbage collection */ + + /* store first entry of each object in Pe */ + /* FLIP the first entry in each object */ + for (j = 0 ; j < n ; j++) + { + pn = Pe [j] ; + if (pn >= 0) + { + ASSERT (pn >= 0 && pn < iwlen) ; + Pe [j] = Iw [pn] ; + Iw [pn] = FLIP (j) ; + } + } + + /* psrc/pdst point to source/destination */ + psrc = 0 ; + pdst = 0 ; + pend = pme1 - 1 ; + + while (psrc <= pend) + { + /* search for next FLIP'd entry */ + j = FLIP (Iw [psrc++]) ; + if (j >= 0) + { + AMD_DEBUG2 (("Got object j: "ID"\n", j)) ; + Iw [pdst] = Pe [j] ; + Pe [j] = pdst++ ; + lenj = Len [j] ; + /* copy from source to destination */ + for (knt3 = 0 ; knt3 <= lenj - 2 ; knt3++) + { + Iw [pdst++] = Iw [psrc++] ; + } + } + } + + /* move the new partially-constructed element */ + p1 = pdst ; + for (psrc = pme1 ; psrc <= pfree-1 ; psrc++) + { + Iw [pdst++] = Iw [psrc] ; + } + pme1 = p1 ; + pfree = pdst ; + pj = Pe [e] ; + p = Pe [me] ; + + } + + /* ------------------------------------------------- */ + /* i is a principal variable not yet placed in Lme */ + /* store i in new list */ + /* ------------------------------------------------- */ + + /* flag i as being in Lme by negating Nv [i] */ + degme += nvi ; + Nv [i] = -nvi ; + Iw [pfree++] = i ; + AMD_DEBUG2 ((" s: "ID" nv "ID"\n", i, Nv [i])); + + /* ------------------------------------------------- */ + /* remove variable i from degree link list */ + /* ------------------------------------------------- */ + + ilast = Last [i] ; + inext = Next [i] ; + ASSERT (ilast >= EMPTY && ilast < n) ; + ASSERT (inext >= EMPTY && inext < n) ; + if (inext != EMPTY) Last [inext] = ilast ; + if (ilast != EMPTY) + { + Next [ilast] = inext ; + } + else + { + /* i is at the head of the degree list */ + ASSERT (Degree [i] >= 0 && Degree [i] < n) ; + Head [Degree [i]] = inext ; + } + } + } + + if (e != me) + { + /* set tree pointer and flag to indicate element e is + * absorbed into new element me (the parent of e is me) */ + AMD_DEBUG1 ((" Element "ID" => "ID"\n", e, me)) ; + Pe [e] = FLIP (me) ; + W [e] = 0 ; + } + } + + pme2 = pfree - 1 ; + } + + /* ----------------------------------------------------------------- */ + /* me has now been converted into an element in Iw [pme1..pme2] */ + /* ----------------------------------------------------------------- */ + + /* degme holds the external degree of new element */ + Degree [me] = degme ; + Pe [me] = pme1 ; + Len [me] = pme2 - pme1 + 1 ; + ASSERT (Pe [me] >= 0 && Pe [me] < iwlen) ; + + Elen [me] = FLIP (nvpiv + degme) ; + /* FLIP (Elen (me)) is now the degree of pivot (including + * diagonal part). */ + +#ifndef NDEBUG + AMD_DEBUG2 (("New element structure: length= "ID"\n", pme2-pme1+1)) ; + for (pme = pme1 ; pme <= pme2 ; pme++) AMD_DEBUG3 ((" "ID"", Iw[pme])); + AMD_DEBUG3 (("\n")) ; +#endif + + /* ----------------------------------------------------------------- */ + /* make sure that wflg is not too large. */ + /* ----------------------------------------------------------------- */ + + /* With the current value of wflg, wflg+n must not cause integer + * overflow */ + + wflg = clear_flag (wflg, wbig, W, n) ; + +/* ========================================================================= */ +/* COMPUTE (W [e] - wflg) = |Le\Lme| FOR ALL ELEMENTS */ +/* ========================================================================= */ + + /* ----------------------------------------------------------------- + * Scan 1: compute the external degrees of previous elements with + * respect to the current element. That is: + * (W [e] - wflg) = |Le \ Lme| + * for each element e that appears in any supervariable in Lme. The + * notation Le refers to the pattern (list of supervariables) of a + * previous element e, where e is not yet absorbed, stored in + * Iw [Pe [e] + 1 ... Pe [e] + Len [e]]. The notation Lme + * refers to the pattern of the current element (stored in + * Iw [pme1..pme2]). If aggressive absorption is enabled, and + * (W [e] - wflg) becomes zero, then the element e will be absorbed + * in Scan 2. + * ----------------------------------------------------------------- */ + + AMD_DEBUG2 (("me: ")) ; + for (pme = pme1 ; pme <= pme2 ; pme++) + { + i = Iw [pme] ; + ASSERT (i >= 0 && i < n) ; + eln = Elen [i] ; + AMD_DEBUG3 ((""ID" Elen "ID": \n", i, eln)) ; + if (eln > 0) + { + /* note that Nv [i] has been negated to denote i in Lme: */ + nvi = -Nv [i] ; + ASSERT (nvi > 0 && Pe [i] >= 0 && Pe [i] < iwlen) ; + wnvi = wflg - nvi ; + for (p = Pe [i] ; p <= Pe [i] + eln - 1 ; p++) + { + e = Iw [p] ; + ASSERT (e >= 0 && e < n) ; + we = W [e] ; + AMD_DEBUG4 ((" e "ID" we "ID" ", e, we)) ; + if (we >= wflg) + { + /* unabsorbed element e has been seen in this loop */ + AMD_DEBUG4 ((" unabsorbed, first time seen")) ; + we -= nvi ; + } + else if (we != 0) + { + /* e is an unabsorbed element */ + /* this is the first we have seen e in all of Scan 1 */ + AMD_DEBUG4 ((" unabsorbed")) ; + we = Degree [e] + wnvi ; + } + AMD_DEBUG4 (("\n")) ; + W [e] = we ; + } + } + } + AMD_DEBUG2 (("\n")) ; + +/* ========================================================================= */ +/* DEGREE UPDATE AND ELEMENT ABSORPTION */ +/* ========================================================================= */ + + /* ----------------------------------------------------------------- + * Scan 2: for each i in Lme, sum up the degree of Lme (which is + * degme), plus the sum of the external degrees of each Le for the + * elements e appearing within i, plus the supervariables in i. + * Place i in hash list. + * ----------------------------------------------------------------- */ + + for (pme = pme1 ; pme <= pme2 ; pme++) + { + i = Iw [pme] ; + ASSERT (i >= 0 && i < n && Nv [i] < 0 && Elen [i] >= 0) ; + AMD_DEBUG2 (("Updating: i "ID" "ID" "ID"\n", i, Elen[i], Len [i])); + p1 = Pe [i] ; + p2 = p1 + Elen [i] - 1 ; + pn = p1 ; + hash = 0 ; + deg = 0 ; + ASSERT (p1 >= 0 && p1 < iwlen && p2 >= -1 && p2 < iwlen) ; + + /* ------------------------------------------------------------- */ + /* scan the element list associated with supervariable i */ + /* ------------------------------------------------------------- */ + + /* UMFPACK/MA38-style approximate degree: */ + if (aggressive) + { + for (p = p1 ; p <= p2 ; p++) + { + e = Iw [p] ; + ASSERT (e >= 0 && e < n) ; + we = W [e] ; + if (we != 0) + { + /* e is an unabsorbed element */ + /* dext = | Le \ Lme | */ + dext = we - wflg ; + if (dext > 0) + { + deg += dext ; + Iw [pn++] = e ; + hash += e ; + AMD_DEBUG4 ((" e: "ID" hash = "ID"\n",e,hash)) ; + } + else + { + /* external degree of e is zero, absorb e into me*/ + AMD_DEBUG1 ((" Element "ID" =>"ID" (aggressive)\n", + e, me)) ; + ASSERT (dext == 0) ; + Pe [e] = FLIP (me) ; + W [e] = 0 ; + } + } + } + } + else + { + for (p = p1 ; p <= p2 ; p++) + { + e = Iw [p] ; + ASSERT (e >= 0 && e < n) ; + we = W [e] ; + if (we != 0) + { + /* e is an unabsorbed element */ + dext = we - wflg ; + ASSERT (dext >= 0) ; + deg += dext ; + Iw [pn++] = e ; + hash += e ; + AMD_DEBUG4 ((" e: "ID" hash = "ID"\n",e,hash)) ; + } + } + } + + /* count the number of elements in i (including me): */ + Elen [i] = pn - p1 + 1 ; + + /* ------------------------------------------------------------- */ + /* scan the supervariables in the list associated with i */ + /* ------------------------------------------------------------- */ + + /* The bulk of the AMD run time is typically spent in this loop, + * particularly if the matrix has many dense rows that are not + * removed prior to ordering. */ + p3 = pn ; + p4 = p1 + Len [i] ; + for (p = p2 + 1 ; p < p4 ; p++) + { + j = Iw [p] ; + ASSERT (j >= 0 && j < n) ; + nvj = Nv [j] ; + if (nvj > 0) + { + /* j is unabsorbed, and not in Lme. */ + /* add to degree and add to new list */ + deg += nvj ; + Iw [pn++] = j ; + hash += j ; + AMD_DEBUG4 ((" s: "ID" hash "ID" Nv[j]= "ID"\n", + j, hash, nvj)) ; + } + } + + /* ------------------------------------------------------------- */ + /* update the degree and check for mass elimination */ + /* ------------------------------------------------------------- */ + + /* with aggressive absorption, deg==0 is identical to the + * Elen [i] == 1 && p3 == pn test, below. */ + ASSERT (IMPLIES (aggressive, (deg==0) == (Elen[i]==1 && p3==pn))) ; + + if (Elen [i] == 1 && p3 == pn) + { + + /* --------------------------------------------------------- */ + /* mass elimination */ + /* --------------------------------------------------------- */ + + /* There is nothing left of this node except for an edge to + * the current pivot element. Elen [i] is 1, and there are + * no variables adjacent to node i. Absorb i into the + * current pivot element, me. Note that if there are two or + * more mass eliminations, fillin due to mass elimination is + * possible within the nvpiv-by-nvpiv pivot block. It is this + * step that causes AMD's analysis to be an upper bound. + * + * The reason is that the selected pivot has a lower + * approximate degree than the true degree of the two mass + * eliminated nodes. There is no edge between the two mass + * eliminated nodes. They are merged with the current pivot + * anyway. + * + * No fillin occurs in the Schur complement, in any case, + * and this effect does not decrease the quality of the + * ordering itself, just the quality of the nonzero and + * flop count analysis. It also means that the post-ordering + * is not an exact elimination tree post-ordering. */ + + AMD_DEBUG1 ((" MASS i "ID" => parent e "ID"\n", i, me)) ; + Pe [i] = FLIP (me) ; + nvi = -Nv [i] ; + degme -= nvi ; + nvpiv += nvi ; + nel += nvi ; + Nv [i] = 0 ; + Elen [i] = EMPTY ; + + } + else + { + + /* --------------------------------------------------------- */ + /* update the upper-bound degree of i */ + /* --------------------------------------------------------- */ + + /* the following degree does not yet include the size + * of the current element, which is added later: */ + + Degree [i] = MIN (Degree [i], deg) ; + + /* --------------------------------------------------------- */ + /* add me to the list for i */ + /* --------------------------------------------------------- */ + + /* move first supervariable to end of list */ + Iw [pn] = Iw [p3] ; + /* move first element to end of element part of list */ + Iw [p3] = Iw [p1] ; + /* add new element, me, to front of list. */ + Iw [p1] = me ; + /* store the new length of the list in Len [i] */ + Len [i] = pn - p1 + 1 ; + + /* --------------------------------------------------------- */ + /* place in hash bucket. Save hash key of i in Last [i]. */ + /* --------------------------------------------------------- */ + + /* NOTE: this can fail if hash is negative, because the ANSI C + * standard does not define a % b when a and/or b are negative. + * That's why hash is defined as an unsigned Int, to avoid this + * problem. */ + hash = hash % n ; + ASSERT (((Int) hash) >= 0 && ((Int) hash) < n) ; + + /* if the Hhead array is not used: */ + j = Head [hash] ; + if (j <= EMPTY) + { + /* degree list is empty, hash head is FLIP (j) */ + Next [i] = FLIP (j) ; + Head [hash] = FLIP (i) ; + } + else + { + /* degree list is not empty, use Last [Head [hash]] as + * hash head. */ + Next [i] = Last [j] ; + Last [j] = i ; + } + + /* if a separate Hhead array is used: * + Next [i] = Hhead [hash] ; + Hhead [hash] = i ; + */ + + Last [i] = hash ; + } + } + + Degree [me] = degme ; + + /* ----------------------------------------------------------------- */ + /* Clear the counter array, W [...], by incrementing wflg. */ + /* ----------------------------------------------------------------- */ + + /* make sure that wflg+n does not cause integer overflow */ + lemax = MAX (lemax, degme) ; + wflg += lemax ; + wflg = clear_flag (wflg, wbig, W, n) ; + /* at this point, W [0..n-1] < wflg holds */ + +/* ========================================================================= */ +/* SUPERVARIABLE DETECTION */ +/* ========================================================================= */ + + AMD_DEBUG1 (("Detecting supervariables:\n")) ; + for (pme = pme1 ; pme <= pme2 ; pme++) + { + i = Iw [pme] ; + ASSERT (i >= 0 && i < n) ; + AMD_DEBUG2 (("Consider i "ID" nv "ID"\n", i, Nv [i])) ; + if (Nv [i] < 0) + { + /* i is a principal variable in Lme */ + + /* --------------------------------------------------------- + * examine all hash buckets with 2 or more variables. We do + * this by examing all unique hash keys for supervariables in + * the pattern Lme of the current element, me + * --------------------------------------------------------- */ + + /* let i = head of hash bucket, and empty the hash bucket */ + ASSERT (Last [i] >= 0 && Last [i] < n) ; + hash = Last [i] ; + + /* if Hhead array is not used: */ + j = Head [hash] ; + if (j == EMPTY) + { + /* hash bucket and degree list are both empty */ + i = EMPTY ; + } + else if (j < EMPTY) + { + /* degree list is empty */ + i = FLIP (j) ; + Head [hash] = EMPTY ; + } + else + { + /* degree list is not empty, restore Last [j] of head j */ + i = Last [j] ; + Last [j] = EMPTY ; + } + + /* if separate Hhead array is used: * + i = Hhead [hash] ; + Hhead [hash] = EMPTY ; + */ + + ASSERT (i >= EMPTY && i < n) ; + AMD_DEBUG2 (("----i "ID" hash "ID"\n", i, hash)) ; + + while (i != EMPTY && Next [i] != EMPTY) + { + + /* ----------------------------------------------------- + * this bucket has one or more variables following i. + * scan all of them to see if i can absorb any entries + * that follow i in hash bucket. Scatter i into w. + * ----------------------------------------------------- */ + + ln = Len [i] ; + eln = Elen [i] ; + ASSERT (ln >= 0 && eln >= 0) ; + ASSERT (Pe [i] >= 0 && Pe [i] < iwlen) ; + /* do not flag the first element in the list (me) */ + for (p = Pe [i] + 1 ; p <= Pe [i] + ln - 1 ; p++) + { + ASSERT (Iw [p] >= 0 && Iw [p] < n) ; + W [Iw [p]] = wflg ; + } + + /* ----------------------------------------------------- */ + /* scan every other entry j following i in bucket */ + /* ----------------------------------------------------- */ + + jlast = i ; + j = Next [i] ; + ASSERT (j >= EMPTY && j < n) ; + + while (j != EMPTY) + { + /* ------------------------------------------------- */ + /* check if j and i have identical nonzero pattern */ + /* ------------------------------------------------- */ + + AMD_DEBUG3 (("compare i "ID" and j "ID"\n", i,j)) ; + + /* check if i and j have the same Len and Elen */ + ASSERT (Len [j] >= 0 && Elen [j] >= 0) ; + ASSERT (Pe [j] >= 0 && Pe [j] < iwlen) ; + ok = (Len [j] == ln) && (Elen [j] == eln) ; + /* skip the first element in the list (me) */ + for (p = Pe [j] + 1 ; ok && p <= Pe [j] + ln - 1 ; p++) + { + ASSERT (Iw [p] >= 0 && Iw [p] < n) ; + if (W [Iw [p]] != wflg) ok = 0 ; + } + if (ok) + { + /* --------------------------------------------- */ + /* found it! j can be absorbed into i */ + /* --------------------------------------------- */ + + AMD_DEBUG1 (("found it! j "ID" => i "ID"\n", j,i)); + Pe [j] = FLIP (i) ; + /* both Nv [i] and Nv [j] are negated since they */ + /* are in Lme, and the absolute values of each */ + /* are the number of variables in i and j: */ + Nv [i] += Nv [j] ; + Nv [j] = 0 ; + Elen [j] = EMPTY ; + /* delete j from hash bucket */ + ASSERT (j != Next [j]) ; + j = Next [j] ; + Next [jlast] = j ; + + } + else + { + /* j cannot be absorbed into i */ + jlast = j ; + ASSERT (j != Next [j]) ; + j = Next [j] ; + } + ASSERT (j >= EMPTY && j < n) ; + } + + /* ----------------------------------------------------- + * no more variables can be absorbed into i + * go to next i in bucket and clear flag array + * ----------------------------------------------------- */ + + wflg++ ; + i = Next [i] ; + ASSERT (i >= EMPTY && i < n) ; + + } + } + } + AMD_DEBUG2 (("detect done\n")) ; + +/* ========================================================================= */ +/* RESTORE DEGREE LISTS AND REMOVE NONPRINCIPAL SUPERVARIABLES FROM ELEMENT */ +/* ========================================================================= */ + + p = pme1 ; + nleft = n - nel ; + for (pme = pme1 ; pme <= pme2 ; pme++) + { + i = Iw [pme] ; + ASSERT (i >= 0 && i < n) ; + nvi = -Nv [i] ; + AMD_DEBUG3 (("Restore i "ID" "ID"\n", i, nvi)) ; + if (nvi > 0) + { + /* i is a principal variable in Lme */ + /* restore Nv [i] to signify that i is principal */ + Nv [i] = nvi ; + + /* --------------------------------------------------------- */ + /* compute the external degree (add size of current element) */ + /* --------------------------------------------------------- */ + + deg = Degree [i] + degme - nvi ; + deg = MIN (deg, nleft - nvi) ; + ASSERT (IMPLIES (aggressive, deg > 0) && deg >= 0 && deg < n) ; + + /* --------------------------------------------------------- */ + /* place the supervariable at the head of the degree list */ + /* --------------------------------------------------------- */ + + inext = Head [deg] ; + ASSERT (inext >= EMPTY && inext < n) ; + if (inext != EMPTY) Last [inext] = i ; + Next [i] = inext ; + Last [i] = EMPTY ; + Head [deg] = i ; + + /* --------------------------------------------------------- */ + /* save the new degree, and find the minimum degree */ + /* --------------------------------------------------------- */ + + mindeg = MIN (mindeg, deg) ; + Degree [i] = deg ; + + /* --------------------------------------------------------- */ + /* place the supervariable in the element pattern */ + /* --------------------------------------------------------- */ + + Iw [p++] = i ; + + } + } + AMD_DEBUG2 (("restore done\n")) ; + +/* ========================================================================= */ +/* FINALIZE THE NEW ELEMENT */ +/* ========================================================================= */ + + AMD_DEBUG2 (("ME = "ID" DONE\n", me)) ; + Nv [me] = nvpiv ; + /* save the length of the list for the new element me */ + Len [me] = p - pme1 ; + if (Len [me] == 0) + { + /* there is nothing left of the current pivot element */ + /* it is a root of the assembly tree */ + Pe [me] = EMPTY ; + W [me] = 0 ; + } + if (elenme != 0) + { + /* element was not constructed in place: deallocate part of */ + /* it since newly nonprincipal variables may have been removed */ + pfree = p ; + } + + /* The new element has nvpiv pivots and the size of the contribution + * block for a multifrontal method is degme-by-degme, not including + * the "dense" rows/columns. If the "dense" rows/columns are included, + * the frontal matrix is no larger than + * (degme+ndense)-by-(degme+ndense). + */ + + if (Info != (double *) NULL) + { + f = nvpiv ; + r = degme + ndense ; + dmax = MAX (dmax, f + r) ; + + /* number of nonzeros in L (excluding the diagonal) */ + lnzme = f*r + (f-1)*f/2 ; + lnz += lnzme ; + + /* number of divide operations for LDL' and for LU */ + ndiv += lnzme ; + + /* number of multiply-subtract pairs for LU */ + s = f*r*r + r*(f-1)*f + (f-1)*f*(2*f-1)/6 ; + nms_lu += s ; + + /* number of multiply-subtract pairs for LDL' */ + nms_ldl += (s + lnzme)/2 ; + } + +#ifndef NDEBUG + AMD_DEBUG2 (("finalize done nel "ID" n "ID"\n ::::\n", nel, n)) ; + for (pme = Pe [me] ; pme <= Pe [me] + Len [me] - 1 ; pme++) + { + AMD_DEBUG3 ((" "ID"", Iw [pme])) ; + } + AMD_DEBUG3 (("\n")) ; +#endif + + } + +/* ========================================================================= */ +/* DONE SELECTING PIVOTS */ +/* ========================================================================= */ + + if (Info != (double *) NULL) + { + + /* count the work to factorize the ndense-by-ndense submatrix */ + f = ndense ; + dmax = MAX (dmax, (double) ndense) ; + + /* number of nonzeros in L (excluding the diagonal) */ + lnzme = (f-1)*f/2 ; + lnz += lnzme ; + + /* number of divide operations for LDL' and for LU */ + ndiv += lnzme ; + + /* number of multiply-subtract pairs for LU */ + s = (f-1)*f*(2*f-1)/6 ; + nms_lu += s ; + + /* number of multiply-subtract pairs for LDL' */ + nms_ldl += (s + lnzme)/2 ; + + /* number of nz's in L (excl. diagonal) */ + Info [AMD_LNZ] = lnz ; + + /* number of divide ops for LU and LDL' */ + Info [AMD_NDIV] = ndiv ; + + /* number of multiply-subtract pairs for LDL' */ + Info [AMD_NMULTSUBS_LDL] = nms_ldl ; + + /* number of multiply-subtract pairs for LU */ + Info [AMD_NMULTSUBS_LU] = nms_lu ; + + /* number of "dense" rows/columns */ + Info [AMD_NDENSE] = ndense ; + + /* largest front is dmax-by-dmax */ + Info [AMD_DMAX] = dmax ; + + /* number of garbage collections in AMD */ + Info [AMD_NCMPA] = ncmpa ; + + /* successful ordering */ + Info [AMD_STATUS] = AMD_OK ; + } + +/* ========================================================================= */ +/* POST-ORDERING */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- + * Variables at this point: + * + * Pe: holds the elimination tree. The parent of j is FLIP (Pe [j]), + * or EMPTY if j is a root. The tree holds both elements and + * non-principal (unordered) variables absorbed into them. + * Dense variables are non-principal and unordered. + * + * Elen: holds the size of each element, including the diagonal part. + * FLIP (Elen [e]) > 0 if e is an element. For unordered + * variables i, Elen [i] is EMPTY. + * + * Nv: Nv [e] > 0 is the number of pivots represented by the element e. + * For unordered variables i, Nv [i] is zero. + * + * Contents no longer needed: + * W, Iw, Len, Degree, Head, Next, Last. + * + * The matrix itself has been destroyed. + * + * n: the size of the matrix. + * No other scalars needed (pfree, iwlen, etc.) + * ------------------------------------------------------------------------- */ + + /* restore Pe */ + for (i = 0 ; i < n ; i++) + { + Pe [i] = FLIP (Pe [i]) ; + } + + /* restore Elen, for output information, and for postordering */ + for (i = 0 ; i < n ; i++) + { + Elen [i] = FLIP (Elen [i]) ; + } + +/* Now the parent of j is Pe [j], or EMPTY if j is a root. Elen [e] > 0 + * is the size of element e. Elen [i] is EMPTY for unordered variable i. */ + +#ifndef NDEBUG + AMD_DEBUG2 (("\nTree:\n")) ; + for (i = 0 ; i < n ; i++) + { + AMD_DEBUG2 ((" "ID" parent: "ID" ", i, Pe [i])) ; + ASSERT (Pe [i] >= EMPTY && Pe [i] < n) ; + if (Nv [i] > 0) + { + /* this is an element */ + e = i ; + AMD_DEBUG2 ((" element, size is "ID"\n", Elen [i])) ; + ASSERT (Elen [e] > 0) ; + } + AMD_DEBUG2 (("\n")) ; + } + AMD_DEBUG2 (("\nelements:\n")) ; + for (e = 0 ; e < n ; e++) + { + if (Nv [e] > 0) + { + AMD_DEBUG3 (("Element e= "ID" size "ID" nv "ID" \n", e, + Elen [e], Nv [e])) ; + } + } + AMD_DEBUG2 (("\nvariables:\n")) ; + for (i = 0 ; i < n ; i++) + { + Int cnt ; + if (Nv [i] == 0) + { + AMD_DEBUG3 (("i unordered: "ID"\n", i)) ; + j = Pe [i] ; + cnt = 0 ; + AMD_DEBUG3 ((" j: "ID"\n", j)) ; + if (j == EMPTY) + { + AMD_DEBUG3 ((" i is a dense variable\n")) ; + } + else + { + ASSERT (j >= 0 && j < n) ; + while (Nv [j] == 0) + { + AMD_DEBUG3 ((" j : "ID"\n", j)) ; + j = Pe [j] ; + AMD_DEBUG3 ((" j:: "ID"\n", j)) ; + cnt++ ; + if (cnt > n) break ; + } + e = j ; + AMD_DEBUG3 ((" got to e: "ID"\n", e)) ; + } + } + } +#endif + +/* ========================================================================= */ +/* compress the paths of the variables */ +/* ========================================================================= */ + + for (i = 0 ; i < n ; i++) + { + if (Nv [i] == 0) + { + + /* ------------------------------------------------------------- + * i is an un-ordered row. Traverse the tree from i until + * reaching an element, e. The element, e, was the principal + * supervariable of i and all nodes in the path from i to when e + * was selected as pivot. + * ------------------------------------------------------------- */ + + AMD_DEBUG1 (("Path compression, i unordered: "ID"\n", i)) ; + j = Pe [i] ; + ASSERT (j >= EMPTY && j < n) ; + AMD_DEBUG3 ((" j: "ID"\n", j)) ; + if (j == EMPTY) + { + /* Skip a dense variable. It has no parent. */ + AMD_DEBUG3 ((" i is a dense variable\n")) ; + continue ; + } + + /* while (j is a variable) */ + while (Nv [j] == 0) + { + AMD_DEBUG3 ((" j : "ID"\n", j)) ; + j = Pe [j] ; + AMD_DEBUG3 ((" j:: "ID"\n", j)) ; + ASSERT (j >= 0 && j < n) ; + } + /* got to an element e */ + e = j ; + AMD_DEBUG3 (("got to e: "ID"\n", e)) ; + + /* ------------------------------------------------------------- + * traverse the path again from i to e, and compress the path + * (all nodes point to e). Path compression allows this code to + * compute in O(n) time. + * ------------------------------------------------------------- */ + + j = i ; + /* while (j is a variable) */ + while (Nv [j] == 0) + { + jnext = Pe [j] ; + AMD_DEBUG3 (("j "ID" jnext "ID"\n", j, jnext)) ; + Pe [j] = e ; + j = jnext ; + ASSERT (j >= 0 && j < n) ; + } + } + } + +/* ========================================================================= */ +/* postorder the assembly tree */ +/* ========================================================================= */ + + AMD_postorder (n, Pe, Nv, Elen, + W, /* output order */ + Head, Next, Last) ; /* workspace */ + +/* ========================================================================= */ +/* compute output permutation and inverse permutation */ +/* ========================================================================= */ + + /* W [e] = k means that element e is the kth element in the new + * order. e is in the range 0 to n-1, and k is in the range 0 to + * the number of elements. Use Head for inverse order. */ + + for (k = 0 ; k < n ; k++) + { + Head [k] = EMPTY ; + Next [k] = EMPTY ; + } + for (e = 0 ; e < n ; e++) + { + k = W [e] ; + ASSERT ((k == EMPTY) == (Nv [e] == 0)) ; + if (k != EMPTY) + { + ASSERT (k >= 0 && k < n) ; + Head [k] = e ; + } + } + + /* construct output inverse permutation in Next, + * and permutation in Last */ + nel = 0 ; + for (k = 0 ; k < n ; k++) + { + e = Head [k] ; + if (e == EMPTY) break ; + ASSERT (e >= 0 && e < n && Nv [e] > 0) ; + Next [e] = nel ; + nel += Nv [e] ; + } + ASSERT (nel == n - ndense) ; + + /* order non-principal variables (dense, & those merged into supervar's) */ + for (i = 0 ; i < n ; i++) + { + if (Nv [i] == 0) + { + e = Pe [i] ; + ASSERT (e >= EMPTY && e < n) ; + if (e != EMPTY) + { + /* This is an unordered variable that was merged + * into element e via supernode detection or mass + * elimination of i when e became the pivot element. + * Place i in order just before e. */ + ASSERT (Next [i] == EMPTY && Nv [e] > 0) ; + Next [i] = Next [e] ; + Next [e]++ ; + } + else + { + /* This is a dense unordered variable, with no parent. + * Place it last in the output order. */ + Next [i] = nel++ ; + } + } + } + ASSERT (nel == n) ; + + AMD_DEBUG2 (("\n\nPerm:\n")) ; + for (i = 0 ; i < n ; i++) + { + k = Next [i] ; + ASSERT (k >= 0 && k < n) ; + Last [k] = i ; + AMD_DEBUG2 ((" perm ["ID"] = "ID"\n", k, i)) ; + } +} diff --git a/src/AMD/Source/amd_aat.c b/src/AMD/Source/amd_aat.c new file mode 100644 index 0000000..67c03f7 --- /dev/null +++ b/src/AMD/Source/amd_aat.c @@ -0,0 +1,184 @@ +/* ========================================================================= */ +/* === AMD_aat ============================================================= */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* AMD_aat: compute the symmetry of the pattern of A, and count the number of + * nonzeros each column of A+A' (excluding the diagonal). Assumes the input + * matrix has no errors, with sorted columns and no duplicates + * (AMD_valid (n, n, Ap, Ai) must be AMD_OK, but this condition is not + * checked). + */ + +#include "amd_internal.h" + +GLOBAL size_t AMD_aat /* returns nz in A+A' */ +( + Int n, + const Int Ap [ ], + const Int Ai [ ], + Int Len [ ], /* Len [j]: length of column j of A+A', excl diagonal*/ + Int Tp [ ], /* workspace of size n */ + double Info [ ] +) +{ + Int p1, p2, p, i, j, pj, pj2, k, nzdiag, nzboth, nz ; + double sym ; + size_t nzaat ; + +#ifndef NDEBUG + AMD_debug_init ("AMD AAT") ; + for (k = 0 ; k < n ; k++) Tp [k] = EMPTY ; + ASSERT (AMD_valid (n, n, Ap, Ai) == AMD_OK) ; +#endif + + if (Info != (double *) NULL) + { + /* clear the Info array, if it exists */ + for (i = 0 ; i < AMD_INFO ; i++) + { + Info [i] = EMPTY ; + } + Info [AMD_STATUS] = AMD_OK ; + } + + for (k = 0 ; k < n ; k++) + { + Len [k] = 0 ; + } + + nzdiag = 0 ; + nzboth = 0 ; + nz = Ap [n] ; + + for (k = 0 ; k < n ; k++) + { + p1 = Ap [k] ; + p2 = Ap [k+1] ; + AMD_DEBUG2 (("\nAAT Column: "ID" p1: "ID" p2: "ID"\n", k, p1, p2)) ; + + /* construct A+A' */ + for (p = p1 ; p < p2 ; ) + { + /* scan the upper triangular part of A */ + j = Ai [p] ; + if (j < k) + { + /* entry A (j,k) is in the strictly upper triangular part, + * add both A (j,k) and A (k,j) to the matrix A+A' */ + Len [j]++ ; + Len [k]++ ; + AMD_DEBUG3 ((" upper ("ID","ID") ("ID","ID")\n", j,k, k,j)); + p++ ; + } + else if (j == k) + { + /* skip the diagonal */ + p++ ; + nzdiag++ ; + break ; + } + else /* j > k */ + { + /* first entry below the diagonal */ + break ; + } + /* scan lower triangular part of A, in column j until reaching + * row k. Start where last scan left off. */ + ASSERT (Tp [j] != EMPTY) ; + ASSERT (Ap [j] <= Tp [j] && Tp [j] <= Ap [j+1]) ; + pj2 = Ap [j+1] ; + for (pj = Tp [j] ; pj < pj2 ; ) + { + i = Ai [pj] ; + if (i < k) + { + /* A (i,j) is only in the lower part, not in upper. + * add both A (i,j) and A (j,i) to the matrix A+A' */ + Len [i]++ ; + Len [j]++ ; + AMD_DEBUG3 ((" lower ("ID","ID") ("ID","ID")\n", + i,j, j,i)) ; + pj++ ; + } + else if (i == k) + { + /* entry A (k,j) in lower part and A (j,k) in upper */ + pj++ ; + nzboth++ ; + break ; + } + else /* i > k */ + { + /* consider this entry later, when k advances to i */ + break ; + } + } + Tp [j] = pj ; + } + /* Tp [k] points to the entry just below the diagonal in column k */ + Tp [k] = p ; + } + + /* clean up, for remaining mismatched entries */ + for (j = 0 ; j < n ; j++) + { + for (pj = Tp [j] ; pj < Ap [j+1] ; pj++) + { + i = Ai [pj] ; + /* A (i,j) is only in the lower part, not in upper. + * add both A (i,j) and A (j,i) to the matrix A+A' */ + Len [i]++ ; + Len [j]++ ; + AMD_DEBUG3 ((" lower cleanup ("ID","ID") ("ID","ID")\n", + i,j, j,i)) ; + } + } + + /* --------------------------------------------------------------------- */ + /* compute the symmetry of the nonzero pattern of A */ + /* --------------------------------------------------------------------- */ + + /* Given a matrix A, the symmetry of A is: + * B = tril (spones (A), -1) + triu (spones (A), 1) ; + * sym = nnz (B & B') / nnz (B) ; + * or 1 if nnz (B) is zero. + */ + + if (nz == nzdiag) + { + sym = 1 ; + } + else + { + sym = (2 * (double) nzboth) / ((double) (nz - nzdiag)) ; + } + + nzaat = 0 ; + for (k = 0 ; k < n ; k++) + { + nzaat += Len [k] ; + } + + AMD_DEBUG1 (("AMD nz in A+A', excluding diagonal (nzaat) = %g\n", + (double) nzaat)) ; + AMD_DEBUG1 ((" nzboth: "ID" nz: "ID" nzdiag: "ID" symmetry: %g\n", + nzboth, nz, nzdiag, sym)) ; + + if (Info != (double *) NULL) + { + Info [AMD_STATUS] = AMD_OK ; + Info [AMD_N] = n ; + Info [AMD_NZ] = nz ; + Info [AMD_SYMMETRY] = sym ; /* symmetry of pattern of A */ + Info [AMD_NZDIAG] = nzdiag ; /* nonzeros on diagonal of A */ + Info [AMD_NZ_A_PLUS_AT] = nzaat ; /* nonzeros in A+A' */ + } + + return (nzaat) ; +} diff --git a/src/AMD/Source/amd_control.c b/src/AMD/Source/amd_control.c new file mode 100644 index 0000000..b3c4e01 --- /dev/null +++ b/src/AMD/Source/amd_control.c @@ -0,0 +1,63 @@ +/* ========================================================================= */ +/* === AMD_control ========================================================= */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* User-callable. Prints the control parameters for AMD. See amd.h + * for details. If the Control array is not present, the defaults are + * printed instead. + */ + +#include "amd_internal.h" + +GLOBAL void AMD_control +( + double Control [ ] +) +{ + double alpha ; + Int aggressive ; + + if (Control != (double *) NULL) + { + alpha = Control [AMD_DENSE] ; + aggressive = Control [AMD_AGGRESSIVE] != 0 ; + } + else + { + alpha = AMD_DEFAULT_DENSE ; + aggressive = AMD_DEFAULT_AGGRESSIVE ; + } + + PRINTF (("\nAMD version %d.%d.%d, %s: approximate minimum degree ordering\n" + " dense row parameter: %g\n", AMD_MAIN_VERSION, AMD_SUB_VERSION, + AMD_SUBSUB_VERSION, AMD_DATE, alpha)) ; + + if (alpha < 0) + { + PRINTF ((" no rows treated as dense\n")) ; + } + else + { + PRINTF (( + " (rows with more than max (%g * sqrt (n), 16) entries are\n" + " considered \"dense\", and placed last in output permutation)\n", + alpha)) ; + } + + if (aggressive) + { + PRINTF ((" aggressive absorption: yes\n")) ; + } + else + { + PRINTF ((" aggressive absorption: no\n")) ; + } + + PRINTF ((" size of AMD integer: %d\n\n", sizeof (Int))) ; +} diff --git a/src/AMD/Source/amd_defaults.c b/src/AMD/Source/amd_defaults.c new file mode 100644 index 0000000..b9a9079 --- /dev/null +++ b/src/AMD/Source/amd_defaults.c @@ -0,0 +1,37 @@ +/* ========================================================================= */ +/* === AMD_defaults ======================================================== */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* User-callable. Sets default control parameters for AMD. See amd.h + * for details. + */ + +#include "amd_internal.h" + +/* ========================================================================= */ +/* === AMD defaults ======================================================== */ +/* ========================================================================= */ + +GLOBAL void AMD_defaults +( + double Control [ ] +) +{ + Int i ; + + if (Control != (double *) NULL) + { + for (i = 0 ; i < AMD_CONTROL ; i++) + { + Control [i] = 0 ; + } + Control [AMD_DENSE] = AMD_DEFAULT_DENSE ; + Control [AMD_AGGRESSIVE] = AMD_DEFAULT_AGGRESSIVE ; + } +} diff --git a/src/AMD/Source/amd_dump.c b/src/AMD/Source/amd_dump.c new file mode 100644 index 0000000..e58aaf5 --- /dev/null +++ b/src/AMD/Source/amd_dump.c @@ -0,0 +1,179 @@ +/* ========================================================================= */ +/* === AMD_dump ============================================================ */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* Debugging routines for AMD. Not used if NDEBUG is not defined at compile- + * time (the default). See comments in amd_internal.h on how to enable + * debugging. Not user-callable. + */ + +#include "amd_internal.h" + +#ifndef NDEBUG + +/* This global variable is present only when debugging */ +GLOBAL Int AMD_debug = -999 ; /* default is no debug printing */ + +/* ========================================================================= */ +/* === AMD_debug_init ====================================================== */ +/* ========================================================================= */ + +/* Sets the debug print level, by reading the file debug.amd (if it exists) */ + +GLOBAL void AMD_debug_init ( char *s ) +{ + FILE *f ; + f = fopen ("debug.amd", "r") ; + if (f == (FILE *) NULL) + { + AMD_debug = -999 ; + } + else + { + fscanf (f, ID, &AMD_debug) ; + fclose (f) ; + } + if (AMD_debug >= 0) + { + printf ("%s: AMD_debug_init, D= "ID"\n", s, AMD_debug) ; + } +} + +/* ========================================================================= */ +/* === AMD_dump ============================================================ */ +/* ========================================================================= */ + +/* Dump AMD's data structure, except for the hash buckets. This routine + * cannot be called when the hash buckets are non-empty. + */ + +GLOBAL void AMD_dump ( + Int n, /* A is n-by-n */ + Int Pe [ ], /* pe [0..n-1]: index in iw of start of row i */ + Int Iw [ ], /* workspace of size iwlen, iwlen [0..pfree-1] + * holds the matrix on input */ + Int Len [ ], /* len [0..n-1]: length for row i */ + Int iwlen, /* length of iw */ + Int pfree, /* iw [pfree ... iwlen-1] is empty on input */ + Int Nv [ ], /* nv [0..n-1] */ + Int Next [ ], /* next [0..n-1] */ + Int Last [ ], /* last [0..n-1] */ + Int Head [ ], /* head [0..n-1] */ + Int Elen [ ], /* size n */ + Int Degree [ ], /* size n */ + Int W [ ], /* size n */ + Int nel +) +{ + Int i, pe, elen, nv, len, e, p, k, j, deg, w, cnt, ilast ; + + if (AMD_debug < 0) return ; + ASSERT (pfree <= iwlen) ; + AMD_DEBUG3 (("\nAMD dump, pfree: "ID"\n", pfree)) ; + for (i = 0 ; i < n ; i++) + { + pe = Pe [i] ; + elen = Elen [i] ; + nv = Nv [i] ; + len = Len [i] ; + w = W [i] ; + + if (elen >= EMPTY) + { + if (nv == 0) + { + AMD_DEBUG3 (("\nI "ID": nonprincipal: ", i)) ; + ASSERT (elen == EMPTY) ; + if (pe == EMPTY) + { + AMD_DEBUG3 ((" dense node\n")) ; + ASSERT (w == 1) ; + } + else + { + ASSERT (pe < EMPTY) ; + AMD_DEBUG3 ((" i "ID" -> parent "ID"\n", i, FLIP (Pe[i]))); + } + } + else + { + AMD_DEBUG3 (("\nI "ID": active principal supervariable:\n",i)); + AMD_DEBUG3 ((" nv(i): "ID" Flag: %d\n", nv, (nv < 0))) ; + ASSERT (elen >= 0) ; + ASSERT (nv > 0 && pe >= 0) ; + p = pe ; + AMD_DEBUG3 ((" e/s: ")) ; + if (elen == 0) AMD_DEBUG3 ((" : ")) ; + ASSERT (pe + len <= pfree) ; + for (k = 0 ; k < len ; k++) + { + j = Iw [p] ; + AMD_DEBUG3 ((" "ID"", j)) ; + ASSERT (j >= 0 && j < n) ; + if (k == elen-1) AMD_DEBUG3 ((" : ")) ; + p++ ; + } + AMD_DEBUG3 (("\n")) ; + } + } + else + { + e = i ; + if (w == 0) + { + AMD_DEBUG3 (("\nE "ID": absorbed element: w "ID"\n", e, w)) ; + ASSERT (nv > 0 && pe < 0) ; + AMD_DEBUG3 ((" e "ID" -> parent "ID"\n", e, FLIP (Pe [e]))) ; + } + else + { + AMD_DEBUG3 (("\nE "ID": unabsorbed element: w "ID"\n", e, w)) ; + ASSERT (nv > 0 && pe >= 0) ; + p = pe ; + AMD_DEBUG3 ((" : ")) ; + ASSERT (pe + len <= pfree) ; + for (k = 0 ; k < len ; k++) + { + j = Iw [p] ; + AMD_DEBUG3 ((" "ID"", j)) ; + ASSERT (j >= 0 && j < n) ; + p++ ; + } + AMD_DEBUG3 (("\n")) ; + } + } + } + + /* this routine cannot be called when the hash buckets are non-empty */ + AMD_DEBUG3 (("\nDegree lists:\n")) ; + if (nel >= 0) + { + cnt = 0 ; + for (deg = 0 ; deg < n ; deg++) + { + if (Head [deg] == EMPTY) continue ; + ilast = EMPTY ; + AMD_DEBUG3 ((ID": \n", deg)) ; + for (i = Head [deg] ; i != EMPTY ; i = Next [i]) + { + AMD_DEBUG3 ((" "ID" : next "ID" last "ID" deg "ID"\n", + i, Next [i], Last [i], Degree [i])) ; + ASSERT (i >= 0 && i < n && ilast == Last [i] && + deg == Degree [i]) ; + cnt += Nv [i] ; + ilast = i ; + } + AMD_DEBUG3 (("\n")) ; + } + ASSERT (cnt == n - nel) ; + } + +} + +#endif diff --git a/src/AMD/Source/amd_global.c b/src/AMD/Source/amd_global.c new file mode 100644 index 0000000..2bf5542 --- /dev/null +++ b/src/AMD/Source/amd_global.c @@ -0,0 +1,83 @@ +/* ========================================================================= */ +/* === amd_global ========================================================== */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +#include + +#ifdef MATLAB_MEX_FILE +#include "mex.h" +#include "matrix.h" +#endif + +#ifndef NULL +#define NULL 0 +#endif + +/* ========================================================================= */ +/* === Default AMD memory manager ========================================== */ +/* ========================================================================= */ + +/* The user can redefine these global pointers at run-time to change the memory + * manager used by AMD. AMD only uses malloc and free; realloc and calloc are + * include for completeness, in case another package wants to use the same + * memory manager as AMD. + * + * If compiling as a MATLAB mexFunction, the default memory manager is mxMalloc. + * You can also compile AMD as a standard ANSI-C library and link a mexFunction + * against it, and then redefine these pointers at run-time, in your + * mexFunction. + * + * If -DNMALLOC is defined at compile-time, no memory manager is specified at + * compile-time. You must then define these functions at run-time, before + * calling AMD, for AMD to work properly. + */ + +#ifndef NMALLOC +#ifdef MATLAB_MEX_FILE +/* MATLAB mexFunction: */ +void *(*amd_malloc) (size_t) = mxMalloc ; +void (*amd_free) (void *) = mxFree ; +void *(*amd_realloc) (void *, size_t) = mxRealloc ; +void *(*amd_calloc) (size_t, size_t) = mxCalloc ; +#else +/* standard ANSI-C: */ +void *(*amd_malloc) (size_t) = malloc ; +void (*amd_free) (void *) = free ; +void *(*amd_realloc) (void *, size_t) = realloc ; +void *(*amd_calloc) (size_t, size_t) = calloc ; +#endif +#else +/* no memory manager defined at compile-time; you MUST define one at run-time */ +void *(*amd_malloc) (size_t) = NULL ; +void (*amd_free) (void *) = NULL ; +void *(*amd_realloc) (void *, size_t) = NULL ; +void *(*amd_calloc) (size_t, size_t) = NULL ; +#endif + +/* ========================================================================= */ +/* === Default AMD printf routine ========================================== */ +/* ========================================================================= */ + +/* The user can redefine this global pointer at run-time to change the printf + * routine used by AMD. If NULL, no printing occurs. + * + * If -DNPRINT is defined at compile-time, stdio.h is not included. Printing + * can then be enabled at run-time by setting amd_printf to a non-NULL function. + */ + +#ifndef NPRINT +#ifdef MATLAB_MEX_FILE +int (*amd_printf) (const char *, ...) = mexPrintf ; +#else +#include +int (*amd_printf) (const char *, ...) = printf ; +#endif +#else +int (*amd_printf) (const char *, ...) = NULL ; +#endif diff --git a/src/AMD/Source/amd_info.c b/src/AMD/Source/amd_info.c new file mode 100644 index 0000000..47e6dff --- /dev/null +++ b/src/AMD/Source/amd_info.c @@ -0,0 +1,119 @@ +/* ========================================================================= */ +/* === AMD_info ============================================================ */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* User-callable. Prints the output statistics for AMD. See amd.h + * for details. If the Info array is not present, nothing is printed. + */ + +#include "amd_internal.h" + +#define PRI(format,x) { if (x >= 0) { PRINTF ((format, x)) ; }} + +GLOBAL void AMD_info +( + double Info [ ] +) +{ + double n, ndiv, nmultsubs_ldl, nmultsubs_lu, lnz, lnzd ; + + PRINTF (("\nAMD version %d.%d.%d, %s, results:\n", + AMD_MAIN_VERSION, AMD_SUB_VERSION, AMD_SUBSUB_VERSION, AMD_DATE)) ; + + if (!Info) + { + return ; + } + + n = Info [AMD_N] ; + ndiv = Info [AMD_NDIV] ; + nmultsubs_ldl = Info [AMD_NMULTSUBS_LDL] ; + nmultsubs_lu = Info [AMD_NMULTSUBS_LU] ; + lnz = Info [AMD_LNZ] ; + lnzd = (n >= 0 && lnz >= 0) ? (n + lnz) : (-1) ; + + /* AMD return status */ + PRINTF ((" status: ")) ; + if (Info [AMD_STATUS] == AMD_OK) + { + PRINTF (("OK\n")) ; + } + else if (Info [AMD_STATUS] == AMD_OUT_OF_MEMORY) + { + PRINTF (("out of memory\n")) ; + } + else if (Info [AMD_STATUS] == AMD_INVALID) + { + PRINTF (("invalid matrix\n")) ; + } + else if (Info [AMD_STATUS] == AMD_OK_BUT_JUMBLED) + { + PRINTF (("OK, but jumbled\n")) ; + } + else + { + PRINTF (("unknown\n")) ; + } + + /* statistics about the input matrix */ + PRI (" n, dimension of A: %.20g\n", n); + PRI (" nz, number of nonzeros in A: %.20g\n", + Info [AMD_NZ]) ; + PRI (" symmetry of A: %.4f\n", + Info [AMD_SYMMETRY]) ; + PRI (" number of nonzeros on diagonal: %.20g\n", + Info [AMD_NZDIAG]) ; + PRI (" nonzeros in pattern of A+A' (excl. diagonal): %.20g\n", + Info [AMD_NZ_A_PLUS_AT]) ; + PRI (" # dense rows/columns of A+A': %.20g\n", + Info [AMD_NDENSE]) ; + + /* statistics about AMD's behavior */ + PRI (" memory used, in bytes: %.20g\n", + Info [AMD_MEMORY]) ; + PRI (" # of memory compactions: %.20g\n", + Info [AMD_NCMPA]) ; + + /* statistics about the ordering quality */ + PRINTF (("\n" + " The following approximate statistics are for a subsequent\n" + " factorization of A(P,P) + A(P,P)'. They are slight upper\n" + " bounds if there are no dense rows/columns in A+A', and become\n" + " looser if dense rows/columns exist.\n\n")) ; + + PRI (" nonzeros in L (excluding diagonal): %.20g\n", + lnz) ; + PRI (" nonzeros in L (including diagonal): %.20g\n", + lnzd) ; + PRI (" # divide operations for LDL' or LU: %.20g\n", + ndiv) ; + PRI (" # multiply-subtract operations for LDL': %.20g\n", + nmultsubs_ldl) ; + PRI (" # multiply-subtract operations for LU: %.20g\n", + nmultsubs_lu) ; + PRI (" max nz. in any column of L (incl. diagonal): %.20g\n", + Info [AMD_DMAX]) ; + + /* total flop counts for various factorizations */ + + if (n >= 0 && ndiv >= 0 && nmultsubs_ldl >= 0 && nmultsubs_lu >= 0) + { + PRINTF (("\n" + " chol flop count for real A, sqrt counted as 1 flop: %.20g\n" + " LDL' flop count for real A: %.20g\n" + " LDL' flop count for complex A: %.20g\n" + " LU flop count for real A (with no pivoting): %.20g\n" + " LU flop count for complex A (with no pivoting): %.20g\n\n", + n + ndiv + 2*nmultsubs_ldl, + ndiv + 2*nmultsubs_ldl, + 9*ndiv + 8*nmultsubs_ldl, + ndiv + 2*nmultsubs_lu, + 9*ndiv + 8*nmultsubs_lu)) ; + } +} diff --git a/src/AMD/Source/amd_order.c b/src/AMD/Source/amd_order.c new file mode 100644 index 0000000..7ab6db7 --- /dev/null +++ b/src/AMD/Source/amd_order.c @@ -0,0 +1,199 @@ +/* ========================================================================= */ +/* === AMD_order =========================================================== */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* User-callable AMD minimum degree ordering routine. See amd.h for + * documentation. + */ + +#include "amd_internal.h" + +/* ========================================================================= */ +/* === AMD_order =========================================================== */ +/* ========================================================================= */ + +GLOBAL Int AMD_order +( + Int n, + const Int Ap [ ], + const Int Ai [ ], + Int P [ ], + double Control [ ], + double Info [ ] +) +{ + Int *Len, *S, nz, i, *Pinv, info, status, *Rp, *Ri, *Cp, *Ci, ok ; + size_t nzaat, slen ; + double mem = 0 ; + +#ifndef NDEBUG + AMD_debug_init ("amd") ; +#endif + + /* clear the Info array, if it exists */ + info = Info != (double *) NULL ; + if (info) + { + for (i = 0 ; i < AMD_INFO ; i++) + { + Info [i] = EMPTY ; + } + Info [AMD_N] = n ; + Info [AMD_STATUS] = AMD_OK ; + } + + /* make sure inputs exist and n is >= 0 */ + if (Ai == (Int *) NULL || Ap == (Int *) NULL || P == (Int *) NULL || n < 0) + { + if (info) Info [AMD_STATUS] = AMD_INVALID ; + return (AMD_INVALID) ; /* arguments are invalid */ + } + + if (n == 0) + { + return (AMD_OK) ; /* n is 0 so there's nothing to do */ + } + + nz = Ap [n] ; + if (info) + { + Info [AMD_NZ] = nz ; + } + if (nz < 0) + { + if (info) Info [AMD_STATUS] = AMD_INVALID ; + return (AMD_INVALID) ; + } + + /* check if n or nz will cause size_t overflow */ + if (((size_t) n) >= SIZE_T_MAX / sizeof (Int) + || ((size_t) nz) >= SIZE_T_MAX / sizeof (Int)) + { + if (info) Info [AMD_STATUS] = AMD_OUT_OF_MEMORY ; + return (AMD_OUT_OF_MEMORY) ; /* problem too large */ + } + + /* check the input matrix: AMD_OK, AMD_INVALID, or AMD_OK_BUT_JUMBLED */ + status = AMD_valid (n, n, Ap, Ai) ; + + if (status == AMD_INVALID) + { + if (info) Info [AMD_STATUS] = AMD_INVALID ; + return (AMD_INVALID) ; /* matrix is invalid */ + } + + /* allocate two size-n integer workspaces */ + Len = amd_malloc (n * sizeof (Int)) ; + Pinv = amd_malloc (n * sizeof (Int)) ; + mem += n ; + mem += n ; + if (!Len || !Pinv) + { + /* :: out of memory :: */ + amd_free (Len) ; + amd_free (Pinv) ; + if (info) Info [AMD_STATUS] = AMD_OUT_OF_MEMORY ; + return (AMD_OUT_OF_MEMORY) ; + } + + if (status == AMD_OK_BUT_JUMBLED) + { + /* sort the input matrix and remove duplicate entries */ + AMD_DEBUG1 (("Matrix is jumbled\n")) ; + Rp = amd_malloc ((n+1) * sizeof (Int)) ; + Ri = amd_malloc (MAX (nz,1) * sizeof (Int)) ; + mem += (n+1) ; + mem += MAX (nz,1) ; + if (!Rp || !Ri) + { + /* :: out of memory :: */ + amd_free (Rp) ; + amd_free (Ri) ; + amd_free (Len) ; + amd_free (Pinv) ; + if (info) Info [AMD_STATUS] = AMD_OUT_OF_MEMORY ; + return (AMD_OUT_OF_MEMORY) ; + } + /* use Len and Pinv as workspace to create R = A' */ + AMD_preprocess (n, Ap, Ai, Rp, Ri, Len, Pinv) ; + Cp = Rp ; + Ci = Ri ; + } + else + { + /* order the input matrix as-is. No need to compute R = A' first */ + Rp = NULL ; + Ri = NULL ; + Cp = (Int *) Ap ; + Ci = (Int *) Ai ; + } + + /* --------------------------------------------------------------------- */ + /* determine the symmetry and count off-diagonal nonzeros in A+A' */ + /* --------------------------------------------------------------------- */ + + nzaat = AMD_aat (n, Cp, Ci, Len, P, Info) ; + AMD_DEBUG1 (("nzaat: %g\n", (double) nzaat)) ; + ASSERT ((MAX (nz-n, 0) <= nzaat) && (nzaat <= 2 * (size_t) nz)) ; + + /* --------------------------------------------------------------------- */ + /* allocate workspace for matrix, elbow room, and 6 size-n vectors */ + /* --------------------------------------------------------------------- */ + + S = NULL ; + slen = nzaat ; /* space for matrix */ + ok = ((slen + nzaat/5) >= slen) ; /* check for size_t overflow */ + slen += nzaat/5 ; /* add elbow room */ + for (i = 0 ; ok && i < 7 ; i++) + { + ok = ((slen + n) > slen) ; /* check for size_t overflow */ + slen += n ; /* size-n elbow room, 6 size-n work */ + } + mem += slen ; + ok = ok && (slen < SIZE_T_MAX / sizeof (Int)) ; /* check for overflow */ + ok = ok && (slen < Int_MAX) ; /* S[i] for Int i must be OK */ + if (ok) + { + S = amd_malloc (slen * sizeof (Int)) ; + } + AMD_DEBUG1 (("slen %g\n", (double) slen)) ; + if (!S) + { + /* :: out of memory :: (or problem too large) */ + amd_free (Rp) ; + amd_free (Ri) ; + amd_free (Len) ; + amd_free (Pinv) ; + if (info) Info [AMD_STATUS] = AMD_OUT_OF_MEMORY ; + return (AMD_OUT_OF_MEMORY) ; + } + if (info) + { + /* memory usage, in bytes. */ + Info [AMD_MEMORY] = mem * sizeof (Int) ; + } + + /* --------------------------------------------------------------------- */ + /* order the matrix */ + /* --------------------------------------------------------------------- */ + + AMD_1 (n, Cp, Ci, P, Pinv, Len, slen, S, Control, Info) ; + + /* --------------------------------------------------------------------- */ + /* free the workspace */ + /* --------------------------------------------------------------------- */ + + amd_free (Rp) ; + amd_free (Ri) ; + amd_free (Len) ; + amd_free (Pinv) ; + amd_free (S) ; + if (info) Info [AMD_STATUS] = status ; + return (status) ; /* successful ordering */ +} diff --git a/src/AMD/Source/amd_post_tree.c b/src/AMD/Source/amd_post_tree.c new file mode 100644 index 0000000..516c95c --- /dev/null +++ b/src/AMD/Source/amd_post_tree.c @@ -0,0 +1,120 @@ +/* ========================================================================= */ +/* === AMD_post_tree ======================================================= */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* Post-ordering of a supernodal elimination tree. */ + +#include "amd_internal.h" + +GLOBAL Int AMD_post_tree +( + Int root, /* root of the tree */ + Int k, /* start numbering at k */ + Int Child [ ], /* input argument of size nn, undefined on + * output. Child [i] is the head of a link + * list of all nodes that are children of node + * i in the tree. */ + const Int Sibling [ ], /* input argument of size nn, not modified. + * If f is a node in the link list of the + * children of node i, then Sibling [f] is the + * next child of node i. + */ + Int Order [ ], /* output order, of size nn. Order [i] = k + * if node i is the kth node of the reordered + * tree. */ + Int Stack [ ] /* workspace of size nn */ +#ifndef NDEBUG + , Int nn /* nodes are in the range 0..nn-1. */ +#endif +) +{ + Int f, head, h, i ; + +#if 0 + /* --------------------------------------------------------------------- */ + /* recursive version (Stack [ ] is not used): */ + /* --------------------------------------------------------------------- */ + + /* this is simple, but can caouse stack overflow if nn is large */ + i = root ; + for (f = Child [i] ; f != EMPTY ; f = Sibling [f]) + { + k = AMD_post_tree (f, k, Child, Sibling, Order, Stack, nn) ; + } + Order [i] = k++ ; + return (k) ; +#endif + + /* --------------------------------------------------------------------- */ + /* non-recursive version, using an explicit stack */ + /* --------------------------------------------------------------------- */ + + /* push root on the stack */ + head = 0 ; + Stack [0] = root ; + + while (head >= 0) + { + /* get head of stack */ + ASSERT (head < nn) ; + i = Stack [head] ; + AMD_DEBUG1 (("head of stack "ID" \n", i)) ; + ASSERT (i >= 0 && i < nn) ; + + if (Child [i] != EMPTY) + { + /* the children of i are not yet ordered */ + /* push each child onto the stack in reverse order */ + /* so that small ones at the head of the list get popped first */ + /* and the biggest one at the end of the list gets popped last */ + for (f = Child [i] ; f != EMPTY ; f = Sibling [f]) + { + head++ ; + ASSERT (head < nn) ; + ASSERT (f >= 0 && f < nn) ; + } + h = head ; + ASSERT (head < nn) ; + for (f = Child [i] ; f != EMPTY ; f = Sibling [f]) + { + ASSERT (h > 0) ; + Stack [h--] = f ; + AMD_DEBUG1 (("push "ID" on stack\n", f)) ; + ASSERT (f >= 0 && f < nn) ; + } + ASSERT (Stack [h] == i) ; + + /* delete child list so that i gets ordered next time we see it */ + Child [i] = EMPTY ; + } + else + { + /* the children of i (if there were any) are already ordered */ + /* remove i from the stack and order it. Front i is kth front */ + head-- ; + AMD_DEBUG1 (("pop "ID" order "ID"\n", i, k)) ; + Order [i] = k++ ; + ASSERT (k <= nn) ; + } + +#ifndef NDEBUG + AMD_DEBUG1 (("\nStack:")) ; + for (h = head ; h >= 0 ; h--) + { + Int j = Stack [h] ; + AMD_DEBUG1 ((" "ID, j)) ; + ASSERT (j >= 0 && j < nn) ; + } + AMD_DEBUG1 (("\n\n")) ; + ASSERT (head < nn) ; +#endif + + } + return (k) ; +} diff --git a/src/AMD/Source/amd_postorder.c b/src/AMD/Source/amd_postorder.c new file mode 100644 index 0000000..e5aea7b --- /dev/null +++ b/src/AMD/Source/amd_postorder.c @@ -0,0 +1,206 @@ +/* ========================================================================= */ +/* === AMD_postorder ======================================================= */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* Perform a postordering (via depth-first search) of an assembly tree. */ + +#include "amd_internal.h" + +GLOBAL void AMD_postorder +( + /* inputs, not modified on output: */ + Int nn, /* nodes are in the range 0..nn-1 */ + Int Parent [ ], /* Parent [j] is the parent of j, or EMPTY if root */ + Int Nv [ ], /* Nv [j] > 0 number of pivots represented by node j, + * or zero if j is not a node. */ + Int Fsize [ ], /* Fsize [j]: size of node j */ + + /* output, not defined on input: */ + Int Order [ ], /* output post-order */ + + /* workspaces of size nn: */ + Int Child [ ], + Int Sibling [ ], + Int Stack [ ] +) +{ + Int i, j, k, parent, frsize, f, fprev, maxfrsize, bigfprev, bigf, fnext ; + + for (j = 0 ; j < nn ; j++) + { + Child [j] = EMPTY ; + Sibling [j] = EMPTY ; + } + + /* --------------------------------------------------------------------- */ + /* place the children in link lists - bigger elements tend to be last */ + /* --------------------------------------------------------------------- */ + + for (j = nn-1 ; j >= 0 ; j--) + { + if (Nv [j] > 0) + { + /* this is an element */ + parent = Parent [j] ; + if (parent != EMPTY) + { + /* place the element in link list of the children its parent */ + /* bigger elements will tend to be at the end of the list */ + Sibling [j] = Child [parent] ; + Child [parent] = j ; + } + } + } + +#ifndef NDEBUG + { + Int nels, ff, nchild ; + AMD_DEBUG1 (("\n\n================================ AMD_postorder:\n")); + nels = 0 ; + for (j = 0 ; j < nn ; j++) + { + if (Nv [j] > 0) + { + AMD_DEBUG1 (( ""ID" : nels "ID" npiv "ID" size "ID + " parent "ID" maxfr "ID"\n", j, nels, + Nv [j], Fsize [j], Parent [j], Fsize [j])) ; + /* this is an element */ + /* dump the link list of children */ + nchild = 0 ; + AMD_DEBUG1 ((" Children: ")) ; + for (ff = Child [j] ; ff != EMPTY ; ff = Sibling [ff]) + { + AMD_DEBUG1 ((ID" ", ff)) ; + ASSERT (Parent [ff] == j) ; + nchild++ ; + ASSERT (nchild < nn) ; + } + AMD_DEBUG1 (("\n")) ; + parent = Parent [j] ; + if (parent != EMPTY) + { + ASSERT (Nv [parent] > 0) ; + } + nels++ ; + } + } + } + AMD_DEBUG1 (("\n\nGo through the children of each node, and put\n" + "the biggest child last in each list:\n")) ; +#endif + + /* --------------------------------------------------------------------- */ + /* place the largest child last in the list of children for each node */ + /* --------------------------------------------------------------------- */ + + for (i = 0 ; i < nn ; i++) + { + if (Nv [i] > 0 && Child [i] != EMPTY) + { + +#ifndef NDEBUG + Int nchild ; + AMD_DEBUG1 (("Before partial sort, element "ID"\n", i)) ; + nchild = 0 ; + for (f = Child [i] ; f != EMPTY ; f = Sibling [f]) + { + ASSERT (f >= 0 && f < nn) ; + AMD_DEBUG1 ((" f: "ID" size: "ID"\n", f, Fsize [f])) ; + nchild++ ; + ASSERT (nchild <= nn) ; + } +#endif + + /* find the biggest element in the child list */ + fprev = EMPTY ; + maxfrsize = EMPTY ; + bigfprev = EMPTY ; + bigf = EMPTY ; + for (f = Child [i] ; f != EMPTY ; f = Sibling [f]) + { + ASSERT (f >= 0 && f < nn) ; + frsize = Fsize [f] ; + if (frsize >= maxfrsize) + { + /* this is the biggest seen so far */ + maxfrsize = frsize ; + bigfprev = fprev ; + bigf = f ; + } + fprev = f ; + } + ASSERT (bigf != EMPTY) ; + + fnext = Sibling [bigf] ; + + AMD_DEBUG1 (("bigf "ID" maxfrsize "ID" bigfprev "ID" fnext "ID + " fprev " ID"\n", bigf, maxfrsize, bigfprev, fnext, fprev)) ; + + if (fnext != EMPTY) + { + /* if fnext is EMPTY then bigf is already at the end of list */ + + if (bigfprev == EMPTY) + { + /* delete bigf from the element of the list */ + Child [i] = fnext ; + } + else + { + /* delete bigf from the middle of the list */ + Sibling [bigfprev] = fnext ; + } + + /* put bigf at the end of the list */ + Sibling [bigf] = EMPTY ; + ASSERT (Child [i] != EMPTY) ; + ASSERT (fprev != bigf) ; + ASSERT (fprev != EMPTY) ; + Sibling [fprev] = bigf ; + } + +#ifndef NDEBUG + AMD_DEBUG1 (("After partial sort, element "ID"\n", i)) ; + for (f = Child [i] ; f != EMPTY ; f = Sibling [f]) + { + ASSERT (f >= 0 && f < nn) ; + AMD_DEBUG1 ((" "ID" "ID"\n", f, Fsize [f])) ; + ASSERT (Nv [f] > 0) ; + nchild-- ; + } + ASSERT (nchild == 0) ; +#endif + + } + } + + /* --------------------------------------------------------------------- */ + /* postorder the assembly tree */ + /* --------------------------------------------------------------------- */ + + for (i = 0 ; i < nn ; i++) + { + Order [i] = EMPTY ; + } + + k = 0 ; + + for (i = 0 ; i < nn ; i++) + { + if (Parent [i] == EMPTY && Nv [i] > 0) + { + AMD_DEBUG1 (("Root of assembly tree "ID"\n", i)) ; + k = AMD_post_tree (i, k, Child, Sibling, Order, Stack +#ifndef NDEBUG + , nn +#endif + ) ; + } + } +} diff --git a/src/AMD/Source/amd_preprocess.c b/src/AMD/Source/amd_preprocess.c new file mode 100644 index 0000000..a8139c3 --- /dev/null +++ b/src/AMD/Source/amd_preprocess.c @@ -0,0 +1,118 @@ +/* ========================================================================= */ +/* === AMD_preprocess ====================================================== */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* Sorts, removes duplicate entries, and transposes from the nonzero pattern of + * a column-form matrix A, to obtain the matrix R. The input matrix can have + * duplicate entries and/or unsorted columns (AMD_valid (n,Ap,Ai) must not be + * AMD_INVALID). + * + * This input condition is NOT checked. This routine is not user-callable. + */ + +#include "amd_internal.h" + +/* ========================================================================= */ +/* === AMD_preprocess ====================================================== */ +/* ========================================================================= */ + +/* AMD_preprocess does not check its input for errors or allocate workspace. + * On input, the condition (AMD_valid (n,n,Ap,Ai) != AMD_INVALID) must hold. + */ + +GLOBAL void AMD_preprocess +( + Int n, /* input matrix: A is n-by-n */ + const Int Ap [ ], /* size n+1 */ + const Int Ai [ ], /* size nz = Ap [n] */ + + /* output matrix R: */ + Int Rp [ ], /* size n+1 */ + Int Ri [ ], /* size nz (or less, if duplicates present) */ + + Int W [ ], /* workspace of size n */ + Int Flag [ ] /* workspace of size n */ +) +{ + + /* --------------------------------------------------------------------- */ + /* local variables */ + /* --------------------------------------------------------------------- */ + + Int i, j, p, p2 ; + + ASSERT (AMD_valid (n, n, Ap, Ai) != AMD_INVALID) ; + + /* --------------------------------------------------------------------- */ + /* count the entries in each row of A (excluding duplicates) */ + /* --------------------------------------------------------------------- */ + + for (i = 0 ; i < n ; i++) + { + W [i] = 0 ; /* # of nonzeros in row i (excl duplicates) */ + Flag [i] = EMPTY ; /* Flag [i] = j if i appears in column j */ + } + for (j = 0 ; j < n ; j++) + { + p2 = Ap [j+1] ; + for (p = Ap [j] ; p < p2 ; p++) + { + i = Ai [p] ; + if (Flag [i] != j) + { + /* row index i has not yet appeared in column j */ + W [i]++ ; /* one more entry in row i */ + Flag [i] = j ; /* flag row index i as appearing in col j*/ + } + } + } + + /* --------------------------------------------------------------------- */ + /* compute the row pointers for R */ + /* --------------------------------------------------------------------- */ + + Rp [0] = 0 ; + for (i = 0 ; i < n ; i++) + { + Rp [i+1] = Rp [i] + W [i] ; + } + for (i = 0 ; i < n ; i++) + { + W [i] = Rp [i] ; + Flag [i] = EMPTY ; + } + + /* --------------------------------------------------------------------- */ + /* construct the row form matrix R */ + /* --------------------------------------------------------------------- */ + + /* R = row form of pattern of A */ + for (j = 0 ; j < n ; j++) + { + p2 = Ap [j+1] ; + for (p = Ap [j] ; p < p2 ; p++) + { + i = Ai [p] ; + if (Flag [i] != j) + { + /* row index i has not yet appeared in column j */ + Ri [W [i]++] = j ; /* put col j in row i */ + Flag [i] = j ; /* flag row index i as appearing in col j*/ + } + } + } + +#ifndef NDEBUG + ASSERT (AMD_valid (n, n, Rp, Ri) == AMD_OK) ; + for (j = 0 ; j < n ; j++) + { + ASSERT (W [j] == Rp [j+1]) ; + } +#endif +} diff --git a/src/AMD/Source/amd_valid.c b/src/AMD/Source/amd_valid.c new file mode 100644 index 0000000..609abca --- /dev/null +++ b/src/AMD/Source/amd_valid.c @@ -0,0 +1,92 @@ +/* ========================================================================= */ +/* === AMD_valid =========================================================== */ +/* ========================================================================= */ + +/* ------------------------------------------------------------------------- */ +/* AMD, Copyright (c) Timothy A. Davis, */ +/* Patrick R. Amestoy, and Iain S. Duff. See ../README.txt for License. */ +/* email: DrTimothyAldenDavis@gmail.com */ +/* ------------------------------------------------------------------------- */ + +/* Check if a column-form matrix is valid or not. The matrix A is + * n_row-by-n_col. The row indices of entries in column j are in + * Ai [Ap [j] ... Ap [j+1]-1]. Required conditions are: + * + * n_row >= 0 + * n_col >= 0 + * nz = Ap [n_col] >= 0 number of entries in the matrix + * Ap [0] == 0 + * Ap [j] <= Ap [j+1] for all j in the range 0 to n_col. + * Ai [0 ... nz-1] must be in the range 0 to n_row-1. + * + * If any of the above conditions hold, AMD_INVALID is returned. If the + * following condition holds, AMD_OK_BUT_JUMBLED is returned (a warning, + * not an error): + * + * row indices in Ai [Ap [j] ... Ap [j+1]-1] are not sorted in ascending + * order, and/or duplicate entries exist. + * + * Otherwise, AMD_OK is returned. + * + * In v1.2 and earlier, this function returned TRUE if the matrix was valid + * (now returns AMD_OK), or FALSE otherwise (now returns AMD_INVALID or + * AMD_OK_BUT_JUMBLED). + */ + +#include "amd_internal.h" + +GLOBAL Int AMD_valid +( + /* inputs, not modified on output: */ + Int n_row, /* A is n_row-by-n_col */ + Int n_col, + const Int Ap [ ], /* column pointers of A, of size n_col+1 */ + const Int Ai [ ] /* row indices of A, of size nz = Ap [n_col] */ +) +{ + Int nz, j, p1, p2, ilast, i, p, result = AMD_OK ; + + if (n_row < 0 || n_col < 0 || Ap == NULL || Ai == NULL) + { + return (AMD_INVALID) ; + } + nz = Ap [n_col] ; + if (Ap [0] != 0 || nz < 0) + { + /* column pointers must start at Ap [0] = 0, and Ap [n] must be >= 0 */ + AMD_DEBUG0 (("column 0 pointer bad or nz < 0\n")) ; + return (AMD_INVALID) ; + } + for (j = 0 ; j < n_col ; j++) + { + p1 = Ap [j] ; + p2 = Ap [j+1] ; + AMD_DEBUG2 (("\nColumn: "ID" p1: "ID" p2: "ID"\n", j, p1, p2)) ; + if (p1 > p2) + { + /* column pointers must be ascending */ + AMD_DEBUG0 (("column "ID" pointer bad\n", j)) ; + return (AMD_INVALID) ; + } + ilast = EMPTY ; + for (p = p1 ; p < p2 ; p++) + { + i = Ai [p] ; + AMD_DEBUG3 (("row: "ID"\n", i)) ; + if (i < 0 || i >= n_row) + { + /* row index out of range */ + AMD_DEBUG0 (("index out of range, col "ID" row "ID"\n", j, i)); + return (AMD_INVALID) ; + } + if (i <= ilast) + { + /* row index unsorted, or duplicate entry present */ + AMD_DEBUG1 (("index unsorted/dupl col "ID" row "ID"\n", j, i)); + result = AMD_OK_BUT_JUMBLED ; + } + ilast = i ; + } + } + return (result) ; +} diff --git a/src/AMD/Source/amdbar.f b/src/AMD/Source/amdbar.f new file mode 100644 index 0000000..1384392 --- /dev/null +++ b/src/AMD/Source/amdbar.f @@ -0,0 +1,1206 @@ +C----------------------------------------------------------------------- +C AMDBAR: approximate minimum degree, without aggressive absorption +C----------------------------------------------------------------------- + + SUBROUTINE AMDBAR + $ (N, PE, IW, LEN, IWLEN, PFREE, NV, NEXT, + $ LAST, HEAD, ELEN, DEGREE, NCMPA, W) + + INTEGER N, IWLEN, PFREE, NCMPA, IW (IWLEN), PE (N), + $ DEGREE (N), NV (N), NEXT (N), LAST (N), HEAD (N), + $ ELEN (N), W (N), LEN (N) + +C Given a representation of the nonzero pattern of a symmetric matrix, +C A, (excluding the diagonal) perform an approximate minimum +C (UMFPACK/MA38-style) degree ordering to compute a pivot order +C such that the introduction of nonzeros (fill-in) in the Cholesky +C factors A = LL^T are kept low. At each step, the pivot +C selected is the one with the minimum UMFPACK/MA38-style +C upper-bound on the external degree. +C +C This routine does not do aggresive absorption (as done by AMD). + +C ********************************************************************** +C ***** CAUTION: ARGUMENTS ARE NOT CHECKED FOR ERRORS ON INPUT. ****** +C ********************************************************************** + +C References: +C +C [1] Timothy A. Davis and Iain Duff, "An unsymmetric-pattern +C multifrontal method for sparse LU factorization", SIAM J. +C Matrix Analysis and Applications, vol. 18, no. 1, pp. +C 140-158. Discusses UMFPACK / MA38, which first introduced +C the approximate minimum degree used by this routine. +C +C [2] Patrick Amestoy, Timothy A. Davis, and Iain S. Duff, "An +C approximate degree ordering algorithm," SIAM J. Matrix +C Analysis and Applications, vol. 17, no. 4, pp. 886-905, +C 1996. Discusses AMD, AMDBAR, and MC47B. +C +C [3] Alan George and Joseph Liu, "The evolution of the minimum +C degree ordering algorithm," SIAM Review, vol. 31, no. 1, +C pp. 1-19, 1989. We list below the features mentioned in +C that paper that this code includes: +C +C mass elimination: +C Yes. MA27 relied on supervariable detection for mass +C elimination. +C indistinguishable nodes: +C Yes (we call these "supervariables"). This was also in +C the MA27 code - although we modified the method of +C detecting them (the previous hash was the true degree, +C which we no longer keep track of). A supervariable is +C a set of rows with identical nonzero pattern. All +C variables in a supervariable are eliminated together. +C Each supervariable has as its numerical name that of +C one of its variables (its principal variable). +C quotient graph representation: +C Yes. We use the term "element" for the cliques formed +C during elimination. This was also in the MA27 code. +C The algorithm can operate in place, but it will work +C more efficiently if given some "elbow room." +C element absorption: +C Yes. This was also in the MA27 code. +C external degree: +C Yes. The MA27 code was based on the true degree. +C incomplete degree update and multiple elimination: +C No. This was not in MA27, either. Our method of +C degree update within MC47B/BD is element-based, not +C variable-based. It is thus not well-suited for use +C with incomplete degree update or multiple elimination. + +C----------------------------------------------------------------------- +C Authors, and Copyright (C) 1995 by: +C Timothy A. Davis, Patrick Amestoy, Iain S. Duff, & John K. Reid. +C +C Acknowledgements: +C This work (and the UMFPACK package) was supported by the +C National Science Foundation (ASC-9111263 and DMS-9223088). +C The UMFPACK/MA38 approximate degree update algorithm, the +C unsymmetric analog which forms the basis of MC47B/BD, was +C developed while Tim Davis was supported by CERFACS (Toulouse, +C France) in a post-doctoral position. +C +C Date: September, 1995 +C----------------------------------------------------------------------- + +C----------------------------------------------------------------------- +C INPUT ARGUMENTS (unaltered): +C----------------------------------------------------------------------- + +C n: The matrix order. +C +C Restriction: 1 .le. n .lt. (iovflo/2)-2, where iovflo is +C the largest positive integer that your computer can represent. + +C iwlen: The length of iw (1..iwlen). On input, the matrix is +C stored in iw (1..pfree-1). However, iw (1..iwlen) should be +C slightly larger than what is required to hold the matrix, at +C least iwlen .ge. pfree + n is recommended. Otherwise, +C excessive compressions will take place. +C *** We do not recommend running this algorithm with *** +C *** iwlen .lt. pfree + n. *** +C *** Better performance will be obtained if *** +C *** iwlen .ge. pfree + n *** +C *** or better yet *** +C *** iwlen .gt. 1.2 * pfree *** +C *** (where pfree is its value on input). *** +C The algorithm will not run at all if iwlen .lt. pfree-1. +C +C Restriction: iwlen .ge. pfree-1 + +C----------------------------------------------------------------------- +C INPUT/OUPUT ARGUMENTS: +C----------------------------------------------------------------------- + +C pe: On input, pe (i) is the index in iw of the start of row i, or +C zero if row i has no off-diagonal non-zeros. +C +C During execution, it is used for both supervariables and +C elements: +C +C * Principal supervariable i: index into iw of the +C description of supervariable i. A supervariable +C represents one or more rows of the matrix +C with identical nonzero pattern. +C * Non-principal supervariable i: if i has been absorbed +C into another supervariable j, then pe (i) = -j. +C That is, j has the same pattern as i. +C Note that j might later be absorbed into another +C supervariable j2, in which case pe (i) is still -j, +C and pe (j) = -j2. +C * Unabsorbed element e: the index into iw of the description +C of element e, if e has not yet been absorbed by a +C subsequent element. Element e is created when +C the supervariable of the same name is selected as +C the pivot. +C * Absorbed element e: if element e is absorbed into element +C e2, then pe (e) = -e2. This occurs when the pattern of +C e (that is, Le) is found to be a subset of the pattern +C of e2 (that is, Le2). If element e is "null" (it has +C no nonzeros outside its pivot block), then pe (e) = 0. +C +C On output, pe holds the assembly tree/forest, which implicitly +C represents a pivot order with identical fill-in as the actual +C order (via a depth-first search of the tree). +C +C On output: +C If nv (i) .gt. 0, then i represents a node in the assembly tree, +C and the parent of i is -pe (i), or zero if i is a root. +C If nv (i) = 0, then (i,-pe (i)) represents an edge in a +C subtree, the root of which is a node in the assembly tree. + +C pfree: On input the tail end of the array, iw (pfree..iwlen), +C is empty, and the matrix is stored in iw (1..pfree-1). +C During execution, additional data is placed in iw, and pfree +C is modified so that iw (pfree..iwlen) is always the unused part +C of iw. On output, pfree is set equal to the size of iw that +C would have been needed for no compressions to occur. If +C ncmpa is zero, then pfree (on output) is less than or equal to +C iwlen, and the space iw (pfree+1 ... iwlen) was not used. +C Otherwise, pfree (on output) is greater than iwlen, and all the +C memory in iw was used. + +C----------------------------------------------------------------------- +C INPUT/MODIFIED (undefined on output): +C----------------------------------------------------------------------- + +C len: On input, len (i) holds the number of entries in row i of the +C matrix, excluding the diagonal. The contents of len (1..n) +C are undefined on output. + +C iw: On input, iw (1..pfree-1) holds the description of each row i +C in the matrix. The matrix must be symmetric, and both upper +C and lower triangular parts must be present. The diagonal must +C not be present. Row i is held as follows: +C +C len (i): the length of the row i data structure +C iw (pe (i) ... pe (i) + len (i) - 1): +C the list of column indices for nonzeros +C in row i (simple supervariables), excluding +C the diagonal. All supervariables start with +C one row/column each (supervariable i is just +C row i). +C if len (i) is zero on input, then pe (i) is ignored +C on input. +C +C Note that the rows need not be in any particular order, +C and there may be empty space between the rows. +C +C During execution, the supervariable i experiences fill-in. +C This is represented by placing in i a list of the elements +C that cause fill-in in supervariable i: +C +C len (i): the length of supervariable i +C iw (pe (i) ... pe (i) + elen (i) - 1): +C the list of elements that contain i. This list +C is kept short by removing absorbed elements. +C iw (pe (i) + elen (i) ... pe (i) + len (i) - 1): +C the list of supervariables in i. This list +C is kept short by removing nonprincipal +C variables, and any entry j that is also +C contained in at least one of the elements +C (j in Le) in the list for i (e in row i). +C +C When supervariable i is selected as pivot, we create an +C element e of the same name (e=i): +C +C len (e): the length of element e +C iw (pe (e) ... pe (e) + len (e) - 1): +C the list of supervariables in element e. +C +C An element represents the fill-in that occurs when supervariable +C i is selected as pivot (which represents the selection of row i +C and all non-principal variables whose principal variable is i). +C We use the term Le to denote the set of all supervariables +C in element e. Absorbed supervariables and elements are pruned +C from these lists when computationally convenient. +C +C CAUTION: THE INPUT MATRIX IS OVERWRITTEN DURING COMPUTATION. +C The contents of iw are undefined on output. + +C----------------------------------------------------------------------- +C OUTPUT (need not be set on input): +C----------------------------------------------------------------------- + +C nv: During execution, abs (nv (i)) is equal to the number of rows +C that are represented by the principal supervariable i. If i is +C a nonprincipal variable, then nv (i) = 0. Initially, +C nv (i) = 1 for all i. nv (i) .lt. 0 signifies that i is a +C principal variable in the pattern Lme of the current pivot +C element me. On output, nv (e) holds the true degree of element +C e at the time it was created (including the diagonal part). + +C ncmpa: The number of times iw was compressed. If this is +C excessive, then the execution took longer than what could have +C been. To reduce ncmpa, try increasing iwlen to be 10% or 20% +C larger than the value of pfree on input (or at least +C iwlen .ge. pfree + n). The fastest performance will be +C obtained when ncmpa is returned as zero. If iwlen is set to +C the value returned by pfree on *output*, then no compressions +C will occur. + +C elen: See the description of iw above. At the start of execution, +C elen (i) is set to zero. During execution, elen (i) is the +C number of elements in the list for supervariable i. When e +C becomes an element, elen (e) = -nel is set, where nel is the +C current step of factorization. elen (i) = 0 is done when i +C becomes nonprincipal. +C +C For variables, elen (i) .ge. 0 holds until just before the +C permutation vectors are computed. For elements, +C elen (e) .lt. 0 holds. +C +C On output elen (1..n) holds the inverse permutation (the same +C as the 'INVP' argument in Sparspak). That is, if k = elen (i), +C then row i is the kth pivot row. Row i of A appears as the +C (elen(i))-th row in the permuted matrix, PAP^T. + +C last: In a degree list, last (i) is the supervariable preceding i, +C or zero if i is the head of the list. In a hash bucket, +C last (i) is the hash key for i. last (head (hash)) is also +C used as the head of a hash bucket if head (hash) contains a +C degree list (see head, below). +C +C On output, last (1..n) holds the permutation (the same as the +C 'PERM' argument in Sparspak). That is, if i = last (k), then +C row i is the kth pivot row. Row last (k) of A is the k-th row +C in the permuted matrix, PAP^T. + +C----------------------------------------------------------------------- +C LOCAL (not input or output - used only during execution): +C----------------------------------------------------------------------- + +C degree: If i is a supervariable, then degree (i) holds the +C current approximation of the external degree of row i (an upper +C bound). The external degree is the number of nonzeros in row i, +C minus abs (nv (i)) (the diagonal part). The bound is equal to +C the external degree if elen (i) is less than or equal to two. +C +C We also use the term "external degree" for elements e to refer +C to |Le \ Lme|. If e is an element, then degree (e) holds |Le|, +C which is the degree of the off-diagonal part of the element e +C (not including the diagonal part). + +C head: head is used for degree lists. head (deg) is the first +C supervariable in a degree list (all supervariables i in a +C degree list deg have the same approximate degree, namely, +C deg = degree (i)). If the list deg is empty then +C head (deg) = 0. +C +C During supervariable detection head (hash) also serves as a +C pointer to a hash bucket. +C If head (hash) .gt. 0, there is a degree list of degree hash. +C The hash bucket head pointer is last (head (hash)). +C If head (hash) = 0, then the degree list and hash bucket are +C both empty. +C If head (hash) .lt. 0, then the degree list is empty, and +C -head (hash) is the head of the hash bucket. +C After supervariable detection is complete, all hash buckets +C are empty, and the (last (head (hash)) = 0) condition is +C restored for the non-empty degree lists. + +C next: next (i) is the supervariable following i in a link list, or +C zero if i is the last in the list. Used for two kinds of +C lists: degree lists and hash buckets (a supervariable can be +C in only one kind of list at a time). + +C w: The flag array w determines the status of elements and +C variables, and the external degree of elements. +C +C for elements: +C if w (e) = 0, then the element e is absorbed +C if w (e) .ge. wflg, then w (e) - wflg is the size of +C the set |Le \ Lme|, in terms of nonzeros (the +C sum of abs (nv (i)) for each principal variable i that +C is both in the pattern of element e and NOT in the +C pattern of the current pivot element, me). +C if wflg .gt. w (e) .gt. 0, then e is not absorbed and has +C not yet been seen in the scan of the element lists in +C the computation of |Le\Lme| in loop 150 below. +C +C for variables: +C during supervariable detection, if w (j) .ne. wflg then j is +C not in the pattern of variable i +C +C The w array is initialized by setting w (i) = 1 for all i, +C and by setting wflg = 2. It is reinitialized if wflg becomes +C too large (to ensure that wflg+n does not cause integer +C overflow). + +C----------------------------------------------------------------------- +C LOCAL INTEGERS: +C----------------------------------------------------------------------- + + INTEGER DEG, DEGME, DMAX, E, ELENME, ELN, HASH, HMOD, I, + $ ILAST, INEXT, J, JLAST, JNEXT, K, KNT1, KNT2, KNT3, + $ LENJ, LN, MAXMEM, ME, MEM, MINDEG, NEL, NEWMEM, + $ NLEFT, NVI, NVJ, NVPIV, SLENME, WE, WFLG, WNVI, X + +C deg: the degree of a variable or element +C degme: size, |Lme|, of the current element, me (= degree (me)) +C dext: external degree, |Le \ Lme|, of some element e +C dmax: largest |Le| seen so far +C e: an element +C elenme: the length, elen (me), of element list of pivotal var. +C eln: the length, elen (...), of an element list +C hash: the computed value of the hash function +C hmod: the hash function is computed modulo hmod = max (1,n-1) +C i: a supervariable +C ilast: the entry in a link list preceding i +C inext: the entry in a link list following i +C j: a supervariable +C jlast: the entry in a link list preceding j +C jnext: the entry in a link list, or path, following j +C k: the pivot order of an element or variable +C knt1: loop counter used during element construction +C knt2: loop counter used during element construction +C knt3: loop counter used during compression +C lenj: len (j) +C ln: length of a supervariable list +C maxmem: amount of memory needed for no compressions +C me: current supervariable being eliminated, and the +C current element created by eliminating that +C supervariable +C mem: memory in use assuming no compressions have occurred +C mindeg: current minimum degree +C nel: number of pivots selected so far +C newmem: amount of new memory needed for current pivot element +C nleft: n - nel, the number of nonpivotal rows/columns remaining +C nvi: the number of variables in a supervariable i (= nv (i)) +C nvj: the number of variables in a supervariable j (= nv (j)) +C nvpiv: number of pivots in current element +C slenme: number of variables in variable list of pivotal variable +C we: w (e) +C wflg: used for flagging the w array. See description of iw. +C wnvi: wflg - nv (i) +C x: either a supervariable or an element + +C----------------------------------------------------------------------- +C LOCAL POINTERS: +C----------------------------------------------------------------------- + + INTEGER P, P1, P2, P3, PDST, PEND, PJ, PME, PME1, PME2, PN, PSRC + +C Any parameter (pe (...) or pfree) or local variable +C starting with "p" (for Pointer) is an index into iw, +C and all indices into iw use variables starting with +C "p." The only exception to this rule is the iwlen +C input argument. + +C p: pointer into lots of things +C p1: pe (i) for some variable i (start of element list) +C p2: pe (i) + elen (i) - 1 for some var. i (end of el. list) +C p3: index of first supervariable in clean list +C pdst: destination pointer, for compression +C pend: end of memory to compress +C pj: pointer into an element or variable +C pme: pointer into the current element (pme1...pme2) +C pme1: the current element, me, is stored in iw (pme1...pme2) +C pme2: the end of the current element +C pn: pointer into a "clean" variable, also used to compress +C psrc: source pointer, for compression + +C----------------------------------------------------------------------- +C FUNCTIONS CALLED: +C----------------------------------------------------------------------- + + INTRINSIC MAX, MIN, MOD + +C======================================================================= +C INITIALIZATIONS +C======================================================================= + + WFLG = 2 + MINDEG = 1 + NCMPA = 0 + NEL = 0 + HMOD = MAX (1, N-1) + DMAX = 0 + MEM = PFREE - 1 + MAXMEM = MEM + ME = 0 + + DO 10 I = 1, N + LAST (I) = 0 + HEAD (I) = 0 + NV (I) = 1 + W (I) = 1 + ELEN (I) = 0 + DEGREE (I) = LEN (I) +10 CONTINUE + +C ---------------------------------------------------------------- +C initialize degree lists and eliminate rows with no off-diag. nz. +C ---------------------------------------------------------------- + + DO 20 I = 1, N + + DEG = DEGREE (I) + + IF (DEG .GT. 0) THEN + +C ---------------------------------------------------------- +C place i in the degree list corresponding to its degree +C ---------------------------------------------------------- + + INEXT = HEAD (DEG) + IF (INEXT .NE. 0) LAST (INEXT) = I + NEXT (I) = INEXT + HEAD (DEG) = I + + ELSE + +C ---------------------------------------------------------- +C we have a variable that can be eliminated at once because +C there is no off-diagonal non-zero in its row. +C ---------------------------------------------------------- + + NEL = NEL + 1 + ELEN (I) = -NEL + PE (I) = 0 + W (I) = 0 + + ENDIF + +20 CONTINUE + +C======================================================================= +C WHILE (selecting pivots) DO +C======================================================================= + +30 CONTINUE + IF (NEL .LT. N) THEN + +C======================================================================= +C GET PIVOT OF MINIMUM DEGREE +C======================================================================= + +C ------------------------------------------------------------- +C find next supervariable for elimination +C ------------------------------------------------------------- + + DO 40 DEG = MINDEG, N + ME = HEAD (DEG) + IF (ME .GT. 0) GOTO 50 +40 CONTINUE +50 CONTINUE + MINDEG = DEG + +C ------------------------------------------------------------- +C remove chosen variable from link list +C ------------------------------------------------------------- + + INEXT = NEXT (ME) + IF (INEXT .NE. 0) LAST (INEXT) = 0 + HEAD (DEG) = INEXT + +C ------------------------------------------------------------- +C me represents the elimination of pivots nel+1 to nel+nv(me). +C place me itself as the first in this set. It will be moved +C to the nel+nv(me) position when the permutation vectors are +C computed. +C ------------------------------------------------------------- + + ELENME = ELEN (ME) + ELEN (ME) = - (NEL + 1) + NVPIV = NV (ME) + NEL = NEL + NVPIV + +C======================================================================= +C CONSTRUCT NEW ELEMENT +C======================================================================= + +C ------------------------------------------------------------- +C At this point, me is the pivotal supervariable. It will be +C converted into the current element. Scan list of the +C pivotal supervariable, me, setting tree pointers and +C constructing new list of supervariables for the new element, +C me. p is a pointer to the current position in the old list. +C ------------------------------------------------------------- + +C flag the variable "me" as being in Lme by negating nv (me) + NV (ME) = -NVPIV + DEGME = 0 + + IF (ELENME .EQ. 0) THEN + +C ---------------------------------------------------------- +C construct the new element in place +C ---------------------------------------------------------- + + PME1 = PE (ME) + PME2 = PME1 - 1 + + DO 60 P = PME1, PME1 + LEN (ME) - 1 + I = IW (P) + NVI = NV (I) + IF (NVI .GT. 0) THEN + +C ---------------------------------------------------- +C i is a principal variable not yet placed in Lme. +C store i in new list +C ---------------------------------------------------- + + DEGME = DEGME + NVI +C flag i as being in Lme by negating nv (i) + NV (I) = -NVI + PME2 = PME2 + 1 + IW (PME2) = I + +C ---------------------------------------------------- +C remove variable i from degree list. +C ---------------------------------------------------- + + ILAST = LAST (I) + INEXT = NEXT (I) + IF (INEXT .NE. 0) LAST (INEXT) = ILAST + IF (ILAST .NE. 0) THEN + NEXT (ILAST) = INEXT + ELSE +C i is at the head of the degree list + HEAD (DEGREE (I)) = INEXT + ENDIF + + ENDIF +60 CONTINUE +C this element takes no new memory in iw: + NEWMEM = 0 + + ELSE + +C ---------------------------------------------------------- +C construct the new element in empty space, iw (pfree ...) +C ---------------------------------------------------------- + + P = PE (ME) + PME1 = PFREE + SLENME = LEN (ME) - ELENME + + DO 120 KNT1 = 1, ELENME + 1 + + IF (KNT1 .GT. ELENME) THEN +C search the supervariables in me. + E = ME + PJ = P + LN = SLENME + ELSE +C search the elements in me. + E = IW (P) + P = P + 1 + PJ = PE (E) + LN = LEN (E) + ENDIF + +C ------------------------------------------------------- +C search for different supervariables and add them to the +C new list, compressing when necessary. this loop is +C executed once for each element in the list and once for +C all the supervariables in the list. +C ------------------------------------------------------- + + DO 110 KNT2 = 1, LN + I = IW (PJ) + PJ = PJ + 1 + NVI = NV (I) + IF (NVI .GT. 0) THEN + +C ------------------------------------------------- +C compress iw, if necessary +C ------------------------------------------------- + + IF (PFREE .GT. IWLEN) THEN +C prepare for compressing iw by adjusting +C pointers and lengths so that the lists being +C searched in the inner and outer loops contain +C only the remaining entries. + + PE (ME) = P + LEN (ME) = LEN (ME) - KNT1 + IF (LEN (ME) .EQ. 0) THEN +C nothing left of supervariable me + PE (ME) = 0 + ENDIF + PE (E) = PJ + LEN (E) = LN - KNT2 + IF (LEN (E) .EQ. 0) THEN +C nothing left of element e + PE (E) = 0 + ENDIF + + NCMPA = NCMPA + 1 +C store first item in pe +C set first entry to -item + DO 70 J = 1, N + PN = PE (J) + IF (PN .GT. 0) THEN + PE (J) = IW (PN) + IW (PN) = -J + ENDIF +70 CONTINUE + +C psrc/pdst point to source/destination + PDST = 1 + PSRC = 1 + PEND = PME1 - 1 + +C while loop: +80 CONTINUE + IF (PSRC .LE. PEND) THEN +C search for next negative entry + J = -IW (PSRC) + PSRC = PSRC + 1 + IF (J .GT. 0) THEN + IW (PDST) = PE (J) + PE (J) = PDST + PDST = PDST + 1 +C copy from source to destination + LENJ = LEN (J) + DO 90 KNT3 = 0, LENJ - 2 + IW (PDST + KNT3) = IW (PSRC + KNT3) +90 CONTINUE + PDST = PDST + LENJ - 1 + PSRC = PSRC + LENJ - 1 + ENDIF + GOTO 80 + ENDIF + +C move the new partially-constructed element + P1 = PDST + DO 100 PSRC = PME1, PFREE - 1 + IW (PDST) = IW (PSRC) + PDST = PDST + 1 +100 CONTINUE + PME1 = P1 + PFREE = PDST + PJ = PE (E) + P = PE (ME) + ENDIF + +C ------------------------------------------------- +C i is a principal variable not yet placed in Lme +C store i in new list +C ------------------------------------------------- + + DEGME = DEGME + NVI +C flag i as being in Lme by negating nv (i) + NV (I) = -NVI + IW (PFREE) = I + PFREE = PFREE + 1 + +C ------------------------------------------------- +C remove variable i from degree link list +C ------------------------------------------------- + + ILAST = LAST (I) + INEXT = NEXT (I) + IF (INEXT .NE. 0) LAST (INEXT) = ILAST + IF (ILAST .NE. 0) THEN + NEXT (ILAST) = INEXT + ELSE +C i is at the head of the degree list + HEAD (DEGREE (I)) = INEXT + ENDIF + + ENDIF +110 CONTINUE + + IF (E .NE. ME) THEN +C set tree pointer and flag to indicate element e is +C absorbed into new element me (the parent of e is me) + PE (E) = -ME + W (E) = 0 + ENDIF +120 CONTINUE + + PME2 = PFREE - 1 +C this element takes newmem new memory in iw (possibly zero) + NEWMEM = PFREE - PME1 + MEM = MEM + NEWMEM + MAXMEM = MAX (MAXMEM, MEM) + ENDIF + +C ------------------------------------------------------------- +C me has now been converted into an element in iw (pme1..pme2) +C ------------------------------------------------------------- + +C degme holds the external degree of new element + DEGREE (ME) = DEGME + PE (ME) = PME1 + LEN (ME) = PME2 - PME1 + 1 + +C ------------------------------------------------------------- +C make sure that wflg is not too large. With the current +C value of wflg, wflg+n must not cause integer overflow +C ------------------------------------------------------------- + + IF (WFLG + N .LE. WFLG) THEN + DO 130 X = 1, N + IF (W (X) .NE. 0) W (X) = 1 +130 CONTINUE + WFLG = 2 + ENDIF + +C======================================================================= +C COMPUTE (w (e) - wflg) = |Le\Lme| FOR ALL ELEMENTS +C======================================================================= + +C ------------------------------------------------------------- +C Scan 1: compute the external degrees of previous elements +C with respect to the current element. That is: +C (w (e) - wflg) = |Le \ Lme| +C for each element e that appears in any supervariable in Lme. +C The notation Le refers to the pattern (list of +C supervariables) of a previous element e, where e is not yet +C absorbed, stored in iw (pe (e) + 1 ... pe (e) + iw (pe (e))). +C The notation Lme refers to the pattern of the current element +C (stored in iw (pme1..pme2)). If (w (e) - wflg) becomes +C zero, then the element e will be absorbed in scan 2. +C ------------------------------------------------------------- + + DO 150 PME = PME1, PME2 + I = IW (PME) + ELN = ELEN (I) + IF (ELN .GT. 0) THEN +C note that nv (i) has been negated to denote i in Lme: + NVI = -NV (I) + WNVI = WFLG - NVI + DO 140 P = PE (I), PE (I) + ELN - 1 + E = IW (P) + WE = W (E) + IF (WE .GE. WFLG) THEN +C unabsorbed element e has been seen in this loop + WE = WE - NVI + ELSE IF (WE .NE. 0) THEN +C e is an unabsorbed element +C this is the first we have seen e in all of Scan 1 + WE = DEGREE (E) + WNVI + ENDIF + W (E) = WE +140 CONTINUE + ENDIF +150 CONTINUE + +C======================================================================= +C DEGREE UPDATE AND ELEMENT ABSORPTION +C======================================================================= + +C ------------------------------------------------------------- +C Scan 2: for each i in Lme, sum up the degree of Lme (which +C is degme), plus the sum of the external degrees of each Le +C for the elements e appearing within i, plus the +C supervariables in i. Place i in hash list. +C ------------------------------------------------------------- + + DO 180 PME = PME1, PME2 + I = IW (PME) + P1 = PE (I) + P2 = P1 + ELEN (I) - 1 + PN = P1 + HASH = 0 + DEG = 0 + +C ---------------------------------------------------------- +C scan the element list associated with supervariable i +C ---------------------------------------------------------- + +C UMFPACK/MA38-style approximate degree: + DO 160 P = P1, P2 + E = IW (P) + WE = W (E) + IF (WE .NE. 0) THEN +C e is an unabsorbed element + DEG = DEG + WE - WFLG + IW (PN) = E + PN = PN + 1 + HASH = HASH + E + ENDIF +160 CONTINUE + +C count the number of elements in i (including me): + ELEN (I) = PN - P1 + 1 + +C ---------------------------------------------------------- +C scan the supervariables in the list associated with i +C ---------------------------------------------------------- + + P3 = PN + DO 170 P = P2 + 1, P1 + LEN (I) - 1 + J = IW (P) + NVJ = NV (J) + IF (NVJ .GT. 0) THEN +C j is unabsorbed, and not in Lme. +C add to degree and add to new list + DEG = DEG + NVJ + IW (PN) = J + PN = PN + 1 + HASH = HASH + J + ENDIF +170 CONTINUE + +C ---------------------------------------------------------- +C update the degree and check for mass elimination +C ---------------------------------------------------------- + + IF (ELEN (I) .EQ. 1 .AND. P3 .EQ. PN) THEN + +C ------------------------------------------------------- +C mass elimination +C ------------------------------------------------------- + +C There is nothing left of this node except for an +C edge to the current pivot element. elen (i) is 1, +C and there are no variables adjacent to node i. +C Absorb i into the current pivot element, me. + + PE (I) = -ME + NVI = -NV (I) + DEGME = DEGME - NVI + NVPIV = NVPIV + NVI + NEL = NEL + NVI + NV (I) = 0 + ELEN (I) = 0 + + ELSE + +C ------------------------------------------------------- +C update the upper-bound degree of i +C ------------------------------------------------------- + +C the following degree does not yet include the size +C of the current element, which is added later: + DEGREE (I) = MIN (DEGREE (I), DEG) + +C ------------------------------------------------------- +C add me to the list for i +C ------------------------------------------------------- + +C move first supervariable to end of list + IW (PN) = IW (P3) +C move first element to end of element part of list + IW (P3) = IW (P1) +C add new element to front of list. + IW (P1) = ME +C store the new length of the list in len (i) + LEN (I) = PN - P1 + 1 + +C ------------------------------------------------------- +C place in hash bucket. Save hash key of i in last (i). +C ------------------------------------------------------- + + HASH = MOD (HASH, HMOD) + 1 + J = HEAD (HASH) + IF (J .LE. 0) THEN +C the degree list is empty, hash head is -j + NEXT (I) = -J + HEAD (HASH) = -I + ELSE +C degree list is not empty +C use last (head (hash)) as hash head + NEXT (I) = LAST (J) + LAST (J) = I + ENDIF + LAST (I) = HASH + ENDIF +180 CONTINUE + + DEGREE (ME) = DEGME + +C ------------------------------------------------------------- +C Clear the counter array, w (...), by incrementing wflg. +C ------------------------------------------------------------- + + DMAX = MAX (DMAX, DEGME) + WFLG = WFLG + DMAX + +C make sure that wflg+n does not cause integer overflow + IF (WFLG + N .LE. WFLG) THEN + DO 190 X = 1, N + IF (W (X) .NE. 0) W (X) = 1 +190 CONTINUE + WFLG = 2 + ENDIF +C at this point, w (1..n) .lt. wflg holds + +C======================================================================= +C SUPERVARIABLE DETECTION +C======================================================================= + + DO 250 PME = PME1, PME2 + I = IW (PME) + IF (NV (I) .LT. 0) THEN +C i is a principal variable in Lme + +C ------------------------------------------------------- +C examine all hash buckets with 2 or more variables. We +C do this by examing all unique hash keys for super- +C variables in the pattern Lme of the current element, me +C ------------------------------------------------------- + + HASH = LAST (I) +C let i = head of hash bucket, and empty the hash bucket + J = HEAD (HASH) + IF (J .EQ. 0) GOTO 250 + IF (J .LT. 0) THEN +C degree list is empty + I = -J + HEAD (HASH) = 0 + ELSE +C degree list is not empty, restore last () of head + I = LAST (J) + LAST (J) = 0 + ENDIF + IF (I .EQ. 0) GOTO 250 + +C while loop: +200 CONTINUE + IF (NEXT (I) .NE. 0) THEN + +C ---------------------------------------------------- +C this bucket has one or more variables following i. +C scan all of them to see if i can absorb any entries +C that follow i in hash bucket. Scatter i into w. +C ---------------------------------------------------- + + LN = LEN (I) + ELN = ELEN (I) +C do not flag the first element in the list (me) + DO 210 P = PE (I) + 1, PE (I) + LN - 1 + W (IW (P)) = WFLG +210 CONTINUE + +C ---------------------------------------------------- +C scan every other entry j following i in bucket +C ---------------------------------------------------- + + JLAST = I + J = NEXT (I) + +C while loop: +220 CONTINUE + IF (J .NE. 0) THEN + +C ------------------------------------------------- +C check if j and i have identical nonzero pattern +C ------------------------------------------------- + + IF (LEN (J) .NE. LN) THEN +C i and j do not have same size data structure + GOTO 240 + ENDIF + IF (ELEN (J) .NE. ELN) THEN +C i and j do not have same number of adjacent el + GOTO 240 + ENDIF +C do not flag the first element in the list (me) + DO 230 P = PE (J) + 1, PE (J) + LN - 1 + IF (W (IW (P)) .NE. WFLG) THEN +C an entry (iw(p)) is in j but not in i + GOTO 240 + ENDIF +230 CONTINUE + +C ------------------------------------------------- +C found it! j can be absorbed into i +C ------------------------------------------------- + + PE (J) = -I +C both nv (i) and nv (j) are negated since they +C are in Lme, and the absolute values of each +C are the number of variables in i and j: + NV (I) = NV (I) + NV (J) + NV (J) = 0 + ELEN (J) = 0 +C delete j from hash bucket + J = NEXT (J) + NEXT (JLAST) = J + GOTO 220 + +C ------------------------------------------------- +240 CONTINUE +C j cannot be absorbed into i +C ------------------------------------------------- + + JLAST = J + J = NEXT (J) + GOTO 220 + ENDIF + +C ---------------------------------------------------- +C no more variables can be absorbed into i +C go to next i in bucket and clear flag array +C ---------------------------------------------------- + + WFLG = WFLG + 1 + I = NEXT (I) + IF (I .NE. 0) GOTO 200 + ENDIF + ENDIF +250 CONTINUE + +C======================================================================= +C RESTORE DEGREE LISTS AND REMOVE NONPRINCIPAL SUPERVAR. FROM ELEMENT +C======================================================================= + + P = PME1 + NLEFT = N - NEL + DO 260 PME = PME1, PME2 + I = IW (PME) + NVI = -NV (I) + IF (NVI .GT. 0) THEN +C i is a principal variable in Lme +C restore nv (i) to signify that i is principal + NV (I) = NVI + +C ------------------------------------------------------- +C compute the external degree (add size of current elem) +C ------------------------------------------------------- + + DEG = MAX (1, MIN (DEGREE (I) + DEGME-NVI, NLEFT-NVI)) + +C ------------------------------------------------------- +C place the supervariable at the head of the degree list +C ------------------------------------------------------- + + INEXT = HEAD (DEG) + IF (INEXT .NE. 0) LAST (INEXT) = I + NEXT (I) = INEXT + LAST (I) = 0 + HEAD (DEG) = I + +C ------------------------------------------------------- +C save the new degree, and find the minimum degree +C ------------------------------------------------------- + + MINDEG = MIN (MINDEG, DEG) + DEGREE (I) = DEG + +C ------------------------------------------------------- +C place the supervariable in the element pattern +C ------------------------------------------------------- + + IW (P) = I + P = P + 1 + ENDIF +260 CONTINUE + +C======================================================================= +C FINALIZE THE NEW ELEMENT +C======================================================================= + + NV (ME) = NVPIV + DEGME +C nv (me) is now the degree of pivot (including diagonal part) +C save the length of the list for the new element me + LEN (ME) = P - PME1 + IF (LEN (ME) .EQ. 0) THEN +C there is nothing left of the current pivot element + PE (ME) = 0 + W (ME) = 0 + ENDIF + IF (NEWMEM .NE. 0) THEN +C element was not constructed in place: deallocate part +C of it (final size is less than or equal to newmem, +C since newly nonprincipal variables have been removed). + PFREE = P + MEM = MEM - NEWMEM + LEN (ME) + ENDIF + +C======================================================================= +C END WHILE (selecting pivots) + GOTO 30 + ENDIF +C======================================================================= + +C======================================================================= +C COMPUTE THE PERMUTATION VECTORS +C======================================================================= + +C ---------------------------------------------------------------- +C The time taken by the following code is O(n). At this +C point, elen (e) = -k has been done for all elements e, +C and elen (i) = 0 has been done for all nonprincipal +C variables i. At this point, there are no principal +C supervariables left, and all elements are absorbed. +C ---------------------------------------------------------------- + +C ---------------------------------------------------------------- +C compute the ordering of unordered nonprincipal variables +C ---------------------------------------------------------------- + + DO 290 I = 1, N + IF (ELEN (I) .EQ. 0) THEN + +C ---------------------------------------------------------- +C i is an un-ordered row. Traverse the tree from i until +C reaching an element, e. The element, e, was the +C principal supervariable of i and all nodes in the path +C from i to when e was selected as pivot. +C ---------------------------------------------------------- + + J = -PE (I) +C while (j is a variable) do: +270 CONTINUE + IF (ELEN (J) .GE. 0) THEN + J = -PE (J) + GOTO 270 + ENDIF + E = J + +C ---------------------------------------------------------- +C get the current pivot ordering of e +C ---------------------------------------------------------- + + K = -ELEN (E) + +C ---------------------------------------------------------- +C traverse the path again from i to e, and compress the +C path (all nodes point to e). Path compression allows +C this code to compute in O(n) time. Order the unordered +C nodes in the path, and place the element e at the end. +C ---------------------------------------------------------- + + J = I +C while (j is a variable) do: +280 CONTINUE + IF (ELEN (J) .GE. 0) THEN + JNEXT = -PE (J) + PE (J) = -E + IF (ELEN (J) .EQ. 0) THEN +C j is an unordered row + ELEN (J) = K + K = K + 1 + ENDIF + J = JNEXT + GOTO 280 + ENDIF +C leave elen (e) negative, so we know it is an element + ELEN (E) = -K + ENDIF +290 CONTINUE + +C ---------------------------------------------------------------- +C reset the inverse permutation (elen (1..n)) to be positive, +C and compute the permutation (last (1..n)). +C ---------------------------------------------------------------- + + DO 300 I = 1, N + K = ABS (ELEN (I)) + LAST (K) = I + ELEN (I) = K +300 CONTINUE + +C======================================================================= +C RETURN THE MEMORY USAGE IN IW +C======================================================================= + +C If maxmem is less than or equal to iwlen, then no compressions +C occurred, and iw (maxmem+1 ... iwlen) was unused. Otherwise +C compressions did occur, and iwlen would have had to have been +C greater than or equal to maxmem for no compressions to occur. +C Return the value of maxmem in the pfree argument. + + PFREE = MAXMEM + + RETURN + END + diff --git a/src/CHOLMOD.diff b/src/CHOLMOD.diff new file mode 100644 index 0000000..cb09223 --- /dev/null +++ b/src/CHOLMOD.diff @@ -0,0 +1,89 @@ +diff -r -x '*.o' -x '*.lo' -x .deps -x .dirstamp -x .libs CHOLMOD-orig/Include/cholmod_blas.h CHOLMOD/Include/cholmod_blas.h +108,115c108,115 +< #define BLAS_DTRSV dtrsv +< #define BLAS_DGEMV dgemv +< #define BLAS_DTRSM dtrsm +< #define BLAS_DGEMM dgemm +< #define BLAS_DSYRK dsyrk +< #define BLAS_DGER dger +< #define BLAS_DSCAL dscal +< #define LAPACK_DPOTRF dpotrf +--- +> #define BLAS_DTRSV igraphdtrsv +> #define BLAS_DGEMV igraphdgemv +> #define BLAS_DTRSM igraphdtrsm +> #define BLAS_DGEMM igraphdgemm +> #define BLAS_DSYRK igraphdsyrk +> #define BLAS_DGER igraphdger +> #define BLAS_DSCAL igraphdscal +> #define LAPACK_DPOTRF igraphdpotrf +128,135c128,135 +< #define BLAS_DTRSV dtrsv_ +< #define BLAS_DGEMV dgemv_ +< #define BLAS_DTRSM dtrsm_ +< #define BLAS_DGEMM dgemm_ +< #define BLAS_DSYRK dsyrk_ +< #define BLAS_DGER dger_ +< #define BLAS_DSCAL dscal_ +< #define LAPACK_DPOTRF dpotrf_ +--- +> #define BLAS_DTRSV igraphdtrsv_ +> #define BLAS_DGEMV igraphdgemv_ +> #define BLAS_DTRSM igraphdtrsm_ +> #define BLAS_DGEMM igraphdgemm_ +> #define BLAS_DSYRK igraphdsyrk_ +> #define BLAS_DGER igraphdger_ +> #define BLAS_DSCAL igraphdscal_ +> #define LAPACK_DPOTRF igraphdpotrf_ +diff -r -x '*.o' -x '*.lo' -x .deps -x .dirstamp -x .libs CHOLMOD-orig/Supernodal/cholmod_super_numeric.c CHOLMOD/Supernodal/cholmod_super_numeric.c +79,82c79,82 +< #define COMPLEX +< #include "t_cholmod_super_numeric.c" +< #define ZOMPLEX +< #include "t_cholmod_super_numeric.c" +--- +> /* #define COMPLEX */ +> /* #include "t_cholmod_super_numeric.c" */ +> /* #define ZOMPLEX */ +> /* #include "t_cholmod_super_numeric.c" */ +283,290c283,290 +< case CHOLMOD_COMPLEX: +< ok = c_cholmod_super_numeric (A, F, beta, L, C, Common) ; +< break ; +< +< case CHOLMOD_ZOMPLEX: +< /* This operates on complex L, not zomplex */ +< ok = z_cholmod_super_numeric (A, F, beta, L, C, Common) ; +< break ; +--- +> /* case CHOLMOD_COMPLEX: */ +> /* ok = c_cholmod_super_numeric (A, F, beta, L, C, Common) ; */ +> /* break ; */ +> +> /* case CHOLMOD_ZOMPLEX: */ +> /* /\* This operates on complex L, not zomplex *\/ */ +> /* ok = z_cholmod_super_numeric (A, F, beta, L, C, Common) ; */ +> /* break ; */ +diff -r -x '*.o' -x '*.lo' -x .deps -x .dirstamp -x .libs CHOLMOD-orig/Supernodal/cholmod_super_solve.c CHOLMOD/Supernodal/cholmod_super_solve.c +29,30c29,30 +< #define COMPLEX +< #include "t_cholmod_super_solve.c" +--- +> /* #define COMPLEX */ +> /* #include "t_cholmod_super_solve.c" */ +112,114c112,114 +< case CHOLMOD_COMPLEX: +< c_cholmod_super_lsolve (L, X, E, Common) ; +< break ; +--- +> /* case CHOLMOD_COMPLEX: */ +> /* c_cholmod_super_lsolve (L, X, E, Common) ; */ +> /* break ; */ +205,207c205,207 +< case CHOLMOD_COMPLEX: +< c_cholmod_super_ltsolve (L, X, E, Common) ; +< break ; +--- +> /* case CHOLMOD_COMPLEX: */ +> /* c_cholmod_super_ltsolve (L, X, E, Common) ; */ +> /* break ; */ diff --git a/src/CHOLMOD/Check/License.txt b/src/CHOLMOD/Check/License.txt new file mode 100644 index 0000000..ba50e66 --- /dev/null +++ b/src/CHOLMOD/Check/License.txt @@ -0,0 +1,24 @@ +CHOLMOD/Check Module. Copyright (C) 2005-2006, Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + +Note that this license is for the CHOLMOD/Check module only. +All CHOLMOD modules are licensed separately. + + +-------------------------------------------------------------------------------- + + +This Module is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This Module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this Module; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/src/CHOLMOD/Check/cholmod_check.c b/src/CHOLMOD/Check/cholmod_check.c new file mode 100644 index 0000000..16f1ab9 --- /dev/null +++ b/src/CHOLMOD/Check/cholmod_check.c @@ -0,0 +1,2709 @@ +/* ========================================================================== */ +/* === Check/cholmod_check ================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Check Module. Copyright (C) 2005-2013, Timothy A. Davis + * The CHOLMOD/Check Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Routines to check and print the contents of the 5 CHOLMOD objects: + * + * No CHOLMOD routine calls the check or print routines. If a user wants to + * check CHOLMOD's input parameters, a separate call to the appropriate check + * routine should be used before calling other CHOLMOD routines. + * + * cholmod_check_common check statistics and workspace in Common + * cholmod_check_sparse check sparse matrix in compressed column form + * cholmod_check_dense check dense matrix + * cholmod_check_factor check factorization + * cholmod_check_triplet check sparse matrix in triplet form + * + * cholmod_print_common print statistics in Common + * cholmod_print_sparse print sparse matrix in compressed column form + * cholmod_print_dense print dense matrix + * cholmod_print_factor print factorization + * cholmod_print_triplet print sparse matrix in triplet form + * + * In addition, this file contains routines to check and print three types of + * integer vectors: + * + * cholmod_check_perm check a permutation of 0:n-1 (no duplicates) + * cholmod_check_subset check a subset of 0:n-1 (duplicates OK) + * cholmod_check_parent check an elimination tree + * + * cholmod_print_perm print a permutation + * cholmod_print_subset print a subset + * cholmod_print_parent print an elimination tree + * + * Each Common->print level prints the items at or below the given level: + * + * 0: print nothing; just check the data structures and return TRUE/FALSE + * 1: error messages + * 2: warning messages + * 3: one-line summary of each object printed + * 4: short summary of each object (first and last few entries) + * 5: entire contents of the object + * + * No CHOLMOD routine calls these routines, so no printing occurs unless + * the user specifically calls a cholmod_print_* routine. Thus, the default + * print level is 3. + * + * Common->precise controls the # of digits printed for numerical entries + * (5 if FALSE, 15 if TRUE). + * + * If Common->print_function is NULL, then no printing occurs. The + * cholmod_check_* and cholmod_print_* routines still check their inputs and + * return TRUE/FALSE if the object is valid or not. + * + * This file also includes debugging routines that are enabled only when + * NDEBUG is defined in cholmod_internal.h (cholmod_dump_*). + */ + +#ifndef NCHECK + +#include "cholmod_internal.h" +#include "cholmod_check.h" + +/* ========================================================================== */ +/* === printing definitions ================================================= */ +/* ========================================================================== */ + +#ifdef LONG +#define I8 "%8ld" +#define I_8 "%-8ld" +#else +#define I8 "%8d" +#define I_8 "%-8d" +#endif + +#define PR(i,format,arg) \ +{ \ + if (print >= i && Common->print_function != NULL) \ + { \ + (Common->print_function) (format, arg) ; \ + } \ +} + +#define P1(format,arg) PR(1,format,arg) +#define P2(format,arg) PR(2,format,arg) +#define P3(format,arg) PR(3,format,arg) +#define P4(format,arg) PR(4,format,arg) + +#define ERR(msg) \ +{ \ + P1 ("\nCHOLMOD ERROR: %s: ", type) ; \ + if (name != NULL) \ + { \ + P1 ("%s", name) ; \ + } \ + P1 (": %s\n", msg) ; \ + ERROR (CHOLMOD_INVALID, "invalid") ; \ + return (FALSE) ; \ +} + +/* print a numerical value */ +#define PRINTVALUE(value) \ +{ \ + if (Common->precise) \ + { \ + P4 (" %23.15e", value) ; \ + } \ + else \ + { \ + P4 (" %.5g", value) ; \ + } \ +} + +/* start printing */ +#define ETC_START(count,limit) \ +{ \ + count = (init_print == 4) ? (limit) : (-1) ; \ +} + +/* re-enable printing if condition is met */ +#define ETC_ENABLE(condition,count,limit) \ +{ \ + if ((condition) && init_print == 4) \ + { \ + count = limit ; \ + print = 4 ; \ + } \ +} + +/* turn off printing if limit is reached */ +#define ETC_DISABLE(count) \ +{ \ + if ((count >= 0) && (count-- == 0) && print == 4) \ + { \ + P4 ("%s", " ...\n") ; \ + print = 3 ; \ + } \ +} + +/* re-enable printing, or turn if off after limit is reached */ +#define ETC(condition,count,limit) \ +{ \ + ETC_ENABLE (condition, count, limit) ; \ + ETC_DISABLE (count) ; \ +} + +#define BOOLSTR(x) ((x) ? "true " : "false") + +/* ========================================================================== */ +/* === print_value ========================================================== */ +/* ========================================================================== */ + +static void print_value +( + Int print, + Int xtype, + double *Xx, + double *Xz, + Int p, + cholmod_common *Common) +{ + if (xtype == CHOLMOD_REAL) + { + PRINTVALUE (Xx [p]) ; + } + else if (xtype == CHOLMOD_COMPLEX) + { + P4 ("%s", "(") ; + PRINTVALUE (Xx [2*p ]) ; + P4 ("%s", " , ") ; + PRINTVALUE (Xx [2*p+1]) ; + P4 ("%s", ")") ; + } + else if (xtype == CHOLMOD_ZOMPLEX) + { + P4 ("%s", "(") ; + PRINTVALUE (Xx [p]) ; + P4 ("%s", " , ") ; + PRINTVALUE (Xz [p]) ; + P4 ("%s", ")") ; + } +} + +/* ========================================================================== */ +/* === cholmod_check_common ================================================= */ +/* ========================================================================== */ + +/* Print and verify the contents of Common */ + +static int check_common +( + Int print, + const char *name, + cholmod_common *Common +) +{ + double fl, lnz ; + double *Xwork ; + Int *Flag, *Head ; + SuiteSparse_long mark ; + Int i, nrow, nmethods, ordering, xworksize, amd_backup, init_print ; + const char *type = "common" ; + + /* ---------------------------------------------------------------------- */ + /* print control parameters and statistics */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + init_print = print ; + + P2 ("%s", "\n") ; + + P1 ("CHOLMOD version %d", CHOLMOD_MAIN_VERSION) ; + P1 (".%d", CHOLMOD_SUB_VERSION) ; + P1 (".%d", CHOLMOD_SUBSUB_VERSION) ; + P1 (", %s: ", CHOLMOD_DATE) ; + + if (name != NULL) + { + P1 ("%s: ", name) ; + } + switch (Common->status) + { + + case CHOLMOD_OK: + P1 ("%s", "status: OK\n") ; + break ; + + case CHOLMOD_OUT_OF_MEMORY: + P1 ("%s", "status: ERROR, out of memory\n") ; + break ; + + case CHOLMOD_INVALID: + P1 ("%s", "status: ERROR, invalid parameter\n") ; + break ; + + case CHOLMOD_TOO_LARGE: + P1 ("%s", "status: ERROR, problem too large\n") ; + break ; + + case CHOLMOD_NOT_INSTALLED: + P1 ("%s", "status: ERROR, method not installed\n") ; + break ; + +#if GPU_BLAS + case CHOLMOD_GPU_PROBLEM: + P1 ("%s", "status: ERROR, GPU had a fatal error\n") ; + break ; +#endif + + case CHOLMOD_NOT_POSDEF: + P1 ("%s", "status: warning, matrix not positive definite\n") ; + break ; + + case CHOLMOD_DSMALL: + P1 ("%s", "status: warning, diagonal entry has tiny abs. value\n") ; + break ; + + default: + ERR ("unknown status") ; + } + + P2 (" Architecture: %s\n", CHOLMOD_ARCHITECTURE) ; + P3 (" sizeof(int): %d\n", (int) sizeof (int)) ; + P3 (" sizeof(SuiteSparse_long): %d\n", (int) sizeof (SuiteSparse_long)); + P3 (" sizeof(void *): %d\n", (int) sizeof (void *)) ; + P3 (" sizeof(double): %d\n", (int) sizeof (double)) ; + P3 (" sizeof(Int): %d (CHOLMOD's basic integer)\n", (int) sizeof (Int)) ; + P3 (" sizeof(BLAS_INT): %d (integer used in the BLAS)\n", + (int) sizeof (BLAS_INT)) ; + + if (Common->fl != EMPTY) + { + P2 ("%s", " Results from most recent analysis:\n") ; + P2 (" Cholesky flop count: %.5g\n", Common->fl) ; + P2 (" Nonzeros in L: %.5g\n", Common->lnz) ; + } + if (Common->modfl != EMPTY) + { + P2 (" Update/downdate flop count: %.5g\n", Common->modfl) ; + } + + P2 (" memory blocks in use: %8.0f\n", (double) (Common->malloc_count)) ; + P2 (" memory in use (MB): %8.1f\n", + (double) (Common->memory_inuse) / 1048576.) ; + P2 (" peak memory usage (MB): %8.1f\n", + (double) (Common->memory_usage) / 1048576.) ; + + /* ---------------------------------------------------------------------- */ + /* primary control parameters and related ordering statistics */ + /* ---------------------------------------------------------------------- */ + + P3 (" maxrank: update/downdate rank: "ID"\n", + (Int) CHOLMOD(maxrank) (0, Common)) ; + P3 (" supernodal control: %d", Common->supernodal) ; + P3 (" %g ", Common->supernodal_switch) ; + if (Common->supernodal <= CHOLMOD_SIMPLICIAL) + { + P3 ("%s", "(always do simplicial)\n") ; + } + else if (Common->supernodal == CHOLMOD_AUTO) + { + P3 ("(supernodal if flops/lnz >= %g)\n", Common->supernodal_switch) ; + } + else + { + P3 ("%s", "(always do supernodal)\n") ; + } + + nmethods = MIN (Common->nmethods, CHOLMOD_MAXMETHODS) ; + nmethods = MAX (0, nmethods) ; + + if (nmethods > 0) + { + P3 ("%s", " nmethods: number of ordering methods to try: ") ; + P3 (""ID"\n", nmethods) ; + amd_backup = (nmethods > 1) || (nmethods == 1 && + (Common->method [0].ordering == CHOLMOD_METIS || + Common->method [0].ordering == CHOLMOD_NESDIS)) ; + } + else + { + P3 ("%s", " nmethods=0: default strategy: Try user permutation if " + "given. Try AMD.\n") ; +#ifndef NPARTITION + if (Common->default_nesdis) + { + P3 ("%s", " Try NESDIS if AMD reports flops/nnz(L) >= 500 and " + "nnz(L)/nnz(A) >= 5.\n") ; + } + else + { + P3 ("%s", " Try METIS if AMD reports flops/nnz(L) >= 500 and " + "nnz(L)/nnz(A) >= 5.\n") ; + } +#endif + P3 ("%s", " Select best ordering tried.\n") ; + Common->method [0].ordering = CHOLMOD_GIVEN ; + Common->method [1].ordering = CHOLMOD_AMD ; + Common->method [2].ordering = + (Common->default_nesdis ? CHOLMOD_NESDIS : CHOLMOD_METIS) ; + amd_backup = FALSE ; +#ifndef NPARTITION + nmethods = 3 ; +#else + nmethods = 2 ; +#endif + } + + for (i = 0 ; i < nmethods ; i++) + { + P3 (" method "ID": ", i) ; + ordering = Common->method [i].ordering ; + fl = Common->method [i].fl ; + lnz = Common->method [i].lnz ; + switch (ordering) + { + + case CHOLMOD_NATURAL: + P3 ("%s", "natural\n") ; + break ; + + case CHOLMOD_GIVEN: + P3 ("%s", "user permutation (if given)\n") ; + break ; + + case CHOLMOD_AMD: + P3 ("%s", "AMD (or COLAMD if factorizing AA')\n") ; + amd_backup = FALSE ; + break ; + + case CHOLMOD_COLAMD: + P3 ("%s", "AMD if factorizing A, COLAMD if factorizing AA')\n"); + amd_backup = FALSE ; + break ; + + case CHOLMOD_METIS: + P3 ("%s", "METIS_NodeND nested dissection\n") ; + break ; + + case CHOLMOD_NESDIS: + P3 ("%s", "CHOLMOD nested dissection\n") ; + + P3 (" nd_small: # nodes in uncut subgraph: "ID"\n", + (Int) (Common->method [i].nd_small)) ; + P3 (" nd_compress: compress the graph: %s\n", + BOOLSTR (Common->method [i].nd_compress)) ; + P3 (" nd_camd: use constrained min degree: %s\n", + BOOLSTR (Common->method [i].nd_camd)) ; + break ; + + default: + P3 (ID, ordering) ; + ERR ("unknown ordering method") ; + break ; + + } + + if (!(ordering == CHOLMOD_NATURAL || ordering == CHOLMOD_GIVEN)) + { + if (Common->method [i].prune_dense < 0) + { + P3 (" prune_dense: for pruning dense nodes: %s\n", + " none pruned") ; + } + else + { + P3 (" prune_dense: for pruning dense nodes: " + "%.5g\n", + Common->method [i].prune_dense) ; + P3 (" a dense node has degree " + ">= max(16,(%.5g)*sqrt(n))\n", + Common->method [i].prune_dense) ; + } + } + + if (ordering == CHOLMOD_COLAMD || ordering == CHOLMOD_NESDIS) + { + if (Common->method [i].prune_dense2 < 0) + { + P3 (" prune_dense2: for pruning dense rows for AA':" + " %s\n", " none pruned") ; + } + else + { + P3 (" prune_dense2: for pruning dense rows for AA':" + " %.5g\n", Common->method [i].prune_dense2) ; + P3 (" a dense row has degree " + ">= max(16,(%.5g)*sqrt(ncol))\n", + Common->method [i].prune_dense2) ; + } + } + + if (fl != EMPTY) P3 (" flop count: %.5g\n", fl) ; + if (lnz != EMPTY) P3 (" nnz(L): %.5g\n", lnz) ; + } + + /* backup AMD results, if any */ + if (amd_backup) + { + P3 ("%s", " backup method: ") ; + P3 ("%s", "AMD (or COLAMD if factorizing AA')\n") ; + fl = Common->method [nmethods].fl ; + lnz = Common->method [nmethods].lnz ; + if (fl != EMPTY) P3 (" AMD flop count: %.5g\n", fl) ; + if (lnz != EMPTY) P3 (" AMD nnz(L): %.5g\n", lnz) ; + } + + /* ---------------------------------------------------------------------- */ + /* arcane control parameters */ + /* ---------------------------------------------------------------------- */ + + if (Common->final_asis) + { + P4 ("%s", " final_asis: TRUE, leave as is\n") ; + } + else + { + P4 ("%s", " final_asis: FALSE, convert when done\n") ; + if (Common->final_super) + { + P4 ("%s", " final_super: TRUE, leave in supernodal form\n") ; + } + else + { + P4 ("%s", " final_super: FALSE, convert to simplicial form\n") ; + } + if (Common->final_ll) + { + P4 ("%s", " final_ll: TRUE, convert to LL' form\n") ; + } + else + { + P4 ("%s", " final_ll: FALSE, convert to LDL' form\n") ; + } + if (Common->final_pack) + { + P4 ("%s", " final_pack: TRUE, pack when done\n") ; + } + else + { + P4 ("%s", " final_pack: FALSE, do not pack when done\n") ; + } + if (Common->final_monotonic) + { + P4 ("%s", " final_monotonic: TRUE, ensure L is monotonic\n") ; + } + else + { + P4 ("%s", + " final_monotonic: FALSE, do not ensure L is monotonic\n") ; + } + P4 (" final_resymbol: remove zeros from amalgamation: %s\n", + BOOLSTR (Common->final_resymbol)) ; + } + + P4 (" dbound: LDL' diagonal threshold: % .5g\n Entries with abs. value" + " less than dbound are replaced with +/- dbound.\n", + Common->dbound) ; + + P4 (" grow0: memory reallocation: % .5g\n", Common->grow0) ; + P4 (" grow1: memory reallocation: % .5g\n", Common->grow1) ; + P4 (" grow2: memory reallocation: %g\n", (double) (Common->grow2)) ; + + P4 ("%s", " nrelax, zrelax: supernodal amalgamation rule:\n") ; + P4 ("%s", " s = # columns in two adjacent supernodes\n") ; + P4 ("%s", " z = % of zeros in new supernode if they are merged.\n") ; + P4 ("%s", " Two supernodes are merged if") ; + P4 (" (s <= %g) or (no new zero entries) or\n", + (double) (Common->nrelax [0])) ; + P4 (" (s <= %g and ", (double) (Common->nrelax [1])) ; + P4 ("z < %.5g%%) or", Common->zrelax [0] * 100) ; + P4 (" (s <= %g and ", (double) (Common->nrelax [2])) ; + P4 ("z < %.5g%%) or", Common->zrelax [1] * 100) ; + P4 (" (z < %.5g%%)\n", Common->zrelax [2] * 100) ; + + /* ---------------------------------------------------------------------- */ + /* check workspace */ + /* ---------------------------------------------------------------------- */ + + mark = Common->mark ; + nrow = Common->nrow ; + Flag = Common->Flag ; + Head = Common->Head ; + if (nrow > 0) + { + if (mark < 0 || Flag == NULL || Head == NULL) + { + ERR ("workspace corrupted (Flag and/or Head missing)") ; + } + for (i = 0 ; i < nrow ; i++) + { + if (Flag [i] >= mark) + { + PRINT0 (("Flag ["ID"]="ID", mark = %ld\n", i, Flag [i], mark)) ; + ERR ("workspace corrupted (Flag)") ; + } + } + for (i = 0 ; i <= nrow ; i++) + { + if (Head [i] != EMPTY) + { + PRINT0 (("Head ["ID"] = "ID",\n", i, Head [i])) ; + ERR ("workspace corrupted (Head)") ; + } + } + } + xworksize = Common->xworksize ; + Xwork = Common->Xwork ; + if (xworksize > 0) + { + if (Xwork == NULL) + { + ERR ("workspace corrupted (Xwork missing)") ; + } + for (i = 0 ; i < xworksize ; i++) + { + if (Xwork [i] != 0.) + { + PRINT0 (("Xwork ["ID"] = %g\n", i, Xwork [i])) ; + ERR ("workspace corrupted (Xwork)") ; + } + } + } + + /* workspace and parameters are valid */ + P3 ("%s", " OK\n") ; + P4 ("%s", "\n") ; + return (TRUE) ; +} + + +int CHOLMOD(check_common) +( + cholmod_common *Common +) +{ + return (check_common (0, NULL, Common)) ; +} + + +int CHOLMOD(print_common) +( + /* ---- input ---- */ + const char *name, /* printed name of Common object */ + /* --------------- */ + cholmod_common *Common +) +{ + Int print = (Common == NULL) ? 3 : (Common->print) ; + return (check_common (print, name, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_gpu_stats ==================================================== */ +/* ========================================================================== */ + +/* Print CPU / GPU statistics. If the timer is not installed, the times are + reported as zero, but this function still works. Likewise, the function + still works if the GPU BLAS is not installed. */ + +int CHOLMOD(gpu_stats) +( + cholmod_common *Common /* input */ +) +{ + double cpu_time, gpu_time ; + int print ; + + RETURN_IF_NULL_COMMON (FALSE) ; + print = Common->print ; + + P2 ("%s", "\nCHOLMOD GPU/CPU statistics:\n") ; + P2 ("SYRK CPU calls %12.0f", (double) Common->CHOLMOD_CPU_SYRK_CALLS) ; + P2 (" time %12.4e\n", Common->CHOLMOD_CPU_SYRK_TIME) ; + P2 (" GPU calls %12.0f", (double) Common->CHOLMOD_GPU_SYRK_CALLS) ; + P2 (" time %12.4e\n", Common->CHOLMOD_GPU_SYRK_TIME) ; + P2 ("GEMM CPU calls %12.0f", (double) Common->CHOLMOD_CPU_GEMM_CALLS) ; + P2 (" time %12.4e\n", Common->CHOLMOD_CPU_GEMM_TIME) ; + P2 (" GPU calls %12.0f", (double) Common->CHOLMOD_GPU_GEMM_CALLS) ; + P2 (" time %12.4e\n", Common->CHOLMOD_GPU_GEMM_TIME) ; + P2 ("POTRF CPU calls %12.0f", (double) Common->CHOLMOD_CPU_POTRF_CALLS) ; + P2 (" time %12.4e\n", Common->CHOLMOD_CPU_POTRF_TIME) ; + P2 (" GPU calls %12.0f", (double) Common->CHOLMOD_GPU_POTRF_CALLS) ; + P2 (" time %12.4e\n", Common->CHOLMOD_GPU_POTRF_TIME) ; + P2 ("TRSM CPU calls %12.0f", (double) Common->CHOLMOD_CPU_TRSM_CALLS) ; + P2 (" time %12.4e\n", Common->CHOLMOD_CPU_TRSM_TIME) ; + P2 (" GPU calls %12.0f", (double) Common->CHOLMOD_GPU_TRSM_CALLS) ; + P2 (" time %12.4e\n", Common->CHOLMOD_GPU_TRSM_TIME) ; + + cpu_time = Common->CHOLMOD_CPU_SYRK_TIME + Common->CHOLMOD_CPU_TRSM_TIME + + Common->CHOLMOD_CPU_GEMM_TIME + Common->CHOLMOD_CPU_POTRF_TIME ; + + gpu_time = Common->CHOLMOD_GPU_SYRK_TIME + Common->CHOLMOD_GPU_TRSM_TIME + + Common->CHOLMOD_GPU_GEMM_TIME + Common->CHOLMOD_GPU_POTRF_TIME ; + + P2 ("time in the BLAS: CPU %12.4e", cpu_time) ; + P2 (" GPU %12.4e", gpu_time) ; + P2 (" total: %12.4e\n", cpu_time + gpu_time) ; + + P2 ("assembly time %12.4e", Common->CHOLMOD_ASSEMBLE_TIME) ; + P2 (" %12.4e\n", Common->CHOLMOD_ASSEMBLE_TIME2) ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_check_sparse ================================================= */ +/* ========================================================================== */ + +/* Ensure that a sparse matrix in column-oriented form is valid, and optionally + * print it. Returns the number of entries on the diagonal or -1 if error. + * + * workspace: Iwork (nrow) + */ + +static SuiteSparse_long check_sparse +( + Int *Wi, + Int print, + const char *name, + cholmod_sparse *A, + SuiteSparse_long *nnzdiag, + cholmod_common *Common +) +{ + double *Ax, *Az ; + Int *Ap, *Ai, *Anz ; + Int nrow, ncol, nzmax, sorted, packed, j, p, pend, i, nz, ilast, + space, init_print, dnz, count, xtype ; + const char *type = "sparse" ; + + /* ---------------------------------------------------------------------- */ + /* print header information */ + /* ---------------------------------------------------------------------- */ + + P4 ("%s", "\n") ; + P3 ("%s", "CHOLMOD sparse: ") ; + if (name != NULL) + { + P3 ("%s: ", name) ; + } + + if (A == NULL) + { + ERR ("null") ; + } + + nrow = A->nrow ; + ncol = A->ncol ; + nzmax = A->nzmax ; + sorted = A->sorted ; + packed = A->packed ; + xtype = A->xtype ; + Ap = A->p ; + Ai = A->i ; + Ax = A->x ; + Az = A->z ; + Anz = A->nz ; + nz = CHOLMOD(nnz) (A, Common) ; + + P3 (" "ID"", nrow) ; + P3 ("-by-"ID", ", ncol) ; + P3 ("nz "ID",", nz) ; + if (A->stype > 0) + { + P3 ("%s", " upper.") ; + } + else if (A->stype < 0) + { + P3 ("%s", " lower.") ; + } + else + { + P3 ("%s", " up/lo.") ; + } + + P4 ("\n nzmax "ID", ", nzmax) ; + if (nz > nzmax) + { + ERR ("nzmax too small") ; + } + if (!sorted) + { + P4 ("%s", "un") ; + } + P4 ("%s", "sorted, ") ; + if (!packed) + { + P4 ("%s", "un") ; + } + P4 ("%s", "packed, ") ; + + switch (A->itype) + { + case CHOLMOD_INT: P4 ("%s", "\n scalar types: int, ") ; break ; + case CHOLMOD_INTLONG: ERR ("mixed int/long type unsupported") ; + case CHOLMOD_LONG: P4 ("%s", "\n scalar types: SuiteSparse_long, "); + break ; + default: ERR ("unknown itype") ; + } + + switch (A->xtype) + { + case CHOLMOD_PATTERN: P4 ("%s", "pattern") ; break ; + case CHOLMOD_REAL: P4 ("%s", "real") ; break ; + case CHOLMOD_COMPLEX: P4 ("%s", "complex") ; break ; + case CHOLMOD_ZOMPLEX: P4 ("%s", "zomplex") ; break ; + default: ERR ("unknown xtype") ; + } + + switch (A->dtype) + { + case CHOLMOD_DOUBLE: P4 ("%s", ", double\n") ; break ; + case CHOLMOD_SINGLE: ERR ("float unsupported") ; + default: ERR ("unknown dtype") ; + } + + if (A->itype != ITYPE || A->dtype != DTYPE) + { + ERR ("integer and real type must match routine") ; + } + + if (A->stype && nrow != ncol) + { + ERR ("symmetric but not square") ; + } + + /* check for existence of Ap, Ai, Anz, Ax, and Az arrays */ + if (Ap == NULL) + { + ERR ("p array not present") ; + } + if (Ai == NULL) + { + ERR ("i array not present") ; + } + if (!packed && Anz == NULL) + { + ERR ("nz array not present") ; + } + if (xtype != CHOLMOD_PATTERN && Ax == NULL) + { + ERR ("x array not present") ; + } + if (xtype == CHOLMOD_ZOMPLEX && Az == NULL) + { + ERR ("z array not present") ; + } + + /* packed matrices must start at Ap [0] = 0 */ + if (packed && Ap [0] != 0) + { + ERR ("p [0] must be zero") ; + } + if (packed && (Ap [ncol] < Ap [0] || Ap [ncol] > nzmax)) + { + ERR ("p [ncol] invalid") ; + } + + /* ---------------------------------------------------------------------- */ + /* allocate workspace if needed */ + /* ---------------------------------------------------------------------- */ + + if (!sorted) + { + if (Wi == NULL) + { + CHOLMOD(allocate_work) (0, nrow, 0, Common) ; + Wi = Common->Iwork ; /* size nrow, (i/i/l) */ + } + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; /* out of memory */ + } + for (i = 0 ; i < nrow ; i++) + { + Wi [i] = EMPTY ; + } + } + + /* ---------------------------------------------------------------------- */ + /* check and print each column */ + /* ---------------------------------------------------------------------- */ + + init_print = print ; + dnz = 0 ; + ETC_START (count, 8) ; + + for (j = 0 ; j < ncol ; j++) + { + ETC (j == ncol-1, count, 4) ; + p = Ap [j] ; + if (packed) + { + pend = Ap [j+1] ; + nz = pend - p ; + } + else + { + /* Note that Anz [j] < 0 is treated as zero */ + nz = MAX (0, Anz [j]) ; + pend = p + nz ; + } + /* Note that space can be negative if the matrix is non-monotonic */ + space = Ap [j+1] - p ; + P4 (" col "ID":", j) ; + P4 (" nz "ID"", nz) ; + P4 (" start "ID"", p) ; + P4 (" end "ID"", pend) ; + if (!packed) + { + P4 (" space "ID"", space) ; + } + P4 ("%s", ":\n") ; + if (p < 0 || pend > nzmax) + { + ERR ("pointer invalid") ; + } + if (nz < 0 || nz > nrow) + { + ERR ("nz invalid") ; + } + ilast = EMPTY ; + + for ( ; p < pend ; p++) + { + ETC (j == ncol-1 && p >= pend-4, count, -1) ; + i = Ai [p] ; + P4 (" "I8":", i) ; + + print_value (print, xtype, Ax, Az, p, Common) ; + + if (i == j) + { + dnz++ ; + } + if (i < 0 || i >= nrow) + { + ERR ("row index out of range") ; + } + if (sorted && i <= ilast) + { + ERR ("row indices out of order") ; + } + if (!sorted && Wi [i] == j) + { + ERR ("duplicate row index") ; + } + P4 ("%s", "\n") ; + ilast = i ; + if (!sorted) + { + Wi [i] = j ; + } + } + } + + /* matrix is valid */ + P4 (" nnz on diagonal: "ID"\n", dnz) ; + P3 ("%s", " OK\n") ; + P4 ("%s", "\n") ; + *nnzdiag = dnz ; + return (TRUE) ; +} + + +int CHOLMOD(check_sparse) +( + /* ---- input ---- */ + cholmod_sparse *A, /* sparse matrix to check */ + /* --------------- */ + cholmod_common *Common +) +{ + SuiteSparse_long nnzdiag ; + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_sparse (NULL, 0, NULL, A, &nnzdiag, Common)) ; +} + + +int CHOLMOD(print_sparse) +( + /* ---- input ---- */ + cholmod_sparse *A, /* sparse matrix to print */ + const char *name, /* printed name of sparse matrix */ + /* --------------- */ + cholmod_common *Common +) +{ + SuiteSparse_long nnzdiag ; + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_sparse (NULL, Common->print, name, A, &nnzdiag, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_check_dense ================================================== */ +/* ========================================================================== */ + +/* Ensure a dense matrix is valid, and optionally print it. */ + +static int check_dense +( + Int print, + const char *name, + cholmod_dense *X, + cholmod_common *Common +) +{ + double *Xx, *Xz ; + Int i, j, d, nrow, ncol, nzmax, nz, init_print, count, xtype ; + const char *type = "dense" ; + + /* ---------------------------------------------------------------------- */ + /* print header information */ + /* ---------------------------------------------------------------------- */ + + P4 ("%s", "\n") ; + P3 ("%s", "CHOLMOD dense: ") ; + if (name != NULL) + { + P3 ("%s: ", name) ; + } + + if (X == NULL) + { + ERR ("null") ; + } + + nrow = X->nrow ; + ncol = X->ncol ; + nzmax = X->nzmax ; + d = X->d ; + Xx = X->x ; + Xz = X->z ; + xtype = X->xtype ; + + P3 (" "ID"", nrow) ; + P3 ("-by-"ID", ", ncol) ; + P4 ("\n leading dimension "ID", ", d) ; + P4 ("nzmax "ID", ", nzmax) ; + if (d * ncol > nzmax) + { + ERR ("nzmax too small") ; + } + if (d < nrow) + { + ERR ("leading dimension must be >= # of rows") ; + } + if (Xx == NULL) + { + ERR ("null") ; + } + + switch (X->xtype) + { + case CHOLMOD_PATTERN: ERR ("pattern unsupported") ; break ; + case CHOLMOD_REAL: P4 ("%s", "real") ; break ; + case CHOLMOD_COMPLEX: P4 ("%s", "complex") ; break ; + case CHOLMOD_ZOMPLEX: P4 ("%s", "zomplex") ; break ; + default: ERR ("unknown xtype") ; + } + + switch (X->dtype) + { + case CHOLMOD_DOUBLE: P4 ("%s", ", double\n") ; break ; + case CHOLMOD_SINGLE: ERR ("single unsupported") ; + default: ERR ("unknown dtype") ; + } + + /* ---------------------------------------------------------------------- */ + /* check and print each entry */ + /* ---------------------------------------------------------------------- */ + + if (print >= 4) + { + init_print = print ; + ETC_START (count, 9) ; + nz = nrow * ncol ; + for (j = 0 ; j < ncol ; j++) + { + ETC (j == ncol-1, count, 5) ; + P4 (" col "ID":\n", j) ; + for (i = 0 ; i < nrow ; i++) + { + ETC (j == ncol-1 && i >= nrow-4, count, -1) ; + P4 (" "I8":", i) ; + + print_value (print, xtype, Xx, Xz, i+j*d, Common) ; + + P4 ("%s", "\n") ; + } + } + } + + /* dense is valid */ + P3 ("%s", " OK\n") ; + P4 ("%s", "\n") ; + return (TRUE) ; +} + + +int CHOLMOD(check_dense) +( + /* ---- input ---- */ + cholmod_dense *X, /* dense matrix to check */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_dense (0, NULL, X, Common)) ; +} + + +int CHOLMOD(print_dense) +( + /* ---- input ---- */ + cholmod_dense *X, /* dense matrix to print */ + const char *name, /* printed name of dense matrix */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_dense (Common->print, name, X, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_check_subset ================================================= */ +/* ========================================================================== */ + +/* Ensure S (0:len-1) is a subset of 0:n-1. Duplicates are allowed. S may be + * NULL. A negative len denotes the set 0:n-1. + * + * To check the rset and cset for A(rset,cset), where nc and nr are the length + * of cset and rset respectively: + * + * cholmod_check_subset (cset, nc, A->ncol, Common) ; + * cholmod_check_subset (rset, nr, A->nrow, Common) ; + * + * workspace: none + */ + +static int check_subset +( + Int *S, + SuiteSparse_long len, + size_t n, + Int print, + const char *name, + cholmod_common *Common +) +{ + Int i, k, init_print, count ; + const char *type = "subset" ; + + init_print = print ; + + if (S == NULL) + { + /* zero len denotes S = [ ], negative len denotes S = 0:n-1 */ + len = (len < 0) ? (-1) : 0 ; + } + + P4 ("%s", "\n") ; + P3 ("%s", "CHOLMOD subset: ") ; + if (name != NULL) + { + P3 ("%s: ", name) ; + } + + P3 (" len: %ld ", len) ; + if (len < 0) + { + P3 ("%s", "(denotes 0:n-1) ") ; + } + P3 ("n: "ID"", (Int) n) ; + P4 ("%s", "\n") ; + + if (len <= 0 || S == NULL) + { + P3 ("%s", " OK\n") ; + P4 ("%s", "\n") ; + return (TRUE) ; + } + + if (print >= 4) + { + ETC_START (count, 8) ; + for (k = 0 ; k < ((Int) len) ; k++) + { + ETC (k == ((Int) len) - 4, count, -1) ; + i = S [k] ; + P4 (" "I8":", k) ; + P4 (" "ID"\n", i) ; + if (i < 0 || i >= ((Int) n)) + { + ERR ("entry out range") ; + } + } + } + else + { + for (k = 0 ; k < ((Int) len) ; k++) + { + i = S [k] ; + if (i < 0 || i >= ((Int) n)) + { + ERR ("entry out range") ; + } + } + } + P3 ("%s", " OK\n") ; + P4 ("%s", "\n") ; + return (TRUE) ; +} + + +int CHOLMOD(check_subset) +( + /* ---- input ---- */ + Int *Set, /* Set [0:len-1] is a subset of 0:n-1. Duplicates OK */ + SuiteSparse_long len, /* size of Set (an integer array), or < 0 if 0:n-1 */ + size_t n, /* 0:n-1 is valid range */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_subset (Set, len, n, 0, NULL, Common)) ; +} + + +int CHOLMOD(print_subset) +( + /* ---- input ---- */ + Int *Set, /* Set [0:len-1] is a subset of 0:n-1. Duplicates OK */ + SuiteSparse_long len, /* size of Set (an integer array), or < 0 if 0:n-1 */ + size_t n, /* 0:n-1 is valid range */ + const char *name, /* printed name of Set */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_subset (Set, len, n, Common->print, name, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_check_perm =================================================== */ +/* ========================================================================== */ + +/* Ensure that Perm [0..len-1] is a permutation of a subset of 0:n-1. Perm + * may be NULL, which is interpreted as the identity permutation. There can + * be no duplicate entries (len must be <= n). + * + * If n <= Common->nrow, then this routine takes O(len) time and does not + * allocate any memory, by using Common->Flag. Otherwise, it takes O(n) time + * and ensures that Common->Iwork is at least n*sizeof(Int) in size. + * + * To check the fset: cholmod_check_perm (fset, fsize, ncol, Common) ; + * To check a permutation: cholmod_check_perm (Perm, n, n, Common) ; + * + * workspace: Flag (n) if n <= Common->nrow, Iwork (n) otherwise. + */ + +static int check_perm +( + Int *Wi, + Int print, + const char *name, + Int *Perm, + size_t len, + size_t n, + cholmod_common *Common +) +{ + Int *Flag ; + Int i, k, mark, init_print, count ; + const char *type = "perm" ; + + /* ---------------------------------------------------------------------- */ + /* checks that take O(1) time */ + /* ---------------------------------------------------------------------- */ + + if (Perm == NULL || n == 0) + { + /* Perm is valid implicit identity, or empty */ + return (TRUE) ; + } + + /* ---------------------------------------------------------------------- */ + /* checks that take O(n) time or require memory allocation */ + /* ---------------------------------------------------------------------- */ + + init_print = print ; + ETC_START (count, 8) ; + + if (Wi == NULL && n <= Common->nrow) + { + /* use the Common->Flag array if it's big enough */ + mark = CHOLMOD(clear_flag) (Common) ; + Flag = Common->Flag ; + ASSERT (CHOLMOD(dump_work) (TRUE, FALSE, 0, Common)) ; + if (print >= 4) + { + for (k = 0 ; k < ((Int) len) ; k++) + { + ETC (k >= ((Int) len) - 4, count, -1) ; + i = Perm [k] ; + P4 (" "I8":", k) ; + P4 (""ID"\n", i) ; + if (i < 0 || i >= ((Int) n) || Flag [i] == mark) + { + CHOLMOD(clear_flag) (Common) ; + ERR ("invalid permutation") ; + } + Flag [i] = mark ; + } + } + else + { + for (k = 0 ; k < ((Int) len) ; k++) + { + i = Perm [k] ; + if (i < 0 || i >= ((Int) n) || Flag [i] == mark) + { + CHOLMOD(clear_flag) (Common) ; + ERR ("invalid permutation") ; + } + Flag [i] = mark ; + } + } + CHOLMOD(clear_flag) (Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, FALSE, 0, Common)) ; + } + else + { + if (Wi == NULL) + { + /* use Common->Iwork instead, but initialize it first */ + CHOLMOD(allocate_work) (0, n, 0, Common) ; + Wi = Common->Iwork ; /* size n, (i/i/i) is OK */ + } + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; /* out of memory */ + } + for (i = 0 ; i < ((Int) n) ; i++) + { + Wi [i] = FALSE ; + } + if (print >= 4) + { + for (k = 0 ; k < ((Int) len) ; k++) + { + ETC (k >= ((Int) len) - 4, count, -1) ; + i = Perm [k] ; + P4 (" "I8":", k) ; + P4 (""ID"\n", i) ; + if (i < 0 || i >= ((Int) n) || Wi [i]) + { + ERR ("invalid permutation") ; + } + Wi [i] = TRUE ; + } + } + else + { + for (k = 0 ; k < ((Int) len) ; k++) + { + i = Perm [k] ; + if (i < 0 || i >= ((Int) n) || Wi [i]) + { + ERR ("invalid permutation") ; + } + Wi [i] = TRUE ; + } + } + } + + /* perm is valid */ + return (TRUE) ; +} + + +int CHOLMOD(check_perm) +( + /* ---- input ---- */ + Int *Perm, /* Perm [0:len-1] is a permutation of subset of 0:n-1 */ + size_t len, /* size of Perm (an integer array) */ + size_t n, /* 0:n-1 is valid range */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_perm (NULL, 0, NULL, Perm, len, n, Common)) ; +} + + +int CHOLMOD(print_perm) +( + /* ---- input ---- */ + Int *Perm, /* Perm [0:len-1] is a permutation of subset of 0:n-1 */ + size_t len, /* size of Perm (an integer array) */ + size_t n, /* 0:n-1 is valid range */ + const char *name, /* printed name of Perm */ + /* --------------- */ + cholmod_common *Common +) +{ + Int ok, print ; + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + print = Common->print ; + P4 ("%s", "\n") ; + P3 ("%s", "CHOLMOD perm: ") ; + if (name != NULL) + { + P3 ("%s: ", name) ; + } + P3 (" len: "ID"", (Int) len) ; + P3 (" n: "ID"", (Int) n) ; + P4 ("%s", "\n") ; + ok = check_perm (NULL, print, name, Perm, len, n, Common) ; + if (ok) + { + P3 ("%s", " OK\n") ; + P4 ("%s", "\n") ; + } + return (ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_check_parent ================================================= */ +/* ========================================================================== */ + +/* Ensure that Parent is a valid elimination tree of nodes 0 to n-1. + * If j is a root of the tree then Parent [j] is EMPTY (-1). + * + * NOTE: this check will fail if applied to the component tree (CParent) in + * cholmod_nested_dissection, unless it has been postordered and renumbered. + * + * workspace: none + */ + +static int check_parent +( + Int *Parent, + size_t n, + Int print, + const char *name, + cholmod_common *Common +) +{ + Int j, p, init_print, count ; + const char *type = "parent" ; + + init_print = print ; + + P4 ("%s", "\n") ; + P3 ("%s", "CHOLMOD parent: ") ; + if (name != NULL) + { + P3 ("%s: ", name) ; + } + + P3 (" n: "ID"", (Int) n) ; + P4 ("%s", "\n") ; + + if (Parent == NULL) + { + ERR ("null") ; + } + + /* ---------------------------------------------------------------------- */ + /* checks that take O(n) time */ + /* ---------------------------------------------------------------------- */ + + ETC_START (count, 8) ; + for (j = 0 ; j < ((Int) n) ; j++) + { + ETC (j == ((Int) n) - 4, count, -1) ; + p = Parent [j] ; + P4 (" "I8":", j) ; + P4 (" "ID"\n", p) ; + if (!(p == EMPTY || p > j)) + { + ERR ("invalid") ; + } + } + P3 ("%s", " OK\n") ; + P4 ("%s", "\n") ; + return (TRUE) ; +} + + +int CHOLMOD(check_parent) +( + /* ---- input ---- */ + Int *Parent, /* Parent [0:n-1] is an elimination tree */ + size_t n, /* size of Parent */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_parent (Parent, n, 0, NULL, Common)) ; +} + + +int CHOLMOD(print_parent) +( + /* ---- input ---- */ + Int *Parent, /* Parent [0:n-1] is an elimination tree */ + size_t n, /* size of Parent */ + const char *name, /* printed name of Parent */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_parent (Parent, n, Common->print, name, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_check_factor ================================================= */ +/* ========================================================================== */ + +static int check_factor +( + Int *Wi, + Int print, + const char *name, + cholmod_factor *L, + cholmod_common *Common +) +{ + double *Lx, *Lz ; + Int *Lp, *Li, *Lnz, *Lnext, *Lprev, *Perm, *ColCount, *Lpi, *Lpx, *Super, + *Ls ; + Int n, nzmax, j, p, pend, i, nz, ordering, space, is_monotonic, minor, + count, precise, init_print, ilast, lnz, head, tail, jprev, plast, + jnext, examine_super, nsuper, s, k1, k2, psi, psend, psx, nsrow, nscol, + ps2, psxend, ssize, xsize, maxcsize, maxesize, nsrow2, jj, ii, xtype ; + Int for_cholesky ; + const char *type = "factor" ; + + /* ---------------------------------------------------------------------- */ + /* print header information */ + /* ---------------------------------------------------------------------- */ + + P4 ("%s", "\n") ; + P3 ("%s", "CHOLMOD factor: ") ; + if (name != NULL) + { + P3 ("%s: ", name) ; + } + + if (L == NULL) + { + ERR ("null") ; + } + + n = L->n ; + minor = L->minor ; + ordering = L->ordering ; + xtype = L->xtype ; + + Perm = L->Perm ; + ColCount = L->ColCount ; + lnz = 0 ; + + precise = Common->precise ; + + P3 (" "ID"", n) ; + P3 ("-by-"ID"", n) ; + + if (minor < n) + { + P3 (" not positive definite (column "ID")", minor) ; + } + + switch (L->itype) + { + case CHOLMOD_INT: P4 ("%s", "\n scalar types: int, ") ; break ; + case CHOLMOD_INTLONG: ERR ("mixed int/long type unsupported") ; + case CHOLMOD_LONG: P4 ("%s", "\n scalar types: SuiteSparse_long, "); + break ; + default: ERR ("unknown itype") ; + } + + switch (L->xtype) + { + case CHOLMOD_PATTERN: P4 ("%s", "pattern") ; break ; + case CHOLMOD_REAL: P4 ("%s", "real") ; break ; + case CHOLMOD_COMPLEX: P4 ("%s", "complex") ; break ; + case CHOLMOD_ZOMPLEX: P4 ("%s", "zomplex") ; break ; + default: ERR ("unknown xtype") ; + } + + switch (L->dtype) + { + case CHOLMOD_DOUBLE: P4 ("%s", ", double\n") ; break ; + case CHOLMOD_SINGLE: ERR ("single unsupported") ; + default: ERR ("unknown dtype") ; + } + + if (L->itype != ITYPE || L->dtype != DTYPE) + { + ERR ("integer and real type must match routine") ; + } + + if (L->is_super) + { + P3 ("%s", " supernodal") ; + } + else + { + P3 ("%s", " simplicial") ; + } + + if (L->is_ll) + { + P3 ("%s", ", LL'.") ; + } + else + { + P3 ("%s", ", LDL'.") ; + } + + P4 ("%s", "\n ordering method used: ") ; + switch (L->ordering) + { + case CHOLMOD_POSTORDERED:P4 ("%s", "natural (postordered)") ; break ; + case CHOLMOD_NATURAL: P4 ("%s", "natural") ; break ; + case CHOLMOD_GIVEN: P4 ("%s", "user-provided") ; break ; + case CHOLMOD_AMD: P4 ("%s", "AMD") ; break ; + case CHOLMOD_COLAMD: P4 ("%s", "AMD for A, COLAMD for A*A'") ;break ; +#ifndef NPARTITION + case CHOLMOD_METIS: P4 ("%s", "METIS NodeND") ; break ; + case CHOLMOD_NESDIS: P4 ("%s", "CHOLMOD nested dissection") ; break ; +#endif + default: ERR ("unknown ordering") ; + } + + P4 ("%s", "\n") ; + + init_print = print ; + + if (L->is_super && L->xtype == CHOLMOD_ZOMPLEX) + { + ERR ("Supernodal zomplex L not supported") ; + } + + /* ---------------------------------------------------------------------- */ + /* check L->Perm */ + /* ---------------------------------------------------------------------- */ + + if (!check_perm (Wi, print, name, Perm, n, n, Common)) + { + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* check L->ColCount */ + /* ---------------------------------------------------------------------- */ + + if (ColCount == NULL) + { + ERR ("ColCount vector invalid") ; + } + + ETC_START (count, 8) ; + for (j = 0 ; j < n ; j++) + { + ETC (j >= n-4, count, -1) ; + P4 (" col: "ID" ", j) ; + nz = ColCount [j] ; + P4 ("colcount: "ID"\n", nz) ; + if (nz < 0 || nz > n-j) + { + ERR ("ColCount out of range") ; + } + } + + /* ---------------------------------------------------------------------- */ + /* check factor */ + /* ---------------------------------------------------------------------- */ + + if (L->xtype == CHOLMOD_PATTERN && !(L->is_super)) + { + + /* ------------------------------------------------------------------ */ + /* check simplicial symbolic factor */ + /* ------------------------------------------------------------------ */ + + /* nothing else to do */ ; + + } + else if (L->xtype != CHOLMOD_PATTERN && !(L->is_super)) + { + + /* ------------------------------------------------------------------ */ + /* check simplicial numerical factor */ + /* ------------------------------------------------------------------ */ + + P4 ("monotonic: %d\n", L->is_monotonic) ; + nzmax = L->nzmax ; + P3 (" nzmax "ID".", nzmax) ; + P4 ("%s", "\n") ; + Lp = L->p ; + Li = L->i ; + Lx = L->x ; + Lz = L->z ; + Lnz = L->nz ; + Lnext = L->next ; + Lprev = L->prev ; + + /* check for existence of Lp, Li, Lnz, Lnext, Lprev, and Lx arrays */ + if (Lp == NULL) + { + ERR ("p array not present") ; + } + if (Li == NULL) + { + ERR ("i array not present") ; + } + if (Lnz == NULL) + { + ERR ("nz array not present") ; + } + if (Lx == NULL) + { + ERR ("x array not present") ; + } + if (xtype == CHOLMOD_ZOMPLEX && Lz == NULL) + { + ERR ("z array not present") ; + } + if (Lnext == NULL) + { + ERR ("next array not present") ; + } + if (Lprev == NULL) + { + ERR ("prev array not present") ; + } + + ETC_START (count, 8) ; + + /* check each column of L */ + plast = 0 ; + is_monotonic = TRUE ; + for (j = 0 ; j < n ; j++) + { + ETC (j >= n-3, count, -1) ; + p = Lp [j] ; + nz = Lnz [j] ; + pend = p + nz ; + lnz += nz ; + + P4 (" col "ID":", j) ; + P4 (" nz "ID"", nz) ; + P4 (" start "ID"", p) ; + P4 (" end "ID"", pend) ; + + if (Lnext [j] < 0 || Lnext [j] > n) + { + ERR ("invalid link list") ; + } + space = Lp [Lnext [j]] - p ; + + P4 (" space "ID"", space) ; + P4 (" free "ID":\n", space - nz) ; + + if (p < 0 || pend > nzmax || space < 1) + { + ERR ("pointer invalid") ; + } + if (nz < 1 || nz > (n-j) || nz > space) + { + ERR ("nz invalid") ; + } + ilast = j-1 ; + + if (p < plast) + { + is_monotonic = FALSE ; + } + plast = p ; + + i = Li [p] ; + P4 (" "I8":", i) ; + if (i != j) + { + ERR ("diagonal missing") ; + } + + print_value (print, xtype, Lx, Lz, p, Common) ; + + P4 ("%s", "\n") ; + ilast = j ; + for (p++ ; p < pend ; p++) + { + ETC_DISABLE (count) ; + i = Li [p] ; + P4 (" "I8":", i) ; + if (i < j || i >= n) + { + ERR ("row index out of range") ; + } + if (i <= ilast) + { + ERR ("row indices out of order") ; + } + + print_value (print, xtype, Lx, Lz, p, Common) ; + + P4 ("%s", "\n") ; + ilast = i ; + } + } + + if (L->is_monotonic && !is_monotonic) + { + ERR ("columns not monotonic") ; + } + + /* check the link list */ + head = n+1 ; + tail = n ; + j = head ; + jprev = EMPTY ; + count = 0 ; + for ( ; ; ) + { + if (j < 0 || j > n+1 || count > n+2) + { + ERR ("invalid link list") ; + } + jnext = Lnext [j] ; + if (j >= 0 && j < n) + { + if (jprev != Lprev [j]) + { + ERR ("invalid link list") ; + } + } + count++ ; + if (j == tail) + { + break ; + } + jprev = j ; + j = jnext ; + } + if (Lnext [tail] != EMPTY || count != n+2) + { + ERR ("invalid link list") ; + } + + } + else + { + + /* ------------------------------------------------------------------ */ + /* check supernodal numeric or symbolic factor */ + /* ------------------------------------------------------------------ */ + + nsuper = L->nsuper ; + ssize = L->ssize ; + xsize = L->xsize ; + maxcsize = L->maxcsize ; + maxesize = L->maxesize ; + Ls = L->s ; + Lpi = L->pi ; + Lpx = L->px ; + Super = L->super ; + Lx = L->x ; + ETC_START (count, 8) ; + + P4 (" ssize "ID" ", ssize) ; + P4 ("xsize "ID" ", xsize) ; + P4 ("maxcsize "ID" ", maxcsize) ; + P4 ("maxesize "ID"\n", maxesize) ; + + if (Ls == NULL) + { + ERR ("invalid: L->s missing") ; + } + if (Lpi == NULL) + { + ERR ("invalid: L->pi missing") ; + } + if (Lpx == NULL) + { + ERR ("invalid: L->px missing") ; + } + if (Super == NULL) + { + ERR ("invalid: L->super missing") ; + } + + if (L->xtype != CHOLMOD_PATTERN) + { + /* numerical supernodal factor */ + if (Lx == NULL) + { + ERR ("invalid: L->x missing") ; + } + if (Ls [0] == EMPTY) + { + ERR ("invalid: L->s not defined") ; + } + examine_super = TRUE ; + } + else + { + /* symbolic supernodal factor, but only if it has been computed */ + examine_super = (Ls [0] != EMPTY) ; + } + + if (examine_super) + { + if (Lpi [0] != 0 || MAX (1, Lpi [nsuper]) != ssize) + { + PRINT0 (("Lpi [0] "ID", Lpi [nsuper = "ID"] = "ID"\n", + Lpi [0], nsuper, Lpi [nsuper])) ; + ERR ("invalid: L->pi invalid") ; + } + + for_cholesky = (Lpx [0] != 123456) ; + if (for_cholesky && (Lpx [0] != 0 || MAX (1, Lpx[nsuper]) != xsize)) + { + ERR ("invalid: L->px invalid") ; + } + + /* check and print each supernode */ + for (s = 0 ; s < nsuper ; s++) + { + k1 = Super [s] ; + k2 = Super [s+1] ; + psi = Lpi [s] ; + psend = Lpi [s+1] ; + nsrow = psend - psi ; + nscol = k2 - k1 ; + nsrow2 = nsrow - nscol ; + ps2 = psi + nscol ; + + if (for_cholesky) + { + psx = Lpx [s] ; + psxend = Lpx [s+1] ; + } + + ETC (s == nsuper-1, count, 4) ; + + P4 (" supernode "ID", ", s) ; + P4 ("col "ID" ", k1) ; + P4 ("to "ID". ", k2-1) ; + P4 ("nz in first col: "ID".\n", nsrow) ; + + if (for_cholesky) + { + P4 (" values start "ID", ", psx) ; + P4 ("end "ID"\n", psxend) ; + } + + if (k1 > k2 || k1 < 0 || k2 > n || nsrow < nscol || nsrow2 < 0 + || (for_cholesky && psxend - psx != nsrow * nscol)) + { + ERR ("invalid supernode") ; + } + + lnz += nscol * nsrow - (nscol*nscol - nscol)/2 ; + + if (L->xtype != CHOLMOD_PATTERN) + { + /* print each column of the supernode */ + for (jj = 0 ; jj < nscol ; jj++) + { + ETC_ENABLE (s == nsuper-1 && jj >= nscol-3, count, -1) ; + j = k1 + jj ; + P4 (" col "ID"\n", j) ; + ilast = j ; + i = Ls [psi + jj] ; + P4 (" "I8":", i) ; + if (i != j) + { + ERR ("row index invalid") ; + } + + /* PRINTVALUE (Lx [psx + jj + jj*nsrow]) ; */ + print_value (print, xtype, Lx, NULL, + psx + jj + jj*nsrow, Common) ; + + P4 ("%s", "\n") ; + for (ii = jj + 1 ; ii < nsrow ; ii++) + { + ETC_DISABLE (count) ; + i = Ls [psi + ii] ; + P4 (" "I8":", i) ; + if (i <= ilast || i > n) + { + ERR ("row index out of range") ; + } + + /* PRINTVALUE (Lx [psx + ii + jj*nsrow]) ; */ + print_value (print, xtype, Lx, NULL, + psx + ii + jj*nsrow, Common) ; + + P4 ("%s", "\n") ; + ilast = i ; + } + } + } + else + { + /* just print the leading column of the supernode */ + P4 (" col "ID"\n", k1) ; + for (jj = 0 ; jj < nscol ; jj++) + { + ETC (s == nsuper-1 && jj >= nscol-3, count, -1) ; + j = k1 + jj ; + i = Ls [psi + jj] ; + P4 (" "I8"", i) ; + if (i != j) + { + ERR ("row index invalid") ; + } + P4 ("%s", "\n") ; + } + ilast = j ; + for (ii = nscol ; ii < nsrow ; ii++) + { + ETC_DISABLE (count) ; + i = Ls [psi + ii] ; + P4 (" "I8"", i) ; + if (i <= ilast || i > n) + { + ERR ("row index out of range") ; + } + P4 ("%s", "\n") ; + ilast = i ; + } + } + } + } + } + + /* factor is valid */ + P3 (" nz "ID"", lnz) ; + P3 ("%s", " OK\n") ; + P4 ("%s", "\n") ; + return (TRUE) ; +} + + +int CHOLMOD(check_factor) +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to check */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_factor (NULL, 0, NULL, L, Common)) ; +} + + +int CHOLMOD(print_factor) +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to print */ + const char *name, /* printed name of factor */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_factor (NULL, Common->print, name, L, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_check_triplet ================================================ */ +/* ========================================================================== */ + +/* Ensure a triplet matrix is valid, and optionally print it. */ + +static int check_triplet +( + Int print, + const char *name, + cholmod_triplet *T, + cholmod_common *Common +) +{ + double *Tx, *Tz ; + Int *Ti, *Tj ; + Int i, j, p, nrow, ncol, nzmax, nz, xtype, init_print, count ; + const char *type = "triplet" ; + + /* ---------------------------------------------------------------------- */ + /* print header information */ + /* ---------------------------------------------------------------------- */ + + P4 ("%s", "\n") ; + P3 ("%s", "CHOLMOD triplet: ") ; + if (name != NULL) + { + P3 ("%s: ", name) ; + } + + if (T == NULL) + { + ERR ("null") ; + } + + nrow = T->nrow ; + ncol = T->ncol ; + nzmax = T->nzmax ; + nz = T->nnz ; + Ti = T->i ; + Tj = T->j ; + Tx = T->x ; + Tz = T->z ; + xtype = T->xtype ; + + + P3 (" "ID"", nrow) ; + P3 ("-by-"ID", ", ncol) ; + P3 ("nz "ID",", nz) ; + if (T->stype > 0) + { + P3 ("%s", " upper.") ; + } + else if (T->stype < 0) + { + P3 ("%s", " lower.") ; + } + else + { + P3 ("%s", " up/lo.") ; + } + + P4 ("\n nzmax "ID", ", nzmax) ; + if (nz > nzmax) + { + ERR ("nzmax too small") ; + } + + switch (T->itype) + { + case CHOLMOD_INT: P4 ("%s", "\n scalar types: int, ") ; break ; + case CHOLMOD_INTLONG: ERR ("mixed int/long type unsupported") ; + case CHOLMOD_LONG: P4 ("%s", "\n scalar types: SuiteSparse_long, "); + break ; + default: ERR ("unknown itype") ; + } + + switch (T->xtype) + { + case CHOLMOD_PATTERN: P4 ("%s", "pattern") ; break ; + case CHOLMOD_REAL: P4 ("%s", "real") ; break ; + case CHOLMOD_COMPLEX: P4 ("%s", "complex") ; break ; + case CHOLMOD_ZOMPLEX: P4 ("%s", "zomplex") ; break ; + default: ERR ("unknown xtype") ; + } + + switch (T->dtype) + { + case CHOLMOD_DOUBLE: P4 ("%s", ", double\n") ; break ; + case CHOLMOD_SINGLE: ERR ("single unsupported") ; + default: ERR ("unknown dtype") ; + } + + if (T->itype != ITYPE || T->dtype != DTYPE) + { + ERR ("integer and real type must match routine") ; + } + + if (T->stype && nrow != ncol) + { + ERR ("symmetric but not square") ; + } + + /* check for existence of Ti, Tj, Tx arrays */ + if (Tj == NULL) + { + ERR ("j array not present") ; + } + if (Ti == NULL) + { + ERR ("i array not present") ; + } + + if (xtype != CHOLMOD_PATTERN && Tx == NULL) + { + ERR ("x array not present") ; + } + if (xtype == CHOLMOD_ZOMPLEX && Tz == NULL) + { + ERR ("z array not present") ; + } + + /* ---------------------------------------------------------------------- */ + /* check and print each entry */ + /* ---------------------------------------------------------------------- */ + + init_print = print ; + ETC_START (count, 8) ; + + for (p = 0 ; p < nz ; p++) + { + ETC (p >= nz-4, count, -1) ; + i = Ti [p] ; + P4 (" "I8":", p) ; + P4 (" "I_8"", i) ; + if (i < 0 || i >= nrow) + { + ERR ("row index out of range") ; + } + j = Tj [p] ; + P4 (" "I_8"", j) ; + if (j < 0 || j >= ncol) + { + ERR ("column index out of range") ; + } + + print_value (print, xtype, Tx, Tz, p, Common) ; + + P4 ("%s", "\n") ; + } + + /* triplet matrix is valid */ + P3 ("%s", " OK\n") ; + P4 ("%s", "\n") ; + return (TRUE) ; +} + + + +int CHOLMOD(check_triplet) +( + /* ---- input ---- */ + cholmod_triplet *T, /* triplet matrix to check */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_triplet (0, NULL, T, Common)) ; +} + + +int CHOLMOD(print_triplet) +( + /* ---- input ---- */ + cholmod_triplet *T, /* triplet matrix to print */ + const char *name, /* printed name of triplet matrix */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + return (check_triplet (Common->print, name, T, Common)) ; +} + + + +/* ========================================================================== */ +/* === CHOLMOD debugging routines =========================================== */ +/* ========================================================================== */ + +#ifndef NDEBUG + +/* The global variables present only when debugging enabled. */ +int CHOLMOD(dump) = 0 ; +int CHOLMOD(dump_malloc) = -1 ; + +/* workspace: no debug routines use workspace in Common */ + +/* ========================================================================== */ +/* === cholmod_dump_init ==================================================== */ +/* ========================================================================== */ + +void CHOLMOD(dump_init) (const char *s, cholmod_common *Common) +{ + int i = 0 ; + FILE *f ; + f = fopen ("debug", "r") ; + CHOLMOD(dump) = 0 ; + if (f != NULL) + { + i = fscanf (f, "%d", &CHOLMOD(dump)) ; + fclose (f) ; + } + PRINT1 (("%s: cholmod_dump_init, D = %d\n", s, CHOLMOD(dump))) ; +} + + +/* ========================================================================== */ +/* === cholmod_dump_sparse ================================================== */ +/* ========================================================================== */ + +/* returns nnz (diag (A)) or EMPTY if error */ + +SuiteSparse_long CHOLMOD(dump_sparse) +( + cholmod_sparse *A, + const char *name, + cholmod_common *Common +) +{ + Int *Wi ; + SuiteSparse_long nnzdiag ; + Int ok ; + + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return (0) ; + } + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + Wi = malloc (MAX (1, A->nrow) * sizeof (Int)) ; + ok = check_sparse (Wi, CHOLMOD(dump), name, A, &nnzdiag, Common) ; + if (Wi != NULL) free (Wi) ; + return (ok ? nnzdiag : EMPTY) ; +} + + +/* ========================================================================== */ +/* === cholmod_dump_factor ================================================== */ +/* ========================================================================== */ + +int CHOLMOD(dump_factor) +( + cholmod_factor *L, + const char *name, + cholmod_common *Common +) +{ + Int *Wi ; + int ok ; + + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return (TRUE) ; + } + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + Wi = malloc (MAX (1, L->n) * sizeof (Int)) ; + ok = check_factor (Wi, CHOLMOD(dump), name, L, Common) ; + if (Wi != NULL) free (Wi) ; + return (ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_dump_perm ==================================================== */ +/* ========================================================================== */ + +int CHOLMOD(dump_perm) +( + Int *Perm, + size_t len, + size_t n, + const char *name, + cholmod_common *Common +) +{ + Int *Wi ; + int ok ; + + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return (TRUE) ; + } + RETURN_IF_NULL_COMMON (FALSE) ; + Wi = malloc (MAX (1, n) * sizeof (Int)) ; + ok = check_perm (Wi, CHOLMOD(dump), name, Perm, len, n,Common) ; + if (Wi != NULL) free (Wi) ; + return (ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_dump_dense =================================================== */ +/* ========================================================================== */ + +int CHOLMOD(dump_dense) +( + cholmod_dense *X, + const char *name, + cholmod_common *Common +) +{ + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return (TRUE) ; + } + RETURN_IF_NULL_COMMON (FALSE) ; + return (check_dense (CHOLMOD(dump), name, X, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_dump_triplet ================================================= */ +/* ========================================================================== */ + +int CHOLMOD(dump_triplet) +( + cholmod_triplet *T, + const char *name, + cholmod_common *Common +) +{ + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return (TRUE) ; + } + RETURN_IF_NULL_COMMON (FALSE) ; + return (check_triplet (CHOLMOD(dump), name, T, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_dump_subset ================================================== */ +/* ========================================================================== */ + +int CHOLMOD(dump_subset) +( + Int *S, + size_t len, + size_t n, + const char *name, + cholmod_common *Common +) +{ + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return (TRUE) ; + } + RETURN_IF_NULL_COMMON (FALSE) ; + return (check_subset (S, len, n, CHOLMOD(dump), name, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_dump_parent ================================================== */ +/* ========================================================================== */ + +int CHOLMOD(dump_parent) +( + Int *Parent, + size_t n, + const char *name, + cholmod_common *Common +) +{ + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return (TRUE) ; + } + RETURN_IF_NULL_COMMON (FALSE) ; + return (check_parent (Parent, n, CHOLMOD(dump), name, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_dump_real ==================================================== */ +/* ========================================================================== */ + +void CHOLMOD(dump_real) +( + const char *name, + Real *X, SuiteSparse_long nrow, SuiteSparse_long ncol, int lower, + int xentry, cholmod_common *Common +) +{ + /* dump an nrow-by-ncol real dense matrix */ + SuiteSparse_long i, j ; + double x, z ; + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return ; + } + PRINT1 (("%s: dump_real, nrow: %ld ncol: %ld lower: %d\n", + name, nrow, ncol, lower)) ; + for (j = 0 ; j < ncol ; j++) + { + PRINT2 ((" col %ld\n", j)) ; + for (i = 0 ; i < nrow ; i++) + { + /* X is stored in column-major form */ + if (lower && i < j) + { + PRINT2 ((" %5ld: -", i)) ; + } + else + { + x = *X ; + PRINT2 ((" %5ld: %e", i, x)) ; + if (xentry == 2) + { + z = *(X+1) ; + PRINT2 ((", %e", z)) ; + } + } + PRINT2 (("\n")) ; + X += xentry ; + } + } +} + + +/* ========================================================================== */ +/* === cholmod_dump_super =================================================== */ +/* ========================================================================== */ + +void CHOLMOD(dump_super) +( + SuiteSparse_long s, + Int *Super, Int *Lpi, Int *Ls, Int *Lpx, double *Lx, + int xentry, + cholmod_common *Common +) +{ + Int k1, k2, do_values, psi, psx, nsrow, nscol, psend, ilast, p, i ; + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return ; + } + k1 = Super [s] ; + k2 = Super [s+1] ; + nscol = k2 - k1 ; + do_values = (Lpx != NULL) && (Lx != NULL) ; + psi = Lpi [s] ; + psend = Lpi [s+1] ; + nsrow = psend - psi ; + PRINT1 (("\nSuper %ld, columns "ID" to "ID", "ID" rows "ID" cols\n", + s, k1, k2-1, nsrow, nscol)) ; + ilast = -1 ; + for (p = psi ; p < psend ; p++) + { + i = Ls [p] ; + PRINT2 ((" "ID" : p-psi "ID"\n", i, p-psi)) ; + ASSERT (IMPLIES (p-psi < nscol, i == k1 + (p-psi))) ; + if (p-psi == nscol-1) PRINT2 (("------\n")) ; + ASSERT (i > ilast) ; + ilast = i ; + } + if (do_values) + { + psx = Lpx [s] ; + CHOLMOD(dump_real) ("Supernode", Lx + xentry*psx, nsrow, nscol, TRUE, + xentry, Common) ; + } +} + + +/* ========================================================================== */ +/* === cholmod_dump_mem ===================================================== */ +/* ========================================================================== */ + +int CHOLMOD(dump_mem) +( + const char *where, + SuiteSparse_long should, + cholmod_common *Common +) +{ + SuiteSparse_long diff = should - Common->memory_inuse ; + if (diff != 0) + { + PRINT0 (("mem: %-15s peak %10g inuse %10g should %10g\n", + where, (double) Common->memory_usage, (double) Common->memory_inuse, + (double) should)) ; + PRINT0 (("mem: %s diff %ld !\n", where, diff)) ; + } + return (diff == 0) ; +} + + +/* ========================================================================== */ +/* === cholmod_dump_partition =============================================== */ +/* ========================================================================== */ + +/* make sure we have a proper separator (for debugging only) + * + * workspace: none + */ + +int CHOLMOD(dump_partition) +( + SuiteSparse_long n, + Int *Cp, + Int *Ci, + Int *Cnw, + Int *Part, + SuiteSparse_long sepsize, + cholmod_common *Common +) +{ + Int chek [3], which, ok, i, j, p ; + PRINT1 (("bisect sepsize %ld\n", sepsize)) ; + ok = TRUE ; + chek [0] = 0 ; + chek [1] = 0 ; + chek [2] = 0 ; + for (j = 0 ; j < n ; j++) + { + PRINT2 (("--------j "ID" in part "ID" nw "ID"\n", j, Part [j], Cnw[j])); + which = Part [j] ; + for (p = Cp [j] ; p < Cp [j+1] ; p++) + { + i = Ci [p] ; + PRINT3 (("i "ID", part "ID"\n", i, Part [i])) ; + if (which == 0) + { + if (Part [i] == 1) + { + PRINT0 (("Error! "ID" "ID"\n", i, j)) ; + ok = FALSE ; + } + } + else if (which == 1) + { + if (Part [i] == 0) + { + PRINT0 (("Error! "ID" "ID"\n", i, j)) ; + ok = FALSE ; + } + } + } + if (which < 0 || which > 2) + { + PRINT0 (("Part out of range\n")) ; + ok = FALSE ; + } + chek [which] += Cnw [j] ; + } + PRINT1 (("sepsize %ld check "ID" "ID" "ID"\n", + sepsize, chek[0], chek[1],chek[2])); + if (sepsize != chek[2]) + { + PRINT0 (("mismatch!\n")) ; + ok = FALSE ; + } + return (ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_dump_work ==================================================== */ +/* ========================================================================== */ + +int CHOLMOD(dump_work) (int flag, int head, SuiteSparse_long wsize, + cholmod_common *Common) +{ + double *W ; + Int *Flag, *Head ; + Int k, nrow, mark ; + + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return (TRUE) ; + } + + RETURN_IF_NULL_COMMON (FALSE) ; + nrow = Common->nrow ; + Flag = Common->Flag ; + Head = Common->Head ; + W = Common->Xwork ; + mark = Common->mark ; + + if (wsize < 0) + { + /* check all of Xwork */ + wsize = Common->xworksize ; + } + else + { + /* check on the first wsize doubles in Xwork */ + wsize = MIN (wsize, (Int) (Common->xworksize)) ; + } + + if (flag) + { + for (k = 0 ; k < nrow ; k++) + { + if (Flag [k] >= mark) + { + PRINT0 (("Flag invalid, Flag ["ID"] = "ID", mark = "ID"\n", + k, Flag [k], mark)) ; + ASSERT (0) ; + return (FALSE) ; + } + } + } + + if (head) + { + for (k = 0 ; k < nrow ; k++) + { + if (Head [k] != EMPTY) + { + PRINT0 (("Head invalid, Head ["ID"] = "ID"\n", k, Head [k])) ; + ASSERT (0) ; + return (FALSE) ; + } + } + } + + for (k = 0 ; k < wsize ; k++) + { + if (W [k] != 0.) + { + PRINT0 (("W invalid, W ["ID"] = %g\n", k, W [k])) ; + ASSERT (0) ; + return (FALSE) ; + } + } + + return (TRUE) ; +} +#endif +#endif diff --git a/src/CHOLMOD/Check/cholmod_read.c b/src/CHOLMOD/Check/cholmod_read.c new file mode 100644 index 0000000..144fd60 --- /dev/null +++ b/src/CHOLMOD/Check/cholmod_read.c @@ -0,0 +1,1319 @@ +/* ========================================================================== */ +/* === Check/cholmod_read =================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Check Module. Copyright (C) 2005-2006, Timothy A. Davis. + * The CHOLMOD/Check Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Read a sparse matrix in triplet or dense form. A triplet matrix can be + * returned as compressed-column sparse matrix. The file format is compatible + * with all variations of the Matrix Market "coordinate" and "array" format + * (http://www.nist.gov/MatrixMarket). The format supported by these routines + * also allow other formats, where the Matrix Market header is optional. + * + * Although the Matrix Market header is optional, I recommend that users stick + * with the strict Matrix Market format. The optional format appears here to + * support the reading of symmetric matrices stored with just their upper + * triangular parts present, for testing and development of the A->stype > 0 + * format in CHOLMOD. That format is not included in the Matrix Market format. + * + * If the first line of the file starts with %%MatrixMarket, then it is + * interpretted as a file in Matrix Market format. This line must have + * the following format: + * + * %%MatrixMarket matrix + * + * is one of: coordinate or array. The former is a sparse matrix in + * triplet form. The latter is a dense matrix in column-major form. + * + * is one of: real, complex, pattern, or integer. + * The functions here convert the "integer" and "pattern" types to real. + * + * is one of: general, hermitian, symmetric, or skew-symmetric + * + * The strings are case-insensitive. Only the first character is + * significant (or the first two for skew-symmetric). + * + * is ignored for all matrices; the actual type (real, complex, + * or pattern) is inferred from the number of tokens in each line of the + * file. For a "coordinate" matrix: 2: pattern, 3: real, 4: complex; for + * a dense "array" matrix: 1: real, 2: complex. This is compatible with + * the Matrix Market format, since pattern matrices must have two tokens + * per line, real matrices must have 3, and complex matrices must have 4. + * A storage of "general" implies an stype of zero (see below). + * "symmetric" and "hermitian" imply an stype of -1. Skew-symmetric and + * complex symmetric matrices are always returned with both upper and lower + * triangular parts present, with an stype of zero, since CHOLMOD does not + * have a method for representing skew-symmetric and complex symmetric + * matrices. Real symmetric and complex Hermitian matrices may optionally + * be returned with both parts present. + * + * Any other lines starting with "%" are treated as comments, and are ignored. + * Blank lines are ignored. The Matrix Market header is optional in this + * routine (it is not optional in the Matrix Market format). + * + * Note that complex matrices are always returned in CHOLMOD_COMPLEX format, + * not CHOLMOD_ZOMPLEX. + * + * ----------------------------------------------------------------------------- + * Triplet matrices: + * ----------------------------------------------------------------------------- + * + * The first data line of a triplet matrix contains 3 or 4 integers: + * + * nrow ncol nnz stype + * + * where stype is optional (stype does not appear in the Matrix Market format). + * The matrix is nrow-by-ncol. The following nnz lines (excluding comments + * and blank lines) each contain a single entry. Duplicates are permitted, + * and are summed in the output matrix. + * + * The stype is first derived from the Matrix Market header. If the stype + * appears as the fourth integer in the first data line, it is determined from + * that line. + * + * If stype is present, it denotes the storage format for the matrix. + * stype = 0 denotes an unsymmetric matrix (same as Matrix Market "general"). + * stype = -1 denotes a real symmetric or complex Hermitian matrix whose lower + * triangular entries are stored. Entries may be present in the upper + * triangular part, but these are ignored (same as Matrix Market + * "real symmetric" and "complex Hermitian"). + * stype = 1 denotes a real symmetric or complex Hermitian matrix whose upper + * triangular entries are stored. Entries may be present in the lower + * triangular part, but these are ignored. This option is not present + * in the Matrix Market format. + * + * If stype is not present (no Matrix Market header and not in the first data + * line) it is inferred from the rest of the data. If the matrix is + * rectangular, or has entries in both the upper and lower triangular parts, + * then it is assumed to be unsymmetric (stype=0). If only entries in the + * lower triangular part are present, the matrix is assumed to have stype = -1. + * If only entries in the upper triangular part are present, the matrix is + * assumed to have stype = 1. + * + * After the first data line (with nrow, ncol, nnz, and optionally stype), + * each nonzero consists of one line with 2, 3, or 4 entries. All lines must + * have the same number of entries. The first two entries are the row and + * column indices of the nonzero. If 3 entries are present, the 3rd entry is + * the numerical value, and the matrix is real. If 4 entries are present, + * the 3rd and 4th entries in the line are the real and imaginary parts of + * a complex value. + * + * The matrix can be either 0-based or 1-based. It is first assumed to be + * one-based (all matrices in the Matrix Market are one-based), with row indices + * in the range 1 to ncol and column indices in the range 1 to nrow. If a row + * or column index of zero is found, the matrix is assumed to be zero-based + * (with row indices in the range 0 to ncol-1 and column indices in the range 0 + * to nrow-1). + * + * If Common->prefer_binary is set to its default value of FALSE, then + * for symmetric pattern-only matrices, the kth diagonal (if present) is set to + * one plus the degree of the row/column k, and the off-diagonal entries are set + * to -1. A symmetric pattern-only matrix with a zero-free diagonal is thus + * converted into a symmetric positive definite matrix. All entries are set to + * one for an unsymmetric pattern-only matrix. This differs from the + * Matrix Market format (A = mmread ('file') returns a binary pattern for A for + * symmetric pattern-only matrices). If Common->prefer_binary is TRUE, then + * this function returns a binary matrix (just like mmread('file')). + * + * ----------------------------------------------------------------------------- + * Dense matrices: + * ----------------------------------------------------------------------------- + * + * A dense matrix is specified by the Matrix Market "array" format. The + * Matrix Market header is optional; if not present, the matrix is assumed to + * be in the Matrix Market "general" format. The first data line contains just + * two integers: + * + * nrow ncol + * + * The can be real, integer, or complex (not pattern). These functions + * convert an integer type to real. The entries in the matrix are stored in + * column-major format, with one line per entry. Two entries are present in + * each line for complex matrices, one for real and integer matrices. In + * rectangular and unsymmetric matrices, all entries are present. For real + * symmetric or complex Hermitian matrices, only entries in the lower triangular + * part appear. For skew-symmetric matrices, only entries in the strictly + * lower triangular part appear. + * + * Since CHOLMOD does not have a data structure for presenting dense symmetric/ + * Hermitian matrices, these functions always return a dense matrix in its + * general form, with both upper and lower parts present. + */ + +#ifndef NCHECK + +#include "cholmod_internal.h" +#include "cholmod_check.h" +#include +#include + +/* The MatrixMarket format specificies a maximum line length of 1024 */ +#define MAXLINE 1030 + +/* ========================================================================== */ +/* === get_line ============================================================= */ +/* ========================================================================== */ + +/* Read one line of the file, return TRUE if successful, FALSE if EOF. */ + +static int get_line (FILE *f, char *buf) +{ + buf [0] = '\0' ; + buf [1] = '\0' ; + buf [MAXLINE] = '\0' ; + return (fgets (buf, MAXLINE, f) != NULL) ; +} + +/* ========================================================================== */ +/* === fix_inf ============================================================== */ +/* ========================================================================== */ + +/* Replace huge values with +/- Inf's, since scanf and printf don't deal + * with Inf's properly. + */ + +static double fix_inf (double x) +{ + if ((x >= HUGE_DOUBLE) || (x <= -HUGE_DOUBLE)) + { + /* treat this as +/- Inf (assume 2*x leads to overflow) */ + x = 2*x ; + } + return (x) ; +} + +/* ========================================================================== */ +/* === is_blank_line ======================================================== */ +/* ========================================================================== */ + +/* TRUE if s is a blank line or comment, FALSE otherwise */ + +static int is_blank_line +( + char *s +) +{ + int c, k ; + if (s [0] == '%') + { + /* a comment line */ + return (TRUE) ; + } + for (k = 0 ; k <= MAXLINE ; k++) + { + c = s [k] ; + if (c == '\0') + { + /* end of line */ + break ; + } + if (!isspace (c)) + { + /* non-space character */ + return (FALSE) ; + } + } + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === read_header ========================================================== */ +/* ========================================================================== */ + +/* Read the header. This consists of zero or more comment lines (blank, or + * starting with a "%" in the first column), followed by a single data line + * containing up to four numerical values. + * + * The first line may optionally be a Matrix Market header line, of the form + * + * %%MatrixMarket matrix + * + * The first data line of a sparse matrix in triplet form consists of 3 or 4 + * numerical values: + * + * nrow ncol nnz stype + * + * where stype is optional (it does not appear in the Matrix Market file + * format). The first line of a dense matrix in column-major form consists of + * two numerical values: + * + * nrow ncol + * + * The stype of the matrix is determine either from the Matrix Market header, + * or (optionally) from the first data line. stypes of 0 to -3 directly + * correlate with the Matrix Market format; stype = 1 is an extension to that + * format. + * + * 999: unknown (will be inferred from the data) + * 1: real symmetric or complex Hermitian with upper part stored + * (not in the Matrix Market format) + * 0: unsymmetric (same as Matrix Market "general") + * -1: real symmetric or complex Hermitian, with lower part stored + * (Matrix Market "real symmetric" or "complex hermitian") + * -2: real or complex skew symmetric (lower part stored, can only be + * specified by Matrix Market header) + * -3: complex symmetric (lower part stored) + * specified by Matrix Market header) + * + * The Matrix Market header is optional. If stype appears in the first data + * line, it is determine by that data line. Otherwise, if the Matrix Market + * header appears, stype is determined from that header. If stype does not + * appear, it is set to "unknown" (999). + */ + +#define STYPE_UNKNOWN 999 +#define STYPE_SYMMETRIC_UPPER 1 +#define STYPE_UNSYMMETRIC 0 +#define STYPE_SYMMETRIC_LOWER -1 +#define STYPE_SKEW_SYMMETRIC -2 +#define STYPE_COMPLEX_SYMMETRIC_LOWER -3 + +static int read_header /* returns TRUE if successful, FALSE on error */ +( + /* ---- input ---- */ + FILE *f, /* file to read from */ + /* ---- output --- */ + char *buf, /* a character array of size MAXLINE+1 */ + int *mtype, /* CHOLMOD_TRIPLET or CHOLMOD_DENSE */ + size_t *nrow, /* number of rows in the matrix */ + size_t *ncol, /* number of columns in the matrix */ + size_t *nnz, /* number of entries in a triplet matrix (0 for dense)*/ + int *stype /* stype (see above) */ +) +{ + char *p ; + int first = TRUE, got_mm_header = FALSE, c, c2, is_complex, nitems ; + double l1, l2, l3, l4 ; + + *mtype = CHOLMOD_TRIPLET ; + *nrow = 0 ; + *ncol = 0 ; + *nnz = 0 ; + *stype = STYPE_UNKNOWN ; + + for ( ; ; ) + { + + /* ------------------------------------------------------------------ */ + /* get the next line */ + /* ------------------------------------------------------------------ */ + + if (!get_line (f, buf)) + { + /* premature end of file */ + return (FALSE) ; + } + + if (first && (strncmp (buf, "%%MatrixMarket", 14) == 0)) + { + + /* -------------------------------------------------------------- */ + /* read a Matrix Market header */ + /* -------------------------------------------------------------- */ + + got_mm_header = TRUE ; + p = buf ; + + /* -------------------------------------------------------------- */ + /* get "matrix" token */ + /* -------------------------------------------------------------- */ + + while (*p && !isspace (*p)) p++ ; + while (*p && isspace (*p)) p++ ; + c = tolower (*p) ; + if (c != 'm') + { + /* bad format */ + return (FALSE) ; + } + + /* -------------------------------------------------------------- */ + /* get the fmt token ("coord" or "array") */ + /* -------------------------------------------------------------- */ + + while (*p && !isspace (*p)) p++ ; + while (*p && isspace (*p)) p++ ; + c = tolower (*p) ; + if (c == 'c') + { + *mtype = CHOLMOD_TRIPLET ; + } + else if (c == 'a') + { + *mtype = CHOLMOD_DENSE ; + } + else + { + /* bad format, neither "coordinate" nor "array" */ + return (FALSE) ; + } + + /* -------------------------------------------------------------- */ + /* get type token (real, pattern, complex, integer) */ + /* -------------------------------------------------------------- */ + + while (*p && !isspace (*p)) p++ ; + while (*p && isspace (*p)) p++ ; + c = tolower (*p) ; + if (!(c == 'r' || c == 'p' || c == 'c' || c == 'i')) + { + /* bad format */ + return (FALSE) ; + } + is_complex = (c == 'c') ; + + /* -------------------------------------------------------------- */ + /* get storage token (general, hermitian, symmetric, skew) */ + /* -------------------------------------------------------------- */ + + while (*p && !isspace (*p)) p++ ; + while (*p && isspace (*p)) p++ ; + c = tolower (*p) ; + c2 = tolower (*(p+1)) ; + if (c == 'g') + { + /* "general" storage (unsymmetric matrix), both parts present */ + *stype = STYPE_UNSYMMETRIC ; + } + else if (c == 's' && c2 == 'y') + { + /* "symmetric" */ + if (is_complex) + { + /* complex symmetric, lower triangular part present */ + *stype = STYPE_COMPLEX_SYMMETRIC_LOWER ; + } + else + { + /* real symmetric, lower triangular part present */ + *stype = STYPE_SYMMETRIC_LOWER ; + } + } + else if (c == 'h') + { + /* "hermitian" matrix, lower triangular part present */ + *stype = STYPE_SYMMETRIC_LOWER ; + } + else if (c == 's' && c2 == 'k') + { + /* "skew-symmetric" (real or complex), lower part present */ + *stype = STYPE_SKEW_SYMMETRIC ; + } + else + { + /* bad format */ + return (FALSE) ; + } + + } + else if (is_blank_line (buf)) + { + + /* -------------------------------------------------------------- */ + /* blank line or comment line */ + /* -------------------------------------------------------------- */ + + continue ; + + } + else + { + + /* -------------------------------------------------------------- */ + /* read the first data line and return */ + /* -------------------------------------------------------------- */ + + /* format: nrow ncol nnz stype */ + l1 = EMPTY ; + l2 = EMPTY ; + l3 = 0 ; + l4 = 0 ; + nitems = sscanf (buf, "%lg %lg %lg %lg\n", &l1, &l2, &l3, &l4) ; + if (nitems < 2 || nitems > 4 || l1 > Int_max || l2 > Int_max) + { + /* invalid matrix */ + return (FALSE) ; + } + *nrow = l1 ; + *ncol = l2 ; + if (nitems == 2) + { + /* a dense matrix */ + if (!got_mm_header) + { + *mtype = CHOLMOD_DENSE ; + *stype = STYPE_UNSYMMETRIC ; + } + } + if (nitems == 3 || nitems == 4) + { + /* a sparse triplet matrix */ + *nnz = l3 ; + if (!got_mm_header) + { + *mtype = CHOLMOD_TRIPLET ; + } + } + if (nitems == 4) + { + /* an stype specified here can only be 1, 0, or -1 */ + if (l4 < 0) + { + *stype = STYPE_SYMMETRIC_LOWER ; + } + else if (l4 > 0) + { + *stype = STYPE_SYMMETRIC_UPPER ; + } + else + { + *stype = STYPE_UNSYMMETRIC ; + } + } + if (*nrow != *ncol) + { + /* a rectangular matrix must be unsymmetric */ + *stype = STYPE_UNSYMMETRIC ; + } + return (TRUE) ; + } + + first = FALSE ; + } +} + + +/* ========================================================================== */ +/* === read_triplet ========================================================= */ +/* ========================================================================== */ + +/* Header has already been read in, including first line (nrow ncol nnz stype). + * Read the triplets. */ + +static cholmod_triplet *read_triplet +( + /* ---- input ---- */ + FILE *f, /* file to read from, must already be open */ + size_t nrow, /* number of rows */ + size_t ncol, /* number of columns */ + size_t nnz, /* number of triplets in file to read */ + int stype, /* stype from header, or "unknown" */ + int prefer_unsym, /* if TRUE, always return T->stype of zero */ + /* ---- workspace */ + char *buf, /* of size MAXLINE+1 */ + /* --------------- */ + cholmod_common *Common +) +{ + double x, z ; + double *Tx ; + Int *Ti, *Tj, *Rdeg, *Cdeg ; + cholmod_triplet *T ; + double l1, l2 ; + Int nitems, xtype, unknown, k, nshould, is_lower, is_upper, one_based, i, j, + imax, jmax, skew_symmetric, p, complex_symmetric ; + size_t s, nnz2, extra ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* quick return for empty matrix */ + /* ---------------------------------------------------------------------- */ + + if (nrow == 0 || ncol == 0 || nnz == 0) + { + /* return an empty matrix */ + return (CHOLMOD(allocate_triplet) (nrow, ncol, 0, 0, CHOLMOD_REAL, + Common)) ; + } + + /* ---------------------------------------------------------------------- */ + /* special stype cases: unknown, skew symmetric, and complex symmetric */ + /* ---------------------------------------------------------------------- */ + + unknown = (stype == STYPE_UNKNOWN) ; + skew_symmetric = (stype == STYPE_SKEW_SYMMETRIC) ; + complex_symmetric = (stype == STYPE_COMPLEX_SYMMETRIC_LOWER) ; + + extra = 0 ; + if (stype < STYPE_SYMMETRIC_LOWER + || (prefer_unsym && stype != STYPE_UNSYMMETRIC)) + { + /* 999: unknown might be converted to unsymmetric */ + /* 1: symmetric upper converted to unsym. if prefer_unsym is TRUE */ + /* -1: symmetric lower converted to unsym. if prefer_unsym is TRUE */ + /* -2: real or complex skew symmetric converted to unsymmetric */ + /* -3: complex symmetric converted to unsymmetric */ + stype = STYPE_UNSYMMETRIC ; + extra = nnz ; + } + nnz2 = CHOLMOD(add_size_t) (nnz, extra, &ok) ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* s = nrow + ncol */ + s = CHOLMOD(add_size_t) (nrow, ncol, &ok) ; + if (!ok || nrow > Int_max || ncol > Int_max || nnz > Int_max) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (NULL) ; + } + + CHOLMOD(allocate_work) (0, s, 0, Common) ; + Rdeg = Common->Iwork ; /* size nrow */ + Cdeg = Rdeg + nrow ; /* size ncol */ + + /* ---------------------------------------------------------------------- */ + /* read the triplets */ + /* ---------------------------------------------------------------------- */ + + is_lower = TRUE ; + is_upper = TRUE ; + one_based = TRUE ; + imax = 0 ; + jmax = 0 ; + + Tx = NULL ; + Ti = NULL ; + Tj = NULL ; + xtype = 999 ; + nshould = 0 ; + + for (k = 0 ; k < (Int) nnz ; k++) + { + + /* ------------------------------------------------------------------ */ + /* get the next triplet, skipping blank lines and comment lines */ + /* ------------------------------------------------------------------ */ + + l1 = EMPTY ; + l2 = EMPTY ; + x = 0 ; + z = 0 ; + + for ( ; ; ) + { + if (!get_line (f, buf)) + { + /* premature end of file - not enough triplets read in */ + ERROR (CHOLMOD_INVALID, "premature EOF") ; + return (NULL) ; + } + if (is_blank_line (buf)) + { + /* blank line or comment */ + continue ; + } + nitems = sscanf (buf, "%lg %lg %lg %lg\n", &l1, &l2, &x, &z) ; + x = fix_inf (x) ; + z = fix_inf (z) ; + break ; + } + + nitems = (nitems == EOF) ? 0 : nitems ; + i = l1 ; + j = l2 ; + + /* ------------------------------------------------------------------ */ + /* for first triplet: determine type and allocate triplet matrix */ + /* ------------------------------------------------------------------ */ + + if (k == 0) + { + if (nitems < 2 || nitems > 4) + { + /* invalid matrix */ + ERROR (CHOLMOD_INVALID, "invalid format") ; + return (NULL) ; + } + else if (nitems == 2) + { + /* this will be converted into a real matrix later */ + xtype = CHOLMOD_PATTERN ; + } + else if (nitems == 3) + { + xtype = CHOLMOD_REAL ; + } + else if (nitems == 4) + { + xtype = CHOLMOD_COMPLEX ; + } + + /* the rest of the lines should have the same number of entries */ + nshould = nitems ; + + /* allocate triplet matrix */ + T = CHOLMOD(allocate_triplet) (nrow, ncol, nnz2, stype, + (xtype == CHOLMOD_PATTERN ? CHOLMOD_REAL : xtype), Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + Ti = T->i ; + Tj = T->j ; + Tx = T->x ; + T->nnz = nnz ; + } + + /* ------------------------------------------------------------------ */ + /* save the entry in the triplet matrix */ + /* ------------------------------------------------------------------ */ + + if (nitems != nshould || i < 0 || j < 0) + { + /* wrong format, premature end-of-file, or negative indices */ + CHOLMOD(free_triplet) (&T, Common) ; + ERROR (CHOLMOD_INVALID, "invalid matrix file") ; + return (NULL) ; + } + + Ti [k] = i ; + Tj [k] = j ; + + if (i < j) + { + /* this entry is in the upper triangular part */ + is_lower = FALSE ; + } + if (i > j) + { + /* this entry is in the lower triangular part */ + is_upper = FALSE ; + } + + if (xtype == CHOLMOD_REAL) + { + Tx [k] = x ; + } + else if (xtype == CHOLMOD_COMPLEX) + { + Tx [2*k ] = x ; /* real part */ + Tx [2*k+1] = z ; /* imaginary part */ + } + + if (i == 0 || j == 0) + { + one_based = FALSE ; + } + + imax = MAX (i, imax) ; + jmax = MAX (j, jmax) ; + } + + /* ---------------------------------------------------------------------- */ + /* convert to zero-based */ + /* ---------------------------------------------------------------------- */ + + if (one_based) + { + /* input matrix is one-based; convert matrix to zero-based */ + for (k = 0 ; k < (Int) nnz ; k++) + { + Ti [k]-- ; + Tj [k]-- ; + } + } + + if (one_based ? + (imax > (Int) nrow || jmax > (Int) ncol) : + (imax >= (Int) nrow || jmax >= (Int) ncol)) + { + /* indices out of range */ + CHOLMOD(free_triplet) (&T, Common) ; + ERROR (CHOLMOD_INVALID, "indices out of range") ; + return (NULL) ; + } + + /* ---------------------------------------------------------------------- */ + /* determine the stype, if not yet known */ + /* ---------------------------------------------------------------------- */ + + if (unknown) + { + if (is_lower && is_upper) + { + /* diagonal matrix, symmetric with upper part present */ + stype = STYPE_SYMMETRIC_UPPER ; + } + else if (is_lower && !is_upper) + { + /* symmetric, lower triangular part present */ + stype = STYPE_SYMMETRIC_LOWER ; + } + else if (!is_lower && is_upper) + { + /* symmetric, upper triangular part present */ + stype = STYPE_SYMMETRIC_UPPER ; + } + else + { + /* unsymmetric */ + stype = STYPE_UNSYMMETRIC ; + extra = 0 ; + } + } + + /* ---------------------------------------------------------------------- */ + /* add the remainder of symmetric, skew-symmetric or Hermitian matrices */ + /* ---------------------------------------------------------------------- */ + + /* note that this step is not done for real symmetric or complex Hermitian + * matrices, unless prefer_unsym is TRUE */ + if (extra > 0) + { + p = nnz ; + for (k = 0 ; k < (Int) nnz ; k++) + { + i = Ti [k] ; + j = Tj [k] ; + if (i != j) + { + Ti [p] = j ; + Tj [p] = i ; + if (xtype == CHOLMOD_REAL) + { + if (skew_symmetric) + { + Tx [p] = -Tx [k] ; + } + else + { + Tx [p] = Tx [k] ; + } + } + else if (xtype == CHOLMOD_COMPLEX) + { + if (skew_symmetric) + { + Tx [2*p ] = -Tx [2*k ] ; + Tx [2*p+1] = -Tx [2*k+1] ; + } + else if (complex_symmetric) + { + Tx [2*p ] = Tx [2*k ] ; + Tx [2*p+1] = Tx [2*k+1] ; + } + else /* Hermitian */ + { + Tx [2*p ] = Tx [2*k ] ; + Tx [2*p+1] = -Tx [2*k+1] ; + } + } + p++ ; + } + } + T->nnz = p ; + nnz = p ; + } + + T->stype = stype ; + + /* ---------------------------------------------------------------------- */ + /* create values for a pattern-only matrix */ + /* ---------------------------------------------------------------------- */ + + if (xtype == CHOLMOD_PATTERN) + { + if (stype == STYPE_UNSYMMETRIC || Common->prefer_binary) + { + /* unsymmetric case, or binary case */ + for (k = 0 ; k < (Int) nnz ; k++) + { + Tx [k] = 1 ; + } + } + else + { + /* compute the row and columm degrees (excluding the diagonal) */ + for (i = 0 ; i < (Int) nrow ; i++) + { + Rdeg [i] = 0 ; + } + for (j = 0 ; j < (Int) ncol ; j++) + { + Cdeg [j] = 0 ; + } + for (k = 0 ; k < (Int) nnz ; k++) + { + i = Ti [k] ; + j = Tj [k] ; + if ((stype < 0 && i > j) || (stype > 0 && i < j)) + { + /* both a(i,j) and a(j,i) appear in the matrix */ + Rdeg [i]++ ; + Cdeg [j]++ ; + Rdeg [j]++ ; + Cdeg [i]++ ; + } + } + /* assign the numerical values */ + for (k = 0 ; k < (Int) nnz ; k++) + { + i = Ti [k] ; + j = Tj [k] ; + Tx [k] = (i == j) ? (1 + MAX (Rdeg [i], Cdeg [j])) : (-1) ; + } + } + } + + /* ---------------------------------------------------------------------- */ + /* return the new triplet matrix */ + /* ---------------------------------------------------------------------- */ + + return (T) ; +} + + +/* ========================================================================== */ +/* === read_dense =========================================================== */ +/* ========================================================================== */ + +/* Header has already been read in, including first line (nrow ncol). + * Read a dense matrix. */ + +static cholmod_dense *read_dense +( + /* ---- input ---- */ + FILE *f, /* file to read from, must already be open */ + size_t nrow, /* number of rows */ + size_t ncol, /* number of columns */ + int stype, /* stype from header */ + /* ---- workspace */ + char *buf, /* of size MAXLINE+1 */ + /* --------------- */ + cholmod_common *Common +) +{ + double x, z ; + double *Xx = NULL ; + cholmod_dense *X ; + Int nitems, xtype = -1, nshould = 0, i, j, k, kup, first ; + + /* ---------------------------------------------------------------------- */ + /* quick return for empty matrix */ + /* ---------------------------------------------------------------------- */ + + if (nrow == 0 || ncol == 0) + { + /* return an empty dense matrix */ + return (CHOLMOD(zeros) (nrow, ncol, CHOLMOD_REAL, Common)) ; + } + + /* ---------------------------------------------------------------------- */ + /* read the entries */ + /* ---------------------------------------------------------------------- */ + + first = TRUE ; + + for (j = 0 ; j < (Int) ncol ; j++) + { + + /* ------------------------------------------------------------------ */ + /* get the row index of the first entry in the file for column j */ + /* ------------------------------------------------------------------ */ + + if (stype == STYPE_UNSYMMETRIC) + { + i = 0 ; + } + else if (stype == STYPE_SKEW_SYMMETRIC) + { + i = j+1 ; + } + else /* real symmetric or complex Hermitian lower */ + { + i = j ; + } + + /* ------------------------------------------------------------------ */ + /* get column j */ + /* ------------------------------------------------------------------ */ + + for ( ; i < (Int) nrow ; i++) + { + + /* -------------------------------------------------------------- */ + /* get the next entry, skipping blank lines and comment lines */ + /* -------------------------------------------------------------- */ + + x = 0 ; + z = 0 ; + for ( ; ; ) + { + + if (!get_line (f, buf)) + { + /* premature end of file - not enough entries read in */ + ERROR (CHOLMOD_INVALID, "premature EOF") ; + return (NULL) ; + } + + if (is_blank_line (buf)) + { + /* blank line or comment */ + continue ; + } + nitems = sscanf (buf, "%lg %lg\n", &x, &z) ; + x = fix_inf (x) ; + z = fix_inf (z) ; + break ; + } + + nitems = (nitems == EOF) ? 0 : nitems ; + + /* -------------------------------------------------------------- */ + /* for first entry: determine type and allocate dense matrix */ + /* -------------------------------------------------------------- */ + + if (first) + { + first = FALSE ; + + if (nitems < 1 || nitems > 2) + { + /* invalid matrix */ + ERROR (CHOLMOD_INVALID, "invalid format") ; + return (NULL) ; + } + else if (nitems == 1) + { + /* a real matrix */ + xtype = CHOLMOD_REAL ; + } + else if (nitems == 2) + { + /* a complex matrix */ + xtype = CHOLMOD_COMPLEX ; + } + + /* the rest of the lines should have same number of entries */ + nshould = nitems ; + + /* allocate the result */ + X = CHOLMOD(zeros) (nrow, ncol, xtype, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + Xx = X->x ; + } + + /* -------------------------------------------------------------- */ + /* save the entry in the dense matrix */ + /* -------------------------------------------------------------- */ + + if (nitems != nshould) + { + /* wrong format or premature end-of-file */ + CHOLMOD(free_dense) (&X, Common) ; + ERROR (CHOLMOD_INVALID, "invalid matrix file") ; + return (NULL) ; + } + + k = i + j*nrow ; + kup = j + i*nrow ; + + if (xtype == CHOLMOD_REAL) + { + /* real matrix */ + Xx [k] = x ; + if (k != kup) + { + if (stype == STYPE_SYMMETRIC_LOWER) + { + /* real symmetric matrix */ + Xx [kup] = x ; + } + else if (stype == STYPE_SKEW_SYMMETRIC) + { + /* real skew symmetric matrix */ + Xx [kup] = -x ; + } + } + } + else if (xtype == CHOLMOD_COMPLEX) + { + Xx [2*k ] = x ; /* real part */ + Xx [2*k+1] = z ; /* imaginary part */ + if (k != kup) + { + if (stype == STYPE_SYMMETRIC_LOWER) + { + /* complex Hermitian */ + Xx [2*kup ] = x ; /* real part */ + Xx [2*kup+1] = -z ; /* imaginary part */ + } + else if (stype == STYPE_SKEW_SYMMETRIC) + { + /* complex skew symmetric */ + Xx [2*kup ] = -x ; /* real part */ + Xx [2*kup+1] = -z ; /* imaginary part */ + } + if (stype == STYPE_COMPLEX_SYMMETRIC_LOWER) + { + /* complex symmetric */ + Xx [2*kup ] = x ; /* real part */ + Xx [2*kup+1] = z ; /* imaginary part */ + } + } + } + } + } + + /* ---------------------------------------------------------------------- */ + /* return the new dense matrix */ + /* ---------------------------------------------------------------------- */ + + return (X) ; +} + + +/* ========================================================================== */ +/* === cholmod_read_triplet ================================================= */ +/* ========================================================================== */ + +/* Read in a triplet matrix from a file. */ + +cholmod_triplet *CHOLMOD(read_triplet) +( + /* ---- input ---- */ + FILE *f, /* file to read from, must already be open */ + /* --------------- */ + cholmod_common *Common +) +{ + char buf [MAXLINE+1] ; + size_t nrow, ncol, nnz ; + int stype, mtype ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (f, NULL) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* read the header and first data line */ + /* ---------------------------------------------------------------------- */ + + if (!read_header (f, buf, &mtype, &nrow, &ncol, &nnz, &stype) || + mtype != CHOLMOD_TRIPLET) + { + /* invalid matrix - this function can only read in a triplet matrix */ + ERROR (CHOLMOD_INVALID, "invalid format") ; + return (NULL) ; + } + + /* ---------------------------------------------------------------------- */ + /* read the triplet matrix */ + /* ---------------------------------------------------------------------- */ + + return (read_triplet (f, nrow, ncol, nnz, stype, FALSE, buf, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_read_sparse ================================================== */ +/* ========================================================================== */ + +/* Read a sparse matrix from a file. See cholmod_read_triplet for a discussion + * of the file format. + * + * If Common->prefer_upper is TRUE (the default case), a symmetric matrix is + * returned stored in upper-triangular form (A->stype == 1). + */ + +cholmod_sparse *CHOLMOD(read_sparse) +( + /* ---- input ---- */ + FILE *f, /* file to read from, must already be open */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_sparse *A, *A2 ; + cholmod_triplet *T ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (f, NULL) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* convert to a sparse matrix in compressed-column form */ + /* ---------------------------------------------------------------------- */ + + T = CHOLMOD(read_triplet) (f, Common) ; + A = CHOLMOD(triplet_to_sparse) (T, 0, Common) ; + CHOLMOD(free_triplet) (&T, Common) ; + + if (Common->prefer_upper && A != NULL && A->stype == -1) + { + /* A=A' */ + A2 = CHOLMOD(transpose) (A, 2, Common) ; + CHOLMOD(free_sparse) (&A, Common) ; + A = A2 ; + } + return (A) ; +} + + +/* ========================================================================== */ +/* === cholmod_read_dense =================================================== */ +/* ========================================================================== */ + +/* Read a dense matrix from a file. */ + +cholmod_dense *CHOLMOD(read_dense) +( + /* ---- input ---- */ + FILE *f, /* file to read from, must already be open */ + /* --------------- */ + cholmod_common *Common +) +{ + char buf [MAXLINE+1] ; + size_t nrow, ncol, nnz ; + int stype, mtype ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (f, NULL) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* read the header and first data line */ + /* ---------------------------------------------------------------------- */ + + if (!read_header (f, buf, &mtype, &nrow, &ncol, &nnz, &stype) || + mtype != CHOLMOD_DENSE) + { + /* invalid matrix - this function can only read in a dense matrix */ + ERROR (CHOLMOD_INVALID, "invalid format") ; + return (NULL) ; + } + + /* ---------------------------------------------------------------------- */ + /* read the dense matrix */ + /* ---------------------------------------------------------------------- */ + + return (read_dense (f, nrow, ncol, stype, buf, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_read_matrix ================================================== */ +/* ========================================================================== */ + +/* Read a triplet matrix, sparse matrix or a dense matrix from a file. Returns + * a void pointer to either a cholmod_triplet, cholmod_sparse, or cholmod_dense + * object. The type of object is passed back to the caller as the mtype + * argument. */ + +void *CHOLMOD(read_matrix) +( + /* ---- input ---- */ + FILE *f, /* file to read from, must already be open */ + int prefer, /* If 0, a sparse matrix is always return as a + * cholmod_triplet form. It can have any stype + * (symmetric-lower, unsymmetric, or + * symmetric-upper). + * If 1, a sparse matrix is returned as an unsymmetric + * cholmod_sparse form (A->stype == 0), with both + * upper and lower triangular parts present. + * This is what the MATLAB mread mexFunction does, + * since MATLAB does not have an stype. + * If 2, a sparse matrix is returned with an stype of 0 + * or 1 (unsymmetric, or symmetric with upper part + * stored). + * This argument has no effect for dense matrices. + */ + /* ---- output---- */ + int *mtype, /* CHOLMOD_TRIPLET, CHOLMOD_SPARSE or CHOLMOD_DENSE */ + /* --------------- */ + cholmod_common *Common +) +{ + void *G = NULL ; + cholmod_sparse *A, *A2 ; + cholmod_triplet *T ; + char buf [MAXLINE+1] ; + size_t nrow, ncol, nnz ; + int stype ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (f, NULL) ; + RETURN_IF_NULL (mtype, NULL) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* read the header to determine the mtype */ + /* ---------------------------------------------------------------------- */ + + if (!read_header (f, buf, mtype, &nrow, &ncol, &nnz, &stype)) + { + /* invalid matrix */ + ERROR (CHOLMOD_INVALID, "invalid format") ; + return (NULL) ; + } + + /* ---------------------------------------------------------------------- */ + /* read a matrix */ + /* ---------------------------------------------------------------------- */ + + if (*mtype == CHOLMOD_TRIPLET) + { + /* read in the triplet matrix, converting to unsymmetric format if + * prefer == 1 */ + T = read_triplet (f, nrow, ncol, nnz, stype, prefer == 1, buf, Common) ; + if (prefer == 0) + { + /* return matrix in its original triplet form */ + G = T ; + } + else + { + /* return matrix in a compressed-column form */ + A = CHOLMOD(triplet_to_sparse) (T, 0, Common) ; + CHOLMOD(free_triplet) (&T, Common) ; + if (A != NULL && prefer == 2 && A->stype == -1) + { + /* convert A from symmetric-lower to symmetric-upper */ + A2 = CHOLMOD(transpose) (A, 2, Common) ; + CHOLMOD(free_sparse) (&A, Common) ; + A = A2 ; + } + *mtype = CHOLMOD_SPARSE ; + G = A ; + } + } + else if (*mtype == CHOLMOD_DENSE) + { + /* return a dense matrix */ + G = read_dense (f, nrow, ncol, stype, buf, Common) ; + } + return (G) ; +} +#endif diff --git a/src/CHOLMOD/Check/cholmod_write.c b/src/CHOLMOD/Check/cholmod_write.c new file mode 100644 index 0000000..eb578f9 --- /dev/null +++ b/src/CHOLMOD/Check/cholmod_write.c @@ -0,0 +1,744 @@ +/* ========================================================================== */ +/* === Check/cholmod_write ================================================== */ +/* ========================================================================== */ + +/* Write a matrix to a file in Matrix Market form. + * + * A can be sparse or full. + * + * If present and non-empty, A and Z must have the same dimension. Z contains + * the explicit zero entries in the matrix (which MATLAB drops). The entries + * of Z appear as explicit zeros in the output file. Z is optional. If it is + * an empty matrix it is ignored. Z must be sparse or empty, if present. + * It is ignored if A is full. + * + * filename is the name of the output file. comments is file whose + * contents are include after the Matrix Market header and before the first + * data line. Ignored if an empty string or not present. + * + * Except for the workspace used by cholmod_symmetry (ncol integers) for + * the sparse case, these routines use no workspace at all. + */ + +#ifndef NCHECK + +#include "cholmod_internal.h" +#include "cholmod_check.h" +#include "cholmod_matrixops.h" +#include +#include + +#define MMLEN 1024 +#define MAXLINE MMLEN+6 + +/* ========================================================================== */ +/* === include_comments ===================================================== */ +/* ========================================================================== */ + +/* Read in the comments file, if it exists, and copy it to the Matrix Market + * file. A "%" is prepended to each line. Returns TRUE if successful, FALSE + * otherwise. + */ + +static int include_comments (FILE *f, const char *comments) +{ + FILE *cf = NULL ; + char buffer [MAXLINE] ; + int ok = TRUE ; + if (comments != NULL && comments [0] != '\0') + { + cf = fopen (comments, "r") ; + if (cf == NULL) + { + return (FALSE) ; + } + while (ok && fgets (buffer, MAXLINE, cf) != NULL) + { + /* ensure the line is not too long */ + buffer [MMLEN-1] = '\0' ; + buffer [MMLEN-2] = '\n' ; + ok = ok && (fprintf (f, "%%%s", buffer) > 0) ; + } + fclose (cf) ; + } + return (ok) ; +} + + +/* ========================================================================== */ +/* === get_value ============================================================ */ +/* ========================================================================== */ + +/* Get the pth value in the matrix. */ + +static void get_value +( + double *Ax, /* real values, or real/imag. for CHOLMOD_COMPLEX type */ + double *Az, /* imaginary values for CHOLMOD_ZOMPLEX type */ + Int p, /* get the pth entry */ + Int xtype, /* A->xtype: pattern, real, complex, or zomplex */ + double *x, /* the real part */ + double *z /* the imaginary part */ +) +{ + switch (xtype) + { + case CHOLMOD_PATTERN: + *x = 1 ; + *z = 0 ; + break ; + + case CHOLMOD_REAL: + *x = Ax [p] ; + *z = 0 ; + break ; + + case CHOLMOD_COMPLEX: + *x = Ax [2*p] ; + *z = Ax [2*p+1] ; + break ; + + case CHOLMOD_ZOMPLEX: + *x = Ax [p] ; + *z = Az [p] ; + break ; + } +} + + +/* ========================================================================== */ +/* === print_value ========================================================== */ +/* ========================================================================== */ + +/* Print a numeric value to the file, using the shortest format that ensures + * the value is written precisely. Returns TRUE if successful, FALSE otherwise. + */ + +static int print_value +( + FILE *f, /* file to print to */ + double x, /* value to print */ + Int is_integer /* TRUE if printing as an integer */ +) +{ + double y ; + char s [MAXLINE], *p ; + Int i, dest = 0, src = 0 ; + int width, ok ; + + if (is_integer) + { + i = (Int) x ; + ok = (fprintf (f, ID, i) > 0) ; + return (ok) ; + } + + /* ---------------------------------------------------------------------- */ + /* handle Inf and NaN */ + /* ---------------------------------------------------------------------- */ + + /* change -inf to -HUGE_DOUBLE, and change +inf and nan to +HUGE_DOUBLE */ + if (CHOLMOD_IS_NAN (x) || x >= HUGE_DOUBLE) + { + x = HUGE_DOUBLE ; + } + else if (x <= -HUGE_DOUBLE) + { + x = -HUGE_DOUBLE ; + } + + /* ---------------------------------------------------------------------- */ + /* find the smallest acceptable precision */ + /* ---------------------------------------------------------------------- */ + + for (width = 6 ; width < 20 ; width++) + { + sprintf (s, "%.*g", width, x) ; + sscanf (s, "%lg", &y) ; + if (x == y) break ; + } + + /* ---------------------------------------------------------------------- */ + /* shorten the string */ + /* ---------------------------------------------------------------------- */ + + /* change "e+0" to "e", change "e+" to "e", and change "e-0" to "e-" */ + for (i = 0 ; i < MAXLINE && s [i] != '\0' ; i++) + { + if (s [i] == 'e') + { + if (s [i+1] == '+') + { + dest = i+1 ; + if (s [i+2] == '0') + { + /* delete characters s[i+1] and s[i+2] */ + src = i+3 ; + } + else + { + /* delete characters s[i+1] */ + src = i+2 ; + } + } + else if (s [i+1] == '-') + { + dest = i+2 ; + if (s [i+2] == '0') + { + /* delete character s[i+2] */ + src = i+3 ; + } + else + { + /* no change */ + break ; + } + } + while (s [src] != '\0') + { + s [dest++] = s [src++] ; + } + s [dest] = '\0' ; + break ; + } + } + + /* delete the leading "0" if present and not necessary */ + p = s ; + s [MAXLINE-1] = '\0' ; + i = strlen (s) ; + if (i > 2 && s [0] == '0' && s [1] == '.') + { + /* change "0.x" to ".x" */ + p = s + 1 ; + } + else if (i > 3 && s [0] == '-' && s [1] == '0' && s [2] == '.') + { + /* change "-0.x" to "-.x" */ + s [1] = '-' ; + p = s + 1 ; + } + +#if 0 + /* double-check */ + i = sscanf (p, "%lg", &z) ; + if (i != 1 || y != z) + { + /* oops! something went wrong in the "e+0" edit, above. */ + /* this "cannot" happen */ + sprintf (s, "%.*g", width, x) ; + p = s ; + } +#endif + + /* ---------------------------------------------------------------------- */ + /* print the value to the file */ + /* ---------------------------------------------------------------------- */ + + ok = (fprintf (f, "%s", p) > 0) ; + return (ok) ; +} + + +/* ========================================================================== */ +/* === print_triplet ======================================================== */ +/* ========================================================================== */ + +/* Print a triplet, converting it to one-based. Returns TRUE if successful, + * FALSE otherwise. + */ + +static int print_triplet +( + FILE *f, /* file to print to */ + Int is_binary, /* TRUE if file is "pattern" */ + Int is_complex, /* TRUE if file is "complex" */ + Int is_integer, /* TRUE if file is "integer" */ + Int i, /* row index (zero-based) */ + Int j, /* column index (zero-based) */ + double x, /* real part */ + double z /* imaginary part */ +) +{ + int ok ; + ok = (fprintf (f, ID " " ID, 1+i, 1+j) > 0) ; + if (!is_binary) + { + fprintf (f, " ") ; + ok = ok && print_value (f, x, is_integer) ; + if (is_complex) + { + fprintf (f, " ") ; + ok = ok && print_value (f, z, is_integer) ; + } + } + ok = ok && (fprintf (f, "\n") > 0) ; + return (ok) ; +} + + +/* ========================================================================== */ +/* === ntriplets ============================================================ */ +/* ========================================================================== */ + +/* Compute the number of triplets that will be printed to the file + * from the matrix A. */ + +static Int ntriplets +( + cholmod_sparse *A, /* matrix that will be printed */ + Int is_sym /* TRUE if the file is symmetric (lower part only)*/ +) +{ + Int *Ap, *Ai, *Anz, packed, i, j, p, pend, ncol, stype, nz = 0 ; + if (A == NULL) + { + /* the Z matrix is NULL */ + return (0) ; + } + stype = A->stype ; + Ap = A->p ; + Ai = A->i ; + Anz = A->nz ; + packed = A->packed ; + ncol = A->ncol ; + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? Ap [j+1] : p + Anz [j] ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if ((stype < 0 && i >= j) || (stype == 0 && (i >= j || !is_sym))) + { + /* CHOLMOD matrix is symmetric-lower (and so is the file); + * or CHOLMOD matrix is unsymmetric and either A(i,j) is in + * the lower part or the file is unsymmetric. */ + nz++ ; + } + else if (stype > 0 && i <= j) + { + /* CHOLMOD matrix is symmetric-upper, but the file is + * symmetric-lower. Need to transpose the entry. */ + nz++ ; + } + } + } + return (nz) ; +} + + +/* ========================================================================== */ +/* === cholmod_write_sparse ================================================= */ +/* ========================================================================== */ + +/* Write a sparse matrix to a file in Matrix Market format. Optionally include + * comments, and print explicit zero entries given by the pattern of the Z + * matrix. If not NULL, the Z matrix must have the same dimensions and stype + * as A. + * + * Returns the symmetry in which the matrix was printed (1 to 7, see the + * CHOLMOD_MM_* codes in CHOLMOD/Include/cholmod_core.h), or -1 on failure. + * + * If A and Z are sorted on input, and either unsymmetric (stype = 0) or + * symmetric-lower (stype < 0), and if A and Z do not overlap, then the triplets + * are sorted, first by column and then by row index within each column, with + * no duplicate entries. If all the above holds except stype > 0, then the + * triplets are sorted by row first and then column. + */ + +int CHOLMOD(write_sparse) +( + /* ---- input ---- */ + FILE *f, /* file to write to, must already be open */ + cholmod_sparse *A, /* matrix to print */ + cholmod_sparse *Z, /* optional matrix with pattern of explicit zeros */ + const char *comments, /* optional filename of comments to include */ + /* --------------- */ + cholmod_common *Common +) +{ + double x = 0, z = 0 ; + double *Ax, *Az ; + Int *Ap, *Ai, *Anz, *Zp, *Zi, *Znz ; + Int nrow, ncol, is_complex, symmetry, i, j, q, iz, p, nz, is_binary, stype, + is_integer, asym, is_sym, xtype, apacked, zpacked, pend, qend, zsym ; + int ok ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (f, EMPTY) ; + RETURN_IF_NULL (A, EMPTY) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ; + if (Z != NULL && (Z->nrow == 0 || Z->ncol == 0)) + { + /* Z is non-NULL but empty, so treat it as a NULL matrix */ + Z = NULL ; + } + if (Z != NULL) + { + RETURN_IF_XTYPE_INVALID (Z, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ; + if (Z->nrow != A->nrow || Z->ncol != A->ncol || Z->stype != A->stype) + { + ERROR (CHOLMOD_INVALID, "dimension or type of A and Z mismatch") ; + return (EMPTY) ; + } + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* get the A matrix */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + Ai = A->i ; + Ax = A->x ; + Az = A->z ; + Anz = A->nz ; + nrow = A->nrow ; + ncol = A->ncol ; + xtype = A->xtype ; + apacked = A->packed ; + + if (xtype == CHOLMOD_PATTERN) + { + /* a CHOLMOD pattern matrix is printed as "pattern" in the file */ + is_binary = TRUE ; + is_integer = FALSE ; + is_complex = FALSE ; + } + else if (xtype == CHOLMOD_REAL) + { + /* determine if a real matrix is in fact binary or integer */ + is_binary = TRUE ; + is_integer = TRUE ; + is_complex = FALSE ; + for (j = 0 ; (is_binary || is_integer) && j < ncol ; j++) + { + p = Ap [j] ; + pend = (apacked) ? Ap [j+1] : p + Anz [j] ; + for ( ; (is_binary || is_integer) && p < pend ; p++) + { + x = Ax [p] ; + if (x != 1) + { + is_binary = FALSE ; + } + /* convert to Int and then back to double */ + i = (Int) x ; + z = (double) i ; + if (z != x) + { + is_integer = FALSE ; + } + } + } + } + else + { + /* a CHOLMOD complex matrix is printed as "complex" in the file */ + is_binary = FALSE ; + is_integer = FALSE ; + is_complex = TRUE ; + } + + /* ---------------------------------------------------------------------- */ + /* get the Z matrix (only consider the pattern) */ + /* ---------------------------------------------------------------------- */ + + Zp = NULL ; + Zi = NULL ; + Znz = NULL ; + zpacked = TRUE ; + if (Z != NULL) + { + Zp = Z->p ; + Zi = Z->i ; + Znz = Z->nz ; + zpacked = Z->packed ; + } + + /* ---------------------------------------------------------------------- */ + /* determine the symmetry of A and Z */ + /* ---------------------------------------------------------------------- */ + + stype = A->stype ; + if (A->nrow != A->ncol) + { + asym = CHOLMOD_MM_RECTANGULAR ; + } + else if (stype != 0) + { + /* CHOLMOD's A and Z matrices have a symmetric (and matching) stype. + * Note that the diagonal is not checked. */ + asym = is_complex ? CHOLMOD_MM_HERMITIAN : CHOLMOD_MM_SYMMETRIC ; + } + else if (!A->sorted) + { + /* A is in unsymmetric storage, but unsorted */ + asym = CHOLMOD_MM_UNSYMMETRIC ; + } + else + { + /* CHOLMOD's stype is zero (stored in unsymmetric form) */ + asym = EMPTY ; + zsym = EMPTY ; + +#ifndef NMATRIXOPS + /* determine if the matrices are in fact symmetric or Hermitian */ + asym = CHOLMOD(symmetry) (A, 1, NULL, NULL, NULL, NULL, Common) ; + zsym = (Z == NULL) ? 999 : + CHOLMOD(symmetry) (Z, 1, NULL, NULL, NULL, NULL, Common) ; +#endif + + if (asym == EMPTY || zsym <= CHOLMOD_MM_UNSYMMETRIC) + { + /* not computed, out of memory, or Z is unsymmetric */ + asym = CHOLMOD_MM_UNSYMMETRIC ; + } + } + + /* ---------------------------------------------------------------------- */ + /* write the Matrix Market header */ + /* ---------------------------------------------------------------------- */ + + ok = fprintf (f, "%%%%MatrixMarket matrix coordinate") > 0 ; + + if (is_complex) + { + ok = ok && (fprintf (f, " complex") > 0) ; + } + else if (is_binary) + { + ok = ok && (fprintf (f, " pattern") > 0) ; + } + else if (is_integer) + { + ok = ok && (fprintf (f, " integer") > 0) ; + } + else + { + ok = ok && (fprintf (f, " real") > 0) ; + } + + is_sym = FALSE ; + + switch (asym) + { + case CHOLMOD_MM_RECTANGULAR: + case CHOLMOD_MM_UNSYMMETRIC: + /* A is rectangular or unsymmetric */ + ok = ok && (fprintf (f, " general\n") > 0) ; + is_sym = FALSE ; + symmetry = CHOLMOD_MM_UNSYMMETRIC ; + break ; + + case CHOLMOD_MM_SYMMETRIC: + case CHOLMOD_MM_SYMMETRIC_POSDIAG: + /* A is symmetric */ + ok = ok && (fprintf (f, " symmetric\n") > 0) ; + is_sym = TRUE ; + symmetry = CHOLMOD_MM_SYMMETRIC ; + break ; + + case CHOLMOD_MM_HERMITIAN: + case CHOLMOD_MM_HERMITIAN_POSDIAG: + /* A is Hermitian */ + ok = ok && (fprintf (f, " Hermitian\n") > 0) ; + is_sym = TRUE ; + symmetry = CHOLMOD_MM_HERMITIAN ; + break ; + + case CHOLMOD_MM_SKEW_SYMMETRIC: + /* A is skew symmetric */ + ok = ok && (fprintf (f, " skew-symmetric\n") > 0) ; + is_sym = TRUE ; + symmetry = CHOLMOD_MM_SKEW_SYMMETRIC ; + break ; + } + + /* ---------------------------------------------------------------------- */ + /* include the comments if present */ + /* ---------------------------------------------------------------------- */ + + ok = ok && include_comments (f, comments) ; + + /* ---------------------------------------------------------------------- */ + /* write a sparse matrix (A and Z) */ + /* ---------------------------------------------------------------------- */ + + nz = ntriplets (A, is_sym) + ntriplets (Z, is_sym) ; + + /* write the first data line, with nrow, ncol, and # of triplets */ + ok = ok && (fprintf (f, ID " " ID " " ID "\n", nrow, ncol, nz) > 0) ; + + for (j = 0 ; ok && j < ncol ; j++) + { + /* merge column of A and Z */ + p = Ap [j] ; + pend = (apacked) ? Ap [j+1] : p + Anz [j] ; + q = (Z == NULL) ? 0 : Zp [j] ; + qend = (Z == NULL) ? 0 : ((zpacked) ? Zp [j+1] : q + Znz [j]) ; + while (ok) + { + /* get the next row index from A and Z */ + i = (p < pend) ? Ai [p] : (nrow+1) ; + iz = (q < qend) ? Zi [q] : (nrow+2) ; + if (i <= iz) + { + /* get A(i,j), or quit if both A and Z are exhausted */ + if (i == nrow+1) break ; + get_value (Ax, Az, p, xtype, &x, &z) ; + p++ ; + } + else + { + /* get Z(i,j) */ + i = iz ; + x = 0 ; + z = 0 ; + q++ ; + } + if ((stype < 0 && i >= j) || (stype == 0 && (i >= j || !is_sym))) + { + /* CHOLMOD matrix is symmetric-lower (and so is the file); + * or CHOLMOD matrix is unsymmetric and either A(i,j) is in + * the lower part or the file is unsymmetric. */ + ok = ok && print_triplet (f, is_binary, is_complex, is_integer, + i,j, x,z) ; + } + else if (stype > 0 && i <= j) + { + /* CHOLMOD matrix is symmetric-upper, but the file is + * symmetric-lower. Need to transpose the entry. If the + * matrix is real, the complex part is ignored. If the matrix + * is complex, it Hermitian. + */ + ASSERT (IMPLIES (is_complex, asym == CHOLMOD_MM_HERMITIAN)) ; + if (z != 0) + { + z = -z ; + } + ok = ok && print_triplet (f, is_binary, is_complex, is_integer, + j,i, x,z) ; + } + } + } + + if (!ok) + { + ERROR (CHOLMOD_INVALID, "error reading/writing file") ; + return (EMPTY) ; + } + + return (asym) ; +} + + +/* ========================================================================== */ +/* === cholmod_write_dense ================================================== */ +/* ========================================================================== */ + +/* Write a dense matrix to a file in Matrix Market format. Optionally include + * comments. Returns > 0 if successful, -1 otherwise (1 if rectangular, 2 if + * square). Future versions may return 1 to 7 on success (a CHOLMOD_MM_* code, + * just as cholmod_write_sparse does). + * + * A dense matrix is written in "general" format; symmetric formats in the + * Matrix Market standard are not exploited. + */ + +int CHOLMOD(write_dense) +( + /* ---- input ---- */ + FILE *f, /* file to write to, must already be open */ + cholmod_dense *X, /* matrix to print */ + const char *comments, /* optional filename of comments to include */ + /* --------------- */ + cholmod_common *Common +) +{ + double x = 0, z = 0 ; + double *Xx, *Xz ; + Int nrow, ncol, is_complex, i, j, xtype, p ; + int ok ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (f, EMPTY) ; + RETURN_IF_NULL (X, EMPTY) ; + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, EMPTY) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* get the X matrix */ + /* ---------------------------------------------------------------------- */ + + Xx = X->x ; + Xz = X->z ; + nrow = X->nrow ; + ncol = X->ncol ; + xtype = X->xtype ; + is_complex = (xtype == CHOLMOD_COMPLEX) || (xtype == CHOLMOD_ZOMPLEX) ; + + /* ---------------------------------------------------------------------- */ + /* write the Matrix Market header */ + /* ---------------------------------------------------------------------- */ + + ok = (fprintf (f, "%%%%MatrixMarket matrix array") > 0) ; + if (is_complex) + { + ok = ok && (fprintf (f, " complex general\n") > 0) ; + } + else + { + ok = ok && (fprintf (f, " real general\n") > 0) ; + } + + /* ---------------------------------------------------------------------- */ + /* include the comments if present */ + /* ---------------------------------------------------------------------- */ + + ok = ok && include_comments (f, comments) ; + + /* ---------------------------------------------------------------------- */ + /* write a dense matrix */ + /* ---------------------------------------------------------------------- */ + + /* write the first data line, with nrow and ncol */ + ok = ok && (fprintf (f, ID " " ID "\n", nrow, ncol) > 0) ; + + Xx = X->x ; + Xz = X->z ; + for (j = 0 ; ok && j < ncol ; j++) + { + for (i = 0 ; ok && i < nrow ; i++) + { + p = i + j*nrow ; + get_value (Xx, Xz, p, xtype, &x, &z) ; + ok = ok && print_value (f, x, FALSE) ; + if (is_complex) + { + ok = ok && (fprintf (f, " ") > 0) ; + ok = ok && print_value (f, z, FALSE) ; + } + ok = ok && (fprintf (f, "\n") > 0) ; + } + } + + if (!ok) + { + ERROR (CHOLMOD_INVALID, "error reading/writing file") ; + return (EMPTY) ; + } + + return ((nrow == ncol) ? CHOLMOD_MM_UNSYMMETRIC : CHOLMOD_MM_RECTANGULAR) ; +} +#endif diff --git a/src/CHOLMOD/Check/lesser.txt b/src/CHOLMOD/Check/lesser.txt new file mode 100644 index 0000000..8add30a --- /dev/null +++ b/src/CHOLMOD/Check/lesser.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/src/CHOLMOD/Cholesky/License.txt b/src/CHOLMOD/Cholesky/License.txt new file mode 100644 index 0000000..800ac5e --- /dev/null +++ b/src/CHOLMOD/Cholesky/License.txt @@ -0,0 +1,24 @@ +CHOLMOD/Cholesky module, Copyright (C) 2005-2006, Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + +Note that this license is for the CHOLMOD/Cholesky module only. +All CHOLMOD modules are licensed separately. + + +-------------------------------------------------------------------------------- + + +This Module is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This Module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this Module; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/src/CHOLMOD/Cholesky/cholmod_amd.c b/src/CHOLMOD/Cholesky/cholmod_amd.c new file mode 100644 index 0000000..9d04532 --- /dev/null +++ b/src/CHOLMOD/Cholesky/cholmod_amd.c @@ -0,0 +1,211 @@ +/* ========================================================================== */ +/* === Cholesky/cholmod_amd ================================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD interface to the AMD ordering routine. Orders A if the matrix is + * symmetric. On output, Perm [k] = i if row/column i of A is the kth + * row/column of P*A*P'. This corresponds to A(p,p) in MATLAB notation. + * + * If A is unsymmetric, cholmod_amd orders A*A'. On output, Perm [k] = i if + * row/column i of A*A' is the kth row/column of P*A*A'*P'. This corresponds to + * A(p,:)*A(p,:)' in MATLAB notation. If f is present, A(p,f)*A(p,f)' is + * ordered. + * + * Computes the flop count for a subsequent LL' factorization, the number + * of nonzeros in L, and the number of nonzeros in the matrix ordered (A, + * A*A' or A(:,f)*A(:,f)'). + * + * workspace: Iwork (6*nrow). Head (nrow). + * + * Allocates a temporary copy of A+A' or A*A' (with + * both upper and lower triangular parts) as input to AMD. + * + * Supports any xtype (pattern, real, complex, or zomplex) + */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "amd.h" +#include "cholmod_cholesky.h" + +#if (!defined (AMD_VERSION) || (AMD_VERSION < AMD_VERSION_CODE (2,0))) +#error "AMD v2.0 or later is required" +#endif + +/* ========================================================================== */ +/* === cholmod_amd ========================================================== */ +/* ========================================================================== */ + +int CHOLMOD(amd) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* ---- output --- */ + Int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) +{ + double Info [AMD_INFO], Control2 [AMD_CONTROL], *Control ; + Int *Cp, *Len, *Nv, *Head, *Elen, *Degree, *Wi, *Iwork, *Next ; + cholmod_sparse *C ; + Int j, n, cnz ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + n = A->nrow ; + + RETURN_IF_NULL (Perm, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + Common->status = CHOLMOD_OK ; + if (n == 0) + { + /* nothing to do */ + Common->fl = 0 ; + Common->lnz = 0 ; + Common->anz = 0 ; + return (TRUE) ; + } + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + /* Note: this is less than the space used in cholmod_analyze, so if + * cholmod_amd is being called by that routine, no space will be + * allocated. + */ + + /* s = MAX (6*n, A->ncol) */ + s = CHOLMOD(mult_size_t) (n, 6, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + s = MAX (s, A->ncol) ; + + CHOLMOD(allocate_work) (n, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + + Iwork = Common->Iwork ; + Degree = Iwork ; /* size n */ + Wi = Iwork + n ; /* size n */ + Len = Iwork + 2*((size_t) n) ; /* size n */ + Nv = Iwork + 3*((size_t) n) ; /* size n */ + Next = Iwork + 4*((size_t) n) ; /* size n */ + Elen = Iwork + 5*((size_t) n) ; /* size n */ + + Head = Common->Head ; /* size n+1, but only n is used */ + + /* ---------------------------------------------------------------------- */ + /* construct the input matrix for AMD */ + /* ---------------------------------------------------------------------- */ + + if (A->stype == 0) + { + /* C = A*A' or A(:,f)*A(:,f)', add extra space of nnz(C)/2+n to C */ + C = CHOLMOD(aat) (A, fset, fsize, -2, Common) ; + } + else + { + /* C = A+A', but use only the upper triangular part of A if A->stype = 1 + * and only the lower part of A if A->stype = -1. Add extra space of + * nnz(C)/2+n to C. */ + C = CHOLMOD(copy) (A, 0, -2, Common) ; + } + + if (Common->status < CHOLMOD_OK) + { + /* out of memory, fset invalid, or other error */ + return (FALSE) ; + } + + Cp = C->p ; + for (j = 0 ; j < n ; j++) + { + Len [j] = Cp [j+1] - Cp [j] ; + } + + /* C does not include the diagonal, and both upper and lower parts. + * Common->anz includes the diagonal, and just the lower part of C */ + cnz = Cp [n] ; + Common->anz = cnz / 2 + n ; + + /* ---------------------------------------------------------------------- */ + /* order C using AMD */ + /* ---------------------------------------------------------------------- */ + + /* get parameters */ + if (Common->current < 0 || Common->current >= CHOLMOD_MAXMETHODS) + { + /* use AMD defaults */ + Control = NULL ; + } + else + { + Control = Control2 ; + Control [AMD_DENSE] = Common->method [Common->current].prune_dense ; + Control [AMD_AGGRESSIVE] = Common->method [Common->current].aggressive ; + } + + /* AMD_2 does not use amd_malloc and amd_free, but set these pointers just + * be safe. */ + amd_malloc = Common->malloc_memory ; + amd_free = Common->free_memory ; + amd_calloc = Common->calloc_memory ; + amd_realloc = Common->realloc_memory ; + + /* AMD_2 doesn't print anything either, but future versions might, + * so set the amd_printf pointer too. */ + amd_printf = Common->print_function ; + +#ifdef LONG + amd_l2 (n, C->p, C->i, Len, C->nzmax, cnz, Nv, Next, Perm, Head, Elen, + Degree, Wi, Control, Info) ; +#else + amd_2 (n, C->p, C->i, Len, C->nzmax, cnz, Nv, Next, Perm, Head, Elen, + Degree, Wi, Control, Info) ; +#endif + + /* LL' flop count. Need to subtract n for LL' flop count. Note that this + * is a slight upper bound which is often exact (see AMD/Source/amd_2.c for + * details). cholmod_analyze computes an exact flop count and fill-in. */ + Common->fl = Info [AMD_NDIV] + 2 * Info [AMD_NMULTSUBS_LDL] + n ; + + /* Info [AMD_LNZ] excludes the diagonal */ + Common->lnz = n + Info [AMD_LNZ] ; + + /* ---------------------------------------------------------------------- */ + /* free the AMD workspace and clear the persistent workspace in Common */ + /* ---------------------------------------------------------------------- */ + + ASSERT (IMPLIES (Common->status == CHOLMOD_OK, + CHOLMOD(dump_perm) (Perm, n, n, "AMD2 perm", Common))) ; + CHOLMOD(free_sparse) (&C, Common) ; + for (j = 0 ; j <= n ; j++) + { + Head [j] = EMPTY ; + } + return (TRUE) ; +} +#endif diff --git a/src/CHOLMOD/Cholesky/cholmod_analyze.c b/src/CHOLMOD/Cholesky/cholmod_analyze.c new file mode 100644 index 0000000..433fd95 --- /dev/null +++ b/src/CHOLMOD/Cholesky/cholmod_analyze.c @@ -0,0 +1,942 @@ +/* ========================================================================== */ +/* === Cholesky/cholmod_analyze ============================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2013, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Order and analyze a matrix (either simplicial or supernodal), in prepartion + * for numerical factorization via cholmod_factorize or via the "expert" + * routines cholmod_rowfac and cholmod_super_numeric. + * + * symmetric case: A or A(p,p) + * unsymmetric case: AA', A(p,:)*A(p,:)', A(:,f)*A(:,f)', or A(p,f)*A(p,f)' + * + * For the symmetric case, only the upper or lower triangular part of A is + * accessed (depending on the type of A). LL'=A (or permuted A) is analzed. + * For the unsymmetric case (LL'=AA' or permuted A). + * + * There can be no duplicate entries in p or f. p is of length m if A is + * m-by-n. f can be length 0 to n. + * + * In both cases, the columns of A need not be sorted. A can be in packed + * or unpacked form. + * + * Ordering options include: + * + * natural: A is not permuted to reduce fill-in + * given: a permutation can be provided to this routine (UserPerm) + * AMD: approximate minumum degree (AMD for the symmetric case, + * COLAMD for the AA' case). + * METIS: nested dissection with METIS_NodeND + * NESDIS: nested dissection using METIS_NodeComputeSeparator, + * typically followed by a constrained minimum degree + * (CAMD for the symmetric case, CCOLAMD for the AA' case). + * + * Multiple ordering options can be tried (up to 9 of them), and the best one + * is selected (the one that gives the smallest number of nonzeros in the + * simplicial factor L). If one method fails, cholmod_analyze keeps going, and + * picks the best among the methods that succeeded. This routine fails (and + * returns NULL) if either initial memory allocation fails, all ordering methods + * fail, or the supernodal analysis (if requested) fails. By default, the 9 + * methods available are: + * + * 1) given permutation (skipped if UserPerm is NULL) + * 2) AMD (symmetric case) or COLAMD (unsymmetric case) + * 3) METIS with default parameters + * 4) NESDIS with default parameters (stopping the partitioning when + * the graph is of size nd_small = 200 or less, remove nodes with + * more than max (16, prune_dense * sqrt (n)) nodes where + * prune_dense = 10, and follow partitioning with CCOLAMD, a + * constrained minimum degree ordering). + * 5) natural + * 6) NESDIS, nd_small = 20000, prune_dense = 10 + * 7) NESDIS, nd_small = 4, prune_dense = 10, no min degree + * 8) NESDIS, nd_small = 200, prune_dense = 0 + * 9) COLAMD for A*A' or AMD for A + * + * By default, the first two are tried, and METIS is tried if AMD reports a high + * flop count and fill-in. Let fl denote the flop count for the AMD, ordering, + * nnz(L) the # of nonzeros in L, and nnz(tril(A)) (or A*A'). If + * fl/nnz(L) >= 500 and nnz(L)/nnz(tril(A)) >= 5, then METIS is attempted. The + * best ordering is used (UserPerm if given, AMD, and METIS if attempted). If + * you do not have METIS, only the first two will be tried (user permutation, + * if provided, and AMD/COLAMD). This default behavior is obtained when + * Common->nmethods is zero. In this case, methods 0, 1, and 2 in + * Common->method [..] are reset to User-provided, AMD, and METIS (or NESDIS + * if Common->default_nesdis is set to the non-default value of TRUE), + * respectively. + * + * You can modify these 9 methods and the number of methods tried by changing + * parameters in the Common argument. If you know the best ordering for your + * matrix, set Common->nmethods to 1 and set Common->method[0].ordering to the + * requested ordering method. Parameters for each method can also be modified + * (refer to cholmod.h for details). + * + * Note that it is possible for METIS to terminate your program if it runs out + * of memory. This is not the case for any CHOLMOD or minimum degree ordering + * routine (AMD, COLAMD, CAMD, CCOLAMD, or CSYMAMD). Since NESDIS relies on + * METIS, it too can terminate your program. + * + * The factor L is returned as simplicial symbolic (L->is_super FALSE) if + * Common->supernodal <= CHOLMOD_SIMPLICIAL (0) or as supernodal symbolic if + * Common->supernodal >= CHOLMOD_SUPERNODAL (2). If Common->supernodal is + * equal to CHOLMOD_AUTO (1), then L is simplicial if the flop count per + * nonzero in L is less than Common->supernodal_switch (default: 40), and + * is returned as a supernodal factor otherwise. + * + * In both cases, L->xtype is CHOLMOD_PATTERN. + * A subsequent call to cholmod_factorize will perform a + * simplicial or supernodal factorization, depending on the type of L. + * + * For the simplicial case, L contains the fill-reducing permutation (L->Perm) + * and the counts of nonzeros in each column of L (L->ColCount). For the + * supernodal case, L also contains the nonzero pattern of each supernode. + * + * workspace: Flag (nrow), Head (nrow+1) + * if symmetric: Iwork (6*nrow) + * if unsymmetric: Iwork (6*nrow+ncol). + * calls various ordering routines, which typically allocate O(nnz(A)) + * temporary workspace ((2 to 3)*nnz(A) * sizeof (Int) is typical, but it + * can be much higher if A*A' must be explicitly formed for METIS). Also + * allocates up to 2 temporary (permuted/transpose) copies of the nonzero + * pattern of A, and up to 3*n*sizeof(Int) additional workspace. + * + * Supports any xtype (pattern, real, complex, or zomplex) + */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "cholmod_cholesky.h" + +#ifndef NSUPERNODAL +#include "cholmod_supernodal.h" +#endif + +#ifndef NPARTITION +#include "cholmod_partition.h" +#endif + + +/* ========================================================================== */ +/* === cholmod_analyze ====================================================== */ +/* ========================================================================== */ + +/* Orders and analyzes A, AA', PAP', or PAA'P' and returns a symbolic factor + * that can later be passed to cholmod_factorize. */ + +cholmod_factor *CHOLMOD(analyze) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order and analyze */ + /* --------------- */ + cholmod_common *Common +) +{ + return (CHOLMOD(analyze_p2) (TRUE, A, NULL, NULL, 0, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_analyze_p ==================================================== */ +/* ========================================================================== */ + +/* Orders and analyzes A, AA', PAP', PAA'P', FF', or PFF'P and returns a + * symbolic factor that can later be passed to cholmod_factorize, where + * F = A(:,fset) if fset is not NULL and A->stype is zero. + * UserPerm is tried if non-NULL. */ + +cholmod_factor *CHOLMOD(analyze_p) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order and analyze */ + Int *UserPerm, /* user-provided permutation, size A->nrow */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* --------------- */ + cholmod_common *Common +) +{ + return (CHOLMOD(analyze_p2) (TRUE, A, UserPerm, fset, fsize, Common)) ; +} + + +/* ========================================================================== */ +/* === permute_matrices ===================================================== */ +/* ========================================================================== */ + +/* Permute and transpose a matrix. Allocates the A1 and A2 matrices, if needed, + * or returns them as NULL if not needed. + */ + +static int permute_matrices +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to permute */ + Int ordering, /* ordering method used */ + Int *Perm, /* fill-reducing permutation */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + Int do_rowcolcounts,/* if TRUE, compute both S and F. If FALSE, only + * S is needed for the symmetric case, and only F for + * the unsymmetric case */ + /* ---- output --- */ + cholmod_sparse **A1_handle, /* see comments below for A1, A2, S, F */ + cholmod_sparse **A2_handle, + cholmod_sparse **S_handle, + cholmod_sparse **F_handle, + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_sparse *A1, *A2, *S, *F ; + + *A1_handle = NULL ; + *A2_handle = NULL ; + *S_handle = NULL ; + *F_handle = NULL ; + A1 = NULL ; + A2 = NULL ; + + if (ordering == CHOLMOD_NATURAL) + { + + /* ------------------------------------------------------------------ */ + /* natural ordering of A */ + /* ------------------------------------------------------------------ */ + + if (A->stype < 0) + { + /* symmetric lower case: A already in lower form, so S=A' */ + /* workspace: Iwork (nrow) */ + A2 = CHOLMOD(ptranspose) (A, 0, NULL, NULL, 0, Common) ; + F = A ; + S = A2 ; + } + else if (A->stype > 0) + { + /* symmetric upper case: F = pattern of triu (A)', S = A */ + /* workspace: Iwork (nrow) */ + if (do_rowcolcounts) + { + /* F not needed for symmetric case if do_rowcolcounts FALSE */ + A1 = CHOLMOD(ptranspose) (A, 0, NULL, fset, fsize, Common) ; + } + F = A1 ; + S = A ; + } + else + { + /* unsymmetric case: F = pattern of A (:,f)', S = A */ + /* workspace: Iwork (nrow if no fset, MAX(nrow,ncol) if fset) */ + A1 = CHOLMOD(ptranspose) (A, 0, NULL, fset, fsize, Common) ; + F = A1 ; + S = A ; + } + + } + else + { + + /* ------------------------------------------------------------------ */ + /* A is permuted */ + /* ------------------------------------------------------------------ */ + + if (A->stype < 0) + { + /* symmetric lower case: S = tril (A (p,p))' and F = S' */ + /* workspace: Iwork (2*nrow) */ + A2 = CHOLMOD(ptranspose) (A, 0, Perm, NULL, 0, Common) ; + S = A2 ; + /* workspace: Iwork (nrow) */ + if (do_rowcolcounts) + { + /* F not needed for symmetric case if do_rowcolcounts FALSE */ + A1 = CHOLMOD(ptranspose) (A2, 0, NULL, NULL, 0, Common) ; + } + F = A1 ; + } + else if (A->stype > 0) + { + /* symmetric upper case: F = triu (A (p,p))' and S = F' */ + /* workspace: Iwork (2*nrow) */ + A1 = CHOLMOD(ptranspose) (A, 0, Perm, NULL, 0, Common) ; + F = A1 ; + /* workspace: Iwork (nrow) */ + A2 = CHOLMOD(ptranspose) (A1, 0, NULL, NULL, 0, Common) ; + S = A2 ; + } + else + { + /* unsymmetric case: F = A (p,f)' and S = F' */ + /* workspace: Iwork (nrow if no fset, MAX(nrow,ncol) if fset) */ + A1 = CHOLMOD(ptranspose) (A, 0, Perm, fset, fsize, Common) ; + F = A1 ; + if (do_rowcolcounts) + { + /* S not needed for unsymmetric case if do_rowcolcounts FALSE */ + /* workspace: Iwork (nrow) */ + A2 = CHOLMOD(ptranspose) (A1, 0, NULL, NULL, 0, Common) ; + } + S = A2 ; + } + } + + /* If any cholmod_*transpose fails, one or more matrices will be NULL */ + *A1_handle = A1 ; + *A2_handle = A2 ; + *S_handle = S ; + *F_handle = F ; + return (Common->status == CHOLMOD_OK) ; +} + + +/* ========================================================================== */ +/* === cholmod_analyze_ordering ============================================= */ +/* ========================================================================== */ + +/* Given a matrix A and its fill-reducing permutation, compute the elimination + * tree, its (non-weighted) postordering, and the number of nonzeros in each + * column of L. Also computes the flop count, the total nonzeros in L, and + * the nonzeros in A (Common->fl, Common->lnz, and Common->anz). + * + * The column counts of L, flop count, and other statistics from + * cholmod_rowcolcounts are not computed if ColCount is NULL. + * + * workspace: Iwork (2*nrow if symmetric, 2*nrow+ncol if unsymmetric), + * Flag (nrow), Head (nrow+1) + */ + +int CHOLMOD(analyze_ordering) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + int ordering, /* ordering method used */ + Int *Perm, /* size n, fill-reducing permutation to analyze */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* ---- output --- */ + Int *Parent, /* size n, elimination tree */ + Int *Post, /* size n, postordering of elimination tree */ + Int *ColCount, /* size n, nnz in each column of L */ + /* ---- workspace */ + Int *First, /* size n workspace for cholmod_postorder */ + Int *Level, /* size n workspace for cholmod_postorder */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_sparse *A1, *A2, *S, *F ; + Int n, ok, do_rowcolcounts ; + + /* check inputs */ + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + + n = A->nrow ; + + do_rowcolcounts = (ColCount != NULL) ; + + /* permute A according to Perm and fset */ + ok = permute_matrices (A, ordering, Perm, fset, fsize, do_rowcolcounts, + &A1, &A2, &S, &F, Common) ; + + /* find etree of S (symmetric upper/lower case) or F (unsym case) */ + /* workspace: symmmetric: Iwork (nrow), unsym: Iwork (nrow+ncol) */ + ok = ok && CHOLMOD(etree) (A->stype ? S:F, Parent, Common) ; + + /* postorder the etree (required by cholmod_rowcolcounts) */ + /* workspace: Iwork (2*nrow) */ + ok = ok && (CHOLMOD(postorder) (Parent, n, NULL, Post, Common) == n) ; + + /* cholmod_postorder doesn't set Common->status if it returns < n */ + Common->status = (!ok && Common->status == CHOLMOD_OK) ? + CHOLMOD_INVALID : Common->status ; + + /* analyze LL'=S or SS' or S(:,f)*S(:,f)' */ + /* workspace: + * if symmetric: Flag (nrow), Iwork (2*nrow) + * if unsymmetric: Flag (nrow), Iwork (2*nrow+ncol), Head (nrow+1) + */ + if (do_rowcolcounts) + { + ok = ok && CHOLMOD(rowcolcounts) (A->stype ? F:S, fset, fsize, Parent, + Post, NULL, ColCount, First, Level, Common) ; + } + + /* free temporary matrices and return result */ + CHOLMOD(free_sparse) (&A1, Common) ; + CHOLMOD(free_sparse) (&A2, Common) ; + return (ok) ; +} + + +/* ========================================================================== */ +/* === Free workspace and return L ========================================== */ +/* ========================================================================== */ + +#define FREE_WORKSPACE_AND_RETURN \ +{ \ + Common->no_workspace_reallocate = FALSE ; \ + CHOLMOD(free) (n, sizeof (Int), Lparent, Common) ; \ + CHOLMOD(free) (n, sizeof (Int), Perm, Common) ; \ + CHOLMOD(free) (n, sizeof (Int), ColCount, Common) ; \ + if (Common->status < CHOLMOD_OK) \ + { \ + CHOLMOD(free_factor) (&L, Common) ; \ + } \ + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; \ + return (L) ; \ +} + + +/* ========================================================================== */ +/* === cholmod_analyze_p2 =================================================== */ +/* ========================================================================== */ + +/* Ordering and analysis for sparse Cholesky or sparse QR. CHOLMOD itself + * always uses for_cholesky = TRUE. The for_cholesky = FALSE option is + * for SuiteSparseQR only. */ + +cholmod_factor *CHOLMOD(analyze_p2) +( + /* ---- input ---- */ + int for_cholesky, /* if TRUE, then analyze for Cholesky; else for QR */ + cholmod_sparse *A, /* matrix to order and analyze */ + Int *UserPerm, /* user-provided permutation, size A->nrow */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* --------------- */ + cholmod_common *Common +) +{ + double lnz_best ; + Int *First, *Level, *Work4n, *Cmember, *CParent, *ColCount, *Lperm, *Parent, + *Post, *Perm, *Lparent, *Lcolcount ; + cholmod_factor *L ; + Int k, n, ordering, method, nmethods, status, default_strategy, ncol, uncol, + skip_analysis, skip_best ; + Int amd_backup ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, NULL) ; + Common->status = CHOLMOD_OK ; + status = CHOLMOD_OK ; + Common->selected = EMPTY ; + Common->called_nd = FALSE ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + n = A->nrow ; + ncol = A->ncol ; + uncol = (A->stype == 0) ? (A->ncol) : 0 ; + + /* ---------------------------------------------------------------------- */ + /* set the default strategy */ + /* ---------------------------------------------------------------------- */ + + lnz_best = (double) EMPTY ; + skip_best = FALSE ; + nmethods = MIN (Common->nmethods, CHOLMOD_MAXMETHODS) ; + nmethods = MAX (0, nmethods) ; + +#ifndef NDEBUG + PRINT1 (("cholmod_analyze_p2 :: nmethods "ID"\n", nmethods)) ; + for (method = 0 ; method < nmethods ; method++) + { + PRINT1 ((" "ID": ordering "ID"\n", + method, Common->method [method].ordering)) ; + } +#endif + + default_strategy = (nmethods == 0) ; + if (default_strategy) + { + /* default strategy: try UserPerm, if given. Try AMD for A, or AMD + * to order A*A'. Try METIS for the symmetric case only if AMD reports + * a high degree of fill-in and flop count. METIS is not tried if the + * Partition Module isn't installed. If Common->default_nesdis is + * TRUE, then NESDIS is used as the 3rd ordering instead. */ + Common->method [0].ordering = CHOLMOD_GIVEN ;/* skip if UserPerm NULL */ + Common->method [1].ordering = CHOLMOD_AMD ; + Common->method [2].ordering = + (Common->default_nesdis ? CHOLMOD_NESDIS : CHOLMOD_METIS) ; + amd_backup = FALSE ; +#ifndef NPARTITION + nmethods = 3 ; +#else + nmethods = 2 ; +#endif + } + else + { + /* If only METIS and NESDIS are selected, or if 2 or more methods are + * being tried, then enable AMD backup */ + amd_backup = (nmethods > 1) || (nmethods == 1 && + (Common->method [0].ordering == CHOLMOD_METIS || + Common->method [0].ordering == CHOLMOD_NESDIS)) ; + } + +#ifdef NSUPERNODAL + /* CHOLMOD Supernodal module not installed, just do simplicial analysis */ + Common->supernodal = CHOLMOD_SIMPLICIAL ; +#endif + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* Note: enough space needs to be allocated here so that routines called by + * cholmod_analyze do not reallocate the space. + */ + + /* s = 6*n + uncol */ + s = CHOLMOD(mult_size_t) (n, 6, &ok) ; + s = CHOLMOD(add_size_t) (s, uncol, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (NULL) ; + } + + CHOLMOD(allocate_work) (n, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ensure that subsequent routines, called by cholmod_analyze, do not + * reallocate any workspace. This is set back to FALSE in the + * FREE_WORKSPACE_AND_RETURN macro, which is the only way this function + * returns to its caller. */ + Common->no_workspace_reallocate = TRUE ; + + /* Use the last 4*n Int's in Iwork for Parent, First, Level, and Post, since + * other CHOLMOD routines will use the first 2n+uncol space. The ordering + * routines (cholmod_amd, cholmod_colamd, cholmod_ccolamd, cholmod_metis) + * are an exception. They can use all 6n + ncol space, since the contents + * of Parent, First, Level, and Post are not needed across calls to those + * routines. */ + Work4n = Common->Iwork ; + Work4n += 2*((size_t) n) + uncol ; + Parent = Work4n ; + First = Work4n + n ; + Level = Work4n + 2*((size_t) n) ; + Post = Work4n + 3*((size_t) n) ; + + /* note that this assignment means that cholmod_nested_dissection, + * cholmod_ccolamd, and cholmod_camd can use only the first 4n+uncol + * space in Common->Iwork */ + Cmember = Post ; + CParent = Level ; + + /* ---------------------------------------------------------------------- */ + /* allocate more workspace, and an empty simplicial symbolic factor */ + /* ---------------------------------------------------------------------- */ + + L = CHOLMOD(allocate_factor) (n, Common) ; + Lparent = CHOLMOD(malloc) (n, sizeof (Int), Common) ; + Perm = CHOLMOD(malloc) (n, sizeof (Int), Common) ; + ColCount = CHOLMOD(malloc) (n, sizeof (Int), Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + FREE_WORKSPACE_AND_RETURN ; + } + Lperm = L->Perm ; + Lcolcount = L->ColCount ; + Common->anz = EMPTY ; + + /* ---------------------------------------------------------------------- */ + /* try all the requested ordering options and backup to AMD if needed */ + /* ---------------------------------------------------------------------- */ + + /* turn off error handling [ */ + Common->try_catch = TRUE ; + + for (method = 0 ; method <= nmethods ; method++) + { + + /* ------------------------------------------------------------------ */ + /* determine the method to try */ + /* ------------------------------------------------------------------ */ + + Common->fl = EMPTY ; + Common->lnz = EMPTY ; + skip_analysis = FALSE ; + + if (method == nmethods) + { + /* All methods failed: backup to AMD */ + if (Common->selected == EMPTY && amd_backup) + { + PRINT1 (("All methods requested failed: backup to AMD\n")) ; + ordering = CHOLMOD_AMD ; + } + else + { + break ; + } + } + else + { + ordering = Common->method [method].ordering ; + } + Common->current = method ; + PRINT1 (("method "ID": Try method: "ID"\n", method, ordering)) ; + + /* ------------------------------------------------------------------ */ + /* find the fill-reducing permutation */ + /* ------------------------------------------------------------------ */ + + if (ordering == CHOLMOD_NATURAL) + { + + /* -------------------------------------------------------------- */ + /* natural ordering */ + /* -------------------------------------------------------------- */ + + for (k = 0 ; k < n ; k++) + { + Perm [k] = k ; + } + + } + else if (ordering == CHOLMOD_GIVEN) + { + + /* -------------------------------------------------------------- */ + /* use given ordering of A, if provided */ + /* -------------------------------------------------------------- */ + + if (UserPerm == NULL) + { + /* this is not an error condition */ + PRINT1 (("skip, no user perm given\n")) ; + continue ; + } + for (k = 0 ; k < n ; k++) + { + /* UserPerm is checked in cholmod_ptranspose */ + Perm [k] = UserPerm [k] ; + } + + } + else if (ordering == CHOLMOD_AMD) + { + + /* -------------------------------------------------------------- */ + /* AMD ordering of A, A*A', or A(:,f)*A(:,f)' */ + /* -------------------------------------------------------------- */ + + amd_backup = FALSE ; /* no need to try AMD twice ... */ + CHOLMOD(amd) (A, fset, fsize, Perm, Common) ; + skip_analysis = TRUE ; + + } + else if (ordering == CHOLMOD_COLAMD) + { + + /* -------------------------------------------------------------- */ + /* AMD for symmetric case, COLAMD for A*A' or A(:,f)*A(:,f)' */ + /* -------------------------------------------------------------- */ + + if (A->stype) + { + CHOLMOD(amd) (A, fset, fsize, Perm, Common) ; + skip_analysis = TRUE ; + } + else + { + /* Alternative: + CHOLMOD(ccolamd) (A, fset, fsize, NULL, Perm, Common) ; + */ + /* do not postorder, it is done later, below */ + /* workspace: Iwork (4*nrow+uncol), Flag (nrow), Head (nrow+1)*/ + CHOLMOD(colamd) (A, fset, fsize, FALSE, Perm, Common) ; + } + + } + else if (ordering == CHOLMOD_METIS) + { + + /* -------------------------------------------------------------- */ + /* use METIS_NodeND directly (via a CHOLMOD wrapper) */ + /* -------------------------------------------------------------- */ + +#ifndef NPARTITION + /* postorder parameter is false, because it will be later, below */ + /* workspace: Iwork (4*nrow+uncol), Flag (nrow), Head (nrow+1) */ + Common->called_nd = TRUE ; + CHOLMOD(metis) (A, fset, fsize, FALSE, Perm, Common) ; +#else + Common->status = CHOLMOD_NOT_INSTALLED ; +#endif + + } + else if (ordering == CHOLMOD_NESDIS) + { + + /* -------------------------------------------------------------- */ + /* use CHOLMOD's nested dissection */ + /* -------------------------------------------------------------- */ + + /* this method is based on METIS' node bissection routine + * (METIS_NodeComputeSeparator). In contrast to METIS_NodeND, + * it calls CAMD or CCOLAMD on the whole graph, instead of MMD + * on just the leaves. */ +#ifndef NPARTITION + /* workspace: Flag (nrow), Head (nrow+1), Iwork (2*nrow) */ + Common->called_nd = TRUE ; + CHOLMOD(nested_dissection) (A, fset, fsize, Perm, CParent, Cmember, + Common) ; +#else + Common->status = CHOLMOD_NOT_INSTALLED ; +#endif + + } + else + { + + /* -------------------------------------------------------------- */ + /* invalid ordering method */ + /* -------------------------------------------------------------- */ + + Common->status = CHOLMOD_INVALID ; + PRINT1 (("No such ordering: "ID"\n", ordering)) ; + } + + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + if (Common->status < CHOLMOD_OK) + { + /* out of memory, or method failed */ + status = MIN (status, Common->status) ; + Common->status = CHOLMOD_OK ; + continue ; + } + + /* ------------------------------------------------------------------ */ + /* analyze the ordering */ + /* ------------------------------------------------------------------ */ + + if (!skip_analysis) + { + if (!CHOLMOD(analyze_ordering) (A, ordering, Perm, fset, fsize, + Parent, Post, ColCount, First, Level, Common)) + { + /* ordering method failed; clear status and try next method */ + status = MIN (status, Common->status) ; + Common->status = CHOLMOD_OK ; + continue ; + } + } + + ASSERT (Common->fl >= 0 && Common->lnz >= 0) ; + Common->method [method].fl = Common->fl ; + Common->method [method].lnz = Common->lnz ; + PRINT1 (("lnz %g fl %g\n", Common->lnz, Common->fl)) ; + + /* ------------------------------------------------------------------ */ + /* pick the best method */ + /* ------------------------------------------------------------------ */ + + /* fl.pt. compare, but lnz can never be NaN */ + if (Common->selected == EMPTY || Common->lnz < lnz_best) + { + Common->selected = method ; + PRINT1 (("this is best so far, method "ID"\n", method)) ; + L->ordering = ordering ; + lnz_best = Common->lnz ; + for (k = 0 ; k < n ; k++) + { + Lperm [k] = Perm [k] ; + } + /* save the results of cholmod_analyze_ordering, if it was called */ + skip_best = skip_analysis ; + if (!skip_analysis) + { + /* save the column count; becomes permanent part of L */ + for (k = 0 ; k < n ; k++) + { + Lcolcount [k] = ColCount [k] ; + } + /* Parent is needed for weighted postordering and for supernodal + * analysis. Does not become a permanent part of L */ + for (k = 0 ; k < n ; k++) + { + Lparent [k] = Parent [k] ; + } + } + } + + /* ------------------------------------------------------------------ */ + /* determine if METIS is to be skipped */ + /* ------------------------------------------------------------------ */ + + if (default_strategy && ordering == CHOLMOD_AMD) + { + if ((Common->fl < 500 * Common->lnz) || + (Common->lnz < 5 * Common->anz)) + { + /* AMD found an ordering with less than 500 flops per nonzero in + * L, or one with a fill-in ratio (nnz(L)/nnz(A)) of less than + * 5. This is pretty good, and it's unlikely that METIS will do + * better (this heuristic is based on tests on all symmetric + * positive definite matrices in the UF sparse matrix + * collection, and it works well across a wide range of + * problems). METIS can take much more time than AMD. */ + break ; + } + } + } + + /* turn error printing back on ] */ + Common->try_catch = FALSE ; + + /* ---------------------------------------------------------------------- */ + /* return if no ordering method succeeded */ + /* ---------------------------------------------------------------------- */ + + if (Common->selected == EMPTY) + { + /* All methods failed. + * If two or more methods failed, they may have failed for different + * reasons. Both would clear Common->status and skip to the next + * method. Common->status needs to be restored here to the worst error + * obtained in any of the methods. CHOLMOD_INVALID is worse + * than CHOLMOD_OUT_OF_MEMORY, since the former implies something may + * be wrong with the user's input. CHOLMOD_OUT_OF_MEMORY is simply an + * indication of lack of resources. */ + if (status >= CHOLMOD_OK) + { + /* this can occur if nmethods = 1, ordering = CHOLMOD_GIVEN, + but UserPerm is NULL */ + status = CHOLMOD_INVALID ; + } + ERROR (status, "all methods failed") ; + FREE_WORKSPACE_AND_RETURN ; + } + + /* ---------------------------------------------------------------------- */ + /* do the analysis for AMD, if skipped */ + /* ---------------------------------------------------------------------- */ + + Common->fl = Common->method [Common->selected].fl ; + Common->lnz = Common->method [Common->selected].lnz ; + ASSERT (Common->lnz >= 0) ; + + if (skip_best) + { + if (!CHOLMOD(analyze_ordering) (A, L->ordering, Lperm, fset, fsize, + Lparent, Post, Lcolcount, First, Level, Common)) + { + /* out of memory, or method failed */ + FREE_WORKSPACE_AND_RETURN ; + } + } + + /* ---------------------------------------------------------------------- */ + /* postorder the etree, weighted by the column counts */ + /* ---------------------------------------------------------------------- */ + + if (Common->postorder) + { + /* combine the fill-reducing ordering with the weighted postorder */ + /* workspace: Iwork (2*nrow) */ + if (CHOLMOD(postorder) (Lparent, n, Lcolcount, Post, Common) == n) + { + /* use First and Level as workspace [ */ + Int *Wi = First, *InvPost = Level ; + Int newchild, oldchild, newparent, oldparent ; + + for (k = 0 ; k < n ; k++) + { + Wi [k] = Lperm [Post [k]] ; + } + for (k = 0 ; k < n ; k++) + { + Lperm [k] = Wi [k] ; + } + + for (k = 0 ; k < n ; k++) + { + Wi [k] = Lcolcount [Post [k]] ; + } + for (k = 0 ; k < n ; k++) + { + Lcolcount [k] = Wi [k] ; + } + for (k = 0 ; k < n ; k++) + { + InvPost [Post [k]] = k ; + } + + /* updated Lparent needed only for supernodal case */ + for (newchild = 0 ; newchild < n ; newchild++) + { + oldchild = Post [newchild] ; + oldparent = Lparent [oldchild] ; + newparent = (oldparent == EMPTY) ? EMPTY : InvPost [oldparent] ; + Wi [newchild] = newparent ; + } + for (k = 0 ; k < n ; k++) + { + Lparent [k] = Wi [k] ; + } + /* done using Iwork as workspace ] */ + + /* L is now postordered, no longer in natural ordering */ + if (L->ordering == CHOLMOD_NATURAL) + { + L->ordering = CHOLMOD_POSTORDERED ; + } + } + } + + /* ---------------------------------------------------------------------- */ + /* supernodal analysis, if requested or if selected automatically */ + /* ---------------------------------------------------------------------- */ + +#ifndef NSUPERNODAL + if (Common->supernodal > CHOLMOD_AUTO + || (Common->supernodal == CHOLMOD_AUTO && + Common->lnz > 0 && + (Common->fl / Common->lnz) >= Common->supernodal_switch)) + { + cholmod_sparse *S, *F, *A2, *A1 ; + + permute_matrices (A, L->ordering, Lperm, fset, fsize, TRUE, + &A1, &A2, &S, &F, Common) ; + + /* workspace: Flag (nrow), Head (nrow), Iwork (5*nrow) */ + CHOLMOD(super_symbolic2) (for_cholesky, S, F, Lparent, L, Common) ; + PRINT1 (("status %d\n", Common->status)) ; + + CHOLMOD(free_sparse) (&A1, Common) ; + CHOLMOD(free_sparse) (&A2, Common) ; + } +#endif + + /* ---------------------------------------------------------------------- */ + /* free temporary matrices and workspace, and return result L */ + /* ---------------------------------------------------------------------- */ + + FREE_WORKSPACE_AND_RETURN ; +} +#endif diff --git a/src/CHOLMOD/Cholesky/cholmod_colamd.c b/src/CHOLMOD/Cholesky/cholmod_colamd.c new file mode 100644 index 0000000..b2d06b0 --- /dev/null +++ b/src/CHOLMOD/Cholesky/cholmod_colamd.c @@ -0,0 +1,209 @@ +/* ========================================================================== */ +/* === Cholesky/cholmod_colamd ============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD interface to the COLAMD ordering routine (version 2.4 or later). + * Finds a permutation p such that the Cholesky factorization of PAA'P' is + * sparser than AA' using colamd. If the postorder input parameter is TRUE, + * the column etree is found and postordered, and the colamd ordering is then + * combined with its postordering. A must be unsymmetric. + * + * There can be no duplicate entries in f. + * f can be length 0 to n if A is m-by-n. + * + * workspace: Iwork (4*nrow+ncol), Head (nrow+1), Flag (nrow) + * Allocates a copy of its input matrix, which + * is then used as CCOLAMD's workspace. + * + * Supports any xtype (pattern, real, complex, or zomplex) + */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "colamd.h" +#include "cholmod_cholesky.h" + +#if (!defined (COLAMD_VERSION) || (COLAMD_VERSION < COLAMD_VERSION_CODE (2,5))) +#error "COLAMD v2.5 or later is required" +#endif + +/* ========================================================================== */ +/* === cholmod_colamd ======================================================= */ +/* ========================================================================== */ + +int CHOLMOD(colamd) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int postorder, /* if TRUE, follow with a coletree postorder */ + /* ---- output --- */ + Int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) +{ + double knobs [COLAMD_KNOBS] ; + cholmod_sparse *C ; + Int *NewPerm, *Parent, *Post, *Work2n ; + Int k, nrow, ncol ; + size_t s, alen ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (Perm, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + if (A->stype != 0) + { + ERROR (CHOLMOD_INVALID, "matrix must be unsymmetric") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nrow = A->nrow ; + ncol = A->ncol ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* Note: this is less than the space used in cholmod_analyze, so if + * cholmod_colamd is being called by that routine, no space will be + * allocated. + */ + + /* s = 4*nrow + ncol */ + s = CHOLMOD(mult_size_t) (nrow, 4, &ok) ; + s = CHOLMOD(add_size_t) (s, ncol, &ok) ; + +#ifdef LONG + alen = colamd_l_recommended (A->nzmax, ncol, nrow) ; + colamd_l_set_defaults (knobs) ; +#else + alen = colamd_recommended (A->nzmax, ncol, nrow) ; + colamd_set_defaults (knobs) ; +#endif + + if (!ok || alen == 0) + { + ERROR (CHOLMOD_TOO_LARGE, "matrix invalid or too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (0, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* allocate COLAMD workspace */ + /* ---------------------------------------------------------------------- */ + + /* colamd_printf is only available in colamd v2.4 or later */ + colamd_printf = Common->print_function ; + + C = CHOLMOD(allocate_sparse) (ncol, nrow, alen, TRUE, TRUE, 0, + CHOLMOD_PATTERN, Common) ; + + /* ---------------------------------------------------------------------- */ + /* copy (and transpose) the input matrix A into the colamd workspace */ + /* ---------------------------------------------------------------------- */ + + /* C = A (:,f)', which also packs A if needed. */ + /* workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset) */ + ok = CHOLMOD(transpose_unsym) (A, 0, NULL, fset, fsize, C, Common) ; + + /* ---------------------------------------------------------------------- */ + /* order the matrix (destroys the contents of C->i and C->p) */ + /* ---------------------------------------------------------------------- */ + + /* get parameters */ + if (Common->current < 0 || Common->current >= CHOLMOD_MAXMETHODS) + { + /* this is the CHOLMOD default, not the COLAMD default */ + knobs [COLAMD_DENSE_ROW] = -1 ; + } + else + { + /* get the knobs from the Common parameters */ + knobs [COLAMD_DENSE_COL] = Common->method[Common->current].prune_dense ; + knobs [COLAMD_DENSE_ROW] = Common->method[Common->current].prune_dense2; + knobs [COLAMD_AGGRESSIVE] = Common->method[Common->current].aggressive ; + } + + if (ok) + { + Int *Cp ; + Int stats [COLAMD_STATS] ; + Cp = C->p ; + +#ifdef LONG + colamd_l (ncol, nrow, alen, C->i, Cp, knobs, stats) ; +#else + colamd (ncol, nrow, alen, C->i, Cp, knobs, stats) ; +#endif + + ok = stats [COLAMD_STATUS] ; + ok = (ok == COLAMD_OK || ok == COLAMD_OK_BUT_JUMBLED) ; + /* permutation returned in C->p, if the ordering succeeded */ + for (k = 0 ; k < nrow ; k++) + { + Perm [k] = Cp [k] ; + } + } + + CHOLMOD(free_sparse) (&C, Common) ; + + /* ---------------------------------------------------------------------- */ + /* column etree postordering */ + /* ---------------------------------------------------------------------- */ + + if (postorder) + { + /* use the last 2*n space in Iwork for Parent and Post */ + Work2n = Common->Iwork ; + Work2n += 2*((size_t) nrow) + ncol ; + Parent = Work2n ; /* size nrow (i/i/l) */ + Post = Work2n + nrow ; /* size nrow (i/i/l) */ + + /* workspace: Iwork (2*nrow+ncol), Flag (nrow), Head (nrow+1) */ + ok = ok && CHOLMOD(analyze_ordering) (A, CHOLMOD_COLAMD, Perm, fset, + fsize, Parent, Post, NULL, NULL, NULL, Common) ; + + /* combine the colamd permutation with its postordering */ + if (ok) + { + NewPerm = Common->Iwork ; /* size nrow (i/i/l) */ + for (k = 0 ; k < nrow ; k++) + { + NewPerm [k] = Perm [Post [k]] ; + } + for (k = 0 ; k < nrow ; k++) + { + Perm [k] = NewPerm [k] ; + } + } + } + + return (ok) ; +} +#endif diff --git a/src/CHOLMOD/Cholesky/cholmod_etree.c b/src/CHOLMOD/Cholesky/cholmod_etree.c new file mode 100644 index 0000000..0dcb85f --- /dev/null +++ b/src/CHOLMOD/Cholesky/cholmod_etree.c @@ -0,0 +1,226 @@ +/* ========================================================================== */ +/* === Cholesky/cholmod_etree =============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Compute the elimination tree of A or A'*A + * + * In the symmetric case, the upper triangular part of A is used. Entries not + * in this part of the matrix are ignored. Computing the etree of a symmetric + * matrix from just its lower triangular entries is not supported. + * + * In the unsymmetric case, all of A is used, and the etree of A'*A is computed. + * + * References: + * + * J. Liu, "A compact row storage scheme for Cholesky factors", ACM Trans. + * Math. Software, vol 12, 1986, pp. 127-148. + * + * J. Liu, "The role of elimination trees in sparse factorization", SIAM J. + * Matrix Analysis & Applic., vol 11, 1990, pp. 134-172. + * + * J. Gilbert, X. Li, E. Ng, B. Peyton, "Computing row and column counts for + * sparse QR and LU factorization", BIT, vol 41, 2001, pp. 693-710. + * + * workspace: symmetric: Iwork (nrow), unsymmetric: Iwork (nrow+ncol) + * + * Supports any xtype (pattern, real, complex, or zomplex) + */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "cholmod_cholesky.h" + +/* ========================================================================== */ +/* === update_etree ========================================================= */ +/* ========================================================================== */ + +static void update_etree +( + /* inputs, not modified */ + Int k, /* process the edge (k,i) in the input graph */ + Int i, + /* inputs, modified on output */ + Int Parent [ ], /* Parent [t] = p if p is the parent of t */ + Int Ancestor [ ] /* Ancestor [t] is the ancestor of node t in the + partially-constructed etree */ +) +{ + Int a ; + for ( ; ; ) /* traverse the path from k to the root of the tree */ + { + a = Ancestor [k] ; + if (a == i) + { + /* final ancestor reached; no change to tree */ + return ; + } + /* perform path compression */ + Ancestor [k] = i ; + if (a == EMPTY) + { + /* final ancestor undefined; this is a new edge in the tree */ + Parent [k] = i ; + return ; + } + /* traverse up to the ancestor of k */ + k = a ; + } +} + +/* ========================================================================== */ +/* === cholmod_etree ======================================================== */ +/* ========================================================================== */ + +/* Find the elimination tree of A or A'*A */ + +int CHOLMOD(etree) +( + /* ---- input ---- */ + cholmod_sparse *A, + /* ---- output --- */ + Int *Parent, /* size ncol. Parent [j] = p if p is the parent of j */ + /* --------------- */ + cholmod_common *Common +) +{ + Int *Ap, *Ai, *Anz, *Ancestor, *Prev, *Iwork ; + Int i, j, jprev, p, pend, nrow, ncol, packed, stype ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (Parent, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + stype = A->stype ; + + /* s = A->nrow + (stype ? 0 : A->ncol) */ + s = CHOLMOD(add_size_t) (A->nrow, (stype ? 0 : A->ncol), &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (0, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; /* out of memory */ + } + + ASSERT (CHOLMOD(dump_sparse) (A, "etree", Common) >= 0) ; + Iwork = Common->Iwork ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + ncol = A->ncol ; /* the number of columns of A */ + nrow = A->nrow ; /* the number of rows of A */ + Ap = A->p ; /* size ncol+1, column pointers for A */ + Ai = A->i ; /* the row indices of A */ + Anz = A->nz ; /* number of nonzeros in each column of A */ + packed = A->packed ; + Ancestor = Iwork ; /* size ncol (i/i/l) */ + + for (j = 0 ; j < ncol ; j++) + { + Parent [j] = EMPTY ; + Ancestor [j] = EMPTY ; + } + + /* ---------------------------------------------------------------------- */ + /* compute the etree */ + /* ---------------------------------------------------------------------- */ + + if (stype > 0) + { + + /* ------------------------------------------------------------------ */ + /* symmetric (upper) case: compute etree (A) */ + /* ------------------------------------------------------------------ */ + + for (j = 0 ; j < ncol ; j++) + { + /* for each row i in column j of triu(A), excluding the diagonal */ + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i < j) + { + update_etree (i, j, Parent, Ancestor) ; + } + } + } + + } + else if (stype == 0) + { + + /* ------------------------------------------------------------------ */ + /* unsymmetric case: compute etree (A'*A) */ + /* ------------------------------------------------------------------ */ + + Prev = Iwork + ncol ; /* size nrow (i/i/l) */ + for (i = 0 ; i < nrow ; i++) + { + Prev [i] = EMPTY ; + } + for (j = 0 ; j < ncol ; j++) + { + /* for each row i in column j of A */ + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + /* a graph is constructed dynamically with one path per row + * of A. If the ith row of A contains column indices + * (j1,j2,j3,j4) then the new graph has edges (j1,j2), (j2,j3), + * and (j3,j4). When at node i of this path-graph, all edges + * (jprev,j) are considered, where jprevPerm). If A is unsymmetric, either + * A(p,:)*A(p,:)'+beta*I or A(p,f)*A(p,f)'+beta*I is factorized. The set f and + * the nonzero pattern of the matrix A must be the same as the matrix passed to + * cholmod_analyze for the supernodal case. For the simplicial case, it can + * be different, but it should be the same for best performance. beta is real. + * + * A simplicial factorization or supernodal factorization is chosen, based on + * the type of the factor L. If L->is_super is TRUE, a supernodal LL' + * factorization is computed. Otherwise, a simplicial numeric factorization + * is computed, either LL' or LDL', depending on Common->final_ll. + * + * Once the factorization is complete, it can be left as is or optionally + * converted into any simplicial numeric type, depending on the + * Common->final_* parameters. If converted from a supernodal to simplicial + * type, and the Common->final_resymbol parameter is true, then numerically + * zero entries in L due to relaxed supernodal amalgamation are removed from + * the simplicial factor (they are always left in the supernodal form of L). + * Entries that are numerically zero but present in the simplicial symbolic + * pattern of L are left in place (that is, the graph of L remains chordal). + * This is required for the update/downdate/rowadd/rowdel routines to work + * properly. + * + * workspace: Flag (nrow), Head (nrow+1), + * if symmetric: Iwork (2*nrow+2*nsuper) + * if unsymmetric: Iwork (2*nrow+MAX(2*nsuper,ncol)) + * where nsuper is 0 if simplicial, or the # of relaxed supernodes in + * L otherwise (nsuper <= nrow). + * if simplicial: W (nrow). + * Allocates up to two temporary copies of its input matrix (including + * both pattern and numerical values). + * + * If the matrix is not positive definite the routine returns TRUE, but + * sets Common->status to CHOLMOD_NOT_POSDEF and L->minor is set to the + * column at which the failure occurred. Columns L->minor to L->n-1 are + * set to zero. + * + * Supports any xtype (pattern, real, complex, or zomplex), except that the + * input matrix A cannot be pattern-only. If L is simplicial, its numeric + * xtype matches A on output. If L is supernodal, its xtype is real if A is + * real, or complex if A is complex or zomplex. + */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "cholmod_cholesky.h" + +#ifndef NSUPERNODAL +#include "cholmod_supernodal.h" +#endif + + +/* ========================================================================== */ +/* === cholmod_factorize ==================================================== */ +/* ========================================================================== */ + +/* Factorizes PAP' (or PAA'P' if A->stype is 0), using a factor obtained + * from cholmod_analyze. The analysis can be re-used simply by calling this + * routine a second time with another matrix. A must have the same nonzero + * pattern as that passed to cholmod_analyze. */ + +int CHOLMOD(factorize) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + /* ---- in/out --- */ + cholmod_factor *L, /* resulting factorization */ + /* --------------- */ + cholmod_common *Common +) +{ + double zero [2] ; + zero [0] = 0 ; + zero [1] = 0 ; + return (CHOLMOD(factorize_p) (A, zero, NULL, 0, L, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_factorize_p ================================================== */ +/* ========================================================================== */ + +/* Same as cholmod_factorize, but with more options. */ + +int CHOLMOD(factorize_p) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + double beta [2], /* factorize beta*I+A or beta*I+A'*A */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* ---- in/out --- */ + cholmod_factor *L, /* resulting factorization */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_sparse *S, *F, *A1, *A2 ; + Int nrow, ncol, stype, convert, n, nsuper, grow2, status ; + size_t s, t, uncol ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + nrow = A->nrow ; + ncol = A->ncol ; + n = L->n ; + stype = A->stype ; + if (L->n != A->nrow) + { + ERROR (CHOLMOD_INVALID, "A and L dimensions do not match") ; + return (FALSE) ; + } + if (stype != 0 && nrow != ncol) + { + ERROR (CHOLMOD_INVALID, "matrix invalid") ; + return (FALSE) ; + } + DEBUG (CHOLMOD(dump_sparse) (A, "A for cholmod_factorize", Common)) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + nsuper = (L->is_super ? L->nsuper : 0) ; + uncol = ((stype != 0) ? 0 : ncol) ; + + /* s = 2*nrow + MAX (uncol, 2*nsuper) */ + s = CHOLMOD(mult_size_t) (nsuper, 2, &ok) ; + s = MAX (uncol, s) ; + t = CHOLMOD(mult_size_t) (nrow, 2, &ok) ; + s = CHOLMOD(add_size_t) (s, t, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (nrow, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + + S = NULL ; + F = NULL ; + A1 = NULL ; + A2 = NULL ; + + /* convert to another form when done, if requested */ + convert = !(Common->final_asis) ; + + /* ---------------------------------------------------------------------- */ + /* perform supernodal LL' or simplicial LDL' factorization */ + /* ---------------------------------------------------------------------- */ + + if (L->is_super) + { + +#ifndef NSUPERNODAL + + /* ------------------------------------------------------------------ */ + /* supernodal factorization */ + /* ------------------------------------------------------------------ */ + + if (L->ordering == CHOLMOD_NATURAL) + { + + /* -------------------------------------------------------------- */ + /* natural ordering */ + /* -------------------------------------------------------------- */ + + if (stype > 0) + { + /* S = tril (A'), F not needed */ + /* workspace: Iwork (nrow) */ + A1 = CHOLMOD(ptranspose) (A, 2, NULL, NULL, 0, Common) ; + S = A1 ; + } + else if (stype < 0) + { + /* This is the fastest option for the natural ordering */ + /* S = A; F not needed */ + S = A ; + } + else + { + /* F = A(:,f)' */ + /* workspace: Iwork (nrow) */ + /* workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset)*/ + A1 = CHOLMOD(ptranspose) (A, 2, NULL, fset, fsize, Common) ; + F = A1 ; + /* S = A */ + S = A ; + } + + } + else + { + + /* -------------------------------------------------------------- */ + /* permute the input matrix before factorization */ + /* -------------------------------------------------------------- */ + + if (stype > 0) + { + /* This is the fastest option for factoring a permuted matrix */ + /* S = tril (PAP'); F not needed */ + /* workspace: Iwork (2*nrow) */ + A1 = CHOLMOD(ptranspose) (A, 2, L->Perm, NULL, 0, Common) ; + S = A1 ; + } + else if (stype < 0) + { + /* A2 = triu (PAP') */ + /* workspace: Iwork (2*nrow) */ + A2 = CHOLMOD(ptranspose) (A, 2, L->Perm, NULL, 0, Common) ; + /* S = tril (A2'); F not needed */ + /* workspace: Iwork (nrow) */ + A1 = CHOLMOD(ptranspose) (A2, 2, NULL, NULL, 0, Common) ; + S = A1 ; + CHOLMOD(free_sparse) (&A2, Common) ; + ASSERT (A2 == NULL) ; + } + else + { + /* F = A(p,f)' */ + /* workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset)*/ + A1 = CHOLMOD(ptranspose) (A, 2, L->Perm, fset, fsize, Common) ; + F = A1 ; + /* S = F' */ + /* workspace: Iwork (nrow) */ + A2 = CHOLMOD(ptranspose) (F, 2, NULL, NULL, 0, Common) ; + S = A2 ; + } + } + + /* ------------------------------------------------------------------ */ + /* supernodal factorization */ + /* ------------------------------------------------------------------ */ + + /* workspace: Flag (nrow), Head (nrow+1), Iwork (2*nrow+2*nsuper) */ + if (Common->status == CHOLMOD_OK) + { + CHOLMOD(super_numeric) (S, F, beta, L, Common) ; + } + status = Common->status ; + ASSERT (IMPLIES (status >= CHOLMOD_OK, L->xtype != CHOLMOD_PATTERN)) ; + + /* ------------------------------------------------------------------ */ + /* convert to final form, if requested */ + /* ------------------------------------------------------------------ */ + + if (Common->status >= CHOLMOD_OK && convert) + { + /* workspace: none */ + ok = CHOLMOD(change_factor) (L->xtype, Common->final_ll, + Common->final_super, Common->final_pack, + Common->final_monotonic, L, Common) ; + if (ok && Common->final_resymbol && !(L->is_super)) + { + /* workspace: Flag (nrow), Head (nrow+1), + * if symmetric: Iwork (2*nrow) + * if unsymmetric: Iwork (2*nrow+ncol) */ + CHOLMOD(resymbol_noperm) (S, fset, fsize, Common->final_pack, + L, Common) ; + } + } + +#else + + /* ------------------------------------------------------------------ */ + /* CHOLMOD Supernodal module not installed */ + /* ------------------------------------------------------------------ */ + + status = CHOLMOD_NOT_INSTALLED ; + ERROR (CHOLMOD_NOT_INSTALLED,"Supernodal module not installed") ; + +#endif + + } + else + { + + /* ------------------------------------------------------------------ */ + /* simplicial LDL' factorization */ + /* ------------------------------------------------------------------ */ + + /* Permute the input matrix A if necessary. cholmod_rowfac requires + * triu(A) in column form for the symmetric case, and A in column form + * for the unsymmetric case (the matrix S). The unsymmetric case + * requires A in row form, or equivalently A' in column form (the + * matrix F). + */ + + if (L->ordering == CHOLMOD_NATURAL) + { + + /* -------------------------------------------------------------- */ + /* natural ordering */ + /* -------------------------------------------------------------- */ + + if (stype > 0) + { + /* F is not needed, S = A */ + S = A ; + } + else if (stype < 0) + { + /* F is not needed, S = A' */ + /* workspace: Iwork (nrow) */ + A2 = CHOLMOD(ptranspose) (A, 2, NULL, NULL, 0, Common) ; + S = A2 ; + } + else + { + /* F = A (:,f)' */ + /* workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset)*/ + A1 = CHOLMOD(ptranspose) (A, 2, NULL, fset, fsize, Common) ; + F = A1 ; + S = A ; + } + + } + else + { + + /* -------------------------------------------------------------- */ + /* permute the input matrix before factorization */ + /* -------------------------------------------------------------- */ + + if (stype > 0) + { + /* F = tril (A (p,p)') */ + /* workspace: Iwork (2*nrow) */ + A1 = CHOLMOD(ptranspose) (A, 2, L->Perm, NULL, 0, Common) ; + /* A2 = triu (F') */ + /* workspace: Iwork (nrow) */ + A2 = CHOLMOD(ptranspose) (A1, 2, NULL, NULL, 0, Common) ; + /* the symmetric case does not need F, free it and set to NULL*/ + CHOLMOD(free_sparse) (&A1, Common) ; + } + else if (stype < 0) + { + /* A2 = triu (A (p,p)'), F not needed. This is the fastest + * way to factorize a matrix using the simplicial routine + * (cholmod_rowfac). */ + /* workspace: Iwork (2*nrow) */ + A2 = CHOLMOD(ptranspose) (A, 2, L->Perm, NULL, 0, Common) ; + } + else + { + /* F = A (p,f)' */ + /* workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset)*/ + A1 = CHOLMOD(ptranspose) (A, 2, L->Perm, fset, fsize, Common) ; + F = A1 ; + /* A2 = F' */ + /* workspace: Iwork (nrow) */ + A2 = CHOLMOD(ptranspose) (F, 2, NULL, NULL, 0, Common) ; + } + S = A2 ; + } + + /* ------------------------------------------------------------------ */ + /* simplicial LDL' or LL' factorization */ + /* ------------------------------------------------------------------ */ + + /* factorize beta*I+S (symmetric) or beta*I+F*F' (unsymmetric) */ + /* workspace: Flag (nrow), W (nrow), Iwork (2*nrow) */ + if (Common->status == CHOLMOD_OK) + { + grow2 = Common->grow2 ; + L->is_ll = BOOLEAN (Common->final_ll) ; + if (L->xtype == CHOLMOD_PATTERN && Common->final_pack) + { + /* allocate a factor with exactly the space required */ + Common->grow2 = 0 ; + } + CHOLMOD(rowfac) (S, F, beta, 0, nrow, L, Common) ; + Common->grow2 = grow2 ; + } + status = Common->status ; + + /* ------------------------------------------------------------------ */ + /* convert to final form, if requested */ + /* ------------------------------------------------------------------ */ + + if (Common->status >= CHOLMOD_OK && convert) + { + /* workspace: none */ + CHOLMOD(change_factor) (L->xtype, L->is_ll, FALSE, + Common->final_pack, Common->final_monotonic, L, Common) ; + } + } + + /* ---------------------------------------------------------------------- */ + /* free A1 and A2 if they exist */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(free_sparse) (&A1, Common) ; + CHOLMOD(free_sparse) (&A2, Common) ; + Common->status = MAX (Common->status, status) ; + return (Common->status >= CHOLMOD_OK) ; +} +#endif diff --git a/src/CHOLMOD/Cholesky/cholmod_postorder.c b/src/CHOLMOD/Cholesky/cholmod_postorder.c new file mode 100644 index 0000000..f66b1df --- /dev/null +++ b/src/CHOLMOD/Cholesky/cholmod_postorder.c @@ -0,0 +1,291 @@ +/* ========================================================================== */ +/* === Cholesky/cholmod_postorder =========================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Compute the postorder of a tree. */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "cholmod_cholesky.h" + + +/* ========================================================================== */ +/* === dfs ================================================================== */ +/* ========================================================================== */ + +/* The code below includes both a recursive and non-recursive depth-first-search + * of a tree. The recursive code is simpler, but can lead to stack overflow. + * It is left here for reference, to understand what the non-recursive code + * is computing. To try the recursive version, uncomment the following + * #define, or compile the code with -DRECURSIVE. Be aware that stack + * overflow may occur. +#define RECURSIVE + */ + +#ifdef RECURSIVE + +/* recursive version: a working code for reference only, not actual use */ + +static Int dfs /* return the new value of k */ +( + Int p, /* start a DFS at node p */ + Int k, /* start the node numbering at k */ + Int Post [ ], /* Post ordering, modified on output */ + Int Head [ ], /* Head [p] = youngest child of p; EMPTY on output */ + Int Next [ ], /* Next [j] = sibling of j; unmodified */ + Int Pstack [ ] /* unused */ +) +{ + Int j ; + /* start a DFS at each child of node p */ + for (j = Head [p] ; j != EMPTY ; j = Next [j]) + { + /* start a DFS at child node j */ + k = dfs (j, k, Post, Head, Next, Pstack) ; + } + Post [k++] = p ; /* order node p as the kth node */ + Head [p] = EMPTY ; /* link list p no longer needed */ + return (k) ; /* the next node will be numbered k */ +} + +#else + +/* non-recursive version for actual use */ + +static Int dfs /* return the new value of k */ +( + Int p, /* start the DFS at a root node p */ + Int k, /* start the node numbering at k */ + Int Post [ ], /* Post ordering, modified on output */ + Int Head [ ], /* Head [p] = youngest child of p; EMPTY on output */ + Int Next [ ], /* Next [j] = sibling of j; unmodified */ + Int Pstack [ ] /* workspace of size n, undefined on input or output */ +) +{ + Int j, phead ; + + /* put the root node on the stack */ + Pstack [0] = p ; + phead = 0 ; + + /* while the stack is not empty, do: */ + while (phead >= 0) + { + /* grab the node p from top of the stack and get its youngest child j */ + p = Pstack [phead] ; + j = Head [p] ; + if (j == EMPTY) + { + /* all children of p ordered. remove p from stack and order it */ + phead-- ; + Post [k++] = p ; /* order node p as the kth node */ + } + else + { + /* leave p on the stack. Start a DFS at child node j by putting + * j on the stack and removing j from the list of children of p. */ + Head [p] = Next [j] ; + Pstack [++phead] = j ; + } + } + return (k) ; /* the next node will be numbered k */ +} + +#endif + +/* ========================================================================== */ +/* === cholmod_postorder ==================================================== */ +/* ========================================================================== */ + +/* Postorder a tree. The tree is either an elimination tree (the output from + * from cholmod_etree) or a component tree (from cholmod_nested_dissection). + * + * An elimination tree is a complete tree of n nodes with Parent [j] > j or + * Parent [j] = EMPTY if j is a root. On output Post [0..n-1] is a complete + * permutation vector. + * + * A component tree is a subset of 0..n-1. Parent [j] = -2 if node j is not + * in the component tree. Parent [j] = EMPTY if j is a root of the component + * tree, and Parent [j] is in the range 0 to n-1 if j is in the component + * tree but not a root. On output, Post [k] is defined only for nodes in + * the component tree. Post [k] = j if node j is the kth node in the + * postordered component tree, where k is in the range 0 to the number of + * components minus 1. + * + * Node j is ignored and not included in the postorder if Parent [j] < EMPTY. + * + * As a result, check_parent (Parent, n,...) may fail on input, since + * cholmod_check_parent assumes Parent is an elimination tree. Similarly, + * cholmod_check_perm (Post, ...) may fail on output, since Post is a partial + * permutation if Parent is a component tree. + * + * An optional node weight can be given. When starting a postorder at node j, + * the children of j are ordered in increasing order of their weight. + * If no weights are given (Weight is NULL) then children are ordered in + * increasing order of their node number. The weight of a node must be in the + * range 0 to n-1. Weights outside that range are silently converted to that + * range (weights < 0 are treated as zero, and weights >= n are treated as n-1). + * + * + * workspace: Head (n), Iwork (2*n) + */ + +SuiteSparse_long CHOLMOD(postorder) /* return # of nodes postordered */ +( + /* ---- input ---- */ + Int *Parent, /* size n. Parent [j] = p if p is the parent of j */ + size_t n, + Int *Weight, /* size n, optional. Weight [j] is weight of node j */ + /* ---- output --- */ + Int *Post, /* size n. Post [k] = j is kth in postordered tree */ + /* --------------- */ + cholmod_common *Common +) +{ + Int *Head, *Next, *Pstack, *Iwork ; + Int j, p, k, w, nextj ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (Parent, EMPTY) ; + RETURN_IF_NULL (Post, EMPTY) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* s = 2*n */ + s = CHOLMOD(mult_size_t) (n, 2, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (EMPTY) ; + } + + CHOLMOD(allocate_work) (n, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (EMPTY) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Head = Common->Head ; /* size n+1, initially all EMPTY */ + Iwork = Common->Iwork ; + Next = Iwork ; /* size n (i/i/l) */ + Pstack = Iwork + n ; /* size n (i/i/l) */ + + /* ---------------------------------------------------------------------- */ + /* construct a link list of children for each node */ + /* ---------------------------------------------------------------------- */ + + if (Weight == NULL) + { + + /* in reverse order so children are in ascending order in each list */ + for (j = n-1 ; j >= 0 ; j--) + { + p = Parent [j] ; + if (p >= 0 && p < ((Int) n)) + { + /* add j to the list of children for node p */ + Next [j] = Head [p] ; + Head [p] = j ; + } + } + + /* Head [p] = j if j is the youngest (least-numbered) child of p */ + /* Next [j1] = j2 if j2 is the next-oldest sibling of j1 */ + + } + else + { + + /* First, construct a set of link lists according to Weight. + * + * Whead [w] = j if node j is the first node in bucket w. + * Next [j1] = j2 if node j2 follows j1 in a link list. + */ + + Int *Whead = Pstack ; /* use Pstack as workspace for Whead [ */ + + for (w = 0 ; w < ((Int) n) ; w++) + { + Whead [w] = EMPTY ; + } + /* do in forward order, so nodes that ties are ordered by node index */ + for (j = 0 ; j < ((Int) n) ; j++) + { + p = Parent [j] ; + if (p >= 0 && p < ((Int) n)) + { + w = Weight [j] ; + w = MAX (0, w) ; + w = MIN (w, ((Int) n) - 1) ; + /* place node j at the head of link list for weight w */ + Next [j] = Whead [w] ; + Whead [w] = j ; + } + } + + /* traverse weight buckets, placing each node in its parent's list */ + for (w = n-1 ; w >= 0 ; w--) + { + for (j = Whead [w] ; j != EMPTY ; j = nextj) + { + nextj = Next [j] ; + /* put node j in the link list of its parent */ + p = Parent [j] ; + ASSERT (p >= 0 && p < ((Int) n)) ; + Next [j] = Head [p] ; + Head [p] = j ; + } + } + + /* Whead no longer needed ] */ + /* Head [p] = j if j is the lightest child of p */ + /* Next [j1] = j2 if j2 is the next-heaviest sibling of j1 */ + } + + /* ---------------------------------------------------------------------- */ + /* start a DFS at each root node of the etree */ + /* ---------------------------------------------------------------------- */ + + k = 0 ; + for (j = 0 ; j < ((Int) n) ; j++) + { + if (Parent [j] == EMPTY) + { + /* j is the root of a tree; start a DFS here */ + k = dfs (j, k, Post, Head, Next, Pstack) ; + } + } + + /* this would normally be EMPTY already, unless Parent is invalid */ + for (j = 0 ; j < ((Int) n) ; j++) + { + Head [j] = EMPTY ; + } + + PRINT1 (("postordered "ID" nodes\n", k)) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + return (k) ; +} +#endif diff --git a/src/CHOLMOD/Cholesky/cholmod_rcond.c b/src/CHOLMOD/Cholesky/cholmod_rcond.c new file mode 100644 index 0000000..f261e31 --- /dev/null +++ b/src/CHOLMOD/Cholesky/cholmod_rcond.c @@ -0,0 +1,160 @@ +/* ========================================================================== */ +/* === Cholesky/cholmod_rcond =============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Return a rough estimate of the reciprocal of the condition number: + * the minimum entry on the diagonal of L (or absolute entry of D for an LDL' + * factorization) divided by the maximum entry (squared for LL'). L can be + * real, complex, or zomplex. Returns -1 on error, 0 if the matrix is singular + * or has a zero entry on the diagonal of L, 1 if the matrix is 0-by-0, or + * min(diag(L))/max(diag(L)) otherwise. Never returns NaN; if L has a NaN on + * the diagonal it returns zero instead. + * + * For an LL' factorization, (min(diag(L))/max(diag(L)))^2 is returned. + * For an LDL' factorization, (min(diag(D))/max(diag(D))) is returned. + */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "cholmod_cholesky.h" + +/* ========================================================================== */ +/* === LMINMAX ============================================================== */ +/* ========================================================================== */ + +/* Update lmin and lmax for one entry L(j,j) */ + +#define FIRST_LMINMAX(Ljj,lmin,lmax) \ +{ \ + double ljj = Ljj ; \ + if (IS_NAN (ljj)) \ + { \ + return (0) ; \ + } \ + lmin = ljj ; \ + lmax = ljj ; \ +} + +#define LMINMAX(Ljj,lmin,lmax) \ +{ \ + double ljj = Ljj ; \ + if (IS_NAN (ljj)) \ + { \ + return (0) ; \ + } \ + if (ljj < lmin) \ + { \ + lmin = ljj ; \ + } \ + else if (ljj > lmax) \ + { \ + lmax = ljj ; \ + } \ +} + +/* ========================================================================== */ +/* === cholmod_rcond ======================================================== */ +/* ========================================================================== */ + +double CHOLMOD(rcond) /* return min(diag(L)) / max(diag(L)) */ +( + /* ---- input ---- */ + cholmod_factor *L, + /* --------------- */ + cholmod_common *Common +) +{ + double lmin, lmax, rcond ; + double *Lx ; + Int *Lpi, *Lpx, *Super, *Lp ; + Int n, e, nsuper, s, k1, k2, psi, psend, psx, nsrow, nscol, jj, j ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (L, EMPTY) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, EMPTY) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + n = L->n ; + if (n == 0) + { + return (1) ; + } + if (L->minor < L->n) + { + return (0) ; + } + + e = (L->xtype == CHOLMOD_COMPLEX) ? 2 : 1 ; + + if (L->is_super) + { + /* L is supernodal */ + nsuper = L->nsuper ; /* number of supernodes in L */ + Lpi = L->pi ; /* column pointers for integer pattern */ + Lpx = L->px ; /* column pointers for numeric values */ + Super = L->super ; /* supernode sizes */ + Lx = L->x ; /* numeric values */ + FIRST_LMINMAX (Lx [0], lmin, lmax) ; /* first diagonal entry of L */ + for (s = 0 ; s < nsuper ; s++) + { + k1 = Super [s] ; /* first column in supernode s */ + k2 = Super [s+1] ; /* last column in supernode is k2-1 */ + psi = Lpi [s] ; /* first row index is L->s [psi] */ + psend = Lpi [s+1] ; /* last row index is L->s [psend-1] */ + psx = Lpx [s] ; /* first numeric entry is Lx [psx] */ + nsrow = psend - psi ; /* supernode is nsrow-by-nscol */ + nscol = k2 - k1 ; + for (jj = 0 ; jj < nscol ; jj++) + { + LMINMAX (Lx [e * (psx + jj + jj*nsrow)], lmin, lmax) ; + } + } + } + else + { + /* L is simplicial */ + Lp = L->p ; + Lx = L->x ; + if (L->is_ll) + { + /* LL' factorization */ + FIRST_LMINMAX (Lx [Lp [0]], lmin, lmax) ; + for (j = 1 ; j < n ; j++) + { + LMINMAX (Lx [e * Lp [j]], lmin, lmax) ; + } + } + else + { + /* LDL' factorization, the diagonal might be negative */ + FIRST_LMINMAX (fabs (Lx [Lp [0]]), lmin, lmax) ; + for (j = 1 ; j < n ; j++) + { + LMINMAX (fabs (Lx [e * Lp [j]]), lmin, lmax) ; + } + } + } + rcond = lmin / lmax ; + if (L->is_ll) + { + rcond = rcond*rcond ; + } + return (rcond) ; +} +#endif diff --git a/src/CHOLMOD/Cholesky/cholmod_resymbol.c b/src/CHOLMOD/Cholesky/cholmod_resymbol.c new file mode 100644 index 0000000..5b39cd9 --- /dev/null +++ b/src/CHOLMOD/Cholesky/cholmod_resymbol.c @@ -0,0 +1,608 @@ +/* ========================================================================== */ +/* === Cholesky/cholmod_resymbol ============================================ */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Recompute the symbolic pattern of L. Entries not in the symbolic pattern + * are dropped. L->Perm can be used (or not) to permute the input matrix A. + * + * These routines are used after a supernodal factorization is converted into + * a simplicial one, to remove zero entries that were added due to relaxed + * supernode amalgamation. They can also be used after a series of downdates + * to remove entries that would no longer be present if the matrix were + * factorized from scratch. A downdate (cholmod_updown) does not remove any + * entries from L. + * + * workspace: Flag (nrow), Head (nrow+1), + * if symmetric: Iwork (2*nrow) + * if unsymmetric: Iwork (2*nrow+ncol). + * Allocates up to 2 copies of its input matrix A (pattern only). + */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "cholmod_cholesky.h" + +/* ========================================================================== */ +/* === cholmod_resymbol ===================================================== */ +/* ========================================================================== */ + +/* Remove entries from L that are not in the factorization of P*A*P', P*A*A'*P', + * or P*F*F'*P' (depending on A->stype and whether fset is NULL or not). + * + * cholmod_resymbol is the same as cholmod_resymbol_noperm, except that it + * first permutes A according to L->Perm. A can be upper/lower/unsymmetric, + * in contrast to cholmod_resymbol_noperm (which can be lower or unsym). */ + +int CHOLMOD(resymbol) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int pack, /* if TRUE, pack the columns of L */ + /* ---- in/out --- */ + cholmod_factor *L, /* factorization, entries pruned on output */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_sparse *H, *F, *G ; + Int stype, nrow, ncol ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + Common->status = CHOLMOD_OK ; + if (L->is_super) + { + /* cannot operate on a supernodal factorization */ + ERROR (CHOLMOD_INVALID, "cannot operate on supernodal L") ; + return (FALSE) ; + } + if (L->n != A->nrow) + { + /* dimensions must agree */ + ERROR (CHOLMOD_INVALID, "A and L dimensions do not match") ; + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + stype = A->stype ; + nrow = A->nrow ; + ncol = A->ncol ; + + /* s = 2*nrow + (stype ? 0 : ncol) */ + s = CHOLMOD(mult_size_t) (nrow, 2, &ok) ; + s = CHOLMOD(add_size_t) (s, (stype ? 0 : ncol), &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (nrow, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* permute the input matrix if necessary */ + /* ---------------------------------------------------------------------- */ + + H = NULL ; + G = NULL ; + + if (stype > 0) + { + if (L->ordering == CHOLMOD_NATURAL) + { + /* F = triu(A)' */ + /* workspace: Iwork (nrow) */ + G = CHOLMOD(ptranspose) (A, 0, NULL, NULL, 0, Common) ; + } + else + { + /* F = triu(A(p,p))' */ + /* workspace: Iwork (2*nrow) */ + G = CHOLMOD(ptranspose) (A, 0, L->Perm, NULL, 0, Common) ; + } + F = G ; + } + else if (stype < 0) + { + if (L->ordering == CHOLMOD_NATURAL) + { + F = A ; + } + else + { + /* G = triu(A(p,p))' */ + /* workspace: Iwork (2*nrow) */ + G = CHOLMOD(ptranspose) (A, 0, L->Perm, NULL, 0, Common) ; + /* H = G' */ + /* workspace: Iwork (nrow) */ + H = CHOLMOD(ptranspose) (G, 0, NULL, NULL, 0, Common) ; + F = H ; + } + } + else + { + if (L->ordering == CHOLMOD_NATURAL) + { + F = A ; + } + else + { + /* G = A(p,f)' */ + /* workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset)*/ + G = CHOLMOD(ptranspose) (A, 0, L->Perm, fset, fsize, Common) ; + /* H = G' */ + /* workspace: Iwork (ncol) */ + H = CHOLMOD(ptranspose) (G, 0, NULL, NULL, 0, Common) ; + F = H ; + } + } + + /* No need to check for failure here. cholmod_resymbol_noperm will return + * FALSE if F is NULL. */ + + /* ---------------------------------------------------------------------- */ + /* resymbol */ + /* ---------------------------------------------------------------------- */ + + ok = CHOLMOD(resymbol_noperm) (F, fset, fsize, pack, L, Common) ; + + /* ---------------------------------------------------------------------- */ + /* free the temporary matrices, if they exist */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(free_sparse) (&H, Common) ; + CHOLMOD(free_sparse) (&G, Common) ; + return (ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_resymbol_noperm ============================================== */ +/* ========================================================================== */ + +/* Redo symbolic LDL' or LL' factorization of I + F*F' or I+A, where F=A(:,f). + * + * L already exists, but is a superset of the true dynamic pattern (simple + * column downdates and row deletions haven't pruned anything). Just redo the + * symbolic factorization and drop entries that are no longer there. The + * diagonal is not modified. The number of nonzeros in column j of L + * (L->nz[j]) can decrease. The column pointers (L->p[j]) remain unchanged if + * pack is FALSE or if L is not monotonic. Otherwise, the columns of L are + * packed in place. + * + * For the symmetric case, the columns of the lower triangular part of A + * are accessed by column. NOTE that this the transpose of the general case. + * + * For the unsymmetric case, F=A(:,f) is accessed by column. + * + * A need not be sorted, and can be packed or unpacked. If L->Perm is not + * identity, then A must already be permuted according to the permutation used + * to factorize L. The advantage of using this routine is that it does not + * need to create permuted copies of A first. + * + * This routine can be called if L is only partially factored via cholmod_rowfac + * since all it does is prune. If an entry is in F*F' or A, but not in L, it + * isn't added to L. + * + * L must be simplicial LDL' or LL'; it cannot be supernodal or symbolic. + * + * The set f is held in fset and fsize. + * fset = NULL means ":" in MATLAB. fset is ignored. + * fset != NULL means f = fset [0..fset-1]. + * fset != NULL and fsize = 0 means f is the empty set. + * There can be no duplicates in fset. + * Common->status is set to CHOLMOD_INVALID if fset is invalid. + * + * workspace: Flag (nrow), Head (nrow+1), + * if symmetric: Iwork (2*nrow) + * if unsymmetric: Iwork (2*nrow+ncol). + * Unlike cholmod_resymbol, this routine does not allocate any temporary + * copies of its input matrix. + */ + +int CHOLMOD(resymbol_noperm) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int pack, /* if TRUE, pack the columns of L */ + /* ---- in/out --- */ + cholmod_factor *L, /* factorization, entries pruned on output */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Lx, *Lz ; + Int i, j, k, row, parent, p, pend, pdest, ncol, apacked, sorted, nrow, nf, + use_fset, mark, jj, stype, xtype ; + Int *Ap, *Ai, *Anz, *Li, *Lp, *Lnz, *Flag, *Head, *Link, *Anext, *Iwork ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + ncol = A->ncol ; + nrow = A->nrow ; + stype = A->stype ; + ASSERT (IMPLIES (stype != 0, nrow == ncol)) ; + if (stype > 0) + { + /* symmetric, with upper triangular part, not supported */ + ERROR (CHOLMOD_INVALID, "symmetric upper not supported ") ; + return (FALSE) ; + } + if (L->is_super) + { + /* cannot operate on a supernodal or symbolic factorization */ + ERROR (CHOLMOD_INVALID, "cannot operate on supernodal L") ; + return (FALSE) ; + } + if (L->n != A->nrow) + { + /* dimensions must agree */ + ERROR (CHOLMOD_INVALID, "A and L dimensions do not match") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* s = 2*nrow + (stype ? 0 : ncol) */ + s = CHOLMOD(mult_size_t) (nrow, 2, &ok) ; + if (stype != 0) + { + s = CHOLMOD(add_size_t) (s, ncol, &ok) ; + } + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (nrow, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; /* out of memory */ + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Ai = A->i ; + Ap = A->p ; + Anz = A->nz ; + apacked = A->packed ; + sorted = A->sorted ; + + Li = L->i ; + Lx = L->x ; + Lz = L->z ; + Lp = L->p ; + Lnz = L->nz ; + xtype = L->xtype ; + + /* If L is monotonic on input, then it can be packed or + * unpacked on output, depending on the pack input parameter. */ + + /* cannot pack a non-monotonic matrix */ + if (!(L->is_monotonic)) + { + pack = FALSE ; + } + + ASSERT (L->nzmax >= (size_t) (Lp [L->n])) ; + + pdest = 0 ; + + PRINT1 (("\n\n===================== Resymbol pack %d Apacked %d\n", + pack, A->packed)) ; + ASSERT (CHOLMOD(dump_sparse) (A, "ReSymbol A:", Common) >= 0) ; + DEBUG (CHOLMOD(dump_factor) (L, "ReSymbol initial L (i, x):", Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Flag = Common->Flag ; /* size nrow */ + Head = Common->Head ; /* size nrow+1 */ + Iwork = Common->Iwork ; + Link = Iwork ; /* size nrow (i/i/l) [ */ + Lnz = Iwork + nrow ; /* size nrow (i/i/l), if L not packed */ + Anext = Iwork + 2*((size_t) nrow) ; /* size ncol (i/i/l), unsym. only */ + for (j = 0 ; j < nrow ; j++) + { + Link [j] = EMPTY ; + } + + /* use Lnz in L itself */ + Lnz = L->nz ; + ASSERT (Lnz != NULL) ; + + /* ---------------------------------------------------------------------- */ + /* for the unsymmetric case, queue each column of A (:,f) */ + /* ---------------------------------------------------------------------- */ + + /* place each column of the basis set on the link list corresponding to */ + /* the smallest row index in that column */ + + if (stype == 0) + { + use_fset = (fset != NULL) ; + if (use_fset) + { + nf = fsize ; + /* This is the only O(ncol) loop in cholmod_resymbol. + * It is required only to check the fset. */ + for (j = 0 ; j < ncol ; j++) + { + Anext [j] = -2 ; + } + for (jj = 0 ; jj < nf ; jj++) + { + j = fset [jj] ; + if (j < 0 || j > ncol || Anext [j] != -2) + { + /* out-of-range or duplicate entry in fset */ + ERROR (CHOLMOD_INVALID, "fset invalid") ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + return (FALSE) ; + } + /* flag column j as having been seen */ + Anext [j] = EMPTY ; + } + /* the fset is now valid */ + ASSERT (CHOLMOD(dump_perm) (fset, nf, ncol, "fset", Common)) ; + } + else + { + nf = ncol ; + } + for (jj = 0 ; jj < nf ; jj++) + { + j = (use_fset) ? (fset [jj]) : jj ; + /* column j is the fset; find the smallest row (if any) */ + p = Ap [j] ; + pend = (apacked) ? (Ap [j+1]) : (p + Anz [j]) ; + if (pend > p) + { + k = Ai [p] ; + if (!sorted) + { + for ( ; p < pend ; p++) + { + k = MIN (k, Ai [p]) ; + } + } + /* place column j on link list k */ + ASSERT (k >= 0 && k < nrow) ; + Anext [j] = Head [k] ; + Head [k] = j ; + } + } + } + + /* ---------------------------------------------------------------------- */ + /* recompute symbolic LDL' factorization */ + /* ---------------------------------------------------------------------- */ + + for (k = 0 ; k < nrow ; k++) + { + +#ifndef NDEBUG + PRINT1 (("\n\n================== Initial column k = "ID"\n", k)) ; + for (p = Lp [k] ; p < Lp [k] + Lnz [k] ; p++) + { + PRINT1 ((" row: "ID" value: ", Li [p])) ; + PRINT1 (("\n")) ; + } + PRINT1 (("Recomputing LDL, column k = "ID"\n", k)) ; +#endif + + /* ------------------------------------------------------------------ */ + /* compute column k of I+F*F' or I+A */ + /* ------------------------------------------------------------------ */ + + /* flag the diagonal entry */ + /* mark = CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + mark = Common->mark ; + + Flag [k] = mark ; + PRINT1 ((" row: "ID" (diagonal)\n", k)) ; + + if (stype != 0) + { + /* merge column k of A into Flag (lower triangular part only) */ + p = Ap [k] ; + pend = (apacked) ? (Ap [k+1]) : (p + Anz [k]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i > k) + { + Flag [i] = mark ; + } + } + } + else + { + /* for each column j whos first row index is in row k */ + for (j = Head [k] ; j != EMPTY ; j = Anext [j]) + { + /* merge column j of A into Flag */ + PRINT1 ((" ---- A column "ID"\n", j)) ; + p = Ap [j] ; + pend = (apacked) ? (Ap [j+1]) : (p + Anz [j]) ; + PRINT1 ((" length "ID" adding\n", pend-p)) ; + for ( ; p < pend ; p++) + { +#ifndef NDEBUG + ASSERT (Ai [p] >= k && Ai [p] < nrow) ; + if (Flag [Ai [p]] < mark) PRINT1 ((" row "ID"\n", Ai [p])) ; +#endif + Flag [Ai [p]] = mark ; + } + } + /* clear the kth link list */ + Head [k] = EMPTY ; + } + + /* ------------------------------------------------------------------ */ + /* compute pruned pattern of kth column of L = union of children */ + /* ------------------------------------------------------------------ */ + + /* for each column j of L whose parent is k */ + for (j = Link [k] ; j != EMPTY ; j = Link [j]) + { + /* merge column j of L into Flag */ + PRINT1 ((" ---- L column "ID"\n", k)) ; + ASSERT (j < k) ; + ASSERT (Lnz [j] > 0) ; + p = Lp [j] ; + pend = p + Lnz [j] ; + ASSERT (Li [p] == j && Li [p+1] == k) ; + p++ ; /* skip past the diagonal entry */ + for ( ; p < pend ; p++) + { + /* add to pattern */ + ASSERT (Li [p] >= k && Li [p] < nrow) ; + Flag [Li [p]] = mark ; + } + } + + /* ------------------------------------------------------------------ */ + /* prune the kth column of L */ + /* ------------------------------------------------------------------ */ + + PRINT1 (("Final column of L:\n")) ; + p = Lp [k] ; + pend = p + Lnz [k] ; + + if (pack) + { + /* shift column k upwards */ + Lp [k] = pdest ; + } + else + { + /* leave column k in place, just reduce Lnz [k] */ + pdest = p ; + } + + for ( ; p < pend ; p++) + { + ASSERT (pdest < pend) ; + ASSERT (pdest <= p) ; + row = Li [p] ; + ASSERT (row >= k && row < nrow) ; + if (Flag [row] == mark) + { + /* keep this entry */ + Li [pdest] = row ; + if (xtype == CHOLMOD_REAL) + { + Lx [pdest] = Lx [p] ; + } + else if (xtype == CHOLMOD_COMPLEX) + { + Lx [2*pdest ] = Lx [2*p ] ; + Lx [2*pdest+1] = Lx [2*p+1] ; + } + else if (xtype == CHOLMOD_ZOMPLEX) + { + Lx [pdest] = Lx [p] ; + Lz [pdest] = Lz [p] ; + } + pdest++ ; + } + } + + /* ------------------------------------------------------------------ */ + /* prepare this column for its parent */ + /* ------------------------------------------------------------------ */ + + Lnz [k] = pdest - Lp [k] ; + + PRINT1 ((" L("ID") length "ID"\n", k, Lnz [k])) ; + ASSERT (Lnz [k] > 0) ; + + /* parent is the first entry in the column after the diagonal */ + parent = (Lnz [k] > 1) ? (Li [Lp [k] + 1]) : EMPTY ; + + PRINT1 (("parent ("ID") = "ID"\n", k, parent)) ; + ASSERT ((parent > k && parent < nrow) || (parent == EMPTY)) ; + + if (parent != EMPTY) + { + Link [k] = Link [parent] ; + Link [parent] = k ; + } + } + + /* done using Iwork for Link, Lnz (if needed), and Anext ] */ + + /* ---------------------------------------------------------------------- */ + /* convert L to packed, if requested */ + /* ---------------------------------------------------------------------- */ + + if (pack) + { + /* finalize Lp */ + Lp [nrow] = pdest ; + /* Shrink L to be just large enough. It cannot fail. */ + /* workspace: none */ + ASSERT ((size_t) (Lp [nrow]) <= L->nzmax) ; + CHOLMOD(reallocate_factor) (Lp [nrow], L, Common) ; + ASSERT (Common->status >= CHOLMOD_OK) ; + } + + /* ---------------------------------------------------------------------- */ + /* clear workspace */ + /* ---------------------------------------------------------------------- */ + + /* CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + + DEBUG (CHOLMOD(dump_factor) (L, "ReSymbol final L (i, x):", Common)) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + return (TRUE) ; +} +#endif diff --git a/src/CHOLMOD/Cholesky/cholmod_rowcolcounts.c b/src/CHOLMOD/Cholesky/cholmod_rowcolcounts.c new file mode 100644 index 0000000..5b0290d --- /dev/null +++ b/src/CHOLMOD/Cholesky/cholmod_rowcolcounts.c @@ -0,0 +1,536 @@ +/* ========================================================================== */ +/* === Cholesky/cholmod_rowcolcounts ======================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Compute the row and column counts of the Cholesky factor L of the matrix + * A or A*A'. The etree and its postordering must already be computed (see + * cholmod_etree and cholmod_postorder) and given as inputs to this routine. + * + * For the symmetric case (LL'=A), A is accessed by column. Only the lower + * triangular part of A is used. Entries not in this part of the matrix are + * ignored. This is the same as storing the upper triangular part of A by + * rows, with entries in the lower triangular part being ignored. NOTE: this + * representation is the TRANSPOSE of the input to cholmod_etree. + * + * For the unsymmetric case (LL'=AA'), A is accessed by column. Equivalently, + * if A is viewed as a matrix in compressed-row form, this routine computes + * the row and column counts for L where LL'=A'A. If the input vector f is + * present, then F*F' is analyzed instead, where F = A(:,f). + * + * The set f is held in fset and fsize. + * fset = NULL means ":" in MATLAB. fset is ignored. + * fset != NULL means f = fset [0..fset-1]. + * fset != NULL and fsize = 0 means f is the empty set. + * Common->status is set to CHOLMOD_INVALID if fset is invalid. + * + * In both cases, the columns of A need not be sorted. + * A can be packed or unpacked. + * + * References: + * J. Gilbert, E. Ng, B. Peyton, "An efficient algorithm to compute row and + * column counts for sparse Cholesky factorization", SIAM J. Matrix Analysis & + * Applic., vol 15, 1994, pp. 1075-1091. + * + * J. Gilbert, X. Li, E. Ng, B. Peyton, "Computing row and column counts for + * sparse QR and LU factorization", BIT, vol 41, 2001, pp. 693-710. + * + * workspace: + * if symmetric: Flag (nrow), Iwork (2*nrow) + * if unsymmetric: Flag (nrow), Iwork (2*nrow+ncol), Head (nrow+1) + * + * Supports any xtype (pattern, real, complex, or zomplex). + */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "cholmod_cholesky.h" + +/* ========================================================================== */ +/* === initialize_node ====================================================== */ +/* ========================================================================== */ + +static int initialize_node /* initial work for kth node in postordered etree */ +( + Int k, /* at the kth step of the algorithm (and kth node) */ + Int Post [ ], /* Post [k] = i, the kth node in postordered etree */ + Int Parent [ ], /* Parent [i] is the parent of i in the etree */ + Int ColCount [ ], /* ColCount [c] is the current weight of node c */ + Int PrevNbr [ ] /* PrevNbr [u] = k if u was last considered at step k */ +) +{ + Int p, parent ; + /* determine p, the kth node in the postordered etree */ + p = Post [k] ; + /* adjust the weight if p is not a root of the etree */ + parent = Parent [p] ; + if (parent != EMPTY) + { + ColCount [parent]-- ; + } + /* flag node p to exclude self edges (p,p) */ + PrevNbr [p] = k ; + return (p) ; +} + + +/* ========================================================================== */ +/* === process_edge ========================================================= */ +/* ========================================================================== */ + +/* edge (p,u) is being processed. p < u is a descendant of its ancestor u in + * the etree. node p is the kth node in the postordered etree. */ + +static void process_edge +( + Int p, /* process edge (p,u) of the matrix */ + Int u, + Int k, /* we are at the kth node in the postordered etree */ + Int First [ ], /* First [i] = k if the postordering of first + * descendent of node i is k */ + Int PrevNbr [ ], /* u was last considered at step k = PrevNbr [u] */ + Int ColCount [ ], /* ColCount [c] is the current weight of node c */ + Int PrevLeaf [ ], /* s = PrevLeaf [u] means that s was the last leaf + * seen in the subtree rooted at u. */ + Int RowCount [ ], /* RowCount [i] is # of nonzeros in row i of L, + * including the diagonal. Not computed if NULL. */ + Int SetParent [ ], /* the FIND/UNION data structure, which forms a set + * of trees. A root i has i = SetParent [i]. Following + * a path from i to the root q of the subtree containing + * i means that q is the SetParent representative of i. + * All nodes in the tree could have their SetParent + * equal to the root q; the tree representation is used + * to save time. When a path is traced from i to its + * root q, the path is re-traversed to set the SetParent + * of the whole path to be the root q. */ + Int Level [ ] /* Level [i] = length of path from node i to root */ +) +{ + Int prevleaf, q, s, sparent ; + if (First [p] > PrevNbr [u]) + { + /* p is a leaf of the subtree of u */ + ColCount [p]++ ; + prevleaf = PrevLeaf [u] ; + if (prevleaf == EMPTY) + { + /* p is the first leaf of subtree of u; RowCount will be incremented + * by the length of the path in the etree from p up to u. */ + q = u ; + } + else + { + /* q = FIND (prevleaf): find the root q of the + * SetParent tree containing prevleaf */ + for (q = prevleaf ; q != SetParent [q] ; q = SetParent [q]) + { + ; + } + /* the root q has been found; re-traverse the path and + * perform path compression */ + s = prevleaf ; + for (s = prevleaf ; s != q ; s = sparent) + { + sparent = SetParent [s] ; + SetParent [s] = q ; + } + /* adjust the RowCount and ColCount; RowCount will be incremented by + * the length of the path from p to the SetParent root q, and + * decrement the ColCount of q by one. */ + ColCount [q]-- ; + } + if (RowCount != NULL) + { + /* if RowCount is being computed, increment it by the length of + * the path from p to q */ + RowCount [u] += (Level [p] - Level [q]) ; + } + /* p is a leaf of the subtree of u, so mark PrevLeaf [u] to be p */ + PrevLeaf [u] = p ; + } + /* flag u has having been processed at step k */ + PrevNbr [u] = k ; +} + + +/* ========================================================================== */ +/* === finalize_node ======================================================== */ +/* ========================================================================== */ + +static void finalize_node /* compute UNION (p, Parent [p]) */ +( + Int p, + Int Parent [ ], /* Parent [p] is the parent of p in the etree */ + Int SetParent [ ] /* see process_edge, above */ +) +{ + /* all nodes in the SetParent tree rooted at p now have as their final + * root the node Parent [p]. This computes UNION (p, Parent [p]) */ + if (Parent [p] != EMPTY) + { + SetParent [p] = Parent [p] ; + } +} + + +/* ========================================================================== */ +/* === cholmod_rowcolcounts ================================================= */ +/* ========================================================================== */ + +int CHOLMOD(rowcolcounts) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + Int *Parent, /* size nrow. Parent [i] = p if p is the parent of i */ + Int *Post, /* size nrow. Post [k] = i if i is the kth node in + * the postordered etree. */ + /* ---- output --- */ + Int *RowCount, /* size nrow. RowCount [i] = # entries in the ith row of + * L, including the diagonal. */ + Int *ColCount, /* size nrow. ColCount [i] = # entries in the ith + * column of L, including the diagonal. */ + Int *First, /* size nrow. First [i] = k is the least postordering + * of any descendant of i. */ + Int *Level, /* size nrow. Level [i] is the length of the path from + * i to the root, with Level [root] = 0. */ + /* --------------- */ + cholmod_common *Common +) +{ + double fl, ff ; + Int *Ap, *Ai, *Anz, *PrevNbr, *SetParent, *Head, *PrevLeaf, *Anext, *Ipost, + *Iwork ; + Int i, j, r, k, len, s, p, pend, inew, stype, nf, anz, inode, parent, + nrow, ncol, packed, use_fset, jj ; + size_t w ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (Parent, FALSE) ; + RETURN_IF_NULL (Post, FALSE) ; + RETURN_IF_NULL (ColCount, FALSE) ; + RETURN_IF_NULL (First, FALSE) ; + RETURN_IF_NULL (Level, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + stype = A->stype ; + if (stype > 0) + { + /* symmetric with upper triangular part not supported */ + ERROR (CHOLMOD_INVALID, "symmetric upper not supported") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + nrow = A->nrow ; /* the number of rows of A */ + ncol = A->ncol ; /* the number of columns of A */ + + /* w = 2*nrow + (stype ? 0 : ncol) */ + w = CHOLMOD(mult_size_t) (nrow, 2, &ok) ; + w = CHOLMOD(add_size_t) (w, (stype ? 0 : ncol), &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (nrow, w, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + + ASSERT (CHOLMOD(dump_perm) (Post, nrow, nrow, "Post", Common)) ; + ASSERT (CHOLMOD(dump_parent) (Parent, nrow, "Parent", Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; /* size ncol+1, column pointers for A */ + Ai = A->i ; /* the row indices of A, of size nz=Ap[ncol+1] */ + Anz = A->nz ; + packed = A->packed ; + ASSERT (IMPLIES (!packed, Anz != NULL)) ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Iwork = Common->Iwork ; + SetParent = Iwork ; /* size nrow (i/i/l) */ + PrevNbr = Iwork + nrow ; /* size nrow (i/i/l) */ + Anext = Iwork + 2*((size_t) nrow) ; /* size ncol (i/i/l) (unsym only) */ + PrevLeaf = Common->Flag ; /* size nrow */ + Head = Common->Head ; /* size nrow+1 (unsym only)*/ + + /* ---------------------------------------------------------------------- */ + /* find the first descendant and level of each node in the tree */ + /* ---------------------------------------------------------------------- */ + + /* First [i] = k if the postordering of first descendent of node i is k */ + /* Level [i] = length of path from node i to the root (Level [root] = 0) */ + + for (i = 0 ; i < nrow ; i++) + { + First [i] = EMPTY ; + } + + /* postorder traversal of the etree */ + for (k = 0 ; k < nrow ; k++) + { + /* node i of the etree is the kth node in the postordered etree */ + i = Post [k] ; + + /* i is a leaf if First [i] is still EMPTY */ + /* ColCount [i] starts at 1 if i is a leaf, zero otherwise */ + ColCount [i] = (First [i] == EMPTY) ? 1 : 0 ; + + /* traverse the path from node i to the root, stopping if we find a + * node r whose First [r] is already defined. */ + len = 0 ; + for (r = i ; (r != EMPTY) && (First [r] == EMPTY) ; r = Parent [r]) + { + First [r] = k ; + len++ ; + } + if (r == EMPTY) + { + /* we hit a root node, the level of which is zero */ + len-- ; + } + else + { + /* we stopped at node r, where Level [r] is already defined */ + len += Level [r] ; + } + /* re-traverse the path from node i to r; set the level of each node */ + for (s = i ; s != r ; s = Parent [s]) + { + Level [s] = len-- ; + } + } + + /* ---------------------------------------------------------------------- */ + /* AA' case: sort columns of A according to first postordered row index */ + /* ---------------------------------------------------------------------- */ + + fl = 0.0 ; + if (stype == 0) + { + /* [ use PrevNbr [0..nrow-1] as workspace for Ipost */ + Ipost = PrevNbr ; + /* Ipost [i] = k if i is the kth node in the postordered etree. */ + for (k = 0 ; k < nrow ; k++) + { + Ipost [Post [k]] = k ; + } + use_fset = (fset != NULL) ; + if (use_fset) + { + nf = fsize ; + /* clear Anext to check fset */ + for (j = 0 ; j < ncol ; j++) + { + Anext [j] = -2 ; + } + /* find the first postordered row in each column of A (post,f) + * and place the column in the corresponding link list */ + for (jj = 0 ; jj < nf ; jj++) + { + j = fset [jj] ; + if (j < 0 || j > ncol || Anext [j] != -2) + { + /* out-of-range or duplicate entry in fset */ + ERROR (CHOLMOD_INVALID, "fset invalid") ; + return (FALSE) ; + } + /* flag column j as having been seen */ + Anext [j] = EMPTY ; + } + /* fset is now valid */ + ASSERT (CHOLMOD(dump_perm) (fset, nf, ncol, "fset", Common)) ; + } + else + { + nf = ncol ; + } + for (jj = 0 ; jj < nf ; jj++) + { + j = (use_fset) ? (fset [jj]) : jj ; + /* column j is in the fset; find the smallest row (if any) */ + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + ff = (double) MAX (0, pend - p) ; + fl += ff*ff + ff ; + if (pend > p) + { + k = Ipost [Ai [p]] ; + for ( ; p < pend ; p++) + { + inew = Ipost [Ai [p]] ; + k = MIN (k, inew) ; + } + /* place column j in link list k */ + ASSERT (k >= 0 && k < nrow) ; + Anext [j] = Head [k] ; + Head [k] = j ; + } + } + /* Ipost no longer needed for inverse postordering ] + * Head [k] contains a link list of all columns whose first + * postordered row index is equal to k, for k = 0 to nrow-1. */ + } + + /* ---------------------------------------------------------------------- */ + /* compute the row counts and node weights */ + /* ---------------------------------------------------------------------- */ + + if (RowCount != NULL) + { + for (i = 0 ; i < nrow ; i++) + { + RowCount [i] = 1 ; + } + } + for (i = 0 ; i < nrow ; i++) + { + PrevLeaf [i] = EMPTY ; + PrevNbr [i] = EMPTY ; + SetParent [i] = i ; /* every node is in its own set, by itself */ + } + + if (stype != 0) + { + + /* ------------------------------------------------------------------ */ + /* symmetric case: LL' = A */ + /* ------------------------------------------------------------------ */ + + /* also determine the number of entries in triu(A) */ + anz = nrow ; + for (k = 0 ; k < nrow ; k++) + { + /* j is the kth node in the postordered etree */ + j = initialize_node (k, Post, Parent, ColCount, PrevNbr) ; + + /* for all nonzeros A(i,j) below the diagonal, in column j of A */ + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i > j) + { + /* j is a descendant of i in etree(A) */ + anz++ ; + process_edge (j, i, k, First, PrevNbr, ColCount, + PrevLeaf, RowCount, SetParent, Level) ; + } + } + /* update SetParent: UNION (j, Parent [j]) */ + finalize_node (j, Parent, SetParent) ; + } + Common->anz = anz ; + } + else + { + + /* ------------------------------------------------------------------ */ + /* unsymmetric case: LL' = AA' */ + /* ------------------------------------------------------------------ */ + + for (k = 0 ; k < nrow ; k++) + { + /* inode is the kth node in the postordered etree */ + inode = initialize_node (k, Post, Parent, ColCount, PrevNbr) ; + + /* for all cols j whose first postordered row is k: */ + for (j = Head [k] ; j != EMPTY ; j = Anext [j]) + { + /* k is the first postordered row in column j of A */ + /* for all rows i in column j: */ + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + /* has i already been considered at this step k */ + if (PrevNbr [i] < k) + { + /* inode is a descendant of i in etree(AA') */ + /* process edge (inode,i) and set PrevNbr[i] to k */ + process_edge (inode, i, k, First, PrevNbr, ColCount, + PrevLeaf, RowCount, SetParent, Level) ; + } + } + } + /* clear link list k */ + Head [k] = EMPTY ; + /* update SetParent: UNION (inode, Parent [inode]) */ + finalize_node (inode, Parent, SetParent) ; + } + } + + /* ---------------------------------------------------------------------- */ + /* finish computing the column counts */ + /* ---------------------------------------------------------------------- */ + + for (j = 0 ; j < nrow ; j++) + { + parent = Parent [j] ; + if (parent != EMPTY) + { + /* add the ColCount of j to its parent */ + ColCount [parent] += ColCount [j] ; + } + } + + /* ---------------------------------------------------------------------- */ + /* clear workspace */ + /* ---------------------------------------------------------------------- */ + + Common->mark = EMPTY ; + /* CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* flop count and nnz(L) for subsequent LL' numerical factorization */ + /* ---------------------------------------------------------------------- */ + + /* use double to avoid integer overflow. lnz cannot be NaN. */ + Common->aatfl = fl ; + Common->lnz = 0. ; + fl = 0 ; + for (j = 0 ; j < nrow ; j++) + { + ff = (double) (ColCount [j]) ; + Common->lnz += ff ; + fl += ff*ff ; + } + + Common->fl = fl ; + PRINT1 (("rowcol fl %g lnz %g\n", Common->fl, Common->lnz)) ; + + return (TRUE) ; +} +#endif diff --git a/src/CHOLMOD/Cholesky/cholmod_rowfac.c b/src/CHOLMOD/Cholesky/cholmod_rowfac.c new file mode 100644 index 0000000..10e6ba1 --- /dev/null +++ b/src/CHOLMOD/Cholesky/cholmod_rowfac.c @@ -0,0 +1,735 @@ +/* ========================================================================== */ +/* === Cholesky/cholmod_rowfac ============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2013, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Full or incremental numerical LDL' or LL' factorization (simplicial, not + * supernodal) cholmod_factorize is the "easy" wrapper for this code, but it + * does not provide access to incremental factorization. + * + * cholmod_rowfac computes the full or incremental LDL' or LL' factorization of + * A+beta*I (where A is symmetric) or A*F+beta*I (where A and F are unsymmetric + * and only the upper triangular part of A*F+beta*I is used). It computes + * L (and D, for LDL') one row at a time. beta is real. + * + * A is nrow-by-ncol or nrow-by-nrow. In "packed" form it is a conventional + * column-oriented sparse matrix. Row indices of column j are in + * Ai [Ap [j] ... Ap [j+1]-1] and values in the same locations of Ax. + * will be faster if A has sorted columns. In "unpacked" form the column + * of A ends at Ap [j] + Anz [j] - 1 instead of Ap [j+1] - 1. + * + * Row indices in each column of A can be sorted or unsorted, but the routine + * routine works fastest if A is sorted, or if only triu(A) is provided + * for the symmetric case. + * + * The unit-diagonal nrow-by-nrow output matrix L is returned in "unpacked" + * column form, with row indices of column j in Li [Lp [j] ... + * Lp [j] + Lnz [j] - 1] and values in the same location in Lx. The row + * indices in each column of L are in sorted order. The unit diagonal of L + * is not stored. + * + * L can be a simplicial symbolic or numeric (L->is_super must be FALSE). + * A symbolic factor is converted immediately into a numeric factor containing + * the identity matrix. + * + * For a full factorization, kstart = 0 and kend = nrow. The existing nonzero + * entries (numerical values in L->x and L->z for the zomplex case, and indices + * in L->i), if any, are overwritten. + * + * To compute an incremental factorization, select kstart and kend as the range + * of rows of L you wish to compute. A correct factorization will be computed + * only if all descendants of all nodes k = kstart to kend-1 in the etree have + * been factorized by a prior call to this routine, and if rows kstart to kend-1 + * have not been factorized. This condition is NOT checked on input. + * + * --------------- + * Symmetric case: + * --------------- + * + * The factorization (in MATLAB notation) is: + * + * S = beta*I + A + * S = triu (S) + triu (S,1)' + * L*D*L' = S, or L*L' = S + * + * A is a conventional sparse matrix in compressed column form. Only the + * diagonal and upper triangular part of A is accessed; the lower + * triangular part is ignored and assumed to be equal to the upper + * triangular part. For an incremental factorization, only columns kstart + * to kend-1 of A are accessed. F is not used. + * + * --------------- + * Unsymmetric case: + * --------------- + * + * The factorization (in MATLAB notation) is: + * + * S = beta*I + A*F + * S = triu (S) + triu (S,1)' + * L*D*L' = S, or L*L' = S + * + * The typical case is F=A'. Alternatively, if F=A(:,f)', then this + * routine factorizes S = beta*I + A(:,f)*A(:,f)'. + * + * All of A and F are accessed, but only the upper triangular part of A*F + * is used. F must be of size A->ncol by A->nrow. F is used for the + * unsymmetric case only. F can be packed or unpacked and it need not be + * sorted. + * + * For a complete factorization of beta*I + A*A', + * this routine performs a number of flops exactly equal to: + * + * sum (for each column j of A) of (Anz (j)^2 + Anz (j)), to form S + * + + * sum (for each column j of L) of (Lnz (j)^2 + 3*Lnz (j)), to factorize S + * + * where Anz (j) is the number of nonzeros in column j of A, and Lnz (j) + * is the number of nonzero in column j of L below the diagonal. + * + * + * workspace: Flag (nrow), W (nrow if real, 2*nrow if complex/zomplex), + * Iwork (nrow) + * + * Supports any xtype, except a pattern-only input matrix A cannot be + * factorized. + */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "cholmod_cholesky.h" + +/* ========================================================================== */ +/* === subtree ============================================================== */ +/* ========================================================================== */ + +/* Compute the nonzero pattern of the sparse triangular solve Lx=b, where L in + * this case is L(0:k-1,0:k-1), and b is a column of A. This is done by + * traversing the kth row-subtree of the elimination tree of L, starting from + * each nonzero entry in b. The pattern is returned postordered, and is valid + * for a subsequent numerical triangular solve of Lx=b. The elimination tree + * can be provided in a Parent array, or extracted from the pattern of L itself. + * + * The pattern of x = inv(L)*b is returned in Stack [top...]. + * Also scatters b, or a multiple of b, into the work vector W. + * + * The SCATTER macro is defines how the numerical values of A or A*A' are to be + * scattered. + * + * PARENT(i) is a macro the defines how the etree is accessed. It is either: + * #define PARENT(i) Parent [i] + * #define PARENT(i) (Lnz [i] > 1) ? (Li [Lp [i] + 1]) : EMPTY + */ + +#define SUBTREE \ + for ( ; p < pend ; p++) \ + { \ + i = Ai [p] ; \ + if (i <= k) \ + { \ + /* scatter the column of A, or A*A' into Wx and Wz */ \ + SCATTER ; \ + /* start at node i and traverse up the subtree, stop at node k */ \ + for (len = 0 ; i < k && i != EMPTY && Flag [i] < mark ; i = parent) \ + { \ + /* L(k,i) is nonzero, and seen for the first time */ \ + Stack [len++] = i ; /* place i on the stack */ \ + Flag [i] = mark ; /* mark i as visited */ \ + parent = PARENT (i) ; /* traverse up the etree to the parent */ \ + } \ + /* move the path down to the bottom of the stack */ \ + while (len > 0) \ + { \ + Stack [--top] = Stack [--len] ; \ + } \ + } \ + else if (sorted) \ + { \ + break ; \ + } \ + } + + +/* ========================================================================== */ +/* === TEMPLATE ============================================================= */ +/* ========================================================================== */ + +#define REAL +#include "t_cholmod_rowfac.c" +#define COMPLEX +#include "t_cholmod_rowfac.c" +#define ZOMPLEX +#include "t_cholmod_rowfac.c" + +#define MASK +#define REAL +#include "t_cholmod_rowfac.c" +#define COMPLEX +#include "t_cholmod_rowfac.c" +#define ZOMPLEX +#include "t_cholmod_rowfac.c" +#undef MASK + + +/* ========================================================================== */ +/* === cholmod_row_subtree ================================================== */ +/* ========================================================================== */ + +/* Compute the nonzero pattern of the solution to the lower triangular system + * L(0:k-1,0:k-1) * x = A (0:k-1,k) if A is symmetric, or + * L(0:k-1,0:k-1) * x = A (0:k-1,:) * A (:,k)' if A is unsymmetric. + * This gives the nonzero pattern of row k of L (excluding the diagonal). + * The pattern is returned postordered. + * + * The symmetric case requires A to be in symmetric-upper form. + * + * The result is returned in R, a pre-allocated sparse matrix of size nrow-by-1, + * with R->nzmax >= nrow. R is assumed to be packed (Rnz [0] is not updated); + * the number of entries in R is given by Rp [0]. + * + * FUTURE WORK: a very minor change to this routine could allow it to compute + * the nonzero pattern of x for any system Lx=b. The SUBTREE macro would need + * to change, to eliminate its dependence on k. + * + * workspace: Flag (nrow) + */ + +int CHOLMOD(row_subtree) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + cholmod_sparse *F, /* used for A*A' case only. F=A' or A(:,f)' */ + size_t krow, /* row k of L */ + Int *Parent, /* elimination tree */ + /* ---- output --- */ + cholmod_sparse *R, /* pattern of L(k,:), 1-by-n with R->nzmax >= n */ + /* --------------- */ + cholmod_common *Common +) +{ + Int *Rp, *Stack, *Flag, *Ap, *Ai, *Anz, *Fp, *Fi, *Fnz ; + Int p, pend, parent, t, stype, nrow, k, pf, pfend, Fpacked, packed, + sorted, top, len, i, mark ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (R, FALSE) ; + RETURN_IF_NULL (Parent, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (R, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + stype = A->stype ; + if (stype == 0) + { + RETURN_IF_NULL (F, FALSE) ; + RETURN_IF_XTYPE_INVALID (F, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + } + if (krow >= A->nrow) + { + ERROR (CHOLMOD_INVALID, "subtree: k invalid") ; + return (FALSE) ; + } + if (R->ncol != 1 || A->nrow != R->nrow || A->nrow > R->nzmax) + { + ERROR (CHOLMOD_INVALID, "subtree: R invalid") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + nrow = A->nrow ; + CHOLMOD(allocate_work) (nrow, 0, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + if (stype > 0) + { + /* symmetric upper case: F is not needed. It may be NULL */ + Fp = NULL ; + Fi = NULL ; + Fnz = NULL ; + Fpacked = TRUE ; + } + else if (stype == 0) + { + /* unsymmetric case: F is required. */ + Fp = F->p ; + Fi = F->i ; + Fnz = F->nz ; + Fpacked = F->packed ; + } + else + { + /* symmetric lower triangular form not supported */ + ERROR (CHOLMOD_INVALID, "symmetric lower not supported") ; + return (FALSE) ; + } + + Ap = A->p ; + Ai = A->i ; + Anz = A->nz ; + packed = A->packed ; + sorted = A->sorted ; + + k = krow ; + Stack = R->i ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Flag = Common->Flag ; /* size nrow, Flag [i] < mark must hold */ + /* mark = CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + mark = Common->mark ; + + /* ---------------------------------------------------------------------- */ + /* compute the pattern of L(k,:) */ + /* ---------------------------------------------------------------------- */ + + top = nrow ; /* Stack is empty */ + Flag [k] = mark ; /* do not include diagonal entry in Stack */ + +#define SCATTER /* do not scatter numerical values */ +#define PARENT(i) Parent [i] /* use Parent for etree */ + + if (stype != 0) + { + /* scatter kth col of triu (A), get pattern L(k,:) */ + p = Ap [k] ; + pend = (packed) ? (Ap [k+1]) : (p + Anz [k]) ; + SUBTREE ; + } + else + { + /* scatter kth col of triu (beta*I+AA'), get pattern L(k,:) */ + pf = Fp [k] ; + pfend = (Fpacked) ? (Fp [k+1]) : (pf + Fnz [k]) ; + for ( ; pf < pfend ; pf++) + { + /* get nonzero entry F (t,k) */ + t = Fi [pf] ; + p = Ap [t] ; + pend = (packed) ? (Ap [t+1]) : (p + Anz [t]) ; + SUBTREE ; + } + } + +#undef SCATTER +#undef PARENT + + /* shift the stack upwards, to the first part of R */ + len = nrow - top ; + for (i = 0 ; i < len ; i++) + { + Stack [i] = Stack [top + i] ; + } + + Rp = R->p ; + Rp [0] = 0 ; + Rp [1] = len ; + R->sorted = FALSE ; + + CHOLMOD(clear_flag) (Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_lsolve_pattern =============================================== */ +/* ========================================================================== */ + +/* Compute the nonzero pattern of Y=L\B. L must be simplicial, and B + * must be a single sparse column vector with B->stype = 0. The values of + * B are not used; it just specifies a nonzero pattern. The pattern of + * Y is not sorted, but is in topological order instead (suitable for a + * sparse forward/backsolve). + */ + +int CHOLMOD(lsolve_pattern) +( + /* ---- input ---- */ + cholmod_sparse *B, /* sparse right-hand-side (a single sparse column) */ + cholmod_factor *L, /* the factor L from which parent(i) is derived */ + /* ---- output --- */ + cholmod_sparse *Yset, /* pattern of Y=L\B, n-by-1 with Y->nzmax >= n */ + /* --------------- */ + cholmod_common *Common +) +{ + size_t krow ; + RETURN_IF_NULL (B, FALSE) ; + krow = B->nrow ; + return (CHOLMOD(row_lsubtree) (B, NULL, 0, krow, L, Yset, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_row_lsubtree ================================================= */ +/* ========================================================================== */ + +/* Identical to cholmod_row_subtree, except that the elimination tree is + * obtained from L itself, as the first off-diagonal entry in each column. + * L must be simplicial, not supernodal. + * + * If krow = A->nrow, then A must be a single sparse column vector, (A->stype + * must be zero), and the nonzero pattern of x=L\b is computed, where b=A(:,0) + * is the single sparse right-hand-side. The inputs Fi and fnz are ignored. + * See CHOLMOD(lsolve_pattern) above for a simpler interface for this case. + */ + +int CHOLMOD(row_lsubtree) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + Int *Fi, size_t fnz, /* nonzero pattern of kth row of A', not required + * for the symmetric case. Need not be sorted. */ + size_t krow, /* row k of L */ + cholmod_factor *L, /* the factor L from which parent(i) is derived */ + /* ---- output --- */ + cholmod_sparse *R, /* pattern of L(k,:), n-by-1 with R->nzmax >= n */ + /* --------------- */ + cholmod_common *Common +) +{ + Int *Rp, *Stack, *Flag, *Ap, *Ai, *Anz, *Lp, *Li, *Lnz ; + Int p, pend, parent, t, stype, nrow, k, pf, packed, sorted, top, len, i, + mark, ka ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (R, FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (R, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + + nrow = A->nrow ; + stype = A->stype ; + if (stype < 0) + { + /* symmetric lower triangular form not supported */ + ERROR (CHOLMOD_INVALID, "symmetric lower not supported") ; + return (FALSE) ; + } + + if (krow > nrow) + { + ERROR (CHOLMOD_INVALID, "lsubtree: krow invalid") ; + return (FALSE) ; + } + else if (krow == nrow) + { + /* find pattern of x=L\b where b=A(:,0) */ + k = nrow ; /* compute all of the result; don't stop in SUBTREE */ + ka = 0 ; /* use column A(:,0) */ + if (stype != 0 || A->ncol != 1) + { + /* A must be unsymmetric (it's a single sparse column vector) */ + ERROR (CHOLMOD_INVALID, "lsubtree: A invalid") ; + return (FALSE) ; + } + } + else + { + /* find pattern of L(k,:) using A(:,k) and Fi if A unsymmetric */ + k = krow ; /* which row of L to compute */ + ka = k ; /* which column of A to use */ + if (stype == 0) + { + RETURN_IF_NULL (Fi, FALSE) ; + } + } + + if (R->ncol != 1 || nrow != R->nrow || nrow > R->nzmax || ka >= A->ncol) + { + ERROR (CHOLMOD_INVALID, "lsubtree: R invalid") ; + return (FALSE) ; + } + if (L->is_super) + { + ERROR (CHOLMOD_INVALID, "lsubtree: L invalid (cannot be supernodal)") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(allocate_work) (nrow, 0, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + Ai = A->i ; + Anz = A->nz ; + packed = A->packed ; + sorted = A->sorted ; + + Stack = R->i ; + + Lp = L->p ; + Li = L->i ; + Lnz = L->nz ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Flag = Common->Flag ; /* size nrow, Flag [i] < mark must hold */ + mark = CHOLMOD(clear_flag) (Common) ; + + /* ---------------------------------------------------------------------- */ + /* compute the pattern of L(k,:) */ + /* ---------------------------------------------------------------------- */ + + top = nrow ; /* Stack is empty */ + if (k < nrow) + { + Flag [k] = mark ; /* do not include diagonal entry in Stack */ + } + +#define SCATTER /* do not scatter numerical values */ +#define PARENT(i) (Lnz [i] > 1) ? (Li [Lp [i] + 1]) : EMPTY + + if (krow == nrow || stype != 0) + { + /* scatter kth col of triu (A), get pattern L(k,:) */ + p = Ap [ka] ; + pend = (packed) ? (Ap [ka+1]) : (p + Anz [ka]) ; + SUBTREE ; + } + else + { + /* scatter kth col of triu (beta*I+AA'), get pattern L(k,:) */ + for (pf = 0 ; pf < (Int) fnz ; pf++) + { + /* get nonzero entry F (t,k) */ + t = Fi [pf] ; + p = Ap [t] ; + pend = (packed) ? (Ap [t+1]) : (p + Anz [t]) ; + SUBTREE ; + } + } + +#undef SCATTER +#undef PARENT + + /* shift the stack upwards, to the first part of R */ + len = nrow - top ; + for (i = 0 ; i < len ; i++) + { + Stack [i] = Stack [top + i] ; + } + + Rp = R->p ; + Rp [0] = 0 ; + Rp [1] = len ; + R->sorted = FALSE ; + + CHOLMOD(clear_flag) (Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_rowfac ======================================================= */ +/* ========================================================================== */ + +/* This is the incremental factorization for general purpose usage. */ + +int CHOLMOD(rowfac) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + cholmod_sparse *F, /* used for A*A' case only. F=A' or A(:,f)' */ + double beta [2], /* factorize beta*I+A or beta*I+AA' */ + size_t kstart, /* first row to factorize */ + size_t kend, /* last row to factorize is kend-1 */ + /* ---- in/out --- */ + cholmod_factor *L, + /* --------------- */ + cholmod_common *Common +) +{ + return (CHOLMOD(rowfac_mask) (A, F, beta, kstart, kend, NULL, NULL, L, + Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_rowfac_mask ================================================== */ +/* ========================================================================== */ + +/* This is meant for use in LPDASA only. */ + +int CHOLMOD(rowfac_mask) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + cholmod_sparse *F, /* used for A*A' case only. F=A' or A(:,f)' */ + double beta [2], /* factorize beta*I+A or beta*I+AA' */ + size_t kstart, /* first row to factorize */ + size_t kend, /* last row to factorize is kend-1 */ + Int *mask, /* size A->nrow. if mask[i] >= 0 row i is set to zero */ + Int *RLinkUp, /* size A->nrow. link list of rows to compute */ + /* ---- in/out --- */ + cholmod_factor *L, + /* --------------- */ + cholmod_common *Common +) +{ + Int n ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + if (L->xtype != CHOLMOD_PATTERN && A->xtype != L->xtype) + { + ERROR (CHOLMOD_INVALID, "xtype of A and L do not match") ; + return (FALSE) ; + } + if (L->is_super) + { + ERROR (CHOLMOD_INVALID, "can only do simplicial factorization"); + return (FALSE) ; + } + if (A->stype == 0) + { + RETURN_IF_NULL (F, FALSE) ; + if (A->xtype != F->xtype) + { + ERROR (CHOLMOD_INVALID, "xtype of A and F do not match") ; + return (FALSE) ; + } + } + if (A->stype < 0) + { + /* symmetric lower triangular form not supported */ + ERROR (CHOLMOD_INVALID, "symmetric lower not supported") ; + return (FALSE) ; + } + if (kend > L->n) + { + ERROR (CHOLMOD_INVALID, "kend invalid") ; + return (FALSE) ; + } + if (A->nrow != L->n) + { + ERROR (CHOLMOD_INVALID, "dimensions of A and L do not match") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + Common->rowfacfl = 0 ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* Xwork is of size n for the real case, 2*n for complex/zomplex */ + n = L->n ; + + /* s = ((A->xtype != CHOLMOD_REAL) ? 2:1)*n */ + s = CHOLMOD(mult_size_t) (n, ((A->xtype != CHOLMOD_REAL) ? 2:1), &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (n, n, s, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, A->nrow, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* factorize the matrix, using template routine */ + /* ---------------------------------------------------------------------- */ + + if (RLinkUp == NULL) + { + + switch (A->xtype) + { + case CHOLMOD_REAL: + ok = r_cholmod_rowfac (A, F, beta, kstart, kend, L, Common) ; + break ; + + case CHOLMOD_COMPLEX: + ok = c_cholmod_rowfac (A, F, beta, kstart, kend, L, Common) ; + break ; + + case CHOLMOD_ZOMPLEX: + ok = z_cholmod_rowfac (A, F, beta, kstart, kend, L, Common) ; + break ; + } + + } + else + { + + switch (A->xtype) + { + case CHOLMOD_REAL: + ok = r_cholmod_rowfac_mask (A, F, beta, kstart, kend, + mask, RLinkUp, L, Common) ; + break ; + + case CHOLMOD_COMPLEX: + ok = c_cholmod_rowfac_mask (A, F, beta, kstart, kend, + mask, RLinkUp, L, Common) ; + break ; + + case CHOLMOD_ZOMPLEX: + ok = z_cholmod_rowfac_mask (A, F, beta, kstart, kend, + mask, RLinkUp, L, Common) ; + break ; + } + } + + return (ok) ; +} +#endif diff --git a/src/CHOLMOD/Cholesky/cholmod_solve.c b/src/CHOLMOD/Cholesky/cholmod_solve.c new file mode 100644 index 0000000..2c5728e --- /dev/null +++ b/src/CHOLMOD/Cholesky/cholmod_solve.c @@ -0,0 +1,1684 @@ +/* ========================================================================== */ +/* === Cholesky/cholmod_solve =============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2013, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Solve one of the following systems. D is identity for an LL' factorization, + * in which the D operation is skipped: + * + * Ax=b 0: CHOLMOD_A x = P' * (L' \ (D \ (L \ (P * b)))) + * LDL'x=b 1: CHOLMOD_LDLt x = (L' \ (D \ (L \ ( b)))) + * LDx=b 2: CHOLMOD_LD x = ( (D \ (L \ ( b)))) + * DL'x=b 3: CHOLMOD_DLt x = (L' \ (D \ ( ( b)))) + * Lx=b 4: CHOLMOD_L x = ( ( (L \ ( b)))) + * L'x=b 5: CHOLMOD_Lt x = (L' \ ( ( ( b)))) + * Dx=b 6: CHOLMOD_D x = ( (D \ ( ( b)))) + * x=Pb 7: CHOLMOD_P x = ( ( ( (P * b)))) + * x=P'b 8: CHOLMOD_Pt x = P' * ( ( ( ( b)))) + * + * The factorization can be simplicial LDL', simplicial LL', or supernodal LL'. + * For an LL' factorization, D is the identity matrix. Thus CHOLMOD_LD and + * CHOLMOD_L solve the same system if an LL' factorization was performed, + * for example. + * + * The supernodal solver uses BLAS routines dtrsv, dgemv, dtrsm, and dgemm, + * or their complex counterparts ztrsv, zgemv, ztrsm, and zgemm. + * + * If both L and B are real, then X is returned real. If either is complex + * or zomplex, X is returned as either complex or zomplex, depending on the + * Common->prefer_zomplex parameter. + * + * Supports any numeric xtype (pattern-only matrices not supported). + * + * This routine does not check to see if the diagonal of L or D is zero, + * because sometimes a partial solve can be done with indefinite or singular + * matrix. If you wish to check in your own code, test L->minor. If + * L->minor == L->n, then the matrix has no zero diagonal entries. + * If k = L->minor < L->n, then L(k,k) is zero for an LL' factorization, or + * D(k,k) is zero for an LDL' factorization. + * + * This routine returns X as NULL only if it runs out of memory. If L is + * indefinite or singular, then X may contain Inf's or NaN's, but it will + * exist on output. + */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "cholmod_cholesky.h" + +#ifndef NSUPERNODAL +#include "cholmod_supernodal.h" +#endif + + +/* ========================================================================== */ +/* === TEMPLATE ============================================================= */ +/* ========================================================================== */ + +#define REAL +#include "t_cholmod_solve.c" + +#define COMPLEX +#include "t_cholmod_solve.c" + +#define ZOMPLEX +#include "t_cholmod_solve.c" + +/* ========================================================================== */ +/* === Permutation macro ==================================================== */ +/* ========================================================================== */ + +/* If Perm is NULL, it is interpretted as the identity permutation */ + +#define P(k) ((Perm == NULL) ? (k) : Perm [k]) + + +/* ========================================================================== */ +/* === perm ================================================================= */ +/* ========================================================================== */ + +/* Y = B (P (1:nrow), k1 : min (k1+ncols,ncol)-1) where B is nrow-by-ncol. + * + * Creates a permuted copy of a contiguous set of columns of B. + * Y is already allocated on input. Y must be of sufficient size. Let nk be + * the number of columns accessed in B. Y->xtype determines the complexity of + * the result. + * + * If B is real and Y is complex (or zomplex), only the real part of B is + * copied into Y. The imaginary part of Y is set to zero. + * + * If B is complex (or zomplex) and Y is real, both the real and imaginary and + * parts of B are returned in Y. Y is returned as nrow-by-2*nk. The even + * columns of Y contain the real part of B and the odd columns contain the + * imaginary part of B. Y->nzmax must be >= 2*nrow*nk. Otherise, Y is + * returned as nrow-by-nk with leading dimension nrow. Y->nzmax must be >= + * nrow*nk. + * + * The case where the input (B) is real and the output (Y) is zomplex is + * not used. + */ + +static void perm +( + /* ---- input ---- */ + cholmod_dense *B, /* input matrix B */ + Int *Perm, /* optional input permutation (can be NULL) */ + Int k1, /* first column of B to copy */ + Int ncols, /* last column to copy is min(k1+ncols,B->ncol)-1 */ + /* ---- in/out --- */ + cholmod_dense *Y /* output matrix Y, already allocated */ +) +{ + double *Yx, *Yz, *Bx, *Bz ; + Int k2, nk, p, k, j, nrow, ncol, d, dual, dj, j2 ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + ncol = B->ncol ; + nrow = B->nrow ; + k2 = MIN (k1+ncols, ncol) ; + nk = MAX (k2 - k1, 0) ; + dual = (Y->xtype == CHOLMOD_REAL && B->xtype != CHOLMOD_REAL) ? 2 : 1 ; + d = B->d ; + Bx = B->x ; + Bz = B->z ; + Yx = Y->x ; + Yz = Y->z ; + Y->nrow = nrow ; + Y->ncol = dual*nk ; + Y->d = nrow ; + ASSERT (((Int) Y->nzmax) >= nrow*nk*dual) ; + + /* ---------------------------------------------------------------------- */ + /* Y = B (P (1:nrow), k1:k2-1) */ + /* ---------------------------------------------------------------------- */ + + switch (Y->xtype) + { + + case CHOLMOD_REAL: + + switch (B->xtype) + { + + case CHOLMOD_REAL: + /* Y real, B real */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [k + j2] = Bx [p] ; /* real */ + } + } + break ; + + case CHOLMOD_COMPLEX: + /* Y real, B complex. Y is nrow-by-2*nk */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * 2 * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [k + j2 ] = Bx [2*p ] ; /* real */ + Yx [k + j2 + nrow] = Bx [2*p+1] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y real, B zomplex. Y is nrow-by-2*nk */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * 2 * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [k + j2 ] = Bx [p] ; /* real */ + Yx [k + j2 + nrow] = Bz [p] ; /* imag */ + } + } + break ; + + } + break ; + + case CHOLMOD_COMPLEX: + + switch (B->xtype) + { + + case CHOLMOD_REAL: + /* Y complex, B real */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * 2 * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [2*k + j2] = Bx [p] ; /* real */ + Yx [2*k+1 + j2] = 0 ; /* imag */ + } + } + break ; + + case CHOLMOD_COMPLEX: + /* Y complex, B complex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * 2 * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [2*k + j2] = Bx [2*p ] ; /* real */ + Yx [2*k+1 + j2] = Bx [2*p+1] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y complex, B zomplex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * 2 * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [2*k + j2] = Bx [p] ; /* real */ + Yx [2*k+1 + j2] = Bz [p] ; /* imag */ + } + } + break ; + + } + break ; + + case CHOLMOD_ZOMPLEX: + + switch (B->xtype) + { + +#if 0 + case CHOLMOD_REAL: + /* this case is not used */ + break ; +#endif + + case CHOLMOD_COMPLEX: + /* Y zomplex, B complex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [k + j2] = Bx [2*p ] ; /* real */ + Yz [k + j2] = Bx [2*p+1] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y zomplex, B zomplex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [k + j2] = Bx [p] ; /* real */ + Yz [k + j2] = Bz [p] ; /* imag */ + } + } + break ; + + } + break ; + + } +} + + +/* ========================================================================== */ +/* === iperm ================================================================ */ +/* ========================================================================== */ + +/* X (P (1:nrow), k1 : min (k1+ncols,ncol)-1) = Y where X is nrow-by-ncol. + * + * Copies and permutes Y into a contiguous set of columns of X. X is already + * allocated on input. Y must be of sufficient size. Let nk be the number + * of columns accessed in X. X->xtype determines the complexity of the result. + * + * If X is real and Y is complex (or zomplex), only the real part of B is + * copied into X. The imaginary part of Y is ignored. + * + * If X is complex (or zomplex) and Y is real, both the real and imaginary and + * parts of Y are returned in X. Y is nrow-by-2*nk. The even + * columns of Y contain the real part of B and the odd columns contain the + * imaginary part of B. Y->nzmax must be >= 2*nrow*nk. Otherise, Y is + * nrow-by-nk with leading dimension nrow. Y->nzmax must be >= nrow*nk. + * + * The case where the input (Y) is complex and the output (X) is real, + * and the case where the input (Y) is zomplex and the output (X) is real, + * are not used. + */ + +static void iperm +( + /* ---- input ---- */ + cholmod_dense *Y, /* input matrix Y */ + Int *Perm, /* optional input permutation (can be NULL) */ + Int k1, /* first column of B to copy */ + Int ncols, /* last column to copy is min(k1+ncols,B->ncol)-1 */ + /* ---- in/out --- */ + cholmod_dense *X /* output matrix X, already allocated */ +) +{ + double *Yx, *Yz, *Xx, *Xz ; + Int k2, nk, p, k, j, nrow, ncol, d, dj, j2 ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + ncol = X->ncol ; + nrow = X->nrow ; + k2 = MIN (k1+ncols, ncol) ; + nk = MAX (k2 - k1, 0) ; + d = X->d ; + Xx = X->x ; + Xz = X->z ; + Yx = Y->x ; + Yz = Y->z ; + ASSERT (((Int) Y->nzmax) >= nrow*nk* + ((X->xtype != CHOLMOD_REAL && Y->xtype == CHOLMOD_REAL) ? 2:1)) ; + + /* ---------------------------------------------------------------------- */ + /* X (P (1:nrow), k1:k2-1) = Y */ + /* ---------------------------------------------------------------------- */ + + switch (Y->xtype) + { + + case CHOLMOD_REAL: + + switch (X->xtype) + { + + case CHOLMOD_REAL: + /* Y real, X real */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [p] = Yx [k + j2] ; /* real */ + } + } + break ; + + case CHOLMOD_COMPLEX: + /* Y real, X complex. Y is nrow-by-2*nk */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * 2 * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [2*p ] = Yx [k + j2 ] ; /* real */ + Xx [2*p+1] = Yx [k + j2 + nrow] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y real, X zomplex. Y is nrow-by-2*nk */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * 2 * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [p] = Yx [k + j2 ] ; /* real */ + Xz [p] = Yx [k + j2 + nrow] ; /* imag */ + } + } + break ; + + } + break ; + + case CHOLMOD_COMPLEX: + + switch (X->xtype) + { + +#if 0 + case CHOLMOD_REAL: + /* this case is not used */ + break ; +#endif + + case CHOLMOD_COMPLEX: + /* Y complex, X complex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * 2 * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [2*p ] = Yx [2*k + j2] ; /* real */ + Xx [2*p+1] = Yx [2*k+1 + j2] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y complex, X zomplex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * 2 * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [p] = Yx [2*k + j2] ; /* real */ + Xz [p] = Yx [2*k+1 + j2] ; /* imag */ + } + } + break ; + + } + break ; + + case CHOLMOD_ZOMPLEX: + + switch (X->xtype) + { + +#if 0 + case CHOLMOD_REAL: + /* this case is not used */ + break ; +#endif + + case CHOLMOD_COMPLEX: + /* Y zomplex, X complex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [2*p ] = Yx [k + j2] ; /* real */ + Xx [2*p+1] = Yz [k + j2] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y zomplex, X zomplex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = nrow * (j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [p] = Yx [k + j2] ; /* real */ + Xz [p] = Yz [k + j2] ; /* imag */ + } + } + break ; + + } + break ; + + } +} + + +/* ========================================================================== */ +/* === ptrans =============================================================== */ +/* ========================================================================== */ + +/* Y = B (P (1:nrow), k1 : min (k1+ncols,ncol)-1)' where B is nrow-by-ncol. + * + * Creates a permuted and transposed copy of a contiguous set of columns of B. + * Y is already allocated on input. Y must be of sufficient size. Let nk be + * the number of columns accessed in B. Y->xtype determines the complexity of + * the result. + * + * If B is real and Y is complex (or zomplex), only the real part of B is + * copied into Y. The imaginary part of Y is set to zero. + * + * If B is complex (or zomplex) and Y is real, both the real and imaginary and + * parts of B are returned in Y. Y is returned as 2*nk-by-nrow. The even + * rows of Y contain the real part of B and the odd rows contain the + * imaginary part of B. Y->nzmax must be >= 2*nrow*nk. Otherise, Y is + * returned as nk-by-nrow with leading dimension nk. Y->nzmax must be >= + * nrow*nk. + * + * The array transpose is performed, not the complex conjugate transpose. + */ + +static void ptrans +( + /* ---- input ---- */ + cholmod_dense *B, /* input matrix B */ + Int *Perm, /* optional input permutation (can be NULL) */ + Int k1, /* first column of B to copy */ + Int ncols, /* last column to copy is min(k1+ncols,B->ncol)-1 */ + /* ---- in/out --- */ + cholmod_dense *Y /* output matrix Y, already allocated */ +) +{ + double *Yx, *Yz, *Bx, *Bz ; + Int k2, nk, p, k, j, nrow, ncol, d, dual, dj, j2 ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + ncol = B->ncol ; + nrow = B->nrow ; + k2 = MIN (k1+ncols, ncol) ; + nk = MAX (k2 - k1, 0) ; + dual = (Y->xtype == CHOLMOD_REAL && B->xtype != CHOLMOD_REAL) ? 2 : 1 ; + d = B->d ; + Bx = B->x ; + Bz = B->z ; + Yx = Y->x ; + Yz = Y->z ; + Y->nrow = dual*nk ; + Y->ncol = nrow ; + Y->d = dual*nk ; + ASSERT (((Int) Y->nzmax) >= nrow*nk*dual) ; + + /* ---------------------------------------------------------------------- */ + /* Y = B (P (1:nrow), k1:k2-1)' */ + /* ---------------------------------------------------------------------- */ + + switch (Y->xtype) + { + + case CHOLMOD_REAL: + + switch (B->xtype) + { + + case CHOLMOD_REAL: + /* Y real, B real */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = j-k1 ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [j2 + k*nk] = Bx [p] ; /* real */ + } + } + break ; + + case CHOLMOD_COMPLEX: + /* Y real, B complex. Y is 2*nk-by-nrow */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = 2*(j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [j2 + k*2*nk] = Bx [2*p ] ; /* real */ + Yx [j2+1 + k*2*nk] = Bx [2*p+1] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y real, B zomplex. Y is 2*nk-by-nrow */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = 2*(j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [j2 + k*2*nk] = Bx [p] ; /* real */ + Yx [j2+1 + k*2*nk] = Bz [p] ; /* imag */ + } + } + break ; + + } + break ; + + case CHOLMOD_COMPLEX: + + switch (B->xtype) + { + + case CHOLMOD_REAL: + /* Y complex, B real */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = 2*(j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [j2 + k*2*nk] = Bx [p] ; /* real */ + Yx [j2+1 + k*2*nk] = 0 ; /* imag */ + } + } + break ; + + case CHOLMOD_COMPLEX: + /* Y complex, B complex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = 2*(j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [j2 + k*2*nk] = Bx [2*p ] ; /* real */ + Yx [j2+1 + k*2*nk] = Bx [2*p+1] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y complex, B zomplex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = 2*(j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [j2 + k*2*nk] = Bx [p] ; /* real */ + Yx [j2+1 + k*2*nk] = Bz [p] ; /* imag */ + } + } + break ; + + } + break ; + + case CHOLMOD_ZOMPLEX: + + switch (B->xtype) + { + + case CHOLMOD_REAL: + /* Y zomplex, B real */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = j-k1 ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [j2 + k*nk] = Bx [p] ; /* real */ + Yz [j2 + k*nk] = 0 ; /* imag */ + } + } + break ; + + case CHOLMOD_COMPLEX: + /* Y zomplex, B complex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = j-k1 ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [j2 + k*nk] = Bx [2*p ] ; /* real */ + Yz [j2 + k*nk] = Bx [2*p+1] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y zomplex, B zomplex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = j-k1 ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Yx [j2 + k*nk] = Bx [p] ; /* real */ + Yz [j2 + k*nk] = Bz [p] ; /* imag */ + } + } + break ; + + } + break ; + + } +} + + +/* ========================================================================== */ +/* === iptrans ============================================================== */ +/* ========================================================================== */ + +/* X (P (1:nrow), k1 : min (k1+ncols,ncol)-1) = Y' where X is nrow-by-ncol. + * + * Copies into a permuted and transposed contiguous set of columns of X. + * X is already allocated on input. Y must be of sufficient size. Let nk be + * the number of columns accessed in X. X->xtype determines the complexity of + * the result. + * + * If X is real and Y is complex (or zomplex), only the real part of Y is + * copied into X. The imaginary part of Y is ignored. + * + * If X is complex (or zomplex) and Y is real, both the real and imaginary and + * parts of X are returned in Y. Y is 2*nk-by-nrow. The even + * rows of Y contain the real part of X and the odd rows contain the + * imaginary part of X. Y->nzmax must be >= 2*nrow*nk. Otherise, Y is + * nk-by-nrow with leading dimension nk. Y->nzmax must be >= nrow*nk. + * + * The case where Y is complex or zomplex, and X is real, is not used. + * + * The array transpose is performed, not the complex conjugate transpose. + */ + +static void iptrans +( + /* ---- input ---- */ + cholmod_dense *Y, /* input matrix Y */ + Int *Perm, /* optional input permutation (can be NULL) */ + Int k1, /* first column of X to copy into */ + Int ncols, /* last column to copy is min(k1+ncols,X->ncol)-1 */ + /* ---- in/out --- */ + cholmod_dense *X /* output matrix X, already allocated */ +) +{ + double *Yx, *Yz, *Xx, *Xz ; + Int k2, nk, p, k, j, nrow, ncol, d, dj, j2 ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + ncol = X->ncol ; + nrow = X->nrow ; + k2 = MIN (k1+ncols, ncol) ; + nk = MAX (k2 - k1, 0) ; + d = X->d ; + Xx = X->x ; + Xz = X->z ; + Yx = Y->x ; + Yz = Y->z ; + ASSERT (((Int) Y->nzmax) >= nrow*nk* + ((X->xtype != CHOLMOD_REAL && Y->xtype == CHOLMOD_REAL) ? 2:1)) ; + + /* ---------------------------------------------------------------------- */ + /* X (P (1:nrow), k1:k2-1) = Y' */ + /* ---------------------------------------------------------------------- */ + + switch (Y->xtype) + { + + case CHOLMOD_REAL: + + switch (X->xtype) + { + + case CHOLMOD_REAL: + /* Y real, X real */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = j-k1 ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [p] = Yx [j2 + k*nk] ; /* real */ + } + } + break ; + + case CHOLMOD_COMPLEX: + /* Y real, X complex. Y is 2*nk-by-nrow */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = 2*(j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [2*p ] = Yx [j2 + k*2*nk] ; /* real */ + Xx [2*p+1] = Yx [j2+1 + k*2*nk] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y real, X zomplex. Y is 2*nk-by-nrow */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = 2*(j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [p] = Yx [j2 + k*2*nk] ; /* real */ + Xz [p] = Yx [j2+1 + k*2*nk] ; /* imag */ + } + } + break ; + + } + break ; + + case CHOLMOD_COMPLEX: + + switch (X->xtype) + { + +#if 0 + case CHOLMOD_REAL: + /* this case is not used */ + break ; +#endif + + case CHOLMOD_COMPLEX: + /* Y complex, X complex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = 2*(j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [2*p ] = Yx [j2 + k*2*nk] ; /* real */ + Xx [2*p+1] = Yx [j2+1 + k*2*nk] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y complex, X zomplex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = 2*(j-k1) ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [p] = Yx [j2 + k*2*nk] ; /* real */ + Xz [p] = Yx [j2+1 + k*2*nk] ; /* imag */ + } + } + break ; + + } + break ; + + case CHOLMOD_ZOMPLEX: + + switch (X->xtype) + { + +#if 0 + case CHOLMOD_REAL: + /* this case is not used */ + break ; +#endif + + case CHOLMOD_COMPLEX: + /* Y zomplex, X complex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = j-k1 ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [2*p ] = Yx [j2 + k*nk] ; /* real */ + Xx [2*p+1] = Yz [j2 + k*nk] ; /* imag */ + } + } + break ; + + case CHOLMOD_ZOMPLEX: + /* Y zomplex, X zomplex */ + for (j = k1 ; j < k2 ; j++) + { + dj = d*j ; + j2 = j-k1 ; + for (k = 0 ; k < nrow ; k++) + { + p = P(k) + dj ; + Xx [p] = Yx [j2 + k*nk] ; /* real */ + Xz [p] = Yz [j2 + k*nk] ; /* imag */ + } + } + break ; + + } + break ; + + } +} + + +/* ========================================================================== */ +/* === cholmod_solve ======================================================== */ +/* ========================================================================== */ + +/* Solve a linear system. + * + * The factorization can be simplicial LDL', simplicial LL', or supernodal LL'. + * The Dx=b solve returns silently for the LL' factorizations (it is implicitly + * identity). + */ + +cholmod_dense *CHOLMOD(solve) +( + /* ---- input ---- */ + int sys, /* system to solve */ + cholmod_factor *L, /* factorization to use */ + cholmod_dense *B, /* right-hand-side */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_dense *Y = NULL, *X = NULL ; + cholmod_dense *E = NULL ; + int ok ; + + /* do the solve, allocating workspaces as needed */ + ok = CHOLMOD (solve2) (sys, L, B, NULL, &X, NULL, &Y, &E, Common) ; + + /* free workspaces if allocated, and free result if an error occured */ + CHOLMOD(free_dense) (&Y, Common) ; + CHOLMOD(free_dense) (&E, Common) ; + if (!ok) + { + CHOLMOD(free_dense) (&X, Common) ; + } + return (X) ; +} + + +/* ========================================================================== */ +/* === cholmod_solve2 ======================================================= */ +/* ========================================================================== */ + +/* This function acts just like cholmod_solve, except that the solution X and + * the internal workspace (Y and E) can be passed in preallocated. If the + * solution X or any required workspaces are not allocated on input, or if they + * are the wrong size or type, then this function frees them and reallocates + * them as the proper size and type. Thus, if you have a sequence of solves to + * do, you can let this function allocate X, Y, and E on the first call. + * Subsequent calls to cholmod_solve2 can then reuse this space. You must + * then free the workspaces Y and E (and X if desired) when you are finished. + * For example, the first call to cholmod_l_solve2, below, will solve the + * requested system. The next 2 calls (with different right-hand-sides but + * the same value of "sys") will resuse the workspace and solution X from the + * first call. Finally, when all solves are done, you must free the workspaces + * Y and E (otherwise you will have a memory leak), and you should also free X + * when you are done with it. Note that on input, X, Y, and E must be either + * valid cholmod_dense matrices, or initialized to NULL. You cannot pass in an + * uninitialized X, Y, or E. + * + * cholmod_dense *X = NULL, *Y = NULL, *E = NULL ; + * ... + * cholmod_l_solve2 (sys, L, B1, NULL, &X, NULL, &Y, &E, Common) ; + * cholmod_l_solve2 (sys, L, B2, NULL, &X, NULL, &Y, &E, Common) ; + * cholmod_l_solve2 (sys, L, B3, NULL, &X, NULL, &Y, &E, Common) ; + * cholmod_l_free_dense (&X, Common) ; + * cholmod_l_free_dense (&Y, Common) ; + * cholmod_l_free_dense (&E, Common) ; + * + * The equivalent when using cholmod_l_solve is: + * + * cholmod_dense *X = NULL, *Y = NULL, *E = NULL ; + * ... + * X = cholmod_l_solve (sys, L, B1, Common) ; + * cholmod_l_free_dense (&X, Common) ; + * X = cholmod_l_solve (sys, L, B2, Common) ; + * cholmod_l_free_dense (&X, Common) ; + * X = cholmod_l_solve (sys, L, B3, Common) ; + * cholmod_l_free_dense (&X, Common) ; + * + * Both methods work fine, but in the 2nd method with cholmod_solve, the + * internal workspaces (Y and E) are allocated and freed on each call. + * + * Bset is an optional sparse column (pattern only) that specifies a set + * of row indices. It is ignored if NULL, or if sys is CHOLMOD_P or + * CHOLMOD_Pt. If it is present and not ignored, B must be a dense column + * vector, and only entries B(i) where i is in the pattern of Bset are + * considered. All others are treated as if they were zero (they are not + * accessed). L must be a simplicial factorization, not supernodal. L is + * converted from supernodal to simplicial if necessary. The solution X is + * defined only for entries in the output sparse pattern of Xset. + * The xtype (real/complex/zomplex) of L and B must match. + * + * NOTE: If Bset is present and L is supernodal, it is converted to simplicial + * on output. + */ + +int CHOLMOD(solve2) /* returns TRUE on success, FALSE on failure */ +( + /* ---- input ---- */ + int sys, /* system to solve */ + cholmod_factor *L, /* factorization to use */ + cholmod_dense *B, /* right-hand-side */ + cholmod_sparse *Bset, + /* ---- output --- */ + cholmod_dense **X_Handle, /* solution, allocated if need be */ + cholmod_sparse **Xset_Handle, + /* ---- workspace */ + cholmod_dense **Y_Handle, /* workspace, or NULL */ + cholmod_dense **E_Handle, /* workspace, or NULL */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Yx, *Yz, *Bx, *Bz, *Xx, *Xz ; + cholmod_dense *Y = NULL, *X = NULL ; + cholmod_sparse *C, *Yset, C_header, Yset_header, *Xset ; + Int *Perm = NULL, *IPerm = NULL ; + Int n, nrhs, ncols, ctype, xtype, k1, nr, ytype, k, blen, p, i, d, nrow ; + Int Cp [2], Ysetp [2], *Ci, *Yseti, ysetlen ; + Int *Bsetp, *Bseti, *Bsetnz, *Xseti, *Xsetp, *Iwork ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_NULL (B, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (B, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + if (sys < CHOLMOD_A || sys > CHOLMOD_Pt) + { + ERROR (CHOLMOD_INVALID, "invalid system") ; + return (FALSE) ; + } + DEBUG (CHOLMOD(dump_factor) (L, "L", Common)) ; + DEBUG (CHOLMOD(dump_dense) (B, "B", Common)) ; + nrhs = B->ncol ; + n = (Int) L->n ; + d = (Int) B->d ; + nrow = (Int) B->nrow ; + if (d < n || nrow != n) + { + ERROR (CHOLMOD_INVALID, "dimensions of L and B do not match") ; + return (FALSE) ; + } + if (Bset) + { + if (nrhs != 1) + { + ERROR (CHOLMOD_INVALID, "Bset requires a single right-hand side") ; + return (FALSE) ; + } + if (L->xtype != B->xtype) + { + ERROR (CHOLMOD_INVALID, "Bset requires xtype of L and B to match") ; + return (FALSE) ; + } + DEBUG (CHOLMOD(dump_sparse) (Bset, "Bset", Common)) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + if ((sys == CHOLMOD_P || sys == CHOLMOD_Pt || sys == CHOLMOD_A) + && L->ordering != CHOLMOD_NATURAL) + { + /* otherwise, Perm is NULL, and the identity permutation is used */ + Perm = L->Perm ; + } + + /* ---------------------------------------------------------------------- */ + /* allocate the result X (or resuse the space from a prior call) */ + /* ---------------------------------------------------------------------- */ + + ctype = (Common->prefer_zomplex) ? CHOLMOD_ZOMPLEX : CHOLMOD_COMPLEX ; + + if (Bset) + { + xtype = L->xtype ; + } + else if (sys == CHOLMOD_P || sys == CHOLMOD_Pt) + { + /* x=Pb and x=P'b return X real if B is real; X is the preferred + * complex/zcomplex type if B is complex or zomplex */ + xtype = (B->xtype == CHOLMOD_REAL) ? CHOLMOD_REAL : ctype ; + } + else if (L->xtype == CHOLMOD_REAL && B->xtype == CHOLMOD_REAL) + { + /* X is real if both L and B are real */ + xtype = CHOLMOD_REAL ; + } + else + { + /* X is complex, use the preferred complex/zomplex type */ + xtype = ctype ; + } + + /* ensure X has the right size and type */ + X = CHOLMOD(ensure_dense) (X_Handle, n, nrhs, n, xtype, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* solve using L, D, L', P, or some combination */ + /* ---------------------------------------------------------------------- */ + + if (Bset) + { + + /* ------------------------------------------------------------------ */ + /* solve for a subset of x, with a sparse b */ + /* ------------------------------------------------------------------ */ + + Int save_realloc_state ; + +#ifndef NSUPERNODAL + /* convert a supernodal L to simplicial when using Bset */ + if (L->is_super) + { + /* Can only use Bset on a simplicial factorization. The supernodal + * factor L is converted to simplicial, leaving the xtype unchanged + * (real, complex, or zomplex). Since the supernodal factorization + * is already LL', it is left in that form. This conversion uses + * the ll_super_to_simplicial_numeric function in + * cholmod_change_factor. + */ + CHOLMOD(change_factor) ( + CHOLMOD_REAL, /* ignored, since L is already numeric */ + TRUE, /* convert to LL' (no change to num. values) */ + FALSE, /* convert to simplicial */ + FALSE, /* do not pack the columns of L */ + FALSE, /* (ignored) */ + L, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory, L is returned unchanged */ + return (FALSE) ; + } + } +#endif + + /* L, X, and B are all the same xtype */ + /* ensure Y is the the right size */ + Y = CHOLMOD(ensure_dense) (Y_Handle, 1, n, 1, L->xtype, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (FALSE) ; + } + + /* ------------------------------------------------------------------ */ + /* get the inverse permutation, constructing it if needed */ + /* ------------------------------------------------------------------ */ + + DEBUG (CHOLMOD (dump_perm) (Perm, n,n, "Perm", Common)) ; + + if ((sys == CHOLMOD_A || sys == CHOLMOD_P) && Perm != NULL) + { + /* The inverse permutation IPerm is used for the c=Pb step, + which is needed only for solving Ax=b or x=Pb. No other + steps should use IPerm */ + if (L->IPerm == NULL) + { + /* construct the inverse permutation. This is done only once + * and then stored in L permanently. */ + L->IPerm = CHOLMOD(malloc) (n, sizeof (Int), Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (FALSE) ; + } + IPerm = L->IPerm ; + for (k = 0 ; k < n ; k++) + { + IPerm [Perm [k]] = k ; + } + } + /* x=A\b and x=Pb both need IPerm */ + IPerm = L->IPerm ; + } + + if (sys == CHOLMOD_P) + { + /* x=Pb needs to turn off the subsequent x=P'b permutation */ + Perm = NULL ; + } + + DEBUG (CHOLMOD (dump_perm) (Perm, n,n, "Perm", Common)) ; + DEBUG (CHOLMOD (dump_perm) (IPerm, n,n, "IPerm", Common)) ; + + /* ------------------------------------------------------------------ */ + /* ensure Xset is the right size and type */ + /* ------------------------------------------------------------------ */ + + /* Xset is n-by-1, nzmax >= n, pattern-only, packed, unsorted */ + Xset = *Xset_Handle ; + if (Xset == NULL || (Int) Xset->nrow != n || (Int) Xset->ncol != 1 || + (Int) Xset->nzmax < n || Xset->itype != CHOLMOD_PATTERN) + { + /* this is done only once, for the 1st call to cholmod_solve */ + CHOLMOD(free_sparse) (Xset_Handle, Common) ; + Xset = CHOLMOD(allocate_sparse) (n, 1, n, FALSE, TRUE, 0, + CHOLMOD_PATTERN, Common) ; + *Xset_Handle = Xset ; + } + Xset->sorted = FALSE ; + Xset->stype = 0 ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (FALSE) ; + } + + /* -------------------------------------------------------------- */ + /* ensure Flag of size n, and 3*n Int workspace is available */ + /* -------------------------------------------------------------- */ + + /* does no work if prior calls already allocated enough space */ + CHOLMOD(allocate_work) (n, 3*n, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (FALSE) ; + } + + /* [ use Iwork (n:3n-1) for Ci and Yseti */ + Iwork = Common->Iwork ; + /* Iwork (0:n-1) is not used because it is used by check_perm, + print_perm, check_sparse, and print_sparse */ + Ci = Iwork + n ; + Yseti = Ci + n ; + + /* reallocating workspace would break Ci and Yseti */ + save_realloc_state = Common->no_workspace_reallocate ; + Common->no_workspace_reallocate = TRUE ; + + /* -------------------------------------------------------------- */ + /* C = permuted Bset, to correspond to the permutation of L */ + /* -------------------------------------------------------------- */ + + /* C = IPerm (Bset) */ + DEBUG (CHOLMOD(dump_sparse) (Bset, "Bset", Common)) ; + + Bsetp = Bset->p ; + Bseti = Bset->i ; + Bsetnz = Bset->nz ; + blen = (Bset->packed) ? Bsetp [1] : Bsetnz [0] ; + + /* C = spones (P*B) or C = spones (B) if IPerm is NULL */ + C = &C_header ; + C->nrow = n ; + C->ncol = 1 ; + C->nzmax = n ; + C->packed = TRUE ; + C->stype = 0 ; + C->itype = ITYPE ; + C->xtype = CHOLMOD_PATTERN ; + C->dtype = CHOLMOD_DOUBLE ; + C->nz = NULL ; + C->p = Cp ; + C->i = Ci ; + C->x = NULL ; + C->z = NULL ; + C->sorted = FALSE ; + Cp [0] = 0 ; + Cp [1] = blen ; + for (p = 0 ; p < blen ; p++) + { + Int iold = Bseti [p] ; + Ci [p] = IPerm ? IPerm [iold] : iold ; + } + DEBUG (CHOLMOD (dump_sparse) (C, "C", Common)) ; + + /* create a sparse column Yset from Iwork (n:2n-1) */ + Yset = &Yset_header ; + Yset->nrow = n ; + Yset->ncol = 1 ; + Yset->nzmax = n ; + Yset->packed = TRUE ; + Yset->stype = 0 ; + Yset->itype = ITYPE ; + Yset->xtype = CHOLMOD_PATTERN ; + Yset->dtype = CHOLMOD_DOUBLE ; + Yset->nz = NULL ; + Yset->p = Ysetp ; + Yset->i = Yseti ; + Yset->x = NULL ; + Yset->z = NULL ; + Yset->sorted = FALSE ; + Ysetp [0] = 0 ; + Ysetp [1] = 0 ; + DEBUG (CHOLMOD (dump_sparse) (Yset, "Yset empty", Common)) ; + + /* -------------------------------------------------------------- */ + /* Yset = nonzero pattern of L\C, or just C itself */ + /* -------------------------------------------------------------- */ + + /* this takes O(ysetlen) time */ + if (sys == CHOLMOD_P || sys == CHOLMOD_Pt || sys == CHOLMOD_D) + { + Ysetp [1] = blen ; + for (p = 0 ; p < blen ; p++) + { + Yseti [p] = Ci [p] ; + } + } + else + { + if (!CHOLMOD(lsolve_pattern) (C, L, Yset, Common)) + { + Common->no_workspace_reallocate = save_realloc_state ; + return (FALSE) ; + } + } + DEBUG (CHOLMOD (dump_sparse) (Yset, "Yset", Common)) ; + + /* -------------------------------------------------------------- */ + /* clear the parts of Y that we will use in the solve */ + /* -------------------------------------------------------------- */ + + Yx = Y->x ; + Yz = Y->z ; + ysetlen = Ysetp [1] ; + + switch (L->xtype) + { + + case CHOLMOD_REAL: + for (p = 0 ; p < ysetlen ; p++) + { + i = Yseti [p] ; + Yx [i] = 0 ; + } + break ; + + case CHOLMOD_COMPLEX: + for (p = 0 ; p < ysetlen ; p++) + { + i = Yseti [p] ; + Yx [2*i ] = 0 ; + Yx [2*i+1] = 0 ; + } + break ; + + case CHOLMOD_ZOMPLEX: + for (p = 0 ; p < ysetlen ; p++) + { + i = Yseti [p] ; + Yx [i] = 0 ; + Yz [i] = 0 ; + } + break ; + } + + DEBUG (CHOLMOD (dump_dense) (Y, "Y (Yset) = 0", Common)) ; + + /* -------------------------------------------------------------- */ + /* scatter and permute B into Y */ + /* -------------------------------------------------------------- */ + + /* Y (C) = B (Bset) */ + Bx = B->x ; + Bz = B->z ; + + switch (L->xtype) + { + + case CHOLMOD_REAL: + for (p = 0 ; p < blen ; p++) + { + Int iold = Bseti [p] ; + Int inew = Ci [p] ; + Yx [inew] = Bx [iold] ; + } + break ; + + case CHOLMOD_COMPLEX: + for (p = 0 ; p < blen ; p++) + { + Int iold = Bseti [p] ; + Int inew = Ci [p] ; + Yx [2*inew ] = Bx [2*iold ] ; + Yx [2*inew+1] = Bx [2*iold+1] ; + } + break ; + + case CHOLMOD_ZOMPLEX: + for (p = 0 ; p < blen ; p++) + { + Int iold = Bseti [p] ; + Int inew = Ci [p] ; + Yx [inew] = Bx [iold] ; + Yz [inew] = Bz [iold] ; + } + break ; + } + + DEBUG (CHOLMOD (dump_dense) (Y, "Y (C) = B (Bset)", Common)) ; + + /* -------------------------------------------------------------- */ + /* solve Y = (L' \ (L \ Y'))', or other system, with template */ + /* -------------------------------------------------------------- */ + + /* the solve only iterates over columns in Yseti [0...ysetlen-1] */ + + if (! (sys == CHOLMOD_P || sys == CHOLMOD_Pt)) + { + switch (L->xtype) + { + case CHOLMOD_REAL: + r_simplicial_solver (sys, L, Y, Yseti, ysetlen) ; + break ; + + case CHOLMOD_COMPLEX: + c_simplicial_solver (sys, L, Y, Yseti, ysetlen) ; + break ; + + case CHOLMOD_ZOMPLEX: + z_simplicial_solver (sys, L, Y, Yseti, ysetlen) ; + break ; + } + } + + DEBUG (CHOLMOD (dump_dense) (Y, "Y after solve", Common)) ; + + /* -------------------------------------------------------------- */ + /* X = P'*Y, but only for rows in Yset, and create Xset */ + /* -------------------------------------------------------------- */ + + /* X (Perm (Yset)) = Y (Yset) */ + Xx = X->x ; + Xz = X->z ; + Xseti = Xset->i ; + Xsetp = Xset->p ; + + switch (L->xtype) + { + + case CHOLMOD_REAL: + for (p = 0 ; p < ysetlen ; p++) + { + Int inew = Yseti [p] ; + Int iold = Perm ? Perm [inew] : inew ; + Xx [iold] = Yx [inew] ; + Xseti [p] = iold ; + } + break ; + + case CHOLMOD_COMPLEX: + for (p = 0 ; p < ysetlen ; p++) + { + Int inew = Yseti [p] ; + Int iold = Perm ? Perm [inew] : inew ; + Xx [2*iold ] = Yx [2*inew] ; + Xx [2*iold+1] = Yx [2*inew+1] ; + Xseti [p] = iold ; + } + break ; + + case CHOLMOD_ZOMPLEX: + for (p = 0 ; p < ysetlen ; p++) + { + Int inew = Yseti [p] ; + Int iold = Perm ? Perm [inew] : inew ; + Xx [iold] = Yx [inew] ; + Xz [iold] = Yz [inew] ; + Xseti [p] = iold ; + } + break ; + } + + Xsetp [0] = 0 ; + Xsetp [1] = ysetlen ; + + DEBUG (CHOLMOD(dump_sparse) (Xset, "Xset", Common)) ; + DEBUG (CHOLMOD(dump_dense) (X, "X", Common)) ; + Common->no_workspace_reallocate = save_realloc_state ; + /* done using Iwork (n:3n-1) for Ci and Yseti ] */ + + } + else if (sys == CHOLMOD_P) + { + + /* ------------------------------------------------------------------ */ + /* x = P*b */ + /* ------------------------------------------------------------------ */ + + perm (B, Perm, 0, nrhs, X) ; + + } + else if (sys == CHOLMOD_Pt) + { + + /* ------------------------------------------------------------------ */ + /* x = P'*b */ + /* ------------------------------------------------------------------ */ + + iperm (B, Perm, 0, nrhs, X) ; + + } + else if (L->is_super) + { + + /* ------------------------------------------------------------------ */ + /* solve using a supernodal LL' factorization */ + /* ------------------------------------------------------------------ */ + +#ifndef NSUPERNODAL + /* allocate workspace */ + cholmod_dense *E ; + Int dual ; + Common->blas_ok = TRUE ; + dual = (L->xtype == CHOLMOD_REAL && B->xtype != CHOLMOD_REAL) ? 2 : 1 ; + Y = CHOLMOD(ensure_dense) (Y_Handle, n, dual*nrhs, n, L->xtype, Common); + E = CHOLMOD(ensure_dense) (E_Handle, dual*nrhs, L->maxesize, dual*nrhs, + L->xtype, Common) ; + + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (FALSE) ; + } + + perm (B, Perm, 0, nrhs, Y) ; /* Y = P*B */ + + if (sys == CHOLMOD_A || sys == CHOLMOD_LDLt) + { + CHOLMOD(super_lsolve) (L, Y, E, Common) ; /* Y = L\Y */ + CHOLMOD(super_ltsolve) (L, Y, E, Common) ; /* Y = L'\Y*/ + } + else if (sys == CHOLMOD_L || sys == CHOLMOD_LD) + { + CHOLMOD(super_lsolve) (L, Y, E, Common) ; /* Y = L\Y */ + } + else if (sys == CHOLMOD_Lt || sys == CHOLMOD_DLt) + { + CHOLMOD(super_ltsolve) (L, Y, E, Common) ; /* Y = L'\Y*/ + } + + iperm (Y, Perm, 0, nrhs, X) ; /* X = P'*Y */ + + if (CHECK_BLAS_INT && !Common->blas_ok) + { + /* Integer overflow in the BLAS. This is probably impossible, + * since the BLAS were used to create the supernodal factorization. + * It might be possible for the calls to the BLAS to differ between + * factorization and forward/backsolves, however. This statement + * is untested; it does not appear in the compiled code if + * CHECK_BLAS_INT is true (when the same integer is used in + * CHOLMOD and the BLAS. */ + return (FALSE) ; + } + +#else + /* CHOLMOD Supernodal module not installed */ + ERROR (CHOLMOD_NOT_INSTALLED,"Supernodal module not installed") ; +#endif + + } + else + { + + /* ------------------------------------------------------------------ */ + /* solve using a simplicial LL' or LDL' factorization */ + /* ------------------------------------------------------------------ */ + + if (L->xtype == CHOLMOD_REAL && B->xtype == CHOLMOD_REAL) + { + /* L, B, and Y are all real */ + /* solve with up to 4 columns of B at a time */ + ncols = 4 ; + nr = MAX (4, nrhs) ; + ytype = CHOLMOD_REAL ; + } + else if (L->xtype == CHOLMOD_REAL) + { + /* L is real and B is complex or zomplex */ + /* solve with one column of B (real/imag), at a time */ + ncols = 1 ; + nr = 2 ; + ytype = CHOLMOD_REAL ; + } + else + { + /* L is complex or zomplex, B is real/complex/zomplex, Y has the + * same complexity as L. Solve with one column of B at a time. */ + ncols = 1 ; + nr = 1 ; + ytype = L->xtype ; + } + + Y = CHOLMOD(ensure_dense) (Y_Handle, nr, n, nr, ytype, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (FALSE) ; + } + + for (k1 = 0 ; k1 < nrhs ; k1 += ncols) + { + + /* -------------------------------------------------------------- */ + /* Y = B (P, k1:k1+ncols-1)' = (P * B (:,...))' */ + /* -------------------------------------------------------------- */ + + ptrans (B, Perm, k1, ncols, Y) ; + + /* -------------------------------------------------------------- */ + /* solve Y = (L' \ (L \ Y'))', or other system, with template */ + /* -------------------------------------------------------------- */ + + switch (L->xtype) + { + case CHOLMOD_REAL: + r_simplicial_solver (sys, L, Y, NULL, 0) ; + break ; + + case CHOLMOD_COMPLEX: + c_simplicial_solver (sys, L, Y, NULL, 0) ; + break ; + + case CHOLMOD_ZOMPLEX: + z_simplicial_solver (sys, L, Y, NULL, 0) ; + break ; + } + + /* -------------------------------------------------------------- */ + /* X (P, k1:k2+ncols-1) = Y' */ + /* -------------------------------------------------------------- */ + + iptrans (Y, Perm, k1, ncols, X) ; + } + } + + /* + printf ("bye from solve2\n") ; + */ + DEBUG (CHOLMOD(dump_dense) (X, "X result", Common)) ; + return (TRUE) ; +} +#endif diff --git a/src/CHOLMOD/Cholesky/cholmod_spsolve.c b/src/CHOLMOD/Cholesky/cholmod_spsolve.c new file mode 100644 index 0000000..1b0f71a --- /dev/null +++ b/src/CHOLMOD/Cholesky/cholmod_spsolve.c @@ -0,0 +1,396 @@ +/* ========================================================================== */ +/* === Cholesky/cholmod_spsolve ============================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Given an LL' or LDL' factorization of A, solve one of the following systems: + * + * Ax=b 0: CHOLMOD_A also applies the permutation L->Perm + * LDL'x=b 1: CHOLMOD_LDLt does not apply L->Perm + * LDx=b 2: CHOLMOD_LD + * DL'x=b 3: CHOLMOD_DLt + * Lx=b 4: CHOLMOD_L + * L'x=b 5: CHOLMOD_Lt + * Dx=b 6: CHOLMOD_D + * x=Pb 7: CHOLMOD_P apply a permutation (P is L->Perm) + * x=P'b 8: CHOLMOD_Pt apply an inverse permutation + * + * where b and x are sparse. If L and b are real, then x is real. Otherwise, + * x is complex or zomplex, depending on the Common->prefer_zomplex parameter. + * All xtypes of x and b are supported (real, complex, and zomplex). + */ + +#ifndef NCHOLESKY + +#include "cholmod_internal.h" +#include "cholmod_cholesky.h" + +/* ========================================================================== */ +/* === EXPAND_AS_NEEDED ===================================================== */ +/* ========================================================================== */ + +/* Double the size of the sparse matrix X, if we have run out of space. */ + +#define EXPAND_AS_NEEDED \ +if (xnz >= nzmax) \ +{ \ + nzmax *= 2 ; \ + CHOLMOD(reallocate_sparse) (nzmax, X, Common) ; \ + if (Common->status < CHOLMOD_OK) \ + { \ + CHOLMOD(free_sparse) (&X, Common) ; \ + CHOLMOD(free_dense) (&X4, Common) ; \ + CHOLMOD(free_dense) (&B4, Common) ; \ + return (NULL) ; \ + } \ + Xi = X->i ; \ + Xx = X->x ; \ + Xz = X->z ; \ +} + + +/* ========================================================================== */ +/* === cholmod_spolve ======================================================= */ +/* ========================================================================== */ + +cholmod_sparse *CHOLMOD(spsolve) /* returns the sparse solution X */ +( + /* ---- input ---- */ + int sys, /* system to solve */ + cholmod_factor *L, /* factorization to use */ + cholmod_sparse *B, /* right-hand-side */ + /* --------------- */ + cholmod_common *Common +) +{ + double x, z ; + cholmod_dense *X4, *B4 ; + cholmod_sparse *X ; + double *Bx, *Bz, *Xx, *Xz, *B4x, *B4z, *X4x, *X4z ; + Int *Bi, *Bp, *Xp, *Xi, *Bnz ; + Int n, nrhs, q, p, i, j, jfirst, jlast, packed, block, pend, j_n, xtype ; + size_t xnz, nzmax ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (L, NULL) ; + RETURN_IF_NULL (B, NULL) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, NULL) ; + RETURN_IF_XTYPE_INVALID (B, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, NULL) ; + if (L->n != B->nrow) + { + ERROR (CHOLMOD_INVALID, "dimensions of L and B do not match") ; + return (NULL) ; + } + if (B->stype) + { + ERROR (CHOLMOD_INVALID, "B cannot be stored in symmetric mode") ; + return (NULL) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace B4 and initial result X */ + /* ---------------------------------------------------------------------- */ + + n = L->n ; + nrhs = B->ncol ; + + /* X is real if both L and B are real, complex/zomplex otherwise */ + xtype = (L->xtype == CHOLMOD_REAL && B->xtype == CHOLMOD_REAL) ? + CHOLMOD_REAL : + (Common->prefer_zomplex ? CHOLMOD_ZOMPLEX : CHOLMOD_COMPLEX) ; + + /* solve up to 4 columns at a time */ + block = MIN (nrhs, 4) ; + + /* initial size of X is at most 4*n */ + nzmax = n*block ; + + X = CHOLMOD(spzeros) (n, nrhs, nzmax, xtype, Common) ; + B4 = CHOLMOD(zeros) (n, block, B->xtype, Common) ; + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_sparse) (&X, Common) ; + CHOLMOD(free_dense) (&B4, Common) ; + return (NULL) ; + } + + Bp = B->p ; + Bi = B->i ; + Bx = B->x ; + Bz = B->z ; + Bnz = B->nz ; + packed = B->packed ; + + Xp = X->p ; + Xi = X->i ; + Xx = X->x ; + Xz = X->z ; + + xnz = 0 ; + + B4x = B4->x ; + B4z = B4->z ; + + /* ---------------------------------------------------------------------- */ + /* solve in chunks of 4 columns at a time */ + /* ---------------------------------------------------------------------- */ + + for (jfirst = 0 ; jfirst < nrhs ; jfirst += block) + { + + /* ------------------------------------------------------------------ */ + /* adjust the number of columns of B4 */ + /* ------------------------------------------------------------------ */ + + jlast = MIN (nrhs, jfirst + block) ; + B4->ncol = jlast - jfirst ; + + /* ------------------------------------------------------------------ */ + /* scatter B(jfirst:jlast-1) into B4 */ + /* ------------------------------------------------------------------ */ + + for (j = jfirst ; j < jlast ; j++) + { + p = Bp [j] ; + pend = (packed) ? (Bp [j+1]) : (p + Bnz [j]) ; + j_n = (j-jfirst)*n ; + + switch (B->xtype) + { + + case CHOLMOD_REAL: + for ( ; p < pend ; p++) + { + B4x [Bi [p] + j_n] = Bx [p] ; + } + break ; + + case CHOLMOD_COMPLEX: + for ( ; p < pend ; p++) + { + q = Bi [p] + j_n ; + B4x [2*q ] = Bx [2*p ] ; + B4x [2*q+1] = Bx [2*p+1] ; + } + break ; + + case CHOLMOD_ZOMPLEX: + for ( ; p < pend ; p++) + { + q = Bi [p] + j_n ; + B4x [q] = Bx [p] ; + B4z [q] = Bz [p] ; + } + break ; + } + } + + /* ------------------------------------------------------------------ */ + /* solve the system (X4 = A\B4 or other system) */ + /* ------------------------------------------------------------------ */ + + X4 = CHOLMOD(solve) (sys, L, B4, Common) ; + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_sparse) (&X, Common) ; + CHOLMOD(free_dense) (&B4, Common) ; + CHOLMOD(free_dense) (&X4, Common) ; + return (NULL) ; + } + ASSERT (X4->xtype == xtype) ; + X4x = X4->x ; + X4z = X4->z ; + + /* ------------------------------------------------------------------ */ + /* append the solution onto X */ + /* ------------------------------------------------------------------ */ + + for (j = jfirst ; j < jlast ; j++) + { + Xp [j] = xnz ; + j_n = (j-jfirst)*n ; + if ( xnz + n <= nzmax) + { + + /* ---------------------------------------------------------- */ + /* X is guaranteed to be large enough */ + /* ---------------------------------------------------------- */ + + switch (xtype) + { + + case CHOLMOD_REAL: + for (i = 0 ; i < n ; i++) + { + x = X4x [i + j_n] ; + if (IS_NONZERO (x)) + { + Xi [xnz] = i ; + Xx [xnz] = x ; + xnz++ ; + } + } + break ; + + case CHOLMOD_COMPLEX: + for (i = 0 ; i < n ; i++) + { + x = X4x [2*(i + j_n) ] ; + z = X4x [2*(i + j_n)+1] ; + if (IS_NONZERO (x) || IS_NONZERO (z)) + { + Xi [xnz] = i ; + Xx [2*xnz ] = x ; + Xx [2*xnz+1] = z ; + xnz++ ; + } + } + break ; + + case CHOLMOD_ZOMPLEX: + for (i = 0 ; i < n ; i++) + { + x = X4x [i + j_n] ; + z = X4z [i + j_n] ; + if (IS_NONZERO (x) || IS_NONZERO (z)) + { + Xi [xnz] = i ; + Xx [xnz] = x ; + Xz [xnz] = z ; + xnz++ ; + } + } + break ; + } + + } + else + { + + /* ---------------------------------------------------------- */ + /* X may need to increase in size */ + /* ---------------------------------------------------------- */ + + switch (xtype) + { + + case CHOLMOD_REAL: + for (i = 0 ; i < n ; i++) + { + x = X4x [i + j_n] ; + if (IS_NONZERO (x)) + { + EXPAND_AS_NEEDED ; + Xi [xnz] = i ; + Xx [xnz] = x ; + xnz++ ; + } + } + break ; + + case CHOLMOD_COMPLEX: + for (i = 0 ; i < n ; i++) + { + x = X4x [2*(i + j_n) ] ; + z = X4x [2*(i + j_n)+1] ; + if (IS_NONZERO (x) || IS_NONZERO (z)) + { + EXPAND_AS_NEEDED ; + Xi [xnz] = i ; + Xx [2*xnz ] = x ; + Xx [2*xnz+1] = z ; + xnz++ ; + } + } + break ; + + case CHOLMOD_ZOMPLEX: + for (i = 0 ; i < n ; i++) + { + x = X4x [i + j_n] ; + z = X4z [i + j_n] ; + if (IS_NONZERO (x) || IS_NONZERO (z)) + { + EXPAND_AS_NEEDED ; + Xi [xnz] = i ; + Xx [xnz] = x ; + Xz [xnz] = z ; + xnz++ ; + } + } + break ; + } + + } + } + CHOLMOD(free_dense) (&X4, Common) ; + + /* ------------------------------------------------------------------ */ + /* clear B4 for next iteration */ + /* ------------------------------------------------------------------ */ + + if (jlast < nrhs) + { + + for (j = jfirst ; j < jlast ; j++) + { + p = Bp [j] ; + pend = (packed) ? (Bp [j+1]) : (p + Bnz [j]) ; + j_n = (j-jfirst)*n ; + + switch (B->xtype) + { + + case CHOLMOD_REAL: + for ( ; p < pend ; p++) + { + B4x [Bi [p] + j_n] = 0 ; + } + break ; + + case CHOLMOD_COMPLEX: + for ( ; p < pend ; p++) + { + q = Bi [p] + j_n ; + B4x [2*q ] = 0 ; + B4x [2*q+1] = 0 ; + } + break ; + + case CHOLMOD_ZOMPLEX: + for ( ; p < pend ; p++) + { + q = Bi [p] + j_n ; + B4x [q] = 0 ; + B4z [q] = 0 ; + } + break ; + } + } + } + } + + Xp [nrhs] = xnz ; + + /* ---------------------------------------------------------------------- */ + /* reduce X in size, free workspace, and return result */ + /* ---------------------------------------------------------------------- */ + + ASSERT (xnz <= X->nzmax) ; + CHOLMOD(reallocate_sparse) (xnz, X, Common) ; + ASSERT (Common->status == CHOLMOD_OK) ; + CHOLMOD(free_dense) (&B4, Common) ; + return (X) ; +} +#endif diff --git a/src/CHOLMOD/Cholesky/lesser.txt b/src/CHOLMOD/Cholesky/lesser.txt new file mode 100644 index 0000000..8add30a --- /dev/null +++ b/src/CHOLMOD/Cholesky/lesser.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/src/CHOLMOD/Cholesky/t_cholmod_lsolve.c b/src/CHOLMOD/Cholesky/t_cholmod_lsolve.c new file mode 100644 index 0000000..c91dc2f --- /dev/null +++ b/src/CHOLMOD/Cholesky/t_cholmod_lsolve.c @@ -0,0 +1,850 @@ +/* ========================================================================== */ +/* === Cholesky/t_cholmod_lsolve ============================================ */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2013, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Template routine to solve Lx=b with unit or non-unit diagonal, or + * solve LDx=b. + * + * The numeric xtype of L and Y must match. Y contains b on input and x on + * output, stored in row-form. Y is nrow-by-n, where nrow must equal 1 for the + * complex or zomplex cases, and nrow <= 4 for the real case. + * + * This file is not compiled separately. It is included in t_cholmod_solve.c + * instead. It contains no user-callable routines. + * + * workspace: none + * + * Supports real, complex, and zomplex factors. + */ + +/* undefine all prior definitions */ +#undef FORM_NAME +#undef LSOLVE + +/* -------------------------------------------------------------------------- */ +/* define the method */ +/* -------------------------------------------------------------------------- */ + +#ifdef LL +/* LL': solve Lx=b with non-unit diagonal */ +#define FORM_NAME(prefix,rank) prefix ## ll_lsolve_ ## rank + +#elif defined (LD) +/* LDL': solve LDx=b */ +#define FORM_NAME(prefix,rank) prefix ## ldl_ldsolve_ ## rank + +#else +/* LDL': solve Lx=b with unit diagonal */ +#define FORM_NAME(prefix,rank) prefix ## ldl_lsolve_ ## rank + +#endif + +/* LSOLVE(k) defines the name of a routine for an n-by-k right-hand-side. */ + +#define LSOLVE(prefix,rank) FORM_NAME(prefix,rank) + +#ifdef REAL + +/* ========================================================================== */ +/* === LSOLVE (1) =========================================================== */ +/* ========================================================================== */ + +/* Solve Lx=b, where b has 1 column */ + +static void LSOLVE (PREFIX,1) +( + cholmod_factor *L, + double X [ ] /* n-by-1 in row form */ +) +{ + double *Lx = L->x ; + Int *Li = L->i ; + Int *Lp = L->p ; + Int *Lnz = L->nz ; + Int j, n = L->n ; + + for (j = 0 ; j < n ; ) + { + /* get the start, end, and length of column j */ + Int p = Lp [j] ; + Int lnz = Lnz [j] ; + Int pend = p + lnz ; + + /* find a chain of supernodes (up to j, j+1, and j+2) */ + if (lnz < 4 || lnz != Lnz [j+1] + 1 || Li [p+1] != j+1) + { + + /* -------------------------------------------------------------- */ + /* solve with a single column of L */ + /* -------------------------------------------------------------- */ + + double y = X [j] ; +#ifdef LL + y /= Lx [p] ; + X [j] = y ; +#elif defined (LD) + X [j] = y / Lx [p] ; +#endif + for (p++ ; p < pend ; p++) + { + X [Li [p]] -= Lx [p] * y ; + } + j++ ; /* advance to next column of L */ + + } + else if (lnz != Lnz [j+2] + 2 || Li [p+2] != j+2) + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of two columns of L */ + /* -------------------------------------------------------------- */ + + double y [2] ; + Int q = Lp [j+1] ; +#ifdef LL + y [0] = X [j] / Lx [p] ; + y [1] = (X [j+1] - Lx [p+1] * y [0]) / Lx [q] ; + X [j ] = y [0] ; + X [j+1] = y [1] ; +#elif defined (LD) + y [0] = X [j] ; + y [1] = X [j+1] - Lx [p+1] * y [0] ; + X [j ] = y [0] / Lx [p] ; + X [j+1] = y [1] / Lx [q] ; +#else + y [0] = X [j] ; + y [1] = X [j+1] - Lx [p+1] * y [0] ; + X [j+1] = y [1] ; +#endif + for (p += 2, q++ ; p < pend ; p++, q++) + { + X [Li [p]] -= Lx [p] * y [0] + Lx [q] * y [1] ; + } + j += 2 ; /* advance to next column of L */ + + } + else + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of three columns of L */ + /* -------------------------------------------------------------- */ + + double y [3] ; + Int q = Lp [j+1] ; + Int r = Lp [j+2] ; +#ifdef LL + y [0] = X [j] / Lx [p] ; + y [1] = (X [j+1] - Lx [p+1] * y [0]) / Lx [q] ; + y [2] = (X [j+2] - Lx [p+2] * y [0] - Lx [q+1] * y [1]) / Lx [r] ; + X [j ] = y [0] ; + X [j+1] = y [1] ; + X [j+2] = y [2] ; +#elif defined (LD) + y [0] = X [j] ; + y [1] = X [j+1] - Lx [p+1] * y [0] ; + y [2] = X [j+2] - Lx [p+2] * y [0] - Lx [q+1] * y [1] ; + X [j ] = y [0] / Lx [p] ; + X [j+1] = y [1] / Lx [q] ; + X [j+2] = y [2] / Lx [r] ; +#else + y [0] = X [j] ; + y [1] = X [j+1] - Lx [p+1] * y [0] ; + y [2] = X [j+2] - Lx [p+2] * y [0] - Lx [q+1] * y [1] ; + X [j+1] = y [1] ; + X [j+2] = y [2] ; +#endif + for (p += 3, q += 2, r++ ; p < pend ; p++, q++, r++) + { + X [Li [p]] -= Lx [p] * y [0] + Lx [q] * y [1] + Lx [r] * y [2] ; + } + j += 3 ; /* advance to next column of L */ + } + } +} + + +/* ========================================================================== */ +/* === LSOLVE (2) =========================================================== */ +/* ========================================================================== */ + +/* Solve Lx=b, where b has 2 columns */ + +static void LSOLVE (PREFIX,2) +( + cholmod_factor *L, + double X [ ][2] /* n-by-2 in row form */ +) +{ + double *Lx = L->x ; + Int *Li = L->i ; + Int *Lp = L->p ; + Int *Lnz = L->nz ; + Int j, n = L->n ; + + for (j = 0 ; j < n ; ) + { + /* get the start, end, and length of column j */ + Int p = Lp [j] ; + Int lnz = Lnz [j] ; + Int pend = p + lnz ; + + /* find a chain of supernodes (up to j, j+1, and j+2) */ + if (lnz < 4 || lnz != Lnz [j+1] + 1 || Li [p+1] != j+1) + { + + /* -------------------------------------------------------------- */ + /* solve with a single column of L */ + /* -------------------------------------------------------------- */ + + double y [2] ; + y [0] = X [j][0] ; + y [1] = X [j][1] ; +#ifdef LL + y [0] /= Lx [p] ; + y [1] /= Lx [p] ; + X [j][0] = y [0] ; + X [j][1] = y [1] ; +#elif defined (LD) + X [j][0] = y [0] / Lx [p] ; + X [j][1] = y [1] / Lx [p] ; +#endif + for (p++ ; p < pend ; p++) + { + Int i = Li [p] ; + X [i][0] -= Lx [p] * y [0] ; + X [i][1] -= Lx [p] * y [1] ; + } + j++ ; /* advance to next column of L */ + + } + else if (lnz != Lnz [j+2] + 2 || Li [p+2] != j+2) + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of two columns of L */ + /* -------------------------------------------------------------- */ + + double y [2][2] ; + Int q = Lp [j+1] ; + y [0][0] = X [j][0] ; + y [0][1] = X [j][1] ; +#ifdef LL + y [0][0] /= Lx [p] ; + y [0][1] /= Lx [p] ; + y [1][0] = (X [j+1][0] - Lx [p+1] * y [0][0]) / Lx [q] ; + y [1][1] = (X [j+1][1] - Lx [p+1] * y [0][1]) / Lx [q] ; + X [j ][0] = y [0][0] ; + X [j ][1] = y [0][1] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; +#elif defined (LD) + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + X [j ][0] = y [0][0] / Lx [p] ; + X [j ][1] = y [0][1] / Lx [p] ; + X [j+1][0] = y [1][0] / Lx [q] ; + X [j+1][1] = y [1][1] / Lx [q] ; +#else + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; +#endif + for (p += 2, q++ ; p < pend ; p++, q++) + { + Int i = Li [p] ; + X [i][0] -= Lx [p] * y [0][0] + Lx [q] * y [1][0] ; + X [i][1] -= Lx [p] * y [0][1] + Lx [q] * y [1][1] ; + } + j += 2 ; /* advance to next column of L */ + + } + else + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of three columns of L */ + /* -------------------------------------------------------------- */ + + double y [3][2] ; + Int q = Lp [j+1] ; + Int r = Lp [j+2] ; + y [0][0] = X [j][0] ; + y [0][1] = X [j][1] ; +#ifdef LL + y [0][0] /= Lx [p] ; + y [0][1] /= Lx [p] ; + y [1][0] = (X [j+1][0] - Lx[p+1] * y[0][0]) / Lx [q] ; + y [1][1] = (X [j+1][1] - Lx[p+1] * y[0][1]) / Lx [q] ; + y [2][0] = (X [j+2][0] - Lx[p+2] * y[0][0] - Lx[q+1]*y[1][0])/Lx[r]; + y [2][1] = (X [j+2][1] - Lx[p+2] * y[0][1] - Lx[q+1]*y[1][1])/Lx[r]; + X [j ][0] = y [0][0] ; + X [j ][1] = y [0][1] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; + X [j+2][0] = y [2][0] ; + X [j+2][1] = y [2][1] ; +#elif defined (LD) + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + y [2][0] = X [j+2][0] - Lx [p+2] * y [0][0] - Lx [q+1] * y [1][0] ; + y [2][1] = X [j+2][1] - Lx [p+2] * y [0][1] - Lx [q+1] * y [1][1] ; + X [j ][0] = y [0][0] / Lx [p] ; + X [j ][1] = y [0][1] / Lx [p] ; + X [j+1][0] = y [1][0] / Lx [q] ; + X [j+1][1] = y [1][1] / Lx [q] ; + X [j+2][0] = y [2][0] / Lx [r] ; + X [j+2][1] = y [2][1] / Lx [r] ; +#else + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + y [2][0] = X [j+2][0] - Lx [p+2] * y [0][0] - Lx [q+1] * y [1][0] ; + y [2][1] = X [j+2][1] - Lx [p+2] * y [0][1] - Lx [q+1] * y [1][1] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; + X [j+2][0] = y [2][0] ; + X [j+2][1] = y [2][1] ; +#endif + for (p += 3, q += 2, r++ ; p < pend ; p++, q++, r++) + { + Int i = Li [p] ; + X[i][0] -= Lx[p] * y[0][0] + Lx[q] * y[1][0] + Lx[r] * y[2][0] ; + X[i][1] -= Lx[p] * y[0][1] + Lx[q] * y[1][1] + Lx[r] * y[2][1] ; + } + j += 3 ; /* advance to next column of L */ + } + } +} + + +/* ========================================================================== */ +/* === LSOLVE (3) =========================================================== */ +/* ========================================================================== */ + +/* Solve Lx=b, where b has 3 columns */ + +static void LSOLVE (PREFIX,3) +( + cholmod_factor *L, + double X [ ][3] /* n-by-3 in row form */ +) +{ + double *Lx = L->x ; + Int *Li = L->i ; + Int *Lp = L->p ; + Int *Lnz = L->nz ; + Int j, n = L->n ; + + for (j = 0 ; j < n ; ) + { + /* get the start, end, and length of column j */ + Int p = Lp [j] ; + Int lnz = Lnz [j] ; + Int pend = p + lnz ; + + /* find a chain of supernodes (up to j, j+1, and j+2) */ + if (lnz < 4 || lnz != Lnz [j+1] + 1 || Li [p+1] != j+1) + { + + /* -------------------------------------------------------------- */ + /* solve with a single column of L */ + /* -------------------------------------------------------------- */ + + double y [3] ; + y [0] = X [j][0] ; + y [1] = X [j][1] ; + y [2] = X [j][2] ; +#ifdef LL + y [0] /= Lx [p] ; + y [1] /= Lx [p] ; + y [2] /= Lx [p] ; + X [j][0] = y [0] ; + X [j][1] = y [1] ; + X [j][2] = y [2] ; +#elif defined (LD) + X [j][0] = y [0] / Lx [p] ; + X [j][1] = y [1] / Lx [p] ; + X [j][2] = y [2] / Lx [p] ; +#endif + for (p++ ; p < pend ; p++) + { + Int i = Li [p] ; + double lx = Lx [p] ; + X [i][0] -= lx * y [0] ; + X [i][1] -= lx * y [1] ; + X [i][2] -= lx * y [2] ; + } + j++ ; /* advance to next column of L */ + + } + else if (lnz != Lnz [j+2] + 2 || Li [p+2] != j+2) + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of two columns of L */ + /* -------------------------------------------------------------- */ + + double y [2][3] ; + Int q = Lp [j+1] ; + y [0][0] = X [j][0] ; + y [0][1] = X [j][1] ; + y [0][2] = X [j][2] ; +#ifdef LL + y [0][0] /= Lx [p] ; + y [0][1] /= Lx [p] ; + y [0][2] /= Lx [p] ; + y [1][0] = (X [j+1][0] - Lx [p+1] * y [0][0]) / Lx [q] ; + y [1][1] = (X [j+1][1] - Lx [p+1] * y [0][1]) / Lx [q] ; + y [1][2] = (X [j+1][2] - Lx [p+1] * y [0][2]) / Lx [q] ; + X [j ][0] = y [0][0] ; + X [j ][1] = y [0][1] ; + X [j ][2] = y [0][2] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; + X [j+1][2] = y [1][2] ; +#elif defined (LD) + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + y [1][2] = X [j+1][2] - Lx [p+1] * y [0][2] ; + X [j ][0] = y [0][0] / Lx [p] ; + X [j ][1] = y [0][1] / Lx [p] ; + X [j ][2] = y [0][2] / Lx [p] ; + X [j+1][0] = y [1][0] / Lx [q] ; + X [j+1][1] = y [1][1] / Lx [q] ; + X [j+1][2] = y [1][2] / Lx [q] ; +#else + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + y [1][2] = X [j+1][2] - Lx [p+1] * y [0][2] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; + X [j+1][2] = y [1][2] ; +#endif + for (p += 2, q++ ; p < pend ; p++, q++) + { + Int i = Li [p] ; + double lx [2] ; + lx [0] = Lx [p] ; + lx [1] = Lx [q] ; + X [i][0] -= lx [0] * y [0][0] + lx [1] * y [1][0] ; + X [i][1] -= lx [0] * y [0][1] + lx [1] * y [1][1] ; + X [i][2] -= lx [0] * y [0][2] + lx [1] * y [1][2] ; + } + j += 2 ; /* advance to next column of L */ + + } + else + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of three columns of L */ + /* -------------------------------------------------------------- */ + + double y [3][3] ; + Int q = Lp [j+1] ; + Int r = Lp [j+2] ; + y [0][0] = X [j][0] ; + y [0][1] = X [j][1] ; + y [0][2] = X [j][2] ; +#ifdef LL + y [0][0] /= Lx [p] ; + y [0][1] /= Lx [p] ; + y [0][2] /= Lx [p] ; + y [1][0] = (X [j+1][0] - Lx[p+1] * y[0][0]) / Lx [q] ; + y [1][1] = (X [j+1][1] - Lx[p+1] * y[0][1]) / Lx [q] ; + y [1][2] = (X [j+1][2] - Lx[p+1] * y[0][2]) / Lx [q] ; + y [2][0] = (X [j+2][0] - Lx[p+2] * y[0][0] - Lx[q+1]*y[1][0])/Lx[r]; + y [2][1] = (X [j+2][1] - Lx[p+2] * y[0][1] - Lx[q+1]*y[1][1])/Lx[r]; + y [2][2] = (X [j+2][2] - Lx[p+2] * y[0][2] - Lx[q+1]*y[1][2])/Lx[r]; + X [j ][0] = y [0][0] ; + X [j ][1] = y [0][1] ; + X [j ][2] = y [0][2] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; + X [j+1][2] = y [1][2] ; + X [j+2][0] = y [2][0] ; + X [j+2][1] = y [2][1] ; + X [j+2][2] = y [2][2] ; +#elif defined (LD) + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + y [1][2] = X [j+1][2] - Lx [p+1] * y [0][2] ; + y [2][0] = X [j+2][0] - Lx [p+2] * y [0][0] - Lx [q+1] * y [1][0] ; + y [2][1] = X [j+2][1] - Lx [p+2] * y [0][1] - Lx [q+1] * y [1][1] ; + y [2][2] = X [j+2][2] - Lx [p+2] * y [0][2] - Lx [q+1] * y [1][2] ; + X [j ][0] = y [0][0] / Lx [p] ; + X [j ][1] = y [0][1] / Lx [p] ; + X [j ][2] = y [0][2] / Lx [p] ; + X [j+1][0] = y [1][0] / Lx [q] ; + X [j+1][1] = y [1][1] / Lx [q] ; + X [j+1][2] = y [1][2] / Lx [q] ; + X [j+2][0] = y [2][0] / Lx [r] ; + X [j+2][1] = y [2][1] / Lx [r] ; + X [j+2][2] = y [2][2] / Lx [r] ; +#else + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + y [1][2] = X [j+1][2] - Lx [p+1] * y [0][2] ; + y [2][0] = X [j+2][0] - Lx [p+2] * y [0][0] - Lx [q+1] * y [1][0] ; + y [2][1] = X [j+2][1] - Lx [p+2] * y [0][1] - Lx [q+1] * y [1][1] ; + y [2][2] = X [j+2][2] - Lx [p+2] * y [0][2] - Lx [q+1] * y [1][2] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; + X [j+1][2] = y [1][2] ; + X [j+2][0] = y [2][0] ; + X [j+2][1] = y [2][1] ; + X [j+2][2] = y [2][2] ; +#endif + for (p += 3, q += 2, r++ ; p < pend ; p++, q++, r++) + { + Int i = Li [p] ; + double lx [3] ; + lx [0] = Lx [p] ; + lx [1] = Lx [q] ; + lx [2] = Lx [r] ; + X [i][0] -= lx[0] * y[0][0] + lx[1] * y[1][0] + lx[2] * y[2][0]; + X [i][1] -= lx[0] * y[0][1] + lx[1] * y[1][1] + lx[2] * y[2][1]; + X [i][2] -= lx[0] * y[0][2] + lx[1] * y[1][2] + lx[2] * y[2][2]; + } + j += 3 ; /* advance to next column of L */ + } + } +} + + +/* ========================================================================== */ +/* === LSOLVE (4) =========================================================== */ +/* ========================================================================== */ + +/* Solve Lx=b, where b has 4 columns */ + +static void LSOLVE (PREFIX,4) +( + cholmod_factor *L, + double X [ ][4] /* n-by-4 in row form */ +) +{ + double *Lx = L->x ; + Int *Li = L->i ; + Int *Lp = L->p ; + Int *Lnz = L->nz ; + Int j, n = L->n ; + + for (j = 0 ; j < n ; ) + { + /* get the start, end, and length of column j */ + Int p = Lp [j] ; + Int lnz = Lnz [j] ; + Int pend = p + lnz ; + + /* find a chain of supernodes (up to j, j+1, and j+2) */ + if (lnz < 4 || lnz != Lnz [j+1] + 1 || Li [p+1] != j+1) + { + + /* -------------------------------------------------------------- */ + /* solve with a single column of L */ + /* -------------------------------------------------------------- */ + + double y [4] ; + y [0] = X [j][0] ; + y [1] = X [j][1] ; + y [2] = X [j][2] ; + y [3] = X [j][3] ; +#ifdef LL + y [0] /= Lx [p] ; + y [1] /= Lx [p] ; + y [2] /= Lx [p] ; + y [3] /= Lx [p] ; + X [j][0] = y [0] ; + X [j][1] = y [1] ; + X [j][2] = y [2] ; + X [j][3] = y [3] ; +#elif defined (LD) + X [j][0] = y [0] / Lx [p] ; + X [j][1] = y [1] / Lx [p] ; + X [j][2] = y [2] / Lx [p] ; + X [j][3] = y [3] / Lx [p] ; +#endif + for (p++ ; p < pend ; p++) + { + Int i = Li [p] ; + double lx = Lx [p] ; + X [i][0] -= lx * y [0] ; + X [i][1] -= lx * y [1] ; + X [i][2] -= lx * y [2] ; + X [i][3] -= lx * y [3] ; + } + j++ ; /* advance to next column of L */ + + } + else if (lnz != Lnz [j+2] + 2 || Li [p+2] != j+2) + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of two columns of L */ + /* -------------------------------------------------------------- */ + + double y [2][4] ; + Int q = Lp [j+1] ; + y [0][0] = X [j][0] ; + y [0][1] = X [j][1] ; + y [0][2] = X [j][2] ; + y [0][3] = X [j][3] ; +#ifdef LL + y [0][0] /= Lx [p] ; + y [0][1] /= Lx [p] ; + y [0][2] /= Lx [p] ; + y [0][3] /= Lx [p] ; + y [1][0] = (X [j+1][0] - Lx [p+1] * y [0][0]) / Lx [q] ; + y [1][1] = (X [j+1][1] - Lx [p+1] * y [0][1]) / Lx [q] ; + y [1][2] = (X [j+1][2] - Lx [p+1] * y [0][2]) / Lx [q] ; + y [1][3] = (X [j+1][3] - Lx [p+1] * y [0][3]) / Lx [q] ; + X [j ][0] = y [0][0] ; + X [j ][1] = y [0][1] ; + X [j ][2] = y [0][2] ; + X [j ][3] = y [0][3] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; + X [j+1][2] = y [1][2] ; + X [j+1][3] = y [1][3] ; +#elif defined (LD) + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + y [1][2] = X [j+1][2] - Lx [p+1] * y [0][2] ; + y [1][3] = X [j+1][3] - Lx [p+1] * y [0][3] ; + X [j ][0] = y [0][0] / Lx [p] ; + X [j ][1] = y [0][1] / Lx [p] ; + X [j ][2] = y [0][2] / Lx [p] ; + X [j ][3] = y [0][3] / Lx [p] ; + X [j+1][0] = y [1][0] / Lx [q] ; + X [j+1][1] = y [1][1] / Lx [q] ; + X [j+1][2] = y [1][2] / Lx [q] ; + X [j+1][3] = y [1][3] / Lx [q] ; +#else + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + y [1][2] = X [j+1][2] - Lx [p+1] * y [0][2] ; + y [1][3] = X [j+1][3] - Lx [p+1] * y [0][3] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; + X [j+1][2] = y [1][2] ; + X [j+1][3] = y [1][3] ; +#endif + for (p += 2, q++ ; p < pend ; p++, q++) + { + Int i = Li [p] ; + double lx [2] ; + lx [0] = Lx [p] ; + lx [1] = Lx [q] ; + X [i][0] -= lx [0] * y [0][0] + lx [1] * y [1][0] ; + X [i][1] -= lx [0] * y [0][1] + lx [1] * y [1][1] ; + X [i][2] -= lx [0] * y [0][2] + lx [1] * y [1][2] ; + X [i][3] -= lx [0] * y [0][3] + lx [1] * y [1][3] ; + } + j += 2 ; /* advance to next column of L */ + + } + else + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of three columns of L */ + /* -------------------------------------------------------------- */ + + double y [3][4] ; + Int q = Lp [j+1] ; + Int r = Lp [j+2] ; + y [0][0] = X [j][0] ; + y [0][1] = X [j][1] ; + y [0][2] = X [j][2] ; + y [0][3] = X [j][3] ; +#ifdef LL + y [0][0] /= Lx [p] ; + y [0][1] /= Lx [p] ; + y [0][2] /= Lx [p] ; + y [0][3] /= Lx [p] ; + y [1][0] = (X [j+1][0] - Lx[p+1] * y[0][0]) / Lx [q] ; + y [1][1] = (X [j+1][1] - Lx[p+1] * y[0][1]) / Lx [q] ; + y [1][2] = (X [j+1][2] - Lx[p+1] * y[0][2]) / Lx [q] ; + y [1][3] = (X [j+1][3] - Lx[p+1] * y[0][3]) / Lx [q] ; + y [2][0] = (X [j+2][0] - Lx[p+2] * y[0][0] - Lx[q+1]*y[1][0])/Lx[r]; + y [2][1] = (X [j+2][1] - Lx[p+2] * y[0][1] - Lx[q+1]*y[1][1])/Lx[r]; + y [2][2] = (X [j+2][2] - Lx[p+2] * y[0][2] - Lx[q+1]*y[1][2])/Lx[r]; + y [2][3] = (X [j+2][3] - Lx[p+2] * y[0][3] - Lx[q+1]*y[1][3])/Lx[r]; + X [j ][0] = y [0][0] ; + X [j ][1] = y [0][1] ; + X [j ][2] = y [0][2] ; + X [j ][3] = y [0][3] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; + X [j+1][2] = y [1][2] ; + X [j+1][3] = y [1][3] ; + X [j+2][0] = y [2][0] ; + X [j+2][1] = y [2][1] ; + X [j+2][2] = y [2][2] ; + X [j+2][3] = y [2][3] ; +#elif defined (LD) + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + y [1][2] = X [j+1][2] - Lx [p+1] * y [0][2] ; + y [1][3] = X [j+1][3] - Lx [p+1] * y [0][3] ; + y [2][0] = X [j+2][0] - Lx [p+2] * y [0][0] - Lx [q+1] * y [1][0] ; + y [2][1] = X [j+2][1] - Lx [p+2] * y [0][1] - Lx [q+1] * y [1][1] ; + y [2][2] = X [j+2][2] - Lx [p+2] * y [0][2] - Lx [q+1] * y [1][2] ; + y [2][3] = X [j+2][3] - Lx [p+2] * y [0][3] - Lx [q+1] * y [1][3] ; + X [j ][0] = y [0][0] / Lx [p] ; + X [j ][1] = y [0][1] / Lx [p] ; + X [j ][2] = y [0][2] / Lx [p] ; + X [j ][3] = y [0][3] / Lx [p] ; + X [j+1][0] = y [1][0] / Lx [q] ; + X [j+1][1] = y [1][1] / Lx [q] ; + X [j+1][2] = y [1][2] / Lx [q] ; + X [j+1][3] = y [1][3] / Lx [q] ; + X [j+2][0] = y [2][0] / Lx [r] ; + X [j+2][1] = y [2][1] / Lx [r] ; + X [j+2][2] = y [2][2] / Lx [r] ; + X [j+2][3] = y [2][3] / Lx [r] ; +#else + y [1][0] = X [j+1][0] - Lx [p+1] * y [0][0] ; + y [1][1] = X [j+1][1] - Lx [p+1] * y [0][1] ; + y [1][2] = X [j+1][2] - Lx [p+1] * y [0][2] ; + y [1][3] = X [j+1][3] - Lx [p+1] * y [0][3] ; + y [2][0] = X [j+2][0] - Lx [p+2] * y [0][0] - Lx [q+1] * y [1][0] ; + y [2][1] = X [j+2][1] - Lx [p+2] * y [0][1] - Lx [q+1] * y [1][1] ; + y [2][2] = X [j+2][2] - Lx [p+2] * y [0][2] - Lx [q+1] * y [1][2] ; + y [2][3] = X [j+2][3] - Lx [p+2] * y [0][3] - Lx [q+1] * y [1][3] ; + X [j+1][0] = y [1][0] ; + X [j+1][1] = y [1][1] ; + X [j+1][2] = y [1][2] ; + X [j+1][3] = y [1][3] ; + X [j+2][0] = y [2][0] ; + X [j+2][1] = y [2][1] ; + X [j+2][2] = y [2][2] ; + X [j+2][3] = y [2][3] ; +#endif + for (p += 3, q += 2, r++ ; p < pend ; p++, q++, r++) + { + Int i = Li [p] ; + double lx [3] ; + lx [0] = Lx [p] ; + lx [1] = Lx [q] ; + lx [2] = Lx [r] ; + X [i][0] -= lx[0] * y[0][0] + lx[1] * y[1][0] + lx[2] * y[2][0]; + X [i][1] -= lx[0] * y[0][1] + lx[1] * y[1][1] + lx[2] * y[2][1]; + X [i][2] -= lx[0] * y[0][2] + lx[1] * y[1][2] + lx[2] * y[2][2]; + X [i][3] -= lx[0] * y[0][3] + lx[1] * y[1][3] + lx[2] * y[2][3]; + } + j += 3 ; /* advance to next column of L */ + } + } +} + +#endif + + +/* ========================================================================== */ +/* === LSOLVE (k) =========================================================== */ +/* ========================================================================== */ + +static void LSOLVE (PREFIX,k) +( + cholmod_factor *L, + cholmod_dense *Y, /* nr-by-n where nr is 1 to 4 */ + Int *Yseti, Int ysetlen +) +{ + + double yx [2] ; +#ifdef ZOMPLEX + double yz [1] ; + double *Lz = L->z ; + double *Xz = Y->z ; +#endif + double *Lx = L->x ; + double *Xx = Y->x ; + Int *Li = L->i ; + Int *Lp = L->p ; + Int *Lnz = L->nz ; + Int n = L->n, jj, jjiters ; + + ASSERT (L->xtype == Y->xtype) ; /* L and Y must have the same xtype */ + ASSERT (L->n == Y->ncol) ; /* dimensions must match */ + ASSERT (Y->nrow == Y->d) ; /* leading dimension of Y = # rows of Y */ + ASSERT (L->xtype != CHOLMOD_PATTERN) ; /* L is not symbolic */ + ASSERT (!(L->is_super)) ; /* L is simplicial LL' or LDL' */ + +#ifdef REAL + + if (Yseti == NULL) + { + + /* ------------------------------------------------------------------ */ + /* real case, no Yseti, with 1 to 4 RHS's and dynamic supernodes */ + /* ------------------------------------------------------------------ */ + + ASSERT (Y->nrow <= 4) ; + + switch (Y->nrow) + { + case 1: LSOLVE (PREFIX,1) (L, Y->x) ; break ; + case 2: LSOLVE (PREFIX,2) (L, Y->x) ; break ; + case 3: LSOLVE (PREFIX,3) (L, Y->x) ; break ; + case 4: LSOLVE (PREFIX,4) (L, Y->x) ; break ; + } + + } + else +#endif + { + + /* ------------------------------------------------------------------ */ + /* solve a complex linear system or solve with Yseti */ + /* ------------------------------------------------------------------ */ + + ASSERT (Y->nrow == 1) ; + + jjiters = Yseti ? ysetlen : n ; + + for (jj = 0 ; jj < jjiters ; jj++) + { + Int j = Yseti ? Yseti [jj] : jj ; + + /* get the start, end, and length of column j */ + Int p = Lp [j] ; + Int lnz = Lnz [j] ; + Int pend = p + lnz ; + + /* y = X [j] ; */ + ASSIGN (yx,yz,0, Xx,Xz,j) ; + +#ifdef LL + /* y /= Lx [p] ; */ + /* X [j] = y ; */ + DIV_REAL (yx,yz,0, yx,yz,0, Lx,p) ; + ASSIGN (Xx,Xz,j, yx,yz,0) ; +#elif defined (LD) + /* X [j] = y / Lx [p] ; */ + DIV_REAL (Xx,Xz,j, yx,yz,0, Lx,p) ; +#endif + + for (p++ ; p < pend ; p++) + { + /* X [Li [p]] -= Lx [p] * y ; */ + Int i = Li [p] ; + MULTSUB (Xx,Xz,i, Lx,Lz,p, yx,yz,0) ; + } + } + } +} + +/* prepare for the next inclusion of this file in cholmod_solve.c */ +#undef LL +#undef LD diff --git a/src/CHOLMOD/Cholesky/t_cholmod_ltsolve.c b/src/CHOLMOD/Cholesky/t_cholmod_ltsolve.c new file mode 100644 index 0000000..c04bbbb --- /dev/null +++ b/src/CHOLMOD/Cholesky/t_cholmod_ltsolve.c @@ -0,0 +1,849 @@ +/* ========================================================================== */ +/* === Cholesky/t_cholmod_ltsolve =========================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2013, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Template routine to solve L'x=b with unit or non-unit diagonal, or + * solve DL'x=b. + * + * The numeric xtype of L and Y must match. Y contains b on input and x on + * output, stored in row-form. Y is nrow-by-n, where nrow must equal 1 for the + * complex or zomplex cases, and nrow <= 4 for the real case. + * + * This file is not compiled separately. It is included in t_cholmod_solve.c + * instead. It contains no user-callable routines. + * + * workspace: none + * + * Supports real, complex, and zomplex factors. + */ + +/* undefine all prior definitions */ +#undef FORM_NAME +#undef LSOLVE +#undef DIAG + +/* -------------------------------------------------------------------------- */ +/* define the method */ +/* -------------------------------------------------------------------------- */ + +#ifdef LL +/* LL': solve Lx=b with non-unit diagonal */ +#define FORM_NAME(prefix,rank) prefix ## ll_ltsolve_ ## rank +#define DIAG + +#elif defined (LD) +/* LDL': solve LDx=b */ +#define FORM_NAME(prefix,rank) prefix ## ldl_dltsolve_ ## rank +#define DIAG + +#else +/* LDL': solve Lx=b with unit diagonal */ +#define FORM_NAME(prefix,rank) prefix ## ldl_ltsolve_ ## rank + +#endif + +/* LSOLVE(k) defines the name of a routine for an n-by-k right-hand-side. */ +#define LSOLVE(prefix,rank) FORM_NAME(prefix,rank) + +#ifdef REAL + +/* ========================================================================== */ +/* === LSOLVE (1) =========================================================== */ +/* ========================================================================== */ + +/* Solve L'x=b, where b has 1 column */ + +static void LSOLVE (PREFIX,1) +( + cholmod_factor *L, + double X [ ] /* n-by-1 in row form */ +) +{ + double *Lx = L->x ; + Int *Li = L->i ; + Int *Lp = L->p ; + Int *Lnz = L->nz ; + Int j, n = L->n ; + + for (j = n-1 ; j >= 0 ; ) + { + /* get the start, end, and length of column j */ + Int p = Lp [j] ; + Int lnz = Lnz [j] ; + Int pend = p + lnz ; + + /* find a chain of supernodes (up to j, j-1, and j-2) */ + if (j < 4 || lnz != Lnz [j-1] - 1 || Li [Lp [j-1]+1] != j) + { + + /* -------------------------------------------------------------- */ + /* solve with a single column of L */ + /* -------------------------------------------------------------- */ + + double y = X [j] ; +#ifdef DIAG + double d = Lx [p] ; +#endif +#ifdef LD + y /= d ; +#endif + for (p++ ; p < pend ; p++) + { + y -= Lx [p] * X [Li [p]] ; + } +#ifdef LL + X [j] = y / d ; +#else + X [j] = y ; +#endif + j-- ; /* advance to the next column of L */ + + } + else if (lnz != Lnz [j-2]-2 || Li [Lp [j-2]+2] != j) + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of two columns of L */ + /* -------------------------------------------------------------- */ + + double y [2], t ; + Int q = Lp [j-1] ; +#ifdef DIAG + double d [2] ; + d [0] = Lx [p] ; + d [1] = Lx [q] ; +#endif + t = Lx [q+1] ; +#ifdef LD + y [0] = X [j ] / d [0] ; + y [1] = X [j-1] / d [1] ; +#else + y [0] = X [j ] ; + y [1] = X [j-1] ; +#endif + for (p++, q += 2 ; p < pend ; p++, q++) + { + Int i = Li [p] ; + y [0] -= Lx [p] * X [i] ; + y [1] -= Lx [q] * X [i] ; + } +#ifdef LL + y [0] /= d [0] ; + y [1] = (y [1] - t * y [0]) / d [1] ; +#else + y [1] -= t * y [0] ; +#endif + X [j ] = y [0] ; + X [j-1] = y [1] ; + j -= 2 ; /* advance to the next column of L */ + + } + else + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of three columns of L */ + /* -------------------------------------------------------------- */ + + double y [3], t [3] ; + Int q = Lp [j-1] ; + Int r = Lp [j-2] ; +#ifdef DIAG + double d [3] ; + d [0] = Lx [p] ; + d [1] = Lx [q] ; + d [2] = Lx [r] ; +#endif + t [0] = Lx [q+1] ; + t [1] = Lx [r+1] ; + t [2] = Lx [r+2] ; +#ifdef LD + y [0] = X [j] / d [0] ; + y [1] = X [j-1] / d [1] ; + y [2] = X [j-2] / d [2] ; +#else + y [0] = X [j] ; + y [1] = X [j-1] ; + y [2] = X [j-2] ; +#endif + for (p++, q += 2, r += 3 ; p < pend ; p++, q++, r++) + { + Int i = Li [p] ; + y [0] -= Lx [p] * X [i] ; + y [1] -= Lx [q] * X [i] ; + y [2] -= Lx [r] * X [i] ; + } +#ifdef LL + y [0] /= d [0] ; + y [1] = (y [1] - t [0] * y [0]) / d [1] ; + y [2] = (y [2] - t [2] * y [0] - t [1] * y [1]) / d [2] ; +#else + y [1] -= t [0] * y [0] ; + y [2] -= t [2] * y [0] + t [1] * y [1] ; +#endif + X [j-2] = y [2] ; + X [j-1] = y [1] ; + X [j ] = y [0] ; + j -= 3 ; /* advance to the next column of L */ + } + } +} + + +/* ========================================================================== */ +/* === LSOLVE (2) =========================================================== */ +/* ========================================================================== */ + +/* Solve L'x=b, where b has 2 columns */ + +static void LSOLVE (PREFIX,2) +( + cholmod_factor *L, + double X [ ][2] /* n-by-2 in row form */ +) +{ + double *Lx = L->x ; + Int *Li = L->i ; + Int *Lp = L->p ; + Int *Lnz = L->nz ; + Int j, n = L->n ; + + for (j = n-1 ; j >= 0 ; ) + { + /* get the start, end, and length of column j */ + Int p = Lp [j] ; + Int lnz = Lnz [j] ; + Int pend = p + lnz ; + + /* find a chain of supernodes (up to j, j-1, and j-2) */ + if (j < 4 || lnz != Lnz [j-1] - 1 || Li [Lp [j-1]+1] != j) + { + + /* -------------------------------------------------------------- */ + /* solve with a single column of L */ + /* -------------------------------------------------------------- */ + + double y [2] ; +#ifdef DIAG + double d = Lx [p] ; +#endif +#ifdef LD + y [0] = X [j][0] / d ; + y [1] = X [j][1] / d ; +#else + y [0] = X [j][0] ; + y [1] = X [j][1] ; +#endif + for (p++ ; p < pend ; p++) + { + Int i = Li [p] ; + y [0] -= Lx [p] * X [i][0] ; + y [1] -= Lx [p] * X [i][1] ; + } +#ifdef LL + X [j][0] = y [0] / d ; + X [j][1] = y [1] / d ; +#else + X [j][0] = y [0] ; + X [j][1] = y [1] ; +#endif + j-- ; /* advance to the next column of L */ + + } + else if (lnz != Lnz [j-2]-2 || Li [Lp [j-2]+2] != j) + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of two columns of L */ + /* -------------------------------------------------------------- */ + + double y [2][2], t ; + Int q = Lp [j-1] ; +#ifdef DIAG + double d [2] ; + d [0] = Lx [p] ; + d [1] = Lx [q] ; +#endif + t = Lx [q+1] ; +#ifdef LD + y [0][0] = X [j ][0] / d [0] ; + y [0][1] = X [j ][1] / d [0] ; + y [1][0] = X [j-1][0] / d [1] ; + y [1][1] = X [j-1][1] / d [1] ; +#else + y [0][0] = X [j ][0] ; + y [0][1] = X [j ][1] ; + y [1][0] = X [j-1][0] ; + y [1][1] = X [j-1][1] ; +#endif + for (p++, q += 2 ; p < pend ; p++, q++) + { + Int i = Li [p] ; + y [0][0] -= Lx [p] * X [i][0] ; + y [0][1] -= Lx [p] * X [i][1] ; + y [1][0] -= Lx [q] * X [i][0] ; + y [1][1] -= Lx [q] * X [i][1] ; + } +#ifdef LL + y [0][0] /= d [0] ; + y [0][1] /= d [0] ; + y [1][0] = (y [1][0] - t * y [0][0]) / d [1] ; + y [1][1] = (y [1][1] - t * y [0][1]) / d [1] ; +#else + y [1][0] -= t * y [0][0] ; + y [1][1] -= t * y [0][1] ; +#endif + X [j ][0] = y [0][0] ; + X [j ][1] = y [0][1] ; + X [j-1][0] = y [1][0] ; + X [j-1][1] = y [1][1] ; + j -= 2 ; /* advance to the next column of L */ + + } + else + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of three columns of L */ + /* -------------------------------------------------------------- */ + + double y [3][2], t [3] ; + Int q = Lp [j-1] ; + Int r = Lp [j-2] ; +#ifdef DIAG + double d [3] ; + d [0] = Lx [p] ; + d [1] = Lx [q] ; + d [2] = Lx [r] ; +#endif + t [0] = Lx [q+1] ; + t [1] = Lx [r+1] ; + t [2] = Lx [r+2] ; +#ifdef LD + y [0][0] = X [j ][0] / d [0] ; + y [0][1] = X [j ][1] / d [0] ; + y [1][0] = X [j-1][0] / d [1] ; + y [1][1] = X [j-1][1] / d [1] ; + y [2][0] = X [j-2][0] / d [2] ; + y [2][1] = X [j-2][1] / d [2] ; +#else + y [0][0] = X [j ][0] ; + y [0][1] = X [j ][1] ; + y [1][0] = X [j-1][0] ; + y [1][1] = X [j-1][1] ; + y [2][0] = X [j-2][0] ; + y [2][1] = X [j-2][1] ; +#endif + for (p++, q += 2, r += 3 ; p < pend ; p++, q++, r++) + { + Int i = Li [p] ; + y [0][0] -= Lx [p] * X [i][0] ; + y [0][1] -= Lx [p] * X [i][1] ; + y [1][0] -= Lx [q] * X [i][0] ; + y [1][1] -= Lx [q] * X [i][1] ; + y [2][0] -= Lx [r] * X [i][0] ; + y [2][1] -= Lx [r] * X [i][1] ; + } +#ifdef LL + y [0][0] /= d [0] ; + y [0][1] /= d [0] ; + y [1][0] = (y [1][0] - t [0] * y [0][0]) / d [1] ; + y [1][1] = (y [1][1] - t [0] * y [0][1]) / d [1] ; + y [2][0] = (y [2][0] - t [2] * y [0][0] - t [1] * y [1][0]) / d [2]; + y [2][1] = (y [2][1] - t [2] * y [0][1] - t [1] * y [1][1]) / d [2]; +#else + y [1][0] -= t [0] * y [0][0] ; + y [1][1] -= t [0] * y [0][1] ; + y [2][0] -= t [2] * y [0][0] + t [1] * y [1][0] ; + y [2][1] -= t [2] * y [0][1] + t [1] * y [1][1] ; +#endif + X [j ][0] = y [0][0] ; + X [j ][1] = y [0][1] ; + X [j-1][0] = y [1][0] ; + X [j-1][1] = y [1][1] ; + X [j-2][0] = y [2][0] ; + X [j-2][1] = y [2][1] ; + j -= 3 ; /* advance to the next column of L */ + } + } +} + + +/* ========================================================================== */ +/* === LSOLVE (3) =========================================================== */ +/* ========================================================================== */ + +/* Solve L'x=b, where b has 3 columns */ + +static void LSOLVE (PREFIX,3) +( + cholmod_factor *L, + double X [ ][3] /* n-by-3 in row form */ +) +{ + double *Lx = L->x ; + Int *Li = L->i ; + Int *Lp = L->p ; + Int *Lnz = L->nz ; + Int j, n = L->n ; + + for (j = n-1 ; j >= 0 ; ) + { + /* get the start, end, and length of column j */ + Int p = Lp [j] ; + Int lnz = Lnz [j] ; + Int pend = p + lnz ; + + /* find a chain of supernodes (up to j, j-1, and j-2) */ + if (j < 4 || lnz != Lnz [j-1] - 1 || Li [Lp [j-1]+1] != j) + { + + /* -------------------------------------------------------------- */ + /* solve with a single column of L */ + /* -------------------------------------------------------------- */ + + double y [3] ; +#ifdef DIAG + double d = Lx [p] ; +#endif +#ifdef LD + y [0] = X [j][0] / d ; + y [1] = X [j][1] / d ; + y [2] = X [j][2] / d ; +#else + y [0] = X [j][0] ; + y [1] = X [j][1] ; + y [2] = X [j][2] ; +#endif + for (p++ ; p < pend ; p++) + { + Int i = Li [p] ; + y [0] -= Lx [p] * X [i][0] ; + y [1] -= Lx [p] * X [i][1] ; + y [2] -= Lx [p] * X [i][2] ; + } +#ifdef LL + X [j][0] = y [0] / d ; + X [j][1] = y [1] / d ; + X [j][2] = y [2] / d ; +#else + X [j][0] = y [0] ; + X [j][1] = y [1] ; + X [j][2] = y [2] ; +#endif + j-- ; /* advance to the next column of L */ + + } + else if (lnz != Lnz [j-2]-2 || Li [Lp [j-2]+2] != j) + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of two columns of L */ + /* -------------------------------------------------------------- */ + + double y [2][3], t ; + Int q = Lp [j-1] ; +#ifdef DIAG + double d [2] ; + d [0] = Lx [p] ; + d [1] = Lx [q] ; +#endif + t = Lx [q+1] ; +#ifdef LD + y [0][0] = X [j ][0] / d [0] ; + y [0][1] = X [j ][1] / d [0] ; + y [0][2] = X [j ][2] / d [0] ; + y [1][0] = X [j-1][0] / d [1] ; + y [1][1] = X [j-1][1] / d [1] ; + y [1][2] = X [j-1][2] / d [1] ; +#else + y [0][0] = X [j ][0] ; + y [0][1] = X [j ][1] ; + y [0][2] = X [j ][2] ; + y [1][0] = X [j-1][0] ; + y [1][1] = X [j-1][1] ; + y [1][2] = X [j-1][2] ; +#endif + for (p++, q += 2 ; p < pend ; p++, q++) + { + Int i = Li [p] ; + y [0][0] -= Lx [p] * X [i][0] ; + y [0][1] -= Lx [p] * X [i][1] ; + y [0][2] -= Lx [p] * X [i][2] ; + y [1][0] -= Lx [q] * X [i][0] ; + y [1][1] -= Lx [q] * X [i][1] ; + y [1][2] -= Lx [q] * X [i][2] ; + } +#ifdef LL + y [0][0] /= d [0] ; + y [0][1] /= d [0] ; + y [0][2] /= d [0] ; + y [1][0] = (y [1][0] - t * y [0][0]) / d [1] ; + y [1][1] = (y [1][1] - t * y [0][1]) / d [1] ; + y [1][2] = (y [1][2] - t * y [0][2]) / d [1] ; +#else + y [1][0] -= t * y [0][0] ; + y [1][1] -= t * y [0][1] ; + y [1][2] -= t * y [0][2] ; +#endif + X [j ][0] = y [0][0] ; + X [j ][1] = y [0][1] ; + X [j ][2] = y [0][2] ; + X [j-1][0] = y [1][0] ; + X [j-1][1] = y [1][1] ; + X [j-1][2] = y [1][2] ; + j -= 2 ; /* advance to the next column of L */ + + } + else + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of three columns of L */ + /* -------------------------------------------------------------- */ + + double y [3][3], t [3] ; + Int q = Lp [j-1] ; + Int r = Lp [j-2] ; +#ifdef DIAG + double d [3] ; + d [0] = Lx [p] ; + d [1] = Lx [q] ; + d [2] = Lx [r] ; +#endif + t [0] = Lx [q+1] ; + t [1] = Lx [r+1] ; + t [2] = Lx [r+2] ; +#ifdef LD + y [0][0] = X [j ][0] / d [0] ; + y [0][1] = X [j ][1] / d [0] ; + y [0][2] = X [j ][2] / d [0] ; + y [1][0] = X [j-1][0] / d [1] ; + y [1][1] = X [j-1][1] / d [1] ; + y [1][2] = X [j-1][2] / d [1] ; + y [2][0] = X [j-2][0] / d [2] ; + y [2][1] = X [j-2][1] / d [2] ; + y [2][2] = X [j-2][2] / d [2] ; +#else + y [0][0] = X [j ][0] ; + y [0][1] = X [j ][1] ; + y [0][2] = X [j ][2] ; + y [1][0] = X [j-1][0] ; + y [1][1] = X [j-1][1] ; + y [1][2] = X [j-1][2] ; + y [2][0] = X [j-2][0] ; + y [2][1] = X [j-2][1] ; + y [2][2] = X [j-2][2] ; +#endif + for (p++, q += 2, r += 3 ; p < pend ; p++, q++, r++) + { + Int i = Li [p] ; + y [0][0] -= Lx [p] * X [i][0] ; + y [0][1] -= Lx [p] * X [i][1] ; + y [0][2] -= Lx [p] * X [i][2] ; + y [1][0] -= Lx [q] * X [i][0] ; + y [1][1] -= Lx [q] * X [i][1] ; + y [1][2] -= Lx [q] * X [i][2] ; + y [2][0] -= Lx [r] * X [i][0] ; + y [2][1] -= Lx [r] * X [i][1] ; + y [2][2] -= Lx [r] * X [i][2] ; + } +#ifdef LL + y [0][0] /= d [0] ; + y [0][1] /= d [0] ; + y [0][2] /= d [0] ; + y [1][0] = (y [1][0] - t [0] * y [0][0]) / d [1] ; + y [1][1] = (y [1][1] - t [0] * y [0][1]) / d [1] ; + y [1][2] = (y [1][2] - t [0] * y [0][2]) / d [1] ; + y [2][0] = (y [2][0] - t [2] * y [0][0] - t [1] * y [1][0]) / d [2]; + y [2][1] = (y [2][1] - t [2] * y [0][1] - t [1] * y [1][1]) / d [2]; + y [2][2] = (y [2][2] - t [2] * y [0][2] - t [1] * y [1][2]) / d [2]; +#else + y [1][0] -= t [0] * y [0][0] ; + y [1][1] -= t [0] * y [0][1] ; + y [1][2] -= t [0] * y [0][2] ; + y [2][0] -= t [2] * y [0][0] + t [1] * y [1][0] ; + y [2][1] -= t [2] * y [0][1] + t [1] * y [1][1] ; + y [2][2] -= t [2] * y [0][2] + t [1] * y [1][2] ; +#endif + X [j ][0] = y [0][0] ; + X [j ][1] = y [0][1] ; + X [j ][2] = y [0][2] ; + X [j-1][0] = y [1][0] ; + X [j-1][1] = y [1][1] ; + X [j-1][2] = y [1][2] ; + X [j-2][0] = y [2][0] ; + X [j-2][1] = y [2][1] ; + X [j-2][2] = y [2][2] ; + j -= 3 ; /* advance to the next column of L */ + } + } +} + + +/* ========================================================================== */ +/* === LSOLVE (4) =========================================================== */ +/* ========================================================================== */ + +/* Solve L'x=b, where b has 4 columns */ + +static void LSOLVE (PREFIX,4) +( + cholmod_factor *L, + double X [ ][4] /* n-by-4 in row form */ +) +{ + double *Lx = L->x ; + Int *Li = L->i ; + Int *Lp = L->p ; + Int *Lnz = L->nz ; + Int j, n = L->n ; + + for (j = n-1 ; j >= 0 ; ) + { + /* get the start, end, and length of column j */ + Int p = Lp [j] ; + Int lnz = Lnz [j] ; + Int pend = p + lnz ; + + /* find a chain of supernodes (up to j, j-1, and j-2) */ + if (j < 4 || lnz != Lnz [j-1] - 1 || Li [Lp [j-1]+1] != j) + { + + /* -------------------------------------------------------------- */ + /* solve with a single column of L */ + /* -------------------------------------------------------------- */ + + double y [4] ; +#ifdef DIAG + double d = Lx [p] ; +#endif +#ifdef LD + y [0] = X [j][0] / d ; + y [1] = X [j][1] / d ; + y [2] = X [j][2] / d ; + y [3] = X [j][3] / d ; +#else + y [0] = X [j][0] ; + y [1] = X [j][1] ; + y [2] = X [j][2] ; + y [3] = X [j][3] ; +#endif + for (p++ ; p < pend ; p++) + { + Int i = Li [p] ; + y [0] -= Lx [p] * X [i][0] ; + y [1] -= Lx [p] * X [i][1] ; + y [2] -= Lx [p] * X [i][2] ; + y [3] -= Lx [p] * X [i][3] ; + } +#ifdef LL + X [j][0] = y [0] / d ; + X [j][1] = y [1] / d ; + X [j][2] = y [2] / d ; + X [j][3] = y [3] / d ; +#else + X [j][0] = y [0] ; + X [j][1] = y [1] ; + X [j][2] = y [2] ; + X [j][3] = y [3] ; +#endif + j-- ; /* advance to the next column of L */ + + } + else /* if (j == 1 || lnz != Lnz [j-2]-2 || Li [Lp [j-2]+2] != j) */ + { + + /* -------------------------------------------------------------- */ + /* solve with a supernode of two columns of L */ + /* -------------------------------------------------------------- */ + + double y [2][4], t ; + Int q = Lp [j-1] ; +#ifdef DIAG + double d [2] ; + d [0] = Lx [p] ; + d [1] = Lx [q] ; +#endif + t = Lx [q+1] ; +#ifdef LD + y [0][0] = X [j ][0] / d [0] ; + y [0][1] = X [j ][1] / d [0] ; + y [0][2] = X [j ][2] / d [0] ; + y [0][3] = X [j ][3] / d [0] ; + y [1][0] = X [j-1][0] / d [1] ; + y [1][1] = X [j-1][1] / d [1] ; + y [1][2] = X [j-1][2] / d [1] ; + y [1][3] = X [j-1][3] / d [1] ; +#else + y [0][0] = X [j ][0] ; + y [0][1] = X [j ][1] ; + y [0][2] = X [j ][2] ; + y [0][3] = X [j ][3] ; + y [1][0] = X [j-1][0] ; + y [1][1] = X [j-1][1] ; + y [1][2] = X [j-1][2] ; + y [1][3] = X [j-1][3] ; +#endif + for (p++, q += 2 ; p < pend ; p++, q++) + { + Int i = Li [p] ; + y [0][0] -= Lx [p] * X [i][0] ; + y [0][1] -= Lx [p] * X [i][1] ; + y [0][2] -= Lx [p] * X [i][2] ; + y [0][3] -= Lx [p] * X [i][3] ; + y [1][0] -= Lx [q] * X [i][0] ; + y [1][1] -= Lx [q] * X [i][1] ; + y [1][2] -= Lx [q] * X [i][2] ; + y [1][3] -= Lx [q] * X [i][3] ; + } +#ifdef LL + y [0][0] /= d [0] ; + y [0][1] /= d [0] ; + y [0][2] /= d [0] ; + y [0][3] /= d [0] ; + y [1][0] = (y [1][0] - t * y [0][0]) / d [1] ; + y [1][1] = (y [1][1] - t * y [0][1]) / d [1] ; + y [1][2] = (y [1][2] - t * y [0][2]) / d [1] ; + y [1][3] = (y [1][3] - t * y [0][3]) / d [1] ; +#else + y [1][0] -= t * y [0][0] ; + y [1][1] -= t * y [0][1] ; + y [1][2] -= t * y [0][2] ; + y [1][3] -= t * y [0][3] ; +#endif + X [j ][0] = y [0][0] ; + X [j ][1] = y [0][1] ; + X [j ][2] = y [0][2] ; + X [j ][3] = y [0][3] ; + X [j-1][0] = y [1][0] ; + X [j-1][1] = y [1][1] ; + X [j-1][2] = y [1][2] ; + X [j-1][3] = y [1][3] ; + j -= 2 ; /* advance to the next column of L */ + } + + /* NOTE: with 4 right-hand-sides, it suffices to exploit dynamic + * supernodes of just size 1 and 2. 3-column supernodes are not + * needed. */ + } +} + +#endif + +/* ========================================================================== */ +/* === LSOLVE (k) =========================================================== */ +/* ========================================================================== */ + +static void LSOLVE (PREFIX,k) +( + cholmod_factor *L, + cholmod_dense *Y, /* nr-by-n where nr is 1 to 4 */ + Int *Yseti, Int ysetlen +) +{ + +#ifdef DIAG + double d [1] ; +#endif + double yx [2] ; +#ifdef ZOMPLEX + double yz [1] ; + double *Lz = L->z ; + double *Xz = Y->z ; +#endif + double *Lx = L->x ; + double *Xx = Y->x ; + Int *Li = L->i ; + Int *Lp = L->p ; + Int *Lnz = L->nz ; + Int n = L->n, jj, jjiters ; + + ASSERT (L->xtype == Y->xtype) ; /* L and Y must have the same xtype */ + ASSERT (L->n == Y->ncol) ; /* dimensions must match */ + ASSERT (Y->nrow == Y->d) ; /* leading dimension of Y = # rows of Y */ + ASSERT (L->xtype != CHOLMOD_PATTERN) ; /* L is not symbolic */ + ASSERT (!(L->is_super)) ; /* L is simplicial LL' or LDL' */ + +#ifdef REAL + + if (Yseti == NULL) + { + + /* ------------------------------------------------------------------ */ + /* real case, no Yseti, with 1 to 4 RHS's and dynamic supernodes */ + /* ------------------------------------------------------------------ */ + + ASSERT (Y->nrow <= 4) ; + switch (Y->nrow) + { + case 1: LSOLVE (PREFIX,1) (L, Y->x) ; break ; + case 2: LSOLVE (PREFIX,2) (L, Y->x) ; break ; + case 3: LSOLVE (PREFIX,3) (L, Y->x) ; break ; + case 4: LSOLVE (PREFIX,4) (L, Y->x) ; break ; + } + + } + else +#endif + { + + /* ------------------------------------------------------------------ */ + /* solve a complex linear system or solve with Yseti */ + /* ------------------------------------------------------------------ */ + + ASSERT (Y->nrow == 1) ; + + jjiters = Yseti ? ysetlen : n ; + + for (jj = jjiters-1 ; jj >= 0 ; jj--) + { + + Int j = Yseti ? Yseti [jj] : jj ; + + /* get the start, end, and length of column j */ + Int p = Lp [j] ; + Int lnz = Lnz [j] ; + Int pend = p + lnz ; + + /* y = X [j] ; */ + ASSIGN (yx,yz,0, Xx,Xz,j) ; + +#ifdef DIAG + /* d = Lx [p] ; */ + ASSIGN_REAL (d,0, Lx,p) ; +#endif +#ifdef LD + /* y /= d ; */ + DIV_REAL (yx,yz,0, yx,yz,0, d,0) ; +#endif + + for (p++ ; p < pend ; p++) + { + /* y -= conj (Lx [p]) * X [Li [p]] ; */ + Int i = Li [p] ; + MULTSUBCONJ (yx,yz,0, Lx,Lz,p, Xx,Xz,i) ; + } + +#ifdef LL + /* X [j] = y / d ; */ + DIV_REAL (Xx,Xz,j, yx,yz,0, d,0) ; +#else + /* X [j] = y ; */ + ASSIGN (Xx,Xz,j, yx,yz,0) ; +#endif + + } + } +} + +/* prepare for the next inclusion of this file in cholmod_solve.c */ +#undef LL +#undef LD diff --git a/src/CHOLMOD/Cholesky/t_cholmod_rowfac.c b/src/CHOLMOD/Cholesky/t_cholmod_rowfac.c new file mode 100644 index 0000000..c7cc49b --- /dev/null +++ b/src/CHOLMOD/Cholesky/t_cholmod_rowfac.c @@ -0,0 +1,457 @@ +/* ========================================================================== */ +/* === Cholesky/t_cholmod_rowfac ============================================ */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Template routine for cholmod_rowfac. Supports any numeric xtype + * (real, complex, or zomplex). + * + * workspace: Iwork (n), Flag (n), Xwork (n if real, 2*n if complex) + */ + +#include "cholmod_template.h" + +#ifdef MASK +static int TEMPLATE (cholmod_rowfac_mask) +#else +static int TEMPLATE (cholmod_rowfac) +#endif +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + cholmod_sparse *F, /* used for A*A' case only. F=A' or A(:,f)' */ + double beta [2], /* factorize beta*I+A or beta*I+AA' (beta [0] only) */ + size_t kstart, /* first row to factorize */ + size_t kend, /* last row to factorize is kend-1 */ +#ifdef MASK + /* These inputs are used for cholmod_rowfac_mask only */ + Int *mask, /* size A->nrow. if mask[i] then W(i) is set to zero */ + Int *RLinkUp, /* size A->nrow. link list of rows to compute */ +#endif + /* ---- in/out --- */ + cholmod_factor *L, + /* --------------- */ + cholmod_common *Common +) +{ + double yx [2], lx [2], fx [2], dk [1], di [1], fl = 0 ; +#ifdef ZOMPLEX + double yz [1], lz [1], fz [1] ; +#endif + double *Ax, *Az, *Lx, *Lz, *Wx, *Wz, *Fx, *Fz ; + Int *Ap, *Anz, *Ai, *Lp, *Lnz, *Li, *Lnext, *Flag, *Stack, *Fp, *Fi, *Fnz, + *Iwork ; + Int i, p, k, t, pf, pfend, top, s, mark, pend, n, lnz, is_ll, multadds, + use_dbound, packed, stype, Fpacked, sorted, nzmax, len, parent ; +#ifndef REAL + Int dk_imaginary ; +#endif + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + PRINT1 (("\nin cholmod_rowfac, kstart %d kend %d stype %d\n", + kstart, kend, A->stype)) ; + DEBUG (CHOLMOD(dump_factor) (L, "Initial L", Common)) ; + + n = A->nrow ; + stype = A->stype ; + + if (stype > 0) + { + /* symmetric upper case: F is not needed. It may be NULL */ + Fp = NULL ; + Fi = NULL ; + Fx = NULL ; + Fz = NULL ; + Fnz = NULL ; + Fpacked = TRUE ; + } + else + { + /* unsymmetric case: F is required. */ + Fp = F->p ; + Fi = F->i ; + Fx = F->x ; + Fz = F->z ; + Fnz = F->nz ; + Fpacked = F->packed ; + } + + Ap = A->p ; /* size A->ncol+1, column pointers of A */ + Ai = A->i ; /* size nz = Ap [A->ncol], row indices of A */ + Ax = A->x ; /* size nz, numeric values of A */ + Az = A->z ; + Anz = A->nz ; + packed = A->packed ; + sorted = A->sorted ; + + use_dbound = IS_GT_ZERO (Common->dbound) ; + + /* get the current factors L (and D for LDL'); allocate space if needed */ + is_ll = L->is_ll ; + if (L->xtype == CHOLMOD_PATTERN) + { + /* ------------------------------------------------------------------ */ + /* L is symbolic only; allocate and initialize L (and D for LDL') */ + /* ------------------------------------------------------------------ */ + + /* workspace: none */ + CHOLMOD(change_factor) (A->xtype, is_ll, FALSE, FALSE, TRUE, L, Common); + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (FALSE) ; + } + ASSERT (L->minor == (size_t) n) ; + } + else if (kstart == 0 && kend == (size_t) n) + { + /* ------------------------------------------------------------------ */ + /* refactorization; reset L->nz and L->minor to restart factorization */ + /* ------------------------------------------------------------------ */ + + L->minor = n ; + Lnz = L->nz ; + for (k = 0 ; k < n ; k++) + { + Lnz [k] = 1 ; + } + } + + ASSERT (is_ll == L->is_ll) ; + ASSERT (L->xtype != CHOLMOD_PATTERN) ; + DEBUG (CHOLMOD(dump_factor) (L, "L ready", Common)) ; + DEBUG (CHOLMOD(dump_sparse) (A, "A ready", Common)) ; + DEBUG (if (stype == 0) CHOLMOD(dump_sparse) (F, "F ready", Common)) ; + + /* inputs, can be modified on output: */ + Lp = L->p ; /* size n+1 */ + ASSERT (Lp != NULL) ; + + /* outputs, contents defined on input for incremental case only: */ + Lnz = L->nz ; /* size n */ + Lnext = L->next ; /* size n+2 */ + Li = L->i ; /* size L->nzmax, can change in size */ + Lx = L->x ; /* size L->nzmax or 2*L->nzmax, can change in size */ + Lz = L->z ; /* size L->nzmax for zomplex case, can change in size */ + nzmax = L->nzmax ; + ASSERT (Lnz != NULL && Li != NULL && Lx != NULL) ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Iwork = Common->Iwork ; + Stack = Iwork ; /* size n (i/i/l) */ + Flag = Common->Flag ; /* size n, Flag [i] < mark must hold */ + Wx = Common->Xwork ; /* size n if real, 2*n if complex or + * zomplex. Xwork [i] == 0 must hold. */ + Wz = Wx + n ; /* size n for zomplex case only */ + mark = Common->mark ; + ASSERT ((Int) Common->xworksize >= (L->xtype == CHOLMOD_REAL ? 1:2)*n) ; + + /* ---------------------------------------------------------------------- */ + /* compute LDL' or LL' factorization by rows */ + /* ---------------------------------------------------------------------- */ + +#ifdef MASK +#define NEXT(k) k = RLinkUp [k] +#else +#define NEXT(k) k++ +#endif + + for (k = kstart ; k < ((Int) kend) ; NEXT(k)) + { + PRINT1 (("\n===============K "ID" Lnz [k] "ID"\n", k, Lnz [k])) ; + + /* ------------------------------------------------------------------ */ + /* compute pattern of kth row of L and scatter kth input column */ + /* ------------------------------------------------------------------ */ + + /* column k of L is currently empty */ + ASSERT (Lnz [k] == 1) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 2*n, Common)) ; + + top = n ; /* Stack is empty */ + Flag [k] = mark ; /* do not include diagonal entry in Stack */ + + /* use Li [Lp [i]+1] for etree */ +#define PARENT(i) (Lnz [i] > 1) ? (Li [Lp [i] + 1]) : EMPTY + + if (stype > 0) + { + /* scatter kth col of triu (beta*I+AA'), get pattern L(k,:) */ + p = Ap [k] ; + pend = (packed) ? (Ap [k+1]) : (p + Anz [k]) ; + /* W [i] = Ax [i] ; scatter column of A */ +#define SCATTER ASSIGN(Wx,Wz,i, Ax,Az,p) + SUBTREE ; +#undef SCATTER + } + else + { + /* scatter kth col of triu (beta*I+AA'), get pattern L(k,:) */ + pf = Fp [k] ; + pfend = (Fpacked) ? (Fp [k+1]) : (pf + Fnz [k]) ; + for ( ; pf < pfend ; pf++) + { + /* get nonzero entry F (t,k) */ + t = Fi [pf] ; + /* fk = Fx [pf] */ + ASSIGN (fx, fz, 0, Fx, Fz, pf) ; + p = Ap [t] ; + pend = (packed) ? (Ap [t+1]) : (p + Anz [t]) ; + multadds = 0 ; + /* W [i] += Ax [p] * fx ; scatter column of A*A' */ +#define SCATTER MULTADD (Wx,Wz,i, Ax,Az,p, fx,fz,0) ; multadds++ ; + SUBTREE ; +#undef SCATTER +#ifdef REAL + fl += 2 * ((double) multadds) ; +#else + fl += 8 * ((double) multadds) ; +#endif + } + } + +#undef PARENT + + /* ------------------------------------------------------------------ */ + /* if mask is present, set the corresponding entries in W to zero */ + /* ------------------------------------------------------------------ */ + +#ifdef MASK + /* remove the dead element of Wx */ + if (mask != NULL) + { + +#if 0 + /* older version */ + for (p = n; p > top;) + { + i = Stack [--p] ; + if ( mask [i] >= 0 ) + { + CLEAR (Wx,Wz,i) ; /* set W(i) to zero */ + } + } +#endif + + for (s = top ; s < n ; s++) + { + i = Stack [s] ; + if (mask [i] >= 0) + { + CLEAR (Wx,Wz,i) ; /* set W(i) to zero */ + } + } + + } +#endif + + /* nonzero pattern of kth row of L is now in Stack [top..n-1]. + * Flag [Stack [top..n-1]] is equal to mark, but no longer needed */ + + /* mark = CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + mark = Common->mark ; + + /* ------------------------------------------------------------------ */ + /* compute kth row of L and store in column form */ + /* ------------------------------------------------------------------ */ + + /* Solve L (0:k-1, 0:k-1) * y (0:k-1) = b (0:k-1) where + * b (0:k) = A (0:k,k) or A(0:k,:) * F(:,k) is in W and Stack. + * + * For LDL' factorization: + * L (k, 0:k-1) = y (0:k-1) ./ D (0:k-1) + * D (k) = b (k) - L (k, 0:k-1) * y (0:k-1) + * + * For LL' factorization: + * L (k, 0:k-1) = y (0:k-1) + * L (k,k) = sqrt (b (k) - L (k, 0:k-1) * L (0:k-1, k)) + */ + + /* dk = W [k] + beta */ + ADD_REAL (dk,0, Wx,k, beta,0) ; + +#ifndef REAL + /* In the unsymmetric case, the imaginary part of W[k] must be real, + * since F is assumed to be the complex conjugate transpose of A. In + * the symmetric case, W[k] is the diagonal of A. If the imaginary part + * of W[k] is nonzero, then the Cholesky factorization cannot be + * computed; A is not positive definite */ + dk_imaginary = (stype > 0) ? (IMAG_IS_NONZERO (Wx,Wz,k)) : FALSE ; +#endif + + /* W [k] = 0.0 ; */ + CLEAR (Wx,Wz,k) ; + + for (s = top ; s < n ; s++) + { + /* get i for each nonzero entry L(k,i) */ + i = Stack [s] ; + + /* y = W [i] ; */ + ASSIGN (yx,yz,0, Wx,Wz,i) ; + + /* W [i] = 0.0 ; */ + CLEAR (Wx,Wz,i) ; + + lnz = Lnz [i] ; + p = Lp [i] ; + ASSERT (lnz > 0 && Li [p] == i) ; + pend = p + lnz ; + + /* di = Lx [p] ; the diagonal entry L or D(i,i), which is real */ + ASSIGN_REAL (di,0, Lx,p) ; + + if (i >= (Int) L->minor || IS_ZERO (di [0])) + { + /* For the LL' factorization, L(i,i) is zero. For the LDL', + * D(i,i) is zero. Skip column i of L, and set L(k,i) = 0. */ + CLEAR (lx,lz,0) ; + p = pend ; + } + else if (is_ll) + { +#ifdef REAL + fl += 2 * ((double) (pend - p - 1)) + 3 ; +#else + fl += 8 * ((double) (pend - p - 1)) + 6 ; +#endif + /* forward solve using L (i:(k-1),i) */ + /* divide by L(i,i), which must be real and nonzero */ + /* y /= di [0] */ + DIV_REAL (yx,yz,0, yx,yz,0, di,0) ; + for (p++ ; p < pend ; p++) + { + /* W [Li [p]] -= Lx [p] * y ; */ + MULTSUB (Wx,Wz,Li[p], Lx,Lz,p, yx,yz,0) ; + } + /* do not scale L; compute dot product for L(k,k) */ + /* L(k,i) = conj(y) ; */ + ASSIGN_CONJ (lx,lz,0, yx,yz,0) ; + /* d -= conj(y) * y ; */ + LLDOT (dk,0, yx,yz,0) ; + } + else + { +#ifdef REAL + fl += 2 * ((double) (pend - p - 1)) + 3 ; +#else + fl += 8 * ((double) (pend - p - 1)) + 6 ; +#endif + /* forward solve using D (i,i) and L ((i+1):(k-1),i) */ + for (p++ ; p < pend ; p++) + { + /* W [Li [p]] -= Lx [p] * y ; */ + MULTSUB (Wx,Wz,Li[p], Lx,Lz,p, yx,yz,0) ; + } + /* Scale L (k,0:k-1) for LDL' factorization, compute D (k,k)*/ +#ifdef REAL + /* L(k,i) = y/d */ + lx [0] = yx [0] / di [0] ; + /* d -= L(k,i) * y */ + dk [0] -= lx [0] * yx [0] ; +#else + /* L(k,i) = conj(y) ; */ + ASSIGN_CONJ (lx,lz,0, yx,yz,0) ; + /* L(k,i) /= di ; */ + DIV_REAL (lx,lz,0, lx,lz,0, di,0) ; + /* d -= conj(y) * y / di */ + LDLDOT (dk,0, yx,yz,0, di,0) ; +#endif + } + + /* determine if column i of L can hold the new L(k,i) entry */ + if (p >= Lp [Lnext [i]]) + { + /* column i needs to grow */ + PRINT1 (("Factor Colrealloc "ID", old Lnz "ID"\n", i, Lnz [i])); + if (!CHOLMOD(reallocate_column) (i, lnz + 1, L, Common)) + { + /* out of memory, L is now simplicial symbolic */ + for (i = 0 ; i < n ; i++) + { + /* W [i] = 0 ; */ + CLEAR (Wx,Wz,i) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, n, Common)) ; + return (FALSE) ; + } + Li = L->i ; /* L->i, L->x, L->z may have moved */ + Lx = L->x ; + Lz = L->z ; + p = Lp [i] + lnz ; /* contents of L->p changed */ + ASSERT (p < Lp [Lnext [i]]) ; + } + + /* store L (k,i) in the column form matrix of L */ + Li [p] = k ; + /* Lx [p] = L(k,i) ; */ + ASSIGN (Lx,Lz,p, lx,lz,0) ; + Lnz [i]++ ; + } + + /* ------------------------------------------------------------------ */ + /* ensure abs (d) >= dbound if dbound is given, and store it in L */ + /* ------------------------------------------------------------------ */ + + p = Lp [k] ; + Li [p] = k ; + + if (k >= (Int) L->minor) + { + /* the matrix is already not positive definite */ + dk [0] = 0 ; + } + else if (use_dbound) + { + /* modify the diagonal to force LL' or LDL' to exist */ + dk [0] = CHOLMOD(dbound) (is_ll ? fabs (dk [0]) : dk [0], Common) ; + } + else if ((is_ll ? (IS_LE_ZERO (dk [0])) : (IS_ZERO (dk [0]))) +#ifndef REAL + || dk_imaginary +#endif + ) + { + /* the matrix has just been found to be not positive definite */ + dk [0] = 0 ; + L->minor = k ; + ERROR (CHOLMOD_NOT_POSDEF, "not positive definite") ; + } + + if (is_ll) + { + /* this is counted as one flop, below */ + dk [0] = sqrt (dk [0]) ; + } + + /* Lx [p] = D(k,k) = d ; real part only */ + ASSIGN_REAL (Lx,p, dk,0) ; + CLEAR_IMAG (Lx,Lz,p) ; + } + +#undef NEXT + + if (is_ll) fl += MAX ((Int) kend - (Int) kstart, 0) ; /* count sqrt's */ + Common->rowfacfl = fl ; + + DEBUG (CHOLMOD(dump_factor) (L, "final cholmod_rowfac", Common)) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, n, Common)) ; + return (TRUE) ; +} +#undef PATTERN +#undef REAL +#undef COMPLEX +#undef ZOMPLEX diff --git a/src/CHOLMOD/Cholesky/t_cholmod_solve.c b/src/CHOLMOD/Cholesky/t_cholmod_solve.c new file mode 100644 index 0000000..87aa0a9 --- /dev/null +++ b/src/CHOLMOD/Cholesky/t_cholmod_solve.c @@ -0,0 +1,177 @@ +/* ========================================================================== */ +/* === Cholesky/t_cholmod_solve ============================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Cholesky Module. Copyright (C) 2005-2013, Timothy A. Davis + * The CHOLMOD/Cholesky Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Template routine for cholmod_solve. Supports any numeric xtype (real, + * complex, or zomplex). The xtypes of all matrices (L and Y) must match. + */ + +#include "cholmod_template.h" + +/* ========================================================================== */ +/* === simplicial template solvers ========================================== */ +/* ========================================================================== */ + +/* LL': solve Lx=b with non-unit diagonal */ +#define LL +#include "t_cholmod_lsolve.c" + +/* LDL': solve LDx=b */ +#define LD +#include "t_cholmod_lsolve.c" + +/* LDL': solve Lx=b with unit diagonal */ +#include "t_cholmod_lsolve.c" + +/* LL': solve L'x=b with non-unit diagonal */ +#define LL +#include "t_cholmod_ltsolve.c" + +/* LDL': solve DL'x=b */ +#define LD +#include "t_cholmod_ltsolve.c" + +/* LDL': solve L'x=b with unit diagonal */ +#include "t_cholmod_ltsolve.c" + + +/* ========================================================================== */ +/* === t_ldl_dsolve ========================================================= */ +/* ========================================================================== */ + +/* Solve Dx=b for an LDL' factorization, where Y holds b' on input and x' on + * output. + * + * The number of right-hand-sides (nrhs) is not restricted, even if Yseti + * is present. + */ + +static void TEMPLATE (ldl_dsolve) +( + cholmod_factor *L, + cholmod_dense *Y, /* nr-by-n with leading dimension nr */ + Int *Yseti, Int ysetlen +) +{ + double d [1] ; + double *Lx, *Yx, *Yz ; + Int *Lp ; + Int n, nrhs, k, p, k1, k2, kk, kkiters ; + + ASSERT (L->xtype == Y->xtype) ; /* L and Y must have the same xtype */ + ASSERT (L->n == Y->ncol) ; /* dimensions must match */ + ASSERT (Y->nrow == Y->d) ; /* leading dimension of Y = # rows of Y */ + ASSERT (L->xtype != CHOLMOD_PATTERN) ; /* L is not symbolic */ + ASSERT (!(L->is_super) && !(L->is_ll)) ; /* L is simplicial LDL' */ + + nrhs = Y->nrow ; + n = L->n ; + Lp = L->p ; + Lx = L->x ; + Yx = Y->x ; + Yz = Y->z ; + kkiters = Yseti ? ysetlen : n ; + for (kk = 0 ; kk < kkiters ; kk++) + { + k = Yseti ? Yseti [kk] : kk ; + k1 = k*nrhs ; + k2 = (k+1)*nrhs ; + ASSIGN_REAL (d,0, Lx,Lp[k]) ; + for (p = k1 ; p < k2 ; p++) + { + DIV_REAL (Yx,Yz,p, Yx,Yz,p, d,0) ; + } + } +} + + +/* ========================================================================== */ +/* === t_simplicial_solver ================================================== */ +/* ========================================================================== */ + +/* Solve a linear system, where Y' contains the (array-transposed) right-hand + * side on input, and the solution on output. No permutations are applied; + * these must have already been applied to Y on input. + * + * Yseti [0..ysetlen-1] is an optional list of indices from + * cholmod_lsolve_pattern. The solve is performed only on the columns of L + * corresponding to entries in Yseti. Ignored if NULL. If present, most + * functions require that Y' consist of a single dense column. + */ + +static void TEMPLATE (simplicial_solver) +( + int sys, /* system to solve */ + cholmod_factor *L, /* factor to use, a simplicial LL' or LDL' */ + cholmod_dense *Y, /* right-hand-side on input, solution on output */ + Int *Yseti, Int ysetlen +) +{ + if (L->is_ll) + { + /* The factorization is LL' */ + if (sys == CHOLMOD_A || sys == CHOLMOD_LDLt) + { + /* Solve Ax=b or LL'x=b */ + TEMPLATE (ll_lsolve_k) (L, Y, Yseti, ysetlen) ; + TEMPLATE (ll_ltsolve_k) (L, Y, Yseti, ysetlen) ; + } + else if (sys == CHOLMOD_L || sys == CHOLMOD_LD) + { + /* Solve Lx=b */ + TEMPLATE (ll_lsolve_k) (L, Y, Yseti, ysetlen) ; + } + else if (sys == CHOLMOD_Lt || sys == CHOLMOD_DLt) + { + /* Solve L'x=b */ + TEMPLATE (ll_ltsolve_k) (L, Y, Yseti, ysetlen) ; + } + } + else + { + /* The factorization is LDL' */ + if (sys == CHOLMOD_A || sys == CHOLMOD_LDLt) + { + /* Solve Ax=b or LDL'x=b */ + TEMPLATE (ldl_lsolve_k) (L, Y, Yseti, ysetlen) ; + TEMPLATE (ldl_dltsolve_k) (L, Y, Yseti, ysetlen) ; + } + else if (sys == CHOLMOD_LD) + { + /* Solve LDx=b */ + TEMPLATE (ldl_ldsolve_k) (L, Y, Yseti, ysetlen) ; + } + else if (sys == CHOLMOD_L) + { + /* Solve Lx=b */ + TEMPLATE (ldl_lsolve_k) (L, Y, Yseti, ysetlen) ; + } + else if (sys == CHOLMOD_Lt) + { + /* Solve L'x=b */ + TEMPLATE (ldl_ltsolve_k) (L, Y, Yseti, ysetlen) ; + } + else if (sys == CHOLMOD_DLt) + { + /* Solve DL'x=b */ + TEMPLATE (ldl_dltsolve_k) (L, Y, Yseti, ysetlen) ; + } + else if (sys == CHOLMOD_D) + { + /* Solve Dx=b */ + TEMPLATE (ldl_dsolve) (L, Y, Yseti, ysetlen) ; + } + } +} + +#undef PATTERN +#undef REAL +#undef COMPLEX +#undef ZOMPLEX diff --git a/src/CHOLMOD/Core/License.txt b/src/CHOLMOD/Core/License.txt new file mode 100644 index 0000000..1c3ab99 --- /dev/null +++ b/src/CHOLMOD/Core/License.txt @@ -0,0 +1,25 @@ +CHOLMOD/Core Module. Copyright (C) 2005-2006, Univ. of Florida. +Author: Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + +Note that this license is for the CHOLMOD/Core module only. +All CHOLMOD modules are licensed separately. + + +-------------------------------------------------------------------------------- + + +This Module is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This Module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this Module; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/src/CHOLMOD/Core/cholmod_aat.c b/src/CHOLMOD/Core/cholmod_aat.c new file mode 100644 index 0000000..c62580a --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_aat.c @@ -0,0 +1,301 @@ +/* ========================================================================== */ +/* === Core/cholmod_aat ===================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* C = A*A' or C = A(:,f)*A(:,f)' + * + * A can be packed or unpacked, sorted or unsorted, but must be stored with + * both upper and lower parts (A->stype of zero). C is returned as packed, + * C->stype of zero (both upper and lower parts present), and unsorted. See + * cholmod_ssmult in the MatrixOps Module for a more general matrix-matrix + * multiply. + * + * You can trivially convert C into a symmetric upper/lower matrix by + * changing C->stype = 1 or -1 after calling this routine. + * + * workspace: + * Flag (A->nrow), + * Iwork (max (A->nrow, A->ncol)) if fset present, + * Iwork (A->nrow) if no fset, + * W (A->nrow) if mode > 0, + * allocates temporary copy for A'. + * + * A can be pattern or real. Complex or zomplex cases are supported only + * if the mode is <= 0 (in which case the numerical values are ignored). + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + +cholmod_sparse *CHOLMOD(aat) +( + /* ---- input ---- */ + cholmod_sparse *A, /* input matrix; C=A*A' is constructed */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int mode, /* >0: numerical, 0: pattern, <0: pattern (no diag) + * -2: pattern only, no diagonal, add 50% + n extra + * space to C */ + /* --------------- */ + cholmod_common *Common +) +{ + double fjt ; + double *Ax, *Fx, *Cx, *W ; + Int *Ap, *Anz, *Ai, *Fp, *Fi, *Cp, *Ci, *Flag ; + cholmod_sparse *C, *F ; + Int packed, j, i, pa, paend, pf, pfend, n, mark, cnz, t, p, values, diag, + extra ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + values = (mode > 0) && (A->xtype != CHOLMOD_PATTERN) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + if (A->stype) + { + ERROR (CHOLMOD_INVALID, "matrix cannot be symmetric") ; + return (NULL) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + diag = (mode >= 0) ; + n = A->nrow ; + CHOLMOD(allocate_work) (n, MAX (A->ncol, A->nrow), values ? n : 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n : 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + ASSERT (CHOLMOD(dump_sparse) (A, "A", Common) >= 0) ; + + /* get the A matrix */ + Ap = A->p ; + Anz = A->nz ; + Ai = A->i ; + Ax = A->x ; + packed = A->packed ; + + /* get workspace */ + W = Common->Xwork ; /* size n, unused if values is FALSE */ + Flag = Common->Flag ; /* size n, Flag [0..n-1] < mark on input*/ + + /* ---------------------------------------------------------------------- */ + /* F = A' or A(:,f)' */ + /* ---------------------------------------------------------------------- */ + + /* workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset)*/ + F = CHOLMOD(ptranspose) (A, values, NULL, fset, fsize, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + Fp = F->p ; + Fi = F->i ; + Fx = F->x ; + + /* ---------------------------------------------------------------------- */ + /* count the number of entries in the result C */ + /* ---------------------------------------------------------------------- */ + + cnz = 0 ; + for (j = 0 ; j < n ; j++) + { + /* clear the Flag array */ + /* mark = CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + mark = Common->mark ; + + /* exclude the diagonal, if requested */ + if (!diag) + { + Flag [j] = mark ; + } + + /* for each nonzero F(t,j) in column j, do: */ + pfend = Fp [j+1] ; + for (pf = Fp [j] ; pf < pfend ; pf++) + { + /* F(t,j) is nonzero */ + t = Fi [pf] ; + + /* add the nonzero pattern of A(:,t) to the pattern of C(:,j) */ + pa = Ap [t] ; + paend = (packed) ? (Ap [t+1]) : (pa + Anz [t]) ; + for ( ; pa < paend ; pa++) + { + i = Ai [pa] ; + if (Flag [i] != mark) + { + Flag [i] = mark ; + cnz++ ; + } + } + } + if (cnz < 0) + { + break ; /* integer overflow case */ + } + } + + extra = (mode == -2) ? (cnz/2 + n) : 0 ; + + mark = CHOLMOD(clear_flag) (Common) ; + + /* ---------------------------------------------------------------------- */ + /* check for integer overflow */ + /* ---------------------------------------------------------------------- */ + + if (cnz < 0 || (cnz + extra) < 0) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + CHOLMOD(clear_flag) (Common) ; + CHOLMOD(free_sparse) (&F, Common) ; + return (NULL) ; /* problem too large */ + } + + /* ---------------------------------------------------------------------- */ + /* allocate C */ + /* ---------------------------------------------------------------------- */ + + C = CHOLMOD(allocate_sparse) (n, n, cnz + extra, FALSE, TRUE, 0, + values ? A->xtype : CHOLMOD_PATTERN, Common) ; + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_sparse) (&F, Common) ; + return (NULL) ; /* out of memory */ + } + + Cp = C->p ; + Ci = C->i ; + Cx = C->x ; + + /* ---------------------------------------------------------------------- */ + /* C = A*A' */ + /* ---------------------------------------------------------------------- */ + + cnz = 0 ; + + if (values) + { + + /* pattern and values */ + for (j = 0 ; j < n ; j++) + { + /* clear the Flag array */ + mark = CHOLMOD(clear_flag) (Common) ; + + /* start column j of C */ + Cp [j] = cnz ; + + /* for each nonzero F(t,j) in column j, do: */ + pfend = Fp [j+1] ; + for (pf = Fp [j] ; pf < pfend ; pf++) + { + /* F(t,j) is nonzero */ + t = Fi [pf] ; + fjt = Fx [pf] ; + + /* add the nonzero pattern of A(:,t) to the pattern of C(:,j) + * and scatter the values into W */ + pa = Ap [t] ; + paend = (packed) ? (Ap [t+1]) : (pa + Anz [t]) ; + for ( ; pa < paend ; pa++) + { + i = Ai [pa] ; + if (Flag [i] != mark) + { + Flag [i] = mark ; + Ci [cnz++] = i ; + } + W [i] += Ax [pa] * fjt ; + } + } + + /* gather the values into C(:,j) */ + for (p = Cp [j] ; p < cnz ; p++) + { + i = Ci [p] ; + Cx [p] = W [i] ; + W [i] = 0 ; + } + } + + } + else + { + + /* pattern only */ + for (j = 0 ; j < n ; j++) + { + /* clear the Flag array */ + mark = CHOLMOD(clear_flag) (Common) ; + + /* exclude the diagonal, if requested */ + if (!diag) + { + Flag [j] = mark ; + } + + /* start column j of C */ + Cp [j] = cnz ; + + /* for each nonzero F(t,j) in column j, do: */ + pfend = Fp [j+1] ; + for (pf = Fp [j] ; pf < pfend ; pf++) + { + /* F(t,j) is nonzero */ + t = Fi [pf] ; + + /* add the nonzero pattern of A(:,t) to the pattern of C(:,j) */ + pa = Ap [t] ; + paend = (packed) ? (Ap [t+1]) : (pa + Anz [t]) ; + for ( ; pa < paend ; pa++) + { + i = Ai [pa] ; + if (Flag [i] != mark) + { + Flag [i] = mark ; + Ci [cnz++] = i ; + } + } + } + } + } + + Cp [n] = cnz ; + ASSERT (IMPLIES (mode != -2, MAX (1,cnz) == C->nzmax)) ; + + /* ---------------------------------------------------------------------- */ + /* clear workspace and free temporary matrices and return result */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(free_sparse) (&F, Common) ; + CHOLMOD(clear_flag) (Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n : 0, Common)) ; + DEBUG (i = CHOLMOD(dump_sparse) (C, "aat", Common)) ; + ASSERT (IMPLIES (mode < 0, i == 0)) ; + return (C) ; +} diff --git a/src/CHOLMOD/Core/cholmod_add.c b/src/CHOLMOD/Core/cholmod_add.c new file mode 100644 index 0000000..f7fc675 --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_add.c @@ -0,0 +1,281 @@ +/* ========================================================================== */ +/* === Core/cholmod_add ===================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* C = alpha*A + beta*B, or spones(A+B). Result is packed, with sorted or + * unsorted columns. This routine is much faster and takes less memory if C + * is allowed to have unsorted columns. + * + * If A and B are both symmetric (in upper form) then C is the same. Likewise, + * if A and B are both symmetric (in lower form) then C is the same. + * Otherwise, C is unsymmetric. A and B must have the same dimension. + * + * workspace: Flag (nrow), W (nrow) if values, Iwork (max (nrow,ncol)). + * allocates temporary copies for A and B if they are symmetric. + * allocates temporary copy of C if it is to be returned sorted. + * + * A and B can have an xtype of pattern or real. Complex or zomplex cases + * are supported only if the "values" input parameter is FALSE. + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + +cholmod_sparse *CHOLMOD(add) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to add */ + cholmod_sparse *B, /* matrix to add */ + double alpha [2], /* scale factor for A */ + double beta [2], /* scale factor for B */ + int values, /* if TRUE compute the numerical values of C */ + int sorted, /* if TRUE, sort columns of C */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Ax, *Bx, *Cx, *W ; + Int apacked, up, lo, nrow, ncol, bpacked, nzmax, pa, paend, pb, pbend, i, + j, p, mark, nz ; + Int *Ap, *Ai, *Anz, *Bp, *Bi, *Bnz, *Flag, *Cp, *Ci ; + cholmod_sparse *A2, *B2, *C ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + RETURN_IF_NULL (B, NULL) ; + values = values && + (A->xtype != CHOLMOD_PATTERN) && (B->xtype != CHOLMOD_PATTERN) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + RETURN_IF_XTYPE_INVALID (B, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + if (A->nrow != B->nrow || A->ncol != B->ncol) + { + /* A and B must have the same dimensions */ + ERROR (CHOLMOD_INVALID, "A and B dimesions do not match") ; + return (NULL) ; + } + /* A and B must have the same numerical type if values is TRUE (both must + * be CHOLMOD_REAL, this is implicitly checked above) */ + + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + nrow = A->nrow ; + ncol = A->ncol ; + CHOLMOD(allocate_work) (nrow, MAX (nrow,ncol), values ? nrow : 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + if (nrow <= 1) + { + /* C will be implicitly sorted, so no need to sort it here */ + sorted = FALSE ; + } + + /* convert A or B to unsymmetric, if necessary */ + A2 = NULL ; + B2 = NULL ; + + if (A->stype != B->stype) + { + if (A->stype) + { + /* workspace: Iwork (max (nrow,ncol)) */ + A2 = CHOLMOD(copy) (A, 0, values, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + A = A2 ; + } + if (B->stype) + { + /* workspace: Iwork (max (nrow,ncol)) */ + B2 = CHOLMOD(copy) (B, 0, values, Common) ; + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_sparse) (&A2, Common) ; + return (NULL) ; /* out of memory */ + } + B = B2 ; + } + } + + /* get the A matrix */ + ASSERT (A->stype == B->stype) ; + up = (A->stype > 0) ; + lo = (A->stype < 0) ; + + Ap = A->p ; + Anz = A->nz ; + Ai = A->i ; + Ax = A->x ; + apacked = A->packed ; + + /* get the B matrix */ + Bp = B->p ; + Bnz = B->nz ; + Bi = B->i ; + Bx = B->x ; + bpacked = B->packed ; + + /* get workspace */ + W = Common->Xwork ; /* size nrow, used if values is TRUE */ + Flag = Common->Flag ; /* size nrow, Flag [0..nrow-1] < mark on input */ + + /* ---------------------------------------------------------------------- */ + /* allocate the result C */ + /* ---------------------------------------------------------------------- */ + + /* If integer overflow occurs, nzmax < 0 and the allocate fails properly + * (likewise in most other matrix manipulation routines). */ + + nzmax = CHOLMOD(nnz) (A, Common) + CHOLMOD(nnz) (B, Common) ; + + C = CHOLMOD(allocate_sparse) (nrow, ncol, nzmax, FALSE, TRUE, + SIGN (A->stype), values ? A->xtype : CHOLMOD_PATTERN, Common) ; + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_sparse) (&A2, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + return (NULL) ; /* out of memory */ + } + + Cp = C->p ; + Ci = C->i ; + Cx = C->x ; + + /* ---------------------------------------------------------------------- */ + /* compute C = alpha*A + beta*B */ + /* ---------------------------------------------------------------------- */ + + nz = 0 ; + for (j = 0 ; j < ncol ; j++) + { + Cp [j] = nz ; + + /* clear the Flag array */ + /* mark = CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + mark = Common->mark ; + + /* scatter B into W */ + pb = Bp [j] ; + pbend = (bpacked) ? (Bp [j+1]) : (pb + Bnz [j]) ; + for (p = pb ; p < pbend ; p++) + { + i = Bi [p] ; + if ((up && i > j) || (lo && i < j)) + { + continue ; + } + Flag [i] = mark ; + if (values) + { + W [i] = beta [0] * Bx [p] ; + } + } + + /* add A and gather from W into C(:,j) */ + pa = Ap [j] ; + paend = (apacked) ? (Ap [j+1]) : (pa + Anz [j]) ; + for (p = pa ; p < paend ; p++) + { + i = Ai [p] ; + if ((up && i > j) || (lo && i < j)) + { + continue ; + } + Flag [i] = EMPTY ; + Ci [nz] = i ; + if (values) + { + Cx [nz] = W [i] + alpha [0] * Ax [p] ; + W [i] = 0 ; + } + nz++ ; + } + + /* gather remaining entries into C(:,j), using pattern of B */ + for (p = pb ; p < pbend ; p++) + { + i = Bi [p] ; + if ((up && i > j) || (lo && i < j)) + { + continue ; + } + if (Flag [i] == mark) + { + Ci [nz] = i ; + if (values) + { + Cx [nz] = W [i] ; + W [i] = 0 ; + } + nz++ ; + } + } + } + + Cp [ncol] = nz ; + + /* ---------------------------------------------------------------------- */ + /* reduce C in size and free temporary matrices */ + /* ---------------------------------------------------------------------- */ + + ASSERT (MAX (1,nz) <= C->nzmax) ; + CHOLMOD(reallocate_sparse) (nz, C, Common) ; + ASSERT (Common->status >= CHOLMOD_OK) ; + + /* clear the Flag array */ + mark = CHOLMOD(clear_flag) (Common) ; + + CHOLMOD(free_sparse) (&A2, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + + /* ---------------------------------------------------------------------- */ + /* sort C, if requested */ + /* ---------------------------------------------------------------------- */ + + if (sorted) + { + /* workspace: Iwork (max (nrow,ncol)) */ + if (!CHOLMOD(sort) (C, Common)) + { + CHOLMOD(free_sparse) (&C, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + } + } + + /* ---------------------------------------------------------------------- */ + /* return result */ + /* ---------------------------------------------------------------------- */ + + ASSERT (CHOLMOD(dump_sparse) (C, "add", Common) >= 0) ; + return (C) ; +} diff --git a/src/CHOLMOD/Core/cholmod_band.c b/src/CHOLMOD/Core/cholmod_band.c new file mode 100644 index 0000000..fd03698 --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_band.c @@ -0,0 +1,373 @@ +/* ========================================================================== */ +/* === Core/cholmod_band ==================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* C = tril (triu (A,k1), k2) + * + * C is a matrix consisting of the diagonals of A from k1 to k2. + * + * k=0 is the main diagonal of A, k=1 is the superdiagonal, k=-1 is the + * subdiagonal, and so on. If A is m-by-n, then: + * + * k1=-m C = tril (A,k2) + * k2=n C = triu (A,k1) + * k1=0 and k2=0 C = diag(A), except C is a matrix, not a vector + * + * Values of k1 and k2 less than -m are treated as -m, and values greater + * than n are treated as n. + * + * A can be of any symmetry (upper, lower, or unsymmetric); C is returned in + * the same form, and packed. If A->stype > 0, entries in the lower + * triangular part of A are ignored, and the opposite is true if + * A->stype < 0. If A has sorted columns, then so does C. + * C has the same size as A. + * + * If inplace is TRUE, then the matrix A is modified in place. + * Only packed matrices can be converted in place. + * + * C can be returned as a numerical valued matrix (if A has numerical values + * and mode > 0), as a pattern-only (mode == 0), or as a pattern-only but with + * the diagonal entries removed (mode < 0). + * + * workspace: none + * + * A can have an xtype of pattern or real. Complex and zomplex cases supported + * only if mode <= 0 (in which case the numerical values are ignored). + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + +static cholmod_sparse *band /* returns C, or NULL if failure */ +( + /* ---- input or in/out if inplace is TRUE --- */ + cholmod_sparse *A, + /* ---- input ---- */ + SuiteSparse_long k1, /* ignore entries below the k1-st diagonal */ + SuiteSparse_long k2, /* ignore entries above the k2-nd diagonal */ + int mode, /* >0: numerical, 0: pattern, <0: pattern (no diagonal) */ + int inplace, /* if TRUE, then convert A in place */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Ax, *Cx ; + Int packed, nz, j, p, pend, i, ncol, nrow, jlo, jhi, ilo, ihi, sorted, + values, diag ; + Int *Ap, *Anz, *Ai, *Cp, *Ci ; + cholmod_sparse *C ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + values = (mode > 0) && (A->xtype != CHOLMOD_PATTERN) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + packed = A->packed ; + diag = (mode >= 0) ; + if (inplace && !packed) + { + /* cannot operate on an unpacked matrix in place */ + ERROR (CHOLMOD_INVALID, "cannot operate on unpacked matrix in-place") ; + return (NULL) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + + PRINT1 (("k1 %ld k2 %ld\n", k1, k2)) ; + Ap = A->p ; + Anz = A->nz ; + Ai = A->i ; + Ax = A->x ; + sorted = A->sorted ; + + + if (A->stype > 0) + { + /* ignore any entries in strictly lower triangular part of A */ + k1 = MAX (k1, 0) ; + } + if (A->stype < 0) + { + /* ignore any entries in strictly upper triangular part of A */ + k2 = MIN (k2, 0) ; + } + ncol = A->ncol ; + nrow = A->nrow ; + + /* ensure k1 and k2 are in the range -nrow to +ncol to + * avoid possible integer overflow if k1 and k2 are huge */ + k1 = MAX (-nrow, k1) ; + k1 = MIN (k1, ncol) ; + k2 = MAX (-nrow, k2) ; + k2 = MIN (k2, ncol) ; + + /* consider columns jlo to jhi. columns outside this range are empty */ + jlo = MAX (k1, 0) ; + jhi = MIN (k2+nrow, ncol) ; + + if (k1 > k2) + { + /* nothing to do */ + jlo = ncol ; + jhi = ncol ; + } + + /* ---------------------------------------------------------------------- */ + /* allocate C, or operate on A in place */ + /* ---------------------------------------------------------------------- */ + + if (inplace) + { + /* convert A in place */ + C = A ; + } + else + { + /* count the number of entries in the result C */ + nz = 0 ; + if (sorted) + { + for (j = jlo ; j < jhi ; j++) + { + ilo = j-k2 ; + ihi = j-k1 ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i > ihi) + { + break ; + } + if (i >= ilo && (diag || i != j)) + { + nz++ ; + } + } + } + } + else + { + for (j = jlo ; j < jhi ; j++) + { + ilo = j-k2 ; + ihi = j-k1 ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i >= ilo && i <= ihi && (diag || i != j)) + { + nz++ ; + } + } + } + } + /* allocate C; A will not be modified. C is sorted if A is sorted */ + C = CHOLMOD(allocate_sparse) (A->nrow, ncol, nz, sorted, TRUE, + A->stype, values ? A->xtype : CHOLMOD_PATTERN, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + } + + Cp = C->p ; + Ci = C->i ; + Cx = C->x ; + + /* ---------------------------------------------------------------------- */ + /* construct C */ + /* ---------------------------------------------------------------------- */ + + /* columns 0 to jlo-1 are empty */ + for (j = 0 ; j < jlo ; j++) + { + Cp [j] = 0 ; + } + + nz = 0 ; + if (sorted) + { + if (values) + { + /* pattern and values */ + ASSERT (diag) ; + for (j = jlo ; j < jhi ; j++) + { + ilo = j-k2 ; + ihi = j-k1 ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + Cp [j] = nz ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i > ihi) + { + break ; + } + if (i >= ilo) + { + Ci [nz] = i ; + Cx [nz] = Ax [p] ; + nz++ ; + } + } + } + } + else + { + /* pattern only, perhaps with no diagonal */ + for (j = jlo ; j < jhi ; j++) + { + ilo = j-k2 ; + ihi = j-k1 ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + Cp [j] = nz ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i > ihi) + { + break ; + } + if (i >= ilo && (diag || i != j)) + { + Ci [nz++] = i ; + } + } + } + } + } + else + { + if (values) + { + /* pattern and values */ + ASSERT (diag) ; + for (j = jlo ; j < jhi ; j++) + { + ilo = j-k2 ; + ihi = j-k1 ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + Cp [j] = nz ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i >= ilo && i <= ihi) + { + Ci [nz] = i ; + Cx [nz] = Ax [p] ; + nz++ ; + } + } + } + } + else + { + /* pattern only, perhaps with no diagonal */ + for (j = jlo ; j < jhi ; j++) + { + ilo = j-k2 ; + ihi = j-k1 ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + Cp [j] = nz ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i >= ilo && i <= ihi && (diag || i != j)) + { + Ci [nz++] = i ; + } + } + } + } + } + + /* columns jhi to ncol-1 are empty */ + for (j = jhi ; j <= ncol ; j++) + { + Cp [j] = nz ; + } + + /* ---------------------------------------------------------------------- */ + /* reduce A in size if done in place */ + /* ---------------------------------------------------------------------- */ + + if (inplace) + { + /* free the unused parts of A, and reduce A->i and A->x in size */ + ASSERT (MAX (1,nz) <= A->nzmax) ; + CHOLMOD(reallocate_sparse) (nz, A, Common) ; + ASSERT (Common->status >= CHOLMOD_OK) ; + } + + /* ---------------------------------------------------------------------- */ + /* return the result C */ + /* ---------------------------------------------------------------------- */ + + DEBUG (i = CHOLMOD(dump_sparse) (C, "band", Common)) ; + ASSERT (IMPLIES (mode < 0, i == 0)) ; + return (C) ; +} + + +/* ========================================================================== */ +/* === cholmod_band ========================================================= */ +/* ========================================================================== */ + +cholmod_sparse *CHOLMOD(band) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to extract band matrix from */ + SuiteSparse_long k1, /* ignore entries below the k1-st diagonal */ + SuiteSparse_long k2, /* ignore entries above the k2-nd diagonal */ + int mode, /* >0: numerical, 0: pattern, <0: pattern (no diag) */ + /* --------------- */ + cholmod_common *Common +) +{ + return (band (A, k1, k2, mode, FALSE, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_band_inplace ================================================= */ +/* ========================================================================== */ + +int CHOLMOD(band_inplace) +( + /* ---- input ---- */ + SuiteSparse_long k1, /* ignore entries below the k1-st diagonal */ + SuiteSparse_long k2, /* ignore entries above the k2-nd diagonal */ + int mode, /* >0: numerical, 0: pattern, <0: pattern (no diag) */ + /* ---- in/out --- */ + cholmod_sparse *A, /* matrix from which entries not in band are removed */ + /* --------------- */ + cholmod_common *Common +) +{ + return (band (A, k1, k2, mode, TRUE, Common) != NULL) ; +} diff --git a/src/CHOLMOD/Core/cholmod_change_factor.c b/src/CHOLMOD/Core/cholmod_change_factor.c new file mode 100644 index 0000000..05032b7 --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_change_factor.c @@ -0,0 +1,1226 @@ +/* ========================================================================== */ +/* === Core/cholmod_change_factor =========================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Change the numeric/symbolic, LL/LDL, simplicial/super, packed/unpacked, + * monotonic/non-monotonic status of a cholmod_factor object. + * + * There are four basic classes of factor types: + * + * (1) simplicial symbolic: Consists of two size-n arrays: the fill-reducing + * permutation (L->Perm) and the nonzero count for each column of L + * (L->ColCount). All other factor types also include this information. + * L->ColCount may be exact (obtained from the analysis routines), or + * it may be a guess. During factorization, and certainly after update/ + * downdate, the columns of L can have a different number of nonzeros. + * L->ColCount is used to allocate space. L->ColCount is exact for the + * supernodal factorizations. The nonzero pattern of L is not kept. + * + * (2) simplicial numeric: These represent L in a compressed column form. The + * variants of this type are: + * + * LDL': L is unit diagonal. Row indices in column j are located in + * L->i [L->p [j] ... L->p [j] + L->nz [j]], and corresponding numeric + * values are in the same locations in L->x. The total number of + * entries is the sum of L->nz [j]. The unit diagonal is not stored; + * D is stored on the diagonal of L instead. L->p may or may not be + * monotonic. The order of storage of the columns in L->i and L->x is + * given by a doubly-linked list (L->prev and L->next). L->p is of + * size n+1, but only the first n entries are used (it is used if L + * is converted to a sparse matrix via cholmod_factor_to_sparse). + * + * For the complex case, L->x is stored interleaved with real/imag + * parts, and is of size 2*lnz*sizeof(double). For the zomplex case, + * L->x is of size lnz*sizeof(double) and holds the real part; L->z + * is the same size and holds the imaginary part. + * + * LL': This is identical to the LDL' form, except that the non-unit + * diagonal of L is stored as the first entry in each column of L. + * + * (3) supernodal symbolic: A representation of the nonzero pattern of the + * supernodes for a supernodal factorization. There are L->nsuper + * supernodes. Columns L->super [k] to L->super [k+1]-1 are in the kth + * supernode. The row indices for the kth supernode are in + * L->s [L->pi [k] ... L->pi [k+1]-1]. The numerical values are not + * allocated (L->x), but when they are they will be located in + * L->x [L->px [k] ... L->px [k+1]-1], and the L->px array is defined + * in this factor type. + * + * For the complex case, L->x is stored interleaved with real/imag parts, + * and is of size 2*L->xsize*sizeof(double). The zomplex supernodal case + * is not supported, since it is not compatible with LAPACK and the BLAS. + * + * (4) supernodal numeric: Always an LL' factorization. L is non-unit + * diagonal. L->x contains the numerical values of the supernodes, as + * described above for the supernodal symbolic factor. + * For the complex case, L->x is stored interleaved, and is of size + * 2*L->xsize*sizeof(double). The zomplex supernodal case is not + * supported, since it is not compatible with LAPACK and the BLAS. + * + * FUTURE WORK: support a supernodal LDL' factor. + * + * + * In all cases, the row indices in each column (L->i for simplicial L and + * L->s for supernodal L) are kept sorted from low indices to high indices. + * This means the diagonal of L (or D for LDL' factors) is always kept as the + * first entry in each column. + * + * The cholmod_change_factor routine can do almost all possible conversions. + * It cannot do the following conversions: + * + * (1) Simplicial numeric types cannot be converted to a supernodal + * symbolic type. This would simultaneously deallocate the + * simplicial pattern and numeric values and reallocate uninitialized + * space for the supernodal pattern. This isn't useful for the user, + * and not needed by CHOLMOD's own routines either. + * + * (2) Only a symbolic factor (simplicial to supernodal) can be converted + * to a supernodal numeric factor. + * + * Some conversions are meant only to be used internally by other CHOLMOD + * routines, and should not be performed by the end user. They allocate space + * whose contents are undefined: + * + * (1) converting from simplicial symbolic to supernodal symbolic. + * (2) converting any factor to supernodal numeric. + * + * workspace: no conversion routine uses workspace in Common. No temporary + * workspace is allocated. + * + * Supports all xtypes, except that there is no supernodal zomplex L. + * + * The to_xtype parameter is used only when converting from symbolic to numeric + * or numeric to symbolic. It cannot be used to convert a numeric xtype (real, + * complex, or zomplex) to a different numeric xtype. For that conversion, + * use cholmod_factor_xtype instead. + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + +static void natural_list (cholmod_factor *L) ; + +/* ========================================================================== */ +/* === TEMPLATE ============================================================= */ +/* ========================================================================== */ + +#define REAL +#include "t_cholmod_change_factor.c" +#define COMPLEX +#include "t_cholmod_change_factor.c" +#define ZOMPLEX +#include "t_cholmod_change_factor.c" + + +/* ========================================================================== */ +/* === L_is_packed ========================================================== */ +/* ========================================================================== */ + +/* Return TRUE if the columns of L are packed, FALSE otherwise. For debugging + * only. */ + +#ifndef NDEBUG +static int L_is_packed (cholmod_factor *L, cholmod_common *Common) +{ + Int j ; + Int *Lnz = L->nz ; + Int *Lp = L->p ; + Int n = L->n ; + + if (L->xtype == CHOLMOD_PATTERN || L->is_super) + { + return (TRUE) ; + } + + if (Lnz == NULL || Lp == NULL) + { + return (TRUE) ; + } + + for (j = 0 ; j < n ; j++) + { + PRINT3 (("j: "ID" Lnz "ID" Lp[j+1] "ID" Lp[j] "ID"\n", j, Lnz [j], + Lp [j+1], Lp [j])) ; + if (Lnz [j] != (Lp [j+1] - Lp [j])) + { + PRINT2 (("L is not packed\n")) ; + return (FALSE) ; + } + } + return (TRUE) ; +} +#endif + + +/* ========================================================================== */ +/* === natural_list ========================================================= */ +/* ========================================================================== */ + +/* Create a naturally-ordered doubly-linked list of columns. */ + +static void natural_list (cholmod_factor *L) +{ + Int head, tail, n, j ; + Int *Lnext, *Lprev ; + Lnext = L->next ; + Lprev = L->prev ; + ASSERT (Lprev != NULL && Lnext != NULL) ; + n = L->n ; + head = n+1 ; + tail = n ; + Lnext [head] = 0 ; + Lprev [head] = EMPTY ; + Lnext [tail] = EMPTY ; + Lprev [tail] = n-1 ; + for (j = 0 ; j < n ; j++) + { + Lnext [j] = j+1 ; + Lprev [j] = j-1 ; + } + Lprev [0] = head ; + L->is_monotonic = TRUE ; +} + + +/* ========================================================================== */ +/* === allocate_simplicial_numeric ========================================== */ +/* ========================================================================== */ + +/* Allocate O(n) arrays for simplicial numeric factorization. Initializes + * the link lists only. Does not allocate the L->i, L->x, or L->z arrays. */ + +static int allocate_simplicial_numeric +( + cholmod_factor *L, + cholmod_common *Common +) +{ + Int n ; + Int *Lp, *Lnz, *Lprev, *Lnext ; + size_t n1, n2 ; + + PRINT1 (("Allocate simplicial\n")) ; + + ASSERT (L->xtype == CHOLMOD_PATTERN || L->is_super) ; + ASSERT (L->p == NULL) ; + ASSERT (L->nz == NULL) ; + ASSERT (L->prev == NULL) ; + ASSERT (L->next == NULL) ; + + n = L->n ; + + /* this cannot cause size_t overflow */ + n1 = ((size_t) n) + 1 ; + n2 = ((size_t) n) + 2 ; + + Lp = CHOLMOD(malloc) (n1, sizeof (Int), Common) ; + Lnz = CHOLMOD(malloc) (n, sizeof (Int), Common) ; + Lprev = CHOLMOD(malloc) (n2, sizeof (Int), Common) ; + Lnext = CHOLMOD(malloc) (n2, sizeof (Int), Common) ; + + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free) (n1, sizeof (Int), Lp, Common) ; + CHOLMOD(free) (n, sizeof (Int), Lnz, Common) ; + CHOLMOD(free) (n2, sizeof (Int), Lprev, Common) ; + CHOLMOD(free) (n2, sizeof (Int), Lnext, Common) ; + PRINT1 (("Allocate simplicial failed\n")) ; + return (FALSE) ; /* out of memory */ + } + + /* ============================================== commit the changes to L */ + + L->p = Lp ; + L->nz = Lnz ; + L->prev = Lprev ; + L->next = Lnext ; + /* initialize a doubly linked list for columns in natural order */ + natural_list (L) ; + PRINT1 (("Allocate simplicial done\n")) ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === simplicial_symbolic_to_super_symbolic ================================ */ +/* ========================================================================== */ + +/* Convert a simplicial symbolic factor supernodal symbolic factor. Does not + * initialize the new space. */ + +static int simplicial_symbolic_to_super_symbolic +( + cholmod_factor *L, + cholmod_common *Common +) +{ + Int nsuper, xsize, ssize ; + Int *Lsuper, *Lpi, *Lpx, *Ls ; + size_t nsuper1 ; + + ASSERT (L->xtype == CHOLMOD_PATTERN && !(L->is_super)) ; + + xsize = L->xsize ; + ssize = L->ssize ; + nsuper = L->nsuper ; + nsuper1 = ((size_t) nsuper) + 1 ; + + PRINT1 (("simple sym to super sym: ssize "ID" xsize "ID" nsuper "ID"" + " status %d\n", ssize, xsize, nsuper, Common->status)) ; + + /* O(nsuper) arrays, where nsuper <= n */ + Lsuper = CHOLMOD(malloc) (nsuper1, sizeof (Int), Common) ; + Lpi = CHOLMOD(malloc) (nsuper1, sizeof (Int), Common) ; + Lpx = CHOLMOD(malloc) (nsuper1, sizeof (Int), Common) ; + + /* O(ssize) array, where ssize <= nnz(L), and usually much smaller */ + Ls = CHOLMOD(malloc) (ssize, sizeof (Int), Common) ; + + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free) (nsuper1, sizeof (Int), Lsuper, Common) ; + CHOLMOD(free) (nsuper1, sizeof (Int), Lpi, Common) ; + CHOLMOD(free) (nsuper1, sizeof (Int), Lpx, Common) ; + CHOLMOD(free) (ssize, sizeof (Int), Ls, Common) ; + return (FALSE) ; /* out of memory */ + } + + /* ============================================== commit the changes to L */ + + ASSERT (Lsuper != NULL && Lpi != NULL && Lpx != NULL && Ls != NULL) ; + + L->maxcsize = 0 ; + L->maxesize = 0 ; + + L->super = Lsuper ; + L->pi = Lpi ; + L->px = Lpx ; + L->s = Ls ; + Ls [0] = EMPTY ; /* supernodal pattern undefined */ + + L->is_super = TRUE ; + L->is_ll = TRUE ; /* supernodal LDL' not supported */ + L->xtype = CHOLMOD_PATTERN ; + L->dtype = DTYPE ; + L->minor = L->n ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === any_to_simplicial_symbolic =========================================== */ +/* ========================================================================== */ + +/* Convert any factor L to a simplicial symbolic factor, leaving only L->Perm + * and L->ColCount. Cannot fail. Any of the components of L (except Perm and + * ColCount) may already be free'd. */ + +static void any_to_simplicial_symbolic +( + cholmod_factor *L, + int to_ll, + cholmod_common *Common +) +{ + Int n, lnz, xs, ss, s, e ; + size_t n1, n2 ; + + /* ============================================== commit the changes to L */ + + n = L->n ; + lnz = L->nzmax ; + s = L->nsuper + 1 ; + xs = (L->is_super) ? ((Int) (L->xsize)) : (lnz) ; + e = (L->xtype == CHOLMOD_COMPLEX ? 2 : 1) ; + ss = L->ssize ; + + /* this cannot cause size_t overflow */ + n1 = ((size_t) n) + 1 ; + n2 = ((size_t) n) + 2 ; + + /* free all but the symbolic analysis (Perm and ColCount) */ + L->p = CHOLMOD(free) (n1, sizeof (Int), L->p, Common) ; + L->i = CHOLMOD(free) (lnz, sizeof (Int), L->i, Common) ; + L->x = CHOLMOD(free) (xs, e*sizeof (double), L->x, Common) ; + L->z = CHOLMOD(free) (lnz, sizeof (double), L->z, Common) ; + L->nz = CHOLMOD(free) (n, sizeof (Int), L->nz, Common) ; + L->next = CHOLMOD(free) (n2, sizeof (Int), L->next, Common) ; + L->prev = CHOLMOD(free) (n2, sizeof (Int), L->prev, Common) ; + L->super = CHOLMOD(free) (s, sizeof (Int), L->super, Common) ; + L->pi = CHOLMOD(free) (s, sizeof (Int), L->pi, Common) ; + L->px = CHOLMOD(free) (s, sizeof (Int), L->px, Common) ; + L->s = CHOLMOD(free) (ss, sizeof (Int), L->s, Common) ; + L->nzmax = 0 ; + L->is_super = FALSE ; + L->xtype = CHOLMOD_PATTERN ; + L->dtype = DTYPE ; + L->minor = n ; + L->is_ll = to_ll ; +} + + +/* ========================================================================== */ +/* === ll_super_to_super_symbolic =========================================== */ +/* ========================================================================== */ + +/* Convert a numerical supernodal L to symbolic supernodal. Cannot fail. */ + +static void ll_super_to_super_symbolic +( + cholmod_factor *L, + cholmod_common *Common +) +{ + + /* ============================================== commit the changes to L */ + + /* free all but the supernodal numerical factor */ + ASSERT (L->xtype != CHOLMOD_PATTERN && L->is_super && L->is_ll) ; + DEBUG (CHOLMOD(dump_factor) (L, "start to super symbolic", Common)) ; + L->x = CHOLMOD(free) (L->xsize, + (L->xtype == CHOLMOD_COMPLEX ? 2 : 1) * sizeof (double), L->x, + Common) ; + L->xtype = CHOLMOD_PATTERN ; + L->dtype = DTYPE ; + L->minor = L->n ; + L->is_ll = TRUE ; /* supernodal LDL' not supported */ + DEBUG (CHOLMOD(dump_factor) (L, "done to super symbolic", Common)) ; +} + + +/* ========================================================================== */ +/* === simplicial_symbolic_to_simplicial_numeric ============================ */ +/* ========================================================================== */ + +/* Convert a simplicial symbolic L to a simplicial numeric L; allocate space + * for L using L->ColCount from symbolic analysis, and set L to identity. + * + * If packed < 0, then this routine is creating a copy of another factor + * (via cholmod_copy_factor). In this case, the space is not initialized. */ + +static void simplicial_symbolic_to_simplicial_numeric +( + cholmod_factor *L, + int to_ll, + int packed, + int to_xtype, + cholmod_common *Common +) +{ + double grow0, grow1, xlen, xlnz ; + double *Lx, *Lz ; + Int *Li, *Lp, *Lnz, *ColCount ; + Int n, grow, grow2, p, j, lnz, len, ok, e ; + + ASSERT (L->xtype == CHOLMOD_PATTERN && !(L->is_super)) ; + if (!allocate_simplicial_numeric (L, Common)) + { + PRINT1 (("out of memory, allocate simplicial numeric\n")) ; + return ; /* out of memory */ + } + ASSERT (L->ColCount != NULL && L->nz != NULL && L->p != NULL) ; + ASSERT (L->x == NULL && L->z == NULL && L->i == NULL) ; + + ColCount = L->ColCount ; + Lnz = L->nz ; + Lp = L->p ; + ok = TRUE ; + n = L->n ; + + if (packed < 0) + { + + /* ------------------------------------------------------------------ */ + /* used by cholmod_copy_factor to allocate a copy of a factor object */ + /* ------------------------------------------------------------------ */ + + lnz = L->nzmax ; + L->nzmax = 0 ; + + } + else if (packed) + { + + /* ------------------------------------------------------------------ */ + /* LDL' or LL' packed */ + /* ------------------------------------------------------------------ */ + + PRINT1 (("convert to packed LL' or LDL'\n")) ; + lnz = 0 ; + for (j = 0 ; ok && j < n ; j++) + { + /* ensure len is in the range 1 to n-j */ + len = ColCount [j] ; + len = MAX (1, len) ; + len = MIN (len, n-j) ; + lnz += len ; + ok = (lnz >= 0) ; + } + for (j = 0 ; j <= n ; j++) + { + Lp [j] = j ; + } + for (j = 0 ; j < n ; j++) + { + Lnz [j] = 1 ; + } + + } + else + { + + /* ------------------------------------------------------------------ */ + /* LDL' unpacked */ + /* ------------------------------------------------------------------ */ + + PRINT1 (("convert to unpacked\n")) ; + /* compute new lnzmax */ + /* if any parameter is NaN, grow is false */ + grow0 = Common->grow0 ; + grow1 = Common->grow1 ; + grow2 = Common->grow2 ; + grow0 = IS_NAN (grow0) ? 1 : grow0 ; + grow1 = IS_NAN (grow1) ? 1 : grow1 ; + /* fl.pt. compare, but no NaN's: */ + grow = (grow0 >= 1.0) && (grow1 >= 1.0) && (grow2 > 0) ; + PRINT1 (("init, grow1 %g grow2 "ID"\n", grow1, grow2)) ; + /* initialize Lp and Lnz for each column */ + lnz = 0 ; + for (j = 0 ; ok && j < n ; j++) + { + Lp [j] = lnz ; + Lnz [j] = 1 ; + + /* ensure len is in the range 1 to n-j */ + len = ColCount [j] ; + len = MAX (1, len) ; + len = MIN (len, n-j) ; + + /* compute len in double to avoid integer overflow */ + PRINT1 (("ColCount ["ID"] = "ID"\n", j, len)) ; + if (grow) + { + xlen = (double) len ; + xlen = grow1 * xlen + grow2 ; + xlen = MIN (xlen, n-j) ; + len = (Int) xlen ; + } + ASSERT (len >= 1 && len <= n-j) ; + lnz += len ; + ok = (lnz >= 0) ; + } + if (ok) + { + Lp [n] = lnz ; + if (grow) + { + /* add extra space */ + xlnz = (double) lnz ; + xlnz *= grow0 ; + xlnz = MIN (xlnz, Size_max) ; + xlnz = MIN (xlnz, ((double) n * (double) n + (double) n) / 2) ; + lnz = (Int) xlnz ; + } + } + } + + lnz = MAX (1, lnz) ; + + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + } + + /* allocate L->i, L->x, and L->z */ + PRINT1 (("resizing from zero size to lnz "ID"\n", lnz)) ; + ASSERT (L->nzmax == 0) ; + e = (to_xtype == CHOLMOD_COMPLEX ? 2 : 1) ; + if (!ok || !CHOLMOD(realloc_multiple) (lnz, 1, to_xtype, &(L->i), NULL, + &(L->x), &(L->z), &(L->nzmax), Common)) + { + L->p = CHOLMOD(free) (n+1, sizeof (Int), L->p, Common) ; + L->nz = CHOLMOD(free) (n, sizeof (Int), L->nz, Common) ; + L->prev = CHOLMOD(free) (n+2, sizeof (Int), L->prev, Common) ; + L->next = CHOLMOD(free) (n+2, sizeof (Int), L->next, Common) ; + L->i = CHOLMOD(free) (lnz, sizeof (Int), L->i, Common) ; + L->x = CHOLMOD(free) (lnz, e*sizeof (double), L->x, Common) ; + L->z = CHOLMOD(free) (lnz, sizeof (double), L->z, Common) ; + PRINT1 (("cannot realloc simplicial numeric\n")) ; + return ; /* out of memory */ + } + + /* ============================================== commit the changes to L */ + + /* initialize L to be the identity matrix */ + L->xtype = to_xtype ; + L->dtype = DTYPE ; + L->minor = n ; + + Li = L->i ; + Lx = L->x ; + Lz = L->z ; + +#if 0 + if (lnz == 1) + { + /* the user won't expect to access this entry, but some CHOLMOD + * routines may. Set it to zero so that valgrind doesn't complain. */ + switch (to_xtype) + { + case CHOLMOD_REAL: + Lx [0] = 0 ; + break ; + + case CHOLMOD_COMPLEX: + Lx [0] = 0 ; + Lx [1] = 0 ; + break ; + + case CHOLMOD_ZOMPLEX: + Lx [0] = 0 ; + Lz [0] = 0 ; + break ; + } + } +#endif + + if (packed >= 0) + { + /* create the unit diagonal for either the LL' or LDL' case */ + + switch (L->xtype) + { + case CHOLMOD_REAL: + for (j = 0 ; j < n ; j++) + { + ASSERT (Lp [j] < Lp [j+1]) ; + p = Lp [j] ; + Li [p] = j ; + Lx [p] = 1 ; + } + break ; + + case CHOLMOD_COMPLEX: + for (j = 0 ; j < n ; j++) + { + ASSERT (Lp [j] < Lp [j+1]) ; + p = Lp [j] ; + Li [p] = j ; + Lx [2*p ] = 1 ; + Lx [2*p+1] = 0 ; + } + break ; + + case CHOLMOD_ZOMPLEX: + for (j = 0 ; j < n ; j++) + { + ASSERT (Lp [j] < Lp [j+1]) ; + p = Lp [j] ; + Li [p] = j ; + Lx [p] = 1 ; + Lz [p] = 0 ; + } + break ; + } + } + + L->is_ll = to_ll ; + + PRINT1 (("done convert simplicial symbolic to numeric\n")) ; +} + + +/* ========================================================================== */ +/* === change_simplicial_numeric ============================================ */ +/* ========================================================================== */ + +/* Change LL' to LDL', LDL' to LL', or leave as-is. + * + * If to_packed is TRUE, then the columns of L are packed and made monotonic + * (to_monotonic is ignored; it is implicitly TRUE). + * + * If to_monotonic is TRUE but to_packed is FALSE, the columns of L are made + * monotonic but not packed. + * + * If both to_packed and to_monotonic are FALSE, then the columns of L are + * left as-is, and the conversion is done in place. + * + * If L is already monotonic, or if it is to be left non-monotonic, then this + * conversion always succeeds. + * + * When converting an LDL' to LL' factorization, any column with a negative + * or zero diagonal entry is not modified so that conversion back to LDL' will + * succeed. This can result in a matrix L with a negative entry on the diagonal + * If the kth entry on the diagonal of D is negative, it and the kth column of + * L are left unchanged. A subsequent conversion back to an LDL' form will also + * leave the column unchanged, so the correct LDL' factorization will be + * restored. L->minor is set to the smallest k for which D (k,k) is negative. + */ + +static void change_simplicial_numeric +( + cholmod_factor *L, + int to_ll, + int to_packed, + int to_monotonic, + cholmod_common *Common +) +{ + double grow0, grow1, xlen, xlnz ; + void *newLi, *newLx, *newLz ; + double *Lx, *Lz ; + Int *Lp, *Li, *Lnz ; + Int make_monotonic, grow2, n, j, lnz, len, grow, ok, make_ll, make_ldl ; + size_t nzmax0 ; + + PRINT1 (("\n===Change simplicial numeric: %d %d %d\n", + to_ll, to_packed, to_monotonic)) ; + DEBUG (CHOLMOD(dump_factor) (L, "change simplicial numeric", Common)) ; + ASSERT (L->xtype != CHOLMOD_PATTERN && !(L->is_super)) ; + + make_monotonic = ((to_packed || to_monotonic) && !(L->is_monotonic)) ; + make_ll = (to_ll && !(L->is_ll)) ; + make_ldl = (!to_ll && L->is_ll) ; + + n = L->n ; + Lp = L->p ; + Li = L->i ; + Lx = L->x ; + Lz = L->z ; + Lnz = L->nz ; + + grow = FALSE ; + grow0 = Common->grow0 ; + grow1 = Common->grow1 ; + grow2 = Common->grow2 ; + grow0 = IS_NAN (grow0) ? 1 : grow0 ; + grow1 = IS_NAN (grow1) ? 1 : grow1 ; + ok = TRUE ; + newLi = NULL ; + newLx = NULL ; + newLz = NULL ; + lnz = 0 ; + + if (make_monotonic) + { + + /* ------------------------------------------------------------------ */ + /* Columns out of order, but will be reordered and optionally packed. */ + /* ------------------------------------------------------------------ */ + + PRINT1 (("L is non-monotonic\n")) ; + + /* compute new L->nzmax */ + if (!to_packed) + { + /* if any parameter is NaN, grow is false */ + /* fl.pt. comparisons below are false if any parameter is NaN */ + grow = (grow0 >= 1.0) && (grow1 >= 1.0) && (grow2 > 0) ; + } + for (j = 0 ; ok && j < n ; j++) + { + len = Lnz [j] ; + ASSERT (len >= 1 && len <= n-j) ; + + /* compute len in double to avoid integer overflow */ + if (grow) + { + xlen = (double) len ; + xlen = grow1 * xlen + grow2 ; + xlen = MIN (xlen, n-j) ; + len = (Int) xlen ; + } + ASSERT (len >= Lnz [j] && len <= n-j) ; + + PRINT2 (("j: "ID" Lnz[j] "ID" len "ID" p "ID"\n", + j, Lnz [j], len, lnz)) ; + + lnz += len ; + ok = (lnz >= 0) ; + } + + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return ; + } + + if (grow) + { + xlnz = (double) lnz ; + xlnz *= grow0 ; + xlnz = MIN (xlnz, Size_max) ; + xlnz = MIN (xlnz, ((double) n * (double) n + (double) n) / 2) ; + lnz = (Int) xlnz ; + } + + lnz = MAX (1, lnz) ; + PRINT1 (("final lnz "ID"\n", lnz)) ; + nzmax0 = 0 ; + + CHOLMOD(realloc_multiple) (lnz, 1, L->xtype, &newLi, NULL, + &newLx, &newLz, &nzmax0, Common) ; + + if (Common->status < CHOLMOD_OK) + { + return ; /* out of memory */ + } + } + + /* ============================================== commit the changes to L */ + + /* ---------------------------------------------------------------------- */ + /* convert the simplicial L, using template routine */ + /* ---------------------------------------------------------------------- */ + + switch (L->xtype) + { + + case CHOLMOD_REAL: + r_change_simplicial_numeric (L, to_ll, to_packed, + newLi, newLx, newLz, lnz, grow, grow1, grow2, + make_ll, make_monotonic, make_ldl, Common) ; + break ; + + case CHOLMOD_COMPLEX: + c_change_simplicial_numeric (L, to_ll, to_packed, + newLi, newLx, newLz, lnz, grow, grow1, grow2, + make_ll, make_monotonic, make_ldl, Common) ; + break ; + + case CHOLMOD_ZOMPLEX: + z_change_simplicial_numeric (L, to_ll, to_packed, + newLi, newLx, newLz, lnz, grow, grow1, grow2, + make_ll, make_monotonic, make_ldl, Common) ; + break ; + } + + DEBUG (CHOLMOD(dump_factor) (L, "L simplicial changed", Common)) ; +} + + +/* ========================================================================== */ +/* === ll_super_to_simplicial_numeric ======================================= */ +/* ========================================================================== */ + +/* Convert a supernodal numeric factorization to any simplicial numeric one. + * Leaves L->xtype unchanged (real or complex, not zomplex since there is + * no supernodal zomplex L). */ + +static void ll_super_to_simplicial_numeric +( + cholmod_factor *L, + int to_packed, + int to_ll, + cholmod_common *Common +) +{ + Int *Ls, *Lpi, *Lpx, *Super, *Li ; + Int n, lnz, s, nsuper, psi, psend, nsrow, nscol, k1, k2, erows ; + + DEBUG (CHOLMOD(dump_factor) (L, "start LL super to simplicial", Common)) ; + PRINT1 (("super -> simplicial (%d %d)\n", to_packed, to_ll)) ; + ASSERT (L->xtype != CHOLMOD_PATTERN && L->is_ll && L->is_super) ; + ASSERT (L->x != NULL && L->i == NULL) ; + + n = L->n ; + nsuper = L->nsuper ; + Lpi = L->pi ; + Lpx = L->px ; + Ls = L->s ; + Super = L->super ; + + /* Int overflow cannot occur since supernodal L already exists */ + + if (to_packed) + { + /* count the number of nonzeros in L. Each supernode is of the form + * + * l . . . For this example, nscol = 4 (# columns). nsrow = 9. + * l l . . The "." entries are allocated in the supernodal + * l l l . factor, but not used. They are not copied to the + * l l l l simplicial factor. Some "l" and "e" entries may be + * e e e e numerically zero and even symbolically zero if a + * e e e e tight simplicial factorization or resymbol were + * e e e e done, because of numerical cancellation and relaxed + * e e e e supernode amalgamation, respectively. + * e e e e + */ + lnz = 0 ; + for (s = 0 ; s < nsuper ; s++) + { + k1 = Super [s] ; + k2 = Super [s+1] ; + psi = Lpi [s] ; + psend = Lpi [s+1] ; + nsrow = psend - psi ; + nscol = k2 - k1 ; + ASSERT (nsrow >= nscol) ; + erows = nsrow - nscol ; + + /* lower triangular part, including the diagonal, + * counting the "l" terms in the figure above. */ + lnz += nscol * (nscol+1) / 2 ; + + /* rectangular part, below the diagonal block (the "e" terms) */ + lnz += nscol * erows ; + } + ASSERT (lnz <= (Int) (L->xsize)) ; + } + else + { + /* Li will be the same size as Lx */ + lnz = L->xsize ; + } + ASSERT (lnz >= 0) ; + PRINT1 (("simplicial lnz = "ID" to_packed: %d to_ll: %d L->xsize %g\n", + lnz, to_ll, to_packed, (double) L->xsize)) ; + + Li = CHOLMOD(malloc) (lnz, sizeof (Int), Common) ; + if (Common->status < CHOLMOD_OK) + { + return ; /* out of memory */ + } + + if (!allocate_simplicial_numeric (L, Common)) + { + CHOLMOD(free) (lnz, sizeof (Int), Li, Common) ; + return ; /* out of memory */ + } + + /* ============================================== commit the changes to L */ + + L->i = Li ; + L->nzmax = lnz ; + + /* ---------------------------------------------------------------------- */ + /* convert the supernodal L, using template routine */ + /* ---------------------------------------------------------------------- */ + + switch (L->xtype) + { + + case CHOLMOD_REAL: + r_ll_super_to_simplicial_numeric (L, to_packed, to_ll, Common) ; + break ; + + case CHOLMOD_COMPLEX: + c_ll_super_to_simplicial_numeric (L, to_packed, to_ll, Common) ; + break ; + } + + /* ---------------------------------------------------------------------- */ + /* free unused parts of L */ + /* ---------------------------------------------------------------------- */ + + L->super = CHOLMOD(free) (nsuper+1, sizeof (Int), L->super, Common) ; + L->pi = CHOLMOD(free) (nsuper+1, sizeof (Int), L->pi, Common) ; + L->px = CHOLMOD(free) (nsuper+1, sizeof (Int), L->px, Common) ; + L->s = CHOLMOD(free) (L->ssize, sizeof (Int), L->s, Common) ; + + L->ssize = 0 ; + L->xsize = 0 ; + L->nsuper = 0 ; + L->maxesize = 0 ; + L->maxcsize = 0 ; + + L->is_super = FALSE ; + + DEBUG (CHOLMOD(dump_factor) (L, "done LL super to simplicial", Common)) ; +} + + +/* ========================================================================== */ +/* === super_symbolic_to_ll_super =========================================== */ +/* ========================================================================== */ + +/* Convert a supernodal symbolic factorization to a supernodal numeric + * factorization by allocating L->x. Contents of L->x are undefined. + */ + +static int super_symbolic_to_ll_super +( + int to_xtype, + cholmod_factor *L, + cholmod_common *Common +) +{ + double *Lx ; + Int wentry = (to_xtype == CHOLMOD_REAL) ? 1 : 2 ; + PRINT1 (("convert super sym to num\n")) ; + ASSERT (L->xtype == CHOLMOD_PATTERN && L->is_super) ; + Lx = CHOLMOD(malloc) (L->xsize, wentry * sizeof (double), Common) ; + PRINT1 (("xsize %g\n", (double) L->xsize)) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; /* out of memory */ + } + + /* ============================================== commit the changes to L */ + + if (L->xsize == 1) + { + /* the caller won't expect to access this entry, but some CHOLMOD + * routines may. Set it to zero so that valgrind doesn't complain. */ + switch (to_xtype) + { + case CHOLMOD_REAL: + Lx [0] = 0 ; + break ; + + case CHOLMOD_COMPLEX: + Lx [0] = 0 ; + Lx [1] = 0 ; + break ; + } + } + + L->x = Lx ; + L->xtype = to_xtype ; + L->dtype = DTYPE ; + L->minor = L->n ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_change_factor ================================================ */ +/* ========================================================================== */ + +/* Convert a factor L. Some conversions simply allocate uninitialized space + * that meant to be filled later. + * + * If the conversion fails, the factor is left in its original form, with one + * exception. Converting a supernodal symbolic factor to a simplicial numeric + * one (with L=D=I) may leave the factor in simplicial symbolic form. + * + * Memory allocated for each conversion is listed below. + */ + +int CHOLMOD(change_factor) +( + /* ---- input ---- */ + int to_xtype, /* convert to CHOLMOD_PATTERN, _REAL, _COMPLEX, or + * _ZOMPLEX */ + int to_ll, /* TRUE: convert to LL', FALSE: LDL' */ + int to_super, /* TRUE: convert to supernodal, FALSE: simplicial */ + int to_packed, /* TRUE: pack simplicial columns, FALSE: do not pack */ + int to_monotonic, /* TRUE: put simplicial columns in order, FALSE: not */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) +{ + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + if (to_xtype < CHOLMOD_PATTERN || to_xtype > CHOLMOD_ZOMPLEX) + { + ERROR (CHOLMOD_INVALID, "xtype invalid") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + PRINT1 (("-----convert from (%d,%d,%d,%d,%d) to (%d,%d,%d,%d,%d)\n", + L->xtype, L->is_ll, L->is_super, L_is_packed (L, Common), L->is_monotonic, + to_xtype, to_ll, to_super, to_packed, to_monotonic)) ; + + /* ensure all parameters are TRUE/FALSE */ + to_ll = BOOLEAN (to_ll) ; + to_super = BOOLEAN (to_super) ; + + ASSERT (BOOLEAN (L->is_ll) == L->is_ll) ; + ASSERT (BOOLEAN (L->is_super) == L->is_super) ; + + if (to_super && to_xtype == CHOLMOD_ZOMPLEX) + { + ERROR (CHOLMOD_INVALID, "supernodal zomplex L not supported") ; + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* convert */ + /* ---------------------------------------------------------------------- */ + + if (to_xtype == CHOLMOD_PATTERN) + { + + /* ------------------------------------------------------------------ */ + /* convert to symbolic */ + /* ------------------------------------------------------------------ */ + + if (!to_super) + { + + /* -------------------------------------------------------------- */ + /* convert any factor into a simplicial symbolic factor */ + /* -------------------------------------------------------------- */ + + any_to_simplicial_symbolic (L, to_ll, Common) ; /* cannot fail */ + + } + else + { + + /* -------------------------------------------------------------- */ + /* convert to a supernodal symbolic factor */ + /* -------------------------------------------------------------- */ + + if (L->xtype != CHOLMOD_PATTERN && L->is_super) + { + /* convert from supernodal numeric to supernodal symbolic. + * this preserves symbolic pattern of L, discards numeric + * values */ + ll_super_to_super_symbolic (L, Common) ; /* cannot fail */ + } + else if (L->xtype == CHOLMOD_PATTERN && !(L->is_super)) + { + /* convert from simplicial symbolic to supernodal symbolic. + * contents of supernodal pattern are uninitialized. Not meant + * for the end user. */ + simplicial_symbolic_to_super_symbolic (L, Common) ; + } + else + { + /* cannot convert from simplicial numeric to supernodal + * symbolic */ + ERROR (CHOLMOD_INVALID, + "cannot convert L to supernodal symbolic") ; + } + } + + } + else + { + + /* ------------------------------------------------------------------ */ + /* convert to numeric */ + /* ------------------------------------------------------------------ */ + + if (to_super) + { + + /* -------------------------------------------------------------- */ + /* convert to supernodal numeric factor */ + /* -------------------------------------------------------------- */ + + if (L->xtype == CHOLMOD_PATTERN) + { + if (L->is_super) + { + /* Convert supernodal symbolic to supernodal numeric. + * Contents of supernodal numeric values are uninitialized. + * This is used by cholmod_super_numeric. Not meant for + * the end user. */ + super_symbolic_to_ll_super (to_xtype, L, Common) ; + } + else + { + /* Convert simplicial symbolic to supernodal numeric. + * Contents not defined. This is used by + * Core/cholmod_copy_factor only. Not meant for the end + * user. */ + if (!simplicial_symbolic_to_super_symbolic (L, Common)) + { + /* failure, convert back to simplicial symbolic */ + any_to_simplicial_symbolic (L, to_ll, Common) ; + } + else + { + /* conversion to super symbolic OK, allocate numeric + * part */ + super_symbolic_to_ll_super (to_xtype, L, Common) ; + } + } + } + else + { + /* nothing to do if L is already in supernodal numeric form */ + if (!(L->is_super)) + { + ERROR (CHOLMOD_INVALID, + "cannot convert simplicial L to supernodal") ; + } + /* FUTURE WORK: convert to/from supernodal LL' and LDL' */ + } + + } + else + { + + /* -------------------------------------------------------------- */ + /* convert any factor to simplicial numeric */ + /* -------------------------------------------------------------- */ + + if (L->xtype == CHOLMOD_PATTERN && !(L->is_super)) + { + + /* ---------------------------------------------------------- */ + /* convert simplicial symbolic to simplicial numeric (L=I,D=I)*/ + /* ---------------------------------------------------------- */ + + simplicial_symbolic_to_simplicial_numeric (L, to_ll, to_packed, + to_xtype, Common) ; + + } + else if (L->xtype != CHOLMOD_PATTERN && L->is_super) + { + + /* ---------------------------------------------------------- */ + /* convert a supernodal LL' to simplicial numeric */ + /* ---------------------------------------------------------- */ + + ll_super_to_simplicial_numeric (L, to_packed, to_ll, Common) ; + + } + else if (L->xtype == CHOLMOD_PATTERN && L->is_super) + { + + /* ---------------------------------------------------------- */ + /* convert a supernodal symbolic to simplicial numeric (L=D=I)*/ + /* ---------------------------------------------------------- */ + + any_to_simplicial_symbolic (L, to_ll, Common) ; + /* if the following fails, it leaves the factor in simplicial + * symbolic form */ + simplicial_symbolic_to_simplicial_numeric (L, to_ll, to_packed, + to_xtype, Common) ; + + } + else + { + + /* ---------------------------------------------------------- */ + /* change a simplicial numeric factor */ + /* ---------------------------------------------------------- */ + + /* change LL' to LDL', LDL' to LL', or leave as-is. pack the + * columns of L, or leave as-is. Ensure the columns are + * monotonic, or leave as-is. */ + + change_simplicial_numeric (L, to_ll, to_packed, to_monotonic, + Common) ; + } + } + } + + /* ---------------------------------------------------------------------- */ + /* return result */ + /* ---------------------------------------------------------------------- */ + + return (Common->status >= CHOLMOD_OK) ; +} diff --git a/src/CHOLMOD/Core/cholmod_common.c b/src/CHOLMOD/Core/cholmod_common.c new file mode 100644 index 0000000..2394fab --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_common.c @@ -0,0 +1,701 @@ +/* ========================================================================== */ +/* === Core/cholmod_common ================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Core utility routines for the cholmod_common object: + * + * Primary routines: + * ----------------- + * cholmod_start the first call to CHOLMOD + * cholmod_finish the last call to CHOLMOD + * + * Secondary routines: + * ------------------- + * cholmod_defaults restore (most) default control parameters + * cholmod_allocate_work allocate (or reallocate) workspace in Common + * cholmod_free_work free workspace in Common + * cholmod_clear_flag clear Common->Flag in workspace + * cholmod_maxrank column dimension of Common->Xwork workspace + * + * The Common object is unique. It cannot be allocated or deallocated by + * CHOLMOD, since it contains the definition of the memory management routines + * used (pointers to malloc, free, realloc, and calloc, or their equivalent). + * The Common object contains workspace that is used between calls to + * CHOLMOD routines. This workspace allocated by CHOLMOD as needed, by + * cholmod_allocate_work and cholmod_free_work. + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + +/* ========================================================================== */ +/* === cholmod_start ======================================================== */ +/* ========================================================================== */ + +/* Initialize Common default parameters and statistics. Sets workspace + * pointers to NULL. + * + * This routine must be called just once, prior to calling any other CHOLMOD + * routine. Do not call this routine after any other CHOLMOD routine (except + * cholmod_finish, to start a new CHOLMOD session), or a memory leak will + * occur. + * + * workspace: none + */ + +int CHOLMOD(start) +( + cholmod_common *Common +) +{ + int k ; + + if (Common == NULL) + { + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* user error handling routine */ + /* ---------------------------------------------------------------------- */ + + Common->error_handler = NULL ; + + /* ---------------------------------------------------------------------- */ + /* integer and numerical types */ + /* ---------------------------------------------------------------------- */ + + Common->itype = ITYPE ; + Common->dtype = DTYPE ; + + /* ---------------------------------------------------------------------- */ + /* default control parameters */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(defaults) (Common) ; + Common->try_catch = FALSE ; + + /* ---------------------------------------------------------------------- */ + /* memory management routines */ + /* ---------------------------------------------------------------------- */ + + /* The user can replace cholmod's memory management routines by redefining + * these function pointers. */ + +#ifndef NMALLOC + /* stand-alone ANSI C program */ + Common->malloc_memory = malloc ; + Common->free_memory = free ; + Common->realloc_memory = realloc ; + Common->calloc_memory = calloc ; +#else + /* no memory manager defined at compile-time; MUST define one at run-time */ + Common->malloc_memory = NULL ; + Common->free_memory = NULL ; + Common->realloc_memory = NULL ; + Common->calloc_memory = NULL ; +#endif + + /* ---------------------------------------------------------------------- */ + /* complex arithmetic routines */ + /* ---------------------------------------------------------------------- */ + + Common->complex_divide = CHOLMOD(divcomplex) ; + Common->hypotenuse = CHOLMOD(hypot) ; + + /* ---------------------------------------------------------------------- */ + /* print routine */ + /* ---------------------------------------------------------------------- */ + +#ifndef NPRINT + /* stand-alone ANSI C program */ + Common->print_function = printf ; +#else + /* printing disabled */ + Common->print_function = NULL ; +#endif + + /* ---------------------------------------------------------------------- */ + /* workspace */ + /* ---------------------------------------------------------------------- */ + + /* This code assumes the workspace held in Common is not initialized. If + * it is, then a memory leak will occur because the pointers are + * overwritten with NULL. */ + + Common->nrow = 0 ; + Common->mark = EMPTY ; + Common->xworksize = 0 ; + Common->iworksize = 0 ; + Common->Flag = NULL ; + Common->Head = NULL ; + Common->Iwork = NULL ; + Common->Xwork = NULL ; + Common->no_workspace_reallocate = FALSE ; + + /* ---------------------------------------------------------------------- */ + /* statistics */ + /* ---------------------------------------------------------------------- */ + + /* fl and lnz are computed in cholmod_analyze and cholmod_rowcolcounts */ + Common->fl = EMPTY ; + Common->lnz = EMPTY ; + + /* modfl is computed in cholmod_updown, cholmod_rowadd, and cholmod_rowdel*/ + Common->modfl = EMPTY ; + + /* all routines use status as their error-report code */ + Common->status = CHOLMOD_OK ; + + Common->malloc_count = 0 ; /* # calls to malloc minus # calls to free */ + Common->memory_usage = 0 ; /* peak memory usage (in bytes) */ + Common->memory_inuse = 0 ; /* current memory in use (in bytes) */ + + Common->nrealloc_col = 0 ; + Common->nrealloc_factor = 0 ; + Common->ndbounds_hit = 0 ; + Common->rowfacfl = 0 ; + Common->aatfl = EMPTY ; + + /* Common->called_nd is TRUE if cholmod_analyze called or NESDIS */ + Common->called_nd = FALSE ; + + Common->blas_ok = TRUE ; /* false if BLAS int overflow occurs */ + + /* ---------------------------------------------------------------------- */ + /* default SuiteSparseQR knobs and statististics */ + /* ---------------------------------------------------------------------- */ + + for (k = 0 ; k < 4 ; k++) Common->SPQR_xstat [k] = 0 ; + for (k = 0 ; k < 10 ; k++) Common->SPQR_istat [k] = 0 ; + + for (k = 0 ; k < 10 ; k++) Common->other1 [k] = 0 ; + for (k = 0 ; k < 6 ; k++) Common->other2 [k] = 0 ; + for (k = 0 ; k < 10 ; k++) Common->other3 [k] = 0 ; + for (k = 0 ; k < 16 ; k++) Common->other4 [k] = 0 ; + for (k = 0 ; k < 16 ; k++) Common->other5 [k] = (void *) NULL ; + + Common->SPQR_grain = 1 ; /* no Intel TBB multitasking, by default */ + Common->SPQR_small = 1e6 ; /* target min task size for TBB */ + Common->SPQR_shrink = 1 ; /* controls SPQR shrink realloc */ + Common->SPQR_nthreads = 0 ; /* 0: let TBB decide how many threads to use */ + + /* ---------------------------------------------------------------------- */ + /* GPU initializations */ + /* ---------------------------------------------------------------------- */ + +#ifdef GPU_BLAS + Common->cublasHandle = NULL ; + Common->cudaStreamSyrk = NULL ; + Common->cudaStreamGemm = NULL ; + Common->cudaStreamTrsm = NULL ; + Common->cudaStreamPotrf [0] = NULL ; + Common->cudaStreamPotrf [1] = NULL ; + Common->cudaStreamPotrf [2] = NULL ; + Common->cublasEventPotrf [0] = NULL ; + Common->cublasEventPotrf [1] = NULL ; + Common->HostPinnedMemory = NULL ; + Common->devPotrfWork = NULL ; + Common->devSyrkGemmPtrLx = NULL ; + Common->devSyrkGemmPtrC = NULL ; + Common->GemmUsed = 0 ; + Common->SyrkUsed = 0 ; + Common->syrkStart = 0 ; +#endif + + DEBUG_INIT ("cholmod start", Common) ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_defaults ===================================================== */ +/* ========================================================================== */ + +/* Set Common default parameters, except for the function pointers. + * + * workspace: none + */ + +int CHOLMOD(defaults) +( + cholmod_common *Common +) +{ + Int i ; + + RETURN_IF_NULL_COMMON (FALSE) ; + + /* ---------------------------------------------------------------------- */ + /* default control parameters */ + /* ---------------------------------------------------------------------- */ + + Common->dbound = 0.0 ; + Common->grow0 = 1.2 ; + Common->grow1 = 1.2 ; + Common->grow2 = 5 ; + Common->maxrank = 8 ; + + Common->final_asis = TRUE ; + Common->final_super = TRUE ; + Common->final_ll = FALSE ; + Common->final_pack = TRUE ; + Common->final_monotonic = TRUE ; + Common->final_resymbol = FALSE ; + + /* use simplicial factorization if flop/nnz(L) < 40, supernodal otherwise */ + Common->supernodal = CHOLMOD_AUTO ; + Common->supernodal_switch = 40 ; + + Common->nrelax [0] = 4 ; + Common->nrelax [1] = 16 ; + Common->nrelax [2] = 48 ; + Common->zrelax [0] = 0.8 ; + Common->zrelax [1] = 0.1 ; + Common->zrelax [2] = 0.05 ; + + Common->prefer_zomplex = FALSE ; + Common->prefer_upper = TRUE ; + Common->prefer_binary = FALSE ; + Common->quick_return_if_not_posdef = FALSE ; + + /* METIS workarounds */ + Common->metis_memory = 0.0 ; /* > 0 for memory guard (2 is reasonable) */ + Common->metis_nswitch = 3000 ; + Common->metis_dswitch = 0.66 ; + + Common->print = 3 ; + Common->precise = FALSE ; + + /* ---------------------------------------------------------------------- */ + /* default ordering methods */ + /* ---------------------------------------------------------------------- */ + + /* Note that if the Partition module is not installed, the CHOLMOD_METIS + * and CHOLMOD_NESDIS methods will not be available. cholmod_analyze will + * report the CHOLMOD_NOT_INSTALLED error, and safely skip over them. + */ + +#if (CHOLMOD_MAXMETHODS < 9) +#error "CHOLMOD_MAXMETHODS must be 9 or more (defined in cholmod_core.h)." +#endif + + /* default strategy: try given, AMD, and then METIS if AMD reports high + * fill-in. NESDIS can be used instead, if Common->default_nesdis is TRUE. + */ + Common->nmethods = 0 ; /* use default strategy */ + Common->default_nesdis = FALSE ; /* use METIS in default strategy */ + + Common->current = 0 ; /* current method being tried */ + Common->selected = 0 ; /* the best method selected */ + + /* first, fill each method with default parameters */ + for (i = 0 ; i <= CHOLMOD_MAXMETHODS ; i++) + { + /* CHOLMOD's default method is AMD for A or AA' */ + Common->method [i].ordering = CHOLMOD_AMD ; + + /* CHOLMOD nested dissection and minimum degree parameter */ + Common->method [i].prune_dense = 10.0 ; /* dense row/col control */ + + /* min degree parameters (AMD, COLAMD, SYMAMD, CAMD, CCOLAMD, CSYMAMD)*/ + Common->method [i].prune_dense2 = -1 ; /* COLAMD dense row control */ + Common->method [i].aggressive = TRUE ; /* aggressive absorption */ + Common->method [i].order_for_lu = FALSE ;/* order for Cholesky not LU */ + + /* CHOLMOD's nested dissection (METIS + constrained AMD) */ + Common->method [i].nd_small = 200 ; /* small graphs aren't cut */ + Common->method [i].nd_compress = TRUE ; /* compress graph & subgraphs */ + Common->method [i].nd_camd = 1 ; /* use CAMD */ + Common->method [i].nd_components = FALSE ; /* lump connected comp. */ + Common->method [i].nd_oksep = 1.0 ; /* sep ok if < oksep*n */ + + /* statistics for each method are not yet computed */ + Common->method [i].fl = EMPTY ; + Common->method [i].lnz = EMPTY ; + } + + Common->postorder = TRUE ; /* follow ordering with weighted postorder */ + + /* Next, define some methods. The first five use default parameters. */ + Common->method [0].ordering = CHOLMOD_GIVEN ; /* skip if UserPerm NULL */ + Common->method [1].ordering = CHOLMOD_AMD ; + Common->method [2].ordering = CHOLMOD_METIS ; + Common->method [3].ordering = CHOLMOD_NESDIS ; + Common->method [4].ordering = CHOLMOD_NATURAL ; + + /* CHOLMOD's nested dissection with large leaves of separator tree */ + Common->method [5].ordering = CHOLMOD_NESDIS ; + Common->method [5].nd_small = 20000 ; + + /* CHOLMOD's nested dissection with tiny leaves, and no AMD ordering */ + Common->method [6].ordering = CHOLMOD_NESDIS ; + Common->method [6].nd_small = 4 ; + Common->method [6].nd_camd = 0 ; /* no CSYMAMD or CAMD */ + + /* CHOLMOD's nested dissection with no dense node removal */ + Common->method [7].ordering = CHOLMOD_NESDIS ; + Common->method [7].prune_dense = -1. ; + + /* COLAMD for A*A', AMD for A */ + Common->method [8].ordering = CHOLMOD_COLAMD ; + + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_finish ======================================================= */ +/* ========================================================================== */ + +/* The last call to CHOLMOD must be cholmod_finish. You may call this routine + * more than once, and can safely call any other CHOLMOD routine after calling + * it (including cholmod_start). + * + * The statistics and parameter settings in Common are preserved. The + * workspace in Common is freed. This routine is just another name for + * cholmod_free_work. + */ + +int CHOLMOD(finish) +( + cholmod_common *Common +) +{ + return (CHOLMOD(free_work) (Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_allocate_work ================================================ */ +/* ========================================================================== */ + +/* Allocate and initialize workspace for CHOLMOD routines, or increase the size + * of already-allocated workspace. If enough workspace is already allocated, + * then nothing happens. + * + * workspace: Flag (nrow), Head (nrow+1), Iwork (iworksize), Xwork (xworksize) + */ + +int CHOLMOD(allocate_work) +( + /* ---- input ---- */ + size_t nrow, /* # of rows in the matrix A */ + size_t iworksize, /* size of Iwork */ + size_t xworksize, /* size of Xwork */ + /* --------------- */ + cholmod_common *Common +) +{ + double *W ; + Int *Head ; + Int i ; + size_t nrow1 ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* Allocate Flag (nrow) and Head (nrow+1) */ + /* ---------------------------------------------------------------------- */ + + nrow = MAX (1, nrow) ; + + /* nrow1 = nrow + 1 */ + nrow1 = CHOLMOD(add_size_t) (nrow, 1, &ok) ; + if (!ok) + { + /* nrow+1 causes size_t overflow ; problem is too large */ + Common->status = CHOLMOD_TOO_LARGE ; + CHOLMOD(free_work) (Common) ; + return (FALSE) ; + } + + if (nrow > Common->nrow) + { + + if (Common->no_workspace_reallocate) + { + /* CHOLMOD is not allowed to change the workspace here */ + Common->status = CHOLMOD_INVALID ; + return (FALSE) ; + } + + /* free the old workspace (if any) and allocate new space */ + Common->Flag = CHOLMOD(free) (Common->nrow, sizeof (Int), Common->Flag, + Common) ; + Common->Head = CHOLMOD(free) (Common->nrow+1,sizeof (Int), Common->Head, + Common) ; + Common->Flag = CHOLMOD(malloc) (nrow, sizeof (Int), Common) ; + Common->Head = CHOLMOD(malloc) (nrow1, sizeof (Int), Common) ; + + /* record the new size of Flag and Head */ + Common->nrow = nrow ; + + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_work) (Common) ; + return (FALSE) ; + } + + /* initialize Flag and Head */ + Common->mark = EMPTY ; + CHOLMOD(clear_flag) (Common) ; + Head = Common->Head ; + for (i = 0 ; i <= (Int) (nrow) ; i++) + { + Head [i] = EMPTY ; + } + } + + /* ---------------------------------------------------------------------- */ + /* Allocate Iwork (iworksize) */ + /* ---------------------------------------------------------------------- */ + + iworksize = MAX (1, iworksize) ; + if (iworksize > Common->iworksize) + { + + if (Common->no_workspace_reallocate) + { + /* CHOLMOD is not allowed to change the workspace here */ + Common->status = CHOLMOD_INVALID ; + return (FALSE) ; + } + + /* free the old workspace (if any) and allocate new space. + * integer overflow safely detected in cholmod_malloc */ + CHOLMOD(free) (Common->iworksize, sizeof (Int), Common->Iwork, Common) ; + Common->Iwork = CHOLMOD(malloc) (iworksize, sizeof (Int), Common) ; + + /* record the new size of Iwork */ + Common->iworksize = iworksize ; + + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_work) (Common) ; + return (FALSE) ; + } + + /* note that Iwork does not need to be initialized */ + } + + /* ---------------------------------------------------------------------- */ + /* Allocate Xwork (xworksize) and set it to ((double) 0.) */ + /* ---------------------------------------------------------------------- */ + + /* make sure xworksize is >= 1 */ + xworksize = MAX (1, xworksize) ; + if (xworksize > Common->xworksize) + { + + if (Common->no_workspace_reallocate) + { + /* CHOLMOD is not allowed to change the workspace here */ + Common->status = CHOLMOD_INVALID ; + return (FALSE) ; + } + + /* free the old workspace (if any) and allocate new space */ + CHOLMOD(free) (Common->xworksize, sizeof (double), Common->Xwork, + Common) ; + Common->Xwork = CHOLMOD(malloc) (xworksize, sizeof (double), Common) ; + + /* record the new size of Xwork */ + Common->xworksize = xworksize ; + + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_work) (Common) ; + return (FALSE) ; + } + + /* initialize Xwork */ + W = Common->Xwork ; + for (i = 0 ; i < (Int) xworksize ; i++) + { + W [i] = 0. ; + } + } + + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_free_work ==================================================== */ +/* ========================================================================== */ + +/* Deallocate the CHOLMOD workspace. + * + * workspace: deallocates all workspace in Common + */ + +int CHOLMOD(free_work) +( + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + Common->Flag = CHOLMOD(free) (Common->nrow, sizeof (Int), + Common->Flag, Common) ; + Common->Head = CHOLMOD(free) (Common->nrow+1, sizeof (Int), + Common->Head, Common) ; + Common->Iwork = CHOLMOD(free) (Common->iworksize, sizeof (Int), + Common->Iwork, Common) ; + Common->Xwork = CHOLMOD(free) (Common->xworksize, sizeof (double), + Common->Xwork, Common) ; + Common->nrow = 0 ; + Common->iworksize = 0 ; + Common->xworksize = 0 ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_clear_flag =================================================== */ +/* ========================================================================== */ + +/* Increment mark to ensure Flag [0..nrow-1] < mark. If integer overflow + * occurs, or mark was initially negative, reset the entire array. This is + * not an error condition, but an intended function of the Flag workspace. + * + * workspace: Flag (nrow). Does not modify Flag if nrow is zero. + */ + +SuiteSparse_long CHOLMOD(clear_flag) +( + cholmod_common *Common +) +{ + Int i, nrow, *Flag ; + + RETURN_IF_NULL_COMMON (-1) ; + + Common->mark++ ; + if (Common->mark <= 0) + { + nrow = Common->nrow ; + Flag = Common->Flag ; + PRINT2 (("reset Flag: nrow "ID"\n", nrow)) ; + PRINT2 (("reset Flag: mark %ld\n", Common->mark)) ; + for (i = 0 ; i < nrow ; i++) + { + Flag [i] = EMPTY ; + } + Common->mark = 0 ; + } + return (Common->mark) ; +} + + +/* ========================================================================== */ +/* ==== cholmod_maxrank ===================================================== */ +/* ========================================================================== */ + +/* Find a valid value of Common->maxrank. Returns 0 if error, or 2, 4, or 8 + * if successful. */ + +size_t CHOLMOD(maxrank) /* returns validated value of Common->maxrank */ +( + /* ---- input ---- */ + size_t n, /* A and L will have n rows */ + /* --------------- */ + cholmod_common *Common +) +{ + size_t maxrank ; + RETURN_IF_NULL_COMMON (0) ; + maxrank = Common->maxrank ; + if (n > 0) + { + /* Ensure maxrank*n*sizeof(double) does not result in integer overflow. + * If n is so large that 2*n*sizeof(double) results in integer overflow + * (n = 268,435,455 if an Int is 32 bits), then maxrank will be 0 or 1, + * but maxrank will be set to 2 below. 2*n will not result in integer + * overflow, and CHOLMOD will run out of memory or safely detect integer + * overflow elsewhere. + */ + maxrank = MIN (maxrank, Size_max / (n * sizeof (double))) ; + } + if (maxrank <= 2) + { + maxrank = 2 ; + } + else if (maxrank <= 4) + { + maxrank = 4 ; + } + else + { + maxrank = 8 ; + } + return (maxrank) ; +} + + +/* ========================================================================== */ +/* === cholmod_dbound ======================================================= */ +/* ========================================================================== */ + +/* Ensure the absolute value of a diagonal entry, D (j,j), is greater than + * Common->dbound. This routine is not meant for the user to call. It is used + * by the various LDL' factorization and update/downdate routines. The + * default value of Common->dbound is zero, and in that case this routine is not + * called at all. No change is made if D (j,j) is NaN. CHOLMOD does not call + * this routine if Common->dbound is NaN. + */ + +double CHOLMOD(dbound) /* returns modified diagonal entry of D */ +( + /* ---- input ---- */ + double dj, /* diagonal entry of D, for LDL' factorization */ + /* --------------- */ + cholmod_common *Common +) +{ + double dbound ; + RETURN_IF_NULL_COMMON (0) ; + if (!IS_NAN (dj)) + { + dbound = Common->dbound ; + if (dj < 0) + { + if (dj > -dbound) + { + dj = -dbound ; + Common->ndbounds_hit++ ; + if (Common->status == CHOLMOD_OK) + { + ERROR (CHOLMOD_DSMALL, "diagonal below threshold") ; + } + } + } + else + { + if (dj < dbound) + { + dj = dbound ; + Common->ndbounds_hit++ ; + if (Common->status == CHOLMOD_OK) + { + ERROR (CHOLMOD_DSMALL, "diagonal below threshold") ; + } + } + } + } + return (dj) ; +} diff --git a/src/CHOLMOD/Core/cholmod_complex.c b/src/CHOLMOD/Core/cholmod_complex.c new file mode 100644 index 0000000..a9a853a --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_complex.c @@ -0,0 +1,549 @@ +/* ========================================================================== */ +/* === Core/cholmod_complex ================================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* If you convert a matrix that contains uninitialized data, valgrind will + * complain. This can occur in a factor L which has gaps (a partial + * factorization, or after updates that change the nonzero pattern), an + * unpacked sparse matrix, a dense matrix with leading dimension d > # of rows, + * or any matrix (dense, sparse, triplet, or factor) with more space allocated + * than is used. You can safely ignore any of these complaints by valgrind. */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + +/* ========================================================================== */ +/* === cholmod_hypot ======================================================== */ +/* ========================================================================== */ + +/* There is an equivalent routine called hypot in , which conforms + * to ANSI C99. However, CHOLMOD does not assume that ANSI C99 is available. + * You can use the ANSI C99 hypot routine with: + * + * #include + * Common->hypotenuse = hypot ; + * + * Default value of the Common->hypotenuse pointer is cholmod_hypot. + * + * s = hypot (x,y) computes s = sqrt (x*x + y*y) but does so more accurately. + * The NaN cases for the double relops x >= y and x+y == x are safely ignored. + * + * Source: Algorithm 312, "Absolute value and square root of a complex number," + * P. Friedland, Comm. ACM, vol 10, no 10, October 1967, page 665. + */ + +double CHOLMOD(hypot) (double x, double y) +{ + double s, r ; + x = fabs (x) ; + y = fabs (y) ; + if (x >= y) + { + if (x + y == x) + { + s = x ; + } + else + { + r = y / x ; + s = x * sqrt (1.0 + r*r) ; + } + } + else + { + if (y + x == y) + { + s = y ; + } + else + { + r = x / y ; + s = y * sqrt (1.0 + r*r) ; + } + } + return (s) ; +} + + +/* ========================================================================== */ +/* === cholmod_divcomplex =================================================== */ +/* ========================================================================== */ + +/* c = a/b where c, a, and b are complex. The real and imaginary parts are + * passed as separate arguments to this routine. The NaN case is ignored + * for the double relop br >= bi. Returns 1 if the denominator is zero, + * 0 otherwise. Note that this return value is the single exception to the + * rule that all CHOLMOD routines that return int return TRUE if successful + * or FALSE otherise. + * + * This uses ACM Algo 116, by R. L. Smith, 1962, which tries to avoid + * underflow and overflow. + * + * c can be the same variable as a or b. + * + * Default value of the Common->complex_divide pointer is cholmod_divcomplex. + */ + +int CHOLMOD(divcomplex) +( + double ar, double ai, /* real and imaginary parts of a */ + double br, double bi, /* real and imaginary parts of b */ + double *cr, double *ci /* real and imaginary parts of c */ +) +{ + double tr, ti, r, den ; + if (fabs (br) >= fabs (bi)) + { + r = bi / br ; + den = br + r * bi ; + tr = (ar + ai * r) / den ; + ti = (ai - ar * r) / den ; + } + else + { + r = br / bi ; + den = r * br + bi ; + tr = (ar * r + ai) / den ; + ti = (ai * r - ar) / den ; + } + *cr = tr ; + *ci = ti ; + return (IS_ZERO (den)) ; +} + + +/* ========================================================================== */ +/* === change_complexity ==================================================== */ +/* ========================================================================== */ + +/* X and Z represent an array of size nz, with numeric xtype given by xtype_in. + * + * If xtype_in is: + * CHOLMOD_PATTERN: X and Z must be NULL. + * CHOLMOD_REAL: X is of size nz, Z must be NULL. + * CHOLMOD_COMPLEX: X is of size 2*nz, Z must be NULL. + * CHOLMOD_ZOMPLEX: X is of size nz, Z is of size nz. + * + * The array is changed into the numeric xtype given by xtype_out, with the + * same definitions of X and Z above. Note that the input conditions, above, + * are not checked. These are checked in the caller routine. + * + * Returns TRUE if successful, FALSE otherwise. X and Z are not modified if + * not successful. + */ + +static int change_complexity +( + /* ---- input ---- */ + Int nz, /* size of X and/or Z */ + int xtype_in, /* xtype of X and Z on input */ + int xtype_out, /* requested xtype of X and Z on output */ + int xtype1, /* xtype_out must be in the range [xtype1 .. xtype2] */ + int xtype2, + /* ---- in/out --- */ + void **XX, /* old X on input, new X on output */ + void **ZZ, /* old Z on input, new Z on output */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Xold, *Zold, *Xnew, *Znew ; + Int k ; + size_t nz2 ; + + if (xtype_out < xtype1 || xtype_out > xtype2) + { + ERROR (CHOLMOD_INVALID, "invalid xtype") ; + return (FALSE) ; + } + + Common->status = CHOLMOD_OK ; + Xold = *XX ; + Zold = *ZZ ; + + switch (xtype_in) + { + + /* ------------------------------------------------------------------ */ + /* converting from pattern */ + /* ------------------------------------------------------------------ */ + + case CHOLMOD_PATTERN: + + switch (xtype_out) + { + + /* ---------------------------------------------------------- */ + /* pattern -> real */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_REAL: + /* allocate X and set to all ones */ + Xnew = CHOLMOD(malloc) (nz, sizeof (double), Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + for (k = 0 ; k < nz ; k++) + { + Xnew [k] = 1 ; + } + *XX = Xnew ; + break ; + + /* ---------------------------------------------------------- */ + /* pattern -> complex */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_COMPLEX: + /* allocate X and set to all ones */ + Xnew = CHOLMOD(malloc) (nz, 2*sizeof (double), Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + for (k = 0 ; k < nz ; k++) + { + Xnew [2*k ] = 1 ; + Xnew [2*k+1] = 0 ; + } + *XX = Xnew ; + break ; + + /* ---------------------------------------------------------- */ + /* pattern -> zomplex */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_ZOMPLEX: + /* allocate X and Z and set to all ones */ + Xnew = CHOLMOD(malloc) (nz, sizeof (double), Common) ; + Znew = CHOLMOD(malloc) (nz, sizeof (double), Common) ; + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free) (nz, sizeof (double), Xnew, Common) ; + CHOLMOD(free) (nz, sizeof (double), Znew, Common) ; + return (FALSE) ; + } + for (k = 0 ; k < nz ; k++) + { + Xnew [k] = 1 ; + Znew [k] = 0 ; + } + *XX = Xnew ; + *ZZ = Znew ; + break ; + } + break ; + + /* ------------------------------------------------------------------ */ + /* converting from real */ + /* ------------------------------------------------------------------ */ + + case CHOLMOD_REAL: + + switch (xtype_out) + { + + /* ---------------------------------------------------------- */ + /* real -> pattern */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_PATTERN: + /* free X */ + *XX = CHOLMOD(free) (nz, sizeof (double), *XX, Common) ; + break ; + + /* ---------------------------------------------------------- */ + /* real -> complex */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_COMPLEX: + /* allocate a new X and copy the old X */ + Xnew = CHOLMOD(malloc) (nz, 2*sizeof (double), Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + for (k = 0 ; k < nz ; k++) + { + Xnew [2*k ] = Xold [k] ; + Xnew [2*k+1] = 0 ; + } + CHOLMOD(free) (nz, sizeof (double), *XX, Common) ; + *XX = Xnew ; + break ; + + /* ---------------------------------------------------------- */ + /* real -> zomplex */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_ZOMPLEX: + /* allocate a new Z and set it to zero */ + Znew = CHOLMOD(malloc) (nz, sizeof (double), Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + for (k = 0 ; k < nz ; k++) + { + Znew [k] = 0 ; + } + *ZZ = Znew ; + break ; + } + break ; + + /* ------------------------------------------------------------------ */ + /* converting from complex */ + /* ------------------------------------------------------------------ */ + + case CHOLMOD_COMPLEX: + + switch (xtype_out) + { + + /* ---------------------------------------------------------- */ + /* complex -> pattern */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_PATTERN: + /* free X */ + *XX = CHOLMOD(free) (nz, 2*sizeof (double), *XX, Common) ; + break ; + + /* ---------------------------------------------------------- */ + /* complex -> real */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_REAL: + /* pack the real part of X, discarding the imaginary part */ + for (k = 0 ; k < nz ; k++) + { + Xold [k] = Xold [2*k] ; + } + /* shrink X in half (this cannot fail) */ + nz2 = 2*nz ; + *XX = CHOLMOD(realloc) (nz, sizeof (double), *XX, &nz2, + Common) ; + break ; + + /* ---------------------------------------------------------- */ + /* complex -> zomplex */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_ZOMPLEX: + /* allocate X and Z and copy the old X into them */ + Xnew = CHOLMOD(malloc) (nz, sizeof (double), Common) ; + Znew = CHOLMOD(malloc) (nz, sizeof (double), Common) ; + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free) (nz, sizeof (double), Xnew, Common) ; + CHOLMOD(free) (nz, sizeof (double), Znew, Common) ; + return (FALSE) ; + } + for (k = 0 ; k < nz ; k++) + { + Xnew [k] = Xold [2*k ] ; + Znew [k] = Xold [2*k+1] ; + } + CHOLMOD(free) (nz, 2*sizeof (double), *XX, Common) ; + *XX = Xnew ; + *ZZ = Znew ; + break ; + } + break ; + + /* ------------------------------------------------------------------ */ + /* converting from zomplex */ + /* ------------------------------------------------------------------ */ + + case CHOLMOD_ZOMPLEX: + + switch (xtype_out) + { + + /* ---------------------------------------------------------- */ + /* zomplex -> pattern */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_PATTERN: + /* free X and Z */ + *XX = CHOLMOD(free) (nz, sizeof (double), *XX, Common) ; + *ZZ = CHOLMOD(free) (nz, sizeof (double), *ZZ, Common) ; + break ; + + /* ---------------------------------------------------------- */ + /* zomplex -> real */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_REAL: + /* free the imaginary part */ + *ZZ = CHOLMOD(free) (nz, sizeof (double), *ZZ, Common) ; + break ; + + /* ---------------------------------------------------------- */ + /* zomplex -> complex */ + /* ---------------------------------------------------------- */ + + case CHOLMOD_COMPLEX: + Xnew = CHOLMOD(malloc) (nz, 2*sizeof (double), Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + for (k = 0 ; k < nz ; k++) + { + Xnew [2*k ] = Xold [k] ; + Xnew [2*k+1] = Zold [k] ; + } + CHOLMOD(free) (nz, sizeof (double), *XX, Common) ; + CHOLMOD(free) (nz, sizeof (double), *ZZ, Common) ; + *XX = Xnew ; + *ZZ = NULL ; + break ; + + } + break ; + } + + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_sparse_xtype ================================================= */ +/* ========================================================================== */ + +/* Change the numeric xtype of a sparse matrix. Supports any type on input + * and output (pattern, real, complex, or zomplex). */ + +int CHOLMOD(sparse_xtype) +( + /* ---- input ---- */ + int to_xtype, /* requested xtype */ + /* ---- in/out --- */ + cholmod_sparse *A, /* sparse matrix to change */ + /* --------------- */ + cholmod_common *Common +) +{ + Int ok ; + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + + ok = change_complexity (A->nzmax, A->xtype, to_xtype, + CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, &(A->x), &(A->z), Common) ; + if (ok) + { + A->xtype = to_xtype ; + } + return (ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_triplet_xtype ================================================ */ +/* ========================================================================== */ + +/* Change the numeric xtype of a triplet matrix. Supports any type on input + * and output (pattern, real, complex, or zomplex). */ + +int CHOLMOD(triplet_xtype) +( + /* ---- input ---- */ + int to_xtype, /* requested xtype */ + /* ---- in/out --- */ + cholmod_triplet *T, /* triplet matrix to change */ + /* --------------- */ + cholmod_common *Common +) +{ + Int ok ; + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (T, FALSE) ; + RETURN_IF_XTYPE_INVALID (T, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + ok = change_complexity (T->nzmax, T->xtype, to_xtype, + CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, &(T->x), &(T->z), Common) ; + if (ok) + { + T->xtype = to_xtype ; + } + return (ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_dense_xtype ================================================= */ +/* ========================================================================== */ + +/* Change the numeric xtype of a dense matrix. Supports real, complex or + * zomplex on input and output */ + +int CHOLMOD(dense_xtype) +( + /* ---- input ---- */ + int to_xtype, /* requested xtype */ + /* ---- in/out --- */ + cholmod_dense *X, /* dense matrix to change */ + /* --------------- */ + cholmod_common *Common +) +{ + Int ok ; + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (X, FALSE) ; + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + ok = change_complexity (X->nzmax, X->xtype, to_xtype, + CHOLMOD_REAL, CHOLMOD_ZOMPLEX, &(X->x), &(X->z), Common) ; + if (ok) + { + X->xtype = to_xtype ; + } + return (ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_factor_xtype ================================================= */ +/* ========================================================================== */ + +/* Change the numeric xtype of a factor. Supports real, complex or zomplex on + * input and output. Supernodal zomplex factors are not supported. */ + +int CHOLMOD(factor_xtype) +( + /* ---- input ---- */ + int to_xtype, /* requested xtype */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to change */ + /* --------------- */ + cholmod_common *Common +) +{ + Int ok ; + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + if (L->is_super && + (L->xtype == CHOLMOD_ZOMPLEX || to_xtype == CHOLMOD_ZOMPLEX)) + { + ERROR (CHOLMOD_INVALID, "invalid xtype for supernodal L") ; + return (FALSE) ; + } + ok = change_complexity ((L->is_super ? L->xsize : L->nzmax), L->xtype, + to_xtype, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, &(L->x), &(L->z), Common) ; + if (ok) + { + L->xtype = to_xtype ; + } + return (ok) ; +} diff --git a/src/CHOLMOD/Core/cholmod_copy.c b/src/CHOLMOD/Core/cholmod_copy.c new file mode 100644 index 0000000..9ceb394 --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_copy.c @@ -0,0 +1,406 @@ +/* ========================================================================== */ +/* === Core/cholmod_copy ==================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* C = A, which allocates C and copies A into C, with possible change of + * stype. The diagonal can optionally be removed. The numerical entries + * can optionally be copied. This routine differs from cholmod_copy_sparse, + * which makes an exact copy of a sparse matrix. + * + * A can be of any type (packed/unpacked, upper/lower/unsymmetric). C is + * packed and can be of any stype (upper/lower/unsymmetric), except that if + * A is rectangular C can only be unsymmetric. If the stype of A and C + * differ, then the appropriate conversion is made. + * + * Symmetry of A (A->stype): + * <0: lower: assume A is symmetric with just tril(A); the rest of A is ignored + * 0 unsym: assume A is unsymmetric; consider all entries in A + * >0 upper: assume A is symmetric with just triu(A); the rest of A is ignored + * + * Symmetry of C (stype parameter): + * <0 lower: return just tril(C) + * 0 unsym: return all of C + * >0 upper: return just triu(C) + * + * In MATLAB: Using cholmod_copy: + * ---------- ---------------------------- + * C = A ; A unsymmetric, C unsymmetric + * C = tril (A) ; A unsymmetric, C lower + * C = triu (A) ; A unsymmetric, C upper + * U = triu (A) ; L = tril (U',-1) ; C = L+U ; A upper, C unsymmetric + * C = triu (A)' ; A upper, C lower + * C = triu (A) ; A upper, C upper + * L = tril (A) ; U = triu (L',1) ; C = L+U ; A lower, C unsymmetric + * C = tril (A) ; A lower, C lower + * C = tril (A)' ; A lower, C upper + * + * workspace: Iwork (max (nrow,ncol)) + * + * A can have an xtype of pattern or real. Complex and zomplex cases only + * supported when mode <= 0 (in which case the numerical values are ignored). + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + + +/* ========================================================================== */ +/* === copy_sym_to_unsym ==================================================== */ +/* ========================================================================== */ + +/* Construct an unsymmetric copy of a symmetric sparse matrix. This does the + * work for as C = cholmod_copy (A, 0, mode, Common) when A is symmetric. + * In this case, extra space can be added to C. + */ + +static cholmod_sparse *copy_sym_to_unsym +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to copy */ + int mode, /* >0: numerical, 0: pattern, <0: pattern (no diag) + * -2: pattern only, no diagonal, add 50% + n extra + * space to C */ + /* --------------- */ + cholmod_common *Common +) +{ + double aij ; + double *Ax, *Cx ; + Int *Ap, *Ai, *Anz, *Cp, *Ci, *Wj, *Iwork ; + cholmod_sparse *C ; + Int nrow, ncol, nz, packed, j, p, pend, i, pc, up, lo, values, diag, + astype, extra ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nrow = A->nrow ; + ncol = A->ncol ; + Ap = A->p ; + Anz = A->nz ; + Ai = A->i ; + Ax = A->x ; + packed = A->packed ; + values = (mode > 0) && (A->xtype != CHOLMOD_PATTERN) ; + diag = (mode >= 0) ; + + astype = SIGN (A->stype) ; + up = (astype > 0) ; + lo = (astype < 0) ; + ASSERT (astype != 0) ; + + /* ---------------------------------------------------------------------- */ + /* create an unsymmetric copy of a symmetric matrix */ + /* ---------------------------------------------------------------------- */ + + Iwork = Common->Iwork ; + Wj = Iwork ; /* size ncol (i/i/l) */ + + /* In MATLAB notation, for converting a symmetric/upper matrix: + * U = triu (A) ; + * L = tril (U',-1) ; + * C = L + U ; + * + * For converting a symmetric/lower matrix to unsymmetric: + * L = tril (A) ; + * U = triu (L',1) ; + * C = L + U ; + */ + ASSERT (up || lo) ; + PRINT1 (("copy: convert symmetric to unsym\n")) ; + + /* count the number of entries in each column of C */ + for (j = 0 ; j < ncol ; j++) + { + Wj [j] = 0 ; + } + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i == j) + { + /* the diagonal entry A(i,i) will appear just once + * (unless it is excluded with mode < 0) */ + if (diag) + { + Wj [j]++ ; + } + } + else if ((up && i < j) || (lo && i > j)) + { + /* upper case: A(i,j) is in the strictly upper part; + * A(j,i) will be added to the strictly lower part of C. + * lower case is the opposite. */ + Wj [j]++ ; + Wj [i]++ ; + } + } + } + nz = 0 ; + for (j = 0 ; j < ncol ; j++) + { + nz += Wj [j] ; + } + + extra = (mode == -2) ? (nz/2 + ncol) : 0 ; + + /* allocate C. C is sorted if and only if A is sorted */ + C = CHOLMOD(allocate_sparse) (nrow, ncol, nz + extra, A->sorted, TRUE, 0, + values ? A->xtype : CHOLMOD_PATTERN, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; + } + + Cp = C->p ; + Ci = C->i ; + Cx = C->x ; + + /* construct the column pointers for C */ + p = 0 ; + for (j = 0 ; j < ncol ; j++) + { + Cp [j] = p ; + p += Wj [j] ; + } + Cp [ncol] = p ; + for (j = 0 ; j < ncol ; j++) + { + Wj [j] = Cp [j] ; + } + + /* construct C */ + if (values) + { + + /* pattern and values */ + ASSERT (diag) ; + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + aij = Ax [p] ; + if (i == j) + { + /* add diagonal entry A(i,i) to column i */ + pc = Wj [i]++ ; + Ci [pc] = i ; + Cx [pc] = aij ; + } + else if ((up && i < j) || (lo && i > j)) + { + /* add A(i,j) to column j */ + pc = Wj [j]++ ; + Ci [pc] = i ; + Cx [pc] = aij ; + /* add A(j,i) to column i */ + pc = Wj [i]++ ; + Ci [pc] = j ; + Cx [pc] = aij ; + } + } + } + + } + else + { + + /* pattern only, possibly excluding the diagonal */ + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i == j) + { + /* add diagonal entry A(i,i) to column i + * (unless it is excluded with mode < 0) */ + if (diag) + { + Ci [Wj [i]++] = i ; + } + } + else if ((up && i < j) || (lo && i > j)) + { + /* add A(i,j) to column j */ + Ci [Wj [j]++] = i ; + /* add A(j,i) to column i */ + Ci [Wj [i]++] = j ; + } + } + } + } + + /* ---------------------------------------------------------------------- */ + /* return the result */ + /* ---------------------------------------------------------------------- */ + + DEBUG (i = CHOLMOD(dump_sparse) (C, "copy_sym_to_unsym", Common)) ; + PRINT1 (("mode %d nnzdiag "ID"\n", mode, i)) ; + ASSERT (IMPLIES (mode < 0, i == 0)) ; + return (C) ; +} + + +/* ========================================================================== */ +/* === cholmod_copy ========================================================= */ +/* ========================================================================== */ + +cholmod_sparse *CHOLMOD(copy) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to copy */ + int stype, /* requested stype of C */ + int mode, /* >0: numerical, 0: pattern, <0: pattern (no diag) */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_sparse *C ; + Int nrow, ncol, up, lo, values, diag, astype ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + values = (mode > 0) && (A->xtype != CHOLMOD_PATTERN) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + nrow = A->nrow ; + ncol = A->ncol ; + if ((stype || A->stype) && nrow != ncol) + { + /* inputs invalid */ + ERROR (CHOLMOD_INVALID, "matrix invalid") ; + return (NULL) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(allocate_work) (0, MAX (nrow,ncol), 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + diag = (mode >= 0) ; + astype = SIGN (A->stype) ; + stype = SIGN (stype) ; + up = (astype > 0) ; + lo = (astype < 0) ; + + /* ---------------------------------------------------------------------- */ + /* copy the matrix */ + /* ---------------------------------------------------------------------- */ + + if (astype == stype) + { + + /* ------------------------------------------------------------------ */ + /* symmetry of A and C are the same */ + /* ------------------------------------------------------------------ */ + + /* copy A into C, keeping the same symmetry. If A is symmetric + * entries in the ignored part of A are not copied into C */ + C = CHOLMOD(band) (A, -nrow, ncol, mode, Common) ; + + } + else if (!astype) + { + + /* ------------------------------------------------------------------ */ + /* convert unsymmetric matrix A into a symmetric matrix C */ + /* ------------------------------------------------------------------ */ + + if (stype > 0) + { + /* C = triu (A) */ + C = CHOLMOD(band) (A, 0, ncol, mode, Common) ; + } + else + { + /* C = tril (A) */ + C = CHOLMOD(band) (A, -nrow, 0, mode, Common) ; + } + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + C->stype = stype ; + + } + else if (astype == -stype) + { + + /* ------------------------------------------------------------------ */ + /* transpose a symmetric matrix */ + /* ------------------------------------------------------------------ */ + + /* converting upper to lower or lower to upper */ + /* workspace: Iwork (nrow) */ + C = CHOLMOD(transpose) (A, values, Common) ; + if (!diag) + { + /* remove diagonal, if requested */ + CHOLMOD(band_inplace) (-nrow, ncol, -1, C, Common) ; + } + + } + else + { + + /* ------------------------------------------------------------------ */ + /* create an unsymmetric copy of a symmetric matrix */ + /* ------------------------------------------------------------------ */ + + C = copy_sym_to_unsym (A, mode, Common) ; + } + + /* ---------------------------------------------------------------------- */ + /* return if error */ + /* ---------------------------------------------------------------------- */ + + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + + /* ---------------------------------------------------------------------- */ + /* return the result */ + /* ---------------------------------------------------------------------- */ + + DEBUG (diag = CHOLMOD(dump_sparse) (C, "copy", Common)) ; + PRINT1 (("mode %d nnzdiag "ID"\n", mode, diag)) ; + ASSERT (IMPLIES (mode < 0, diag == 0)) ; + return (C) ; +} diff --git a/src/CHOLMOD/Core/cholmod_dense.c b/src/CHOLMOD/Core/cholmod_dense.c new file mode 100644 index 0000000..250b596 --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_dense.c @@ -0,0 +1,701 @@ +/* ========================================================================== */ +/* === Core/cholmod_dense =================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2013, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Core utility routines for the cholmod_dense object: + * + * The solve routines and some of the MatrixOps and Modify routines use dense + * matrices as inputs. These are held in column-major order. With a leading + * dimension of d, the entry in row i and column j is held in x [i+j*d]. + * + * Primary routines: + * ----------------- + * cholmod_allocate_dense allocate a dense matrix + * cholmod_free_dense free a dense matrix + * + * Secondary routines: + * ------------------- + * cholmod_zeros allocate a dense matrix of all zeros + * cholmod_ones allocate a dense matrix of all ones + * cholmod_eye allocate a dense identity matrix + * cholmod_sparse_to_dense create a dense matrix copy of a sparse matrix + * cholmod_dense_to_sparse create a sparse matrix copy of a dense matrix + * cholmod_copy_dense create a copy of a dense matrix + * cholmod_copy_dense2 copy a dense matrix (pre-allocated) + * + * All routines in this file can handle the real, complex, and zomplex cases. + * Pattern-only dense matrices are not supported. cholmod_sparse_to_dense can + * take a pattern-only input sparse matrix, however, and cholmod_dense_to_sparse + * can generate a pattern-only output sparse matrix. + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + +/* ========================================================================== */ +/* === TEMPLATE ============================================================= */ +/* ========================================================================== */ + +#define PATTERN +#include "t_cholmod_dense.c" +#define REAL +#include "t_cholmod_dense.c" +#define COMPLEX +#include "t_cholmod_dense.c" +#define ZOMPLEX +#include "t_cholmod_dense.c" + + +/* ========================================================================== */ +/* === cholmod_allocate_dense =============================================== */ +/* ========================================================================== */ + +/* Allocate a dense matrix with leading dimension d. The space is not + * initialized. + */ + +cholmod_dense *CHOLMOD(allocate_dense) +( + /* ---- input ---- */ + size_t nrow, /* # of rows of matrix */ + size_t ncol, /* # of columns of matrix */ + size_t d, /* leading dimension */ + int xtype, /* CHOLMOD_REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_dense *X ; + size_t nzmax, nzmax0 ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + if (d < nrow) + { + ERROR (CHOLMOD_INVALID, "leading dimension invalid") ; + return (NULL) ; + } + if (xtype < CHOLMOD_REAL || xtype > CHOLMOD_ZOMPLEX) + { + ERROR (CHOLMOD_INVALID, "xtype invalid") ; + return (NULL) ; + } + + /* ensure the dimensions do not cause integer overflow */ + (void) CHOLMOD(add_size_t) (ncol, 2, &ok) ; + + /* nzmax = MAX (1, d*ncol) ; */ + nzmax = CHOLMOD(mult_size_t) (d, ncol, &ok) ; + nzmax = MAX (1, nzmax) ; + + if (!ok || nrow > Int_max || ncol > Int_max || nzmax > Int_max) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (NULL) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate header */ + /* ---------------------------------------------------------------------- */ + + X = CHOLMOD(malloc) (sizeof (cholmod_dense), 1, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + PRINT1 (("cholmod_allocate_dense %d-by-%d nzmax %d xtype %d\n", + nrow, ncol, nzmax, xtype)) ; + + X->nrow = nrow ; + X->ncol = ncol ; + X->nzmax = nzmax ; + X->xtype = xtype ; + X->dtype = DTYPE ; + X->x = NULL ; + X->z = NULL ; + X->d = d ; + + /* ---------------------------------------------------------------------- */ + /* allocate the matrix itself */ + /* ---------------------------------------------------------------------- */ + + nzmax0 = 0 ; + CHOLMOD(realloc_multiple) (nzmax, 0, xtype, NULL, NULL, &(X->x), &(X->z), + &nzmax0, Common) ; + + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_dense) (&X, Common) ; + return (NULL) ; /* out of memory */ + } + + return (X) ; +} + + +/* ========================================================================== */ +/* === cholmod_zeros ======================================================== */ +/* ========================================================================== */ + +/* Allocate a dense matrix and set it to zero */ + +cholmod_dense *CHOLMOD(zeros) +( + /* ---- input ---- */ + size_t nrow, /* # of rows of matrix */ + size_t ncol, /* # of columns of matrix */ + int xtype, /* CHOLMOD_REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_dense *X ; + double *Xx, *Xz ; + Int i, nz ; + + /* ---------------------------------------------------------------------- */ + /* allocate a dense matrix and set it to zero */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + X = CHOLMOD(allocate_dense) (nrow, ncol, nrow, xtype, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* NULL Common, out of memory, or inputs invalid */ + } + + Xx = X->x ; + Xz = X->z ; + nz = MAX (1, X->nzmax) ; + + switch (xtype) + { + case CHOLMOD_REAL: + for (i = 0 ; i < nz ; i++) + { + Xx [i] = 0 ; + } + break ; + + case CHOLMOD_COMPLEX: + for (i = 0 ; i < 2*nz ; i++) + { + Xx [i] = 0 ; + } + break ; + + case CHOLMOD_ZOMPLEX: + for (i = 0 ; i < nz ; i++) + { + Xx [i] = 0 ; + } + for (i = 0 ; i < nz ; i++) + { + Xz [i] = 0 ; + } + break ; + } + + return (X) ; +} + + +/* ========================================================================== */ +/* === cholmod_ones ========================================================= */ +/* ========================================================================== */ + +/* Allocate a dense matrix and set it to zero */ + +cholmod_dense *CHOLMOD(ones) +( + /* ---- input ---- */ + size_t nrow, /* # of rows of matrix */ + size_t ncol, /* # of columns of matrix */ + int xtype, /* CHOLMOD_REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_dense *X ; + double *Xx, *Xz ; + Int i, nz ; + + /* ---------------------------------------------------------------------- */ + /* allocate a dense matrix and set it to all ones */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + X = CHOLMOD(allocate_dense) (nrow, ncol, nrow, xtype, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* NULL Common, out of memory, or inputs invalid */ + } + + Xx = X->x ; + Xz = X->z ; + nz = MAX (1, X->nzmax) ; + + switch (xtype) + { + case CHOLMOD_REAL: + for (i = 0 ; i < nz ; i++) + { + Xx [i] = 1 ; + } + break ; + + case CHOLMOD_COMPLEX: + for (i = 0 ; i < nz ; i++) + { + Xx [2*i ] = 1 ; + Xx [2*i+1] = 0 ; + } + break ; + + case CHOLMOD_ZOMPLEX: + for (i = 0 ; i < nz ; i++) + { + Xx [i] = 1 ; + } + for (i = 0 ; i < nz ; i++) + { + Xz [i] = 0 ; + } + break ; + } + + return (X) ; +} + + +/* ========================================================================== */ +/* === cholmod_eye ========================================================== */ +/* ========================================================================== */ + +/* Allocate a dense matrix and set it to the identity matrix */ + +cholmod_dense *CHOLMOD(eye) +( + /* ---- input ---- */ + size_t nrow, /* # of rows of matrix */ + size_t ncol, /* # of columns of matrix */ + int xtype, /* CHOLMOD_REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_dense *X ; + double *Xx, *Xz ; + Int i, n, nz ; + + /* ---------------------------------------------------------------------- */ + /* allocate a dense matrix and set it to the identity matrix */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + X = CHOLMOD(zeros) (nrow, ncol, xtype, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* NULL Common, out of memory, or inputs invalid */ + } + + nz = MAX (1, nrow*ncol) ; + Xx = X->x ; + Xz = X->z ; + + n = MIN (nrow, ncol) ; + + switch (xtype) + { + case CHOLMOD_REAL: + case CHOLMOD_ZOMPLEX: + for (i = 0 ; i < n ; i++) + { + Xx [i + i*nrow] = 1 ; + } + break ; + + case CHOLMOD_COMPLEX: + for (i = 0 ; i < n ; i++) + { + Xx [2 * (i + i*nrow)] = 1 ; + } + break ; + } + + return (X) ; +} + +/* ========================================================================== */ +/* === cholmod_free_dense =================================================== */ +/* ========================================================================== */ + +/* free a dense matrix + * + * workspace: none + */ + +int CHOLMOD(free_dense) +( + /* ---- in/out --- */ + cholmod_dense **XHandle, /* dense matrix to deallocate, NULL on output */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_dense *X ; + + RETURN_IF_NULL_COMMON (FALSE) ; + + if (XHandle == NULL) + { + /* nothing to do */ + return (TRUE) ; + } + X = *XHandle ; + if (X == NULL) + { + /* nothing to do */ + return (TRUE) ; + } + + switch (X->xtype) + { + case CHOLMOD_REAL: + X->x = CHOLMOD(free) (X->nzmax, sizeof (double), X->x, Common) ; + break ; + + case CHOLMOD_COMPLEX: + X->x = CHOLMOD(free) (X->nzmax, 2*sizeof (double), X->x, Common) ; + break ; + + case CHOLMOD_ZOMPLEX: + X->x = CHOLMOD(free) (X->nzmax, sizeof (double), X->x, Common) ; + X->z = CHOLMOD(free) (X->nzmax, sizeof (double), X->z, Common) ; + break ; + } + + *XHandle = CHOLMOD(free) (1, sizeof (cholmod_dense), (*XHandle), Common) ; + return (TRUE) ; +} + +/* ========================================================================== */ +/* === cholmod_ensure_dense ================================================= */ +/* ========================================================================== */ + +/* Ensure that the input matrix has a certain size and type. If not, free + * the existing matrix and reallocate one of the right size and type. + * Returns a pointer to the cholmod_dense matrix, possibly reallocated. + * Also modifies the input matrix handle, XHandle, if necessary. + */ + +cholmod_dense *CHOLMOD(ensure_dense) +( + /* ---- input/output ---- */ + cholmod_dense **XHandle, /* matrix handle to check */ + /* ---- input ---- */ + size_t nrow, /* # of rows of matrix */ + size_t ncol, /* # of columns of matrix */ + size_t d, /* leading dimension */ + int xtype, /* CHOLMOD_REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_dense *X ; + + RETURN_IF_NULL_COMMON (NULL) ; + if (XHandle == NULL) + { + ERROR (CHOLMOD_INVALID, "matrix invalid") ; + return (NULL) ; + } + + X = *XHandle ; + if (X == NULL || X->nrow != nrow || X->ncol != ncol + || X->d != d || X->xtype != xtype) + { + /* Matrix X is not allocated, or has the wrong size. Free it and + * reallocate it in the right size and shape. If an error occurs + * (out of memory or inputs nrow, etc invalid), then the error is + * set in cholmod_allocate_dense and X is returned as NULL. */ +#if 0 + if (X == NULL) + { + printf ("oops, X was null\n") ; + } + else + { + printf ("oops, nrow %g %g ncol %g %g d %g %g xtype %g %g\n", + (double) X->nrow, (double) nrow, + (double) X->ncol, (double) ncol, + (double) X->d, (double) d, + (double) X->xtype, (double) xtype + ) ; + } +#endif + CHOLMOD(free_dense) (XHandle, Common) ; + X = CHOLMOD(allocate_dense) (nrow, ncol, d, xtype, Common) ; + *XHandle = X ; + } + return (X) ; +} + + +/* ========================================================================== */ +/* === cholmod_sparse_to_dense ============================================== */ +/* ========================================================================== */ + +/* Convert a sparse matrix to a dense matrix. + * The output dense matrix has the same xtype as the input sparse matrix, + * except that a pattern-only sparse matrix A is converted into a real dense + * matrix X, with 1's and 0's. All xtypes are supported. + */ + +cholmod_dense *CHOLMOD(sparse_to_dense) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to copy */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_dense *X = NULL ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, NULL) ; + if (A->stype && A->nrow != A->ncol) + { + ERROR (CHOLMOD_INVALID, "matrix invalid") ; + return (NULL) ; + } + Common->status = CHOLMOD_OK ; + ASSERT (CHOLMOD(dump_sparse) (A, "A", Common) >= 0) ; + + /* ---------------------------------------------------------------------- */ + /* convert the matrix, using template routine */ + /* ---------------------------------------------------------------------- */ + + switch (A->xtype) + { + case CHOLMOD_PATTERN: + X = p_cholmod_sparse_to_dense (A, Common) ; + break ; + + case CHOLMOD_REAL: + X = r_cholmod_sparse_to_dense (A, Common) ; + break ; + + case CHOLMOD_COMPLEX: + X = c_cholmod_sparse_to_dense (A, Common) ; + break ; + + case CHOLMOD_ZOMPLEX: + X = z_cholmod_sparse_to_dense (A, Common) ; + break ; + } + return (X) ; +} + + +/* ========================================================================== */ +/* === cholmod_dense_to_sparse ============================================== */ +/* ========================================================================== */ + +/* Convert a dense matrix to a sparse matrix, similar to the MATLAB statements: + * + * C = sparse (X) values = TRUE + * C = spones (sparse (X)) values = FALSE + * + * except that X must be double (it can be of many different types in MATLAB) + * + * The resulting sparse matrix C has the same numeric xtype as the input dense + * matrix X, unless "values" is FALSE (in which case C is real, where C(i,j)=1 + * if (i,j) is an entry in X. + */ + +cholmod_sparse *CHOLMOD(dense_to_sparse) +( + /* ---- input ---- */ + cholmod_dense *X, /* matrix to copy */ + int values, /* TRUE if values to be copied, FALSE otherwise */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_sparse *C = NULL ; + + DEBUG (CHOLMOD(dump_dense) (X, "X", Common)) ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (X, NULL) ; + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, NULL) ; + if (X->d < X->nrow) + { + ERROR (CHOLMOD_INVALID, "matrix invalid") ; + return (NULL) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* convert the matrix, using template routine */ + /* ---------------------------------------------------------------------- */ + + switch (X->xtype) + { + case CHOLMOD_REAL: + C = r_cholmod_dense_to_sparse (X, values, Common) ; + break ; + + case CHOLMOD_COMPLEX: + C = c_cholmod_dense_to_sparse (X, values, Common) ; + break ; + + case CHOLMOD_ZOMPLEX: + C = z_cholmod_dense_to_sparse (X, values, Common) ; + break ; + } + return (C) ; +} + + +/* ========================================================================== */ +/* === cholmod_copy_dense2 ================================================== */ +/* ========================================================================== */ + +/* Y = X, where X and Y are both already allocated. The leading dimensions of + * X and Y may differ, but both must be >= the # of rows in X and Y. + * Entries in rows nrow to d-1 are not copied from X, since the space might not + * be initialized. Y->nzmax is unchanged. X->nzmax is typically + * (X->d)*(X->ncol), but a user might modify that condition outside of any + * CHOLMOD routine. + * + * The two dense matrices X and Y must have the same numeric xtype. + */ + +int CHOLMOD(copy_dense2) +( + /* ---- input ---- */ + cholmod_dense *X, /* matrix to copy */ + /* ---- output --- */ + cholmod_dense *Y, /* copy of matrix X */ + /* --------------- */ + cholmod_common *Common +) +{ + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (X, FALSE) ; + RETURN_IF_NULL (Y, FALSE) ; + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (Y, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + if (X->nrow != Y->nrow || X->ncol != Y->ncol || X->xtype != Y->xtype) + { + ERROR (CHOLMOD_INVALID, "X and Y must have same dimensions and xtype") ; + return (FALSE) ; + } + if (X->d < X->nrow || Y->d < Y->nrow + || (X->d * X->ncol) > X->nzmax || (Y->d * Y->ncol) > Y->nzmax) + { + ERROR (CHOLMOD_INVALID, "X and/or Y invalid") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* copy the matrix, using template routine */ + /* ---------------------------------------------------------------------- */ + + switch (X->xtype) + { + case CHOLMOD_REAL: + r_cholmod_copy_dense2 (X, Y) ; + break ; + + case CHOLMOD_COMPLEX: + c_cholmod_copy_dense2 (X, Y) ; + break ; + + case CHOLMOD_ZOMPLEX: + z_cholmod_copy_dense2 (X, Y) ; + break ; + } + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_copy_dense =================================================== */ +/* ========================================================================== */ + +/* Y = X, copy a dense matrix */ + +cholmod_dense *CHOLMOD(copy_dense) +( + /* ---- input ---- */ + cholmod_dense *X, /* matrix to copy */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_dense *Y ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (X, NULL) ; + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, NULL) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate result */ + /* ---------------------------------------------------------------------- */ + + Y = CHOLMOD(allocate_dense) (X->nrow, X->ncol, X->d, X->xtype, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory or X invalid */ + } + + /* ---------------------------------------------------------------------- */ + /* Y = X */ + /* ---------------------------------------------------------------------- */ + + /* This cannot fail (X and Y are allocated, and have the same nrow, ncol + * d, and xtype) */ + CHOLMOD(copy_dense2) (X, Y, Common) ; + + /* ---------------------------------------------------------------------- */ + /* return result */ + /* ---------------------------------------------------------------------- */ + + return (Y) ; +} diff --git a/src/CHOLMOD/Core/cholmod_error.c b/src/CHOLMOD/Core/cholmod_error.c new file mode 100644 index 0000000..b8f4d67 --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_error.c @@ -0,0 +1,79 @@ +/* ========================================================================== */ +/* === Core/cholmod_error =================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD error-handling routine. */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + +/* ========================================================================== */ +/* ==== cholmod_error ======================================================= */ +/* ========================================================================== */ + +/* An error has occurred. Set the status, optionally print an error message, + * and call the user error-handling routine (if it exists). If + * Common->try_catch is TRUE, then CHOLMOD is inside a try/catch block. + * The status is set, but no message is printed and the user error handler + * is not called. This is not (yet) an error, since CHOLMOD may recover. + * + * In the current version, this try/catch mechanism is used internally only in + * cholmod_analyze, which tries multiple ordering methods and picks the best + * one. If one or more ordering method fails, it keeps going. Only one + * ordering needs to succeed for cholmod_analyze to succeed. + */ + +int CHOLMOD(error) +( + /* ---- input ---- */ + int status, /* error status */ + const char *file, /* name of source code file where error occured */ + int line, /* line number in source code file where error occured*/ + const char *message, /* error message */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (FALSE) ; + + Common->status = status ; + + if (!(Common->try_catch)) + { + +#ifndef NPRINT + /* print a warning or error message */ + if (Common->print_function != NULL) + { + if (status > 0 && Common->print > 1) + { + (Common->print_function) ("CHOLMOD warning: %s\n", message) ; + fflush (stdout) ; + fflush (stderr) ; + } + else if (Common->print > 0) + { + (Common->print_function) ("CHOLMOD error: %s\n", message) ; + fflush (stdout) ; + fflush (stderr) ; + } + } +#endif + + /* call the user error handler, if it exists */ + if (Common->error_handler != NULL) + { + Common->error_handler (status, file, line, message) ; + } + } + + return (TRUE) ; +} diff --git a/src/CHOLMOD/Core/cholmod_factor.c b/src/CHOLMOD/Core/cholmod_factor.c new file mode 100644 index 0000000..3ba3be3 --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_factor.c @@ -0,0 +1,936 @@ +/* ========================================================================== */ +/* === Core/cholmod_factor ================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2013, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Core utility routines for the cholmod_factor object: + * + * The data structure for an LL' or LDL' factorization is too complex to + * describe in one sentence. This object can hold the symbolic analysis alone, + * or in combination with a "simplicial" (similar to a sparse matrix) or + * "supernodal" form of the numerical factorization. Only the routine to free + * a factor is primary, since a factor object is created by the factorization + * routine (cholmod_factorize). It must be freed with cholmod_free_factor. + * + * Primary routine: + * ---------------- + * cholmod_free_factor free a factor + * + * Secondary routines: + * ------------------- + * cholmod_allocate_factor allocate a symbolic factor (LL' or LDL') + * cholmod_reallocate_factor change the # entries in a factor + * cholmod_change_factor change the type of factor (e.g., LDL' to LL') + * cholmod_pack_factor pack the columns of a factor + * cholmod_reallocate_column resize a single column of a factor + * cholmod_factor_to_sparse create a sparse matrix copy of a factor + * cholmod_copy_factor create a copy of a factor + * + * Note that there is no cholmod_sparse_to_factor routine to create a factor + * as a copy of a sparse matrix. It could be done, after a fashion, but a + * lower triangular sparse matrix would not necessarily have a chordal graph, + * which would break the many CHOLMOD routines that rely on this property. + * + * The cholmod_factor_to_sparse routine is provided so that matrix operations + * in the MatrixOps module may be applied to L. Those operations operate on + * cholmod_sparse objects, and they are not guaranteed to maintain the chordal + * property of L. Such a modified L cannot be safely converted back to a + * cholmod_factor object. + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + + +/* ========================================================================== */ +/* === cholmod_allocate_factor ============================================== */ +/* ========================================================================== */ + +/* Allocate a simplicial symbolic factor, with L->Perm and L->ColCount allocated + * and initialized to "empty" values (Perm [k] = k, and ColCount[k] = 1). + * The integer and numerical parts of L are not allocated. L->xtype is returned + * as CHOLMOD_PATTERN and L->is_super are returned as FALSE. L->is_ll is also + * returned FALSE, but this may be modified when the matrix is factorized. + * + * This is sufficient (but far from ideal) for input to cholmod_factorize, + * since the simplicial LL' or LDL' factorization (cholmod_rowfac) can + * reallocate the columns of L as needed. The primary purpose of this routine + * is to allocate space for a symbolic factorization, for the "expert" user to + * do his or her own symbolic analysis. The typical user should use + * cholmod_analyze instead of this routine. + * + * workspace: none + */ + +cholmod_factor *CHOLMOD(allocate_factor) +( + /* ---- input ---- */ + size_t n, /* L is n-by-n */ + /* --------------- */ + cholmod_common *Common +) +{ + Int j ; + Int *Perm, *ColCount ; + cholmod_factor *L ; + int ok = TRUE ; + + RETURN_IF_NULL_COMMON (FALSE) ; + Common->status = CHOLMOD_OK ; + + /* ensure the dimension does not cause integer overflow */ + (void) CHOLMOD(add_size_t) (n, 2, &ok) ; + if (!ok || n > Int_max) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (NULL) ; + } + + L = CHOLMOD(malloc) (sizeof (cholmod_factor), 1, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + L->n = n ; + L->is_ll = FALSE ; + L->is_super = FALSE ; + L->is_monotonic = TRUE ; + L->itype = ITYPE ; + L->xtype = CHOLMOD_PATTERN ; + L->dtype = DTYPE ; + + /* allocate the purely symbolic part of L */ + L->ordering = CHOLMOD_NATURAL ; + L->Perm = CHOLMOD(malloc) (n, sizeof (Int), Common) ; + L->IPerm = NULL ; /* only created by cholmod_solve2 when needed */ + L->ColCount = CHOLMOD(malloc) (n, sizeof (Int), Common) ; + + /* simplicial part of L is empty */ + L->nzmax = 0 ; + L->p = NULL ; + L->i = NULL ; + L->x = NULL ; + L->z = NULL ; + L->nz = NULL ; + L->next = NULL ; + L->prev = NULL ; + + /* supernodal part of L is also empty */ + L->nsuper = 0 ; + L->ssize = 0 ; + L->xsize = 0 ; + L->maxesize = 0 ; + L->maxcsize = 0 ; + L->super = NULL ; + L->pi = NULL ; + L->px = NULL ; + L->s = NULL ; + + /* L has not been factorized */ + L->minor = n ; + + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_factor) (&L, Common) ; + return (NULL) ; /* out of memory */ + } + + /* initialize Perm and ColCount */ + Perm = L->Perm ; + for (j = 0 ; j < ((Int) n) ; j++) + { + Perm [j] = j ; + } + ColCount = L->ColCount ; + for (j = 0 ; j < ((Int) n) ; j++) + { + ColCount [j] = 1 ; + } + + return (L) ; +} + + +/* ========================================================================== */ +/* === cholmod_free_factor ================================================== */ +/* ========================================================================== */ + +/* Free a factor object. + * + * workspace: none + */ + +int CHOLMOD(free_factor) +( + /* ---- in/out --- */ + cholmod_factor **LHandle, /* factor to free, NULL on output */ + /* --------------- */ + cholmod_common *Common +) +{ + Int n, lnz, xs, ss, s ; + cholmod_factor *L ; + + RETURN_IF_NULL_COMMON (FALSE) ; + + if (LHandle == NULL) + { + /* nothing to do */ + return (TRUE) ; + } + L = *LHandle ; + if (L == NULL) + { + /* nothing to do */ + return (TRUE) ; + } + + n = L->n ; + lnz = L->nzmax ; + s = L->nsuper + 1 ; + xs = (L->is_super) ? ((Int) (L->xsize)) : (lnz) ; + ss = L->ssize ; + + /* symbolic part of L */ + CHOLMOD(free) (n, sizeof (Int), L->Perm, Common) ; + CHOLMOD(free) (n, sizeof (Int), L->IPerm, Common) ; + CHOLMOD(free) (n, sizeof (Int), L->ColCount, Common) ; + + /* simplicial form of L */ + CHOLMOD(free) (n+1, sizeof (Int), L->p, Common) ; + CHOLMOD(free) (lnz, sizeof (Int), L->i, Common) ; + CHOLMOD(free) (n, sizeof (Int), L->nz, Common) ; + CHOLMOD(free) (n+2, sizeof (Int), L->next, Common) ; + CHOLMOD(free) (n+2, sizeof (Int), L->prev, Common) ; + + /* supernodal form of L */ + CHOLMOD(free) (s, sizeof (Int), L->pi, Common) ; + CHOLMOD(free) (s, sizeof (Int), L->px, Common) ; + CHOLMOD(free) (s, sizeof (Int), L->super, Common) ; + CHOLMOD(free) (ss, sizeof (Int), L->s, Common) ; + + /* numerical values for both simplicial and supernodal L */ + if (L->xtype == CHOLMOD_REAL) + { + CHOLMOD(free) (xs, sizeof (double), L->x, Common) ; + } + else if (L->xtype == CHOLMOD_COMPLEX) + { + CHOLMOD(free) (xs, 2*sizeof (double), L->x, Common) ; + } + else if (L->xtype == CHOLMOD_ZOMPLEX) + { + CHOLMOD(free) (xs, sizeof (double), L->x, Common) ; + CHOLMOD(free) (xs, sizeof (double), L->z, Common) ; + } + + *LHandle = CHOLMOD(free) (1, sizeof (cholmod_factor), (*LHandle), Common) ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_reallocate_factor ============================================ */ +/* ========================================================================== */ + +/* Change the size of L->i and L->x, or allocate them if their current size + * is zero. L must be simplicial. + * + * workspace: none + */ + +int CHOLMOD(reallocate_factor) +( + /* ---- input ---- */ + size_t nznew, /* new # of entries in L */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) +{ + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + PRINT1 (("realloc factor: xtype %d\n", L->xtype)) ; + if (L->is_super) + { + /* L must be simplicial, and not symbolic */ + ERROR (CHOLMOD_INVALID, "L invalid") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + PRINT1 (("realloc factor %g to %g\n", (double) L->nzmax, (double) nznew)) ; + + /* ---------------------------------------------------------------------- */ + /* resize (or allocate) the L->i and L->x components of the factor */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(realloc_multiple) (nznew, 1, L->xtype, &(L->i), NULL, + &(L->x), &(L->z), &(L->nzmax), Common) ; + return (Common->status == CHOLMOD_OK) ; +} + + +/* ========================================================================== */ +/* === cholmod_reallocate_column =========================================== */ +/* ========================================================================== */ + +/* Column j needs more space, reallocate it at the end of L->i and L->x. + * If the reallocation fails, the factor is converted to a simplicial + * symbolic factor (no pattern, just L->Perm and L->ColCount). + * + * workspace: none + */ + +int CHOLMOD(reallocate_column) +( + /* ---- input ---- */ + size_t j, /* the column to reallocate */ + size_t need, /* required size of column j */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) +{ + double xneed ; + double *Lx, *Lz ; + Int *Lp, *Lprev, *Lnext, *Li, *Lnz ; + Int n, pold, pnew, len, k, tail ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + if (L->is_super) + { + ERROR (CHOLMOD_INVALID, "L must be simplicial") ; + return (FALSE) ; + } + n = L->n ; + if (j >= L->n || need == 0) + { + ERROR (CHOLMOD_INVALID, "j invalid") ; + return (FALSE) ; /* j out of range */ + } + Common->status = CHOLMOD_OK ; + + DEBUG (CHOLMOD(dump_factor) (L, "start colrealloc", Common)) ; + + /* ---------------------------------------------------------------------- */ + /* increase the size of L if needed */ + /* ---------------------------------------------------------------------- */ + + /* head = n+1 ; */ + tail = n ; + Lp = L->p ; + Lnz = L->nz ; + Lprev = L->prev ; + Lnext = L->next ; + + ASSERT (Lnz != NULL) ; + ASSERT (Lnext != NULL && Lprev != NULL) ; + PRINT1 (("col %g need %g\n", (double) j, (double) need)) ; + + /* column j cannot have more than n-j entries if all entries are present */ + need = MIN (need, n-j) ; + + /* compute need in double to avoid integer overflow */ + if (Common->grow1 >= 1.0) + { + xneed = (double) need ; + xneed = Common->grow1 * xneed + Common->grow2 ; + xneed = MIN (xneed, n-j) ; + need = (Int) xneed ; + } + PRINT1 (("really new need %g current %g\n", (double) need, + (double) (Lp [Lnext [j]] - Lp [j]))) ; + ASSERT (need >= 1 && need <= n-j) ; + + if (Lp [Lnext [j]] - Lp [j] >= (Int) need) + { + /* no need to reallocate the column, it's already big enough */ + PRINT1 (("colrealloc: quick return %g %g\n", + (double) (Lp [Lnext [j]] - Lp [j]), (double) need)) ; + return (TRUE) ; + + } + + if (Lp [tail] + need > L->nzmax) + { + /* use double to avoid integer overflow */ + xneed = (double) need ; + if (Common->grow0 < 1.2) /* fl. pt. compare, false if NaN */ + { + /* if grow0 is less than 1.2 or NaN, don't use it */ + xneed = 1.2 * (((double) L->nzmax) + xneed + 1) ; + } + else + { + xneed = Common->grow0 * (((double) L->nzmax) + xneed + 1) ; + } + if (xneed > Size_max || + !CHOLMOD(reallocate_factor) ((Int) xneed, L, Common)) + { + /* out of memory, convert to simplicial symbolic */ + CHOLMOD(change_factor) (CHOLMOD_PATTERN, L->is_ll, FALSE, TRUE, + TRUE, L, Common) ; + ERROR (CHOLMOD_OUT_OF_MEMORY, "out of memory; L now symbolic") ; + return (FALSE) ; /* out of memory */ + } + PRINT1 (("\n=== GROW L from %g to %g\n", + (double) L->nzmax, (double) xneed)) ; + /* pack all columns so that each column has at most grow2 free space */ + CHOLMOD(pack_factor) (L, Common) ; + ASSERT (Common->status == CHOLMOD_OK) ; + Common->nrealloc_factor++ ; + } + + /* ---------------------------------------------------------------------- */ + /* reallocate the column */ + /* ---------------------------------------------------------------------- */ + + Common->nrealloc_col++ ; + + Li = L->i ; + Lx = L->x ; + Lz = L->z ; + + /* remove j from its current position in the list */ + Lnext [Lprev [j]] = Lnext [j] ; + Lprev [Lnext [j]] = Lprev [j] ; + + /* place j at the end of the list */ + Lnext [Lprev [tail]] = j ; + Lprev [j] = Lprev [tail] ; + Lnext [j] = n ; + Lprev [tail] = j ; + + /* L is no longer monotonic; columns are out-of-order */ + L->is_monotonic = FALSE ; + + /* allocate space for column j */ + pold = Lp [j] ; + pnew = Lp [tail] ; + Lp [j] = pnew ; + Lp [tail] += need ; + + /* copy column j to the new space */ + len = Lnz [j] ; + for (k = 0 ; k < len ; k++) + { + Li [pnew + k] = Li [pold + k] ; + } + + if (L->xtype == CHOLMOD_REAL) + { + for (k = 0 ; k < len ; k++) + { + Lx [pnew + k] = Lx [pold + k] ; + } + } + else if (L->xtype == CHOLMOD_COMPLEX) + { + for (k = 0 ; k < len ; k++) + { + Lx [2*(pnew + k) ] = Lx [2*(pold + k) ] ; + Lx [2*(pnew + k)+1] = Lx [2*(pold + k)+1] ; + } + } + else if (L->xtype == CHOLMOD_ZOMPLEX) + { + for (k = 0 ; k < len ; k++) + { + Lx [pnew + k] = Lx [pold + k] ; + Lz [pnew + k] = Lz [pold + k] ; + } + } + + DEBUG (CHOLMOD(dump_factor) (L, "colrealloc done", Common)) ; + + /* successful reallocation of column j of L */ + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_pack_factor ================================================== */ +/* ========================================================================== */ + +/* Pack the columns of a simplicial LDL' or LL' factor. This can be followed + * by a call to cholmod_reallocate_factor to reduce the size of L to the exact + * size required by the factor, if desired. Alternatively, you can leave the + * size of L->i and L->x the same, to allow space for future updates/rowadds. + * + * Each column is reduced in size so that it has at most Common->grow2 free + * space at the end of the column. + * + * Does nothing and returns silently if given any other type of factor. + * + * Does NOT force the columns of L to be monotonic. It thus differs from + * cholmod_change_factor (xtype, -, FALSE, TRUE, TRUE, L, Common), which + * packs the columns and ensures that they appear in monotonic order. + */ + +int CHOLMOD(pack_factor) +( + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Lx, *Lz ; + Int *Lp, *Li, *Lnz, *Lnext ; + Int pnew, j, k, pold, len, n, head, tail, grow2 ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + Common->status = CHOLMOD_OK ; + DEBUG (CHOLMOD(dump_factor) (L, "start pack", Common)) ; + PRINT1 (("PACK factor %d\n", L->is_super)) ; + + if (L->xtype == CHOLMOD_PATTERN || L->is_super) + { + /* nothing to do unless L is simplicial numeric */ + return (TRUE) ; + } + + /* ---------------------------------------------------------------------- */ + /* pack */ + /* ---------------------------------------------------------------------- */ + + grow2 = Common->grow2 ; + PRINT1 (("\nPACK grow2 "ID"\n", grow2)) ; + + pnew = 0 ; + n = L->n ; + Lp = L->p ; + Li = L->i ; + Lx = L->x ; + Lz = L->z ; + Lnz = L->nz ; + Lnext = L->next ; + + head = n+1 ; + tail = n ; + + for (j = Lnext [head] ; j != tail ; j = Lnext [j]) + { + /* pack column j */ + pold = Lp [j] ; + len = Lnz [j] ; + ASSERT (len > 0) ; + PRINT2 (("col "ID" pnew "ID" pold "ID"\n", j, pnew, pold)) ; + if (pnew < pold) + { + PRINT2 ((" pack this column\n")) ; + + for (k = 0 ; k < len ; k++) + { + Li [pnew + k] = Li [pold + k] ; + } + + if (L->xtype == CHOLMOD_REAL) + { + for (k = 0 ; k < len ; k++) + { + Lx [pnew + k] = Lx [pold + k] ; + } + } + else if (L->xtype == CHOLMOD_COMPLEX) + { + for (k = 0 ; k < len ; k++) + { + Lx [2*(pnew + k) ] = Lx [2*(pold + k) ] ; + Lx [2*(pnew + k)+1] = Lx [2*(pold + k)+1] ; + } + } + else if (L->xtype == CHOLMOD_ZOMPLEX) + { + for (k = 0 ; k < len ; k++) + { + Lx [pnew + k] = Lx [pold + k] ; + Lz [pnew + k] = Lz [pold + k] ; + } + } + + Lp [j] = pnew ; + } + len = MIN (len + grow2, n - j) ; + pnew = MIN (Lp [j] + len, Lp [Lnext [j]]) ; + } + PRINT2 (("final pnew = "ID"\n", pnew)) ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_factor_to_sparse ============================================= */ +/* ========================================================================== */ + +/* Constructs a column-oriented sparse matrix containing the pattern and values + * of a simplicial or supernodal numerical factor, and then converts the factor + * into a simplicial symbolic factor. If L is already packed, monotonic, + * and simplicial (which is the case when cholmod_factorize uses the simplicial + * Cholesky factorization algorithm) then this routine requires only O(1) + * memory and takes O(1) time. + * + * Only operates on numeric factors (real, complex, or zomplex). Does not + * change the numeric L->xtype (the resulting sparse matrix has the same xtype + * as L). If this routine fails, L is left unmodified. + */ + +cholmod_sparse *CHOLMOD(factor_to_sparse) +( + /* ---- in/out --- */ + cholmod_factor *L, /* factor to copy, converted to symbolic on output */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_sparse *Lsparse ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (L, NULL) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, NULL) ; + Common->status = CHOLMOD_OK ; + DEBUG (CHOLMOD(dump_factor) (L, "start convert to matrix", Common)) ; + + /* ---------------------------------------------------------------------- */ + /* convert to packed, monotonic, simplicial, numeric */ + /* ---------------------------------------------------------------------- */ + + /* leave as LL or LDL' */ + if (!CHOLMOD(change_factor) (L->xtype, L->is_ll, FALSE, TRUE, TRUE, L, + Common)) + { + ERROR (CHOLMOD_INVALID, "cannot convert L") ; + return (NULL) ; + } + + /* ---------------------------------------------------------------------- */ + /* create Lsparse */ + /* ---------------------------------------------------------------------- */ + + /* allocate the header for Lsparse, the sparse matrix version of L */ + Lsparse = CHOLMOD(malloc) (sizeof (cholmod_sparse), 1, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + /* transfer the contents from L to Lsparse */ + Lsparse->nrow = L->n ; + Lsparse->ncol = L->n ; + Lsparse->p = L->p ; + Lsparse->i = L->i ; + Lsparse->x = L->x ; + Lsparse->z = L->z ; + Lsparse->nz = NULL ; + Lsparse->stype = 0 ; + Lsparse->itype = L->itype ; + Lsparse->xtype = L->xtype ; + Lsparse->dtype = L->dtype ; + Lsparse->sorted = TRUE ; + Lsparse->packed = TRUE ; + Lsparse->nzmax = L->nzmax ; + ASSERT (CHOLMOD(dump_sparse) (Lsparse, "Lsparse", Common) >= 0) ; + + /* ---------------------------------------------------------------------- */ + /* convert L to symbolic, but do not free contents transfered to Lsparse */ + /* ---------------------------------------------------------------------- */ + + L->p = NULL ; + L->i = NULL ; + L->x = NULL ; + L->z = NULL ; + L->xtype = CHOLMOD_PATTERN ; + CHOLMOD(change_factor) (CHOLMOD_PATTERN, FALSE, FALSE, TRUE, TRUE, L, + Common) ; + + return (Lsparse) ; +} + + +/* ========================================================================== */ +/* === cholmod_copy_factor ================================================== */ +/* ========================================================================== */ + +/* Create an exact copy of a factor, with one exception: + * + * Entries in unused space are not copied (they might not be initialized, + * and copying them would cause program checkers such as purify and + * valgrind to complain). + * + * Note that a supernodal L cannot be zomplex. + */ + +cholmod_factor *CHOLMOD(copy_factor) +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to copy */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_factor *L2 ; + double *Lx, *L2x, *Lz, *L2z ; + Int *Perm, *ColCount, *Lp, *Li, *Lnz, *Lnext, *Lprev, *Lsuper, *Lpi, *Lpx, + *Ls, *Perm2, *ColCount2, *L2p, *L2i, *L2nz, *L2next, *L2prev, *L2super, + *L2pi, *L2px, *L2s ; + Int n, j, p, pend, s, xsize, ssize, nsuper ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (L, NULL) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, NULL) ; + Common->status = CHOLMOD_OK ; + DEBUG (CHOLMOD(dump_factor) (L, "start copy", Common)) ; + + n = L->n ; + + /* ---------------------------------------------------------------------- */ + /* allocate a simplicial symbolic factor */ + /* ---------------------------------------------------------------------- */ + + /* allocates L2->Perm and L2->ColCount */ + L2 = CHOLMOD(allocate_factor) (n, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + ASSERT (L2->xtype == CHOLMOD_PATTERN && !(L2->is_super)) ; + + Perm = L->Perm ; + ColCount = L->ColCount ; + Perm2 = L2->Perm ; + ColCount2 = L2->ColCount ; + L2->ordering = L->ordering ; + + for (j = 0 ; j < n ; j++) + { + Perm2 [j] = Perm [j] ; + } + for (j = 0 ; j < n ; j++) + { + ColCount2 [j] = ColCount [j] ; + } + L2->is_ll = L->is_ll ; + + /* ---------------------------------------------------------------------- */ + /* copy the rest of the factor */ + /* ---------------------------------------------------------------------- */ + + if (L->xtype != CHOLMOD_PATTERN && !(L->super)) + { + + /* ------------------------------------------------------------------ */ + /* allocate a simplicial numeric factor */ + /* ------------------------------------------------------------------ */ + + /* allocate L2->p, L2->nz, L2->prev, L2->next, L2->i, and L2->x. + * packed = -1 so that cholmod_change_factor allocates space of + * size L2->nzmax */ + L2->nzmax = L->nzmax ; + if (!CHOLMOD(change_factor) (L->xtype, L->is_ll, FALSE, -1, TRUE, + L2, Common)) + { + CHOLMOD(free_factor) (&L2, Common) ; + return (NULL) ; /* out of memory */ + } + ASSERT (MAX (1, L->nzmax) == L2->nzmax) ; + + /* ------------------------------------------------------------------ */ + /* copy the contents of a simplicial numeric factor */ + /* ------------------------------------------------------------------ */ + + Lp = L->p ; + Li = L->i ; + Lx = L->x ; + Lz = L->z ; + Lnz = L->nz ; + Lnext = L->next ; + Lprev = L->prev ; + + L2p = L2->p ; + L2i = L2->i ; + L2x = L2->x ; + L2z = L2->z ; + L2nz = L2->nz ; + L2next = L2->next ; + L2prev = L2->prev ; + L2->xtype = L->xtype ; + L2->dtype = L->dtype ; + + for (j = 0 ; j <= n ; j++) + { + L2p [j] = Lp [j] ; + } + + for (j = 0 ; j < n+2 ; j++) + { + L2prev [j] = Lprev [j] ; + } + + for (j = 0 ; j < n+2 ; j++) + { + L2next [j] = Lnext [j] ; + } + + for (j = 0 ; j < n ; j++) + { + L2nz [j] = Lnz [j] ; + } + + for (j = 0 ; j < n ; j++) + { + p = Lp [j] ; + pend = p + Lnz [j] ; + for ( ; p < pend ; p++) + { + L2i [p] = Li [p] ; + } + p = Lp [j] ; + + if (L->xtype == CHOLMOD_REAL) + { + for ( ; p < pend ; p++) + { + L2x [p] = Lx [p] ; + } + } + else if (L->xtype == CHOLMOD_COMPLEX) + { + for ( ; p < pend ; p++) + { + L2x [2*p ] = Lx [2*p ] ; + L2x [2*p+1] = Lx [2*p+1] ; + } + } + else if (L->xtype == CHOLMOD_ZOMPLEX) + { + for ( ; p < pend ; p++) + { + L2x [p] = Lx [p] ; + L2z [p] = Lz [p] ; + } + } + + } + + } + else if (L->is_super) + { + + /* ------------------------------------------------------------------ */ + /* copy a supernodal factor */ + /* ------------------------------------------------------------------ */ + + xsize = L->xsize ; + ssize = L->ssize ; + nsuper = L->nsuper ; + + L2->xsize = xsize ; + L2->ssize = ssize ; + L2->nsuper = nsuper ; + + /* allocate L2->super, L2->pi, L2->px, and L2->s. Allocate L2->x if + * L is numeric */ + if (!CHOLMOD(change_factor) (L->xtype, TRUE, TRUE, TRUE, TRUE, L2, + Common)) + { + CHOLMOD(free_factor) (&L2, Common) ; + return (NULL) ; /* out of memory */ + } + + ASSERT (L2->s != NULL) ; + + /* ------------------------------------------------------------------ */ + /* copy the contents of a supernodal factor */ + /* ------------------------------------------------------------------ */ + + Lsuper = L->super ; + Lpi = L->pi ; + Lpx = L->px ; + Ls = L->s ; + Lx = L->x ; + + L2super = L2->super ; + L2pi = L2->pi ; + L2px = L2->px ; + L2s = L2->s ; + L2x = L2->x ; + + L2->maxcsize = L->maxcsize ; + L2->maxesize = L->maxesize ; + + for (s = 0 ; s <= nsuper ; s++) + { + L2super [s] = Lsuper [s] ; + } + for (s = 0 ; s <= nsuper ; s++) + { + L2pi [s] = Lpi [s] ; + } + for (s = 0 ; s <= nsuper ; s++) + { + L2px [s] = Lpx [s] ; + } + + L2s [0] = 0 ; + for (p = 0 ; p < ssize ; p++) + { + L2s [p] = Ls [p] ; + } + + if (L->xtype == CHOLMOD_REAL) + { + for (p = 0 ; p < xsize ; p++) + { + L2x [p] = Lx [p] ; + } + } + else if (L->xtype == CHOLMOD_COMPLEX) + { + for (p = 0 ; p < 2*xsize ; p++) + { + L2x [p] = Lx [p] ; + } + } + } + + L2->minor = L->minor ; + L2->is_monotonic = L->is_monotonic ; + + DEBUG (CHOLMOD(dump_factor) (L2, "L2 got copied", Common)) ; + ASSERT (L2->xtype == L->xtype && L2->is_super == L->is_super) ; + return (L2) ; +} diff --git a/src/CHOLMOD/Core/cholmod_memory.c b/src/CHOLMOD/Core/cholmod_memory.c new file mode 100644 index 0000000..9443fad --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_memory.c @@ -0,0 +1,575 @@ +/* ========================================================================== */ +/* === Core/cholmod_memory ================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2013, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Core memory management routines: + * + * Primary routines: + * ----------------- + * cholmod_malloc malloc wrapper + * cholmod_free free wrapper + * + * Secondary routines: + * ------------------- + * cholmod_calloc calloc wrapper + * cholmod_realloc realloc wrapper + * cholmod_realloc_multiple realloc wrapper for multiple objects + * + * The user may make use of these, just like malloc and free. You can even + * malloc an object and safely free it with cholmod_free, and visa versa + * (except that the memory usage statistics will be corrupted). These routines + * do differ from malloc and free. If cholmod_free is given a NULL pointer, + * for example, it does nothing (unlike the ANSI free). cholmod_realloc does + * not return NULL if given a non-NULL pointer and a nonzero size, even if it + * fails (it sets an error code in Common->status instead). + * + * CHOLMOD keeps track of the amount of memory it has allocated, and so the + * cholmod_free routine includes as a parameter the size of the object being + * freed. This is only used for memory usage statistics, which are very useful + * in finding memory leaks in your program. If you, the user of CHOLMOD, pass + * the wrong size, the only consequence is that the memory usage statistics + * will be invalid. This will causes assertions to fail if CHOLMOD is + * compiled with debugging enabled, but otherwise it will cause no errors. + * + * The cholmod_free_* routines for each CHOLMOD object keep track of the size + * of the blocks they free, so they do not require you to pass their sizes + * as a parameter. + * + * If a block of size zero is requested, these routines allocate a block of + * size one instead. + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + +/* ========================================================================== */ +/* === cholmod_add_size_t =================================================== */ +/* ========================================================================== */ + +/* Safely compute a+b, and check for integer overflow. If overflow occurs, + * return 0 and set OK to FALSE. Also return 0 if OK is FALSE on input. */ + +size_t CHOLMOD(add_size_t) (size_t a, size_t b, int *ok) +{ + size_t s = a + b ; + (*ok) = (*ok) && (s >= a) ; + return ((*ok) ? s : 0) ; +} + +/* ========================================================================== */ +/* === cholmod_mult_size_t ================================================== */ +/* ========================================================================== */ + +/* Safely compute a*k, where k should be small, and check for integer overflow. + * If overflow occurs, return 0 and set OK to FALSE. Also return 0 if OK is + * FALSE on input. */ + +size_t CHOLMOD(mult_size_t) (size_t a, size_t k, int *ok) +{ + size_t p = 0, s ; + while (*ok) + { + if (k % 2) + { + p = p + a ; + (*ok) = (*ok) && (p >= a) ; + } + k = k / 2 ; + if (!k) return (p) ; + s = a + a ; + (*ok) = (*ok) && (s >= a) ; + a = s ; + } + return (0) ; +} + + +/* ========================================================================== */ +/* === cholmod_malloc ======================================================= */ +/* ========================================================================== */ + +/* Wrapper around malloc routine. Allocates space of size MAX(1,n)*size, where + * size is normally a sizeof (...). + * + * This routine, cholmod_calloc, and cholmod_realloc do not set Common->status + * to CHOLMOD_OK on success, so that a sequence of cholmod_malloc's, _calloc's, + * or _realloc's can be used. If any of them fails, the Common->status will + * hold the most recent error status. + * + * Usage, for a pointer to int: + * + * p = cholmod_malloc (n, sizeof (int), Common) + * + * Uses a pointer to the malloc routine (or its equivalent) defined in Common. + */ + +void *CHOLMOD(malloc) /* returns pointer to the newly malloc'd block */ +( + /* ---- input ---- */ + size_t n, /* number of items */ + size_t size, /* size of each item */ + /* --------------- */ + cholmod_common *Common +) +{ + void *p ; + size_t s ; + int ok = TRUE ; + + RETURN_IF_NULL_COMMON (NULL) ; + if (size == 0) + { + ERROR (CHOLMOD_INVALID, "sizeof(item) must be > 0") ; + p = NULL ; + } + else if (n >= (Size_max / size) || n >= Int_max) + { + /* object is too big to allocate without causing integer overflow */ + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + p = NULL ; + } + else + { + /* call malloc, or its equivalent */ + s = CHOLMOD(mult_size_t) (MAX (1,n), size, &ok) ; + p = ok ? ((Common->malloc_memory) (s)) : NULL ; + if (p == NULL) + { + /* failure: out of memory */ + ERROR (CHOLMOD_OUT_OF_MEMORY, "out of memory") ; + } + else + { + /* success: increment the count of objects allocated */ + Common->malloc_count++ ; + Common->memory_inuse += (n * size) ; + Common->memory_usage = + MAX (Common->memory_usage, Common->memory_inuse) ; + PRINTM (("cholmod_malloc %p %g cnt: %g inuse %g\n", + p, (double) n*size, (double) Common->malloc_count, + (double) Common->memory_inuse)) ; + } + } + return (p) ; +} + + +/* ========================================================================== */ +/* === cholmod_free ========================================================= */ +/* ========================================================================== */ + +/* Wrapper around free routine. Returns NULL, which can be assigned to the + * pointer being freed, as in: + * + * p = cholmod_free (n, sizeof (int), p, Common) ; + * + * In CHOLMOD, the syntax: + * + * cholmod_free (n, sizeof (int), p, Common) ; + * + * is used if p is a local pointer and the routine is returning shortly. + * Uses a pointer to the free routine (or its equivalent) defined in Common. + * Nothing is freed if the pointer is NULL. + */ + +void *CHOLMOD(free) /* always returns NULL */ +( + /* ---- input ---- */ + size_t n, /* number of items */ + size_t size, /* size of each item */ + /* ---- in/out --- */ + void *p, /* block of memory to free */ + /* --------------- */ + cholmod_common *Common +) +{ + RETURN_IF_NULL_COMMON (NULL) ; + if (p != NULL) + { + /* only free the object if the pointer is not NULL */ + /* call free, or its equivalent */ + (Common->free_memory) (p) ; + Common->malloc_count-- ; + Common->memory_inuse -= (n * size) ; + PRINTM (("cholmod_free %p %g cnt: %g inuse %g\n", + p, (double) n*size, (double) Common->malloc_count, + (double) Common->memory_inuse)) ; + /* This assertion will fail if the user calls cholmod_malloc and + * cholmod_free with mismatched memory sizes. It shouldn't fail + * otherwise. */ + ASSERT (IMPLIES (Common->malloc_count == 0, Common->memory_inuse == 0)); + } + /* return NULL, and the caller should assign this to p. This avoids + * freeing the same pointer twice. */ + return (NULL) ; +} + + +/* ========================================================================== */ +/* === cholmod_calloc ======================================================= */ +/* ========================================================================== */ + +/* Wrapper around calloc routine. + * + * Uses a pointer to the calloc routine (or its equivalent) defined in Common. + * This routine is identical to malloc, except that it zeros the newly allocated + * block to zero. + */ + +void *CHOLMOD(calloc) /* returns pointer to the newly calloc'd block */ +( + /* ---- input ---- */ + size_t n, /* number of items */ + size_t size, /* size of each item */ + /* --------------- */ + cholmod_common *Common +) +{ + void *p ; + + RETURN_IF_NULL_COMMON (NULL) ; + if (size == 0) + { + ERROR (CHOLMOD_INVALID, "sizeof(item) must be > 0") ; + p = NULL ; + } + else if (n >= (Size_max / size) || n >= Int_max) + { + /* object is too big to allocate without causing integer overflow */ + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + p = NULL ; + } + else + { + /* call calloc, or its equivalent */ + p = (Common->calloc_memory) (MAX (1,n), size) ; + if (p == NULL) + { + /* failure: out of memory */ + ERROR (CHOLMOD_OUT_OF_MEMORY, "out of memory") ; + } + else + { + /* success: increment the count of objects allocated */ + Common->malloc_count++ ; + Common->memory_inuse += (n * size) ; + Common->memory_usage = + MAX (Common->memory_usage, Common->memory_inuse) ; + PRINTM (("cholmod_malloc %p %g cnt: %g inuse %g\n", + p, (double) n*size, (double) Common->malloc_count, + (double) Common->memory_inuse)) ; + } + } + return (p) ; +} + + +/* ========================================================================== */ +/* === cholmod_realloc ====================================================== */ +/* ========================================================================== */ + +/* Wrapper around realloc routine. Given a pointer p to a block of size + * (*n)*size memory, it changes the size of the block pointed to by p to be + * MAX(1,nnew)*size in size. It may return a pointer different than p. This + * should be used as (for a pointer to int): + * + * p = cholmod_realloc (nnew, sizeof (int), p, *n, Common) ; + * + * If p is NULL, this is the same as p = cholmod_malloc (...). + * A size of nnew=0 is treated as nnew=1. + * + * If the realloc fails, p is returned unchanged and Common->status is set + * to CHOLMOD_OUT_OF_MEMORY. If successful, Common->status is not modified, + * and p is returned (possibly changed) and pointing to a large block of memory. + * + * Uses a pointer to the realloc routine (or its equivalent) defined in Common. + */ + +void *CHOLMOD(realloc) /* returns pointer to reallocated block */ +( + /* ---- input ---- */ + size_t nnew, /* requested # of items in reallocated block */ + size_t size, /* size of each item */ + /* ---- in/out --- */ + void *p, /* block of memory to realloc */ + size_t *n, /* current size on input, nnew on output if successful*/ + /* --------------- */ + cholmod_common *Common +) +{ + size_t nold = (*n) ; + void *pnew ; + size_t s ; + int ok = TRUE ; + + RETURN_IF_NULL_COMMON (NULL) ; + if (size == 0) + { + ERROR (CHOLMOD_INVALID, "sizeof(item) must be > 0") ; + p = NULL ; + } + else if (p == NULL) + { + /* A fresh object is being allocated. */ + PRINT1 (("realloc fresh: %d %d\n", nnew, size)) ; + p = CHOLMOD(malloc) (nnew, size, Common) ; + *n = (p == NULL) ? 0 : nnew ; + } + else if (nold == nnew) + { + /* Nothing to do. Do not change p or n. */ + PRINT1 (("realloc nothing: %d %d\n", nnew, size)) ; + } + else if (nnew >= (Size_max / size) || nnew >= Int_max) + { + /* failure: nnew is too big. Do not change p or n. */ + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + } + else + { + /* The object exists, and is changing to some other nonzero size. */ + /* call realloc, or its equivalent */ + PRINT1 (("realloc : %d to %d, %d\n", nold, nnew, size)) ; + pnew = NULL ; + + s = CHOLMOD(mult_size_t) (MAX (1,nnew), size, &ok) ; + pnew = ok ? ((Common->realloc_memory) (p, s)) : NULL ; + + if (pnew == NULL) + { + /* Do not change p, since it still points to allocated memory */ + if (nnew <= nold) + { + /* The attempt to reduce the size of the block from n to + * nnew has failed. The current block is not modified, so + * pretend to succeed, but do not change p. Do change + * CHOLMOD's notion of the size of the block, however. */ + *n = nnew ; + PRINTM (("nnew <= nold failed, pretend to succeed\n")) ; + PRINTM (("cholmod_free %p %g cnt: %g inuse %g\n" + "cholmod_malloc %p %g cnt: %g inuse %g\n", + p, (double) nold*size, (double) Common->malloc_count-1, + (double) (Common->memory_inuse - nold*size), + p, (double) nnew*size, (double) Common->malloc_count, + (double) (Common->memory_inuse + (nnew-nold)*size))) ; + Common->memory_inuse += ((nnew-nold) * size) ; + } + else + { + /* Increasing the size of the block has failed. + * Do not change n. */ + ERROR (CHOLMOD_OUT_OF_MEMORY, "out of memory") ; + } + } + else + { + /* success: return revised p and change the size of the block */ + PRINTM (("cholmod_free %p %g cnt: %g inuse %g\n" + "cholmod_malloc %p %g cnt: %g inuse %g\n", + p, (double) nold*size, (double) Common->malloc_count-1, + (double) (Common->memory_inuse - nold*size), + pnew, (double) nnew*size, (double) Common->malloc_count, + (double) (Common->memory_inuse + (nnew-nold)*size))) ; + p = pnew ; + *n = nnew ; + Common->memory_inuse += ((nnew-nold) * size) ; + } + Common->memory_usage = MAX (Common->memory_usage, Common->memory_inuse); + } + + return (p) ; +} + + +/* ========================================================================== */ +/* === cholmod_realloc_multiple ============================================= */ +/* ========================================================================== */ + +/* reallocate multiple blocks of memory, all of the same size (up to two integer + * and two real blocks). Either reallocations all succeed, or all are returned + * in the original size (they are freed if the original size is zero). The nnew + * blocks are of size 1 or more. + */ + +int CHOLMOD(realloc_multiple) +( + /* ---- input ---- */ + size_t nnew, /* requested # of items in reallocated blocks */ + int nint, /* number of int/SuiteSparse_long blocks */ + int xtype, /* CHOLMOD_PATTERN, _REAL, _COMPLEX, or _ZOMPLEX */ + /* ---- in/out --- */ + void **Iblock, /* int or SuiteSparse_long block */ + void **Jblock, /* int or SuiteSparse_long block */ + void **Xblock, /* complex or double block */ + void **Zblock, /* zomplex case only: double block */ + size_t *nold_p, /* current size of the I,J,X,Z blocks on input, + * nnew on output if successful */ + /* --------------- */ + cholmod_common *Common +) +{ + double *xx, *zz ; + size_t i, j, x, z, nold ; + + RETURN_IF_NULL_COMMON (FALSE) ; + + if (xtype < CHOLMOD_PATTERN || xtype > CHOLMOD_ZOMPLEX) + { + ERROR (CHOLMOD_INVALID, "invalid xtype") ; + return (FALSE) ; + } + + nold = *nold_p ; + + if (nint < 1 && xtype == CHOLMOD_PATTERN) + { + /* nothing to do */ + return (TRUE) ; + } + + i = nold ; + j = nold ; + x = nold ; + z = nold ; + + if (nint > 0) + { + *Iblock = CHOLMOD(realloc) (nnew, sizeof (Int), *Iblock, &i, Common) ; + } + if (nint > 1) + { + *Jblock = CHOLMOD(realloc) (nnew, sizeof (Int), *Jblock, &j, Common) ; + } + + switch (xtype) + { + case CHOLMOD_REAL: + *Xblock = CHOLMOD(realloc) (nnew, sizeof (double), *Xblock, &x, + Common) ; + break ; + + case CHOLMOD_COMPLEX: + *Xblock = CHOLMOD(realloc) (nnew, 2*sizeof (double), *Xblock, &x, + Common) ; + break ; + + case CHOLMOD_ZOMPLEX: + *Xblock = CHOLMOD(realloc) (nnew, sizeof (double), *Xblock, &x, + Common) ; + *Zblock = CHOLMOD(realloc) (nnew, sizeof (double), *Zblock, &z, + Common) ; + break ; + } + + if (Common->status < CHOLMOD_OK) + { + /* one or more realloc's failed. Resize all back down to nold. */ + + if (nold == 0) + { + + if (nint > 0) + { + *Iblock = CHOLMOD(free) (i, sizeof (Int), *Iblock, Common) ; + } + if (nint > 1) + { + *Jblock = CHOLMOD(free) (j, sizeof (Int), *Jblock, Common) ; + } + + switch (xtype) + { + case CHOLMOD_REAL: + *Xblock = CHOLMOD(free) (x, sizeof (double), *Xblock, + Common) ; + break ; + + case CHOLMOD_COMPLEX: + *Xblock = CHOLMOD(free) (x, 2*sizeof (double), *Xblock, + Common) ; + break ; + + case CHOLMOD_ZOMPLEX: + *Xblock = CHOLMOD(free) (x, sizeof (double), *Xblock, + Common) ; + *Zblock = CHOLMOD(free) (x, sizeof (double), *Zblock, + Common) ; + break ; + } + + } + else + { + if (nint > 0) + { + *Iblock = CHOLMOD(realloc) (nold, sizeof (Int), *Iblock, &i, + Common) ; + } + if (nint > 1) + { + *Jblock = CHOLMOD(realloc) (nold, sizeof (Int), *Jblock, &j, + Common) ; + } + + switch (xtype) + { + case CHOLMOD_REAL: + *Xblock = CHOLMOD(realloc) (nold, sizeof (double), + *Xblock, &x, Common) ; + break ; + + case CHOLMOD_COMPLEX: + *Xblock = CHOLMOD(realloc) (nold, 2*sizeof (double), + *Xblock, &x, Common) ; + break ; + + case CHOLMOD_ZOMPLEX: + *Xblock = CHOLMOD(realloc) (nold, sizeof (double), + *Xblock, &x, Common) ; + *Zblock = CHOLMOD(realloc) (nold, sizeof (double), + *Zblock, &z, Common) ; + break ; + } + + } + + return (FALSE) ; + } + + if (nold == 0) + { + /* New space was allocated. Clear the first entry so that valgrind + * doesn't complain about its access in change_complexity + * (Core/cholmod_complex.c). */ + xx = *Xblock ; + zz = *Zblock ; + switch (xtype) + { + case CHOLMOD_REAL: + xx [0] = 0 ; + break ; + + case CHOLMOD_COMPLEX: + xx [0] = 0 ; + xx [1] = 0 ; + break ; + + case CHOLMOD_ZOMPLEX: + xx [0] = 0 ; + zz [0] = 0 ; + break ; + } + } + + /* all realloc's succeeded, change size to reflect realloc'ed size. */ + *nold_p = nnew ; + return (TRUE) ; +} diff --git a/src/CHOLMOD/Core/cholmod_sparse.c b/src/CHOLMOD/Core/cholmod_sparse.c new file mode 100644 index 0000000..557dbc1 --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_sparse.c @@ -0,0 +1,651 @@ +/* ========================================================================== */ +/* === Core/cholmod_sparse ================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Core utility routines for the cholmod_sparse object: + * + * A sparse matrix is held in compressed column form. In the basic type + * ("packed", which corresponds to a MATLAB sparse matrix), an n-by-n matrix + * with nz entries is held in three arrays: p of size n+1, i of size nz, and x + * of size nz. Row indices of column j are held in i [p [j] ... p [j+1]-1] and + * in the same locations in x. There may be no duplicate entries in a column. + * Row indices in each column may be sorted or unsorted (CHOLMOD keeps track). + * + * Primary routines: + * ----------------- + * cholmod_allocate_sparse allocate a sparse matrix + * cholmod_free_sparse free a sparse matrix + * + * Secondary routines: + * ------------------- + * cholmod_reallocate_sparse change the size (# entries) of sparse matrix + * cholmod_nnz number of nonzeros in a sparse matrix + * cholmod_speye sparse identity matrix + * cholmod_spzeros sparse zero matrix + * cholmod_copy_sparse create a copy of a sparse matrix + * + * All xtypes are supported (pattern, real, complex, and zomplex) + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + + +/* ========================================================================== */ +/* === cholmod_allocate_sparse ============================================== */ +/* ========================================================================== */ + +/* Allocate space for a matrix. A->i and A->x are not initialized. A->p + * (and A->nz if A is not packed) are set to zero, so a matrix containing no + * entries (all zero) is returned. See also cholmod_spzeros. + * + * workspace: none + */ + +cholmod_sparse *CHOLMOD(allocate_sparse) +( + /* ---- input ---- */ + size_t nrow, /* # of rows of A */ + size_t ncol, /* # of columns of A */ + size_t nzmax, /* max # of nonzeros of A */ + int sorted, /* TRUE if columns of A sorted, FALSE otherwise */ + int packed, /* TRUE if A will be packed, FALSE otherwise */ + int stype, /* stype of A */ + int xtype, /* CHOLMOD_PATTERN, _REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_sparse *A ; + Int *Ap, *Anz ; + size_t nzmax0 ; + Int j ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + if (stype != 0 && nrow != ncol) + { + ERROR (CHOLMOD_INVALID, "rectangular matrix with stype != 0 invalid") ; + return (NULL) ; + } + if (xtype < CHOLMOD_PATTERN || xtype > CHOLMOD_ZOMPLEX) + { + ERROR (CHOLMOD_INVALID, "xtype invalid") ; + return (NULL) ; + } + /* ensure the dimensions do not cause integer overflow */ + (void) CHOLMOD(add_size_t) (ncol, 2, &ok) ; + if (!ok || nrow > Int_max || ncol > Int_max || nzmax > Int_max) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (NULL) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate header */ + /* ---------------------------------------------------------------------- */ + + A = CHOLMOD(malloc) (sizeof (cholmod_sparse), 1, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + PRINT1 (("cholmod_allocate_sparse %d-by-%d nzmax %d sorted %d packed %d" + " xtype %d\n", nrow, ncol, nzmax, sorted, packed, xtype)) ; + + nzmax = MAX (1, nzmax) ; + + A->nrow = nrow ; + A->ncol = ncol ; + A->nzmax = nzmax ; + A->packed = packed ; /* default is packed (A->nz not present) */ + A->stype = stype ; + A->itype = ITYPE ; + A->xtype = xtype ; + A->dtype = DTYPE ; + + A->nz = NULL ; + A->p = NULL ; + A->i = NULL ; + A->x = NULL ; + A->z = NULL ; + + /* A 1-by-m matrix always has sorted columns */ + A->sorted = (nrow <= 1) ? TRUE : sorted ; + + /* ---------------------------------------------------------------------- */ + /* allocate the matrix itself */ + /* ---------------------------------------------------------------------- */ + + /* allocate O(ncol) space */ + A->p = CHOLMOD(malloc) (((size_t) ncol)+1, sizeof (Int), Common) ; + if (!packed) + { + A->nz = CHOLMOD(malloc) (ncol, sizeof (Int), Common) ; + } + + /* allocate O(nz) space */ + nzmax0 = 0 ; + CHOLMOD(realloc_multiple) (nzmax, 1, xtype, &(A->i), NULL, &(A->x), &(A->z), + &nzmax0, Common) ; + + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_sparse) (&A, Common) ; + return (NULL) ; /* out of memory */ + } + + /* ---------------------------------------------------------------------- */ + /* initialize A->p and A->nz so that A is an empty matrix */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + for (j = 0 ; j <= (Int) ncol ; j++) + { + Ap [j] = 0 ; + } + if (!packed) + { + Anz = A->nz ; + for (j = 0 ; j < (Int) ncol ; j++) + { + Anz [j] = 0 ; + } + } + return (A) ; +} + + +/* ========================================================================== */ +/* === cholmod_free_sparse ================================================== */ +/* ========================================================================== */ + +/* free a sparse matrix + * + * workspace: none + */ + +int CHOLMOD(free_sparse) +( + /* ---- in/out --- */ + cholmod_sparse **AHandle, /* matrix to deallocate, NULL on output */ + /* --------------- */ + cholmod_common *Common +) +{ + Int n, nz ; + cholmod_sparse *A ; + + RETURN_IF_NULL_COMMON (FALSE) ; + + if (AHandle == NULL) + { + /* nothing to do */ + return (TRUE) ; + } + A = *AHandle ; + if (A == NULL) + { + /* nothing to do */ + return (TRUE) ; + } + n = A->ncol ; + nz = A->nzmax ; + A->p = CHOLMOD(free) (n+1, sizeof (Int), A->p, Common) ; + A->i = CHOLMOD(free) (nz, sizeof (Int), A->i, Common) ; + A->nz = CHOLMOD(free) (n, sizeof (Int), A->nz, Common) ; + + switch (A->xtype) + { + case CHOLMOD_REAL: + A->x = CHOLMOD(free) (nz, sizeof (double), A->x, Common) ; + break ; + + case CHOLMOD_COMPLEX: + A->x = CHOLMOD(free) (nz, 2*sizeof (double), A->x, Common) ; + break ; + + case CHOLMOD_ZOMPLEX: + A->x = CHOLMOD(free) (nz, sizeof (double), A->x, Common) ; + A->z = CHOLMOD(free) (nz, sizeof (double), A->z, Common) ; + break ; + } + + *AHandle = CHOLMOD(free) (1, sizeof (cholmod_sparse), (*AHandle), Common) ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_reallocate_sparse ============================================ */ +/* ========================================================================== */ + +/* Change the size of A->i, A->x, and A->z, or allocate them if their current + * size is zero. A->x and A->z are not modified if A->xtype is CHOLMOD_PATTERN. + * A->z is not modified unless A->xtype is CHOLMOD_ZOMPLEX. + * + * workspace: none + */ + +int CHOLMOD(reallocate_sparse) +( + /* ---- input ---- */ + size_t nznew, /* new # of entries in A */ + /* ---- in/out --- */ + cholmod_sparse *A, /* matrix to reallocate */ + /* --------------- */ + cholmod_common *Common +) +{ + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + Common->status = CHOLMOD_OK ; + PRINT1 (("realloc matrix %d to %d, xtype: %d\n", + A->nzmax, nznew, A->xtype)) ; + + /* ---------------------------------------------------------------------- */ + /* resize the matrix */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(realloc_multiple) (MAX (1,nznew), 1, A->xtype, &(A->i), NULL, + &(A->x), &(A->z), &(A->nzmax), Common) ; + + return (Common->status == CHOLMOD_OK) ; +} + + +/* ========================================================================== */ +/* === cholmod_speye ======================================================== */ +/* ========================================================================== */ + +/* Return a sparse identity matrix. */ + +cholmod_sparse *CHOLMOD(speye) +( + /* ---- input ---- */ + size_t nrow, /* # of rows of A */ + size_t ncol, /* # of columns of A */ + int xtype, /* CHOLMOD_PATTERN, _REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Ax, *Az ; + cholmod_sparse *A ; + Int *Ap, *Ai ; + Int j, n ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate the matrix */ + /* ---------------------------------------------------------------------- */ + + n = MIN (nrow, ncol) ; + A = CHOLMOD(allocate_sparse) (nrow, ncol, n, TRUE, TRUE, 0, xtype, + Common) ; + + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory or inputs invalid */ + } + + /* ---------------------------------------------------------------------- */ + /* create the identity matrix */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + Ai = A->i ; + Ax = A->x ; + Az = A->z ; + + for (j = 0 ; j < n ; j++) + { + Ap [j] = j ; + } + for (j = n ; j <= ((Int) ncol) ; j++) + { + Ap [j] = n ; + } + for (j = 0 ; j < n ; j++) + { + Ai [j] = j ; + } + + switch (xtype) + { + case CHOLMOD_REAL: + for (j = 0 ; j < n ; j++) + { + Ax [j] = 1 ; + } + break ; + + case CHOLMOD_COMPLEX: + for (j = 0 ; j < n ; j++) + { + Ax [2*j ] = 1 ; + Ax [2*j+1] = 0 ; + } + break ; + + case CHOLMOD_ZOMPLEX: + for (j = 0 ; j < n ; j++) + { + Ax [j] = 1 ; + } + for (j = 0 ; j < n ; j++) + { + Az [j] = 0 ; + } + break ; + } + + return (A) ; +} + + +/* ========================================================================== */ +/* === cholmod_spzeros ====================================================== */ +/* ========================================================================== */ + +/* Return a sparse zero matrix. */ + +cholmod_sparse *CHOLMOD(spzeros) +( + /* ---- input ---- */ + size_t nrow, /* # of rows of A */ + size_t ncol, /* # of columns of A */ + size_t nzmax, /* max # of nonzeros of A */ + int xtype, /* CHOLMOD_PATTERN, _REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) +{ + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate the matrix */ + /* ---------------------------------------------------------------------- */ + + return (CHOLMOD(allocate_sparse) (nrow, ncol, nzmax, TRUE, TRUE, 0, xtype, + Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_nnz ========================================================== */ +/* ========================================================================== */ + +/* Return the number of entries in a sparse matrix. + * + * workspace: none + * integer overflow cannot occur, since the matrix is already allocated. + */ + +SuiteSparse_long CHOLMOD(nnz) +( + /* ---- input ---- */ + cholmod_sparse *A, + /* --------------- */ + cholmod_common *Common +) +{ + Int *Ap, *Anz ; + size_t nz ; + Int j, ncol ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (A, EMPTY) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* return nnz (A) */ + /* ---------------------------------------------------------------------- */ + + ncol = A->ncol ; + if (A->packed) + { + Ap = A->p ; + RETURN_IF_NULL (Ap, EMPTY) ; + nz = Ap [ncol] ; + } + else + { + Anz = A->nz ; + RETURN_IF_NULL (Anz, EMPTY) ; + nz = 0 ; + for (j = 0 ; j < ncol ; j++) + { + nz += MAX (0, Anz [j]) ; + } + } + return (nz) ; +} + + +/* ========================================================================== */ +/* === cholmod_copy_sparse ================================================== */ +/* ========================================================================== */ + +/* C = A. Create an exact copy of a sparse matrix, with one exception. + * Entries in unused space are not copied (they might not be initialized, + * and copying them would cause program checkers such as purify and + * valgrind to complain). The xtype of the resulting matrix C is the same as + * the xtype of the input matrix A. + * + * See also Core/cholmod_copy, which copies a matrix with possible changes + * in stype, presence of diagonal entries, pattern vs. numerical values, + * real and/or imaginary parts, and so on. + */ + +cholmod_sparse *CHOLMOD(copy_sparse) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to copy */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Ax, *Cx, *Az, *Cz ; + Int *Ap, *Ai, *Anz, *Cp, *Ci, *Cnz ; + cholmod_sparse *C ; + Int p, pend, j, ncol, packed, nzmax, nz, xtype ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, NULL) ; + if (A->stype != 0 && A->nrow != A->ncol) + { + ERROR (CHOLMOD_INVALID, "rectangular matrix with stype != 0 invalid") ; + return (NULL) ; + } + Common->status = CHOLMOD_OK ; + ASSERT (CHOLMOD(dump_sparse) (A, "A original", Common) >= 0) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + ncol = A->ncol ; + nzmax = A->nzmax ; + packed = A->packed ; + Ap = A->p ; + Ai = A->i ; + Ax = A->x ; + Az = A->z ; + Anz = A->nz ; + xtype = A->xtype ; + + /* ---------------------------------------------------------------------- */ + /* allocate the copy */ + /* ---------------------------------------------------------------------- */ + + C = CHOLMOD(allocate_sparse) (A->nrow, A->ncol, A->nzmax, A->sorted, + A->packed, A->stype, A->xtype, Common) ; + + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + Cp = C->p ; + Ci = C->i ; + Cx = C->x ; + Cz = C->z ; + Cnz = C->nz ; + + /* ---------------------------------------------------------------------- */ + /* copy the matrix */ + /* ---------------------------------------------------------------------- */ + + for (j = 0 ; j <= ncol ; j++) + { + Cp [j] = Ap [j] ; + } + + if (packed) + { + nz = Ap [ncol] ; + for (p = 0 ; p < nz ; p++) + { + Ci [p] = Ai [p] ; + } + + switch (xtype) + { + case CHOLMOD_REAL: + for (p = 0 ; p < nz ; p++) + { + Cx [p] = Ax [p] ; + } + break ; + + case CHOLMOD_COMPLEX: + for (p = 0 ; p < 2*nz ; p++) + { + Cx [p] = Ax [p] ; + } + break ; + + case CHOLMOD_ZOMPLEX: + for (p = 0 ; p < nz ; p++) + { + Cx [p] = Ax [p] ; + Cz [p] = Az [p] ; + } + break ; + } + + } + else + { + + for (j = 0 ; j < ncol ; j++) + { + Cnz [j] = Anz [j] ; + } + + switch (xtype) + { + case CHOLMOD_PATTERN: + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = p + Anz [j] ; + for ( ; p < pend ; p++) + { + Ci [p] = Ai [p] ; + } + } + break ; + + case CHOLMOD_REAL: + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = p + Anz [j] ; + for ( ; p < pend ; p++) + { + Ci [p] = Ai [p] ; + Cx [p] = Ax [p] ; + } + } + break ; + + case CHOLMOD_COMPLEX: + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = p + Anz [j] ; + for ( ; p < pend ; p++) + { + Ci [p] = Ai [p] ; + Cx [2*p ] = Ax [2*p ] ; + Cx [2*p+1] = Ax [2*p+1] ; + } + } + break ; + + case CHOLMOD_ZOMPLEX: + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = p + Anz [j] ; + for ( ; p < pend ; p++) + { + Ci [p] = Ai [p] ; + Cx [p] = Ax [p] ; + Cz [p] = Az [p] ; + } + } + break ; + } + } + + /* ---------------------------------------------------------------------- */ + /* return the result */ + /* ---------------------------------------------------------------------- */ + + ASSERT (CHOLMOD(dump_sparse) (C, "C copy", Common) >= 0) ; + return (C) ; +} diff --git a/src/CHOLMOD/Core/cholmod_transpose.c b/src/CHOLMOD/Core/cholmod_transpose.c new file mode 100644 index 0000000..51a7035 --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_transpose.c @@ -0,0 +1,1138 @@ +/* ========================================================================== */ +/* === Core/cholmod_transpose =============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Core utility routines for the cholmod_sparse object to + * compute the transpose or permuted transpose of a matrix: + * + * Primary routines: + * ----------------- + * cholmod_transpose transpose sparse matrix + * cholmod_ptranspose transpose and permute sparse matrix + * cholmod_sort sort row indices in each column of sparse matrix + * + * Secondary routines: + * ------------------- + * cholmod_transpose_unsym transpose unsymmetric sparse matrix + * cholmod_transpose_sym transpose symmetric sparse matrix + * + * All xtypes (pattern, real, complex, and zomplex) are supported. + * + * --------------------------------------- + * Unsymmetric case: A->stype is zero. + * --------------------------------------- + * + * Computes F = A', F = A (:,f)' or F = A (p,f)', except that the indexing by + * f does not work the same as the MATLAB notation (see below). A->stype + * is zero, which denotes that both the upper and lower triangular parts of + * A are present (and used). A may in fact be symmetric in pattern and/or + * value; A->stype just denotes which part of A are stored. A may be + * rectangular. + * + * p is a permutation of 0:m-1, and f is a subset of 0:n-1, where A is m-by-n. + * There can be no duplicate entries in p or f. + * + * The set f is held in fset and fsize. + * fset = NULL means ":" in MATLAB. fsize is ignored. + * fset != NULL means f = fset [0..fsize-1]. + * fset != NULL and fsize = 0 means f is the empty set. + * + * Columns not in the set f are considered to be zero. That is, + * if A is 5-by-10 then F = A (:,[3 4])' is not 2-by-5, but 10-by-5, and rows + * 3 and 4 of F are equal to columns 3 and 4 of A (the other rows of F are + * zero). More precisely, in MATLAB notation: + * + * [m n] = size (A) ; + * F = A ; + * notf = ones (1,n) ; + * notf (f) = 0 ; + * F (:, find (notf)) = 0 + * F = F' + * + * If you want the MATLAB equivalent F=A(p,f) operation, use cholmod_submatrix + * instead (which does not compute the transpose). + * + * F->nzmax must be large enough to hold the matrix F. It is not modified. + * If F->nz is present then F->nz [j] = # of entries in column j of F. + * + * A can be sorted or unsorted, with packed or unpacked columns. + * + * If f is present and not sorted in ascending order, then F is unsorted + * (that is, it may contain columns whose row indices do not appear in + * ascending order). Otherwise, F is sorted (the row indices in each + * column of F appear in strictly ascending order). + * + * F is returned in packed or unpacked form, depending on F->packed on input. + * If F->packed is false, then F is returned in unpacked form (F->nz must be + * present). Each row i of F is large enough to hold all the entries in row i + * of A, even if f is provided. That is, F->i and + * F->x [F->p [i] .. F->p [i] + F->nz [i] - 1] contain all entries in A (i,f), + * but F->p [i+1] - F->p [i] is equal to the number of nonzeros in A (i,:), + * not just A (i,f). + * + * The cholmod_transpose_unsym routine is the only operation in CHOLMOD that + * can produce an unpacked matrix. + * + * --------------------------------------- + * Symmetric case: A->stype is nonzero. + * --------------------------------------- + * + * Computes F = A' or F = A(p,p)', the transpose or permuted transpose, where + * A->stype is nonzero. + * + * If A->stype > 0, then A is a symmetric matrix where just the upper part + * of the matrix is stored. Entries in the lower triangular part may be + * present, but are ignored. A must be square. If F=A', then F is returned + * sorted; otherwise F is unsorted for the F=A(p,p)' case. + * + * There can be no duplicate entries in p. + * The fset and fsize parameters are not used. + * + * Three kinds of transposes are available, depending on the "values" parameter: + * 0: do not transpose the numerical values; create a CHOLMOD_PATTERN matrix + * 1: array transpose + * 2: complex conjugate transpose (same as 2 if input is real or pattern) + * + * ----------------------------------------------------------------------------- + * + * For cholmod_transpose_unsym and cholmod_transpose_sym, the output matrix + * F must already be pre-allocated by the caller, with the correct dimensions. + * If F is not valid or has the wrong dimensions, it is not modified. + * Otherwise, if F is too small, the transpose is not computed; the contents + * of F->p contain the column pointers of the resulting matrix, where + * F->p [F->ncol] > F->nzmax. In this case, the remaining contents of F are + * not modified. F can still be properly free'd with cholmod_free_sparse. + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + + +/* ========================================================================== */ +/* === TEMPLATE ============================================================= */ +/* ========================================================================== */ + +#define PATTERN +#include "t_cholmod_transpose.c" +#define REAL +#include "t_cholmod_transpose.c" +#define COMPLEX +#include "t_cholmod_transpose.c" +#define COMPLEX +#define NCONJUGATE +#include "t_cholmod_transpose.c" +#define ZOMPLEX +#include "t_cholmod_transpose.c" +#define ZOMPLEX +#define NCONJUGATE +#include "t_cholmod_transpose.c" + + +/* ========================================================================== */ +/* === cholmod_transpose_unsym ============================================== */ +/* ========================================================================== */ + +/* Compute F = A', A (:,f)', or A (p,f)', where A is unsymmetric and F is + * already allocated. See cholmod_transpose for a simpler routine. + * + * workspace: + * Iwork (MAX (nrow,ncol)) if fset is present + * Iwork (nrow) if fset is NULL + * + * The xtype of A and F must match, unless values is zero or F->xtype is + * CHOLMOD_PATTERN (in which case only the pattern of A is transpose into F). + */ + +int CHOLMOD(transpose_unsym) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to transpose */ + int values, /* 2: complex conj. transpose, 1: array transpose, + 0: do not transpose the numerical values */ + Int *Perm, /* size nrow, if present (can be NULL) */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* ---- output --- */ + cholmod_sparse *F, /* F = A', A(:,f)', or A(p,f)' */ + /* --------------- */ + cholmod_common *Common +) +{ + Int *Fp, *Fnz, *Ap, *Ai, *Anz, *Wi ; + Int nrow, ncol, permute, use_fset, Apacked, Fpacked, p, pend, + i, j, k, Fsorted, nf, jj, jlast ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (F, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (F, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + if (A->nrow != F->ncol || A->ncol != F->nrow) + { + ERROR (CHOLMOD_INVALID, "F has the wrong dimensions") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nf = fsize ; + use_fset = (fset != NULL) ; + nrow = A->nrow ; + ncol = A->ncol ; + + Ap = A->p ; /* size A->ncol+1, column pointers of A */ + Ai = A->i ; /* size nz = Ap [A->ncol], row indices of A */ + Anz = A->nz ; + Apacked = A->packed ; + ASSERT (IMPLIES (!Apacked, Anz != NULL)) ; + + permute = (Perm != NULL) ; + + Fp = F->p ; /* size A->nrow+1, row pointers of F */ + Fnz = F->nz ; + Fpacked = F->packed ; + ASSERT (IMPLIES (!Fpacked, Fnz != NULL)) ; + + nf = (use_fset) ? nf : ncol ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* s = nrow + ((fset != NULL) ? ncol : 0) */ + s = CHOLMOD(add_size_t) (nrow, ((fset != NULL) ? ncol : 0), &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (0, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; /* out of memory */ + } + + Wi = Common->Iwork ; /* size nrow (i/l/l) */ + + /* ---------------------------------------------------------------------- */ + /* check Perm and fset */ + /* ---------------------------------------------------------------------- */ + + if (permute) + { + for (i = 0 ; i < nrow ; i++) + { + Wi [i] = 1 ; + } + for (k = 0 ; k < nrow ; k++) + { + i = Perm [k] ; + if (i < 0 || i > nrow || Wi [i] == 0) + { + ERROR (CHOLMOD_INVALID, "invalid permutation") ; + return (FALSE) ; + } + Wi [i] = 0 ; + } + } + + if (use_fset) + { + for (j = 0 ; j < ncol ; j++) + { + Wi [j] = 1 ; + } + for (k = 0 ; k < nf ; k++) + { + j = fset [k] ; + if (j < 0 || j > ncol || Wi [j] == 0) + { + ERROR (CHOLMOD_INVALID, "invalid fset") ; + return (FALSE) ; + } + Wi [j] = 0 ; + } + } + + /* Perm and fset are now valid */ + ASSERT (CHOLMOD(dump_perm) (Perm, nrow, nrow, "Perm", Common)) ; + ASSERT (CHOLMOD(dump_perm) (fset, nf, ncol, "fset", Common)) ; + + /* ---------------------------------------------------------------------- */ + /* count the entries in each row of A or A(:,f) */ + /* ---------------------------------------------------------------------- */ + + for (i = 0 ; i < nrow ; i++) + { + Wi [i] = 0 ; + } + + jlast = EMPTY ; + Fsorted = TRUE ; + + if (use_fset) + { + /* count entries in each row of A(:,f) */ + for (jj = 0 ; jj < nf ; jj++) + { + j = fset [jj] ; + if (j <= jlast) + { + Fsorted = FALSE ; + } + p = Ap [j] ; + pend = (Apacked) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + Wi [Ai [p]]++ ; + } + jlast = j ; + } + + /* save the nz counts if F is unpacked, and recount all of A */ + if (!Fpacked) + { + if (permute) + { + for (i = 0 ; i < nrow ; i++) + { + Fnz [i] = Wi [Perm [i]] ; + } + } + else + { + for (i = 0 ; i < nrow ; i++) + { + Fnz [i] = Wi [i] ; + } + } + for (i = 0 ; i < nrow ; i++) + { + Wi [i] = 0 ; + } + + /* count entries in each row of A */ + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (Apacked) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + Wi [Ai [p]]++ ; + } + } + } + + } + else + { + + /* count entries in each row of A */ + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (Apacked) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + Wi [Ai [p]]++ ; + } + } + + /* save the nz counts if F is unpacked */ + if (!Fpacked) + { + if (permute) + { + for (i = 0 ; i < nrow ; i++) + { + Fnz [i] = Wi [Perm [i]] ; + } + } + else + { + for (i = 0 ; i < nrow ; i++) + { + Fnz [i] = Wi [i] ; + } + } + } + } + + /* ---------------------------------------------------------------------- */ + /* compute the row pointers */ + /* ---------------------------------------------------------------------- */ + + p = 0 ; + if (permute) + { + for (i = 0 ; i < nrow ; i++) + { + Fp [i] = p ; + p += Wi [Perm [i]] ; + } + for (i = 0 ; i < nrow ; i++) + { + Wi [Perm [i]] = Fp [i] ; + } + } + else + { + for (i = 0 ; i < nrow ; i++) + { + Fp [i] = p ; + p += Wi [i] ; + } + for (i = 0 ; i < nrow ; i++) + { + Wi [i] = Fp [i] ; + } + } + Fp [nrow] = p ; + + if (p > (Int) (F->nzmax)) + { + ERROR (CHOLMOD_INVALID, "F is too small") ; + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* transpose matrix, using template routine */ + /* ---------------------------------------------------------------------- */ + + ok = FALSE ; + if (values == 0 || F->xtype == CHOLMOD_PATTERN) + { + ok = p_cholmod_transpose_unsym (A, Perm, fset, nf, F, Common) ; + } + else if (F->xtype == CHOLMOD_REAL) + { + ok = r_cholmod_transpose_unsym (A, Perm, fset, nf, F, Common) ; + } + else if (F->xtype == CHOLMOD_COMPLEX) + { + if (values == 1) + { + /* array transpose */ + ok = ct_cholmod_transpose_unsym (A, Perm, fset, nf, F, Common) ; + } + else + { + /* complex conjugate transpose */ + ok = c_cholmod_transpose_unsym (A, Perm, fset, nf, F, Common) ; + } + } + else if (F->xtype == CHOLMOD_ZOMPLEX) + { + if (values == 1) + { + /* array transpose */ + ok = zt_cholmod_transpose_unsym (A, Perm, fset, nf, F, Common) ; + } + else + { + /* complex conjugate transpose */ + ok = z_cholmod_transpose_unsym (A, Perm, fset, nf, F, Common) ; + } + } + + /* ---------------------------------------------------------------------- */ + /* finalize result F */ + /* ---------------------------------------------------------------------- */ + + if (ok) + { + F->sorted = Fsorted ; + } + ASSERT (CHOLMOD(dump_sparse) (F, "output F unsym", Common) >= 0) ; + return (ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_transpose_sym ================================================ */ +/* ========================================================================== */ + +/* Compute F = A' or A (p,p)', where A is symmetric and F is already allocated. + * See cholmod_transpose for a simpler routine. + * + * workspace: Iwork (nrow) if Perm NULL, Iwork (2*nrow) if Perm non-NULL. + */ + +int CHOLMOD(transpose_sym) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to transpose */ + int values, /* 2: complex conj. transpose, 1: array transpose, + 0: do not transpose the numerical values */ + Int *Perm, /* size nrow, if present (can be NULL) */ + /* ---- output --- */ + cholmod_sparse *F, /* F = A' or A(p,p)' */ + /* --------------- */ + cholmod_common *Common +) +{ + Int *Ap, *Anz, *Ai, *Fp, *Wi, *Pinv, *Iwork ; + Int p, pend, packed, upper, permute, jold, n, i, j, k, iold ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (F, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (F, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + if (A->nrow != A->ncol || A->stype == 0) + { + /* this routine handles square symmetric matrices only */ + ERROR (CHOLMOD_INVALID, "matrix must be symmetric") ; + return (FALSE) ; + } + if (A->nrow != F->ncol || A->ncol != F->nrow) + { + ERROR (CHOLMOD_INVALID, "F has the wrong dimensions") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + permute = (Perm != NULL) ; + n = A->nrow ; + Ap = A->p ; /* size A->ncol+1, column pointers of A */ + Ai = A->i ; /* size nz = Ap [A->ncol], row indices of A */ + Anz = A->nz ; + packed = A->packed ; + ASSERT (IMPLIES (!packed, Anz != NULL)) ; + upper = (A->stype > 0) ; + + Fp = F->p ; /* size A->nrow+1, row pointers of F */ + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* s = (Perm != NULL) ? 2*n : n */ + s = CHOLMOD(add_size_t) (n, ((Perm != NULL) ? n : 0), &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (0, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; /* out of memory */ + } + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Iwork = Common->Iwork ; + Wi = Iwork ; /* size n (i/l/l) */ + Pinv = Iwork + n ; /* size n (i/i/l) , unused if Perm NULL */ + + /* ---------------------------------------------------------------------- */ + /* check Perm and construct inverse permutation */ + /* ---------------------------------------------------------------------- */ + + if (permute) + { + for (i = 0 ; i < n ; i++) + { + Pinv [i] = EMPTY ; + } + for (k = 0 ; k < n ; k++) + { + i = Perm [k] ; + if (i < 0 || i > n || Pinv [i] != EMPTY) + { + ERROR (CHOLMOD_INVALID, "invalid permutation") ; + return (FALSE) ; + } + Pinv [i] = k ; + } + } + + /* Perm is now valid */ + ASSERT (CHOLMOD(dump_perm) (Perm, n, n, "Perm", Common)) ; + + /* ---------------------------------------------------------------------- */ + /* count the entries in each row of F */ + /* ---------------------------------------------------------------------- */ + + for (i = 0 ; i < n ; i++) + { + Wi [i] = 0 ; + } + + if (packed) + { + if (permute) + { + if (upper) + { + /* packed, permuted, upper */ + for (j = 0 ; j < n ; j++) + { + jold = Perm [j] ; + pend = Ap [jold+1] ; + for (p = Ap [jold] ; p < pend ; p++) + { + iold = Ai [p] ; + if (iold <= jold) + { + i = Pinv [iold] ; + Wi [MIN (i, j)]++ ; + } + } + } + } + else + { + /* packed, permuted, lower */ + for (j = 0 ; j < n ; j++) + { + jold = Perm [j] ; + pend = Ap [jold+1] ; + for (p = Ap [jold] ; p < pend ; p++) + { + iold = Ai [p] ; + if (iold >= jold) + { + i = Pinv [iold] ; + Wi [MAX (i, j)]++ ; + } + } + } + } + } + else + { + if (upper) + { + /* packed, unpermuted, upper */ + for (j = 0 ; j < n ; j++) + { + pend = Ap [j+1] ; + for (p = Ap [j] ; p < pend ; p++) + { + i = Ai [p] ; + if (i <= j) + { + Wi [i]++ ; + } + } + } + } + else + { + /* packed, unpermuted, lower */ + for (j = 0 ; j < n ; j++) + { + pend = Ap [j+1] ; + for (p = Ap [j] ; p < pend ; p++) + { + i = Ai [p] ; + if (i >= j) + { + Wi [i]++ ; + } + } + } + } + } + } + else + { + if (permute) + { + if (upper) + { + /* unpacked, permuted, upper */ + for (j = 0 ; j < n ; j++) + { + jold = Perm [j] ; + p = Ap [jold] ; + pend = p + Anz [jold] ; + for ( ; p < pend ; p++) + { + iold = Ai [p] ; + if (iold <= jold) + { + i = Pinv [iold] ; + Wi [MIN (i, j)]++ ; + } + } + } + } + else + { + /* unpacked, permuted, lower */ + for (j = 0 ; j < n ; j++) + { + jold = Perm [j] ; + p = Ap [jold] ; + pend = p + Anz [jold] ; + for ( ; p < pend ; p++) + { + iold = Ai [p] ; + if (iold >= jold) + { + i = Pinv [iold] ; + Wi [MAX (i, j)]++ ; + } + } + } + } + } + else + { + if (upper) + { + /* unpacked, unpermuted, upper */ + for (j = 0 ; j < n ; j++) + { + p = Ap [j] ; + pend = p + Anz [j] ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i <= j) + { + Wi [i]++ ; + } + } + } + } + else + { + /* unpacked, unpermuted, lower */ + for (j = 0 ; j < n ; j++) + { + p = Ap [j] ; + pend = p + Anz [j] ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i >= j) + { + Wi [i]++ ; + } + } + } + } + } + } + + /* ---------------------------------------------------------------------- */ + /* compute the row pointers */ + /* ---------------------------------------------------------------------- */ + + p = 0 ; + for (i = 0 ; i < n ; i++) + { + Fp [i] = p ; + p += Wi [i] ; + } + Fp [n] = p ; + for (i = 0 ; i < n ; i++) + { + Wi [i] = Fp [i] ; + } + + if (p > (Int) (F->nzmax)) + { + ERROR (CHOLMOD_INVALID, "F is too small") ; + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* transpose matrix, using template routine */ + /* ---------------------------------------------------------------------- */ + + ok = FALSE ; + if (values == 0 || F->xtype == CHOLMOD_PATTERN) + { + PRINT2 (("\n:::: p_transpose_sym Perm %p\n", Perm)) ; + ok = p_cholmod_transpose_sym (A, Perm, F, Common) ; + } + else if (F->xtype == CHOLMOD_REAL) + { + PRINT2 (("\n:::: r_transpose_sym Perm %p\n", Perm)) ; + ok = r_cholmod_transpose_sym (A, Perm, F, Common) ; + } + else if (F->xtype == CHOLMOD_COMPLEX) + { + if (values == 1) + { + /* array transpose */ + PRINT2 (("\n:::: ct_transpose_sym Perm %p\n", Perm)) ; + ok = ct_cholmod_transpose_sym (A, Perm, F, Common) ; + } + else + { + /* complex conjugate transpose */ + PRINT2 (("\n:::: c_transpose_sym Perm %p\n", Perm)) ; + ok = c_cholmod_transpose_sym (A, Perm, F, Common) ; + } + } + else if (F->xtype == CHOLMOD_ZOMPLEX) + { + if (values == 1) + { + /* array transpose */ + PRINT2 (("\n:::: zt_transpose_sym Perm %p\n", Perm)) ; + ok = zt_cholmod_transpose_sym (A, Perm, F, Common) ; + } + else + { + /* complex conjugate transpose */ + PRINT2 (("\n:::: z_transpose_sym Perm %p\n", Perm)) ; + ok = z_cholmod_transpose_sym (A, Perm, F, Common) ; + } + } + + /* ---------------------------------------------------------------------- */ + /* finalize result F */ + /* ---------------------------------------------------------------------- */ + + /* F is sorted if there is no permutation vector */ + if (ok) + { + F->sorted = !permute ; + F->packed = TRUE ; + F->stype = - SIGN (A->stype) ; /* flip the stype */ + ASSERT (CHOLMOD(dump_sparse) (F, "output F sym", Common) >= 0) ; + } + return (ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_transpose ==================================================== */ +/* ========================================================================== */ + +/* Returns A'. See also cholmod_ptranspose below. */ + +cholmod_sparse *CHOLMOD(transpose) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to transpose */ + int values, /* 2: complex conj. transpose, 1: array transpose, + 0: do not transpose the numerical values + (returns its result as CHOLMOD_PATTERN) */ + /* --------------- */ + cholmod_common *Common +) +{ + return (CHOLMOD(ptranspose) (A, values, NULL, NULL, 0, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_ptranspose =================================================== */ +/* ========================================================================== */ + +/* Return A' or A(p,p)' if A is symmetric. Return A', A(:,f)', or A(p,f)' if + * A is unsymmetric. + * + * workspace: + * Iwork (MAX (nrow,ncol)) if unsymmetric and fset is non-NULL + * Iwork (nrow) if unsymmetric and fset is NULL + * Iwork (2*nrow) if symmetric and Perm is non-NULL. + * Iwork (nrow) if symmetric and Perm is NULL. + * + * A simple worst-case upper bound on the workspace is nrow+ncol. + */ + +cholmod_sparse *CHOLMOD(ptranspose) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to transpose */ + int values, /* 2: complex conj. transpose, 1: array transpose, + 0: do not transpose the numerical values */ + Int *Perm, /* if non-NULL, F = A(p,f) or A(p,p) */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* --------------- */ + cholmod_common *Common +) +{ + Int *Ap, *Anz ; + cholmod_sparse *F ; + Int nrow, ncol, use_fset, j, jj, fnz, packed, stype, nf, xtype ; + size_t ineed ; + int ok = TRUE ; + + nf = fsize ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, NULL) ; + stype = A->stype ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + nrow = A->nrow ; + ncol = A->ncol ; + + if (stype != 0) + { + use_fset = FALSE ; + if (Perm != NULL) + { + ineed = CHOLMOD(mult_size_t) (A->nrow, 2, &ok) ; + } + else + { + ineed = A->nrow ; + } + } + else + { + use_fset = (fset != NULL) ; + if (use_fset) + { + ineed = MAX (A->nrow, A->ncol) ; + } + else + { + ineed = A->nrow ; + } + } + + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (NULL) ; + } + + CHOLMOD(allocate_work) (0, ineed, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + Anz = A->nz ; + packed = A->packed ; + ASSERT (IMPLIES (!packed, Anz != NULL)) ; + xtype = values ? A->xtype : CHOLMOD_PATTERN ; + + /* ---------------------------------------------------------------------- */ + /* allocate F */ + /* ---------------------------------------------------------------------- */ + + /* determine # of nonzeros in F */ + if (stype != 0) + { + /* F=A' or F=A(p,p)', fset is ignored */ + fnz = CHOLMOD(nnz) (A, Common) ; + } + else + { + nf = (use_fset) ? nf : ncol ; + if (use_fset) + { + fnz = 0 ; + /* F=A(:,f)' or F=A(p,f)' */ + for (jj = 0 ; jj < nf ; jj++) + { + /* The fset is not yet checked; it will be thoroughly checked + * in cholmod_transpose_unsym. For now, just make sure we don't + * access Ap and Anz out of bounds. */ + j = fset [jj] ; + if (j >= 0 && j < ncol) + { + fnz += packed ? (Ap [j+1] - Ap [j]) : MAX (0, Anz [j]) ; + } + } + } + else + { + /* F=A' or F=A(p,:)' */ + fnz = CHOLMOD(nnz) (A, Common) ; + } + } + + /* F is ncol-by-nrow, fnz nonzeros, sorted unless f is present and unsorted, + * packed, of opposite stype as A, and with/without numerical values */ + F = CHOLMOD(allocate_sparse) (ncol, nrow, fnz, TRUE, TRUE, -SIGN(stype), + xtype, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + /* ---------------------------------------------------------------------- */ + /* transpose and optionally permute the matrix A */ + /* ---------------------------------------------------------------------- */ + + if (stype != 0) + { + /* F = A (p,p)', using upper or lower triangular part of A only */ + ok = CHOLMOD(transpose_sym) (A, values, Perm, F, Common) ; + } + else + { + /* F = A (p,f)' */ + ok = CHOLMOD(transpose_unsym) (A, values, Perm, fset, nf, F, Common) ; + } + + /* ---------------------------------------------------------------------- */ + /* return the matrix F, or NULL if an error occured */ + /* ---------------------------------------------------------------------- */ + + if (!ok) + { + CHOLMOD(free_sparse) (&F, Common) ; + } + return (F) ; +} + + +/* ========================================================================== */ +/* === cholmod_sort ========================================================= */ +/* ========================================================================== */ + +/* Sort the columns of A, in place. Returns A in packed form, even if it + * starts as unpacked. Removes entries in the ignored part of a symmetric + * matrix. + * + * workspace: Iwork (max (nrow,ncol)). Allocates additional workspace for a + * temporary copy of A'. + */ + +int CHOLMOD(sort) +( + /* ---- in/out --- */ + cholmod_sparse *A, /* matrix to sort */ + /* --------------- */ + cholmod_common *Common +) +{ + Int *Ap ; + cholmod_sparse *F ; + Int anz, ncol, nrow, stype ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + Common->status = CHOLMOD_OK ; + nrow = A->nrow ; + if (nrow <= 1) + { + /* a 1-by-n sparse matrix must be sorted */ + A->sorted = TRUE ; + return (TRUE) ; + } + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + ncol = A->ncol ; + CHOLMOD(allocate_work) (0, MAX (nrow, ncol), 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; /* out of memory */ + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + anz = CHOLMOD(nnz) (A, Common) ; + stype = A->stype ; + + /* ---------------------------------------------------------------------- */ + /* sort the columns of the matrix */ + /* ---------------------------------------------------------------------- */ + + /* allocate workspace for transpose: ncol-by-nrow, same # of nonzeros as A, + * sorted, packed, same stype as A, and of the same numeric type as A. */ + F = CHOLMOD(allocate_sparse) (ncol, nrow, anz, TRUE, TRUE, stype, + A->xtype, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; /* out of memory */ + } + + if (stype != 0) + { + /* F = A', upper or lower triangular part only */ + CHOLMOD(transpose_sym) (A, 1, NULL, F, Common) ; + A->packed = TRUE ; + /* A = F' */ + CHOLMOD(transpose_sym) (F, 1, NULL, A, Common) ; + } + else + { + /* F = A' */ + CHOLMOD(transpose_unsym) (A, 1, NULL, NULL, 0, F, Common) ; + A->packed = TRUE ; + /* A = F' */ + CHOLMOD(transpose_unsym) (F, 1, NULL, NULL, 0, A, Common) ; + } + + ASSERT (A->sorted && A->packed) ; + ASSERT (CHOLMOD(dump_sparse) (A, "Asorted", Common) >= 0) ; + + /* ---------------------------------------------------------------------- */ + /* reduce A in size, if needed. This must succeed. */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + anz = Ap [ncol] ; + ASSERT ((size_t) anz <= A->nzmax) ; + CHOLMOD(reallocate_sparse) (anz, A, Common) ; + ASSERT (Common->status >= CHOLMOD_OK) ; + + /* ---------------------------------------------------------------------- */ + /* free workspace */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(free_sparse) (&F, Common) ; + return (TRUE) ; +} diff --git a/src/CHOLMOD/Core/cholmod_triplet.c b/src/CHOLMOD/Core/cholmod_triplet.c new file mode 100644 index 0000000..a5f39b2 --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_triplet.c @@ -0,0 +1,772 @@ +/* ========================================================================== */ +/* === Core/cholmod_triplet ================================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Core utility routines for the cholmod_triplet object: + * + * A sparse matrix held in triplet form is the simplest one for a user to + * create. It consists of a list of nz entries in arbitrary order, held in + * three arrays: i, j, and x, each of length nk. The kth entry is in row i[k], + * column j[k], with value x[k]. There may be duplicate values; if A(i,j) + * appears more than once, its value is the sum of the entries with those row + * and column indices. + * + * Primary routines: + * ----------------- + * cholmod_allocate_triplet allocate a triplet matrix + * cholmod_free_triplet free a triplet matrix + * + * Secondary routines: + * ------------------- + * cholmod_reallocate_triplet reallocate a triplet matrix + * cholmod_sparse_to_triplet create a triplet matrix copy of a sparse matrix + * cholmod_triplet_to_sparse create a sparse matrix copy of a triplet matrix + * cholmod_copy_triplet create a copy of a triplet matrix + * + * The relationship between an m-by-n cholmod_sparse matrix A and a + * cholmod_triplet matrix (i, j, and x) is identical to how they are used in + * the MATLAB "sparse" and "find" functions: + * + * [i j x] = find (A) + * [m n] = size (A) + * A = sparse (i,j,x,m,n) + * + * with the exception that the cholmod_sparse matrix may be "unpacked", may + * have either sorted or unsorted columns (depending on the option selected), + * and may be symmetric with just the upper or lower triangular part stored. + * Likewise, the cholmod_triplet matrix may contain just the entries in the + * upper or lower triangular part of a symmetric matrix. + * + * MATLAB sparse matrices are always "packed", always have sorted columns, + * and always store both parts of a symmetric matrix. In some cases, MATLAB + * behaves like CHOLMOD by ignoring entries in the upper or lower triangular + * part of a matrix that is otherwise assumed to be symmetric (such as the + * input to chol). In CHOLMOD, that option is a characteristic of the object. + * In MATLAB, that option is based on how a matrix is used as the input to + * a function. + * + * The triplet matrix is provided to give the user a simple way of constructing + * a sparse matrix. There are very few operations supported for triplet + * matrices. The assumption is that they will be converted to cholmod_sparse + * matrix form first. + * + * Adding two triplet matrices simply involves concatenating the contents of + * the three arrays (i, j, and x). To permute a triplet matrix, just replace + * the row and column indices with their permuted values. For example, if + * P is a permutation vector, then P [k] = j means row/column j is the kth + * row/column in C=P*A*P'. In MATLAB notation, C=A(p,p). If Pinv is an array + * of size n and T is the triplet form of A, then: + * + * Ti = T->i ; + * Tj = T->j ; + * for (k = 0 ; k < n ; k++) Pinv [P [k]] = k ; + * for (k = 0 ; k < nz ; k++) Ti [k] = Pinv [Ti [k]] ; + * for (k = 0 ; k < nz ; k++) Tj [k] = Pinv [Tj [k]] ; + * + * overwrites T with the triplet form of C=P*A*P'. The conversion + * + * C = cholmod_triplet_to_sparse (T, 0, &Common) ; + * + * will then return the matrix C = P*A*P'. + * + * Note that T->stype > 0 means that entries in the lower triangular part of + * T are transposed into the upper triangular part when T is converted to + * sparse matrix (cholmod_sparse) form with cholmod_triplet_to_sparse. The + * opposite is true for T->stype < 0. + * + * Since the triplet matrix T is so simple to generate, it's quite easy + * to remove entries that you do not want, prior to converting T to the + * cholmod_sparse form. So if you include these entries in T, CHOLMOD + * assumes that there must be a reason (such as the one above). Thus, + * no entry in a triplet matrix is ever ignored. + * + * Other operations, such as extacting a submatrix, horizontal and vertical + * concatenation, multiply a triplet matrix times a dense matrix, are also + * simple. Multiplying two triplet matrices is not trivial; the simplest + * method is to convert them to cholmod_sparse matrices first. + * + * Supports all xtypes (pattern, real, complex, and zomplex). + */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + + +/* ========================================================================== */ +/* === TEMPLATE ============================================================= */ +/* ========================================================================== */ + +#define PATTERN +#include "t_cholmod_triplet.c" +#define REAL +#include "t_cholmod_triplet.c" +#define COMPLEX +#include "t_cholmod_triplet.c" +#define ZOMPLEX +#include "t_cholmod_triplet.c" + + +/* ========================================================================== */ +/* === cholmod_allocate_triplet ============================================= */ +/* ========================================================================== */ + +/* allocate space for a triplet matrix + * + * workspace: none + */ + +cholmod_triplet *CHOLMOD(allocate_triplet) +( + /* ---- input ---- */ + size_t nrow, /* # of rows of T */ + size_t ncol, /* # of columns of T */ + size_t nzmax, /* max # of nonzeros of T */ + int stype, /* stype of T */ + int xtype, /* CHOLMOD_PATTERN, _REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_triplet *T ; + size_t nzmax0 ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + if (xtype < CHOLMOD_PATTERN || xtype > CHOLMOD_ZOMPLEX) + { + ERROR (CHOLMOD_INVALID, "xtype invalid") ; + return (NULL) ; + } + /* ensure the dimensions do not cause integer overflow */ + (void) CHOLMOD(add_size_t) (ncol, 2, &ok) ; + if (!ok || nrow > Int_max || ncol > Int_max || nzmax > Int_max) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (NULL) ; + } + + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate header */ + /* ---------------------------------------------------------------------- */ + + T = CHOLMOD(malloc) (sizeof (cholmod_triplet), 1, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + PRINT1 (("cholmod_allocate_triplet %d-by-%d nzmax %d xtype %d\n", + nrow, ncol, nzmax, xtype)) ; + + nzmax = MAX (1, nzmax) ; + + T->nrow = nrow ; + T->ncol = ncol ; + T->nzmax = nzmax ; + T->nnz = 0 ; + T->stype = stype ; + T->itype = ITYPE ; + T->xtype = xtype ; + T->dtype = DTYPE ; + + T->j = NULL ; + T->i = NULL ; + T->x = NULL ; + T->z = NULL ; + + /* ---------------------------------------------------------------------- */ + /* allocate the matrix itself */ + /* ---------------------------------------------------------------------- */ + + nzmax0 = 0 ; + CHOLMOD(realloc_multiple) (nzmax, 2, xtype, &(T->i), &(T->j), + &(T->x), &(T->z), &nzmax0, Common) ; + + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_triplet) (&T, Common) ; + return (NULL) ; /* out of memory */ + } + + return (T) ; +} + + +/* ========================================================================== */ +/* === cholmod_free_triplet ================================================= */ +/* ========================================================================== */ + +/* free a triplet matrix + * + * workspace: none + */ + +int CHOLMOD(free_triplet) +( + /* ---- in/out --- */ + cholmod_triplet **THandle, /* matrix to deallocate, NULL on output */ + /* --------------- */ + cholmod_common *Common +) +{ + Int nz ; + cholmod_triplet *T ; + + RETURN_IF_NULL_COMMON (FALSE) ; + + if (THandle == NULL) + { + /* nothing to do */ + return (TRUE) ; + } + T = *THandle ; + if (T == NULL) + { + /* nothing to do */ + return (TRUE) ; + } + nz = T->nzmax ; + T->j = CHOLMOD(free) (nz, sizeof (Int), T->j, Common) ; + T->i = CHOLMOD(free) (nz, sizeof (Int), T->i, Common) ; + if (T->xtype == CHOLMOD_REAL) + { + T->x = CHOLMOD(free) (nz, sizeof (double), T->x, Common) ; + } + else if (T->xtype == CHOLMOD_COMPLEX) + { + T->x = CHOLMOD(free) (nz, 2*sizeof (double), T->x, Common) ; + } + else if (T->xtype == CHOLMOD_ZOMPLEX) + { + T->x = CHOLMOD(free) (nz, sizeof (double), T->x, Common) ; + T->z = CHOLMOD(free) (nz, sizeof (double), T->z, Common) ; + } + *THandle = CHOLMOD(free) (1, sizeof (cholmod_triplet), (*THandle), Common) ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_reallocate_triplet =========================================== */ +/* ========================================================================== */ + +/* Change the size of T->i, T->j, and T->x, or allocate them if their current + * size is zero. T->x is not modified if T->xtype is CHOLMOD_PATTERN. + * + * workspace: none + */ + +int CHOLMOD(reallocate_triplet) +( + /* ---- input ---- */ + size_t nznew, /* new # of entries in T */ + /* ---- in/out --- */ + cholmod_triplet *T, /* triplet matrix to modify */ + /* --------------- */ + cholmod_common *Common +) +{ + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (T, FALSE) ; + RETURN_IF_XTYPE_INVALID (T, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + Common->status = CHOLMOD_OK ; + PRINT1 (("realloc triplet %d to %d, xtype: %d\n", + T->nzmax, nznew, T->xtype)) ; + + /* ---------------------------------------------------------------------- */ + /* resize the matrix */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(realloc_multiple) (MAX (1,nznew), 2, T->xtype, &(T->i), &(T->j), + &(T->x), &(T->z), &(T->nzmax), Common) ; + + return (Common->status == CHOLMOD_OK) ; +} + + +/* ========================================================================== */ +/* === cholmod_triplet_to_sparse ============================================ */ +/* ========================================================================== */ + +/* Convert a set of triplets into a cholmod_sparse matrix. In MATLAB notation, + * for unsymmetric matrices: + * + * A = sparse (Ti, Tj, Tx, nrow, ncol, nzmax) ; + * + * For the symmetric upper case: + * + * A = sparse (min(Ti,Tj), max(Ti,Tj), Tx, nrow, ncol, nzmax) ; + * + * For the symmetric lower case: + * + * A = sparse (max(Ti,Tj), min(Ti,Tj), Tx, nrow, ncol, nzmax) ; + * + * If Tx is NULL, then A->x is not allocated, and only the pattern of A is + * computed. A is returned in packed form, and can be of any stype + * (upper/lower/unsymmetric). It has enough space to hold the values in T, + * or nzmax, whichever is larger. + * + * workspace: Iwork (max (nrow,ncol)) + * allocates a temporary copy of its output matrix. + * + * The resulting sparse matrix has the same xtype as the input triplet matrix. + */ + +cholmod_sparse *CHOLMOD(triplet_to_sparse) +( + /* ---- input ---- */ + cholmod_triplet *T, /* matrix to copy */ + size_t nzmax, /* allocate at least this much space in output matrix */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_sparse *R, *A = NULL ; + Int *Wj, *Rp, *Ri, *Rnz, *Ti, *Tj ; + Int i, j, p, k, stype, nrow, ncol, nz, ok ; + size_t anz = 0 ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (T, NULL) ; + Ti = T->i ; + Tj = T->j ; + RETURN_IF_NULL (Ti, NULL) ; + RETURN_IF_NULL (Tj, NULL) ; + RETURN_IF_XTYPE_INVALID (T, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, NULL) ; + stype = SIGN (T->stype) ; + if (stype && T->nrow != T->ncol) + { + /* inputs invalid */ + ERROR (CHOLMOD_INVALID, "matrix invalid") ; + return (NULL) ; + } + Common->status = CHOLMOD_OK ; + DEBUG (CHOLMOD(dump_triplet) (T, "T", Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nrow = T->nrow ; + ncol = T->ncol ; + nz = T->nnz ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(allocate_work) (0, MAX (nrow, ncol), 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + /* ---------------------------------------------------------------------- */ + /* allocate temporary matrix R */ + /* ---------------------------------------------------------------------- */ + + R = CHOLMOD(allocate_sparse) (ncol, nrow, nz, FALSE, FALSE, -stype, + T->xtype, Common) ; + + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + Rp = R->p ; + Ri = R->i ; + Rnz = R->nz ; + + /* ---------------------------------------------------------------------- */ + /* count the entries in each row of A (also counting duplicates) */ + /* ---------------------------------------------------------------------- */ + + for (i = 0 ; i < nrow ; i++) + { + Rnz [i] = 0 ; + } + + if (stype > 0) + { + for (k = 0 ; k < nz ; k++) + { + i = Ti [k] ; + j = Tj [k] ; + if (i < 0 || i >= nrow || j < 0 || j >= ncol) + { + ERROR (CHOLMOD_INVALID, "index out of range") ; + break ; + } + /* A will be symmetric with just the upper triangular part stored. + * Create a matrix R that is lower triangular. Entries in the + * upper part of R are transposed to the lower part. */ + Rnz [MIN (i,j)]++ ; + } + } + else if (stype < 0) + { + for (k = 0 ; k < nz ; k++) + { + i = Ti [k] ; + j = Tj [k] ; + if (i < 0 || i >= nrow || j < 0 || j >= ncol) + { + ERROR (CHOLMOD_INVALID, "index out of range") ; + break ; + } + /* A will be symmetric with just the lower triangular part stored. + * Create a matrix R that is upper triangular. Entries in the + * lower part of R are transposed to the upper part. */ + Rnz [MAX (i,j)]++ ; + } + } + else + { + for (k = 0 ; k < nz ; k++) + { + i = Ti [k] ; + j = Tj [k] ; + if (i < 0 || i >= nrow || j < 0 || j >= ncol) + { + ERROR (CHOLMOD_INVALID, "index out of range") ; + break ; + } + /* constructing an unsymmetric matrix */ + Rnz [i]++ ; + } + } + + if (Common->status < CHOLMOD_OK) + { + /* triplet matrix is invalid */ + CHOLMOD(free_sparse) (&R, Common) ; + return (NULL) ; + } + + /* ---------------------------------------------------------------------- */ + /* construct the row pointers */ + /* ---------------------------------------------------------------------- */ + + p = 0 ; + for (i = 0 ; i < nrow ; i++) + { + Rp [i] = p ; + p += Rnz [i] ; + } + Rp [nrow] = p ; + + /* use Wj (i/l/l) as temporary row pointers */ + Wj = Common->Iwork ; /* size MAX (nrow,ncol) FUTURE WORK: (i/l/l) */ + for (i = 0 ; i < nrow ; i++) + { + Wj [i] = Rp [i] ; + } + + /* ---------------------------------------------------------------------- */ + /* construct triplet matrix, using template routine */ + /* ---------------------------------------------------------------------- */ + + switch (T->xtype) + { + case CHOLMOD_PATTERN: + anz = p_cholmod_triplet_to_sparse (T, R, Common) ; + break ; + + case CHOLMOD_REAL: + anz = r_cholmod_triplet_to_sparse (T, R, Common) ; + break ; + + case CHOLMOD_COMPLEX: + anz = c_cholmod_triplet_to_sparse (T, R, Common) ; + break ; + + case CHOLMOD_ZOMPLEX: + anz = z_cholmod_triplet_to_sparse (T, R, Common) ; + break ; + } + + /* ---------------------------------------------------------------------- */ + /* A = R' (array transpose, not complex conjugate transpose) */ + /* ---------------------------------------------------------------------- */ + + /* workspace: Iwork (R->nrow), which is A->ncol */ + + ASSERT (CHOLMOD(dump_sparse) (R, "R", Common) >= 0) ; + + A = CHOLMOD(allocate_sparse) (nrow, ncol, MAX (anz, nzmax), TRUE, TRUE, + stype, T->xtype, Common) ; + + if (stype) + { + ok = CHOLMOD(transpose_sym) (R, 1, NULL, A, Common) ; + } + else + { + ok = CHOLMOD(transpose_unsym) (R, 1, NULL, NULL, 0, A, Common) ; + } + + CHOLMOD(free_sparse) (&R, Common) ; + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free_sparse) (&A, Common) ; + } + + /* ---------------------------------------------------------------------- */ + /* return result */ + /* ---------------------------------------------------------------------- */ + + ASSERT (CHOLMOD(dump_sparse) (A, "A = triplet(T) result", Common) >= 0) ; + return (A) ; +} + + +/* ========================================================================== */ +/* === cholmod_sparse_to_triplet ============================================ */ +/* ========================================================================== */ + +/* Converts a sparse column-oriented matrix to triplet form. + * The resulting triplet matrix has the same xtype as the sparse matrix. + * + * workspace: none + */ + +cholmod_triplet *CHOLMOD(sparse_to_triplet) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to copy */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Ax, *Az, *Tx, *Tz ; + Int *Ap, *Ai, *Ti, *Tj, *Anz ; + cholmod_triplet *T ; + Int i, xtype, p, pend, k, j, nrow, ncol, nz, stype, packed, up, lo, + both ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, NULL) ; + stype = SIGN (A->stype) ; + nrow = A->nrow ; + ncol = A->ncol ; + if (stype && nrow != ncol) + { + /* inputs invalid */ + ERROR (CHOLMOD_INVALID, "matrix invalid") ; + return (NULL) ; + } + Ax = A->x ; + Az = A->z ; + xtype = A->xtype ; + Common->status = CHOLMOD_OK ; + + ASSERT (CHOLMOD(dump_sparse) (A, "A", Common) >= 0) ; + + /* ---------------------------------------------------------------------- */ + /* allocate triplet matrix */ + /* ---------------------------------------------------------------------- */ + + nz = CHOLMOD(nnz) (A, Common) ; + T = CHOLMOD(allocate_triplet) (nrow, ncol, nz, A->stype, A->xtype, Common) ; + + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + /* ---------------------------------------------------------------------- */ + /* convert to a sparse matrix */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + Ai = A->i ; + Anz = A->nz ; + packed = A->packed ; + + Ti = T->i ; + Tj = T->j ; + Tx = T->x ; + Tz = T->z ; + T->stype = A->stype ; + + both = (A->stype == 0) ; + up = (A->stype > 0) ; + lo = (A->stype < 0) ; + + k = 0 ; + + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (both || (up && i <= j) || (lo && i >= j)) + { + Ti [k] = Ai [p] ; + Tj [k] = j ; + + if (xtype == CHOLMOD_REAL) + { + Tx [k] = Ax [p] ; + } + else if (xtype == CHOLMOD_COMPLEX) + { + Tx [2*k ] = Ax [2*p ] ; + Tx [2*k+1] = Ax [2*p+1] ; + } + else if (xtype == CHOLMOD_ZOMPLEX) + { + Tx [k] = Ax [p] ; + Tz [k] = Az [p] ; + } + + k++ ; + ASSERT (k <= nz) ; + } + } + } + + T->nnz = k ; + + /* ---------------------------------------------------------------------- */ + /* return result */ + /* ---------------------------------------------------------------------- */ + + ASSERT (CHOLMOD(dump_triplet) (T, "T", Common)) ; + return (T) ; +} + + +/* ========================================================================== */ +/* === cholmod_copy_triplet ================================================= */ +/* ========================================================================== */ + +/* Create an exact copy of a triplet matrix, except that entries in unused + * space are not copied (they might not be initialized, and copying them would + * cause program checkers such as purify and valgrind to complain). + * The output triplet matrix has the same xtype as the input triplet matrix. + */ + +cholmod_triplet *CHOLMOD(copy_triplet) +( + /* ---- input ---- */ + cholmod_triplet *T, /* matrix to copy */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Tx, *Tz, *Cx, *Cz ; + Int *Ci, *Cj, *Ti, *Tj ; + cholmod_triplet *C ; + Int xtype, k, nz ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (T, NULL) ; + RETURN_IF_XTYPE_INVALID (T, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, NULL) ; + nz = T->nnz ; + Ti = T->i ; + Tj = T->j ; + Tx = T->x ; + Tz = T->z ; + xtype = T->xtype ; + RETURN_IF_NULL (Ti, NULL) ; + RETURN_IF_NULL (Tj, NULL) ; + Common->status = CHOLMOD_OK ; + DEBUG (CHOLMOD(dump_triplet) (T, "T input", Common)) ; + + /* ---------------------------------------------------------------------- */ + /* allocate copy */ + /* ---------------------------------------------------------------------- */ + + C = CHOLMOD(allocate_triplet) (T->nrow, T->ncol, T->nzmax, T->stype, + xtype, Common) ; + + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + + /* ---------------------------------------------------------------------- */ + /* copy the triplet matrix */ + /* ---------------------------------------------------------------------- */ + + Ci = C->i ; + Cj = C->j ; + Cx = C->x ; + Cz = C->z ; + C->nnz = nz ; + + for (k = 0 ; k < nz ; k++) + { + Ci [k] = Ti [k] ; + } + for (k = 0 ; k < nz ; k++) + { + Cj [k] = Tj [k] ; + } + + if (xtype == CHOLMOD_REAL) + { + for (k = 0 ; k < nz ; k++) + { + Cx [k] = Tx [k] ; + } + } + else if (xtype == CHOLMOD_COMPLEX) + { + for (k = 0 ; k < nz ; k++) + { + Cx [2*k ] = Tx [2*k ] ; + Cx [2*k+1] = Tx [2*k+1] ; + } + } + else if (xtype == CHOLMOD_ZOMPLEX) + { + for (k = 0 ; k < nz ; k++) + { + Cx [k] = Tx [k] ; + Cz [k] = Tz [k] ; + } + } + + /* ---------------------------------------------------------------------- */ + /* return the result */ + /* ---------------------------------------------------------------------- */ + + ASSERT (CHOLMOD(dump_triplet) (C, "C triplet copy", Common)) ; + return (C) ; +} diff --git a/src/CHOLMOD/Core/cholmod_version.c b/src/CHOLMOD/Core/cholmod_version.c new file mode 100644 index 0000000..0cc034e --- /dev/null +++ b/src/CHOLMOD/Core/cholmod_version.c @@ -0,0 +1,37 @@ +/* ========================================================================== */ +/* === Core/cholmod_version ================================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2013, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Return the current version of CHOLMOD. Unlike all other functions in + CHOLMOD, this function does not require the CHOLMOD Common. */ + +#include "cholmod_internal.h" +#include "cholmod_core.h" + +int CHOLMOD(version) /* returns CHOLMOD_VERSION */ +( + /* output, contents not defined on input. Not used if NULL. + version [0] = CHOLMOD_MAIN_VERSION ; + version [1] = CHOLMOD_SUB_VERSION ; + version [2] = CHOLMOD_SUBSUB_VERSION ; + */ + int version [3] +) +{ + if (version != NULL) + { + version [0] = CHOLMOD_MAIN_VERSION ; + version [1] = CHOLMOD_SUB_VERSION ; + version [2] = CHOLMOD_SUBSUB_VERSION ; + } + return (CHOLMOD_VERSION) ; +} + diff --git a/src/CHOLMOD/Core/lesser.txt b/src/CHOLMOD/Core/lesser.txt new file mode 100644 index 0000000..8add30a --- /dev/null +++ b/src/CHOLMOD/Core/lesser.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/src/CHOLMOD/Core/t_cholmod_change_factor.c b/src/CHOLMOD/Core/t_cholmod_change_factor.c new file mode 100644 index 0000000..dd49b44 --- /dev/null +++ b/src/CHOLMOD/Core/t_cholmod_change_factor.c @@ -0,0 +1,660 @@ +/* ========================================================================== */ +/* === Core/t_cholmod_change_factor ========================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Template routine for cholmod_change_factor. All xtypes supported. */ + +#include "cholmod_template.h" + +/* ========================================================================== */ +/* === t_change_simplicial_numeric ========================================== */ +/* ========================================================================== */ + +static void TEMPLATE (change_simplicial_numeric) +( + cholmod_factor *L, + Int to_ll, + Int to_packed, + Int *newLi, + double *newLx, + double *newLz, + Int lnz, + Int grow, + double grow1, + Int grow2, + Int make_ll, + Int make_monotonic, + Int make_ldl, + cholmod_common *Common +) +{ + double xlen, dj [1], ljj [1], lj2 [1] ; + double *Lx, *Lz ; + Int *Lp, *Li, *Lnz ; + Int n, j, len, pnew, pold, k, p, pend ; + + n = L->n ; + Lp = L->p ; + Li = L->i ; + Lx = L->x ; + Lz = L->z ; + Lnz = L->nz ; + + if (make_ll) + { + L->minor = n ; + } + + if (make_monotonic) + { + + /* ------------------------------------------------------------------ */ + /* reorder the columns to make them monotonic */ + /* ------------------------------------------------------------------ */ + + pnew = 0 ; + for (j = 0 ; j < n ; j++) + { + /* copy and pack column j */ + len = Lnz [j] ; + PRINT2 (("j: "ID" Lnz[j] "ID" len "ID" p "ID"\n", + j, Lnz [j], len, pnew)) ; + pold = Lp [j] ; + ASSERT (Li [pold] == j) ; + + if (make_ll) + { + + /* ---------------------------------------------------------- */ + /* copy and convert LDL' to LL' */ + /* ---------------------------------------------------------- */ + + /* dj = Lx [pold] ; */ + ASSIGN_REAL (dj,0, Lx,pold) ; + + if (IS_LE_ZERO (dj [0])) + { + /* Conversion has failed; matrix is not positive definite. + * Do not modify the column so that the LDL' factorization + * can be restored if desired, by converting back to LDL'. + * Continue the conversion, but flag the error. */ + if (L->minor == (size_t) n) + { + ERROR (CHOLMOD_NOT_POSDEF, "L not positive definite") ; + L->minor = j ; + } + for (k = 0 ; k < len ; k++) + { + newLi [pnew + k] = Li [pold + k] ; + /* newLx [pnew + k] = Lx [pold + k] ; */ + ASSIGN (newLx, newLz, pnew+k, Lx, Lz, pold+k) ; + } + } + else + { + ljj [0] = sqrt (dj [0]) ; + newLi [pnew] = j ; + /* newLx [pnew] = ljj ; */ + ASSIGN_REAL (newLx, pnew, ljj, 0) ; + CLEAR_IMAG (newLx, newLz, pnew) ; + + for (k = 1 ; k < len ; k++) + { + newLi [pnew + k] = Li [pold + k] ; + /* newLx [pnew + k] = Lx [pold + k] * ljj ; */ + MULT_REAL (newLx, newLz, pnew+k, Lx, Lz, pold+k, ljj,0); + } + } + + } + else if (make_ldl) + { + + /* ---------------------------------------------------------- */ + /* copy and convert LL' to LDL' */ + /* ---------------------------------------------------------- */ + + /* ljj = Lx [pold] ; */ + ASSIGN_REAL (ljj, 0, Lx, pold) ; + + if (ljj [0] <= 0) + { + /* matrix is not positive-definite; copy column as-is */ + for (k = 0 ; k < len ; k++) + { + newLi [pnew + k] = Li [pold + k] ; + /* newLx [pnew + k] = Lx [pold + k] ; */ + ASSIGN (newLx, newLz, pnew+k, Lx, Lz, pold+k) ; + } + } + else + { + newLi [pnew] = j ; + /* newLx [pnew] = ljj*ljj ; */ + lj2 [0] = ljj [0] * ljj [0] ; + ASSIGN_REAL (newLx, pnew, lj2, 0) ; + CLEAR_IMAG (newLx, newLz, pnew) ; + + for (k = 1 ; k < len ; k++) + { + newLi [pnew + k] = Li [pold + k] ; + /* newLx [pnew + k] = Lx [pold + k] / ljj ; */ + DIV_REAL (newLx, newLz, pnew+k, Lx, Lz, pold+k, ljj,0) ; + } + } + + } + else + { + + /* ---------------------------------------------------------- */ + /* copy and leave LL' or LDL' as-is */ + /* ---------------------------------------------------------- */ + + for (k = 0 ; k < len ; k++) + { + newLi [pnew + k] = Li [pold + k] ; + /* newLx [pnew + k] = Lx [pold + k] ; */ + ASSIGN (newLx, newLz, pnew+k, Lx, Lz, pold+k) ; + } + } + + Lp [j] = pnew ; + + /* compute len in double to avoid integer overflow */ + if (grow) + { + xlen = (double) len ; + xlen = grow1 * xlen + grow2 ; + xlen = MIN (xlen, n-j) ; + len = (Int) xlen ; + } + ASSERT (len >= Lnz [j] && len <= n-j) ; + pnew += len ; + ASSERT (pnew > 0) ; /* integer overflow case already covered */ + } + Lp [n] = pnew ; + PRINT1 (("final pnew = "ID", lnz "ID" lnzmax %g\n", + pnew, lnz, (double) L->nzmax)) ; + ASSERT (pnew <= lnz) ; + + /* free the old L->i and L->x and replace with the new ones */ + CHOLMOD(free) (L->nzmax, sizeof (Int), L->i, Common) ; + +#ifdef REAL + CHOLMOD(free) (L->nzmax, sizeof (double), L->x, Common) ; +#elif defined (COMPLEX) + CHOLMOD(free) (L->nzmax, 2*sizeof (double), L->x, Common) ; +#else + CHOLMOD(free) (L->nzmax, sizeof (double), L->x, Common) ; + CHOLMOD(free) (L->nzmax, sizeof (double), L->z, Common) ; +#endif + + L->i = newLi ; + L->x = newLx ; + L->z = newLz ; + L->nzmax = lnz ; + + /* reconstruct the link list */ + natural_list (L) ; + + } + else if (to_packed) + { + + /* ------------------------------------------------------------------ */ + /* already monotonic, just pack the columns of L */ + /* ------------------------------------------------------------------ */ + + pnew = 0 ; + + if (make_ll) + { + + /* -------------------------------------------------------------- */ + /* pack and convert LDL' to LL' */ + /* -------------------------------------------------------------- */ + + for (j = 0 ; j < n ; j++) + { + /* pack column j */ + pold = Lp [j] ; + len = Lnz [j] ; + ASSERT (len > 0) ; + ASSERT (Li [pold] == j) ; + PRINT2 (("col "ID" pnew "ID" pold "ID"\n", j, pnew, pold)) ; + + /* dj = Lx [pold] ; */ + ASSIGN_REAL (dj,0, Lx,pold) ; + + if (IS_LE_ZERO (dj [0])) + { + /* Conversion has failed; matrix is not positive definite. + * Do not modify the column so that the LDL' factorization + * can be restored if desired, by converting back to LDL'. + * Continue the conversion, but flag the error. */ + if (L->minor == (size_t) n) + { + ERROR (CHOLMOD_NOT_POSDEF, "L not positive definite") ; + L->minor = j ; + } + for (k = 0 ; k < len ; k++) + { + Li [pnew + k] = Li [pold + k] ; + /* Lx [pnew + k] = Lx [pold + k] ; */ + ASSIGN (Lx, Lz, pnew+k, Lx, Lz, pold+k) ; + } + } + else + { + ljj [0] = sqrt (dj [0]) ; + Li [pnew] = j ; + + /* Lx [pnew] = ljj ; */ + ASSIGN_REAL (Lx, pnew, ljj, 0) ; + CLEAR_IMAG (Lx, Lz, pnew) ; + + for (k = 1 ; k < len ; k++) + { + Li [pnew + k] = Li [pold + k] ; + /* Lx [pnew + k] = Lx [pold + k] * ljj ; */ + MULT_REAL (Lx, Lz, pnew+k, Lx, Lz, pold+k, ljj,0) ; + } + } + Lp [j] = pnew ; + pnew += len ; + } + + } + else if (make_ldl) + { + + /* -------------------------------------------------------------- */ + /* pack and convert LL' to LDL' */ + /* -------------------------------------------------------------- */ + + for (j = 0 ; j < n ; j++) + { + /* pack column j */ + pold = Lp [j] ; + len = Lnz [j] ; + + /* ljj = Lx [pold] ; */ + ASSIGN_REAL (ljj, 0, Lx, pold) ; + + ASSERT (len > 0) ; + PRINT2 (("col "ID" pnew "ID" pold "ID"\n", j, pnew, pold)) ; + if (ljj [0] <= 0) + { + /* matrix is not positive-definite; pack column as-is */ + for (k = 0 ; k < len ; k++) + { + Li [pnew + k] = Li [pold + k] ; + /* Lx [pnew + k] = Lx [pold + k] ; */ + ASSIGN (Lx, Lz, pnew+k, Lx, Lz, pold+k) ; + } + } + else + { + Li [pnew] = Li [pold] ; + + /* Lx [pnew] = ljj*ljj ; */ + lj2 [0] = ljj [0] * ljj [0] ; + ASSIGN_REAL (Lx, pnew, lj2, 0) ; + CLEAR_IMAG (Lx, Lz, pnew) ; + + for (k = 1 ; k < len ; k++) + { + Li [pnew + k] = Li [pold + k] ; + /* Lx [pnew + k] = Lx [pold + k] / ljj ; */ + DIV_REAL (Lx, Lz, pnew+k, Lx, Lz, pold+k, ljj,0) ; + } + } + Lp [j] = pnew ; + pnew += len ; + } + + } + else + { + + /* ---------------------------------------------------------- */ + /* pack and leave LL' or LDL' as-is */ + /* ---------------------------------------------------------- */ + + for (j = 0 ; j < n ; j++) + { + /* pack column j */ + pold = Lp [j] ; + len = Lnz [j] ; + ASSERT (len > 0) ; + PRINT2 (("col "ID" pnew "ID" pold "ID"\n", j, pnew, pold)) ; + if (pnew < pold) + { + PRINT2 ((" pack this column\n")) ; + for (k = 0 ; k < len ; k++) + { + Li [pnew + k] = Li [pold + k] ; + /* Lx [pnew + k] = Lx [pold + k] ; */ + ASSIGN (Lx, Lz, pnew+k, Lx, Lz, pold+k) ; + } + Lp [j] = pnew ; + } + pnew += len ; + } + } + + Lp [n] = pnew ; + PRINT2 (("Lp [n] = "ID"\n", pnew)) ; + + } + else if (make_ll) + { + + /* ------------------------------------------------------------------ */ + /* convert LDL' to LL', but do so in-place */ + /* ------------------------------------------------------------------ */ + + for (j = 0 ; j < n ; j++) + { + p = Lp [j] ; + pend = p + Lnz [j] ; + + /* dj = Lx [p] ; */ + ASSIGN_REAL (dj,0, Lx,p) ; + + if (IS_LE_ZERO (dj [0])) + { + /* Conversion has failed; matrix is not positive definite. + * Do not modify the column so that the LDL' factorization + * can be restored if desired, by converting back to LDL'. + * Continue the conversion, but flag the error. */ + if (L->minor == (size_t) n) + { + ERROR (CHOLMOD_NOT_POSDEF, "L not positive definite") ; + L->minor = j ; + } + } + else + { + ljj [0] = sqrt (dj [0]) ; + /* Lx [p] = ljj ; */ + ASSIGN_REAL (Lx,p, ljj,0) ; + CLEAR_IMAG (Lx, Lz, p) ; + + for (p++ ; p < pend ; p++) + { + /* Lx [p] *= ljj ; */ + MULT_REAL (Lx,Lz,p, Lx,Lz,p, ljj,0) ; + } + } + } + + } + else if (make_ldl) + { + + /* ------------------------------------------------------------------ */ + /* convert LL' to LDL', but do so in-place */ + /* ------------------------------------------------------------------ */ + + for (j = 0 ; j < n ; j++) + { + p = Lp [j] ; + pend = p + Lnz [j] ; + + /* ljj = Lx [p] ; */ + ASSIGN_REAL (ljj, 0, Lx, p) ; + + if (ljj [0] > 0) + { + /* Lx [p] = ljj*ljj ; */ + lj2 [0] = ljj [0] * ljj [0] ; + ASSIGN_REAL (Lx, p, lj2, 0) ; + CLEAR_IMAG (Lx, Lz, p) ; + + for (p++ ; p < pend ; p++) + { + /* Lx [p] /= ljj ; */ + DIV_REAL (Lx,Lz,p, Lx,Lz,p, ljj,0) ; + } + } + } + } + + L->is_ll = to_ll ; + + DEBUG (CHOLMOD(dump_factor) (L, "done change simplicial numeric", Common)) ; +} + + +/* ========================================================================== */ +/* === t_ll_super_to_simplicial_numeric ===================================== */ +/* ========================================================================== */ + +/* A supernodal L can only be real or complex, not zomplex */ + +#ifndef ZOMPLEX + +static void TEMPLATE (ll_super_to_simplicial_numeric) +( + cholmod_factor *L, + Int to_packed, + Int to_ll, + cholmod_common *Common +) +{ + double ljj [1], lj2 [1] ; + double *Lx ; + Int *Ls, *Lpi, *Lpx, *Super, *Lp, *Li, *Lnz ; + Int n, lnz, s, nsuper, p, psi, psx, psend, nsrow, nscol, ii, jj, j, k1, k2, + q ; + + L->is_ll = to_ll ; + + Lp = L->p ; + Li = L->i ; + Lx = L->x ; + Lnz = L->nz ; + lnz = L->nzmax ; + + n = L->n ; + nsuper = L->nsuper ; + Lpi = L->pi ; + Lpx = L->px ; + Ls = L->s ; + Super = L->super ; + + p = 0 ; + + for (s = 0 ; s < nsuper ; s++) + { + k1 = Super [s] ; + k2 = Super [s+1] ; + psi = Lpi [s] ; + psend = Lpi [s+1] ; + psx = Lpx [s] ; + nsrow = psend - psi ; + nscol = k2 - k1 ; + + for (jj = 0 ; jj < nscol ; jj++) + { + /* column j of L starts here */ + j = jj + k1 ; + + if (to_ll) + { + if (to_packed) + { + + /* ------------------------------------------------------ */ + /* convert to LL' packed */ + /* ------------------------------------------------------ */ + + Lp [j] = p ; + PRINT2 (("Col j "ID" p "ID"\n", j, p)) ; + for (ii = jj ; ii < nsrow ; ii++) + { + /* get L(i,j) from supernode and store in column j */ + ASSERT (p < (Int) (L->xsize) && p <= psx+ii+jj*nsrow) ; + Li [p] = Ls [psi + ii] ; + /* Lx [p] = Lx [psx + ii + jj*nsrow] ; */ + q = psx + ii + jj*nsrow ; + ASSIGN (Lx,-,p, Lx,-,q) ; + PRINT2 ((" i "ID" ", Li [p])) ; + XPRINT2 (Lx,-,q) ; + PRINT2 (("\n")) ; + p++ ; + } + Lnz [j] = p - Lp [j] ; + + } + else + { + + /* ------------------------------------------------------ */ + /* convert to LL' unpacked */ + /* ------------------------------------------------------ */ + + p = psx + jj + jj*nsrow ; + Lp [j] = p ; + Li [p] = j ; + Lnz [j] = nsrow - jj ; + p++ ; + for (ii = jj + 1 ; ii < nsrow ; ii++) + { + /* get L(i,j) from supernode and store in column j */ + Li [psx + ii + jj*nsrow] = Ls [psi + ii] ; + } + + } + } + else + { + if (to_packed) + { + + /* ------------------------------------------------------ */ + /* convert to LDL' packed */ + /* ------------------------------------------------------ */ + + Lp [j] = p ; + PRINT2 (("Col j "ID" p "ID"\n", Lp [j], p)) ; + /* ljj = Lx [psx + jj + jj*nsrow] ; */ + ASSIGN_REAL (ljj, 0, Lx, psx + jj + jj*nsrow) ; + + if (ljj [0] <= 0) + { + /* the matrix is not positive definite; do not divide */ + /* Lx [p] = ljj ; */ + ASSIGN_REAL (Lx, p, ljj, 0) ; + CLEAR_IMAG (Lx, Lz, p) ; + ljj [0] = 1 ; + } + else + { + lj2 [0] = ljj [0] * ljj [0] ; + /* Lx [p] = ljj*ljj ; */ + ASSIGN_REAL (Lx, p, lj2, 0) ; + CLEAR_IMAG (Lx, Lz, p) ; + } + Li [p] = j ; + p++ ; + for (ii = jj + 1 ; ii < nsrow ; ii++) + { + /* get L(i,j) from supernode and store in column j */ + ASSERT (p < (Int) (L->xsize) && p <= psx+ii+jj*nsrow) ; + Li [p] = Ls [psi + ii] ; + + /* Lx [p] = Lx [psx + ii + jj*nsrow] / ljj ; */ + q = psx + ii + jj*nsrow ; + DIV_REAL (Lx, Lz, p, Lx, Lz, q, ljj,0) ; + + PRINT2 ((" i "ID" %g\n", Li [p], Lx [p])) ; + p++ ; + } + Lnz [j] = p - Lp [j] ; + + } + else + { + + /* ------------------------------------------------------ */ + /* convert to LDL' unpacked */ + /* ------------------------------------------------------ */ + + p = psx + jj + jj*nsrow ; + Lp [j] = p ; + + /* ljj = Lx [p] ; */ + ASSIGN_REAL (ljj,0, Lx,p) ; + + if (ljj [0] <= 0) + { + /* the matrix is not positive definite; do not divide */ + /* Lx [p] = ljj ; */ + ASSIGN_REAL (Lx, p, ljj, 0) ; + CLEAR_IMAG (Lx, Lz, p) ; + ljj [0] = 1 ; + } + else + { + lj2 [0] = ljj [0] * ljj [0] ; + /* Lx [p] = ljj*ljj ; */ + ASSIGN_REAL (Lx, p, lj2, 0) ; + CLEAR_IMAG (Lx, Lz, p) ; + } + Li [p] = j ; + Lnz [j] = nsrow - jj ; + p++ ; + for (ii = jj + 1 ; ii < nsrow ; ii++) + { + /* get L(i,j) from supernode and store in column j */ + Li [psx + ii + jj*nsrow] = Ls [psi + ii] ; + + /* Lx [psx + ii + jj*nsrow] /= ljj ; */ + q = psx + ii + jj*nsrow ; + DIV_REAL (Lx, Lz, q, Lx, Lz, q, ljj,0) ; + } + } + } + } + } + + if (to_packed) + { + Lp [n] = p ; + PRINT1 (("Final Lp "ID" n "ID" lnz "ID"\n", p, n, lnz)) ; + ASSERT (Lp [n] == lnz) ; + ASSERT (lnz <= (Int) (L->xsize)) ; + /* reduce size of L->x to match L->i. This cannot fail. */ + L->x = CHOLMOD(realloc) (lnz, +#ifdef COMPLEX + 2 * +#endif + sizeof (double), L->x, &(L->xsize), Common) ; + ASSERT (lnz == (Int) (L->xsize)) ; + Common->status = CHOLMOD_OK ; + } + else + { + Lp [n] = Lpx [nsuper] ; + ASSERT (MAX (1,Lp [n]) == (Int) (L->xsize)) ; + ASSERT (MAX (1,Lp [n]) == (Int) (L->nzmax)) ; + } +} + +#endif + +#undef PATTERN +#undef REAL +#undef COMPLEX +#undef ZOMPLEX diff --git a/src/CHOLMOD/Core/t_cholmod_dense.c b/src/CHOLMOD/Core/t_cholmod_dense.c new file mode 100644 index 0000000..6f3f270 --- /dev/null +++ b/src/CHOLMOD/Core/t_cholmod_dense.c @@ -0,0 +1,265 @@ +/* ========================================================================== */ +/* === Core/t_cholmod_dense ================================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Template routine for cholmod_dense. All xtypes supported, except that there + * are no dense matrices with an xtype of pattern. */ + +#include "cholmod_template.h" + +/* ========================================================================== */ +/* === t_cholmod_sparse_to_dense ============================================ */ +/* ========================================================================== */ + +static cholmod_dense *TEMPLATE (cholmod_sparse_to_dense) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to copy */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Ax, *Xx, *Az, *Xz ; + Int *Ap, *Ai, *Anz ; + cholmod_dense *X ; + Int i, j, p, pend, nrow, ncol, packed ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nrow = A->nrow ; + ncol = A->ncol ; + packed = A->packed ; + Ap = A->p ; + Ai = A->i ; + Ax = A->x ; + Az = A->z ; + Anz = A->nz ; + + /* ---------------------------------------------------------------------- */ + /* allocate result */ + /* ---------------------------------------------------------------------- */ + + X = CHOLMOD(zeros) (nrow, ncol, XTYPE2, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + Xx = X->x ; + Xz = X->z ; + + /* ---------------------------------------------------------------------- */ + /* copy into dense matrix */ + /* ---------------------------------------------------------------------- */ + + if (A->stype < 0) + { + /* A is symmetric with lower stored, but both parts of X are present */ + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i >= j) + { + ASSIGN2 (Xx, Xz, i+j*nrow, Ax, Az, p) ; + ASSIGN2_CONJ (Xx, Xz, j+i*nrow, Ax, Az, p) ; + } + } + } + } + else if (A->stype > 0) + { + /* A is symmetric with upper stored, but both parts of X are present */ + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i <= j) + { + ASSIGN2 (Xx, Xz, i+j*nrow, Ax, Az, p) ; + ASSIGN2_CONJ (Xx, Xz, j+i*nrow, Ax, Az, p) ; + } + } + } + } + else + { + /* both parts of A and X are present */ + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + ASSIGN2 (Xx, Xz, i+j*nrow, Ax, Az, p) ; + } + } + } + + return (X) ; +} + + +#ifndef PATTERN + +/* There are no dense matrices of xtype CHOLMOD_PATTERN */ + +/* ========================================================================== */ +/* === t_cholmod_dense_to_sparse ============================================ */ +/* ========================================================================== */ + +static cholmod_sparse *TEMPLATE (cholmod_dense_to_sparse) +( + /* ---- input ---- */ + cholmod_dense *X, /* matrix to copy */ + int values, /* TRUE if values to be copied, FALSE otherwise */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Xx, *Cx, *Xz, *Cz ; + Int *Ci, *Cp ; + cholmod_sparse *C ; + Int i, j, p, d, nrow, ncol, nz ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nrow = X->nrow ; + ncol = X->ncol ; + d = X->d ; + Xx = X->x ; + Xz = X->z ; + + /* ---------------------------------------------------------------------- */ + /* count the number of nonzeros in the result */ + /* ---------------------------------------------------------------------- */ + + nz = 0 ; + for (j = 0 ; j < ncol ; j++) + { + for (i = 0 ; i < nrow ; i++) + { + if (ENTRY_IS_NONZERO (Xx, Xz, i+j*d)) + { + nz++ ; + } + } + } + + /* ---------------------------------------------------------------------- */ + /* allocate the result C */ + /* ---------------------------------------------------------------------- */ + + C = CHOLMOD(allocate_sparse) (nrow, ncol, nz, TRUE, TRUE, 0, + values ? XTYPE : CHOLMOD_PATTERN, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (NULL) ; /* out of memory */ + } + Cp = C->p ; + Ci = C->i ; + Cx = C->x ; + Cz = C->z ; + + /* ---------------------------------------------------------------------- */ + /* copy the dense matrix X into the sparse matrix C */ + /* ---------------------------------------------------------------------- */ + + p = 0 ; + for (j = 0 ; j < ncol ; j++) + { + Cp [j] = p ; + for (i = 0 ; i < nrow ; i++) + { + if (ENTRY_IS_NONZERO (Xx, Xz, i+j*d)) + { + Ci [p] = i ; + if (values) + { + ASSIGN (Cx, Cz, p, Xx, Xz, i+j*d) ; + } + p++ ; + } + } + } + ASSERT (p == nz) ; + Cp [ncol] = nz ; + + /* ---------------------------------------------------------------------- */ + /* return result */ + /* ---------------------------------------------------------------------- */ + + ASSERT (CHOLMOD(dump_sparse) (C, "C", Common) >= 0) ; + return (C) ; +} + + +/* ========================================================================== */ +/* === t_cholmod_copy_dense2 ================================================ */ +/* ========================================================================== */ + +/* Y = X, where X and Y are both already allocated. */ + +static int TEMPLATE (cholmod_copy_dense2) +( + /* ---- input ---- */ + cholmod_dense *X, /* matrix to copy */ + /* ---- output --- */ + cholmod_dense *Y /* copy of matrix X */ +) +{ + double *Xx, *Xz, *Yx, *Yz ; + Int i, j, nrow, ncol, dy, dx ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Xx = X->x ; + Xz = X->z ; + Yx = Y->x ; + Yz = Y->z ; + dx = X->d ; + dy = Y->d ; + nrow = X->nrow ; + ncol = X->ncol ; + + /* ---------------------------------------------------------------------- */ + /* copy */ + /* ---------------------------------------------------------------------- */ + + CLEAR (Yx, Yz, 0) ; + for (j = 0 ; j < ncol ; j++) + { + for (i = 0 ; i < nrow ; i++) + { + ASSIGN (Yx, Yz, i+j*dy, Xx, Xz, i+j*dx) ; + } + } + return (TRUE) ; +} + +#endif + +#undef PATTERN +#undef REAL +#undef COMPLEX +#undef ZOMPLEX diff --git a/src/CHOLMOD/Core/t_cholmod_transpose.c b/src/CHOLMOD/Core/t_cholmod_transpose.c new file mode 100644 index 0000000..a5dd849 --- /dev/null +++ b/src/CHOLMOD/Core/t_cholmod_transpose.c @@ -0,0 +1,317 @@ +/* ========================================================================== */ +/* === Core/t_cholmod_transpose ============================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Template routine for cholmod_transpose. All xtypes are supported. For + * complex matrices, either the array tranpose or complex conjugate transpose + * can be computed. */ + +#include "cholmod_template.h" + +/* ========================================================================== */ +/* === t_cholmod_transpose_unsym ============================================ */ +/* ========================================================================== */ + +/* Compute F = A', A (:,f)', or A (p,f)', where A is unsymmetric and F is + * already allocated. The complex case performs either the array transpose + * or complex conjugate transpose. + * + * workspace: + * Iwork (MAX (nrow,ncol)) if fset is present + * Iwork (nrow) if fset is NULL + */ + +static int TEMPLATE (cholmod_transpose_unsym) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to transpose */ + Int *Perm, /* size nrow, if present (can be NULL) */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + Int nf, /* size of fset */ + /* ---- output --- */ + cholmod_sparse *F, /* F = A', A(:,f)', or A(p,f)' */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Ax, *Az, *Fx, *Fz ; + Int *Ap, *Anz, *Ai, *Fp, *Fnz, *Fj, *Wi, *Iwork ; + Int j, p, pend, nrow, ncol, Apacked, use_fset, fp, Fpacked, jj, permute ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + /* ensure the xtype of A and F match (ignored if this is pattern version) */ + if (!XTYPE_OK (A->xtype)) + { + ERROR (CHOLMOD_INVALID, "real/complex mismatch") ; + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + use_fset = (fset != NULL) ; + nrow = A->nrow ; + ncol = A->ncol ; + + Ap = A->p ; /* size A->ncol+1, column pointers of A */ + Ai = A->i ; /* size nz = Ap [A->ncol], row indices of A */ + Ax = A->x ; /* size nz, real values of A */ + Az = A->z ; /* size nz, imag values of A */ + Anz = A->nz ; + Apacked = A->packed ; + ASSERT (IMPLIES (!Apacked, Anz != NULL)) ; + + permute = (Perm != NULL) ; + + Fp = F->p ; /* size A->nrow+1, row pointers of F */ + Fj = F->i ; /* size nz, column indices of F */ + Fx = F->x ; /* size nz, real values of F */ + Fz = F->z ; /* size nz, imag values of F */ + Fnz = F->nz ; + Fpacked = F->packed ; + ASSERT (IMPLIES (!Fpacked, Fnz != NULL)) ; + + nf = (use_fset) ? nf : ncol ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Iwork = Common->Iwork ; + Wi = Iwork ; /* size nrow (i/l/l) */ + + /* ---------------------------------------------------------------------- */ + /* construct the transpose */ + /* ---------------------------------------------------------------------- */ + + for (jj = 0 ; jj < nf ; jj++) + { + j = (use_fset) ? (fset [jj]) : jj ; + p = Ap [j] ; + pend = (Apacked) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + fp = Wi [Ai [p]]++ ; + Fj [fp] = j ; +#ifdef NCONJUGATE + ASSIGN (Fx, Fz, fp, Ax, Az, p) ; +#else + ASSIGN_CONJ (Fx, Fz, fp, Ax, Az, p) ; +#endif + } + } + + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === t_cholmod_transpose_sym ============================================== */ +/* ========================================================================== */ + +/* Compute F = A' or A (p,p)', where A is symmetric and F is already allocated. + * The complex case performs either the array transpose or complex conjugate + * transpose. + * + * workspace: Iwork (nrow) if Perm NULL, Iwork (2*nrow) if Perm non-NULL. + */ + +static int TEMPLATE (cholmod_transpose_sym) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to transpose */ + Int *Perm, /* size n, if present (can be NULL) */ + /* ---- output --- */ + cholmod_sparse *F, /* F = A' or A(p,p)' */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Ax, *Az, *Fx, *Fz ; + Int *Ap, *Anz, *Ai, *Fp, *Fj, *Wi, *Pinv, *Iwork ; + Int p, pend, packed, fp, upper, permute, jold, n, i, j, iold ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + /* ensure the xtype of A and F match (ignored if this is pattern version) */ + if (!XTYPE_OK (A->xtype)) + { + ERROR (CHOLMOD_INVALID, "real/complex mismatch") ; + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + permute = (Perm != NULL) ; + n = A->nrow ; + Ap = A->p ; /* size A->ncol+1, column pointers of A */ + Ai = A->i ; /* size nz = Ap [A->ncol], row indices of A */ + Ax = A->x ; /* size nz, real values of A */ + Az = A->z ; /* size nz, imag values of A */ + Anz = A->nz ; + packed = A->packed ; + ASSERT (IMPLIES (!packed, Anz != NULL)) ; + upper = (A->stype > 0) ; + + Fp = F->p ; /* size A->nrow+1, row pointers of F */ + Fj = F->i ; /* size nz, column indices of F */ + Fx = F->x ; /* size nz, real values of F */ + Fz = F->z ; /* size nz, imag values of F */ + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Iwork = Common->Iwork ; + Wi = Iwork ; /* size n (i/l/l) */ + Pinv = Iwork + n ; /* size n (i/i/l) , unused if Perm NULL */ + + /* ---------------------------------------------------------------------- */ + /* construct the transpose */ + /* ---------------------------------------------------------------------- */ + + if (permute) + { + if (upper) + { + /* permuted, upper */ + for (j = 0 ; j < n ; j++) + { + jold = Perm [j] ; + p = Ap [jold] ; + pend = (packed) ? Ap [jold+1] : p + Anz [jold] ; + for ( ; p < pend ; p++) + { + iold = Ai [p] ; + if (iold <= jold) + { + i = Pinv [iold] ; + if (i < j) + { + fp = Wi [i]++ ; + Fj [fp] = j ; +#ifdef NCONJUGATE + ASSIGN (Fx, Fz, fp, Ax, Az, p) ; +#else + ASSIGN_CONJ (Fx, Fz, fp, Ax, Az, p) ; +#endif + } + else + { + fp = Wi [j]++ ; + Fj [fp] = i ; + ASSIGN (Fx, Fz, fp, Ax, Az, p) ; + } + } + } + } + } + else + { + /* permuted, lower */ + for (j = 0 ; j < n ; j++) + { + jold = Perm [j] ; + p = Ap [jold] ; + pend = (packed) ? Ap [jold+1] : p + Anz [jold] ; + for ( ; p < pend ; p++) + { + iold = Ai [p] ; + if (iold >= jold) + { + i = Pinv [iold] ; + if (i > j) + { + fp = Wi [i]++ ; + Fj [fp] = j ; +#ifdef NCONJUGATE + ASSIGN (Fx, Fz, fp, Ax, Az, p) ; +#else + ASSIGN_CONJ (Fx, Fz, fp, Ax, Az, p) ; +#endif + } + else + { + fp = Wi [j]++ ; + Fj [fp] = i ; + ASSIGN (Fx, Fz, fp, Ax, Az, p) ; + } + } + } + } + } + } + else + { + if (upper) + { + /* unpermuted, upper */ + for (j = 0 ; j < n ; j++) + { + p = Ap [j] ; + pend = (packed) ? Ap [j+1] : p + Anz [j] ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i <= j) + { + fp = Wi [i]++ ; + Fj [fp] = j ; +#ifdef NCONJUGATE + ASSIGN (Fx, Fz, fp, Ax, Az, p) ; +#else + ASSIGN_CONJ (Fx, Fz, fp, Ax, Az, p) ; +#endif + } + } + } + } + else + { + /* unpermuted, lower */ + for (j = 0 ; j < n ; j++) + { + p = Ap [j] ; + pend = (packed) ? Ap [j+1] : p + Anz [j] ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i >= j) + { + fp = Wi [i]++ ; + Fj [fp] = j ; +#ifdef NCONJUGATE + ASSIGN (Fx, Fz, fp, Ax, Az, p) ; +#else + ASSIGN_CONJ (Fx, Fz, fp, Ax, Az, p) ; +#endif + } + } + } + } + } + + return (TRUE) ; +} + +#undef PATTERN +#undef REAL +#undef COMPLEX +#undef ZOMPLEX +#undef NCONJUGATE diff --git a/src/CHOLMOD/Core/t_cholmod_triplet.c b/src/CHOLMOD/Core/t_cholmod_triplet.c new file mode 100644 index 0000000..d2b1c82 --- /dev/null +++ b/src/CHOLMOD/Core/t_cholmod_triplet.c @@ -0,0 +1,175 @@ +/* ========================================================================== */ +/* === Core/t_cholmod_triplet =============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Core Module. Copyright (C) 2005-2006, + * Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Core Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Template routine for cholmod_triplet. All xtypes supported */ + +#include "cholmod_template.h" + +/* ========================================================================== */ +/* === t_cholmod_triplet_to_sparse ========================================== */ +/* ========================================================================== */ + +static size_t TEMPLATE (cholmod_triplet_to_sparse) +( + /* ---- input ---- */ + cholmod_triplet *T, /* matrix to copy */ + /* ---- in/out --- */ + cholmod_sparse *R, /* output matrix */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Rx, *Rz, *Tx, *Tz ; + Int *Wj, *Rp, *Ri, *Rnz, *Ti, *Tj ; + Int i, j, p, p1, p2, pdest, pj, k, stype, nrow, ncol, nz ; + size_t anz ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + /* Wj contains a copy of Rp on input [ */ + Wj = Common->Iwork ; /* size MAX (nrow,ncol). (i/l/l) */ + + Rp = R->p ; + Ri = R->i ; + Rnz = R->nz ; + Rx = R->x ; + Rz = R->z ; + + Ti = T->i ; + Tj = T->j ; + Tx = T->x ; + Tz = T->z ; + nz = T->nnz ; + nrow = T->nrow ; + ncol = T->ncol ; + stype = SIGN (T->stype) ; + + /* ---------------------------------------------------------------------- */ + /* construct the row form */ + /* ---------------------------------------------------------------------- */ + + /* if Ti is jumbled, this part dominates the run time */ + + if (stype > 0) + { + for (k = 0 ; k < nz ; k++) + { + i = Ti [k] ; + j = Tj [k] ; + if (i < j) + { + /* place triplet (j,i,x) in column i of R */ + p = Wj [i]++ ; + Ri [p] = j ; + } + else + { + /* place triplet (i,j,x) in column j of R */ + p = Wj [j]++ ; + Ri [p] = i ; + } + ASSIGN (Rx, Rz, p, Tx, Tz, k) ; + } + } + else if (stype < 0) + { + for (k = 0 ; k < nz ; k++) + { + i = Ti [k] ; + j = Tj [k] ; + if (i > j) + { + /* place triplet (j,i,x) in column i of R */ + p = Wj [i]++ ; + Ri [p] = j ; + } + else + { + /* place triplet (i,j,x) in column j of R */ + p = Wj [j]++ ; + Ri [p] = i ; + } + ASSIGN (Rx, Rz, p, Tx, Tz, k) ; + } + } + else + { + for (k = 0 ; k < nz ; k++) + { + /* place triplet (i,j,x) in column i of R */ + p = Wj [Ti [k]]++ ; + Ri [p] = Tj [k] ; + ASSIGN (Rx, Rz, p, Tx, Tz, k) ; + } + } + + /* done using Wj (i/l/l) as temporary row pointers ] */ + + /* ---------------------------------------------------------------------- */ + /* sum up duplicates */ + /* ---------------------------------------------------------------------- */ + + /* use Wj (i/l/l) of size ncol to keep track of duplicates in each row [ */ + for (j = 0 ; j < ncol ; j++) + { + Wj [j] = EMPTY ; + } + + anz = 0 ; + for (i = 0 ; i < nrow ; i++) + { + p1 = Rp [i] ; + p2 = Rp [i+1] ; + pdest = p1 ; + /* at this point Wj [j] < p1 holds true for all columns j, because + * Ri/Rx is stored in row oriented manner */ + for (p = p1 ; p < p2 ; p++) + { + j = Ri [p] ; + pj = Wj [j] ; + if (pj >= p1) + { + /* this column index j is already in row i at position pj; + * sum up the duplicate entry */ + /* Rx [pj] += Rx [p] ; */ + ASSEMBLE (Rx, Rz, pj, Rx, Rz, p) ; + } + else + { + /* keep the entry and keep track in Wj [j] for case above */ + Wj [j] = pdest ; + if (pdest != p) + { + Ri [pdest] = j ; + ASSIGN (Rx, Rz, pdest, Rx, Rz, p) ; + } + pdest++ ; + } + } + Rnz [i] = pdest - p1 ; + anz += (pdest - p1) ; + } + /* done using Wj to keep track of duplicate entries in each row ] */ + + /* ---------------------------------------------------------------------- */ + /* return number of entries after summing up duplicates */ + /* ---------------------------------------------------------------------- */ + + return (anz) ; +} + +#undef PATTERN +#undef REAL +#undef COMPLEX +#undef ZOMPLEX diff --git a/src/CHOLMOD/Include/License.txt b/src/CHOLMOD/Include/License.txt new file mode 100644 index 0000000..ea2c374 --- /dev/null +++ b/src/CHOLMOD/Include/License.txt @@ -0,0 +1,8 @@ +CHOLMOD/Include/* files. +Copyright (C) 2005-2006, either Univ. of Florida or T. Davis, +depending on the file. + +Refer to each include file in this directory; each file is licensed +separately, according to the Module for which it contains definitions +and prototypes. + diff --git a/src/CHOLMOD/Include/README.txt b/src/CHOLMOD/Include/README.txt new file mode 100644 index 0000000..ec68624 --- /dev/null +++ b/src/CHOLMOD/Include/README.txt @@ -0,0 +1,25 @@ +CHOLMOD: a sparse Cholesky factorization package. http://www.suitesparse.com + +The Include/*.h files in this directory provide a basic documentation of all +user-callable routines and user-visible data structures in the CHOLMOD +package. Start with cholmod.h, which describes the general structure of +the parameter lists of CHOLMOD routines. cholmod_core.h describes the +data structures and basic operations on them (creating and deleting them). + +cholmod.h single include file for all user programs +cholmod_config.h CHOLMOD compile-time configuration + +cholmod_core.h Core module: data structures and basic support routines +cholmod_check.h Check module: check/print CHOLMOD data structures +cholmod_cholesky.h Cholesky module: LL' and LDL' factorization +cholmod_matrixops.h MatrixOps module: sparse matrix operators (add, mult,..) +cholmod_modify.h Modify module: update/downdate/... +cholmod_partition.h Partition module: nested dissection ordering +cholmod_supernodal.h Supernodal module: supernodal Cholesky + +These include files are not used in user programs, but in CHOLMOD only: + +cholmod_blas.h BLAS definitions +cholmod_complexity.h complex arithmetic +cholmod_template.h complex arithmetic for template routines +cholmod_internal.h internal definitions, not visible to user program diff --git a/src/CHOLMOD/Include/cholmod.h b/src/CHOLMOD/Include/cholmod.h new file mode 100644 index 0000000..11073c3 --- /dev/null +++ b/src/CHOLMOD/Include/cholmod.h @@ -0,0 +1,125 @@ +/* ========================================================================== */ +/* === Include/cholmod.h ==================================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod.h. + * Copyright (C) 2005-2013, Univ. of Florida. Author: Timothy A. Davis + * CHOLMOD/Include/cholmod.h is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * + * Portions of CHOLMOD (the Core and Partition Modules) are copyrighted by the + * University of Florida. The Modify Module is co-authored by William W. + * Hager, Univ. of Florida. + * + * Acknowledgements: this work was supported in part by the National Science + * Foundation (NFS CCR-0203270 and DMS-9803599), and a grant from Sandia + * National Laboratories (Dept. of Energy) which supported the development of + * CHOLMOD's Partition Module. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD include file, for inclusion user programs. + * + * The include files listed below include a short description of each user- + * callable routine. Each routine in CHOLMOD has a consistent interface. + * More details about the CHOLMOD data types is in the cholmod_core.h file. + * + * Naming convention: + * ------------------ + * + * All routine names, data types, and CHOLMOD library files use the + * cholmod_ prefix. All macros and other #define's use the CHOLMOD + * prefix. + * + * Return value: + * ------------- + * + * Most CHOLMOD routines return an int (TRUE (1) if successful, or FALSE + * (0) otherwise. A SuiteSparse_long or double return value is >= 0 if + * successful, or -1 otherwise. A size_t return value is > 0 if + * successful, or 0 otherwise. + * + * If a routine returns a pointer, it is a pointer to a newly allocated + * object or NULL if a failure occured, with one exception. cholmod_free + * always returns NULL. + * + * "Common" parameter: + * ------------------ + * + * The last parameter in all CHOLMOD routines is a pointer to the CHOLMOD + * "Common" object. This contains control parameters, statistics, and + * workspace used between calls to CHOLMOD. It is always an input/output + * parameter. + * + * Input, Output, and Input/Output parameters: + * ------------------------------------------- + * + * Input parameters are listed first. They are not modified by CHOLMOD. + * + * Input/output are listed next. They must be defined on input, and + * are modified on output. + * + * Output parameters are listed next. If they are pointers, they must + * point to allocated space on input, but their contents are not defined + * on input. + * + * Workspace parameters appear next. They are used in only two routines + * in the Supernodal module. + * + * The cholmod_common *Common parameter always appears as the last + * parameter. It is always an input/output parameter. + */ + +#ifndef CHOLMOD_H +#define CHOLMOD_H + +/* make it easy for C++ programs to include CHOLMOD */ +#ifdef __cplusplus +extern "C" { +#endif + +/* assume large file support. If problems occur, compile with -DNLARGEFILE */ +#include "cholmod_io64.h" + +#include "SuiteSparse_config.h" + +#include "cholmod_config.h" + +/* CHOLMOD always includes the Core module. */ +#include "cholmod_core.h" + +#ifndef NCHECK +#include "cholmod_check.h" +#endif + +#ifndef NCHOLESKY +#include "cholmod_cholesky.h" +#endif + +#ifndef NMATRIXOPS +#include "cholmod_matrixops.h" +#endif + +#ifndef NMODIFY +#include "cholmod_modify.h" +#endif + +#ifndef NCAMD +#include "cholmod_camd.h" +#endif + +#ifndef NPARTITION +#include "cholmod_partition.h" +#endif + +#ifndef NSUPERNODAL +#include "cholmod_supernodal.h" +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/CHOLMOD/Include/cholmod_blas.h b/src/CHOLMOD/Include/cholmod_blas.h new file mode 100644 index 0000000..83e013f --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_blas.h @@ -0,0 +1,455 @@ +/* ========================================================================== */ +/* === Include/cholmod_blas.h =============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_blas.h. + * Copyright (C) 2005-2006, Univ. of Florida. Author: Timothy A. Davis + * CHOLMOD/Include/cholmod_blas.h is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* This does not need to be included in the user's program. */ + +#ifndef CHOLMOD_BLAS_H +#define CHOLMOD_BLAS_H + +/* ========================================================================== */ +/* === Architecture ========================================================= */ +/* ========================================================================== */ + +#if defined (__sun) || defined (MSOL2) || defined (ARCH_SOL2) +#define CHOLMOD_SOL2 +#define CHOLMOD_ARCHITECTURE "Sun Solaris" + +#elif defined (__sgi) || defined (MSGI) || defined (ARCH_SGI) +#define CHOLMOD_SGI +#define CHOLMOD_ARCHITECTURE "SGI Irix" + +#elif defined (__linux) || defined (MGLNX86) || defined (ARCH_GLNX86) +#define CHOLMOD_LINUX +#define CHOLMOD_ARCHITECTURE "Linux" + +#elif defined (__APPLE__) +#define CHOLMOD_MAC +#define CHOLMOD_ARCHITECTURE "Mac" + +#elif defined (_AIX) || defined (MIBM_RS) || defined (ARCH_IBM_RS) +#define CHOLMOD_AIX +#define CHOLMOD_ARCHITECTURE "IBM AIX" +/* recent reports from IBM AIX seem to indicate that this is not needed: */ +/* #define BLAS_NO_UNDERSCORE */ + +#elif defined (__alpha) || defined (MALPHA) || defined (ARCH_ALPHA) +#define CHOLMOD_ALPHA +#define CHOLMOD_ARCHITECTURE "Compaq Alpha" + +#elif defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64) +#if defined (__MINGW32__) || defined (__MINGW32__) +#define CHOLMOD_MINGW +#elif defined (__CYGWIN32__) || defined (__CYGWIN32__) +#define CHOLMOD_CYGWIN +#else +#define CHOLMOD_WINDOWS +//#define BLAS_NO_UNDERSCORE +#endif +#define CHOLMOD_ARCHITECTURE "Microsoft Windows" + +#elif defined (__hppa) || defined (__hpux) || defined (MHPUX) || defined (ARCH_HPUX) +#define CHOLMOD_HP +#define CHOLMOD_ARCHITECTURE "HP Unix" +#define BLAS_NO_UNDERSCORE + +#elif defined (__hp700) || defined (MHP700) || defined (ARCH_HP700) +#define CHOLMOD_HP +#define CHOLMOD_ARCHITECTURE "HP 700 Unix" +#define BLAS_NO_UNDERSCORE + +#else +/* If the architecture is unknown, and you call the BLAS, you may need to */ +/* define BLAS_BY_VALUE, BLAS_NO_UNDERSCORE, and/or BLAS_CHAR_ARG yourself. */ +#define CHOLMOD_ARCHITECTURE "unknown" +#endif + +/* ========================================================================== */ +/* === BLAS and LAPACK names ================================================ */ +/* ========================================================================== */ + +/* Prototypes for the various versions of the BLAS. */ + +/* Determine if the 64-bit Sun Performance BLAS is to be used */ +#if defined(CHOLMOD_SOL2) && !defined(NSUNPERF) && defined(BLAS64) +#define SUN64 +#endif + +#ifdef SUN64 + +#define BLAS_DTRSV dtrsv_64_ +#define BLAS_DGEMV dgemv_64_ +#define BLAS_DTRSM dtrsm_64_ +#define BLAS_DGEMM dgemm_64_ +#define BLAS_DSYRK dsyrk_64_ +#define BLAS_DGER dger_64_ +#define BLAS_DSCAL dscal_64_ +#define LAPACK_DPOTRF dpotrf_64_ + +#define BLAS_ZTRSV ztrsv_64_ +#define BLAS_ZGEMV zgemv_64_ +#define BLAS_ZTRSM ztrsm_64_ +#define BLAS_ZGEMM zgemm_64_ +#define BLAS_ZHERK zherk_64_ +#define BLAS_ZGER zgeru_64_ +#define BLAS_ZSCAL zscal_64_ +#define LAPACK_ZPOTRF zpotrf_64_ + +#elif defined (BLAS_NO_UNDERSCORE) + +#define BLAS_DTRSV igraphdtrsv +#define BLAS_DGEMV igraphdgemv +#define BLAS_DTRSM igraphdtrsm +#define BLAS_DGEMM igraphdgemm +#define BLAS_DSYRK igraphdsyrk +#define BLAS_DGER igraphdger +#define BLAS_DSCAL igraphdscal +#define LAPACK_DPOTRF igraphdpotrf + +#define BLAS_ZTRSV ztrsv +#define BLAS_ZGEMV zgemv +#define BLAS_ZTRSM ztrsm +#define BLAS_ZGEMM zgemm +#define BLAS_ZHERK zherk +#define BLAS_ZGER zgeru +#define BLAS_ZSCAL zscal +#define LAPACK_ZPOTRF zpotrf + +#else + +#define BLAS_DTRSV igraphdtrsv_ +#define BLAS_DGEMV igraphdgemv_ +#define BLAS_DTRSM igraphdtrsm_ +#define BLAS_DGEMM igraphdgemm_ +#define BLAS_DSYRK igraphdsyrk_ +#define BLAS_DGER igraphdger_ +#define BLAS_DSCAL igraphdscal_ +#define LAPACK_DPOTRF igraphdpotrf_ + +#define BLAS_ZTRSV ztrsv_ +#define BLAS_ZGEMV zgemv_ +#define BLAS_ZTRSM ztrsm_ +#define BLAS_ZGEMM zgemm_ +#define BLAS_ZHERK zherk_ +#define BLAS_ZGER zgeru_ +#define BLAS_ZSCAL zscal_ +#define LAPACK_ZPOTRF zpotrf_ + +#endif + +/* ========================================================================== */ +/* === BLAS and LAPACK integer arguments ==================================== */ +/* ========================================================================== */ + +/* Compile CHOLMOD, UMFPACK, and SPQR with -DBLAS64 if you have a BLAS that + * uses 64-bit integers */ + +#if defined (LONGBLAS) || defined (BLAS64) +#define BLAS_INT SuiteSparse_long +#else +#define BLAS_INT int +#endif + +/* If the BLAS integer is smaller than the basic CHOLMOD integer, then we need + * to check for integer overflow when converting from Int to BLAS_INT. If + * any integer overflows, the externally-defined BLAS_OK variable is + * set to FALSE. BLAS_OK should be set to TRUE before calling any + * BLAS_* macro. + */ + +#define CHECK_BLAS_INT (sizeof (BLAS_INT) < sizeof (Int)) +#define EQ(K,k) (((BLAS_INT) K) == ((Int) k)) + +/* ========================================================================== */ +/* === BLAS and LAPACK prototypes and macros ================================ */ +/* ========================================================================== */ + +int BLAS_DGEMV (char *trans, BLAS_INT *m, BLAS_INT *n, double *alpha, + double *A, BLAS_INT *lda, double *X, BLAS_INT *incx, double *beta, + double *Y, BLAS_INT *incy) ; + +#define BLAS_dgemv(trans,m,n,alpha,A,lda,X,incx,beta,Y,incy) \ +{ \ + BLAS_INT M = m, N = n, LDA = lda, INCX = incx, INCY = incy ; \ + if (CHECK_BLAS_INT && !(EQ (M,m) && EQ (N,n) && EQ (LDA,lda) && \ + EQ (INCX,incx) && EQ (INCY,incy))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_DGEMV (trans, &M, &N, alpha, A, &LDA, X, &INCX, beta, Y, &INCY) ; \ + } \ +} + +void BLAS_ZGEMV (char *trans, BLAS_INT *m, BLAS_INT *n, double *alpha, + double *A, BLAS_INT *lda, double *X, BLAS_INT *incx, double *beta, + double *Y, BLAS_INT *incy) ; + +#define BLAS_zgemv(trans,m,n,alpha,A,lda,X,incx,beta,Y,incy) \ +{ \ + BLAS_INT M = m, N = n, LDA = lda, INCX = incx, INCY = incy ; \ + if (CHECK_BLAS_INT && !(EQ (M,m) && EQ (N,n) && EQ (LDA,lda) && \ + EQ (INCX,incx) && EQ (INCY,incy))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_ZGEMV (trans, &M, &N, alpha, A, &LDA, X, &INCX, beta, Y, &INCY) ; \ + } \ +} + +void BLAS_DTRSV (char *uplo, char *trans, char *diag, BLAS_INT *n, double *A, + BLAS_INT *lda, double *X, BLAS_INT *incx) ; + +#define BLAS_dtrsv(uplo,trans,diag,n,A,lda,X,incx) \ +{ \ + BLAS_INT N = n, LDA = lda, INCX = incx ; \ + if (CHECK_BLAS_INT && !(EQ (N,n) && EQ (LDA,lda) && EQ (INCX,incx))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_DTRSV (uplo, trans, diag, &N, A, &LDA, X, &INCX) ; \ + } \ +} + +void BLAS_ZTRSV (char *uplo, char *trans, char *diag, BLAS_INT *n, double *A, + BLAS_INT *lda, double *X, BLAS_INT *incx) ; + +#define BLAS_ztrsv(uplo,trans,diag,n,A,lda,X,incx) \ +{ \ + BLAS_INT N = n, LDA = lda, INCX = incx ; \ + if (CHECK_BLAS_INT && !(EQ (N,n) && EQ (LDA,lda) && EQ (INCX,incx))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_ZTRSV (uplo, trans, diag, &N, A, &LDA, X, &INCX) ; \ + } \ +} + +void BLAS_DTRSM (char *side, char *uplo, char *transa, char *diag, BLAS_INT *m, + BLAS_INT *n, double *alpha, double *A, BLAS_INT *lda, double *B, + BLAS_INT *ldb) ; + +#define BLAS_dtrsm(side,uplo,transa,diag,m,n,alpha,A,lda,B,ldb) \ +{ \ + BLAS_INT M = m, N = n, LDA = lda, LDB = ldb ; \ + if (CHECK_BLAS_INT && !(EQ (M,m) && EQ (N,n) && EQ (LDA,lda) && \ + EQ (LDB,ldb))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_DTRSM (side, uplo, transa, diag, &M, &N, alpha, A, &LDA, B, &LDB);\ + } \ +} + +void BLAS_ZTRSM (char *side, char *uplo, char *transa, char *diag, BLAS_INT *m, + BLAS_INT *n, double *alpha, double *A, BLAS_INT *lda, double *B, + BLAS_INT *ldb) ; + +#define BLAS_ztrsm(side,uplo,transa,diag,m,n,alpha,A,lda,B,ldb) \ +{ \ + BLAS_INT M = m, N = n, LDA = lda, LDB = ldb ; \ + if (CHECK_BLAS_INT && !(EQ (M,m) && EQ (N,n) && EQ (LDA,lda) && \ + EQ (LDB,ldb))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_ZTRSM (side, uplo, transa, diag, &M, &N, alpha, A, &LDA, B, &LDB);\ + } \ +} + +int BLAS_DGEMM (char *transa, char *transb, BLAS_INT *m, BLAS_INT *n, + BLAS_INT *k, double *alpha, double *A, BLAS_INT *lda, double *B, + BLAS_INT *ldb, double *beta, double *C, BLAS_INT *ldc) ; + +#define BLAS_dgemm(transa,transb,m,n,k,alpha,A,lda,B,ldb,beta,C,ldc) \ +{ \ + BLAS_INT M = m, N = n, K = k, LDA = lda, LDB = ldb, LDC = ldc ; \ + if (CHECK_BLAS_INT && !(EQ (M,m) && EQ (N,n) && EQ (K,k) && \ + EQ (LDA,lda) && EQ (LDB,ldb) && EQ (LDC,ldc))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_DGEMM (transa, transb, &M, &N, &K, alpha, A, &LDA, B, &LDB, beta, \ + C, &LDC) ; \ + } \ +} + +void BLAS_ZGEMM (char *transa, char *transb, BLAS_INT *m, BLAS_INT *n, + BLAS_INT *k, double *alpha, double *A, BLAS_INT *lda, double *B, + BLAS_INT *ldb, double *beta, double *C, BLAS_INT *ldc) ; + +#define BLAS_zgemm(transa,transb,m,n,k,alpha,A,lda,B,ldb,beta,C,ldc) \ +{ \ + BLAS_INT M = m, N = n, K = k, LDA = lda, LDB = ldb, LDC = ldc ; \ + if (CHECK_BLAS_INT && !(EQ (M,m) && EQ (N,n) && EQ (K,k) && \ + EQ (LDA,lda) && EQ (LDB,ldb) && EQ (LDC,ldc))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_ZGEMM (transa, transb, &M, &N, &K, alpha, A, &LDA, B, &LDB, beta, \ + C, &LDC) ; \ + } \ +} + +void BLAS_DSYRK (char *uplo, char *trans, BLAS_INT *n, BLAS_INT *k, + double *alpha, double *A, BLAS_INT *lda, double *beta, double *C, + BLAS_INT *ldc) ; + +#define BLAS_dsyrk(uplo,trans,n,k,alpha,A,lda,beta,C,ldc) \ +{ \ + BLAS_INT N = n, K = k, LDA = lda, LDC = ldc ; \ + if (CHECK_BLAS_INT && !(EQ (N,n) && EQ (K,k) && EQ (LDA,lda) && \ + EQ (LDC,ldc))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_DSYRK (uplo, trans, &N, &K, alpha, A, &LDA, beta, C, &LDC) ; \ + } \ +} \ + +void BLAS_ZHERK (char *uplo, char *trans, BLAS_INT *n, BLAS_INT *k, + double *alpha, double *A, BLAS_INT *lda, double *beta, double *C, + BLAS_INT *ldc) ; + +#define BLAS_zherk(uplo,trans,n,k,alpha,A,lda,beta,C,ldc) \ +{ \ + BLAS_INT N = n, K = k, LDA = lda, LDC = ldc ; \ + if (CHECK_BLAS_INT && !(EQ (N,n) && EQ (K,k) && EQ (LDA,lda) && \ + EQ (LDC,ldc))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_ZHERK (uplo, trans, &N, &K, alpha, A, &LDA, beta, C, &LDC) ; \ + } \ +} \ + +void LAPACK_DPOTRF (char *uplo, BLAS_INT *n, double *A, BLAS_INT *lda, + BLAS_INT *info) ; + +#define LAPACK_dpotrf(uplo,n,A,lda,info) \ +{ \ + BLAS_INT N = n, LDA = lda, INFO = 1 ; \ + if (CHECK_BLAS_INT && !(EQ (N,n) && EQ (LDA,lda))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + LAPACK_DPOTRF (uplo, &N, A, &LDA, &INFO) ; \ + } \ + info = INFO ; \ +} + +void LAPACK_ZPOTRF (char *uplo, BLAS_INT *n, double *A, BLAS_INT *lda, + BLAS_INT *info) ; + +#define LAPACK_zpotrf(uplo,n,A,lda,info) \ +{ \ + BLAS_INT N = n, LDA = lda, INFO = 1 ; \ + if (CHECK_BLAS_INT && !(EQ (N,n) && EQ (LDA,lda))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + LAPACK_ZPOTRF (uplo, &N, A, &LDA, &INFO) ; \ + } \ + info = INFO ; \ +} + +/* ========================================================================== */ + +void BLAS_DSCAL (BLAS_INT *n, double *alpha, double *Y, BLAS_INT *incy) ; + +#define BLAS_dscal(n,alpha,Y,incy) \ +{ \ + BLAS_INT N = n, INCY = incy ; \ + if (CHECK_BLAS_INT && !(EQ (N,n) && EQ (INCY,incy))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_DSCAL (&N, alpha, Y, &INCY) ; \ + } \ +} + +void BLAS_ZSCAL (BLAS_INT *n, double *alpha, double *Y, BLAS_INT *incy) ; + +#define BLAS_zscal(n,alpha,Y,incy) \ +{ \ + BLAS_INT N = n, INCY = incy ; \ + if (CHECK_BLAS_INT && !(EQ (N,n) && EQ (INCY,incy))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_ZSCAL (&N, alpha, Y, &INCY) ; \ + } \ +} + +void BLAS_DGER (BLAS_INT *m, BLAS_INT *n, double *alpha, + double *X, BLAS_INT *incx, double *Y, BLAS_INT *incy, + double *A, BLAS_INT *lda) ; + +#define BLAS_dger(m,n,alpha,X,incx,Y,incy,A,lda) \ +{ \ + BLAS_INT M = m, N = n, LDA = lda, INCX = incx, INCY = incy ; \ + if (CHECK_BLAS_INT && !(EQ (M,m) && EQ (N,n) && EQ (LDA,lda) && \ + EQ (INCX,incx) && EQ (INCY,incy))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_DGER (&M, &N, alpha, X, &INCX, Y, &INCY, A, &LDA) ; \ + } \ +} + +void BLAS_ZGER (BLAS_INT *m, BLAS_INT *n, double *alpha, + double *X, BLAS_INT *incx, double *Y, BLAS_INT *incy, + double *A, BLAS_INT *lda) ; + +#define BLAS_zgeru(m,n,alpha,X,incx,Y,incy,A,lda) \ +{ \ + BLAS_INT M = m, N = n, LDA = lda, INCX = incx, INCY = incy ; \ + if (CHECK_BLAS_INT && !(EQ (M,m) && EQ (N,n) && EQ (LDA,lda) && \ + EQ (INCX,incx) && EQ (INCY,incy))) \ + { \ + BLAS_OK = FALSE ; \ + } \ + if (!CHECK_BLAS_INT || BLAS_OK) \ + { \ + BLAS_ZGER (&M, &N, alpha, X, &INCX, Y, &INCY, A, &LDA) ; \ + } \ +} + +#endif diff --git a/src/CHOLMOD/Include/cholmod_camd.h b/src/CHOLMOD/Include/cholmod_camd.h new file mode 100644 index 0000000..3e1bf8d --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_camd.h @@ -0,0 +1,102 @@ +/* ========================================================================== */ +/* === Include/cholmod_camd.h =============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_camd.h. + * Copyright (C) 2005-2013, Univ. of Florida. Author: Timothy A. Davis + * CHOLMOD/Include/cholmod_partition.h is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD Partition module, interface to CAMD, CCOLAMD, and CSYMAMD + * + * An interface to CCOLAMD and CSYMAMD, constrained minimum degree ordering + * methods which order a matrix following constraints determined via nested + * dissection. + * + * These functions do not require METIS. They are installed unless NCAMD + * is defined: + * cholmod_ccolamd interface to CCOLAMD ordering + * cholmod_csymamd interface to CSYMAMD ordering + * cholmod_camd interface to CAMD ordering + * + * Requires the Core and Cholesky modules, and two packages: CAMD, + * and CCOLAMD. Used by functions in the Partition Module. + */ + +#ifndef CHOLMOD_CAMD_H +#define CHOLMOD_CAMD_H + +#include "cholmod_core.h" + +/* -------------------------------------------------------------------------- */ +/* cholmod_ccolamd */ +/* -------------------------------------------------------------------------- */ + +/* Order AA' or A(:,f)*A(:,f)' using CCOLAMD. */ + +int cholmod_ccolamd +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int *Cmember, /* size A->nrow. Cmember [i] = c if row i is in the + * constraint set c. c must be >= 0. The # of + * constraint sets is max (Cmember) + 1. If Cmember is + * NULL, then it is interpretted as Cmember [i] = 0 for + * all i */ + /* ---- output --- */ + int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_ccolamd (cholmod_sparse *, SuiteSparse_long *, size_t, + SuiteSparse_long *, SuiteSparse_long *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_csymamd */ +/* -------------------------------------------------------------------------- */ + +/* Order A using CSYMAMD. */ + +int cholmod_csymamd +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + /* ---- output --- */ + int *Cmember, /* size nrow. see cholmod_ccolamd above */ + int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_csymamd (cholmod_sparse *, SuiteSparse_long *, + SuiteSparse_long *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_camd */ +/* -------------------------------------------------------------------------- */ + +/* Order A using CAMD. */ + +int cholmod_camd +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* ---- output --- */ + int *Cmember, /* size nrow. see cholmod_ccolamd above */ + int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_camd (cholmod_sparse *, SuiteSparse_long *, size_t, + SuiteSparse_long *, SuiteSparse_long *, cholmod_common *) ; + +#endif diff --git a/src/CHOLMOD/Include/cholmod_check.h b/src/CHOLMOD/Include/cholmod_check.h new file mode 100644 index 0000000..e75c415 --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_check.h @@ -0,0 +1,427 @@ +/* ========================================================================== */ +/* === Include/cholmod_check.h ============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_check.h. Copyright (C) 2005-2006, Timothy A. Davis + * CHOLMOD/Include/cholmod_check.h is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* CHOLMOD Check module. + * + * Routines that check and print the 5 basic data types in CHOLMOD, and 3 kinds + * of integer vectors (subset, perm, and parent), and read in matrices from a + * file: + * + * cholmod_check_common check/print the Common object + * cholmod_print_common + * + * cholmod_check_sparse check/print a sparse matrix in column-oriented form + * cholmod_print_sparse + * + * cholmod_check_dense check/print a dense matrix + * cholmod_print_dense + * + * cholmod_check_factor check/print a Cholesky factorization + * cholmod_print_factor + * + * cholmod_check_triplet check/print a sparse matrix in triplet form + * cholmod_print_triplet + * + * cholmod_check_subset check/print a subset (integer vector in given range) + * cholmod_print_subset + * + * cholmod_check_perm check/print a permutation (an integer vector) + * cholmod_print_perm + * + * cholmod_check_parent check/print an elimination tree (an integer vector) + * cholmod_print_parent + * + * cholmod_read_triplet read a matrix in triplet form (any Matrix Market + * "coordinate" format, or a generic triplet format). + * + * cholmod_read_sparse read a matrix in sparse form (same file format as + * cholmod_read_triplet). + * + * cholmod_read_dense read a dense matrix (any Matrix Market "array" + * format, or a generic dense format). + * + * cholmod_write_sparse write a sparse matrix to a Matrix Market file. + * + * cholmod_write_dense write a dense matrix to a Matrix Market file. + * + * cholmod_print_common and cholmod_check_common are the only two routines that + * you may call after calling cholmod_finish. + * + * Requires the Core module. Not required by any CHOLMOD module, except when + * debugging is enabled (in which case all modules require the Check module). + * + * See cholmod_read.c for a description of the file formats supported by the + * cholmod_read_* routines. + */ + +#ifndef CHOLMOD_CHECK_H +#define CHOLMOD_CHECK_H + +#include "cholmod_core.h" +#include + +/* -------------------------------------------------------------------------- */ +/* cholmod_check_common: check the Common object */ +/* -------------------------------------------------------------------------- */ + +int cholmod_check_common +( + cholmod_common *Common +) ; + +int cholmod_l_check_common (cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_print_common: print the Common object */ +/* -------------------------------------------------------------------------- */ + +int cholmod_print_common +( + /* ---- input ---- */ + const char *name, /* printed name of Common object */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_print_common (const char *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_gpu_stats: print the GPU / CPU statistics */ +/* -------------------------------------------------------------------------- */ + +int cholmod_gpu_stats (cholmod_common *) ; +int cholmod_l_gpu_stats (cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_check_sparse: check a sparse matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_check_sparse +( + /* ---- input ---- */ + cholmod_sparse *A, /* sparse matrix to check */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_check_sparse (cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_print_sparse */ +/* -------------------------------------------------------------------------- */ + +int cholmod_print_sparse +( + /* ---- input ---- */ + cholmod_sparse *A, /* sparse matrix to print */ + const char *name, /* printed name of sparse matrix */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_print_sparse (cholmod_sparse *, const char *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_check_dense: check a dense matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_check_dense +( + /* ---- input ---- */ + cholmod_dense *X, /* dense matrix to check */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_check_dense (cholmod_dense *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_print_dense: print a dense matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_print_dense +( + /* ---- input ---- */ + cholmod_dense *X, /* dense matrix to print */ + const char *name, /* printed name of dense matrix */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_print_dense (cholmod_dense *, const char *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_check_factor: check a factor */ +/* -------------------------------------------------------------------------- */ + +int cholmod_check_factor +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to check */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_check_factor (cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_print_factor: print a factor */ +/* -------------------------------------------------------------------------- */ + +int cholmod_print_factor +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to print */ + const char *name, /* printed name of factor */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_print_factor (cholmod_factor *, const char *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_check_triplet: check a sparse matrix in triplet form */ +/* -------------------------------------------------------------------------- */ + +int cholmod_check_triplet +( + /* ---- input ---- */ + cholmod_triplet *T, /* triplet matrix to check */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_check_triplet (cholmod_triplet *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_print_triplet: print a triplet matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_print_triplet +( + /* ---- input ---- */ + cholmod_triplet *T, /* triplet matrix to print */ + const char *name, /* printed name of triplet matrix */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_print_triplet (cholmod_triplet *, const char *, cholmod_common *); + +/* -------------------------------------------------------------------------- */ +/* cholmod_check_subset: check a subset */ +/* -------------------------------------------------------------------------- */ + +int cholmod_check_subset +( + /* ---- input ---- */ + int *Set, /* Set [0:len-1] is a subset of 0:n-1. Duplicates OK */ + SuiteSparse_long len, /* size of Set (an integer array) */ + size_t n, /* 0:n-1 is valid range */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_check_subset (SuiteSparse_long *, SuiteSparse_long, size_t, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_print_subset: print a subset */ +/* -------------------------------------------------------------------------- */ + +int cholmod_print_subset +( + /* ---- input ---- */ + int *Set, /* Set [0:len-1] is a subset of 0:n-1. Duplicates OK */ + SuiteSparse_long len, /* size of Set (an integer array) */ + size_t n, /* 0:n-1 is valid range */ + const char *name, /* printed name of Set */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_print_subset (SuiteSparse_long *, SuiteSparse_long, size_t, + const char *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_check_perm: check a permutation */ +/* -------------------------------------------------------------------------- */ + +int cholmod_check_perm +( + /* ---- input ---- */ + int *Perm, /* Perm [0:len-1] is a permutation of subset of 0:n-1 */ + size_t len, /* size of Perm (an integer array) */ + size_t n, /* 0:n-1 is valid range */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_check_perm (SuiteSparse_long *, size_t, size_t, cholmod_common *); + +/* -------------------------------------------------------------------------- */ +/* cholmod_print_perm: print a permutation vector */ +/* -------------------------------------------------------------------------- */ + +int cholmod_print_perm +( + /* ---- input ---- */ + int *Perm, /* Perm [0:len-1] is a permutation of subset of 0:n-1 */ + size_t len, /* size of Perm (an integer array) */ + size_t n, /* 0:n-1 is valid range */ + const char *name, /* printed name of Perm */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_print_perm (SuiteSparse_long *, size_t, size_t, const char *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_check_parent: check an elimination tree */ +/* -------------------------------------------------------------------------- */ + +int cholmod_check_parent +( + /* ---- input ---- */ + int *Parent, /* Parent [0:n-1] is an elimination tree */ + size_t n, /* size of Parent */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_check_parent (SuiteSparse_long *, size_t, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_print_parent */ +/* -------------------------------------------------------------------------- */ + +int cholmod_print_parent +( + /* ---- input ---- */ + int *Parent, /* Parent [0:n-1] is an elimination tree */ + size_t n, /* size of Parent */ + const char *name, /* printed name of Parent */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_print_parent (SuiteSparse_long *, size_t, const char *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_read_sparse: read a sparse matrix from a file */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_read_sparse +( + /* ---- input ---- */ + FILE *f, /* file to read from, must already be open */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_read_sparse (FILE *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_read_triplet: read a triplet matrix from a file */ +/* -------------------------------------------------------------------------- */ + +cholmod_triplet *cholmod_read_triplet +( + /* ---- input ---- */ + FILE *f, /* file to read from, must already be open */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_triplet *cholmod_l_read_triplet (FILE *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_read_dense: read a dense matrix from a file */ +/* -------------------------------------------------------------------------- */ + +cholmod_dense *cholmod_read_dense +( + /* ---- input ---- */ + FILE *f, /* file to read from, must already be open */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_dense *cholmod_l_read_dense (FILE *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_read_matrix: read a sparse or dense matrix from a file */ +/* -------------------------------------------------------------------------- */ + +void *cholmod_read_matrix +( + /* ---- input ---- */ + FILE *f, /* file to read from, must already be open */ + int prefer, /* If 0, a sparse matrix is always return as a + * cholmod_triplet form. It can have any stype + * (symmetric-lower, unsymmetric, or + * symmetric-upper). + * If 1, a sparse matrix is returned as an unsymmetric + * cholmod_sparse form (A->stype == 0), with both + * upper and lower triangular parts present. + * This is what the MATLAB mread mexFunction does, + * since MATLAB does not have an stype. + * If 2, a sparse matrix is returned with an stype of 0 + * or 1 (unsymmetric, or symmetric with upper part + * stored). + * This argument has no effect for dense matrices. + */ + /* ---- output---- */ + int *mtype, /* CHOLMOD_TRIPLET, CHOLMOD_SPARSE or CHOLMOD_DENSE */ + /* --------------- */ + cholmod_common *Common +) ; + +void *cholmod_l_read_matrix (FILE *, int, int *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_write_sparse: write a sparse matrix to a file */ +/* -------------------------------------------------------------------------- */ + +int cholmod_write_sparse +( + /* ---- input ---- */ + FILE *f, /* file to write to, must already be open */ + cholmod_sparse *A, /* matrix to print */ + cholmod_sparse *Z, /* optional matrix with pattern of explicit zeros */ + const char *comments, /* optional filename of comments to include */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_write_sparse (FILE *, cholmod_sparse *, cholmod_sparse *, + const char *c, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_write_dense: write a dense matrix to a file */ +/* -------------------------------------------------------------------------- */ + +int cholmod_write_dense +( + /* ---- input ---- */ + FILE *f, /* file to write to, must already be open */ + cholmod_dense *X, /* matrix to print */ + const char *comments, /* optional filename of comments to include */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_write_dense (FILE *, cholmod_dense *, const char *, + cholmod_common *) ; +#endif diff --git a/src/CHOLMOD/Include/cholmod_cholesky.h b/src/CHOLMOD/Include/cholmod_cholesky.h new file mode 100644 index 0000000..aa2634a --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_cholesky.h @@ -0,0 +1,565 @@ +/* ========================================================================== */ +/* === Include/cholmod_cholesky.h =========================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_cholesky.h. Copyright (C) 2005-2013, Timothy A. Davis + * CHOLMOD/Include/cholmod_cholesky.h is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* CHOLMOD Cholesky module. + * + * Sparse Cholesky routines: analysis, factorization, and solve. + * + * The primary routines are all that a user requires to order, analyze, and + * factorize a sparse symmetric positive definite matrix A (or A*A'), and + * to solve Ax=b (or A*A'x=b). The primary routines rely on the secondary + * routines, the CHOLMOD Core module, and the AMD and COLAMD packages. They + * make optional use of the CHOLMOD Supernodal and Partition modules, the + * METIS package, and the CCOLAMD package. + * + * Primary routines: + * ----------------- + * + * cholmod_analyze order and analyze (simplicial or supernodal) + * cholmod_factorize simplicial or supernodal Cholesky factorization + * cholmod_solve solve a linear system (simplicial or supernodal) + * cholmod_solve2 like cholmod_solve, but reuse workspace + * cholmod_spsolve solve a linear system (sparse x and b) + * + * Secondary routines: + * ------------------ + * + * cholmod_analyze_p analyze, with user-provided permutation or f set + * cholmod_factorize_p factorize, with user-provided permutation or f + * cholmod_analyze_ordering analyze a fill-reducing ordering + * cholmod_etree find the elimination tree + * cholmod_rowcolcounts compute the row/column counts of L + * cholmod_amd order using AMD + * cholmod_colamd order using COLAMD + * cholmod_rowfac incremental simplicial factorization + * cholmod_rowfac_mask rowfac, specific to LPDASA + * cholmod_row_subtree find the nonzero pattern of a row of L + * cholmod_resymbol recompute the symbolic pattern of L + * cholmod_resymbol_noperm recompute the symbolic pattern of L, no L->Perm + * cholmod_postorder postorder a tree + * + * Requires the Core module, and two packages: AMD and COLAMD. + * Optionally uses the Supernodal and Partition modules. + * Required by the Partition module. + */ + +#ifndef CHOLMOD_CHOLESKY_H +#define CHOLMOD_CHOLESKY_H + +#include "cholmod_config.h" +#include "cholmod_core.h" + +#ifndef NPARTITION +#include "cholmod_partition.h" +#endif + +#ifndef NSUPERNODAL +#include "cholmod_supernodal.h" +#endif + +/* -------------------------------------------------------------------------- */ +/* cholmod_analyze: order and analyze (simplicial or supernodal) */ +/* -------------------------------------------------------------------------- */ + +/* Orders and analyzes A, AA', PAP', or PAA'P' and returns a symbolic factor + * that can later be passed to cholmod_factorize. */ + +cholmod_factor *cholmod_analyze +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order and analyze */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_factor *cholmod_l_analyze (cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_analyze_p: analyze, with user-provided permutation or f set */ +/* -------------------------------------------------------------------------- */ + +/* Orders and analyzes A, AA', PAP', PAA'P', FF', or PFF'P and returns a + * symbolic factor that can later be passed to cholmod_factorize, where + * F = A(:,fset) if fset is not NULL and A->stype is zero. + * UserPerm is tried if non-NULL. */ + +cholmod_factor *cholmod_analyze_p +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order and analyze */ + int *UserPerm, /* user-provided permutation, size A->nrow */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_factor *cholmod_l_analyze_p (cholmod_sparse *, SuiteSparse_long *, + SuiteSparse_long *, size_t, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_analyze_p2: analyze for sparse Cholesky or sparse QR */ +/* -------------------------------------------------------------------------- */ + +cholmod_factor *cholmod_analyze_p2 +( + /* ---- input ---- */ + int for_cholesky, /* if TRUE, then analyze for Cholesky; else for QR */ + cholmod_sparse *A, /* matrix to order and analyze */ + int *UserPerm, /* user-provided permutation, size A->nrow */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_factor *cholmod_l_analyze_p2 (int, cholmod_sparse *, SuiteSparse_long *, + SuiteSparse_long *, size_t, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_factorize: simplicial or supernodal Cholesky factorization */ +/* -------------------------------------------------------------------------- */ + +/* Factorizes PAP' (or PAA'P' if A->stype is 0), using a factor obtained + * from cholmod_analyze. The analysis can be re-used simply by calling this + * routine a second time with another matrix. A must have the same nonzero + * pattern as that passed to cholmod_analyze. */ + +int cholmod_factorize +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + /* ---- in/out --- */ + cholmod_factor *L, /* resulting factorization */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_factorize (cholmod_sparse *, cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_factorize_p: factorize, with user-provided permutation or fset */ +/* -------------------------------------------------------------------------- */ + +/* Same as cholmod_factorize, but with more options. */ + +int cholmod_factorize_p +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + double beta [2], /* factorize beta*I+A or beta*I+A'*A */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* ---- in/out --- */ + cholmod_factor *L, /* resulting factorization */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_factorize_p (cholmod_sparse *, double *, SuiteSparse_long *, + size_t, cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_solve: solve a linear system (simplicial or supernodal) */ +/* -------------------------------------------------------------------------- */ + +/* Solves one of many linear systems with a dense right-hand-side, using the + * factorization from cholmod_factorize (or as modified by any other CHOLMOD + * routine). D is identity for LL' factorizations. */ + +#define CHOLMOD_A 0 /* solve Ax=b */ +#define CHOLMOD_LDLt 1 /* solve LDL'x=b */ +#define CHOLMOD_LD 2 /* solve LDx=b */ +#define CHOLMOD_DLt 3 /* solve DL'x=b */ +#define CHOLMOD_L 4 /* solve Lx=b */ +#define CHOLMOD_Lt 5 /* solve L'x=b */ +#define CHOLMOD_D 6 /* solve Dx=b */ +#define CHOLMOD_P 7 /* permute x=Px */ +#define CHOLMOD_Pt 8 /* permute x=P'x */ + +cholmod_dense *cholmod_solve /* returns the solution X */ +( + /* ---- input ---- */ + int sys, /* system to solve */ + cholmod_factor *L, /* factorization to use */ + cholmod_dense *B, /* right-hand-side */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_dense *cholmod_l_solve (int, cholmod_factor *, cholmod_dense *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_solve2: like cholmod_solve, but with reusable workspace */ +/* -------------------------------------------------------------------------- */ + +int cholmod_solve2 /* returns TRUE on success, FALSE on failure */ +( + /* ---- input ---- */ + int sys, /* system to solve */ + cholmod_factor *L, /* factorization to use */ + cholmod_dense *B, /* right-hand-side */ + cholmod_sparse *Bset, + /* ---- output --- */ + cholmod_dense **X_Handle, /* solution, allocated if need be */ + cholmod_sparse **Xset_Handle, + /* ---- workspace */ + cholmod_dense **Y_Handle, /* workspace, or NULL */ + cholmod_dense **E_Handle, /* workspace, or NULL */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_solve2 (int, cholmod_factor *, cholmod_dense *, cholmod_sparse *, + cholmod_dense **, cholmod_sparse **, cholmod_dense **, cholmod_dense **, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_spsolve: solve a linear system with a sparse right-hand-side */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_spsolve +( + /* ---- input ---- */ + int sys, /* system to solve */ + cholmod_factor *L, /* factorization to use */ + cholmod_sparse *B, /* right-hand-side */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_spsolve (int, cholmod_factor *, cholmod_sparse *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_etree: find the elimination tree of A or A'*A */ +/* -------------------------------------------------------------------------- */ + +int cholmod_etree +( + /* ---- input ---- */ + cholmod_sparse *A, + /* ---- output --- */ + int *Parent, /* size ncol. Parent [j] = p if p is the parent of j */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_etree (cholmod_sparse *, SuiteSparse_long *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_rowcolcounts: compute the row/column counts of L */ +/* -------------------------------------------------------------------------- */ + +int cholmod_rowcolcounts +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int *Parent, /* size nrow. Parent [i] = p if p is the parent of i */ + int *Post, /* size nrow. Post [k] = i if i is the kth node in + * the postordered etree. */ + /* ---- output --- */ + int *RowCount, /* size nrow. RowCount [i] = # entries in the ith row of + * L, including the diagonal. */ + int *ColCount, /* size nrow. ColCount [i] = # entries in the ith + * column of L, including the diagonal. */ + int *First, /* size nrow. First [i] = k is the least postordering + * of any descendant of i. */ + int *Level, /* size nrow. Level [i] is the length of the path from + * i to the root, with Level [root] = 0. */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_rowcolcounts (cholmod_sparse *, SuiteSparse_long *, size_t, + SuiteSparse_long *, SuiteSparse_long *, SuiteSparse_long *, + SuiteSparse_long *, SuiteSparse_long *, SuiteSparse_long *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_analyze_ordering: analyze a fill-reducing ordering */ +/* -------------------------------------------------------------------------- */ + +int cholmod_analyze_ordering +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + int ordering, /* ordering method used */ + int *Perm, /* size n, fill-reducing permutation to analyze */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* ---- output --- */ + int *Parent, /* size n, elimination tree */ + int *Post, /* size n, postordering of elimination tree */ + int *ColCount, /* size n, nnz in each column of L */ + /* ---- workspace */ + int *First, /* size nworkspace for cholmod_postorder */ + int *Level, /* size n workspace for cholmod_postorder */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_analyze_ordering (cholmod_sparse *, int, SuiteSparse_long *, + SuiteSparse_long *, size_t, SuiteSparse_long *, SuiteSparse_long *, + SuiteSparse_long *, SuiteSparse_long *, SuiteSparse_long *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_amd: order using AMD */ +/* -------------------------------------------------------------------------- */ + +/* Finds a permutation P to reduce fill-in in the factorization of P*A*P' + * or P*A*A'P' */ + +int cholmod_amd +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* ---- output --- */ + int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_amd (cholmod_sparse *, SuiteSparse_long *, size_t, + SuiteSparse_long *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_colamd: order using COLAMD */ +/* -------------------------------------------------------------------------- */ + +/* Finds a permutation P to reduce fill-in in the factorization of P*A*A'*P'. + * Orders F*F' where F = A (:,fset) if fset is not NULL */ + +int cholmod_colamd +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int postorder, /* if TRUE, follow with a coletree postorder */ + /* ---- output --- */ + int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_colamd (cholmod_sparse *, SuiteSparse_long *, size_t, int, + SuiteSparse_long *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_rowfac: incremental simplicial factorization */ +/* -------------------------------------------------------------------------- */ + +/* Partial or complete simplicial factorization. Rows and columns kstart:kend-1 + * of L and D must be initially equal to rows/columns kstart:kend-1 of the + * identity matrix. Row k can only be factorized if all descendants of node + * k in the elimination tree have been factorized. */ + +int cholmod_rowfac +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + cholmod_sparse *F, /* used for A*A' case only. F=A' or A(:,fset)' */ + double beta [2], /* factorize beta*I+A or beta*I+A'*A */ + size_t kstart, /* first row to factorize */ + size_t kend, /* last row to factorize is kend-1 */ + /* ---- in/out --- */ + cholmod_factor *L, + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_rowfac (cholmod_sparse *, cholmod_sparse *, double *, size_t, + size_t, cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_rowfac_mask: incremental simplicial factorization */ +/* -------------------------------------------------------------------------- */ + +/* cholmod_rowfac_mask is a version of cholmod_rowfac that is specific to + * LPDASA. It is unlikely to be needed by any other application. */ + +int cholmod_rowfac_mask +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + cholmod_sparse *F, /* used for A*A' case only. F=A' or A(:,fset)' */ + double beta [2], /* factorize beta*I+A or beta*I+A'*A */ + size_t kstart, /* first row to factorize */ + size_t kend, /* last row to factorize is kend-1 */ + int *mask, /* if mask[i] >= 0, then set row i to zero */ + int *RLinkUp, /* link list of rows to compute */ + /* ---- in/out --- */ + cholmod_factor *L, + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_rowfac_mask (cholmod_sparse *, cholmod_sparse *, double *, size_t, + size_t, SuiteSparse_long *, SuiteSparse_long *, cholmod_factor *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_row_subtree: find the nonzero pattern of a row of L */ +/* -------------------------------------------------------------------------- */ + +/* Find the nonzero pattern of x for the system Lx=b where L = (0:k-1,0:k-1) + * and b = kth column of A or A*A' (rows 0 to k-1 only) */ + +int cholmod_row_subtree +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + cholmod_sparse *F, /* used for A*A' case only. F=A' or A(:,fset)' */ + size_t k, /* row k of L */ + int *Parent, /* elimination tree */ + /* ---- output --- */ + cholmod_sparse *R, /* pattern of L(k,:), n-by-1 with R->nzmax >= n */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_row_subtree (cholmod_sparse *, cholmod_sparse *, size_t, + SuiteSparse_long *, cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_lsolve_pattern: find the nonzero pattern of x=L\b */ +/* -------------------------------------------------------------------------- */ + +int cholmod_lsolve_pattern +( + /* ---- input ---- */ + cholmod_sparse *B, /* sparse right-hand-side (a single sparse column) */ + cholmod_factor *L, /* the factor L from which parent(i) is derived */ + /* ---- output --- */ + cholmod_sparse *X, /* pattern of X=L\B, n-by-1 with X->nzmax >= n */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_lsolve_pattern (cholmod_sparse *, cholmod_factor *, + cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_row_lsubtree: find the nonzero pattern of a row of L */ +/* -------------------------------------------------------------------------- */ + +/* Identical to cholmod_row_subtree, except that it finds the elimination tree + * from L itself. */ + +int cholmod_row_lsubtree +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + int *Fi, size_t fnz, /* nonzero pattern of kth row of A', not required + * for the symmetric case. Need not be sorted. */ + size_t k, /* row k of L */ + cholmod_factor *L, /* the factor L from which parent(i) is derived */ + /* ---- output --- */ + cholmod_sparse *R, /* pattern of L(k,:), n-by-1 with R->nzmax >= n */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_row_lsubtree (cholmod_sparse *, SuiteSparse_long *, size_t, + size_t, cholmod_factor *, cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_resymbol: recompute the symbolic pattern of L */ +/* -------------------------------------------------------------------------- */ + +/* Remove entries from L that are not in the factorization of P*A*P', P*A*A'*P', + * or P*F*F'*P' (depending on A->stype and whether fset is NULL or not). + * + * cholmod_resymbol is the same as cholmod_resymbol_noperm, except that it + * first permutes A according to L->Perm. A can be upper/lower/unsymmetric, + * in contrast to cholmod_resymbol_noperm (which can be lower or unsym). */ + +int cholmod_resymbol +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int pack, /* if TRUE, pack the columns of L */ + /* ---- in/out --- */ + cholmod_factor *L, /* factorization, entries pruned on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_resymbol (cholmod_sparse *, SuiteSparse_long *, size_t, int, + cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_resymbol_noperm: recompute the symbolic pattern of L, no L->Perm */ +/* -------------------------------------------------------------------------- */ + +/* Remove entries from L that are not in the factorization of A, A*A', + * or F*F' (depending on A->stype and whether fset is NULL or not). */ + +int cholmod_resymbol_noperm +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int pack, /* if TRUE, pack the columns of L */ + /* ---- in/out --- */ + cholmod_factor *L, /* factorization, entries pruned on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_resymbol_noperm (cholmod_sparse *, SuiteSparse_long *, size_t, int, + cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_rcond: compute rough estimate of reciprocal of condition number */ +/* -------------------------------------------------------------------------- */ + +double cholmod_rcond /* return min(diag(L)) / max(diag(L)) */ +( + /* ---- input ---- */ + cholmod_factor *L, + /* --------------- */ + cholmod_common *Common +) ; + +double cholmod_l_rcond (cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_postorder: Compute the postorder of a tree */ +/* -------------------------------------------------------------------------- */ + +SuiteSparse_long cholmod_postorder /* return # of nodes postordered */ +( + /* ---- input ---- */ + int *Parent, /* size n. Parent [j] = p if p is the parent of j */ + size_t n, + int *Weight_p, /* size n, optional. Weight [j] is weight of node j */ + /* ---- output --- */ + int *Post, /* size n. Post [k] = j is kth in postordered tree */ + /* --------------- */ + cholmod_common *Common +) ; + +SuiteSparse_long cholmod_l_postorder (SuiteSparse_long *, size_t, + SuiteSparse_long *, SuiteSparse_long *, cholmod_common *) ; + +#endif diff --git a/src/CHOLMOD/Include/cholmod_complexity.h b/src/CHOLMOD/Include/cholmod_complexity.h new file mode 100644 index 0000000..a84583a --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_complexity.h @@ -0,0 +1,264 @@ +/* ========================================================================== */ +/* === Include/cholmod_complexity.h ========================================= */ +/* ========================================================================== */ + +/* Define operations on pattern, real, complex, and zomplex objects. + * + * The xtype of an object defines it numerical type. A qttern object has no + * numerical values (A->x and A->z are NULL). A real object has no imaginary + * qrt (A->x is used, A->z is NULL). A complex object has an imaginary qrt + * that is stored interleaved with its real qrt (A->x is of size 2*nz, A->z + * is NULL). A zomplex object has both real and imaginary qrts, which are + * stored seqrately, as in MATLAB (A->x and A->z are both used). + * + * XTYPE is CHOLMOD_PATTERN, _REAL, _COMPLEX or _ZOMPLEX, and is the xtype of + * the template routine under construction. XTYPE2 is equal to XTYPE, except + * if XTYPE is CHOLMOD_PATTERN, in which case XTYPE is CHOLMOD_REAL. + * XTYPE and XTYPE2 are defined in cholmod_template.h. + */ + +/* -------------------------------------------------------------------------- */ +/* pattern */ +/* -------------------------------------------------------------------------- */ + +#define P_TEMPLATE(name) p_ ## name +#define P_ASSIGN2(x,z,p,ax,az,q) x [p] = 1 +#define P_PRINT(k,x,z,p) PRK(k, ("1")) + +/* -------------------------------------------------------------------------- */ +/* real */ +/* -------------------------------------------------------------------------- */ + +#define R_TEMPLATE(name) r_ ## name +#define R_ASSEMBLE(x,z,p,ax,az,q) x [p] += ax [q] +#define R_ASSIGN(x,z,p,ax,az,q) x [p] = ax [q] +#define R_ASSIGN_CONJ(x,z,p,ax,az,q) x [p] = ax [q] +#define R_ASSIGN_REAL(x,p,ax,q) x [p] = ax [q] +#define R_XTYPE_OK(type) ((type) == CHOLMOD_REAL) +#define R_IS_NONZERO(ax,az,q) IS_NONZERO (ax [q]) +#define R_IS_ZERO(ax,az,q) IS_ZERO (ax [q]) +#define R_IS_ONE(ax,az,q) (ax [q] == 1) +#define R_MULT(x,z,p, ax,az,q, bx,bz,r) x [p] = ax [q] * bx [r] +#define R_MULTADD(x,z,p, ax,az,q, bx,bz,r) x [p] += ax [q] * bx [r] +#define R_MULTSUB(x,z,p, ax,az,q, bx,bz,r) x [p] -= ax [q] * bx [r] +#define R_MULTADDCONJ(x,z,p, ax,az,q, bx,bz,r) x [p] += ax [q] * bx [r] +#define R_MULTSUBCONJ(x,z,p, ax,az,q, bx,bz,r) x [p] -= ax [q] * bx [r] +#define R_ADD(x,z,p, ax,az,q, bx,bz,r) x [p] = ax [q] + bx [r] +#define R_ADD_REAL(x,p, ax,q, bx,r) x [p] = ax [q] + bx [r] +#define R_CLEAR(x,z,p) x [p] = 0 +#define R_CLEAR_IMAG(x,z,p) +#define R_DIV(x,z,p,ax,az,q) x [p] /= ax [q] +#define R_LLDOT(x,p, ax,az,q) x [p] -= ax [q] * ax [q] +#define R_PRINT(k,x,z,p) PRK(k, ("%24.16e", x [p])) + +#define R_DIV_REAL(x,z,p, ax,az,q, bx,r) x [p] = ax [q] / bx [r] +#define R_MULT_REAL(x,z,p, ax,az,q, bx,r) x [p] = ax [q] * bx [r] + +#define R_LDLDOT(x,p, ax,az,q, bx,r) x [p] -=(ax[q] * ax[q])/ bx[r] + +/* -------------------------------------------------------------------------- */ +/* complex */ +/* -------------------------------------------------------------------------- */ + +#define C_TEMPLATE(name) c_ ## name +#define CT_TEMPLATE(name) ct_ ## name + +#define C_ASSEMBLE(x,z,p,ax,az,q) \ + x [2*(p) ] += ax [2*(q) ] ; \ + x [2*(p)+1] += ax [2*(q)+1] + +#define C_ASSIGN(x,z,p,ax,az,q) \ + x [2*(p) ] = ax [2*(q) ] ; \ + x [2*(p)+1] = ax [2*(q)+1] + +#define C_ASSIGN_REAL(x,p,ax,q) x [2*(p)] = ax [2*(q)] + +#define C_ASSIGN_CONJ(x,z,p,ax,az,q) \ + x [2*(p) ] = ax [2*(q) ] ; \ + x [2*(p)+1] = -ax [2*(q)+1] + +#define C_XTYPE_OK(type) ((type) == CHOLMOD_COMPLEX) + +#define C_IS_NONZERO(ax,az,q) \ + (IS_NONZERO (ax [2*(q)]) || IS_NONZERO (ax [2*(q)+1])) + +#define C_IS_ZERO(ax,az,q) \ + (IS_ZERO (ax [2*(q)]) && IS_ZERO (ax [2*(q)+1])) + +#define C_IS_ONE(ax,az,q) \ + ((ax [2*(q)] == 1) && IS_ZERO (ax [2*(q)+1])) + +#define C_IMAG_IS_NONZERO(ax,az,q) (IS_NONZERO (ax [2*(q)+1])) + +#define C_MULT(x,z,p, ax,az,q, bx,bz,r) \ +x [2*(p) ] = ax [2*(q) ] * bx [2*(r)] - ax [2*(q)+1] * bx [2*(r)+1] ; \ +x [2*(p)+1] = ax [2*(q)+1] * bx [2*(r)] + ax [2*(q) ] * bx [2*(r)+1] + +#define C_MULTADD(x,z,p, ax,az,q, bx,bz,r) \ +x [2*(p) ] += ax [2*(q) ] * bx [2*(r)] - ax [2*(q)+1] * bx [2*(r)+1] ; \ +x [2*(p)+1] += ax [2*(q)+1] * bx [2*(r)] + ax [2*(q) ] * bx [2*(r)+1] + +#define C_MULTSUB(x,z,p, ax,az,q, bx,bz,r) \ +x [2*(p) ] -= ax [2*(q) ] * bx [2*(r)] - ax [2*(q)+1] * bx [2*(r)+1] ; \ +x [2*(p)+1] -= ax [2*(q)+1] * bx [2*(r)] + ax [2*(q) ] * bx [2*(r)+1] + +/* s += conj(a)*b */ +#define C_MULTADDCONJ(x,z,p, ax,az,q, bx,bz,r) \ +x [2*(p) ] += ax [2*(q) ] * bx [2*(r)] + ax [2*(q)+1] * bx [2*(r)+1] ; \ +x [2*(p)+1] += (-ax [2*(q)+1]) * bx [2*(r)] + ax [2*(q) ] * bx [2*(r)+1] + +/* s -= conj(a)*b */ +#define C_MULTSUBCONJ(x,z,p, ax,az,q, bx,bz,r) \ +x [2*(p) ] -= ax [2*(q) ] * bx [2*(r)] + ax [2*(q)+1] * bx [2*(r)+1] ; \ +x [2*(p)+1] -= (-ax [2*(q)+1]) * bx [2*(r)] + ax [2*(q) ] * bx [2*(r)+1] + +#define C_ADD(x,z,p, ax,az,q, bx,bz,r) \ + x [2*(p) ] = ax [2*(q) ] + bx [2*(r) ] ; \ + x [2*(p)+1] = ax [2*(q)+1] + bx [2*(r)+1] + +#define C_ADD_REAL(x,p, ax,q, bx,r) \ + x [2*(p)] = ax [2*(q)] + bx [2*(r)] + +#define C_CLEAR(x,z,p) \ + x [2*(p) ] = 0 ; \ + x [2*(p)+1] = 0 + +#define C_CLEAR_IMAG(x,z,p) \ + x [2*(p)+1] = 0 + +/* s = s / a */ +#define C_DIV(x,z,p,ax,az,q) \ + Common->complex_divide ( \ + x [2*(p)], x [2*(p)+1], \ + ax [2*(q)], ax [2*(q)+1], \ + &x [2*(p)], &x [2*(p)+1]) + +/* s -= conj(a)*a ; note that the result of conj(a)*a is real */ +#define C_LLDOT(x,p, ax,az,q) \ + x [2*(p)] -= ax [2*(q)] * ax [2*(q)] + ax [2*(q)+1] * ax [2*(q)+1] + +#define C_PRINT(k,x,z,p) PRK(k, ("(%24.16e,%24.16e)", x [2*(p)], x [2*(p)+1])) + +#define C_DIV_REAL(x,z,p, ax,az,q, bx,r) \ + x [2*(p) ] = ax [2*(q) ] / bx [2*(r)] ; \ + x [2*(p)+1] = ax [2*(q)+1] / bx [2*(r)] + +#define C_MULT_REAL(x,z,p, ax,az,q, bx,r) \ + x [2*(p) ] = ax [2*(q) ] * bx [2*(r)] ; \ + x [2*(p)+1] = ax [2*(q)+1] * bx [2*(r)] + +/* s -= conj(a)*a/t */ +#define C_LDLDOT(x,p, ax,az,q, bx,r) \ + x [2*(p)] -= (ax [2*(q)] * ax [2*(q)] + ax [2*(q)+1] * ax [2*(q)+1]) / bx[r] + +/* -------------------------------------------------------------------------- */ +/* zomplex */ +/* -------------------------------------------------------------------------- */ + +#define Z_TEMPLATE(name) z_ ## name +#define ZT_TEMPLATE(name) zt_ ## name + +#define Z_ASSEMBLE(x,z,p,ax,az,q) \ + x [p] += ax [q] ; \ + z [p] += az [q] + +#define Z_ASSIGN(x,z,p,ax,az,q) \ + x [p] = ax [q] ; \ + z [p] = az [q] + +#define Z_ASSIGN_REAL(x,p,ax,q) x [p] = ax [q] + +#define Z_ASSIGN_CONJ(x,z,p,ax,az,q) \ + x [p] = ax [q] ; \ + z [p] = -az [q] + +#define Z_XTYPE_OK(type) ((type) == CHOLMOD_ZOMPLEX) + +#define Z_IS_NONZERO(ax,az,q) \ + (IS_NONZERO (ax [q]) || IS_NONZERO (az [q])) + +#define Z_IS_ZERO(ax,az,q) \ + (IS_ZERO (ax [q]) && IS_ZERO (az [q])) + +#define Z_IS_ONE(ax,az,q) \ + ((ax [q] == 1) && IS_ZERO (az [q])) + +#define Z_IMAG_IS_NONZERO(ax,az,q) (IS_NONZERO (az [q])) + +#define Z_MULT(x,z,p, ax,az,q, bx,bz,r) \ + x [p] = ax [q] * bx [r] - az [q] * bz [r] ; \ + z [p] = az [q] * bx [r] + ax [q] * bz [r] + +#define Z_MULTADD(x,z,p, ax,az,q, bx,bz,r) \ + x [p] += ax [q] * bx [r] - az [q] * bz [r] ; \ + z [p] += az [q] * bx [r] + ax [q] * bz [r] + +#define Z_MULTSUB(x,z,p, ax,az,q, bx,bz,r) \ + x [p] -= ax [q] * bx [r] - az [q] * bz [r] ; \ + z [p] -= az [q] * bx [r] + ax [q] * bz [r] + +#define Z_MULTADDCONJ(x,z,p, ax,az,q, bx,bz,r) \ + x [p] += ax [q] * bx [r] + az [q] * bz [r] ; \ + z [p] += (-az [q]) * bx [r] + ax [q] * bz [r] + +#define Z_MULTSUBCONJ(x,z,p, ax,az,q, bx,bz,r) \ + x [p] -= ax [q] * bx [r] + az [q] * bz [r] ; \ + z [p] -= (-az [q]) * bx [r] + ax [q] * bz [r] + +#define Z_ADD(x,z,p, ax,az,q, bx,bz,r) \ + x [p] = ax [q] + bx [r] ; \ + z [p] = az [q] + bz [r] + +#define Z_ADD_REAL(x,p, ax,q, bx,r) \ + x [p] = ax [q] + bx [r] + +#define Z_CLEAR(x,z,p) \ + x [p] = 0 ; \ + z [p] = 0 + +#define Z_CLEAR_IMAG(x,z,p) \ + z [p] = 0 + +/* s = s/a */ +#define Z_DIV(x,z,p,ax,az,q) \ + Common->complex_divide (x [p], z [p], ax [q], az [q], &x [p], &z [p]) + +/* s -= conj(a)*a ; note that the result of conj(a)*a is real */ +#define Z_LLDOT(x,p, ax,az,q) \ + x [p] -= ax [q] * ax [q] + az [q] * az [q] + +#define Z_PRINT(k,x,z,p) PRK(k, ("(%24.16e,%24.16e)", x [p], z [p])) + +#define Z_DIV_REAL(x,z,p, ax,az,q, bx,r) \ + x [p] = ax [q] / bx [r] ; \ + z [p] = az [q] / bx [r] + +#define Z_MULT_REAL(x,z,p, ax,az,q, bx,r) \ + x [p] = ax [q] * bx [r] ; \ + z [p] = az [q] * bx [r] + +/* s -= conj(a)*a/t */ +#define Z_LDLDOT(x,p, ax,az,q, bx,r) \ + x [p] -= (ax [q] * ax [q] + az [q] * az [q]) / bx[r] + +/* -------------------------------------------------------------------------- */ +/* all classes */ +/* -------------------------------------------------------------------------- */ + +/* Check if A->xtype and the two arrays A->x and A->z are valid. Set status to + * invalid, unless status is already "out of memory". A can be a sparse matrix, + * dense matrix, factor, or triplet. */ + +#define RETURN_IF_XTYPE_INVALID(A,xtype1,xtype2,result) \ +{ \ + if ((A)->xtype < (xtype1) || (A)->xtype > (xtype2) || \ + ((A)->xtype != CHOLMOD_PATTERN && ((A)->x) == NULL) || \ + ((A)->xtype == CHOLMOD_ZOMPLEX && ((A)->z) == NULL)) \ + { \ + if (Common->status != CHOLMOD_OUT_OF_MEMORY) \ + { \ + ERROR (CHOLMOD_INVALID, "invalid xtype") ; \ + } \ + return (result) ; \ + } \ +} diff --git a/src/CHOLMOD/Include/cholmod_config.h b/src/CHOLMOD/Include/cholmod_config.h new file mode 100644 index 0000000..5ada402 --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_config.h @@ -0,0 +1,85 @@ +/* ========================================================================== */ +/* === Include/cholmod_config.h ============================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_config.h. + * Copyright (C) 2005-2013, Univ. of Florida. Author: Timothy A. Davis + * CHOLMOD/Include/cholmod_config.h is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD configuration file, for inclusion in user programs. + * + * You do not have to edit any CHOLMOD files to compile and install CHOLMOD. + * However, if you do not use all of CHOLMOD's modules, you need to compile + * with the appropriate flag, or edit this file to add the appropriate #define. + * + * If you wish to use CHOLMOD under the GNU LGPL license only, then you must + * compile CHOLMOD with -DNMATRIXOPS -DNSUPERNODAL and -DNMODIFY. This can + * be done using just -DNGPL. + * + * Compiler flags for CHOLMOD: + * + * -DNCHECK do not include the Check module. License: GNU LGPL + * -DNCHOLESKY do not include the Cholesky module. License: GNU LGPL + * -DNPARTITION do not include the Partition module. License: GNU LGPL + * -DNCAMD do not include the interfaces to CAMD, + * CCOLAMD, CSYMAND in Partition module. License: GNU LGPL + * + * -DNGPL do not include any GNU GPL Modules in the CHOLMOD library. + * -DNMATRIXOPS do not include the MatrixOps module. License: GNU GPL + * -DNMODIFY do not include the Modify module. License: GNU GPL + * -DNSUPERNODAL do not include the Supernodal module. License: GNU GPL + * + * -DNPRINT do not print anything + * + * -D'LONGBLAS=long' or -DLONGBLAS='long long' defines the integers used by + * LAPACK and the BLAS. Use LONGBLAS=long on Solaris to use + * the 64-bit Sun Performance BLAS in cholmod_l_* routines. + * You may need to use -D'LONGBLAS=long long' on the SGI + * (this is not tested). + * + * -DNSUNPERF for Solaris only. If defined, do not use the Sun + * Performance Library. The default is to use SunPerf. + * You must compile CHOLMOD with -xlic_lib=sunperf. + * + * The Core Module (License GNU LGPL) is always included in the CHOLMOD library. + */ + +#ifndef CHOLMOD_CONFIG_H +#define CHOLMOD_CONFIG_H + +/* Use the compiler flag, or uncomment the definition(s), if you want to use + * one or more non-default installation options: */ + +/* +#define NCHECK +#define NCHOLESKY +#define NCAMD +#define NPARTITION + +#define NGPL +#define NMATRIXOPS +#define NMODIFY +#define NSUPERNODAL + +#define NPRINT + +#define LONGBLAS long +#define LONGBLAS long long +#define NSUNPERF +*/ + +/* -------------------------------------------------------------------------- */ +/* if NGPL is defined, disable all GNU GPL Modules */ +/* -------------------------------------------------------------------------- */ + +#ifdef NGPL +#define NMATRIXOPS +#define NMODIFY +#define NSUPERNODAL +#endif + +#endif diff --git a/src/CHOLMOD/Include/cholmod_core.h b/src/CHOLMOD/Include/cholmod_core.h new file mode 100644 index 0000000..a435e5f --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_core.h @@ -0,0 +1,2395 @@ +/* ========================================================================== */ +/* === Include/cholmod_core.h =============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_core.h. + * Copyright (C) 2005-2013, Univ. of Florida. Author: Timothy A. Davis + * CHOLMOD/Include/cholmod_core.h is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD Core module: basic CHOLMOD objects and routines. + * Required by all CHOLMOD modules. Requires no other module or package. + * + * The CHOLMOD modules are: + * + * Core basic data structures and definitions + * Check check/print the 5 CHOLMOD objects, & 3 types of integer vectors + * Cholesky sparse Cholesky factorization + * Modify sparse Cholesky update/downdate/row-add/row-delete + * MatrixOps sparse matrix functions (add, multiply, norm, ...) + * Supernodal supernodal sparse Cholesky factorization + * Partition graph-partitioning based orderings + * + * The CHOLMOD objects: + * -------------------- + * + * cholmod_common parameters, statistics, and workspace + * cholmod_sparse a sparse matrix in compressed column form + * cholmod_factor an LL' or LDL' factorization + * cholmod_dense a dense matrix + * cholmod_triplet a sparse matrix in "triplet" form + * + * The Core module described here defines the CHOLMOD data structures, and + * basic operations on them. To create and solve a sparse linear system Ax=b, + * the user must create A and b, populate them with values, and then pass them + * to the routines in the CHOLMOD Cholesky module. There are two primary + * methods for creating A: (1) allocate space for a column-oriented sparse + * matrix and fill it with pattern and values, or (2) create a triplet form + * matrix and convert it to a sparse matrix. The latter option is simpler. + * + * The matrices b and x are typically dense matrices, but can also be sparse. + * You can allocate and free them as dense matrices with the + * cholmod_allocate_dense and cholmod_free_dense routines. + * + * The cholmod_factor object contains the symbolic and numeric LL' or LDL' + * factorization of sparse symmetric matrix. The matrix must be positive + * definite for an LL' factorization. It need only be symmetric and have well- + * conditioned leading submatrices for it to have an LDL' factorization + * (CHOLMOD does not pivot for numerical stability). It is typically created + * with the cholmod_factorize routine in the Cholesky module, but can also + * be initialized to L=D=I in the Core module and then modified by the Modify + * module. It must be freed with cholmod_free_factor, defined below. + * + * The Core routines for each object are described below. Each list is split + * into two parts: the primary routines and secondary routines. + * + * ============================================================================ + * === cholmod_common ========================================================= + * ============================================================================ + * + * The Common object contains control parameters, statistics, and + * You must call cholmod_start before calling any other CHOLMOD routine, and + * must call cholmod_finish as your last call to CHOLMOD, with two exceptions: + * you may call cholmod_print_common and cholmod_check_common in the Check + * module after calling cholmod_finish. + * + * cholmod_start first call to CHOLMOD + * cholmod_finish last call to CHOLMOD + * ----------------------------- + * cholmod_defaults restore default parameters + * cholmod_maxrank maximum rank for update/downdate + * cholmod_allocate_work allocate workspace in Common + * cholmod_free_work free workspace in Common + * cholmod_clear_flag clear Flag workspace in Common + * cholmod_error called when CHOLMOD encounters an error + * cholmod_dbound for internal use in CHOLMOD only + * cholmod_hypot compute sqrt (x*x + y*y) accurately + * cholmod_divcomplex complex division, c = a/b + * + * ============================================================================ + * === cholmod_sparse ========================================================= + * ============================================================================ + * + * A sparse matrix is held in compressed column form. In the basic type + * ("packed", which corresponds to a MATLAB sparse matrix), an n-by-n matrix + * with nz entries is held in three arrays: p of size n+1, i of size nz, and x + * of size nz. Row indices of column j are held in i [p [j] ... p [j+1]-1] and + * in the same locations in x. There may be no duplicate entries in a column. + * Row indices in each column may be sorted or unsorted (CHOLMOD keeps track). + * A->stype determines the storage mode: 0 if both upper/lower parts are stored, + * -1 if A is symmetric and just tril(A) is stored, +1 if symmetric and triu(A) + * is stored. + * + * cholmod_allocate_sparse allocate a sparse matrix + * cholmod_free_sparse free a sparse matrix + * ----------------------------- + * cholmod_reallocate_sparse change the size (# entries) of sparse matrix + * cholmod_nnz number of nonzeros in a sparse matrix + * cholmod_speye sparse identity matrix + * cholmod_spzeros sparse zero matrix + * cholmod_transpose transpose a sparse matrix + * cholmod_ptranspose transpose/permute a sparse matrix + * cholmod_transpose_unsym transpose/permute an unsymmetric sparse matrix + * cholmod_transpose_sym transpose/permute a symmetric sparse matrix + * cholmod_sort sort row indices in each column of sparse matrix + * cholmod_band C = tril (triu (A,k1), k2) + * cholmod_band_inplace A = tril (triu (A,k1), k2) + * cholmod_aat C = A*A' + * cholmod_copy_sparse C = A, create an exact copy of a sparse matrix + * cholmod_copy C = A, with possible change of stype + * cholmod_add C = alpha*A + beta*B + * cholmod_sparse_xtype change the xtype of a sparse matrix + * + * ============================================================================ + * === cholmod_factor ========================================================= + * ============================================================================ + * + * The data structure for an LL' or LDL' factorization is too complex to + * describe in one sentence. This object can hold the symbolic analysis alone, + * or in combination with a "simplicial" (similar to a sparse matrix) or + * "supernodal" form of the numerical factorization. Only the routine to free + * a factor is primary, since a factor object is created by the factorization + * routine (cholmod_factorize). It must be freed with cholmod_free_factor. + * + * cholmod_free_factor free a factor + * ----------------------------- + * cholmod_allocate_factor allocate a factor (LL' or LDL') + * cholmod_reallocate_factor change the # entries in a factor + * cholmod_change_factor change the type of factor (e.g., LDL' to LL') + * cholmod_pack_factor pack the columns of a factor + * cholmod_reallocate_column resize a single column of a factor + * cholmod_factor_to_sparse create a sparse matrix copy of a factor + * cholmod_copy_factor create a copy of a factor + * cholmod_factor_xtype change the xtype of a factor + * + * Note that there is no cholmod_sparse_to_factor routine to create a factor + * as a copy of a sparse matrix. It could be done, after a fashion, but a + * lower triangular sparse matrix would not necessarily have a chordal graph, + * which would break the many CHOLMOD routines that rely on this property. + * + * ============================================================================ + * === cholmod_dense ========================================================== + * ============================================================================ + * + * The solve routines and some of the MatrixOps and Modify routines use dense + * matrices as inputs. These are held in column-major order. With a leading + * dimension of d, the entry in row i and column j is held in x [i+j*d]. + * + * cholmod_allocate_dense allocate a dense matrix + * cholmod_free_dense free a dense matrix + * ----------------------------- + * cholmod_zeros allocate a dense matrix of all zeros + * cholmod_ones allocate a dense matrix of all ones + * cholmod_eye allocate a dense identity matrix + * cholmod_sparse_to_dense create a dense matrix copy of a sparse matrix + * cholmod_dense_to_sparse create a sparse matrix copy of a dense matrix + * cholmod_copy_dense create a copy of a dense matrix + * cholmod_copy_dense2 copy a dense matrix (pre-allocated) + * cholmod_dense_xtype change the xtype of a dense matrix + * cholmod_ensure_dense ensure a dense matrix has a given size and type + * + * ============================================================================ + * === cholmod_triplet ======================================================== + * ============================================================================ + * + * A sparse matrix held in triplet form is the simplest one for a user to + * create. It consists of a list of nz entries in arbitrary order, held in + * three arrays: i, j, and x, each of length nk. The kth entry is in row i[k], + * column j[k], with value x[k]. There may be duplicate values; if A(i,j) + * appears more than once, its value is the sum of the entries with those row + * and column indices. + * + * cholmod_allocate_triplet allocate a triplet matrix + * cholmod_triplet_to_sparse create a sparse matrix copy of a triplet matrix + * cholmod_free_triplet free a triplet matrix + * ----------------------------- + * cholmod_reallocate_triplet change the # of entries in a triplet matrix + * cholmod_sparse_to_triplet create a triplet matrix copy of a sparse matrix + * cholmod_copy_triplet create a copy of a triplet matrix + * cholmod_triplet_xtype change the xtype of a triplet matrix + * + * ============================================================================ + * === memory management ====================================================== + * ============================================================================ + * + * cholmod_malloc malloc wrapper + * cholmod_calloc calloc wrapper + * cholmod_free free wrapper + * cholmod_realloc realloc wrapper + * cholmod_realloc_multiple realloc wrapper for multiple objects + * + * ============================================================================ + * === Core CHOLMOD prototypes ================================================ + * ============================================================================ + * + * All CHOLMOD routines (in all modules) use the following protocol for return + * values, with one exception: + * + * int TRUE (1) if successful, or FALSE (0) otherwise. + * (exception: cholmod_divcomplex) + * SuiteSparse_long a value >= 0 if successful, or -1 otherwise. + * double a value >= 0 if successful, or -1 otherwise. + * size_t a value > 0 if successful, or 0 otherwise. + * void * a non-NULL pointer to newly allocated memory if + * successful, or NULL otherwise. + * cholmod_sparse * a non-NULL pointer to a newly allocated matrix + * if successful, or NULL otherwise. + * cholmod_factor * a non-NULL pointer to a newly allocated factor + * if successful, or NULL otherwise. + * cholmod_triplet * a non-NULL pointer to a newly allocated triplet + * matrix if successful, or NULL otherwise. + * cholmod_dense * a non-NULL pointer to a newly allocated triplet + * matrix if successful, or NULL otherwise. + * + * The last parameter to all routines is always a pointer to the CHOLMOD + * Common object. + * + * TRUE and FALSE are not defined here, since they may conflict with the user + * program. A routine that described here returning TRUE or FALSE returns 1 + * or 0, respectively. Any TRUE/FALSE parameter is true if nonzero, false if + * zero. + */ + +#ifndef CHOLMOD_CORE_H +#define CHOLMOD_CORE_H + +/* ========================================================================== */ +/* === CHOLMOD version ====================================================== */ +/* ========================================================================== */ + +/* All versions of CHOLMOD will include the following definitions. + * As an example, to test if the version you are using is 1.3 or later: + * + * if (CHOLMOD_VERSION >= CHOLMOD_VER_CODE (1,3)) ... + * + * This also works during compile-time: + * + * #if CHOLMOD_VERSION >= CHOLMOD_VER_CODE (1,3) + * printf ("This is version 1.3 or later\n") ; + * #else + * printf ("This is version is earlier than 1.3\n") ; + * #endif + */ + +#define CHOLMOD_HAS_VERSION_FUNCTION + +#define CHOLMOD_DATE "April 25, 2013" +#define CHOLMOD_VER_CODE(main,sub) ((main) * 1000 + (sub)) +#define CHOLMOD_MAIN_VERSION 2 +#define CHOLMOD_SUB_VERSION 1 +#define CHOLMOD_SUBSUB_VERSION 2 +#define CHOLMOD_VERSION \ + CHOLMOD_VER_CODE(CHOLMOD_MAIN_VERSION,CHOLMOD_SUB_VERSION) + + +/* ========================================================================== */ +/* === non-CHOLMOD include files ============================================ */ +/* ========================================================================== */ + +/* This is the only non-CHOLMOD include file imposed on the user program. + * It required for size_t definition used here. CHOLMOD itself includes other + * ANSI C89 standard #include files, but does not expose them to the user. + * + * CHOLMOD assumes that your C compiler is ANSI C89 compliant. It does not make + * use of ANSI C99 features. + */ + +#include +#include + +/* ========================================================================== */ +/* === CUDA BLAS for the GPU ================================================ */ +/* ========================================================================== */ + +#ifdef GPU_BLAS +#include +#include +#endif + + +/* ========================================================================== */ +/* === CHOLMOD objects ====================================================== */ +/* ========================================================================== */ + +/* Each CHOLMOD object has its own type code. */ + +#define CHOLMOD_COMMON 0 +#define CHOLMOD_SPARSE 1 +#define CHOLMOD_FACTOR 2 +#define CHOLMOD_DENSE 3 +#define CHOLMOD_TRIPLET 4 + +/* ========================================================================== */ +/* === CHOLMOD Common ======================================================= */ +/* ========================================================================== */ + +/* itype defines the types of integer used: */ +#define CHOLMOD_INT 0 /* all integer arrays are int */ +#define CHOLMOD_INTLONG 1 /* most are int, some are SuiteSparse_long */ +#define CHOLMOD_LONG 2 /* all integer arrays are SuiteSparse_long */ + +/* The itype of all parameters for all CHOLMOD routines must match. + * FUTURE WORK: CHOLMOD_INTLONG is not yet supported. + */ + +/* dtype defines what the numerical type is (double or float): */ +#define CHOLMOD_DOUBLE 0 /* all numerical values are double */ +#define CHOLMOD_SINGLE 1 /* all numerical values are float */ + +/* The dtype of all parameters for all CHOLMOD routines must match. + * + * Scalar floating-point values are always passed as double arrays of size 2 + * (for the real and imaginary parts). They are typecast to float as needed. + * FUTURE WORK: the float case is not supported yet. + */ + +/* xtype defines the kind of numerical values used: */ +#define CHOLMOD_PATTERN 0 /* pattern only, no numerical values */ +#define CHOLMOD_REAL 1 /* a real matrix */ +#define CHOLMOD_COMPLEX 2 /* a complex matrix (ANSI C99 compatible) */ +#define CHOLMOD_ZOMPLEX 3 /* a complex matrix (MATLAB compatible) */ + +/* The xtype of all parameters for all CHOLMOD routines must match. + * + * CHOLMOD_PATTERN: x and z are ignored. + * CHOLMOD_DOUBLE: x is non-null of size nzmax, z is ignored. + * CHOLMOD_COMPLEX: x is non-null of size 2*nzmax doubles, z is ignored. + * CHOLMOD_ZOMPLEX: x and z are non-null of size nzmax + * + * In the real case, z is ignored. The kth entry in the matrix is x [k]. + * There are two methods for the complex case. In the ANSI C99-compatible + * CHOLMOD_COMPLEX case, the real and imaginary parts of the kth entry + * are in x [2*k] and x [2*k+1], respectively. z is ignored. In the + * MATLAB-compatible CHOLMOD_ZOMPLEX case, the real and imaginary + * parts of the kth entry are in x [k] and z [k]. + * + * Scalar floating-point values are always passed as double arrays of size 2 + * (real and imaginary parts). The imaginary part of a scalar is ignored if + * the routine operates on a real matrix. + * + * These Modules support complex and zomplex matrices, with a few exceptions: + * + * Check all routines + * Cholesky all routines + * Core all except cholmod_aat, add, band, copy + * Demo all routines + * Partition all routines + * Supernodal all routines support any real, complex, or zomplex input. + * There will never be a supernodal zomplex L; a complex + * supernodal L is created if A is zomplex. + * Tcov all routines + * Valgrind all routines + * + * These Modules provide partial support for complex and zomplex matrices: + * + * MATLAB all routines support real and zomplex only, not complex, + * with the exception of ldlupdate, which supports + * real matrices only. This is a minor constraint since + * MATLAB's matrices are all real or zomplex. + * MatrixOps only norm_dense, norm_sparse, and sdmult support complex + * and zomplex + * + * These Modules do not support complex and zomplex matrices at all: + * + * Modify all routines support real matrices only + */ + +/* Definitions for cholmod_common: */ +#define CHOLMOD_MAXMETHODS 9 /* maximum number of different methods that */ + /* cholmod_analyze can try. Must be >= 9. */ + +/* Common->status values. zero means success, negative means a fatal error, + * positive is a warning. */ +#define CHOLMOD_OK 0 /* success */ +#define CHOLMOD_NOT_INSTALLED (-1) /* failure: method not installed */ +#define CHOLMOD_OUT_OF_MEMORY (-2) /* failure: out of memory */ +#define CHOLMOD_TOO_LARGE (-3) /* failure: integer overflow occured */ +#define CHOLMOD_INVALID (-4) /* failure: invalid input */ +#define CHOLMOD_GPU_PROBLEM (-5) /* failure: GPU fatal error */ +#define CHOLMOD_NOT_POSDEF (1) /* warning: matrix not pos. def. */ +#define CHOLMOD_DSMALL (2) /* warning: D for LDL' or diag(L) or */ + /* LL' has tiny absolute value */ + +/* ordering method (also used for L->ordering) */ +#define CHOLMOD_NATURAL 0 /* use natural ordering */ +#define CHOLMOD_GIVEN 1 /* use given permutation */ +#define CHOLMOD_AMD 2 /* use minimum degree (AMD) */ +#define CHOLMOD_METIS 3 /* use METIS' nested dissection */ +#define CHOLMOD_NESDIS 4 /* use CHOLMOD's version of nested dissection:*/ + /* node bisector applied recursively, followed + * by constrained minimum degree (CSYMAMD or + * CCOLAMD) */ +#define CHOLMOD_COLAMD 5 /* use AMD for A, COLAMD for A*A' */ + +/* POSTORDERED is not a method, but a result of natural ordering followed by a + * weighted postorder. It is used for L->ordering, not method [ ].ordering. */ +#define CHOLMOD_POSTORDERED 6 /* natural ordering, postordered. */ + +/* supernodal strategy (for Common->supernodal) */ +#define CHOLMOD_SIMPLICIAL 0 /* always do simplicial */ +#define CHOLMOD_AUTO 1 /* select simpl/super depending on matrix */ +#define CHOLMOD_SUPERNODAL 2 /* always do supernodal */ + +typedef struct cholmod_common_struct +{ + /* ---------------------------------------------------------------------- */ + /* parameters for symbolic/numeric factorization and update/downdate */ + /* ---------------------------------------------------------------------- */ + + double dbound ; /* Smallest absolute value of diagonal entries of D + * for LDL' factorization and update/downdate/rowadd/ + * rowdel, or the diagonal of L for an LL' factorization. + * Entries in the range 0 to dbound are replaced with dbound. + * Entries in the range -dbound to 0 are replaced with -dbound. No + * changes are made to the diagonal if dbound <= 0. Default: zero */ + + double grow0 ; /* For a simplicial factorization, L->i and L->x can + * grow if necessary. grow0 is the factor by which + * it grows. For the initial space, L is of size MAX (1,grow0) times + * the required space. If L runs out of space, the new size of L is + * MAX(1.2,grow0) times the new required space. If you do not plan on + * modifying the LDL' factorization in the Modify module, set grow0 to + * zero (or set grow2 to 0, see below). Default: 1.2 */ + + double grow1 ; + + size_t grow2 ; /* For a simplicial factorization, each column j of L + * is initialized with space equal to + * grow1*L->ColCount[j] + grow2. If grow0 < 1, grow1 < 1, or grow2 == 0, + * then the space allocated is exactly equal to L->ColCount[j]. If the + * column j runs out of space, it increases to grow1*need + grow2 in + * size, where need is the total # of nonzeros in that column. If you do + * not plan on modifying the factorization in the Modify module, set + * grow2 to zero. Default: grow1 = 1.2, grow2 = 5. */ + + size_t maxrank ; /* rank of maximum update/downdate. Valid values: + * 2, 4, or 8. A value < 2 is set to 2, and a + * value > 8 is set to 8. It is then rounded up to the next highest + * power of 2, if not already a power of 2. Workspace (Xwork, below) of + * size nrow-by-maxrank double's is allocated for the update/downdate. + * If an update/downdate of rank-k is requested, with k > maxrank, + * it is done in steps of maxrank. Default: 8, which is fastest. + * Memory usage can be reduced by setting maxrank to 2 or 4. + */ + + double supernodal_switch ; /* supernodal vs simplicial factorization */ + int supernodal ; /* If Common->supernodal <= CHOLMOD_SIMPLICIAL + * (0) then cholmod_analyze performs a + * simplicial analysis. If >= CHOLMOD_SUPERNODAL (2), then a supernodal + * analysis is performed. If == CHOLMOD_AUTO (1) and + * flop/nnz(L) < Common->supernodal_switch, then a simplicial analysis + * is done. A supernodal analysis done otherwise. + * Default: CHOLMOD_AUTO. Default supernodal_switch = 40 */ + + int final_asis ; /* If TRUE, then ignore the other final_* parameters + * (except for final_pack). + * The factor is left as-is when done. Default: TRUE.*/ + + int final_super ; /* If TRUE, leave a factor in supernodal form when + * supernodal factorization is finished. If FALSE, + * then convert to a simplicial factor when done. + * Default: TRUE */ + + int final_ll ; /* If TRUE, leave factor in LL' form when done. + * Otherwise, leave in LDL' form. Default: FALSE */ + + int final_pack ; /* If TRUE, pack the columns when done. If TRUE, and + * cholmod_factorize is called with a symbolic L, L is + * allocated with exactly the space required, using L->ColCount. If you + * plan on modifying the factorization, set Common->final_pack to FALSE, + * and each column will be given a little extra slack space for future + * growth in fill-in due to updates. Default: TRUE */ + + int final_monotonic ; /* If TRUE, ensure columns are monotonic when done. + * Default: TRUE */ + + int final_resymbol ;/* if cholmod_factorize performed a supernodal + * factorization, final_resymbol is true, and + * final_super is FALSE (convert a simplicial numeric factorization), + * then numerically zero entries that resulted from relaxed supernodal + * amalgamation are removed. This does not remove entries that are zero + * due to exact numeric cancellation, since doing so would break the + * update/downdate rowadd/rowdel routines. Default: FALSE. */ + + /* supernodal relaxed amalgamation parameters: */ + double zrelax [3] ; + size_t nrelax [3] ; + + /* Let ns be the total number of columns in two adjacent supernodes. + * Let z be the fraction of zero entries in the two supernodes if they + * are merged (z includes zero entries from prior amalgamations). The + * two supernodes are merged if: + * (ns <= nrelax [0]) || (no new zero entries added) || + * (ns <= nrelax [1] && z < zrelax [0]) || + * (ns <= nrelax [2] && z < zrelax [1]) || (z < zrelax [2]) + * + * Default parameters result in the following rule: + * (ns <= 4) || (no new zero entries added) || + * (ns <= 16 && z < 0.8) || (ns <= 48 && z < 0.1) || (z < 0.05) + */ + + int prefer_zomplex ; /* X = cholmod_solve (sys, L, B, Common) computes + * x=A\b or solves a related system. If L and B are + * both real, then X is real. Otherwise, X is returned as + * CHOLMOD_COMPLEX if Common->prefer_zomplex is FALSE, or + * CHOLMOD_ZOMPLEX if Common->prefer_zomplex is TRUE. This parameter + * is needed because there is no supernodal zomplex L. Suppose the + * caller wants all complex matrices to be stored in zomplex form + * (MATLAB, for example). A supernodal L is returned in complex form + * if A is zomplex. B can be real, and thus X = cholmod_solve (L,B) + * should return X as zomplex. This cannot be inferred from the input + * arguments L and B. Default: FALSE, since all data types are + * supported in CHOLMOD_COMPLEX form and since this is the native type + * of LAPACK and the BLAS. Note that the MATLAB/cholmod.c mexFunction + * sets this parameter to TRUE, since MATLAB matrices are in + * CHOLMOD_ZOMPLEX form. + */ + + int prefer_upper ; /* cholmod_analyze and cholmod_factorize work + * fastest when a symmetric matrix is stored in + * upper triangular form when a fill-reducing ordering is used. In + * MATLAB, this corresponds to how x=A\b works. When the matrix is + * ordered as-is, they work fastest when a symmetric matrix is in lower + * triangular form. In MATLAB, R=chol(A) does the opposite. This + * parameter affects only how cholmod_read returns a symmetric matrix. + * If TRUE (the default case), a symmetric matrix is always returned in + * upper-triangular form (A->stype = 1). */ + + int quick_return_if_not_posdef ; /* if TRUE, the supernodal numeric + * factorization will return quickly if + * the matrix is not positive definite. Default: FALSE. */ + + /* ---------------------------------------------------------------------- */ + /* printing and error handling options */ + /* ---------------------------------------------------------------------- */ + + int print ; /* print level. Default: 3 */ + int precise ; /* if TRUE, print 16 digits. Otherwise print 5 */ + int (*print_function) (const char *, ...) ; /* pointer to printf */ + + int try_catch ; /* if TRUE, then ignore errors; CHOLMOD is in the middle + * of a try/catch block. No error message is printed + * and the Common->error_handler function is not called. */ + + void (*error_handler) (int status, const char *file, + int line, const char *message) ; + + /* Common->error_handler is the user's error handling routine. If not + * NULL, this routine is called if an error occurs in CHOLMOD. status + * can be CHOLMOD_OK (0), negative for a fatal error, and positive for + * a warning. file is a string containing the name of the source code + * file where the error occured, and line is the line number in that + * file. message is a string describing the error in more detail. */ + + /* ---------------------------------------------------------------------- */ + /* ordering options */ + /* ---------------------------------------------------------------------- */ + + /* The cholmod_analyze routine can try many different orderings and select + * the best one. It can also try one ordering method multiple times, with + * different parameter settings. The default is to use three orderings, + * the user's permutation (if provided), AMD which is the fastest ordering + * and generally gives good fill-in, and METIS. CHOLMOD's nested dissection + * (METIS with a constrained AMD) usually gives a better ordering than METIS + * alone (by about 5% to 10%) but it takes more time. + * + * If you know the method that is best for your matrix, set Common->nmethods + * to 1 and set Common->method [0] to the set of parameters for that method. + * If you set it to 1 and do not provide a permutation, then only AMD will + * be called. + * + * If METIS is not available, the default # of methods tried is 2 (the user + * permutation, if any, and AMD). + * + * To try other methods, set Common->nmethods to the number of methods you + * want to try. The suite of default methods and their parameters is + * described in the cholmod_defaults routine, and summarized here: + * + * Common->method [i]: + * i = 0: user-provided ordering (cholmod_analyze_p only) + * i = 1: AMD (for both A and A*A') + * i = 2: METIS + * i = 3: CHOLMOD's nested dissection (NESDIS), default parameters + * i = 4: natural + * i = 5: NESDIS with nd_small = 20000 + * i = 6: NESDIS with nd_small = 4, no constrained minimum degree + * i = 7: NESDIS with no dense node removal + * i = 8: AMD for A, COLAMD for A*A' + * + * You can modify the suite of methods you wish to try by modifying + * Common.method [...] after calling cholmod_start or cholmod_defaults. + * + * For example, to use AMD, followed by a weighted postordering: + * + * Common->nmethods = 1 ; + * Common->method [0].ordering = CHOLMOD_AMD ; + * Common->postorder = TRUE ; + * + * To use the natural ordering (with no postordering): + * + * Common->nmethods = 1 ; + * Common->method [0].ordering = CHOLMOD_NATURAL ; + * Common->postorder = FALSE ; + * + * If you are going to factorize hundreds or more matrices with the same + * nonzero pattern, you may wish to spend a great deal of time finding a + * good permutation. In this case, try setting Common->nmethods to 9. + * The time spent in cholmod_analysis will be very high, but you need to + * call it only once. + * + * cholmod_analyze sets Common->current to a value between 0 and nmethods-1. + * Each ordering method uses the set of options defined by this parameter. + */ + + int nmethods ; /* The number of ordering methods to try. Default: 0. + * nmethods = 0 is a special case. cholmod_analyze + * will try the user-provided ordering (if given) and AMD. Let fl and + * lnz be the flop count and nonzeros in L from AMD's ordering. Let + * anz be the number of nonzeros in the upper or lower triangular part + * of the symmetric matrix A. If fl/lnz < 500 or lnz/anz < 5, then this + * is a good ordering, and METIS is not attempted. Otherwise, METIS is + * tried. The best ordering found is used. If nmethods > 0, the + * methods used are given in the method[ ] array, below. The first + * three methods in the default suite of orderings is (1) use the given + * permutation (if provided), (2) use AMD, and (3) use METIS. Maximum + * allowed value is CHOLMOD_MAXMETHODS. */ + + int current ; /* The current method being tried. Default: 0. Valid + * range is 0 to nmethods-1. */ + + int selected ; /* The best method found. */ + + /* The suite of ordering methods and parameters: */ + + struct cholmod_method_struct + { + /* statistics for this method */ + double lnz ; /* nnz(L) excl. zeros from supernodal amalgamation, + * for a "pure" L */ + + double fl ; /* flop count for a "pure", real simplicial LL' + * factorization, with no extra work due to + * amalgamation. Subtract n to get the LDL' flop count. Multiply + * by about 4 if the matrix is complex or zomplex. */ + + /* ordering method parameters */ + double prune_dense ;/* dense row/col control for AMD, SYMAMD, CSYMAMD, + * and NESDIS (cholmod_nested_dissection). For a + * symmetric n-by-n matrix, rows/columns with more than + * MAX (16, prune_dense * sqrt (n)) entries are removed prior to + * ordering. They appear at the end of the re-ordered matrix. + * + * If prune_dense < 0, only completely dense rows/cols are removed. + * + * This paramater is also the dense column control for COLAMD and + * CCOLAMD. For an m-by-n matrix, columns with more than + * MAX (16, prune_dense * sqrt (MIN (m,n))) entries are removed prior + * to ordering. They appear at the end of the re-ordered matrix. + * CHOLMOD factorizes A*A', so it calls COLAMD and CCOLAMD with A', + * not A. Thus, this parameter affects the dense *row* control for + * CHOLMOD's matrix, and the dense *column* control for COLAMD and + * CCOLAMD. + * + * Removing dense rows and columns improves the run-time of the + * ordering methods. It has some impact on ordering quality + * (usually minimal, sometimes good, sometimes bad). + * + * Default: 10. */ + + double prune_dense2 ;/* dense row control for COLAMD and CCOLAMD. + * Rows with more than MAX (16, dense2 * sqrt (n)) + * for an m-by-n matrix are removed prior to ordering. CHOLMOD's + * matrix is transposed before ordering it with COLAMD or CCOLAMD, + * so this controls the dense *columns* of CHOLMOD's matrix, and + * the dense *rows* of COLAMD's or CCOLAMD's matrix. + * + * If prune_dense2 < 0, only completely dense rows/cols are removed. + * + * Default: -1. Note that this is not the default for COLAMD and + * CCOLAMD. -1 is best for Cholesky. 10 is best for LU. */ + + double nd_oksep ; /* in NESDIS, when a node separator is computed, it + * discarded if nsep >= nd_oksep*n, where nsep is + * the number of nodes in the separator, and n is the size of the + * graph being cut. Valid range is 0 to 1. If 1 or greater, the + * separator is discarded if it consists of the entire graph. + * Default: 1 */ + + double other_1 [4] ; /* future expansion */ + + size_t nd_small ; /* do not partition graphs with fewer nodes than + * nd_small, in NESDIS. Default: 200 (same as + * METIS) */ + + size_t other_2 [4] ; /* future expansion */ + + int aggressive ; /* Aggresive absorption in AMD, COLAMD, SYMAMD, + * CCOLAMD, and CSYMAMD. Default: TRUE */ + + int order_for_lu ; /* CCOLAMD can be optimized to produce an ordering + * for LU or Cholesky factorization. CHOLMOD only + * performs a Cholesky factorization. However, you may wish to use + * CHOLMOD as an interface for CCOLAMD but use it for your own LU + * factorization. In this case, order_for_lu should be set to FALSE. + * When factorizing in CHOLMOD itself, you should *** NEVER *** set + * this parameter FALSE. Default: TRUE. */ + + int nd_compress ; /* If TRUE, compress the graph and subgraphs before + * partitioning them in NESDIS. Default: TRUE */ + + int nd_camd ; /* If 1, follow the nested dissection ordering + * with a constrained minimum degree ordering that + * respects the partitioning just found (using CAMD). If 2, use + * CSYMAMD instead. If you set nd_small very small, you may not need + * this ordering, and can save time by setting it to zero (no + * constrained minimum degree ordering). Default: 1. */ + + int nd_components ; /* The nested dissection ordering finds a node + * separator that splits the graph into two parts, + * which may be unconnected. If nd_components is TRUE, each of + * these connected components is split independently. If FALSE, + * each part is split as a whole, even if it consists of more than + * one connected component. Default: FALSE */ + + /* fill-reducing ordering to use */ + int ordering ; + + size_t other_3 [4] ; /* future expansion */ + + } method [CHOLMOD_MAXMETHODS + 1] ; + + int postorder ; /* If TRUE, cholmod_analyze follows the ordering with a + * weighted postorder of the elimination tree. Improves + * supernode amalgamation. Does not affect fundamental nnz(L) and + * flop count. Default: TRUE. */ + + /* ---------------------------------------------------------------------- */ + /* memory management routines */ + /* ---------------------------------------------------------------------- */ + + void *(*malloc_memory) (size_t) ; /* pointer to malloc */ + void *(*realloc_memory) (void *, size_t) ; /* pointer to realloc */ + void (*free_memory) (void *) ; /* pointer to free */ + void *(*calloc_memory) (size_t, size_t) ; /* pointer to calloc */ + + /* ---------------------------------------------------------------------- */ + /* routines for complex arithmetic */ + /* ---------------------------------------------------------------------- */ + + int (*complex_divide) (double ax, double az, double bx, double bz, + double *cx, double *cz) ; + + /* flag = complex_divide (ax, az, bx, bz, &cx, &cz) computes the complex + * division c = a/b, where ax and az hold the real and imaginary part + * of a, and b and c are stored similarly. flag is returned as 1 if + * a divide-by-zero occurs, or 0 otherwise. By default, the function + * pointer Common->complex_divide is set equal to cholmod_divcomplex. + */ + + double (*hypotenuse) (double x, double y) ; + + /* s = hypotenuse (x,y) computes s = sqrt (x*x + y*y), but does so more + * accurately. By default, the function pointer Common->hypotenuse is + * set equal to cholmod_hypot. See also the hypot function in the C99 + * standard, which has an identical syntax and function. If you have + * a C99-compliant compiler, you can set Common->hypotenuse = hypot. */ + + /* ---------------------------------------------------------------------- */ + /* METIS workarounds */ + /* ---------------------------------------------------------------------- */ + + double metis_memory ; /* This is a parameter for CHOLMOD's interface to + * METIS, not a parameter to METIS itself. METIS + * uses an amount of memory that is difficult to estimate precisely + * beforehand. If it runs out of memory, it terminates your program. + * All routines in CHOLMOD except for CHOLMOD's interface to METIS + * return an error status and safely return to your program if they run + * out of memory. To mitigate this problem, the CHOLMOD interface + * can allocate a single block of memory equal in size to an empirical + * upper bound of METIS's memory usage times the Common->metis_memory + * parameter, and then immediately free it. It then calls METIS. If + * this pre-allocation fails, it is possible that METIS will fail as + * well, and so CHOLMOD returns with an out-of-memory condition without + * calling METIS. + * + * METIS_NodeND (used in the CHOLMOD_METIS ordering option) with its + * default parameter settings typically uses about (4*nz+40n+4096) + * times sizeof(int) memory, where nz is equal to the number of entries + * in A for the symmetric case or AA' if an unsymmetric matrix is + * being ordered (where nz includes both the upper and lower parts + * of A or AA'). The observed "upper bound" (with 2 exceptions), + * measured in an instrumented copy of METIS 4.0.1 on thousands of + * matrices, is (10*nz+50*n+4096) * sizeof(int). Two large matrices + * exceeded this bound, one by almost a factor of 2 (Gupta/gupta2). + * + * If your program is terminated by METIS, try setting metis_memory to + * 2.0, or even higher if needed. By default, CHOLMOD assumes that METIS + * does not have this problem (so that CHOLMOD will work correctly when + * this issue is fixed in METIS). Thus, the default value is zero. + * This work-around is not guaranteed anyway. + * + * If a matrix exceeds this predicted memory usage, AMD is attempted + * instead. It, too, may run out of memory, but if it does so it will + * not terminate your program. + */ + + double metis_dswitch ; /* METIS_NodeND in METIS 4.0.1 gives a seg */ + size_t metis_nswitch ; /* fault with one matrix of order n = 3005 and + * nz = 6,036,025. This is a very dense graph. + * The workaround is to use AMD instead of METIS for matrices of dimension + * greater than Common->metis_nswitch (default 3000) or more and with + * density of Common->metis_dswitch (default 0.66) or more. + * cholmod_nested_dissection has no problems with the same matrix, even + * though it uses METIS_NodeComputeSeparator on this matrix. If this + * seg fault does not affect you, set metis_nswitch to zero or less, + * and CHOLMOD will not switch to AMD based just on the density of the + * matrix (it will still switch to AMD if the metis_memory parameter + * causes the switch). + */ + + /* ---------------------------------------------------------------------- */ + /* workspace */ + /* ---------------------------------------------------------------------- */ + + /* CHOLMOD has several routines that take less time than the size of + * workspace they require. Allocating and initializing the workspace would + * dominate the run time, unless workspace is allocated and initialized + * just once. CHOLMOD allocates this space when needed, and holds it here + * between calls to CHOLMOD. cholmod_start sets these pointers to NULL + * (which is why it must be the first routine called in CHOLMOD). + * cholmod_finish frees the workspace (which is why it must be the last + * call to CHOLMOD). + */ + + size_t nrow ; /* size of Flag and Head */ + SuiteSparse_long mark ; /* mark value for Flag array */ + size_t iworksize ; /* size of Iwork. Upper bound: 6*nrow+ncol */ + size_t xworksize ; /* size of Xwork, in bytes. + * maxrank*nrow*sizeof(double) for update/downdate. + * 2*nrow*sizeof(double) otherwise */ + + /* initialized workspace: contents needed between calls to CHOLMOD */ + void *Flag ; /* size nrow, an integer array. Kept cleared between + * calls to cholmod rouines (Flag [i] < mark) */ + + void *Head ; /* size nrow+1, an integer array. Kept cleared between + * calls to cholmod routines (Head [i] = EMPTY) */ + + void *Xwork ; /* a double array. Its size varies. It is nrow for + * most routines (cholmod_rowfac, cholmod_add, + * cholmod_aat, cholmod_norm, cholmod_ssmult) for the real case, twice + * that when the input matrices are complex or zomplex. It is of size + * 2*nrow for cholmod_rowadd and cholmod_rowdel. For cholmod_updown, + * its size is maxrank*nrow where maxrank is 2, 4, or 8. Kept cleared + * between calls to cholmod (set to zero). */ + + /* uninitialized workspace, contents not needed between calls to CHOLMOD */ + void *Iwork ; /* size iworksize, 2*nrow+ncol for most routines, + * up to 6*nrow+ncol for cholmod_analyze. */ + + int itype ; /* If CHOLMOD_LONG, Flag, Head, and Iwork are + * SuiteSparse_long. Otherwise all three are int. */ + + int dtype ; /* double or float */ + + /* Common->itype and Common->dtype are used to define the types of all + * sparse matrices, triplet matrices, dense matrices, and factors + * created using this Common struct. The itypes and dtypes of all + * parameters to all CHOLMOD routines must match. */ + + int no_workspace_reallocate ; /* this is an internal flag, used as a + * precaution by cholmod_analyze. It is normally false. If true, + * cholmod_allocate_work is not allowed to reallocate any workspace; + * they must use the existing workspace in Common (Iwork, Flag, Head, + * and Xwork). Added for CHOLMOD v1.1 */ + + /* ---------------------------------------------------------------------- */ + /* statistics */ + /* ---------------------------------------------------------------------- */ + + /* fl and lnz are set only in cholmod_analyze and cholmod_rowcolcounts, + * in the Cholesky modudle. modfl is set only in the Modify module. */ + + int status ; /* error code */ + double fl ; /* LL' flop count from most recent analysis */ + double lnz ; /* fundamental nz in L */ + double anz ; /* nonzeros in tril(A) if A is symmetric/lower, + * triu(A) if symmetric/upper, or tril(A*A') if + * unsymmetric, in last call to cholmod_analyze. */ + double modfl ; /* flop count from most recent update/downdate/ + * rowadd/rowdel (excluding flops to modify the + * solution to Lx=b, if computed) */ + size_t malloc_count ; /* # of objects malloc'ed minus the # free'd*/ + size_t memory_usage ; /* peak memory usage in bytes */ + size_t memory_inuse ; /* current memory usage in bytes */ + + double nrealloc_col ; /* # of column reallocations */ + double nrealloc_factor ;/* # of factor reallocations due to col. reallocs */ + double ndbounds_hit ; /* # of times diagonal modified by dbound */ + + double rowfacfl ; /* # of flops in last call to cholmod_rowfac */ + double aatfl ; /* # of flops to compute A(:,f)*A(:,f)' */ + + /* ---------------------------------------------------------------------- */ + /* statistics, parameters, and future expansion */ + /* ---------------------------------------------------------------------- */ + + /* The goal for future expansion is to keep sizeof(Common) unchanged. */ + + double other1 [10] ; /* [0..9] for CHOLMOD GPU/CPU numerical + factorization statistics, and [0..3] + used by SuiteSparseQR statistics */ + + double SPQR_xstat [4] ; /* for SuiteSparseQR statistics */ + + /* SuiteSparseQR control parameters: */ + double SPQR_grain ; /* task size is >= max (total flops / grain) */ + double SPQR_small ; /* task size is >= small */ + + /* ---------------------------------------------------------------------- */ + SuiteSparse_long SPQR_istat [10] ; /* for SuiteSparseQR statistics */ + SuiteSparse_long other2 [6] ; /* unused (for future expansion) */ + + /* ---------------------------------------------------------------------- */ + int other3 [10] ; /* unused (for future expansion) */ + + int prefer_binary ; /* cholmod_read_triplet converts a symmetric + * pattern-only matrix into a real matrix. If + * prefer_binary is FALSE, the diagonal entries are set to 1 + the degree + * of the row/column, and off-diagonal entries are set to -1 (resulting + * in a positive definite matrix if the diagonal is zero-free). Most + * symmetric patterns are the pattern a positive definite matrix. If + * this parameter is TRUE, then the matrix is returned with a 1 in each + * entry, instead. Default: FALSE. Added in v1.3. */ + + /* control parameter (added for v1.2): */ + int default_nesdis ; /* Default: FALSE. If FALSE, then the default + * ordering strategy (when Common->nmethods == 0) + * is to try the given ordering (if present), AMD, and then METIS if AMD + * reports high fill-in. If Common->default_nesdis is TRUE then NESDIS + * is used instead in the default strategy. */ + + /* statistic (added for v1.2): */ + int called_nd ; /* TRUE if the last call to + * cholmod_analyze called NESDIS or METIS. */ + + int blas_ok ; /* FALSE if BLAS int overflow; TRUE otherwise */ + + /* SuiteSparseQR control parameters: */ + int SPQR_shrink ; /* controls stack realloc method */ + int SPQR_nthreads ; /* number of TBB threads, 0 = auto */ + + /* ---------------------------------------------------------------------- */ + size_t other4 [16] ; /* [0..7] for CHOLMOD GPU/CPU numerical + factorization statistics, remainder + unused (for future expansion) */ + + /* ---------------------------------------------------------------------- */ + void *other5 [16] ; /* unused (for future expansion) */ + + /* ---------------------------------------------------------------------- */ + /* GPU configuration */ + /* ---------------------------------------------------------------------- */ + +#ifdef GPU_BLAS + /* gpuConfig_t gpuConfig ; */ + + cublasHandle_t cublasHandle ; + cudaStream_t cudaStreamSyrk ; + cudaStream_t cudaStreamGemm ; + cudaStream_t cudaStreamTrsm ; + cudaStream_t cudaStreamPotrf [3] ; + cudaEvent_t cublasEventPotrf [2] ; + void *HostPinnedMemory ; + void *devPotrfWork ; + void *devSyrkGemmPtrLx ; + void *devSyrkGemmPtrC ; + int GemmUsed ; /* TRUE if cuda dgemm used, false otherwise */ + int SyrkUsed ; /* TRUE if cuda dsyrk used, false otherwise */ + double syrkStart ; /* time syrk started */ + +#endif + +} cholmod_common ; + +/* size_t BLAS statistcs in Common: */ +#define CHOLMOD_CPU_GEMM_CALLS other4 [0] +#define CHOLMOD_CPU_SYRK_CALLS other4 [1] +#define CHOLMOD_CPU_TRSM_CALLS other4 [2] +#define CHOLMOD_CPU_POTRF_CALLS other4 [3] +#define CHOLMOD_GPU_GEMM_CALLS other4 [4] +#define CHOLMOD_GPU_SYRK_CALLS other4 [5] +#define CHOLMOD_GPU_TRSM_CALLS other4 [6] +#define CHOLMOD_GPU_POTRF_CALLS other4 [7] + +/* double BLAS statistics in Common: */ +#define CHOLMOD_CPU_GEMM_TIME other1 [0] +#define CHOLMOD_CPU_SYRK_TIME other1 [1] +#define CHOLMOD_CPU_TRSM_TIME other1 [2] +#define CHOLMOD_CPU_POTRF_TIME other1 [3] +#define CHOLMOD_GPU_GEMM_TIME other1 [4] +#define CHOLMOD_GPU_SYRK_TIME other1 [5] +#define CHOLMOD_GPU_TRSM_TIME other1 [6] +#define CHOLMOD_GPU_POTRF_TIME other1 [7] +#define CHOLMOD_ASSEMBLE_TIME other1 [8] +#define CHOLMOD_ASSEMBLE_TIME2 other1 [9] + + +/* -------------------------------------------------------------------------- */ +/* cholmod_start: first call to CHOLMOD */ +/* -------------------------------------------------------------------------- */ + +int cholmod_start +( + cholmod_common *Common +) ; + +int cholmod_l_start (cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_finish: last call to CHOLMOD */ +/* -------------------------------------------------------------------------- */ + +int cholmod_finish +( + cholmod_common *Common +) ; + +int cholmod_l_finish (cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_defaults: restore default parameters */ +/* -------------------------------------------------------------------------- */ + +int cholmod_defaults +( + cholmod_common *Common +) ; + +int cholmod_l_defaults (cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_maxrank: return valid maximum rank for update/downdate */ +/* -------------------------------------------------------------------------- */ + +size_t cholmod_maxrank /* returns validated value of Common->maxrank */ +( + /* ---- input ---- */ + size_t n, /* A and L will have n rows */ + /* --------------- */ + cholmod_common *Common +) ; + +size_t cholmod_l_maxrank (size_t, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_allocate_work: allocate workspace in Common */ +/* -------------------------------------------------------------------------- */ + +int cholmod_allocate_work +( + /* ---- input ---- */ + size_t nrow, /* size: Common->Flag (nrow), Common->Head (nrow+1) */ + size_t iworksize, /* size of Common->Iwork */ + size_t xworksize, /* size of Common->Xwork */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_allocate_work (size_t, size_t, size_t, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_free_work: free workspace in Common */ +/* -------------------------------------------------------------------------- */ + +int cholmod_free_work +( + cholmod_common *Common +) ; + +int cholmod_l_free_work (cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_clear_flag: clear Flag workspace in Common */ +/* -------------------------------------------------------------------------- */ + +/* use a macro for speed */ +#define CHOLMOD_CLEAR_FLAG(Common) \ +{ \ + Common->mark++ ; \ + if (Common->mark <= 0) \ + { \ + Common->mark = EMPTY ; \ + CHOLMOD (clear_flag) (Common) ; \ + } \ +} + +SuiteSparse_long cholmod_clear_flag +( + cholmod_common *Common +) ; + +SuiteSparse_long cholmod_l_clear_flag (cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_error: called when CHOLMOD encounters an error */ +/* -------------------------------------------------------------------------- */ + +int cholmod_error +( + /* ---- input ---- */ + int status, /* error status */ + const char *file, /* name of source code file where error occured */ + int line, /* line number in source code file where error occured*/ + const char *message,/* error message */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_error (int, const char *, int, const char *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_dbound: for internal use in CHOLMOD only */ +/* -------------------------------------------------------------------------- */ + +double cholmod_dbound /* returns modified diagonal entry of D or L */ +( + /* ---- input ---- */ + double dj, /* diagonal entry of D for LDL' or L for LL' */ + /* --------------- */ + cholmod_common *Common +) ; + +double cholmod_l_dbound (double, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_hypot: compute sqrt (x*x + y*y) accurately */ +/* -------------------------------------------------------------------------- */ + +double cholmod_hypot +( + /* ---- input ---- */ + double x, double y +) ; + +double cholmod_l_hypot (double, double) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_divcomplex: complex division, c = a/b */ +/* -------------------------------------------------------------------------- */ + +int cholmod_divcomplex /* return 1 if divide-by-zero, 0 otherise */ +( + /* ---- input ---- */ + double ar, double ai, /* real and imaginary parts of a */ + double br, double bi, /* real and imaginary parts of b */ + /* ---- output --- */ + double *cr, double *ci /* real and imaginary parts of c */ +) ; + +int cholmod_l_divcomplex (double, double, double, double, double *, double *) ; + + +/* ========================================================================== */ +/* === Core/cholmod_sparse ================================================== */ +/* ========================================================================== */ + +/* A sparse matrix stored in compressed-column form. */ + +typedef struct cholmod_sparse_struct +{ + size_t nrow ; /* the matrix is nrow-by-ncol */ + size_t ncol ; + size_t nzmax ; /* maximum number of entries in the matrix */ + + /* pointers to int or SuiteSparse_long: */ + void *p ; /* p [0..ncol], the column pointers */ + void *i ; /* i [0..nzmax-1], the row indices */ + + /* for unpacked matrices only: */ + void *nz ; /* nz [0..ncol-1], the # of nonzeros in each col. In + * packed form, the nonzero pattern of column j is in + * A->i [A->p [j] ... A->p [j+1]-1]. In unpacked form, column j is in + * A->i [A->p [j] ... A->p [j]+A->nz[j]-1] instead. In both cases, the + * numerical values (if present) are in the corresponding locations in + * the array x (or z if A->xtype is CHOLMOD_ZOMPLEX). */ + + /* pointers to double or float: */ + void *x ; /* size nzmax or 2*nzmax, if present */ + void *z ; /* size nzmax, if present */ + + int stype ; /* Describes what parts of the matrix are considered: + * + * 0: matrix is "unsymmetric": use both upper and lower triangular parts + * (the matrix may actually be symmetric in pattern and value, but + * both parts are explicitly stored and used). May be square or + * rectangular. + * >0: matrix is square and symmetric, use upper triangular part. + * Entries in the lower triangular part are ignored. + * <0: matrix is square and symmetric, use lower triangular part. + * Entries in the upper triangular part are ignored. + * + * Note that stype>0 and stype<0 are different for cholmod_sparse and + * cholmod_triplet. See the cholmod_triplet data structure for more + * details. + */ + + int itype ; /* CHOLMOD_INT: p, i, and nz are int. + * CHOLMOD_INTLONG: p is SuiteSparse_long, + * i and nz are int. + * CHOLMOD_LONG: p, i, and nz are SuiteSparse_long */ + + int xtype ; /* pattern, real, complex, or zomplex */ + int dtype ; /* x and z are double or float */ + int sorted ; /* TRUE if columns are sorted, FALSE otherwise */ + int packed ; /* TRUE if packed (nz ignored), FALSE if unpacked + * (nz is required) */ + +} cholmod_sparse ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_allocate_sparse: allocate a sparse matrix */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_allocate_sparse +( + /* ---- input ---- */ + size_t nrow, /* # of rows of A */ + size_t ncol, /* # of columns of A */ + size_t nzmax, /* max # of nonzeros of A */ + int sorted, /* TRUE if columns of A sorted, FALSE otherwise */ + int packed, /* TRUE if A will be packed, FALSE otherwise */ + int stype, /* stype of A */ + int xtype, /* CHOLMOD_PATTERN, _REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_allocate_sparse (size_t, size_t, size_t, int, int, + int, int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_free_sparse: free a sparse matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_free_sparse +( + /* ---- in/out --- */ + cholmod_sparse **A, /* matrix to deallocate, NULL on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_free_sparse (cholmod_sparse **, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_reallocate_sparse: change the size (# entries) of sparse matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_reallocate_sparse +( + /* ---- input ---- */ + size_t nznew, /* new # of entries in A */ + /* ---- in/out --- */ + cholmod_sparse *A, /* matrix to reallocate */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_reallocate_sparse ( size_t, cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_nnz: return number of nonzeros in a sparse matrix */ +/* -------------------------------------------------------------------------- */ + +SuiteSparse_long cholmod_nnz +( + /* ---- input ---- */ + cholmod_sparse *A, + /* --------------- */ + cholmod_common *Common +) ; + +SuiteSparse_long cholmod_l_nnz (cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_speye: sparse identity matrix */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_speye +( + /* ---- input ---- */ + size_t nrow, /* # of rows of A */ + size_t ncol, /* # of columns of A */ + int xtype, /* CHOLMOD_PATTERN, _REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_speye (size_t, size_t, int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_spzeros: sparse zero matrix */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_spzeros +( + /* ---- input ---- */ + size_t nrow, /* # of rows of A */ + size_t ncol, /* # of columns of A */ + size_t nzmax, /* max # of nonzeros of A */ + int xtype, /* CHOLMOD_PATTERN, _REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_spzeros (size_t, size_t, size_t, int, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_transpose: transpose a sparse matrix */ +/* -------------------------------------------------------------------------- */ + +/* Return A' or A.' The "values" parameter is 0, 1, or 2 to denote the pattern + * transpose, the array transpose (A.'), and the complex conjugate transpose + * (A'). + */ + +cholmod_sparse *cholmod_transpose +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to transpose */ + int values, /* 0: pattern, 1: array transpose, 2: conj. transpose */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_transpose (cholmod_sparse *, int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_transpose_unsym: transpose an unsymmetric sparse matrix */ +/* -------------------------------------------------------------------------- */ + +/* Compute F = A', A (:,f)', or A (p,f)', where A is unsymmetric and F is + * already allocated. See cholmod_transpose for a simpler routine. */ + +int cholmod_transpose_unsym +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to transpose */ + int values, /* 0: pattern, 1: array transpose, 2: conj. transpose */ + int *Perm, /* size nrow, if present (can be NULL) */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* ---- output --- */ + cholmod_sparse *F, /* F = A', A(:,f)', or A(p,f)' */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_transpose_unsym (cholmod_sparse *, int, SuiteSparse_long *, + SuiteSparse_long *, size_t, cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_transpose_sym: transpose a symmetric sparse matrix */ +/* -------------------------------------------------------------------------- */ + +/* Compute F = A' or A (p,p)', where A is symmetric and F is already allocated. + * See cholmod_transpose for a simpler routine. */ + +int cholmod_transpose_sym +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to transpose */ + int values, /* 0: pattern, 1: array transpose, 2: conj. transpose */ + int *Perm, /* size nrow, if present (can be NULL) */ + /* ---- output --- */ + cholmod_sparse *F, /* F = A' or A(p,p)' */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_transpose_sym (cholmod_sparse *, int, SuiteSparse_long *, + cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_ptranspose: transpose a sparse matrix */ +/* -------------------------------------------------------------------------- */ + +/* Return A' or A(p,p)' if A is symmetric. Return A', A(:,f)', or A(p,f)' if + * A is unsymmetric. */ + +cholmod_sparse *cholmod_ptranspose +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to transpose */ + int values, /* 0: pattern, 1: array transpose, 2: conj. transpose */ + int *Perm, /* if non-NULL, F = A(p,f) or A(p,p) */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_ptranspose (cholmod_sparse *, int, SuiteSparse_long *, + SuiteSparse_long *, size_t, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_sort: sort row indices in each column of sparse matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_sort +( + /* ---- in/out --- */ + cholmod_sparse *A, /* matrix to sort */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_sort (cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_band: C = tril (triu (A,k1), k2) */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_band +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to extract band matrix from */ + SuiteSparse_long k1, /* ignore entries below the k1-st diagonal */ + SuiteSparse_long k2, /* ignore entries above the k2-nd diagonal */ + int mode, /* >0: numerical, 0: pattern, <0: pattern (no diag) */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_band (cholmod_sparse *, SuiteSparse_long, + SuiteSparse_long, int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_band_inplace: A = tril (triu (A,k1), k2) */ +/* -------------------------------------------------------------------------- */ + +int cholmod_band_inplace +( + /* ---- input ---- */ + SuiteSparse_long k1, /* ignore entries below the k1-st diagonal */ + SuiteSparse_long k2, /* ignore entries above the k2-nd diagonal */ + int mode, /* >0: numerical, 0: pattern, <0: pattern (no diag) */ + /* ---- in/out --- */ + cholmod_sparse *A, /* matrix from which entries not in band are removed */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_band_inplace (SuiteSparse_long, SuiteSparse_long, int, + cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_aat: C = A*A' or A(:,f)*A(:,f)' */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_aat +( + /* ---- input ---- */ + cholmod_sparse *A, /* input matrix; C=A*A' is constructed */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int mode, /* >0: numerical, 0: pattern, <0: pattern (no diag), + * -2: pattern only, no diagonal, add 50%+n extra + * space to C */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_aat (cholmod_sparse *, SuiteSparse_long *, size_t, + int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_copy_sparse: C = A, create an exact copy of a sparse matrix */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_copy_sparse +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to copy */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_copy_sparse (cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_copy: C = A, with possible change of stype */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_copy +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to copy */ + int stype, /* requested stype of C */ + int mode, /* >0: numerical, 0: pattern, <0: pattern (no diag) */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_copy (cholmod_sparse *, int, int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_add: C = alpha*A + beta*B */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_add +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to add */ + cholmod_sparse *B, /* matrix to add */ + double alpha [2], /* scale factor for A */ + double beta [2], /* scale factor for B */ + int values, /* if TRUE compute the numerical values of C */ + int sorted, /* if TRUE, sort columns of C */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_add (cholmod_sparse *, cholmod_sparse *, double *, + double *, int, int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_sparse_xtype: change the xtype of a sparse matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_sparse_xtype +( + /* ---- input ---- */ + int to_xtype, /* requested xtype (pattern, real, complex, zomplex) */ + /* ---- in/out --- */ + cholmod_sparse *A, /* sparse matrix to change */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_sparse_xtype (int, cholmod_sparse *, cholmod_common *) ; + + +/* ========================================================================== */ +/* === Core/cholmod_factor ================================================== */ +/* ========================================================================== */ + +/* A symbolic and numeric factorization, either simplicial or supernodal. + * In all cases, the row indices in the columns of L are kept sorted. */ + +typedef struct cholmod_factor_struct +{ + /* ---------------------------------------------------------------------- */ + /* for both simplicial and supernodal factorizations */ + /* ---------------------------------------------------------------------- */ + + size_t n ; /* L is n-by-n */ + + size_t minor ; /* If the factorization failed, L->minor is the column + * at which it failed (in the range 0 to n-1). A value + * of n means the factorization was successful or + * the matrix has not yet been factorized. */ + + /* ---------------------------------------------------------------------- */ + /* symbolic ordering and analysis */ + /* ---------------------------------------------------------------------- */ + + void *Perm ; /* size n, permutation used */ + void *ColCount ; /* size n, column counts for simplicial L */ + + void *IPerm ; /* size n, inverse permutation. Only created by + * cholmod_solve2 if Bset is used. */ + + /* ---------------------------------------------------------------------- */ + /* simplicial factorization */ + /* ---------------------------------------------------------------------- */ + + size_t nzmax ; /* size of i and x */ + + void *p ; /* p [0..ncol], the column pointers */ + void *i ; /* i [0..nzmax-1], the row indices */ + void *x ; /* x [0..nzmax-1], the numerical values */ + void *z ; + void *nz ; /* nz [0..ncol-1], the # of nonzeros in each column. + * i [p [j] ... p [j]+nz[j]-1] contains the row indices, + * and the numerical values are in the same locatins + * in x. The value of i [p [k]] is always k. */ + + void *next ; /* size ncol+2. next [j] is the next column in i/x */ + void *prev ; /* size ncol+2. prev [j] is the prior column in i/x. + * head of the list is ncol+1, and the tail is ncol. */ + + /* ---------------------------------------------------------------------- */ + /* supernodal factorization */ + /* ---------------------------------------------------------------------- */ + + /* Note that L->x is shared with the simplicial data structure. L->x has + * size L->nzmax for a simplicial factor, and size L->xsize for a supernodal + * factor. */ + + size_t nsuper ; /* number of supernodes */ + size_t ssize ; /* size of s, integer part of supernodes */ + size_t xsize ; /* size of x, real part of supernodes */ + size_t maxcsize ; /* size of largest update matrix */ + size_t maxesize ; /* max # of rows in supernodes, excl. triangular part */ + + void *super ; /* size nsuper+1, first col in each supernode */ + void *pi ; /* size nsuper+1, pointers to integer patterns */ + void *px ; /* size nsuper+1, pointers to real parts */ + void *s ; /* size ssize, integer part of supernodes */ + + /* ---------------------------------------------------------------------- */ + /* factorization type */ + /* ---------------------------------------------------------------------- */ + + int ordering ; /* ordering method used */ + + int is_ll ; /* TRUE if LL', FALSE if LDL' */ + int is_super ; /* TRUE if supernodal, FALSE if simplicial */ + int is_monotonic ; /* TRUE if columns of L appear in order 0..n-1. + * Only applicable to simplicial numeric types. */ + + /* There are 8 types of factor objects that cholmod_factor can represent + * (only 6 are used): + * + * Numeric types (xtype is not CHOLMOD_PATTERN) + * -------------------------------------------- + * + * simplicial LDL': (is_ll FALSE, is_super FALSE). Stored in compressed + * column form, using the simplicial components above (nzmax, p, i, + * x, z, nz, next, and prev). The unit diagonal of L is not stored, + * and D is stored in its place. There are no supernodes. + * + * simplicial LL': (is_ll TRUE, is_super FALSE). Uses the same storage + * scheme as the simplicial LDL', except that D does not appear. + * The first entry of each column of L is the diagonal entry of + * that column of L. + * + * supernodal LDL': (is_ll FALSE, is_super TRUE). Not used. + * FUTURE WORK: add support for supernodal LDL' + * + * supernodal LL': (is_ll TRUE, is_super TRUE). A supernodal factor, + * using the supernodal components described above (nsuper, ssize, + * xsize, maxcsize, maxesize, super, pi, px, s, x, and z). + * + * + * Symbolic types (xtype is CHOLMOD_PATTERN) + * ----------------------------------------- + * + * simplicial LDL': (is_ll FALSE, is_super FALSE). Nothing is present + * except Perm and ColCount. + * + * simplicial LL': (is_ll TRUE, is_super FALSE). Identical to the + * simplicial LDL', except for the is_ll flag. + * + * supernodal LDL': (is_ll FALSE, is_super TRUE). Not used. + * FUTURE WORK: add support for supernodal LDL' + * + * supernodal LL': (is_ll TRUE, is_super TRUE). A supernodal symbolic + * factorization. The simplicial symbolic information is present + * (Perm and ColCount), as is all of the supernodal factorization + * except for the numerical values (x and z). + */ + + int itype ; /* The integer arrays are Perm, ColCount, p, i, nz, + * next, prev, super, pi, px, and s. If itype is + * CHOLMOD_INT, all of these are int arrays. + * CHOLMOD_INTLONG: p, pi, px are SuiteSparse_long, others int. + * CHOLMOD_LONG: all integer arrays are SuiteSparse_long. */ + int xtype ; /* pattern, real, complex, or zomplex */ + int dtype ; /* x and z double or float */ + +} cholmod_factor ; + + +/* -------------------------------------------------------------------------- */ +/* cholmod_allocate_factor: allocate a factor (symbolic LL' or LDL') */ +/* -------------------------------------------------------------------------- */ + +cholmod_factor *cholmod_allocate_factor +( + /* ---- input ---- */ + size_t n, /* L is n-by-n */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_factor *cholmod_l_allocate_factor (size_t, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_free_factor: free a factor */ +/* -------------------------------------------------------------------------- */ + +int cholmod_free_factor +( + /* ---- in/out --- */ + cholmod_factor **L, /* factor to free, NULL on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_free_factor (cholmod_factor **, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_reallocate_factor: change the # entries in a factor */ +/* -------------------------------------------------------------------------- */ + +int cholmod_reallocate_factor +( + /* ---- input ---- */ + size_t nznew, /* new # of entries in L */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_reallocate_factor (size_t, cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_change_factor: change the type of factor (e.g., LDL' to LL') */ +/* -------------------------------------------------------------------------- */ + +int cholmod_change_factor +( + /* ---- input ---- */ + int to_xtype, /* to CHOLMOD_PATTERN, _REAL, _COMPLEX, _ZOMPLEX */ + int to_ll, /* TRUE: convert to LL', FALSE: LDL' */ + int to_super, /* TRUE: convert to supernodal, FALSE: simplicial */ + int to_packed, /* TRUE: pack simplicial columns, FALSE: do not pack */ + int to_monotonic, /* TRUE: put simplicial columns in order, FALSE: not */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_change_factor ( int, int, int, int, int, cholmod_factor *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_pack_factor: pack the columns of a factor */ +/* -------------------------------------------------------------------------- */ + +/* Pack the columns of a simplicial factor. Unlike cholmod_change_factor, + * it can pack the columns of a factor even if they are not stored in their + * natural order (non-monotonic). */ + +int cholmod_pack_factor +( + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_pack_factor (cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_reallocate_column: resize a single column of a factor */ +/* -------------------------------------------------------------------------- */ + +int cholmod_reallocate_column +( + /* ---- input ---- */ + size_t j, /* the column to reallocate */ + size_t need, /* required size of column j */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_reallocate_column (size_t, size_t, cholmod_factor *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_factor_to_sparse: create a sparse matrix copy of a factor */ +/* -------------------------------------------------------------------------- */ + +/* Only operates on numeric factors, not symbolic ones */ + +cholmod_sparse *cholmod_factor_to_sparse +( + /* ---- in/out --- */ + cholmod_factor *L, /* factor to copy, converted to symbolic on output */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_factor_to_sparse (cholmod_factor *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_copy_factor: create a copy of a factor */ +/* -------------------------------------------------------------------------- */ + +cholmod_factor *cholmod_copy_factor +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to copy */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_factor *cholmod_l_copy_factor (cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_factor_xtype: change the xtype of a factor */ +/* -------------------------------------------------------------------------- */ + +int cholmod_factor_xtype +( + /* ---- input ---- */ + int to_xtype, /* requested xtype (real, complex, or zomplex) */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to change */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_factor_xtype (int, cholmod_factor *, cholmod_common *) ; + + +/* ========================================================================== */ +/* === Core/cholmod_dense =================================================== */ +/* ========================================================================== */ + +/* A dense matrix in column-oriented form. It has no itype since it contains + * no integers. Entry in row i and column j is located in x [i+j*d]. + */ + +typedef struct cholmod_dense_struct +{ + size_t nrow ; /* the matrix is nrow-by-ncol */ + size_t ncol ; + size_t nzmax ; /* maximum number of entries in the matrix */ + size_t d ; /* leading dimension (d >= nrow must hold) */ + void *x ; /* size nzmax or 2*nzmax, if present */ + void *z ; /* size nzmax, if present */ + int xtype ; /* pattern, real, complex, or zomplex */ + int dtype ; /* x and z double or float */ + +} cholmod_dense ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_allocate_dense: allocate a dense matrix (contents uninitialized) */ +/* -------------------------------------------------------------------------- */ + +cholmod_dense *cholmod_allocate_dense +( + /* ---- input ---- */ + size_t nrow, /* # of rows of matrix */ + size_t ncol, /* # of columns of matrix */ + size_t d, /* leading dimension */ + int xtype, /* CHOLMOD_REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_dense *cholmod_l_allocate_dense (size_t, size_t, size_t, int, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_zeros: allocate a dense matrix and set it to zero */ +/* -------------------------------------------------------------------------- */ + +cholmod_dense *cholmod_zeros +( + /* ---- input ---- */ + size_t nrow, /* # of rows of matrix */ + size_t ncol, /* # of columns of matrix */ + int xtype, /* CHOLMOD_REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_dense *cholmod_l_zeros (size_t, size_t, int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_ones: allocate a dense matrix and set it to all ones */ +/* -------------------------------------------------------------------------- */ + +cholmod_dense *cholmod_ones +( + /* ---- input ---- */ + size_t nrow, /* # of rows of matrix */ + size_t ncol, /* # of columns of matrix */ + int xtype, /* CHOLMOD_REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_dense *cholmod_l_ones (size_t, size_t, int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_eye: allocate a dense matrix and set it to the identity matrix */ +/* -------------------------------------------------------------------------- */ + +cholmod_dense *cholmod_eye +( + /* ---- input ---- */ + size_t nrow, /* # of rows of matrix */ + size_t ncol, /* # of columns of matrix */ + int xtype, /* CHOLMOD_REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_dense *cholmod_l_eye (size_t, size_t, int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_free_dense: free a dense matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_free_dense +( + /* ---- in/out --- */ + cholmod_dense **X, /* dense matrix to deallocate, NULL on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_free_dense (cholmod_dense **, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_ensure_dense: ensure a dense matrix has a given size and type */ +/* -------------------------------------------------------------------------- */ + +cholmod_dense *cholmod_ensure_dense +( + /* ---- input/output ---- */ + cholmod_dense **XHandle, /* matrix handle to check */ + /* ---- input ---- */ + size_t nrow, /* # of rows of matrix */ + size_t ncol, /* # of columns of matrix */ + size_t d, /* leading dimension */ + int xtype, /* CHOLMOD_REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_dense *cholmod_l_ensure_dense (cholmod_dense **, size_t, size_t, size_t, + int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_sparse_to_dense: create a dense matrix copy of a sparse matrix */ +/* -------------------------------------------------------------------------- */ + +cholmod_dense *cholmod_sparse_to_dense +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to copy */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_dense *cholmod_l_sparse_to_dense (cholmod_sparse *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_dense_to_sparse: create a sparse matrix copy of a dense matrix */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_dense_to_sparse +( + /* ---- input ---- */ + cholmod_dense *X, /* matrix to copy */ + int values, /* TRUE if values to be copied, FALSE otherwise */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_dense_to_sparse (cholmod_dense *, int, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_copy_dense: create a copy of a dense matrix */ +/* -------------------------------------------------------------------------- */ + +cholmod_dense *cholmod_copy_dense +( + /* ---- input ---- */ + cholmod_dense *X, /* matrix to copy */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_dense *cholmod_l_copy_dense (cholmod_dense *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_copy_dense2: copy a dense matrix (pre-allocated) */ +/* -------------------------------------------------------------------------- */ + +int cholmod_copy_dense2 +( + /* ---- input ---- */ + cholmod_dense *X, /* matrix to copy */ + /* ---- output --- */ + cholmod_dense *Y, /* copy of matrix X */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_copy_dense2 (cholmod_dense *, cholmod_dense *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_dense_xtype: change the xtype of a dense matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_dense_xtype +( + /* ---- input ---- */ + int to_xtype, /* requested xtype (real, complex,or zomplex) */ + /* ---- in/out --- */ + cholmod_dense *X, /* dense matrix to change */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_dense_xtype (int, cholmod_dense *, cholmod_common *) ; + + +/* ========================================================================== */ +/* === Core/cholmod_triplet ================================================= */ +/* ========================================================================== */ + +/* A sparse matrix stored in triplet form. */ + +typedef struct cholmod_triplet_struct +{ + size_t nrow ; /* the matrix is nrow-by-ncol */ + size_t ncol ; + size_t nzmax ; /* maximum number of entries in the matrix */ + size_t nnz ; /* number of nonzeros in the matrix */ + + void *i ; /* i [0..nzmax-1], the row indices */ + void *j ; /* j [0..nzmax-1], the column indices */ + void *x ; /* size nzmax or 2*nzmax, if present */ + void *z ; /* size nzmax, if present */ + + int stype ; /* Describes what parts of the matrix are considered: + * + * 0: matrix is "unsymmetric": use both upper and lower triangular parts + * (the matrix may actually be symmetric in pattern and value, but + * both parts are explicitly stored and used). May be square or + * rectangular. + * >0: matrix is square and symmetric. Entries in the lower triangular + * part are transposed and added to the upper triangular part when + * the matrix is converted to cholmod_sparse form. + * <0: matrix is square and symmetric. Entries in the upper triangular + * part are transposed and added to the lower triangular part when + * the matrix is converted to cholmod_sparse form. + * + * Note that stype>0 and stype<0 are different for cholmod_sparse and + * cholmod_triplet. The reason is simple. You can permute a symmetric + * triplet matrix by simply replacing a row and column index with their + * new row and column indices, via an inverse permutation. Suppose + * P = L->Perm is your permutation, and Pinv is an array of size n. + * Suppose a symmetric matrix A is represent by a triplet matrix T, with + * entries only in the upper triangular part. Then the following code: + * + * Ti = T->i ; + * Tj = T->j ; + * for (k = 0 ; k < n ; k++) Pinv [P [k]] = k ; + * for (k = 0 ; k < nz ; k++) Ti [k] = Pinv [Ti [k]] ; + * for (k = 0 ; k < nz ; k++) Tj [k] = Pinv [Tj [k]] ; + * + * creates the triplet form of C=P*A*P'. However, if T initially + * contains just the upper triangular entries (T->stype = 1), after + * permutation it has entries in both the upper and lower triangular + * parts. These entries should be transposed when constructing the + * cholmod_sparse form of A, which is what cholmod_triplet_to_sparse + * does. Thus: + * + * C = cholmod_triplet_to_sparse (T, 0, &Common) ; + * + * will return the matrix C = P*A*P'. + * + * Since the triplet matrix T is so simple to generate, it's quite easy + * to remove entries that you do not want, prior to converting T to the + * cholmod_sparse form. So if you include these entries in T, CHOLMOD + * assumes that there must be a reason (such as the one above). Thus, + * no entry in a triplet matrix is ever ignored. + */ + + int itype ; /* CHOLMOD_LONG: i and j are SuiteSparse_long. Otherwise int */ + int xtype ; /* pattern, real, complex, or zomplex */ + int dtype ; /* x and z are double or float */ + +} cholmod_triplet ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_allocate_triplet: allocate a triplet matrix */ +/* -------------------------------------------------------------------------- */ + +cholmod_triplet *cholmod_allocate_triplet +( + /* ---- input ---- */ + size_t nrow, /* # of rows of T */ + size_t ncol, /* # of columns of T */ + size_t nzmax, /* max # of nonzeros of T */ + int stype, /* stype of T */ + int xtype, /* CHOLMOD_PATTERN, _REAL, _COMPLEX, or _ZOMPLEX */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_triplet *cholmod_l_allocate_triplet (size_t, size_t, size_t, int, int, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_free_triplet: free a triplet matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_free_triplet +( + /* ---- in/out --- */ + cholmod_triplet **T, /* triplet matrix to deallocate, NULL on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_free_triplet (cholmod_triplet **, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_reallocate_triplet: change the # of entries in a triplet matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_reallocate_triplet +( + /* ---- input ---- */ + size_t nznew, /* new # of entries in T */ + /* ---- in/out --- */ + cholmod_triplet *T, /* triplet matrix to modify */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_reallocate_triplet (size_t, cholmod_triplet *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_sparse_to_triplet: create a triplet matrix copy of a sparse matrix*/ +/* -------------------------------------------------------------------------- */ + +cholmod_triplet *cholmod_sparse_to_triplet +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to copy */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_triplet *cholmod_l_sparse_to_triplet (cholmod_sparse *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_triplet_to_sparse: create a sparse matrix copy of a triplet matrix*/ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_triplet_to_sparse +( + /* ---- input ---- */ + cholmod_triplet *T, /* matrix to copy */ + size_t nzmax, /* allocate at least this much space in output matrix */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_triplet_to_sparse (cholmod_triplet *, size_t, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_copy_triplet: create a copy of a triplet matrix */ +/* -------------------------------------------------------------------------- */ + +cholmod_triplet *cholmod_copy_triplet +( + /* ---- input ---- */ + cholmod_triplet *T, /* matrix to copy */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_triplet *cholmod_l_copy_triplet (cholmod_triplet *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_triplet_xtype: change the xtype of a triplet matrix */ +/* -------------------------------------------------------------------------- */ + +int cholmod_triplet_xtype +( + /* ---- input ---- */ + int to_xtype, /* requested xtype (pattern, real, complex,or zomplex)*/ + /* ---- in/out --- */ + cholmod_triplet *T, /* triplet matrix to change */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_triplet_xtype (int, cholmod_triplet *, cholmod_common *) ; + + +/* ========================================================================== */ +/* === Core/cholmod_memory ================================================== */ +/* ========================================================================== */ + +/* The user may make use of these, just like malloc and free. You can even + * malloc an object and safely free it with cholmod_free, and visa versa + * (except that the memory usage statistics will be corrupted). These routines + * do differ from malloc and free. If cholmod_free is given a NULL pointer, + * for example, it does nothing (unlike the ANSI free). cholmod_realloc does + * not return NULL if given a non-NULL pointer and a nonzero size, even if it + * fails (it returns the original pointer and sets an error code in + * Common->status instead). + * + * CHOLMOD keeps track of the amount of memory it has allocated, and so the + * cholmod_free routine also takes the size of the object being freed. This + * is only used for statistics. If you, the user of CHOLMOD, pass the wrong + * size, the only consequence is that the memory usage statistics will be + * corrupted. + */ + +void *cholmod_malloc /* returns pointer to the newly malloc'd block */ +( + /* ---- input ---- */ + size_t n, /* number of items */ + size_t size, /* size of each item */ + /* --------------- */ + cholmod_common *Common +) ; + +void *cholmod_l_malloc (size_t, size_t, cholmod_common *) ; + +void *cholmod_calloc /* returns pointer to the newly calloc'd block */ +( + /* ---- input ---- */ + size_t n, /* number of items */ + size_t size, /* size of each item */ + /* --------------- */ + cholmod_common *Common +) ; + +void *cholmod_l_calloc (size_t, size_t, cholmod_common *) ; + +void *cholmod_free /* always returns NULL */ +( + /* ---- input ---- */ + size_t n, /* number of items */ + size_t size, /* size of each item */ + /* ---- in/out --- */ + void *p, /* block of memory to free */ + /* --------------- */ + cholmod_common *Common +) ; + +void *cholmod_l_free (size_t, size_t, void *, cholmod_common *) ; + +void *cholmod_realloc /* returns pointer to reallocated block */ +( + /* ---- input ---- */ + size_t nnew, /* requested # of items in reallocated block */ + size_t size, /* size of each item */ + /* ---- in/out --- */ + void *p, /* block of memory to realloc */ + size_t *n, /* current size on input, nnew on output if successful*/ + /* --------------- */ + cholmod_common *Common +) ; + +void *cholmod_l_realloc (size_t, size_t, void *, size_t *, cholmod_common *) ; + +int cholmod_realloc_multiple +( + /* ---- input ---- */ + size_t nnew, /* requested # of items in reallocated blocks */ + int nint, /* number of int/SuiteSparse_long blocks */ + int xtype, /* CHOLMOD_PATTERN, _REAL, _COMPLEX, or _ZOMPLEX */ + /* ---- in/out --- */ + void **Iblock, /* int or SuiteSparse_long block */ + void **Jblock, /* int or SuiteSparse_long block */ + void **Xblock, /* complex, double, or float block */ + void **Zblock, /* zomplex case only: double or float block */ + size_t *n, /* current size of the I,J,X,Z blocks on input, + * nnew on output if successful */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_realloc_multiple (size_t, int, int, void **, void **, void **, + void **, size_t *, cholmod_common *) ; + +/* ========================================================================== */ +/* === version control ====================================================== */ +/* ========================================================================== */ + +int cholmod_version /* returns CHOLMOD_VERSION */ +( + /* output, contents not defined on input. Not used if NULL. + version [0] = CHOLMOD_MAIN_VERSION + version [1] = CHOLMOD_SUB_VERSION + version [2] = CHOLMOD_SUBSUB_VERSION + */ + int version [3] +) ; + +int cholmod_l_version (int version [3]) ; + +/* Versions prior to 2.1.1 do not have the above function. The following + code fragment will work with any version of CHOLMOD: + #ifdef CHOLMOD_HAS_VERSION_FUNCTION + v = cholmod_version (NULL) ; + #else + v = CHOLMOD_VERSION ; + #endif +*/ + +/* ========================================================================== */ +/* === symmetry types ======================================================= */ +/* ========================================================================== */ + +#define CHOLMOD_MM_RECTANGULAR 1 +#define CHOLMOD_MM_UNSYMMETRIC 2 +#define CHOLMOD_MM_SYMMETRIC 3 +#define CHOLMOD_MM_HERMITIAN 4 +#define CHOLMOD_MM_SKEW_SYMMETRIC 5 +#define CHOLMOD_MM_SYMMETRIC_POSDIAG 6 +#define CHOLMOD_MM_HERMITIAN_POSDIAG 7 + +/* ========================================================================== */ +/* === Numerical relop macros =============================================== */ +/* ========================================================================== */ + +/* These macros correctly handle the NaN case. + * + * CHOLMOD_IS_NAN(x): + * True if x is NaN. False otherwise. The commonly-existing isnan(x) + * function could be used, but it's not in Kernighan & Ritchie 2nd edition + * (ANSI C89). It may appear in , but I'm not certain about + * portability. The expression x != x is true if and only if x is NaN, + * according to the IEEE 754 floating-point standard. + * + * CHOLMOD_IS_ZERO(x): + * True if x is zero. False if x is nonzero, NaN, or +/- Inf. + * This is (x == 0) if the compiler is IEEE 754 compliant. + * + * CHOLMOD_IS_NONZERO(x): + * True if x is nonzero, NaN, or +/- Inf. False if x zero. + * This is (x != 0) if the compiler is IEEE 754 compliant. + * + * CHOLMOD_IS_LT_ZERO(x): + * True if x is < zero or -Inf. False if x is >= 0, NaN, or +Inf. + * This is (x < 0) if the compiler is IEEE 754 compliant. + * + * CHOLMOD_IS_GT_ZERO(x): + * True if x is > zero or +Inf. False if x is <= 0, NaN, or -Inf. + * This is (x > 0) if the compiler is IEEE 754 compliant. + * + * CHOLMOD_IS_LE_ZERO(x): + * True if x is <= zero or -Inf. False if x is > 0, NaN, or +Inf. + * This is (x <= 0) if the compiler is IEEE 754 compliant. + */ + +#ifdef CHOLMOD_WINDOWS + +/* Yes, this is exceedingly ugly. Blame Microsoft, which hopelessly */ +/* violates the IEEE 754 floating-point standard in a bizarre way. */ +/* If you're using an IEEE 754-compliant compiler, then x != x is true */ +/* iff x is NaN. For Microsoft, (x < x) is true iff x is NaN. */ +/* So either way, this macro safely detects a NaN. */ +#define CHOLMOD_IS_NAN(x) (((x) != (x)) || (((x) < (x)))) +#define CHOLMOD_IS_ZERO(x) (((x) == 0.) && !CHOLMOD_IS_NAN(x)) +#define CHOLMOD_IS_NONZERO(x) (((x) != 0.) || CHOLMOD_IS_NAN(x)) +#define CHOLMOD_IS_LT_ZERO(x) (((x) < 0.) && !CHOLMOD_IS_NAN(x)) +#define CHOLMOD_IS_GT_ZERO(x) (((x) > 0.) && !CHOLMOD_IS_NAN(x)) +#define CHOLMOD_IS_LE_ZERO(x) (((x) <= 0.) && !CHOLMOD_IS_NAN(x)) + +#else + +/* These all work properly, according to the IEEE 754 standard ... except on */ +/* a PC with windows. Works fine in Linux on the same PC... */ +#define CHOLMOD_IS_NAN(x) ((x) != (x)) +#define CHOLMOD_IS_ZERO(x) ((x) == 0.) +#define CHOLMOD_IS_NONZERO(x) ((x) != 0.) +#define CHOLMOD_IS_LT_ZERO(x) ((x) < 0.) +#define CHOLMOD_IS_GT_ZERO(x) ((x) > 0.) +#define CHOLMOD_IS_LE_ZERO(x) ((x) <= 0.) + +#endif + +#endif diff --git a/src/CHOLMOD/Include/cholmod_internal.h b/src/CHOLMOD/Include/cholmod_internal.h new file mode 100644 index 0000000..4a5f506 --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_internal.h @@ -0,0 +1,404 @@ +/* ========================================================================== */ +/* === Include/cholmod_internal.h =========================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_internal.h. + * Copyright (C) 2005-2013, Univ. of Florida. Author: Timothy A. Davis + * CHOLMOD/Include/cholmod_internal.h is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD internal include file. + * + * This file contains internal definitions for CHOLMOD, not meant to be included + * in user code. They define macros that are not prefixed with CHOLMOD_. This + * file can safely #include'd in user code if you want to make use of the + * macros defined here, and don't mind the possible name conflicts with your + * code, however. + * + * Required by all CHOLMOD routines. Not required by any user routine that + * uses CHOLMOMD. Unless debugging is enabled, this file does not require any + * CHOLMOD module (not even the Core module). + * + * If debugging is enabled, all CHOLMOD modules require the Check module. + * Enabling debugging requires that this file be editted. Debugging cannot be + * enabled with a compiler flag. This is because CHOLMOD is exceedingly slow + * when debugging is enabled. Debugging is meant for development of CHOLMOD + * itself, not by users of CHOLMOD. + */ + +#ifndef CHOLMOD_INTERNAL_H +#define CHOLMOD_INTERNAL_H + +/* ========================================================================== */ +/* === large file I/O ======================================================= */ +/* ========================================================================== */ + +/* Definitions for large file I/O must come before any other #includes. If + * this causes problems (may not be portable to all platforms), then compile + * CHOLMOD with -DNLARGEFILE. You must do this for MATLAB 6.5 and earlier, + * for example. */ + +#include "cholmod_io64.h" + +/* ========================================================================== */ +/* === debugging and basic includes ========================================= */ +/* ========================================================================== */ + +/* turn off debugging */ +#ifndef NDEBUG +#define NDEBUG +#endif + +/* Uncomment this line to enable debugging. CHOLMOD will be very slow. +#undef NDEBUG + */ + +#ifdef MATLAB_MEX_FILE +#include "mex.h" +#endif + +#if !defined(NPRINT) || !defined(NDEBUG) +#include +#endif + +#include +#include +#include +#include +#include + +/* ========================================================================== */ +/* === basic definitions ==================================================== */ +/* ========================================================================== */ + +/* Some non-conforming compilers insist on defining TRUE and FALSE. */ +#undef TRUE +#undef FALSE +#define TRUE 1 +#define FALSE 0 +#define BOOLEAN(x) ((x) ? TRUE : FALSE) + +/* NULL should already be defined, but ensure it is here. */ +#ifndef NULL +#define NULL ((void *) 0) +#endif + +/* FLIP is a "negation about -1", and is used to mark an integer i that is + * normally non-negative. FLIP (EMPTY) is EMPTY. FLIP of a number > EMPTY + * is negative, and FLIP of a number < EMTPY is positive. FLIP (FLIP (i)) = i + * for all integers i. UNFLIP (i) is >= EMPTY. */ +#define EMPTY (-1) +#define FLIP(i) (-(i)-2) +#define UNFLIP(i) (((i) < EMPTY) ? FLIP (i) : (i)) + +/* MAX and MIN are not safe to use for NaN's */ +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define MAX3(a,b,c) (((a) > (b)) ? (MAX (a,c)) : (MAX (b,c))) +#define MAX4(a,b,c,d) (((a) > (b)) ? (MAX3 (a,c,d)) : (MAX3 (b,c,d))) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define IMPLIES(p,q) (!(p) || (q)) + +/* find the sign: -1 if x < 0, 1 if x > 0, zero otherwise. + * Not safe for NaN's */ +#define SIGN(x) (((x) < 0) ? (-1) : (((x) > 0) ? 1 : 0)) + +/* round up an integer x to a multiple of s */ +#define ROUNDUP(x,s) ((s) * (((x) + ((s) - 1)) / (s))) + +#define ERROR(status,msg) \ + CHOLMOD(error) (status, __FILE__, __LINE__, msg, Common) + +/* Check a pointer and return if null. Set status to invalid, unless the + * status is already "out of memory" */ +#define RETURN_IF_NULL(A,result) \ +{ \ + if ((A) == NULL) \ + { \ + if (Common->status != CHOLMOD_OUT_OF_MEMORY) \ + { \ + ERROR (CHOLMOD_INVALID, "argument missing") ; \ + } \ + return (result) ; \ + } \ +} + +/* Return if Common is NULL or invalid */ +#define RETURN_IF_NULL_COMMON(result) \ +{ \ + if (Common == NULL) \ + { \ + return (result) ; \ + } \ + if (Common->itype != ITYPE || Common->dtype != DTYPE) \ + { \ + Common->status = CHOLMOD_INVALID ; \ + return (result) ; \ + } \ +} + +#define IS_NAN(x) CHOLMOD_IS_NAN(x) +#define IS_ZERO(x) CHOLMOD_IS_ZERO(x) +#define IS_NONZERO(x) CHOLMOD_IS_NONZERO(x) +#define IS_LT_ZERO(x) CHOLMOD_IS_LT_ZERO(x) +#define IS_GT_ZERO(x) CHOLMOD_IS_GT_ZERO(x) +#define IS_LE_ZERO(x) CHOLMOD_IS_LE_ZERO(x) + +/* 1e308 is a huge number that doesn't take many characters to print in a + * file, in CHOLMOD/Check/cholmod_read and _write. Numbers larger than this + * are interpretted as Inf, since sscanf doesn't read in Inf's properly. + * This assumes IEEE double precision arithmetic. DBL_MAX would be a little + * better, except that it takes too many digits to print in a file. */ +#define HUGE_DOUBLE 1e308 + +/* ========================================================================== */ +/* === int/long and double/float definitions ================================ */ +/* ========================================================================== */ + +/* CHOLMOD is designed for 3 types of integer variables: + * + * (1) all integers are int + * (2) most integers are int, some are SuiteSparse_long + * (3) all integers are SuiteSparse_long + * + * and two kinds of floating-point values: + * + * (1) double + * (2) float + * + * the complex types (ANSI-compatible complex, and MATLAB-compatable zomplex) + * are based on the double or float type, and are not selected here. They + * are typically selected via template routines. + * + * This gives 6 different modes in which CHOLMOD can be compiled (only the + * first two are currently supported): + * + * DINT double, int prefix: cholmod_ + * DLONG double, SuiteSparse_long prefix: cholmod_l_ + * DMIX double, mixed int/SuiteSparse_long prefix: cholmod_m_ + * SINT float, int prefix: cholmod_si_ + * SLONG float, SuiteSparse_long prefix: cholmod_sl_ + * SMIX float, mixed int/log prefix: cholmod_sm_ + * + * These are selected with compile time flags (-DDLONG, for example). If no + * flag is selected, the default is DINT. + * + * All six versions use the same include files. The user-visible include files + * are completely independent of which int/long/double/float version is being + * used. The integer / real types in all data structures (sparse, triplet, + * dense, common, and triplet) are defined at run-time, not compile-time, so + * there is only one "cholmod_sparse" data type. Void pointers are used inside + * that data structure to point to arrays of the proper type. Each data + * structure has an itype and dtype field which determines the kind of basic + * types used. These are defined in Include/cholmod_core.h. + * + * FUTURE WORK: support all six types (float, and mixed int/long) + * + * SuiteSparse_long is normally defined as long. However, for WIN64 it is + * __int64. It can also be redefined for other platforms, by modifying + * SuiteSparse_config.h. + */ + +#include "SuiteSparse_config.h" + +/* -------------------------------------------------------------------------- */ +/* Size_max: the largest value of size_t */ +/* -------------------------------------------------------------------------- */ + +#define Size_max ((size_t) (-1)) + +/* routines for doing arithmetic on size_t, and checking for overflow */ +size_t cholmod_add_size_t (size_t a, size_t b, int *ok) ; +size_t cholmod_mult_size_t (size_t a, size_t k, int *ok) ; +size_t cholmod_l_add_size_t (size_t a, size_t b, int *ok) ; +size_t cholmod_l_mult_size_t (size_t a, size_t k, int *ok) ; + +/* -------------------------------------------------------------------------- */ +/* double (also complex double), SuiteSparse_long */ +/* -------------------------------------------------------------------------- */ + +#ifdef DLONG +#define Real double +#define Int SuiteSparse_long +#define Int_max SuiteSparse_long_max +#define CHOLMOD(name) cholmod_l_ ## name +#define LONG +#define DOUBLE +#define ITYPE CHOLMOD_LONG +#define DTYPE CHOLMOD_DOUBLE +#define ID SuiteSparse_long_id + +/* -------------------------------------------------------------------------- */ +/* double, int/SuiteSparse_long */ +/* -------------------------------------------------------------------------- */ + +#elif defined (DMIX) +#error "mixed int/SuiteSparse_long not yet supported" + +/* -------------------------------------------------------------------------- */ +/* single, int */ +/* -------------------------------------------------------------------------- */ + +#elif defined (SINT) +#error "single-precision not yet supported" + +/* -------------------------------------------------------------------------- */ +/* single, SuiteSparse_long */ +/* -------------------------------------------------------------------------- */ + +#elif defined (SLONG) +#error "single-precision not yet supported" + +/* -------------------------------------------------------------------------- */ +/* single, int/SuiteSparse_long */ +/* -------------------------------------------------------------------------- */ + +#elif defined (SMIX) +#error "single-precision not yet supported" + +/* -------------------------------------------------------------------------- */ +/* double (also complex double), int: this is the default */ +/* -------------------------------------------------------------------------- */ + +#else + +#ifndef DINT +#define DINT +#endif +#define INT +#define DOUBLE + +#define Real double +#define Int int +#define Int_max INT_MAX +#define CHOLMOD(name) cholmod_ ## name +#define ITYPE CHOLMOD_INT +#define DTYPE CHOLMOD_DOUBLE +#define ID "%d" + +#endif + + +/* ========================================================================== */ +/* === real/complex arithmetic ============================================== */ +/* ========================================================================== */ + +#include "cholmod_complexity.h" + +/* ========================================================================== */ +/* === Architecture and BLAS ================================================ */ +/* ========================================================================== */ + +#define BLAS_OK Common->blas_ok +#include "cholmod_blas.h" + +/* ========================================================================== */ +/* === debugging definitions ================================================ */ +/* ========================================================================== */ + +#ifndef NDEBUG + +#include +#include "cholmod.h" + +/* The cholmod_dump routines are in the Check module. No CHOLMOD routine + * calls the cholmod_check_* or cholmod_print_* routines in the Check module, + * since they use Common workspace that may already be in use. Instead, they + * use the cholmod_dump_* routines defined there, which allocate their own + * workspace if they need it. */ + +#ifndef EXTERN +#define EXTERN extern +#endif + +/* double, int */ +EXTERN int cholmod_dump ; +EXTERN int cholmod_dump_malloc ; +SuiteSparse_long cholmod_dump_sparse (cholmod_sparse *, const char *, + cholmod_common *) ; +int cholmod_dump_factor (cholmod_factor *, const char *, cholmod_common *) ; +int cholmod_dump_triplet (cholmod_triplet *, const char *, cholmod_common *) ; +int cholmod_dump_dense (cholmod_dense *, const char *, cholmod_common *) ; +int cholmod_dump_subset (int *, size_t, size_t, const char *, + cholmod_common *) ; +int cholmod_dump_perm (int *, size_t, size_t, const char *, cholmod_common *) ; +int cholmod_dump_parent (int *, size_t, const char *, cholmod_common *) ; +void cholmod_dump_init (const char *, cholmod_common *) ; +int cholmod_dump_mem (const char *, SuiteSparse_long, cholmod_common *) ; +void cholmod_dump_real (const char *, Real *, SuiteSparse_long, + SuiteSparse_long, int, int, cholmod_common *) ; +void cholmod_dump_super (SuiteSparse_long, int *, int *, int *, int *, double *, + int, cholmod_common *) ; +int cholmod_dump_partition (SuiteSparse_long, int *, int *, int *, int *, + SuiteSparse_long, cholmod_common *) ; +int cholmod_dump_work(int, int, SuiteSparse_long, cholmod_common *) ; + +/* double, SuiteSparse_long */ +EXTERN int cholmod_l_dump ; +EXTERN int cholmod_l_dump_malloc ; +SuiteSparse_long cholmod_l_dump_sparse (cholmod_sparse *, const char *, + cholmod_common *) ; +int cholmod_l_dump_factor (cholmod_factor *, const char *, cholmod_common *) ; +int cholmod_l_dump_triplet (cholmod_triplet *, const char *, cholmod_common *); +int cholmod_l_dump_dense (cholmod_dense *, const char *, cholmod_common *) ; +int cholmod_l_dump_subset (SuiteSparse_long *, size_t, size_t, const char *, + cholmod_common *) ; +int cholmod_l_dump_perm (SuiteSparse_long *, size_t, size_t, const char *, + cholmod_common *) ; +int cholmod_l_dump_parent (SuiteSparse_long *, size_t, const char *, + cholmod_common *) ; +void cholmod_l_dump_init (const char *, cholmod_common *) ; +int cholmod_l_dump_mem (const char *, SuiteSparse_long, cholmod_common *) ; +void cholmod_l_dump_real (const char *, Real *, SuiteSparse_long, + SuiteSparse_long, int, int, cholmod_common *) ; +void cholmod_l_dump_super (SuiteSparse_long, SuiteSparse_long *, + SuiteSparse_long *, SuiteSparse_long *, SuiteSparse_long *, + double *, int, cholmod_common *) ; +int cholmod_l_dump_partition (SuiteSparse_long, SuiteSparse_long *, + SuiteSparse_long *, SuiteSparse_long *, + SuiteSparse_long *, SuiteSparse_long, cholmod_common *) ; +int cholmod_l_dump_work(int, int, SuiteSparse_long, cholmod_common *) ; + +#define DEBUG_INIT(s,Common) { CHOLMOD(dump_init)(s, Common) ; } +#define ASSERT(expression) (assert (expression)) + +#define PRK(k,params) \ +{ \ + if (CHOLMOD(dump) >= (k) && Common->print_function != NULL) \ + { \ + (Common->print_function) params ; \ + } \ +} + +#define PRINT0(params) PRK (0, params) +#define PRINT1(params) PRK (1, params) +#define PRINT2(params) PRK (2, params) +#define PRINT3(params) PRK (3, params) + +#define PRINTM(params) \ +{ \ + if (CHOLMOD(dump_malloc) > 0) \ + { \ + printf params ; \ + } \ +} + +#define DEBUG(statement) statement + +#else + +/* Debugging disabled (the normal case) */ +#define PRK(k,params) +#define DEBUG_INIT(s,Common) +#define PRINT0(params) +#define PRINT1(params) +#define PRINT2(params) +#define PRINT3(params) +#define PRINTM(params) +#define ASSERT(expression) +#define DEBUG(statement) +#endif + +#endif diff --git a/src/CHOLMOD/Include/cholmod_io64.h b/src/CHOLMOD/Include/cholmod_io64.h new file mode 100644 index 0000000..1964418 --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_io64.h @@ -0,0 +1,45 @@ +/* ========================================================================== */ +/* === Include/cholmod_io64 ================================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_io64.h. + * Copyright (C) 2005-2006, Univ. of Florida. Author: Timothy A. Davis + * CHOLMOD/Include/cholmod_io64.h is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* Definitions required for large file I/O, which must come before any other + * #includes. These are not used if -DNLARGEFILE is defined at compile time. + * Large file support may not be portable across all platforms and compilers; + * if you encounter an error here, compile your code with -DNLARGEFILE. In + * particular, you must use -DNLARGEFILE for MATLAB 6.5 or earlier (which does + * not have the io64.h include file). + */ + +#ifndef CHOLMOD_IO_H +#define CHOLMOD_IO_H + +/* skip all of this if NLARGEFILE is defined at the compiler command line */ +#ifndef NLARGEFILE + +#if defined(MATLAB_MEX_FILE) || defined(MATHWORKS) + +/* CHOLMOD is being compiled as a MATLAB mexFunction, or for use in MATLAB */ +#include "io64.h" + +#else + +/* CHOLMOD is being compiled in a stand-alone library */ +#undef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE +#undef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 64 + +#endif + +#endif + +#endif + diff --git a/src/CHOLMOD/Include/cholmod_matrixops.h b/src/CHOLMOD/Include/cholmod_matrixops.h new file mode 100644 index 0000000..7cce7b2 --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_matrixops.h @@ -0,0 +1,237 @@ +/* ========================================================================== */ +/* === Include/cholmod_matrixops.h ========================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_matrixops.h. + * Copyright (C) 2005-2006, Timothy A. Davis + * CHOLMOD/Include/cholmod_matrixops.h is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* CHOLMOD MatrixOps module. + * + * Basic operations on sparse and dense matrices. + * + * cholmod_drop A = entries in A with abs. value >= tol + * cholmod_norm_dense s = norm (X), 1-norm, inf-norm, or 2-norm + * cholmod_norm_sparse s = norm (A), 1-norm or inf-norm + * cholmod_horzcat C = [A,B] + * cholmod_scale A = diag(s)*A, A*diag(s), s*A or diag(s)*A*diag(s) + * cholmod_sdmult Y = alpha*(A*X) + beta*Y or alpha*(A'*X) + beta*Y + * cholmod_ssmult C = A*B + * cholmod_submatrix C = A (i,j), where i and j are arbitrary vectors + * cholmod_vertcat C = [A ; B] + * + * A, B, C: sparse matrices (cholmod_sparse) + * X, Y: dense matrices (cholmod_dense) + * s: scalar or vector + * + * Requires the Core module. Not required by any other CHOLMOD module. + */ + +#ifndef CHOLMOD_MATRIXOPS_H +#define CHOLMOD_MATRIXOPS_H + +#include "cholmod_core.h" + +/* -------------------------------------------------------------------------- */ +/* cholmod_drop: drop entries with small absolute value */ +/* -------------------------------------------------------------------------- */ + +int cholmod_drop +( + /* ---- input ---- */ + double tol, /* keep entries with absolute value > tol */ + /* ---- in/out --- */ + cholmod_sparse *A, /* matrix to drop entries from */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_drop (double, cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_norm_dense: s = norm (X), 1-norm, inf-norm, or 2-norm */ +/* -------------------------------------------------------------------------- */ + +double cholmod_norm_dense +( + /* ---- input ---- */ + cholmod_dense *X, /* matrix to compute the norm of */ + int norm, /* type of norm: 0: inf. norm, 1: 1-norm, 2: 2-norm */ + /* --------------- */ + cholmod_common *Common +) ; + +double cholmod_l_norm_dense (cholmod_dense *, int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_norm_sparse: s = norm (A), 1-norm or inf-norm */ +/* -------------------------------------------------------------------------- */ + +double cholmod_norm_sparse +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to compute the norm of */ + int norm, /* type of norm: 0: inf. norm, 1: 1-norm */ + /* --------------- */ + cholmod_common *Common +) ; + +double cholmod_l_norm_sparse (cholmod_sparse *, int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_horzcat: C = [A,B] */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_horzcat +( + /* ---- input ---- */ + cholmod_sparse *A, /* left matrix to concatenate */ + cholmod_sparse *B, /* right matrix to concatenate */ + int values, /* if TRUE compute the numerical values of C */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_horzcat (cholmod_sparse *, cholmod_sparse *, int, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_scale: A = diag(s)*A, A*diag(s), s*A or diag(s)*A*diag(s) */ +/* -------------------------------------------------------------------------- */ + +/* scaling modes, selected by the scale input parameter: */ +#define CHOLMOD_SCALAR 0 /* A = s*A */ +#define CHOLMOD_ROW 1 /* A = diag(s)*A */ +#define CHOLMOD_COL 2 /* A = A*diag(s) */ +#define CHOLMOD_SYM 3 /* A = diag(s)*A*diag(s) */ + +int cholmod_scale +( + /* ---- input ---- */ + cholmod_dense *S, /* scale factors (scalar or vector) */ + int scale, /* type of scaling to compute */ + /* ---- in/out --- */ + cholmod_sparse *A, /* matrix to scale */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_scale (cholmod_dense *, int, cholmod_sparse *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_sdmult: Y = alpha*(A*X) + beta*Y or alpha*(A'*X) + beta*Y */ +/* -------------------------------------------------------------------------- */ + +/* Sparse matrix times dense matrix */ + +int cholmod_sdmult +( + /* ---- input ---- */ + cholmod_sparse *A, /* sparse matrix to multiply */ + int transpose, /* use A if 0, or A' otherwise */ + double alpha [2], /* scale factor for A */ + double beta [2], /* scale factor for Y */ + cholmod_dense *X, /* dense matrix to multiply */ + /* ---- in/out --- */ + cholmod_dense *Y, /* resulting dense matrix */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_sdmult (cholmod_sparse *, int, double *, double *, + cholmod_dense *, cholmod_dense *Y, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_ssmult: C = A*B */ +/* -------------------------------------------------------------------------- */ + +/* Sparse matrix times sparse matrix */ + +cholmod_sparse *cholmod_ssmult +( + /* ---- input ---- */ + cholmod_sparse *A, /* left matrix to multiply */ + cholmod_sparse *B, /* right matrix to multiply */ + int stype, /* requested stype of C */ + int values, /* TRUE: do numerical values, FALSE: pattern only */ + int sorted, /* if TRUE then return C with sorted columns */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_ssmult (cholmod_sparse *, cholmod_sparse *, int, int, + int, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_submatrix: C = A (r,c), where i and j are arbitrary vectors */ +/* -------------------------------------------------------------------------- */ + +/* rsize < 0 denotes ":" in MATLAB notation, or more precisely 0:(A->nrow)-1. + * In this case, r can be NULL. An rsize of zero, or r = NULL and rsize >= 0, + * denotes "[ ]" in MATLAB notation (the empty set). + * Similar rules hold for csize. + */ + +cholmod_sparse *cholmod_submatrix +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to subreference */ + int *rset, /* set of row indices, duplicates OK */ + SuiteSparse_long rsize, /* size of r; rsize < 0 denotes ":" */ + int *cset, /* set of column indices, duplicates OK */ + SuiteSparse_long csize, /* size of c; csize < 0 denotes ":" */ + int values, /* if TRUE compute the numerical values of C */ + int sorted, /* if TRUE then return C with sorted columns */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_submatrix (cholmod_sparse *, SuiteSparse_long *, + SuiteSparse_long, SuiteSparse_long *, SuiteSparse_long, int, int, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_vertcat: C = [A ; B] */ +/* -------------------------------------------------------------------------- */ + +cholmod_sparse *cholmod_vertcat +( + /* ---- input ---- */ + cholmod_sparse *A, /* left matrix to concatenate */ + cholmod_sparse *B, /* right matrix to concatenate */ + int values, /* if TRUE compute the numerical values of C */ + /* --------------- */ + cholmod_common *Common +) ; + +cholmod_sparse *cholmod_l_vertcat (cholmod_sparse *, cholmod_sparse *, int, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_symmetry: determine if a sparse matrix is symmetric */ +/* -------------------------------------------------------------------------- */ + +int cholmod_symmetry +( + /* ---- input ---- */ + cholmod_sparse *A, + int option, + /* ---- output ---- */ + int *xmatched, + int *pmatched, + int *nzoffdiag, + int *nzdiag, + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_symmetry (cholmod_sparse *, int, SuiteSparse_long *, + SuiteSparse_long *, SuiteSparse_long *, SuiteSparse_long *, + cholmod_common *) ; + +#endif diff --git a/src/CHOLMOD/Include/cholmod_modify.h b/src/CHOLMOD/Include/cholmod_modify.h new file mode 100644 index 0000000..12a884c --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_modify.h @@ -0,0 +1,306 @@ +/* ========================================================================== */ +/* === Include/cholmod_modify.h ============================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_modify.h. + * Copyright (C) 2005-2006, Timothy A. Davis and William W. Hager + * CHOLMOD/Include/cholmod_modify.h is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* CHOLMOD Modify module. + * + * Sparse Cholesky modification routines: update / downdate / rowadd / rowdel. + * Can also modify a corresponding solution to Lx=b when L is modified. This + * module is most useful when applied on a Cholesky factorization computed by + * the Cholesky module, but it does not actually require the Cholesky module. + * The Core module can create an identity Cholesky factorization (LDL' where + * L=D=I) that can then by modified by these routines. + * + * Primary routines: + * ----------------- + * + * cholmod_updown multiple rank update/downdate + * cholmod_rowadd add a row to an LDL' factorization + * cholmod_rowdel delete a row from an LDL' factorization + * + * Secondary routines: + * ------------------- + * + * cholmod_updown_solve update/downdate, and modify solution to Lx=b + * cholmod_updown_mark update/downdate, and modify solution to partial Lx=b + * cholmod_updown_mask update/downdate for LPDASA + * cholmod_rowadd_solve add a row, and update solution to Lx=b + * cholmod_rowadd_mark add a row, and update solution to partial Lx=b + * cholmod_rowdel_solve delete a row, and downdate Lx=b + * cholmod_rowdel_mark delete a row, and downdate solution to partial Lx=b + * + * Requires the Core module. Not required by any other CHOLMOD module. + */ + +#ifndef CHOLMOD_MODIFY_H +#define CHOLMOD_MODIFY_H + +#include "cholmod_core.h" + +/* -------------------------------------------------------------------------- */ +/* cholmod_updown: multiple rank update/downdate */ +/* -------------------------------------------------------------------------- */ + +/* Compute the new LDL' factorization of LDL'+CC' (an update) or LDL'-CC' + * (a downdate). The factor object L need not be an LDL' factorization; it + * is converted to one if it isn't. */ + +int cholmod_updown +( + /* ---- input ---- */ + int update, /* TRUE for update, FALSE for downdate */ + cholmod_sparse *C, /* the incoming sparse update */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_updown (int, cholmod_sparse *, cholmod_factor *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_updown_solve: update/downdate, and modify solution to Lx=b */ +/* -------------------------------------------------------------------------- */ + +/* Does the same as cholmod_updown, except that it also updates/downdates the + * solution to Lx=b+DeltaB. x and b must be n-by-1 dense matrices. b is not + * need as input to this routine, but a sparse change to b is (DeltaB). Only + * entries in DeltaB corresponding to columns modified in L are accessed; the + * rest must be zero. */ + +int cholmod_updown_solve +( + /* ---- input ---- */ + int update, /* TRUE for update, FALSE for downdate */ + cholmod_sparse *C, /* the incoming sparse update */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_updown_solve (int, cholmod_sparse *, cholmod_factor *, + cholmod_dense *, cholmod_dense *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_updown_mark: update/downdate, and modify solution to partial Lx=b */ +/* -------------------------------------------------------------------------- */ + +/* Does the same as cholmod_updown_solve, except only part of L is used in + * the update/downdate of the solution to Lx=b. This routine is an "expert" + * routine. It is meant for use in LPDASA only. See cholmod_updown.c for + * a description of colmark. */ + +int cholmod_updown_mark +( + /* ---- input ---- */ + int update, /* TRUE for update, FALSE for downdate */ + cholmod_sparse *C, /* the incoming sparse update */ + int *colmark, /* int array of size n. See cholmod_updown.c */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_updown_mark (int, cholmod_sparse *, SuiteSparse_long *, + cholmod_factor *, cholmod_dense *, cholmod_dense *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_updown_mask: update/downdate, for LPDASA */ +/* -------------------------------------------------------------------------- */ + +/* Does the same as cholmod_updown_mark, except has an additional "mask" + * argument. This routine is an "expert" routine. It is meant for use in + * LPDASA only. See cholmod_updown.c for a description of mask. */ + +int cholmod_updown_mask +( + /* ---- input ---- */ + int update, /* TRUE for update, FALSE for downdate */ + cholmod_sparse *C, /* the incoming sparse update */ + int *colmark, /* int array of size n. See cholmod_updown.c */ + int *mask, /* size n */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_updown_mask (int, cholmod_sparse *, SuiteSparse_long *, + SuiteSparse_long *, cholmod_factor *, cholmod_dense *, cholmod_dense *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_rowadd: add a row to an LDL' factorization (a rank-2 update) */ +/* -------------------------------------------------------------------------- */ + +/* cholmod_rowadd adds a row to the LDL' factorization. It computes the kth + * row and kth column of L, and then updates the submatrix L (k+1:n,k+1:n) + * accordingly. The kth row and column of L must originally be equal to the + * kth row and column of the identity matrix. The kth row/column of L is + * computed as the factorization of the kth row/column of the matrix to + * factorize, which is provided as a single n-by-1 sparse matrix R. */ + +int cholmod_rowadd +( + /* ---- input ---- */ + size_t k, /* row/column index to add */ + cholmod_sparse *R, /* row/column of matrix to factorize (n-by-1) */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_rowadd (size_t, cholmod_sparse *, cholmod_factor *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_rowadd_solve: add a row, and update solution to Lx=b */ +/* -------------------------------------------------------------------------- */ + +/* Does the same as cholmod_rowadd, and also updates the solution to Lx=b + * See cholmod_updown for a description of how Lx=b is updated. There is on + * additional parameter: bk specifies the new kth entry of b. */ + +int cholmod_rowadd_solve +( + /* ---- input ---- */ + size_t k, /* row/column index to add */ + cholmod_sparse *R, /* row/column of matrix to factorize (n-by-1) */ + double bk [2], /* kth entry of the right-hand-side b */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_rowadd_solve (size_t, cholmod_sparse *, double *, + cholmod_factor *, cholmod_dense *, cholmod_dense *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_rowadd_mark: add a row, and update solution to partial Lx=b */ +/* -------------------------------------------------------------------------- */ + +/* Does the same as cholmod_rowadd_solve, except only part of L is used in + * the update/downdate of the solution to Lx=b. This routine is an "expert" + * routine. It is meant for use in LPDASA only. */ + +int cholmod_rowadd_mark +( + /* ---- input ---- */ + size_t k, /* row/column index to add */ + cholmod_sparse *R, /* row/column of matrix to factorize (n-by-1) */ + double bk [2], /* kth entry of the right hand side, b */ + int *colmark, /* int array of size n. See cholmod_updown.c */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_rowadd_mark (size_t, cholmod_sparse *, double *, + SuiteSparse_long *, cholmod_factor *, cholmod_dense *, cholmod_dense *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_rowdel: delete a row from an LDL' factorization (a rank-2 update) */ +/* -------------------------------------------------------------------------- */ + +/* Sets the kth row and column of L to be the kth row and column of the identity + * matrix, and updates L(k+1:n,k+1:n) accordingly. To reduce the running time, + * the caller can optionally provide the nonzero pattern (or an upper bound) of + * kth row of L, as the sparse n-by-1 vector R. Provide R as NULL if you want + * CHOLMOD to determine this itself, which is easier for the caller, but takes + * a little more time. + */ + +int cholmod_rowdel +( + /* ---- input ---- */ + size_t k, /* row/column index to delete */ + cholmod_sparse *R, /* NULL, or the nonzero pattern of kth row of L */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_rowdel (size_t, cholmod_sparse *, cholmod_factor *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_rowdel_solve: delete a row, and downdate Lx=b */ +/* -------------------------------------------------------------------------- */ + +/* Does the same as cholmod_rowdel, but also downdates the solution to Lx=b. + * When row/column k of A is "deleted" from the system A*y=b, this can induce + * a change to x, in addition to changes arising when L and b are modified. + * If this is the case, the kth entry of y is required as input (yk) */ + +int cholmod_rowdel_solve +( + /* ---- input ---- */ + size_t k, /* row/column index to delete */ + cholmod_sparse *R, /* NULL, or the nonzero pattern of kth row of L */ + double yk [2], /* kth entry in the solution to A*y=b */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_rowdel_solve (size_t, cholmod_sparse *, double *, + cholmod_factor *, cholmod_dense *, cholmod_dense *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_rowdel_mark: delete a row, and downdate solution to partial Lx=b */ +/* -------------------------------------------------------------------------- */ + +/* Does the same as cholmod_rowdel_solve, except only part of L is used in + * the update/downdate of the solution to Lx=b. This routine is an "expert" + * routine. It is meant for use in LPDASA only. */ + +int cholmod_rowdel_mark +( + /* ---- input ---- */ + size_t k, /* row/column index to delete */ + cholmod_sparse *R, /* NULL, or the nonzero pattern of kth row of L */ + double yk [2], /* kth entry in the solution to A*y=b */ + int *colmark, /* int array of size n. See cholmod_updown.c */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_rowdel_mark (size_t, cholmod_sparse *, double *, + SuiteSparse_long *, cholmod_factor *, cholmod_dense *, cholmod_dense *, + cholmod_common *) ; + +#endif diff --git a/src/CHOLMOD/Include/cholmod_partition.h b/src/CHOLMOD/Include/cholmod_partition.h new file mode 100644 index 0000000..1e8ecd3 --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_partition.h @@ -0,0 +1,166 @@ +/* ========================================================================== */ +/* === Include/cholmod_partition.h ========================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_partition.h. + * Copyright (C) 2005-2013, Univ. of Florida. Author: Timothy A. Davis + * CHOLMOD/Include/cholmod_partition.h is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD Partition module. + * + * Graph partitioning and graph-partition-based orderings. Includes an + * interface to CCOLAMD and CSYMAMD, constrained minimum degree ordering + * methods which order a matrix following constraints determined via nested + * dissection. + * + * These functions require METIS: + * cholmod_nested_dissection CHOLMOD nested dissection ordering + * cholmod_metis METIS nested dissection ordering (METIS_NodeND) + * cholmod_bisect graph partitioner (currently based on METIS) + * cholmod_metis_bisector direct interface to METIS_NodeComputeSeparator + * + * Requires the Core and Cholesky modules, and three packages: METIS, CAMD, + * and CCOLAMD. Optionally used by the Cholesky module. + * + * Note that METIS does not have a version that uses SuiteSparse_long integers. + * If you try to use cholmod_nested_dissection, cholmod_metis, cholmod_bisect, + * or cholmod_metis_bisector on a matrix that is too large, an error code will + * be returned. METIS does have an "idxtype", which could be redefined as + * SuiteSparse_long, if you wish to edit METIS or use compile-time flags to + * redefine idxtype. + */ + +#ifndef CHOLMOD_PARTITION_H +#define CHOLMOD_PARTITION_H + +#include "cholmod_core.h" +#include "cholmod_camd.h" + +/* -------------------------------------------------------------------------- */ +/* cholmod_nested_dissection */ +/* -------------------------------------------------------------------------- */ + +/* Order A, AA', or A(:,f)*A(:,f)' using CHOLMOD's nested dissection method + * (METIS's node bisector applied recursively to compute the separator tree + * and constraint sets, followed by CCOLAMD using the constraints). Usually + * finds better orderings than METIS_NodeND, but takes longer. + */ + +SuiteSparse_long cholmod_nested_dissection /* returns # of components */ +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* ---- output --- */ + int *Perm, /* size A->nrow, output permutation */ + int *CParent, /* size A->nrow. On output, CParent [c] is the parent + * of component c, or EMPTY if c is a root, and where + * c is in the range 0 to # of components minus 1 */ + int *Cmember, /* size A->nrow. Cmember [j] = c if node j of A is + * in component c */ + /* --------------- */ + cholmod_common *Common +) ; + +SuiteSparse_long cholmod_l_nested_dissection (cholmod_sparse *, + SuiteSparse_long *, size_t, SuiteSparse_long *, SuiteSparse_long *, + SuiteSparse_long *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_metis */ +/* -------------------------------------------------------------------------- */ + +/* Order A, AA', or A(:,f)*A(:,f)' using METIS_NodeND. */ + +int cholmod_metis +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int postorder, /* if TRUE, follow with etree or coletree postorder */ + /* ---- output --- */ + int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_metis (cholmod_sparse *, SuiteSparse_long *, size_t, int, + SuiteSparse_long *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_bisect */ +/* -------------------------------------------------------------------------- */ + +/* Finds a node bisector of A, A*A', A(:,f)*A(:,f)'. */ + +SuiteSparse_long cholmod_bisect /* returns # of nodes in separator */ +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to bisect */ + int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int compress, /* if TRUE, compress the graph first */ + /* ---- output --- */ + int *Partition, /* size A->nrow. Node i is in the left graph if + * Partition [i] = 0, the right graph if 1, and in the + * separator if 2. */ + /* --------------- */ + cholmod_common *Common +) ; + +SuiteSparse_long cholmod_l_bisect (cholmod_sparse *, SuiteSparse_long *, + size_t, int, SuiteSparse_long *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_metis_bisector */ +/* -------------------------------------------------------------------------- */ + +/* Find a set of nodes that bisects the graph of A or AA' (direct interface + * to METIS_NodeComputeSeparator). */ + +SuiteSparse_long cholmod_metis_bisector /* returns separator size */ +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to bisect */ + int *Anw, /* size A->nrow, node weights */ + int *Aew, /* size nz, edge weights */ + /* ---- output --- */ + int *Partition, /* size A->nrow. see cholmod_bisect above. */ + /* --------------- */ + cholmod_common *Common +) ; + +SuiteSparse_long cholmod_l_metis_bisector (cholmod_sparse *, + SuiteSparse_long *, SuiteSparse_long *, SuiteSparse_long *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_collapse_septree */ +/* -------------------------------------------------------------------------- */ + +/* Collapse nodes in a separator tree. */ + +SuiteSparse_long cholmod_collapse_septree +( + /* ---- input ---- */ + size_t n, /* # of nodes in the graph */ + size_t ncomponents, /* # of nodes in the separator tree (must be <= n) */ + double nd_oksep, /* collapse if #sep >= nd_oksep * #nodes in subtree */ + size_t nd_small, /* collapse if #nodes in subtree < nd_small */ + /* ---- in/out --- */ + int *CParent, /* size ncomponents; from cholmod_nested_dissection */ + int *Cmember, /* size n; from cholmod_nested_dissection */ + /* --------------- */ + cholmod_common *Common +) ; + +SuiteSparse_long cholmod_l_collapse_septree (size_t, size_t, double, size_t, + SuiteSparse_long *, SuiteSparse_long *, cholmod_common *) ; + +#endif diff --git a/src/CHOLMOD/Include/cholmod_supernodal.h b/src/CHOLMOD/Include/cholmod_supernodal.h new file mode 100644 index 0000000..9636168 --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_supernodal.h @@ -0,0 +1,172 @@ +/* ========================================================================== */ +/* === Include/cholmod_supernodal.h ========================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Include/cholmod_supernodal.h. + * Copyright (C) 2005-2006, Timothy A. Davis + * CHOLMOD/Include/cholmod_supernodal.h is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* CHOLMOD Supernodal module. + * + * Supernodal analysis, factorization, and solve. The simplest way to use + * these routines is via the Cholesky module. It does not provide any + * fill-reducing orderings, but does accept the orderings computed by the + * Cholesky module. It does not require the Cholesky module itself, however. + * + * Primary routines: + * ----------------- + * cholmod_super_symbolic supernodal symbolic analysis + * cholmod_super_numeric supernodal numeric factorization + * cholmod_super_lsolve supernodal Lx=b solve + * cholmod_super_ltsolve supernodal L'x=b solve + * + * Prototypes for the BLAS and LAPACK routines that CHOLMOD uses are listed + * below, including how they are used in CHOLMOD. + * + * BLAS routines: + * -------------- + * dtrsv solve Lx=b or L'x=b, L non-unit diagonal, x and b stride-1 + * dtrsm solve LX=B or L'X=b, L non-unit diagonal + * dgemv y=y-A*x or y=y-A'*x (x and y stride-1) + * dgemm C=A*B', C=C-A*B, or C=C-A'*B + * dsyrk C=tril(A*A') + * + * LAPACK routines: + * ---------------- + * dpotrf LAPACK: A=chol(tril(A)) + * + * Requires the Core module, and two external packages: LAPACK and the BLAS. + * Optionally used by the Cholesky module. + */ + +#ifndef CHOLMOD_SUPERNODAL_H +#define CHOLMOD_SUPERNODAL_H + +#include "cholmod_core.h" + +/* -------------------------------------------------------------------------- */ +/* cholmod_super_symbolic */ +/* -------------------------------------------------------------------------- */ + +/* Analyzes A, AA', or A(:,f)*A(:,f)' in preparation for a supernodal numeric + * factorization. The user need not call this directly; cholmod_analyze is + * a "simple" wrapper for this routine. + */ + +int cholmod_super_symbolic +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + cholmod_sparse *F, /* F = A' or A(:,f)' */ + int *Parent, /* elimination tree */ + /* ---- in/out --- */ + cholmod_factor *L, /* simplicial symbolic on input, + * supernodal symbolic on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_super_symbolic (cholmod_sparse *, cholmod_sparse *, + SuiteSparse_long *, cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_super_symbolic2 */ +/* -------------------------------------------------------------------------- */ + +/* Analyze for supernodal Cholesky or multifrontal QR. CHOLMOD itself always + * analyzes for supernodal Cholesky, of course. This "for_cholesky = TRUE" + * option is used by SuiteSparseQR only. Added for V1.7 */ + +int cholmod_super_symbolic2 +( + /* ---- input ---- */ + int for_cholesky, /* Cholesky if TRUE, QR if FALSE */ + cholmod_sparse *A, /* matrix to analyze */ + cholmod_sparse *F, /* F = A' or A(:,f)' */ + int *Parent, /* elimination tree */ + /* ---- in/out --- */ + cholmod_factor *L, /* simplicial symbolic on input, + * supernodal symbolic on output */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_super_symbolic2 (int, cholmod_sparse *, cholmod_sparse *, + SuiteSparse_long *, cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_super_numeric */ +/* -------------------------------------------------------------------------- */ + +/* Computes the numeric LL' factorization of A, AA', or A(:,f)*A(:,f)' using + * a BLAS-based supernodal method. The user need not call this directly; + * cholmod_factorize is a "simple" wrapper for this routine. + */ + +int cholmod_super_numeric +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + cholmod_sparse *F, /* F = A' or A(:,f)' */ + double beta [2], /* beta*I is added to diagonal of matrix to factorize */ + /* ---- in/out --- */ + cholmod_factor *L, /* factorization */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_super_numeric (cholmod_sparse *, cholmod_sparse *, double *, + cholmod_factor *, cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_super_lsolve */ +/* -------------------------------------------------------------------------- */ + +/* Solve Lx=b where L is from a supernodal numeric factorization. The user + * need not call this routine directly. cholmod_solve is a "simple" wrapper + * for this routine. */ + +int cholmod_super_lsolve +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to use for the forward solve */ + /* ---- output ---- */ + cholmod_dense *X, /* b on input, solution to Lx=b on output */ + /* ---- workspace */ + cholmod_dense *E, /* workspace of size nrhs*(L->maxesize) */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_super_lsolve (cholmod_factor *, cholmod_dense *, cholmod_dense *, + cholmod_common *) ; + +/* -------------------------------------------------------------------------- */ +/* cholmod_super_ltsolve */ +/* -------------------------------------------------------------------------- */ + +/* Solve L'x=b where L is from a supernodal numeric factorization. The user + * need not call this routine directly. cholmod_solve is a "simple" wrapper + * for this routine. */ + +int cholmod_super_ltsolve +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to use for the backsolve */ + /* ---- output ---- */ + cholmod_dense *X, /* b on input, solution to L'x=b on output */ + /* ---- workspace */ + cholmod_dense *E, /* workspace of size nrhs*(L->maxesize) */ + /* --------------- */ + cholmod_common *Common +) ; + +int cholmod_l_super_ltsolve (cholmod_factor *, cholmod_dense *, cholmod_dense *, + cholmod_common *) ; + +#endif diff --git a/src/CHOLMOD/Include/cholmod_template.h b/src/CHOLMOD/Include/cholmod_template.h new file mode 100644 index 0000000..aa45b4d --- /dev/null +++ b/src/CHOLMOD/Include/cholmod_template.h @@ -0,0 +1,238 @@ +/* ========================================================================== */ +/* === Include/cholmod_template.h =========================================== */ +/* ========================================================================== */ + +/* -------------------------------------------------------------------------- */ +/* undefine current xtype macros, and then define macros for current type */ +/* -------------------------------------------------------------------------- */ + +#undef TEMPLATE +#undef XTYPE +#undef XTYPE2 +#undef XTYPE_OK +#undef ENTRY_IS_NONZERO +#undef ENTRY_IS_ZERO +#undef ENTRY_IS_ONE +#undef IMAG_IS_NONZERO + +#undef ASSEMBLE +#undef ASSIGN +#undef ASSIGN_CONJ +#undef ASSIGN2 +#undef ASSIGN2_CONJ +#undef ASSIGN_REAL +#undef MULT +#undef MULTADD +#undef ADD +#undef ADD_REAL +#undef MULTSUB +#undef MULTADDCONJ +#undef MULTSUBCONJ +#undef LLDOT +#undef CLEAR +#undef DIV +#undef DIV_REAL +#undef MULT_REAL +#undef CLEAR_IMAG +#undef LDLDOT +#undef PREFIX + +#undef ENTRY_SIZE + +#undef XPRINT0 +#undef XPRINT1 +#undef XPRINT2 +#undef XPRINT3 + +/* -------------------------------------------------------------------------- */ +/* pattern */ +/* -------------------------------------------------------------------------- */ + + +#ifdef PATTERN + +#define PREFIX p_ +#define TEMPLATE(name) P_TEMPLATE(name) +#define XTYPE CHOLMOD_PATTERN +#define XTYPE2 CHOLMOD_REAL +#define XTYPE_OK(type) (TRUE) +#define ENTRY_IS_NONZERO(ax,az,q) (TRUE) +#define ENTRY_IS_ZERO(ax,az,q) (FALSE) +#define ENTRY_IS_ONE(ax,az,q) (TRUE) +#define IMAG_IS_NONZERO(ax,az,q) (FALSE) +#define ENTRY_SIZE 0 + +#define ASSEMBLE(x,z,p,ax,az,q) +#define ASSIGN(x,z,p,ax,az,q) +#define ASSIGN_CONJ(x,z,p,ax,az,q) +#define ASSIGN2(x,z,p,ax,az,q) P_ASSIGN2(x,z,p,ax,az,q) +#define ASSIGN2_CONJ(x,z,p,ax,az,q) P_ASSIGN2(x,z,p,ax,az,q) +#define ASSIGN_REAL(x,p,ax,q) +#define MULT(x,z,p,ax,az,q,bx,bz,pb) +#define MULTADD(x,z,p,ax,az,q,bx,bz,pb) +#define ADD(x,z,p,ax,az,q,bx,bz,pb) +#define ADD_REAL(x,p, ax,q, bx,r) +#define MULTSUB(x,z,p,ax,az,q,bx,bz,pb) +#define MULTADDCONJ(x,z,p,ax,az,q,bx,bz,pb) +#define MULTSUBCONJ(x,z,p,ax,az,q,bx,bz,pb) +#define LLDOT(x,p,ax,az,q) +#define CLEAR(x,z,p) +#define CLEAR_IMAG(x,z,p) +#define DIV(x,z,p,ax,az,q) +#define DIV_REAL(x,z,p, ax,az,q, bx,r) +#define MULT_REAL(x,z,p, ax,az,q, bx,r) +#define LDLDOT(x,p, ax,az,q, bx,r) + +#define XPRINT0(x,z,p) P_PRINT(0,x,z,p) +#define XPRINT1(x,z,p) P_PRINT(1,x,z,p) +#define XPRINT2(x,z,p) P_PRINT(2,x,z,p) +#define XPRINT3(x,z,p) P_PRINT(3,x,z,p) + +/* -------------------------------------------------------------------------- */ +/* real */ +/* -------------------------------------------------------------------------- */ + +#elif defined (REAL) + +#define PREFIX r_ +#define TEMPLATE(name) R_TEMPLATE(name) +#define XTYPE CHOLMOD_REAL +#define XTYPE2 CHOLMOD_REAL +#define XTYPE_OK(type) R_XTYPE_OK(type) +#define ENTRY_IS_NONZERO(ax,az,q) R_IS_NONZERO(ax,az,q) +#define ENTRY_IS_ZERO(ax,az,q) R_IS_ZERO(ax,az,q) +#define ENTRY_IS_ONE(ax,az,q) R_IS_ONE(ax,az,q) +#define IMAG_IS_NONZERO(ax,az,q) (FALSE) +#define ENTRY_SIZE 1 + +#define ASSEMBLE(x,z,p,ax,az,q) R_ASSEMBLE(x,z,p,ax,az,q) +#define ASSIGN(x,z,p,ax,az,q) R_ASSIGN(x,z,p,ax,az,q) +#define ASSIGN_CONJ(x,z,p,ax,az,q) R_ASSIGN(x,z,p,ax,az,q) +#define ASSIGN2(x,z,p,ax,az,q) R_ASSIGN(x,z,p,ax,az,q) +#define ASSIGN2_CONJ(x,z,p,ax,az,q) R_ASSIGN(x,z,p,ax,az,q) +#define ASSIGN_REAL(x,p,ax,q) R_ASSIGN_REAL(x,p,ax,q) +#define MULT(x,z,p,ax,az,q,bx,bz,pb) R_MULT(x,z,p,ax,az,q,bx,bz,pb) +#define MULTADD(x,z,p,ax,az,q,bx,bz,pb) R_MULTADD(x,z,p,ax,az,q,bx,bz,pb) +#define ADD(x,z,p,ax,az,q,bx,bz,pb) R_ADD(x,z,p,ax,az,q,bx,bz,pb) +#define ADD_REAL(x,p, ax,q, bx,r) R_ADD_REAL(x,p, ax,q, bx,r) +#define MULTSUB(x,z,p,ax,az,q,bx,bz,pb) R_MULTSUB(x,z,p,ax,az,q,bx,bz,pb) +#define MULTADDCONJ(x,z,p,ax,az,q,bx,bz,pb) \ + R_MULTADDCONJ(x,z,p,ax,az,q,bx,bz,pb) +#define MULTSUBCONJ(x,z,p,ax,az,q,bx,bz,pb) \ + R_MULTSUBCONJ(x,z,p,ax,az,q,bx,bz,pb) +#define LLDOT(x,p,ax,az,q) R_LLDOT(x,p,ax,az,q) +#define CLEAR(x,z,p) R_CLEAR(x,z,p) +#define CLEAR_IMAG(x,z,p) R_CLEAR_IMAG(x,z,p) +#define DIV(x,z,p,ax,az,q) R_DIV(x,z,p,ax,az,q) +#define DIV_REAL(x,z,p, ax,az,q, bx,r) R_DIV_REAL(x,z,p, ax,az,q, bx,r) +#define MULT_REAL(x,z,p, ax,az,q, bx,r) R_MULT_REAL(x,z,p, ax,az,q, bx,r) +#define LDLDOT(x,p, ax,az,q, bx,r) R_LDLDOT(x,p, ax,az,q, bx,r) + +#define XPRINT0(x,z,p) R_PRINT(0,x,z,p) +#define XPRINT1(x,z,p) R_PRINT(1,x,z,p) +#define XPRINT2(x,z,p) R_PRINT(2,x,z,p) +#define XPRINT3(x,z,p) R_PRINT(3,x,z,p) + +/* -------------------------------------------------------------------------- */ +/* complex */ +/* -------------------------------------------------------------------------- */ + +#elif defined (COMPLEX) + +#define PREFIX c_ + +#ifdef NCONJUGATE +#define TEMPLATE(name) CT_TEMPLATE(name) +#else +#define TEMPLATE(name) C_TEMPLATE(name) +#endif + +#define ASSEMBLE(x,z,p,ax,az,q) C_ASSEMBLE(x,z,p,ax,az,q) +#define ASSIGN(x,z,p,ax,az,q) C_ASSIGN(x,z,p,ax,az,q) +#define ASSIGN_CONJ(x,z,p,ax,az,q) C_ASSIGN_CONJ(x,z,p,ax,az,q) +#define ASSIGN2(x,z,p,ax,az,q) C_ASSIGN(x,z,p,ax,az,q) +#define ASSIGN2_CONJ(x,z,p,ax,az,q) C_ASSIGN_CONJ(x,z,p,ax,az,q) +#define ASSIGN_REAL(x,p,ax,q) C_ASSIGN_REAL(x,p,ax,q) +#define XTYPE CHOLMOD_COMPLEX +#define XTYPE2 CHOLMOD_COMPLEX +#define XTYPE_OK(type) C_XTYPE_OK(type) +#define ENTRY_IS_NONZERO(ax,az,q) C_IS_NONZERO(ax,az,q) +#define ENTRY_IS_ZERO(ax,az,q) C_IS_ZERO(ax,az,q) +#define ENTRY_IS_ONE(ax,az,q) C_IS_ONE(ax,az,q) +#define IMAG_IS_NONZERO(ax,az,q) C_IMAG_IS_NONZERO(ax,az,q) +#define ENTRY_SIZE 2 + +#define MULTADD(x,z,p,ax,az,q,bx,bz,pb) C_MULTADD(x,z,p,ax,az,q,bx,bz,pb) +#define MULT(x,z,p,ax,az,q,bx,bz,pb) C_MULT(x,z,p,ax,az,q,bx,bz,pb) +#define ADD(x,z,p,ax,az,q,bx,bz,pb) C_ADD(x,z,p,ax,az,q,bx,bz,pb) +#define ADD_REAL(x,p, ax,q, bx,r) C_ADD_REAL(x,p, ax,q, bx,r) +#define MULTSUB(x,z,p,ax,az,q,bx,bz,pb) C_MULTSUB(x,z,p,ax,az,q,bx,bz,pb) +#define MULTADDCONJ(x,z,p,ax,az,q,bx,bz,pb) \ + C_MULTADDCONJ(x,z,p,ax,az,q,bx,bz,pb) +#define MULTSUBCONJ(x,z,p,ax,az,q,bx,bz,pb) \ + C_MULTSUBCONJ(x,z,p,ax,az,q,bx,bz,pb) +#define LLDOT(x,p,ax,az,q) C_LLDOT(x,p,ax,az,q) +#define CLEAR(x,z,p) C_CLEAR(x,z,p) +#define CLEAR_IMAG(x,z,p) C_CLEAR_IMAG(x,z,p) +#define DIV(x,z,p,ax,az,q) C_DIV(x,z,p,ax,az,q) +#define DIV_REAL(x,z,p, ax,az,q, bx,r) C_DIV_REAL(x,z,p, ax,az,q, bx,r) +#define MULT_REAL(x,z,p, ax,az,q, bx,r) C_MULT_REAL(x,z,p, ax,az,q, bx,r) +#define LDLDOT(x,p, ax,az,q, bx,r) C_LDLDOT(x,p, ax,az,q, bx,r) + +#define XPRINT0(x,z,p) C_PRINT(0,x,z,p) +#define XPRINT1(x,z,p) C_PRINT(1,x,z,p) +#define XPRINT2(x,z,p) C_PRINT(2,x,z,p) +#define XPRINT3(x,z,p) C_PRINT(3,x,z,p) + +/* -------------------------------------------------------------------------- */ +/* zomplex */ +/* -------------------------------------------------------------------------- */ + +#elif defined (ZOMPLEX) + +#define PREFIX z_ + +#ifdef NCONJUGATE +#define TEMPLATE(name) ZT_TEMPLATE(name) +#else +#define TEMPLATE(name) Z_TEMPLATE(name) +#endif + +#define ASSEMBLE(x,z,p,ax,az,q) Z_ASSEMBLE(x,z,p,ax,az,q) +#define ASSIGN(x,z,p,ax,az,q) Z_ASSIGN(x,z,p,ax,az,q) +#define ASSIGN_CONJ(x,z,p,ax,az,q) Z_ASSIGN_CONJ(x,z,p,ax,az,q) +#define ASSIGN2(x,z,p,ax,az,q) Z_ASSIGN(x,z,p,ax,az,q) +#define ASSIGN2_CONJ(x,z,p,ax,az,q) Z_ASSIGN_CONJ(x,z,p,ax,az,q) +#define ASSIGN_REAL(x,p,ax,q) Z_ASSIGN_REAL(x,p,ax,q) +#define XTYPE CHOLMOD_ZOMPLEX +#define XTYPE2 CHOLMOD_ZOMPLEX +#define XTYPE_OK(type) Z_XTYPE_OK(type) +#define ENTRY_IS_NONZERO(ax,az,q) Z_IS_NONZERO(ax,az,q) +#define ENTRY_IS_ZERO(ax,az,q) Z_IS_ZERO(ax,az,q) +#define ENTRY_IS_ONE(ax,az,q) Z_IS_ONE(ax,az,q) +#define IMAG_IS_NONZERO(ax,az,q) Z_IMAG_IS_NONZERO(ax,az,q) +#define ENTRY_SIZE 1 + +#define MULTADD(x,z,p,ax,az,q,bx,bz,pb) Z_MULTADD(x,z,p,ax,az,q,bx,bz,pb) +#define MULT(x,z,p,ax,az,q,bx,bz,pb) Z_MULT(x,z,p,ax,az,q,bx,bz,pb) +#define ADD(x,z,p,ax,az,q,bx,bz,pb) Z_ADD(x,z,p,ax,az,q,bx,bz,pb) +#define ADD_REAL(x,p, ax,q, bx,r) Z_ADD_REAL(x,p, ax,q, bx,r) +#define MULTSUB(x,z,p,ax,az,q,bx,bz,pb) Z_MULTSUB(x,z,p,ax,az,q,bx,bz,pb) +#define MULTADDCONJ(x,z,p,ax,az,q,bx,bz,pb) \ + Z_MULTADDCONJ(x,z,p,ax,az,q,bx,bz,pb) +#define MULTSUBCONJ(x,z,p,ax,az,q,bx,bz,pb) \ + Z_MULTSUBCONJ(x,z,p,ax,az,q,bx,bz,pb) +#define LLDOT(x,p,ax,az,q) Z_LLDOT(x,p,ax,az,q) +#define CLEAR(x,z,p) Z_CLEAR(x,z,p) +#define CLEAR_IMAG(x,z,p) Z_CLEAR_IMAG(x,z,p) +#define DIV(x,z,p,ax,az,q) Z_DIV(x,z,p,ax,az,q) +#define DIV_REAL(x,z,p, ax,az,q, bx,r) Z_DIV_REAL(x,z,p, ax,az,q, bx,r) +#define MULT_REAL(x,z,p, ax,az,q, bx,r) Z_MULT_REAL(x,z,p, ax,az,q, bx,r) +#define LDLDOT(x,p, ax,az,q, bx,r) Z_LDLDOT(x,p, ax,az,q, bx,r) + +#define XPRINT0(x,z,p) Z_PRINT(0,x,z,p) +#define XPRINT1(x,z,p) Z_PRINT(1,x,z,p) +#define XPRINT2(x,z,p) Z_PRINT(2,x,z,p) +#define XPRINT3(x,z,p) Z_PRINT(3,x,z,p) + +#endif diff --git a/src/CHOLMOD/Makefile b/src/CHOLMOD/Makefile new file mode 100644 index 0000000..b6d3b5b --- /dev/null +++ b/src/CHOLMOD/Makefile @@ -0,0 +1,75 @@ +#------------------------------------------------------------------------------- +# CHOLMOD Makefile +#------------------------------------------------------------------------------- + +VERSION = 2.1.2 + +# Note: If you do not have METIS, or do not wish to use it in CHOLMOD, you must +# compile CHOLMOD with the -DNPARTITION flag. +# See ../SuiteSparse_config/SuiteSparse_config.mk . + +default: all + +include ../SuiteSparse_config/SuiteSparse_config.mk + +# Compile the C-callable libraries and the Demo programs. +all: + ( cd Demo ; $(MAKE) ) + +# Compile the C-callable libraries only. +library: + ( cd Lib ; $(MAKE) ) + +# Remove all files not in the original distribution +purge: + ( cd Tcov ; $(MAKE) purge ) + ( cd Lib ; $(MAKE) purge ) + ( cd Valgrind ; $(MAKE) dopurge ) + ( cd Demo ; $(MAKE) purge ) + ( cd Doc ; $(MAKE) purge ) + ( cd MATLAB ; $(RM) $(CLEAN) rename.h *.mex* ) + +# Remove all files not in the original distribution, except keep the +# compiled libraries. +clean: + ( cd Tcov ; $(MAKE) clean ) + ( cd Lib ; $(MAKE) clean ) + ( cd Valgrind ; $(MAKE) clean ) + ( cd Demo ; $(MAKE) clean ) + ( cd MATLAB ; $(RM) $(CLEAN) ) + +distclean: purge + +ccode: all + +# Run the test coverage suite. Takes about 40 minutes on a 3.2GHz Pentium. +# Requires Linux (gcc, gcov). +cov: + ( cd Tcov ; $(MAKE) ) + +# Run the test coverage suite using Valgrind. This takes a *** long *** time. +valgrind: + ( cd Valgrind ; $(MAKE) ) + +# Compile the C-callable libraries and the Demo programs. +demos: + ( cd Demo ; $(MAKE) ) + +# create PDF documents for the original distribution +docs: + ( cd Doc ; $(MAKE) ) + +# install CHOLMOD +install: + $(CP) Lib/libcholmod.a $(INSTALL_LIB)/libcholmod.$(VERSION).a + ( cd $(INSTALL_LIB) ; ln -sf libcholmod.$(VERSION).a libcholmod.a ) + $(CP) Include/cholmod*.h $(INSTALL_INCLUDE) + $(RM) $(INSTALL_INCLUDE)/cholmod_internal.h + chmod 644 $(INSTALL_LIB)/libcholmod*.a + chmod 644 $(INSTALL_INCLUDE)/cholmod*.h + +# uninstall CHOLMOD +uninstall: + $(RM) $(INSTALL_LIB)/libcholmod*.a + $(RM) $(INSTALL_INCLUDE)/cholmod*.h + diff --git a/src/CHOLMOD/MatrixOps/License.txt b/src/CHOLMOD/MatrixOps/License.txt new file mode 100644 index 0000000..8c23f46 --- /dev/null +++ b/src/CHOLMOD/MatrixOps/License.txt @@ -0,0 +1,25 @@ +CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, +Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + +Note that this license is for the CHOLMOD/MatrixOps module only. +All CHOLMOD modules are licensed separately. + + +-------------------------------------------------------------------------------- + + +This Module is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This Module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this Module; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/src/CHOLMOD/MatrixOps/cholmod_drop.c b/src/CHOLMOD/MatrixOps/cholmod_drop.c new file mode 100644 index 0000000..69eb0fa --- /dev/null +++ b/src/CHOLMOD/MatrixOps/cholmod_drop.c @@ -0,0 +1,183 @@ +/* ========================================================================== */ +/* === MatrixOps/cholmod_drop =============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/MatrixOps Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Drop small entries from A, and entries in the ignored part of A if A + * is symmetric. None of the matrix operations drop small numerical entries + * from a matrix, except for this one. NaN's and Inf's are kept. + * + * workspace: none + * + * Supports pattern and real matrices, complex and zomplex not supported. + */ + +#ifndef NMATRIXOPS + +#include "cholmod_internal.h" +#include "cholmod_matrixops.h" + + +/* ========================================================================== */ +/* === cholmod_drop ========================================================= */ +/* ========================================================================== */ + +int CHOLMOD(drop) +( + /* ---- input ---- */ + double tol, /* keep entries with absolute value > tol */ + /* ---- in/out --- */ + cholmod_sparse *A, /* matrix to drop entries from */ + /* --------------- */ + cholmod_common *Common +) +{ + double aij ; + double *Ax ; + Int *Ap, *Ai, *Anz ; + Int packed, i, j, nrow, ncol, p, pend, nz, values ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_REAL, FALSE) ; + Common->status = CHOLMOD_OK ; + ASSERT (CHOLMOD(dump_sparse) (A, "A predrop", Common) >= 0) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + Ai = A->i ; + Ax = A->x ; + Anz = A->nz ; + packed = A->packed ; + ncol = A->ncol ; + nrow = A->nrow ; + values = (A->xtype != CHOLMOD_PATTERN) ; + nz = 0 ; + + if (values) + { + + /* ------------------------------------------------------------------ */ + /* drop small numerical entries from A, and entries in ignored part */ + /* ------------------------------------------------------------------ */ + + if (A->stype > 0) + { + + /* -------------------------------------------------------------- */ + /* A is symmetric, with just upper triangular part stored */ + /* -------------------------------------------------------------- */ + + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + Ap [j] = nz ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + aij = Ax [p] ; + if (i <= j && (fabs (aij) > tol || IS_NAN (aij))) + { + Ai [nz] = i ; + Ax [nz] = aij ; + nz++ ; + } + } + } + + } + else if (A->stype < 0) + { + + /* -------------------------------------------------------------- */ + /* A is symmetric, with just lower triangular part stored */ + /* -------------------------------------------------------------- */ + + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + Ap [j] = nz ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + aij = Ax [p] ; + if (i >= j && (fabs (aij) > tol || IS_NAN (aij))) + { + Ai [nz] = i ; + Ax [nz] = aij ; + nz++ ; + } + } + } + } + else + { + + /* -------------------------------------------------------------- */ + /* both parts of A present, just drop small entries */ + /* -------------------------------------------------------------- */ + + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + Ap [j] = nz ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + aij = Ax [p] ; + if (fabs (aij) > tol || IS_NAN (aij)) + { + Ai [nz] = i ; + Ax [nz] = aij ; + nz++ ; + } + } + } + } + Ap [ncol] = nz ; + + /* reduce A->i and A->x in size */ + ASSERT (MAX (1,nz) <= A->nzmax) ; + CHOLMOD(reallocate_sparse) (nz, A, Common) ; + ASSERT (Common->status >= CHOLMOD_OK) ; + + } + else + { + + /* ------------------------------------------------------------------ */ + /* consider only the pattern of A */ + /* ------------------------------------------------------------------ */ + + /* Note that cholmod_band_inplace calls cholmod_reallocate_sparse */ + if (A->stype > 0) + { + CHOLMOD(band_inplace) (0, ncol, 0, A, Common) ; + } + else if (A->stype < 0) + { + CHOLMOD(band_inplace) (-nrow, 0, 0, A, Common) ; + } + } + + ASSERT (CHOLMOD(dump_sparse) (A, "A dropped", Common) >= 0) ; + return (TRUE) ; +} +#endif diff --git a/src/CHOLMOD/MatrixOps/cholmod_horzcat.c b/src/CHOLMOD/MatrixOps/cholmod_horzcat.c new file mode 100644 index 0000000..9388e9a --- /dev/null +++ b/src/CHOLMOD/MatrixOps/cholmod_horzcat.c @@ -0,0 +1,203 @@ +/* ========================================================================== */ +/* === MatrixOps/cholmod_horzcat ============================================ */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/MatrixOps Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Horizontal concatenation, C = [A , B] in MATLAB notation. + * + * A and B can be up/lo/unsym; C is unsymmetric and packed. + * A and B must have the same number of rows. + * C is sorted if both A and B are sorted. + * + * workspace: Iwork (max (A->nrow, A->ncol, B->nrow, B->ncol)). + * allocates temporary copies of A and B if they are symmetric. + * + * A and B must have the same numeric xtype, unless values is FALSE. + * A and B cannot be complex or zomplex, unless values is FALSE. + */ + +#ifndef NMATRIXOPS + +#include "cholmod_internal.h" +#include "cholmod_matrixops.h" + + +/* ========================================================================== */ +/* === cholmod_horzcat ====================================================== */ +/* ========================================================================== */ + +cholmod_sparse *CHOLMOD(horzcat) +( + /* ---- input ---- */ + cholmod_sparse *A, /* left matrix to concatenate */ + cholmod_sparse *B, /* right matrix to concatenate */ + int values, /* if TRUE compute the numerical values of C */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Ax, *Bx, *Cx ; + Int *Ap, *Ai, *Anz, *Bp, *Bi, *Bnz, *Cp, *Ci ; + cholmod_sparse *C, *A2, *B2 ; + Int apacked, bpacked, ancol, bncol, ncol, nrow, anz, bnz, nz, j, p, pend, + pdest ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + RETURN_IF_NULL (B, NULL) ; + values = values && + (A->xtype != CHOLMOD_PATTERN) && (B->xtype != CHOLMOD_PATTERN) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + RETURN_IF_XTYPE_INVALID (B, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + if (A->nrow != B->nrow) + { + /* A and B must have the same number of rows */ + ERROR (CHOLMOD_INVALID, "A and B must have same # rows") ; + return (NULL) ; + } + /* A and B must have the same numerical type if values is TRUE (both must + * be CHOLMOD_REAL, this is implicitly checked above) */ + + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + ancol = A->ncol ; + bncol = B->ncol ; + nrow = A->nrow ; + CHOLMOD(allocate_work) (0, MAX3 (nrow, ancol, bncol), 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + /* convert A to unsymmetric, if necessary */ + A2 = NULL ; + if (A->stype != 0) + { + /* workspace: Iwork (max (A->nrow,A->ncol)) */ + A2 = CHOLMOD(copy) (A, 0, values, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + A = A2 ; + } + + /* convert B to unsymmetric, if necessary */ + B2 = NULL ; + if (B->stype != 0) + { + /* workspace: Iwork (max (B->nrow,B->ncol)) */ + B2 = CHOLMOD(copy) (B, 0, values, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + CHOLMOD(free_sparse) (&A2, Common) ; + return (NULL) ; + } + B = B2 ; + } + + Ap = A->p ; + Anz = A->nz ; + Ai = A->i ; + Ax = A->x ; + apacked = A->packed ; + + Bp = B->p ; + Bnz = B->nz ; + Bi = B->i ; + Bx = B->x ; + bpacked = B->packed ; + + /* ---------------------------------------------------------------------- */ + /* allocate C */ + /* ---------------------------------------------------------------------- */ + + anz = CHOLMOD(nnz) (A, Common) ; + bnz = CHOLMOD(nnz) (B, Common) ; + ncol = ancol + bncol ; + nz = anz + bnz ; + + C = CHOLMOD(allocate_sparse) (nrow, ncol, nz, A->sorted && B->sorted, TRUE, + 0, values ? A->xtype : CHOLMOD_PATTERN, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + CHOLMOD(free_sparse) (&A2, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + return (NULL) ; + } + Cp = C->p ; + Ci = C->i ; + Cx = C->x ; + + /* ---------------------------------------------------------------------- */ + /* C = [A , B] */ + /* ---------------------------------------------------------------------- */ + + pdest = 0 ; + + /* copy A as the first A->ncol columns of C */ + for (j = 0 ; j < ancol ; j++) + { + /* A(:,j) is the jth column of C */ + p = Ap [j] ; + pend = (apacked) ? (Ap [j+1]) : (p + Anz [j]) ; + Cp [j] = pdest ; + for ( ; p < pend ; p++) + { + Ci [pdest] = Ai [p] ; + if (values) Cx [pdest] = Ax [p] ; + pdest++ ; + } + } + + /* copy B as the next B->ncol columns of C */ + for (j = 0 ; j < bncol ; j++) + { + /* B(:,j) is the (ancol+j)th column of C */ + p = Bp [j] ; + pend = (bpacked) ? (Bp [j+1]) : (p + Bnz [j]) ; + Cp [ancol + j] = pdest ; + for ( ; p < pend ; p++) + { + Ci [pdest] = Bi [p] ; + if (values) Cx [pdest] = Bx [p] ; + pdest++ ; + } + } + Cp [ncol] = pdest ; + ASSERT (pdest == anz + bnz) ; + + /* ---------------------------------------------------------------------- */ + /* free the unsymmetric copies of A and B, and return C */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(free_sparse) (&A2, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + return (C) ; +} +#endif diff --git a/src/CHOLMOD/MatrixOps/cholmod_norm.c b/src/CHOLMOD/MatrixOps/cholmod_norm.c new file mode 100644 index 0000000..75eea79 --- /dev/null +++ b/src/CHOLMOD/MatrixOps/cholmod_norm.c @@ -0,0 +1,452 @@ +/* ========================================================================== */ +/* === MatrixOps/cholmod_norm =============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/MatrixOps Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* r = norm (A), compute the infinity-norm, 1-norm, or 2-norm of a sparse or + * dense matrix. Can compute the 2-norm only for a dense column vector. + * Returns -1 if an error occurs. + * + * Pattern, real, complex, and zomplex sparse matrices are supported. + */ + +#ifndef NMATRIXOPS + +#include "cholmod_internal.h" +#include "cholmod_matrixops.h" + + +/* ========================================================================== */ +/* === abs_value ============================================================ */ +/* ========================================================================== */ + +/* Compute the absolute value of a real, complex, or zomplex value */ + +static double abs_value +( + int xtype, + double *Ax, + double *Az, + Int p, + cholmod_common *Common +) +{ + double s = 0 ; + switch (xtype) + { + case CHOLMOD_PATTERN: + s = 1 ; + break ; + + case CHOLMOD_REAL: + s = fabs (Ax [p]) ; + break ; + + case CHOLMOD_COMPLEX: + s = Common->hypotenuse (Ax [2*p], Ax [2*p+1]) ; + break ; + + case CHOLMOD_ZOMPLEX: + s = Common->hypotenuse (Ax [p], Az [p]) ; + break ; + } + return (s) ; +} + + +/* ========================================================================== */ +/* === cholmod_norm_dense =================================================== */ +/* ========================================================================== */ + +double CHOLMOD(norm_dense) +( + /* ---- input ---- */ + cholmod_dense *X, /* matrix to compute the norm of */ + int norm, /* type of norm: 0: inf. norm, 1: 1-norm, 2: 2-norm */ + /* --------------- */ + cholmod_common *Common +) +{ + double xnorm, s, x, z ; + double *Xx, *Xz, *W ; + Int nrow, ncol, d, i, j, use_workspace, xtype ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (X, EMPTY) ; + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, EMPTY) ; + Common->status = CHOLMOD_OK ; + ncol = X->ncol ; + if (norm < 0 || norm > 2 || (norm == 2 && ncol > 1)) + { + ERROR (CHOLMOD_INVALID, "invalid norm") ; + return (EMPTY) ; + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nrow = X->nrow ; + d = X->d ; + Xx = X->x ; + Xz = X->z ; + xtype = X->xtype ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace, if needed */ + /* ---------------------------------------------------------------------- */ + + W = NULL ; + use_workspace = (norm == 0 && ncol > 4) ; + if (use_workspace) + { + CHOLMOD(allocate_work) (0, 0, nrow, Common) ; + W = Common->Xwork ; + if (Common->status < CHOLMOD_OK) + { + /* oops, no workspace */ + use_workspace = FALSE ; + } + } + + + /* ---------------------------------------------------------------------- */ + /* compute the norm */ + /* ---------------------------------------------------------------------- */ + + xnorm = 0 ; + + if (use_workspace) + { + + /* ------------------------------------------------------------------ */ + /* infinity-norm = max row sum, using stride-1 access of X */ + /* ------------------------------------------------------------------ */ + + DEBUG (for (i = 0 ; i < nrow ; i++) ASSERT (W [i] == 0)) ; + + /* this is faster than stride-d, but requires O(nrow) workspace */ + for (j = 0 ; j < ncol ; j++) + { + for (i = 0 ; i < nrow ; i++) + { + W [i] += abs_value (xtype, Xx, Xz, i+j*d, Common) ; + } + } + for (i = 0 ; i < nrow ; i++) + { + s = W [i] ; + if ((IS_NAN (s) || s > xnorm) && !IS_NAN (xnorm)) + { + xnorm = s ; + } + W [i] = 0 ; + } + + } + else if (norm == 0) + { + + /* ------------------------------------------------------------------ */ + /* infinity-norm = max row sum, using stride-d access of X */ + /* ------------------------------------------------------------------ */ + + for (i = 0 ; i < nrow ; i++) + { + s = 0 ; + for (j = 0 ; j < ncol ; j++) + { + s += abs_value (xtype, Xx, Xz, i+j*d, Common) ; + } + if ((IS_NAN (s) || s > xnorm) && !IS_NAN (xnorm)) + { + xnorm = s ; + } + } + + } + else if (norm == 1) + { + + /* ------------------------------------------------------------------ */ + /* 1-norm = max column sum */ + /* ------------------------------------------------------------------ */ + + for (j = 0 ; j < ncol ; j++) + { + s = 0 ; + for (i = 0 ; i < nrow ; i++) + { + s += abs_value (xtype, Xx, Xz, i+j*d, Common) ; + } + if ((IS_NAN (s) || s > xnorm) && !IS_NAN (xnorm)) + { + xnorm = s ; + } + } + } + else + { + + /* ------------------------------------------------------------------ */ + /* 2-norm = sqrt (sum (X.^2)) */ + /* ------------------------------------------------------------------ */ + + switch (xtype) + { + + case CHOLMOD_REAL: + for (i = 0 ; i < nrow ; i++) + { + x = Xx [i] ; + xnorm += x*x ; + } + break ; + + case CHOLMOD_COMPLEX: + for (i = 0 ; i < nrow ; i++) + { + x = Xx [2*i ] ; + z = Xx [2*i+1] ; + xnorm += x*x + z*z ; + } + break ; + + case CHOLMOD_ZOMPLEX: + for (i = 0 ; i < nrow ; i++) + { + x = Xx [i] ; + z = Xz [i] ; + xnorm += x*x + z*z ; + } + break ; + } + + xnorm = sqrt (xnorm) ; + } + + /* ---------------------------------------------------------------------- */ + /* return result */ + /* ---------------------------------------------------------------------- */ + + return (xnorm) ; +} + + +/* ========================================================================== */ +/* === cholmod_norm_sparse ================================================== */ +/* ========================================================================== */ + +double CHOLMOD(norm_sparse) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to compute the norm of */ + int norm, /* type of norm: 0: inf. norm, 1: 1-norm */ + /* --------------- */ + cholmod_common *Common +) +{ + double anorm, s ; + double *Ax, *Az, *W ; + Int *Ap, *Ai, *Anz ; + Int i, j, p, pend, nrow, ncol, packed, xtype ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (A, EMPTY) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ; + Common->status = CHOLMOD_OK ; + ncol = A->ncol ; + nrow = A->nrow ; + if (norm < 0 || norm > 1) + { + ERROR (CHOLMOD_INVALID, "invalid norm") ; + return (EMPTY) ; + } + if (A->stype && nrow != ncol) + { + ERROR (CHOLMOD_INVALID, "matrix invalid") ; + return (EMPTY) ; + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + Ai = A->i ; + Ax = A->x ; + Az = A->z ; + Anz = A->nz ; + packed = A->packed ; + xtype = A->xtype ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace, if needed */ + /* ---------------------------------------------------------------------- */ + + W = NULL ; + if (A->stype || norm == 0) + { + CHOLMOD(allocate_work) (0, 0, nrow, Common) ; + W = Common->Xwork ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (EMPTY) ; + } + DEBUG (for (i = 0 ; i < nrow ; i++) ASSERT (W [i] == 0)) ; + } + + /* ---------------------------------------------------------------------- */ + /* compute the norm */ + /* ---------------------------------------------------------------------- */ + + anorm = 0 ; + + if (A->stype > 0) + { + + /* ------------------------------------------------------------------ */ + /* A is symmetric with upper triangular part stored */ + /* ------------------------------------------------------------------ */ + + /* infinity-norm = 1-norm = max row/col sum */ + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + s = abs_value (xtype, Ax, Az, p, Common) ; + if (i == j) + { + W [i] += s ; + } + else if (i < j) + { + W [i] += s ; + W [j] += s ; + } + } + } + + } + else if (A->stype < 0) + { + + /* ------------------------------------------------------------------ */ + /* A is symmetric with lower triangular part stored */ + /* ------------------------------------------------------------------ */ + + /* infinity-norm = 1-norm = max row/col sum */ + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + s = abs_value (xtype, Ax, Az, p, Common) ; + if (i == j) + { + W [i] += s ; + } + else if (i > j) + { + W [i] += s ; + W [j] += s ; + } + } + } + + } + else if (norm == 0) + { + + /* ------------------------------------------------------------------ */ + /* A is unsymmetric, compute the infinity-norm */ + /* ------------------------------------------------------------------ */ + + /* infinity-norm = max row sum */ + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + W [Ai [p]] += abs_value (xtype, Ax, Az, p, Common) ; + } + } + + } + else + { + + /* ------------------------------------------------------------------ */ + /* A is unsymmetric, compute the 1-norm */ + /* ------------------------------------------------------------------ */ + + /* 1-norm = max column sum */ + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + if (xtype == CHOLMOD_PATTERN) + { + s = pend - p ; + } + else + { + s = 0 ; + for ( ; p < pend ; p++) + { + s += abs_value (xtype, Ax, Az, p, Common) ; + } + } + if ((IS_NAN (s) || s > anorm) && !IS_NAN (anorm)) + { + anorm = s ; + } + } + } + + /* ---------------------------------------------------------------------- */ + /* compute the max row sum */ + /* ---------------------------------------------------------------------- */ + + if (A->stype || norm == 0) + { + for (i = 0 ; i < nrow ; i++) + { + s = W [i] ; + if ((IS_NAN (s) || s > anorm) && !IS_NAN (anorm)) + { + anorm = s ; + } + W [i] = 0 ; + } + } + + /* ---------------------------------------------------------------------- */ + /* return result */ + /* ---------------------------------------------------------------------- */ + + return (anorm) ; +} +#endif diff --git a/src/CHOLMOD/MatrixOps/cholmod_scale.c b/src/CHOLMOD/MatrixOps/cholmod_scale.c new file mode 100644 index 0000000..2f722c6 --- /dev/null +++ b/src/CHOLMOD/MatrixOps/cholmod_scale.c @@ -0,0 +1,217 @@ +/* ========================================================================== */ +/* === MatrixOps/cholmod_scale ============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/MatrixOps Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* scale a matrix: A = diag(s)*A, A*diag(s), s*A, or diag(s)*A*diag(s) + * + * A can be of any type (packed/unpacked, upper/lower/unsymmetric). + * The symmetry of A is ignored; all entries in the matrix are modified. + * + * If A is m-by-n unsymmetric but scaled symmtrically, the result is + * A = diag (s (1:m)) * A * diag (s (1:n)). + * + * Note: diag(s) should be interpretted as spdiags(s,0,n,n) where n=length(s). + * + * Row or column scaling of a symmetric matrix still results in a symmetric + * matrix, since entries are still ignored by other routines. + * For example, when row-scaling a symmetric matrix where just the upper + * triangular part is stored (and lower triangular entries ignored) + * A = diag(s)*triu(A) is performed, where the result A is also + * symmetric-upper. This has the effect of modifying the implicit lower + * triangular part. In MATLAB notation: + * + * U = diag(s)*triu(A) ; + * L = tril (U',-1) + * A = L + U ; + * + * The scale parameter determines the kind of scaling to perform: + * + * CHOLMOD_SCALAR: s[0]*A + * CHOLMOD_ROW: diag(s)*A + * CHOLMOD_COL: A*diag(s) + * CHOLMOD_SYM: diag(s)*A*diag(s) + * + * The size of S depends on the scale parameter: + * + * CHOLMOD_SCALAR: size 1 + * CHOLMOD_ROW: size nrow-by-1 or 1-by-nrow + * CHOLMOD_COL: size ncol-by-1 or 1-by-ncol + * CHOLMOD_SYM: size max(nrow,ncol)-by-1, or 1-by-max(nrow,ncol) + * + * workspace: none + * + * Only real matrices are supported. + */ + +#ifndef NMATRIXOPS + +#include "cholmod_internal.h" +#include "cholmod_matrixops.h" + + +/* ========================================================================== */ +/* === cholmod_scale ======================================================== */ +/* ========================================================================== */ + +int CHOLMOD(scale) +( + /* ---- input ---- */ + cholmod_dense *S, /* scale factors (scalar or vector) */ + int scale, /* type of scaling to compute */ + /* ---- in/out --- */ + cholmod_sparse *A, /* matrix to scale */ + /* --------------- */ + cholmod_common *Common +) +{ + double t ; + double *Ax, *s ; + Int *Ap, *Anz, *Ai ; + Int packed, j, ncol, nrow, p, pend, sncol, snrow, nn, ok ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (S, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_REAL, CHOLMOD_REAL, FALSE) ; + RETURN_IF_XTYPE_INVALID (S, CHOLMOD_REAL, CHOLMOD_REAL, FALSE) ; + ncol = A->ncol ; + nrow = A->nrow ; + sncol = S->ncol ; + snrow = S->nrow ; + if (scale == CHOLMOD_SCALAR) + { + ok = (snrow == 1 && sncol == 1) ; + } + else if (scale == CHOLMOD_ROW) + { + ok = (snrow == nrow && sncol == 1) || (snrow == 1 && sncol == nrow) ; + } + else if (scale == CHOLMOD_COL) + { + ok = (snrow == ncol && sncol == 1) || (snrow == 1 && sncol == ncol) ; + } + else if (scale == CHOLMOD_SYM) + { + nn = MAX (nrow, ncol) ; + ok = (snrow == nn && sncol == 1) || (snrow == 1 && sncol == nn) ; + } + else + { + /* scale invalid */ + ERROR (CHOLMOD_INVALID, "invalid scaling option") ; + return (FALSE) ; + } + if (!ok) + { + /* S is wrong size */ + ERROR (CHOLMOD_INVALID, "invalid scale factors") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + Anz = A->nz ; + Ai = A->i ; + Ax = A->x ; + packed = A->packed ; + s = S->x ; + + /* ---------------------------------------------------------------------- */ + /* scale the matrix */ + /* ---------------------------------------------------------------------- */ + + if (scale == CHOLMOD_ROW) + { + + /* ------------------------------------------------------------------ */ + /* A = diag(s)*A, row scaling */ + /* ------------------------------------------------------------------ */ + + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + Ax [p] *= s [Ai [p]] ; + } + } + + } + else if (scale == CHOLMOD_COL) + { + + /* ------------------------------------------------------------------ */ + /* A = A*diag(s), column scaling */ + /* ------------------------------------------------------------------ */ + + for (j = 0 ; j < ncol ; j++) + { + t = s [j] ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + Ax [p] *= t ; + } + } + + } + else if (scale == CHOLMOD_SYM) + { + + /* ------------------------------------------------------------------ */ + /* A = diag(s)*A*diag(s), symmetric scaling */ + /* ------------------------------------------------------------------ */ + + for (j = 0 ; j < ncol ; j++) + { + t = s [j] ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + Ax [p] *= t * s [Ai [p]] ; + } + } + + } + else if (scale == CHOLMOD_SCALAR) + { + + /* ------------------------------------------------------------------ */ + /* A = s[0] * A, scalar scaling */ + /* ------------------------------------------------------------------ */ + + t = s [0] ; + for (j = 0 ; j < ncol ; j++) + { + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + Ax [p] *= t ; + } + } + } + + ASSERT (CHOLMOD(dump_sparse) (A, "A scaled", Common) >= 0) ; + return (TRUE) ; +} +#endif diff --git a/src/CHOLMOD/MatrixOps/cholmod_sdmult.c b/src/CHOLMOD/MatrixOps/cholmod_sdmult.c new file mode 100644 index 0000000..fd40b48 --- /dev/null +++ b/src/CHOLMOD/MatrixOps/cholmod_sdmult.c @@ -0,0 +1,149 @@ +/* ========================================================================== */ +/* === MatrixOps/cholmod_sdmult ============================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/MatrixOps Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Sparse matrix times dense matrix: + * Y = alpha*(A*X) + beta*Y or Y = alpha*(A'*X) + beta*Y, + * where A is sparse and X and Y are dense. + * + * when using A, X has A->ncol columns and Y has A->nrow rows + * when using A', X has A->nrow columns and Y has A->ncol rows + * + * workspace: none in Common. Temporary workspace of size 4*(X->nrow) is used + * if A is stored in symmetric form and X has four columns or more. If the + * workspace is not available, a slower method is used instead that requires + * no workspace. + * + * transpose = 0: use A + * otherwise, use A' (complex conjugate transpose) + * + * transpose is ignored if the matrix is symmetric or Hermitian. + * (the array transpose A.' is not supported). + * + * Supports real, complex, and zomplex matrices, but the xtypes of A, X, and Y + * must all match. + */ + +#ifndef NMATRIXOPS + +#include "cholmod_internal.h" +#include "cholmod_matrixops.h" + + +/* ========================================================================== */ +/* === TEMPLATE ============================================================= */ +/* ========================================================================== */ + +#define REAL +#include "t_cholmod_sdmult.c" +#define COMPLEX +#include "t_cholmod_sdmult.c" +#define ZOMPLEX +#include "t_cholmod_sdmult.c" + +/* ========================================================================== */ +/* === cholmod_sdmult ======================================================= */ +/* ========================================================================== */ + +int CHOLMOD(sdmult) +( + /* ---- input ---- */ + cholmod_sparse *A, /* sparse matrix to multiply */ + int transpose, /* use A if 0, otherwise use A' */ + double alpha [2], /* scale factor for A */ + double beta [2], /* scale factor for Y */ + cholmod_dense *X, /* dense matrix to multiply */ + /* ---- in/out --- */ + cholmod_dense *Y, /* resulting dense matrix */ + /* --------------- */ + cholmod_common *Common +) +{ + double *w ; + size_t nx, ny ; + Int e ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (X, FALSE) ; + RETURN_IF_NULL (Y, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (Y, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + ny = transpose ? A->ncol : A->nrow ; /* required length of Y */ + nx = transpose ? A->nrow : A->ncol ; /* required length of X */ + if (X->nrow != nx || X->ncol != Y->ncol || Y->nrow != ny) + { + /* X and/or Y have the wrong dimension */ + ERROR (CHOLMOD_INVALID, "X and/or Y have wrong dimensions") ; + return (FALSE) ; + } + if (A->xtype != X->xtype || A->xtype != Y->xtype) + { + ERROR (CHOLMOD_INVALID, "A, X, and Y must have same xtype") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace, if required */ + /* ---------------------------------------------------------------------- */ + + w = NULL ; + e = (A->xtype == CHOLMOD_REAL ? 1:2) ; + if (A->stype && X->ncol >= 4) + { + w = CHOLMOD(malloc) (nx, 4*e*sizeof (double), Common) ; + } + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; /* out of memory */ + } + + /* ---------------------------------------------------------------------- */ + /* Y = alpha*op(A)*X + beta*Y via template routine */ + /* ---------------------------------------------------------------------- */ + + ASSERT (CHOLMOD(dump_sparse) (A, "A", Common) >= 0) ; + DEBUG (CHOLMOD(dump_dense) (X, "X", Common)) ; + DEBUG (if (IS_NONZERO (beta [0]) + || (IS_NONZERO (beta [1]) && A->xtype != CHOLMOD_REAL)) + CHOLMOD(dump_dense) (Y, "Y", Common)) ; + + switch (A->xtype) + { + + case CHOLMOD_REAL: + r_cholmod_sdmult (A, transpose, alpha, beta, X, Y, w) ; + break ; + + case CHOLMOD_COMPLEX: + c_cholmod_sdmult (A, transpose, alpha, beta, X, Y, w) ; + break ; + + case CHOLMOD_ZOMPLEX: + z_cholmod_sdmult (A, transpose, alpha, beta, X, Y, w) ; + break ; + } + + /* ---------------------------------------------------------------------- */ + /* free workspace */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(free) (4*nx, e*sizeof (double), w, Common) ; + DEBUG (CHOLMOD(dump_dense) (Y, "Y", Common)) ; + return (TRUE) ; +} +#endif diff --git a/src/CHOLMOD/MatrixOps/cholmod_ssmult.c b/src/CHOLMOD/MatrixOps/cholmod_ssmult.c new file mode 100644 index 0000000..fbdf0ce --- /dev/null +++ b/src/CHOLMOD/MatrixOps/cholmod_ssmult.c @@ -0,0 +1,487 @@ +/* ========================================================================== */ +/* === MatrixOps/cholmod_ssmult ============================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/MatrixOps Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* C = A*B. Multiply two sparse matrices. + * + * A and B can be packed or unpacked, sorted or unsorted, and of any stype. + * If A or B are symmetric, an internal unsymmetric copy is made first, however. + * C is computed as if A and B are unsymmetric, and then if the stype input + * parameter requests a symmetric form (upper or lower) the matrix is converted + * into that form. + * + * C is returned as packed, and either unsorted or sorted, depending on the + * "sorted" input parameter. If C is returned sorted, then either C = (B'*A')' + * or C = (A*B)'' is computed, depending on the number of nonzeros in A, B, and + * C. + * + * workspace: + * if C unsorted: Flag (A->nrow), W (A->nrow) if values + * if C sorted: Flag (B->ncol), W (B->ncol) if values + * Iwork (max (A->ncol, A->nrow, B->nrow, B->ncol)) + * allocates temporary copies for A, B, and C, if required. + * + * Only pattern and real matrices are supported. Complex and zomplex matrices + * are supported only when the numerical values are not computed ("values" + * is FALSE). + */ + +#ifndef NMATRIXOPS + +#include "cholmod_internal.h" +#include "cholmod_matrixops.h" + + +/* ========================================================================== */ +/* === cholmod_ssmult ======================================================= */ +/* ========================================================================== */ + +cholmod_sparse *CHOLMOD(ssmult) +( + /* ---- input ---- */ + cholmod_sparse *A, /* left matrix to multiply */ + cholmod_sparse *B, /* right matrix to multiply */ + int stype, /* requested stype of C */ + int values, /* TRUE: do numerical values, FALSE: pattern only */ + int sorted, /* if TRUE then return C with sorted columns */ + /* --------------- */ + cholmod_common *Common +) +{ + double bjt ; + double *Ax, *Bx, *Cx, *W ; + Int *Ap, *Anz, *Ai, *Bp, *Bnz, *Bi, *Cp, *Ci, *Flag ; + cholmod_sparse *C, *A2, *B2, *A3, *B3, *C2 ; + Int apacked, bpacked, j, i, pa, paend, pb, pbend, ncol, mark, cnz, t, p, + nrow, anz, bnz, do_swap_and_transpose, n1, n2 ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + RETURN_IF_NULL (B, NULL) ; + values = values && + (A->xtype != CHOLMOD_PATTERN) && (B->xtype != CHOLMOD_PATTERN) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + RETURN_IF_XTYPE_INVALID (B, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + if (A->ncol != B->nrow) + { + /* inner dimensions must agree */ + ERROR (CHOLMOD_INVALID, "A and B inner dimensions must match") ; + return (NULL) ; + } + /* A and B must have the same numerical type if values is TRUE (both must + * be CHOLMOD_REAL, this is implicitly checked above) */ + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + if (A->nrow <= 1) + { + /* C will be implicitly sorted, so no need to sort it here */ + sorted = FALSE ; + } + if (sorted) + { + n1 = MAX (A->nrow, B->ncol) ; + } + else + { + n1 = A->nrow ; + } + n2 = MAX4 (A->ncol, A->nrow, B->nrow, B->ncol) ; + CHOLMOD(allocate_work) (n1, n2, values ? n1 : 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n1 : 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + /* convert A to unsymmetric, if necessary */ + A2 = NULL ; + B2 = NULL ; + if (A->stype) + { + /* workspace: Iwork (max (A->nrow,A->ncol)) */ + A2 = CHOLMOD(copy) (A, 0, values, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n1:0, Common)) ; + return (NULL) ; + } + A = A2 ; + } + + /* convert B to unsymmetric, if necessary */ + if (B->stype) + { + /* workspace: Iwork (max (B->nrow,B->ncol)) */ + B2 = CHOLMOD(copy) (B, 0, values, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + CHOLMOD(free_sparse) (&A2, Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n1:0, Common)) ; + return (NULL) ; + } + B = B2 ; + } + + ASSERT (CHOLMOD(dump_sparse) (A, "A", Common) >= 0) ; + ASSERT (CHOLMOD(dump_sparse) (B, "B", Common) >= 0) ; + + /* get the A matrix */ + Ap = A->p ; + Anz = A->nz ; + Ai = A->i ; + Ax = A->x ; + apacked = A->packed ; + + /* get the B matrix */ + Bp = B->p ; + Bnz = B->nz ; + Bi = B->i ; + Bx = B->x ; + bpacked = B->packed ; + + /* get the size of C */ + nrow = A->nrow ; + ncol = B->ncol ; + + /* get workspace */ + W = Common->Xwork ; /* size nrow, unused if values is FALSE */ + Flag = Common->Flag ; /* size nrow, Flag [0..nrow-1] < mark on input*/ + + /* ---------------------------------------------------------------------- */ + /* count the number of entries in the result C */ + /* ---------------------------------------------------------------------- */ + + cnz = 0 ; + for (j = 0 ; j < ncol ; j++) + { + /* clear the Flag array */ + /* mark = CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + mark = Common->mark ; + + /* for each nonzero B(t,j) in column j, do: */ + pb = Bp [j] ; + pbend = (bpacked) ? (Bp [j+1]) : (pb + Bnz [j]) ; + for ( ; pb < pbend ; pb++) + { + /* B(t,j) is nonzero */ + t = Bi [pb] ; + + /* add the nonzero pattern of A(:,t) to the pattern of C(:,j) */ + pa = Ap [t] ; + paend = (apacked) ? (Ap [t+1]) : (pa + Anz [t]) ; + for ( ; pa < paend ; pa++) + { + i = Ai [pa] ; + if (Flag [i] != mark) + { + Flag [i] = mark ; + cnz++ ; + } + } + } + if (cnz < 0) + { + break ; /* integer overflow case */ + } + } + + /* mark = CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + mark = Common->mark ; + + /* ---------------------------------------------------------------------- */ + /* check for integer overflow */ + /* ---------------------------------------------------------------------- */ + + if (cnz < 0) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + CHOLMOD(free_sparse) (&A2, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n1:0, Common)) ; + return (NULL) ; + } + + /* ---------------------------------------------------------------------- */ + /* Determine how to return C sorted (if requested) */ + /* ---------------------------------------------------------------------- */ + + do_swap_and_transpose = FALSE ; + + if (sorted) + { + /* Determine the best way to return C with sorted columns. Computing + * C = (B'*A')' takes cnz + anz + bnz time (ignoring O(n) terms). + * Sorting C when done, C = (A*B)'', takes 2*cnz time. Pick the one + * with the least amount of work. */ + + anz = CHOLMOD(nnz) (A, Common) ; + bnz = CHOLMOD(nnz) (B, Common) ; + + do_swap_and_transpose = (anz + bnz < cnz) ; + + if (do_swap_and_transpose) + { + + /* -------------------------------------------------------------- */ + /* C = (B'*A')' */ + /* -------------------------------------------------------------- */ + + /* workspace: Iwork (A->nrow) */ + A3 = CHOLMOD(ptranspose) (A, values, NULL, NULL, 0, Common) ; + CHOLMOD(free_sparse) (&A2, Common) ; + A2 = A3 ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + CHOLMOD(free_sparse) (&A2, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n1:0, Common)); + return (NULL) ; + } + /* workspace: Iwork (B->nrow) */ + B3 = CHOLMOD(ptranspose) (B, values, NULL, NULL, 0, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + B2 = B3 ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + CHOLMOD(free_sparse) (&A2, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n1:0, Common)); + return (NULL) ; + } + A = B2 ; + B = A2 ; + + /* get the new A matrix */ + Ap = A->p ; + Anz = A->nz ; + Ai = A->i ; + Ax = A->x ; + apacked = A->packed ; + + /* get the new B matrix */ + Bp = B->p ; + Bnz = B->nz ; + Bi = B->i ; + Bx = B->x ; + bpacked = B->packed ; + + /* get the size of C' */ + nrow = A->nrow ; + ncol = B->ncol ; + } + } + + /* ---------------------------------------------------------------------- */ + /* allocate C */ + /* ---------------------------------------------------------------------- */ + + C = CHOLMOD(allocate_sparse) (nrow, ncol, cnz, FALSE, TRUE, 0, + values ? A->xtype : CHOLMOD_PATTERN, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + CHOLMOD(free_sparse) (&A2, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n1:0, Common)) ; + return (NULL) ; + } + + Cp = C->p ; + Ci = C->i ; + Cx = C->x ; + + /* ---------------------------------------------------------------------- */ + /* C = A*B */ + /* ---------------------------------------------------------------------- */ + + cnz = 0 ; + + if (values) + { + + /* pattern and values */ + for (j = 0 ; j < ncol ; j++) + { + /* clear the Flag array */ + /* mark = CHOLMOD(clear_flag (Common)) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + mark = Common->mark ; + + /* start column j of C */ + Cp [j] = cnz ; + + /* for each nonzero B(t,j) in column j, do: */ + pb = Bp [j] ; + pbend = (bpacked) ? (Bp [j+1]) : (pb + Bnz [j]) ; + for ( ; pb < pbend ; pb++) + { + /* B(t,j) is nonzero */ + t = Bi [pb] ; + bjt = Bx [pb] ; + + /* add the nonzero pattern of A(:,t) to the pattern of C(:,j) + * and scatter the values into W */ + pa = Ap [t] ; + paend = (apacked) ? (Ap [t+1]) : (pa + Anz [t]) ; + for ( ; pa < paend ; pa++) + { + i = Ai [pa] ; + if (Flag [i] != mark) + { + Flag [i] = mark ; + Ci [cnz++] = i ; + } + W [i] += Ax [pa] * bjt ; + } + } + + /* gather the values into C(:,j) */ + for (p = Cp [j] ; p < cnz ; p++) + { + i = Ci [p] ; + Cx [p] = W [i] ; + W [i] = 0 ; + } + } + + } + else + { + + /* pattern only */ + for (j = 0 ; j < ncol ; j++) + { + /* clear the Flag array */ + /* mark = CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + mark = Common->mark ; + + /* start column j of C */ + Cp [j] = cnz ; + + /* for each nonzero B(t,j) in column j, do: */ + pb = Bp [j] ; + pbend = (bpacked) ? (Bp [j+1]) : (pb + Bnz [j]) ; + for ( ; pb < pbend ; pb++) + { + /* B(t,j) is nonzero */ + t = Bi [pb] ; + + /* add the nonzero pattern of A(:,t) to the pattern of C(:,j) */ + pa = Ap [t] ; + paend = (apacked) ? (Ap [t+1]) : (pa + Anz [t]) ; + for ( ; pa < paend ; pa++) + { + i = Ai [pa] ; + if (Flag [i] != mark) + { + Flag [i] = mark ; + Ci [cnz++] = i ; + } + } + } + } + } + + Cp [ncol] = cnz ; + ASSERT (MAX (1,cnz) == C->nzmax) ; + + /* ---------------------------------------------------------------------- */ + /* clear workspace and free temporary matrices */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(free_sparse) (&A2, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + /* CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n1:0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* convert C to a symmetric upper/lower matrix if requested */ + /* ---------------------------------------------------------------------- */ + + /* convert C in place, which cannot fail since no memory is allocated */ + if (stype > 0) + { + /* C = triu (C), in place */ + (void) CHOLMOD(band_inplace) (0, ncol, values, C, Common) ; + C->stype = 1 ; + } + else if (stype < 0) + { + /* C = tril (C), in place */ + (void) CHOLMOD(band_inplace) (-nrow, 0, values, C, Common) ; + C->stype = -1 ; + } + ASSERT (Common->status >= CHOLMOD_OK) ; + + /* ---------------------------------------------------------------------- */ + /* sort C, if requested */ + /* ---------------------------------------------------------------------- */ + + if (sorted) + { + if (do_swap_and_transpose) + { + /* workspace: Iwork (C->ncol), which is A->nrow since C=(B'*A') */ + C2 = CHOLMOD(ptranspose) (C, values, NULL, NULL, 0, Common) ; + CHOLMOD(free_sparse) (&C, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n1:0, Common)); + return (NULL) ; + } + C = C2 ; + } + else + { + /* workspace: Iwork (max (C->nrow,C->ncol)) */ + if (!CHOLMOD(sort) (C, Common)) + { + /* out of memory */ + CHOLMOD(free_sparse) (&C, Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n1:0, Common)); + return (NULL) ; + } + } + } + + /* ---------------------------------------------------------------------- */ + /* return result */ + /* ---------------------------------------------------------------------- */ + + DEBUG (CHOLMOD(dump_sparse) (C, "ssmult", Common) >= 0) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, values ? n1:0, Common)) ; + return (C) ; +} +#endif diff --git a/src/CHOLMOD/MatrixOps/cholmod_submatrix.c b/src/CHOLMOD/MatrixOps/cholmod_submatrix.c new file mode 100644 index 0000000..a412053 --- /dev/null +++ b/src/CHOLMOD/MatrixOps/cholmod_submatrix.c @@ -0,0 +1,425 @@ +/* ========================================================================== */ +/* === MatrixOps/cholmod_submatrix ========================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/MatrixOps Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* C = A (rset,cset), where C becomes length(rset)-by-length(cset) in dimension. + * rset and cset can have duplicate entries. A and C must be unsymmetric. C + * is packed. If the sorted flag is TRUE on input, or rset is sorted and A is + * sorted, then C is sorted; otherwise C is unsorted. + * + * A NULL rset or cset means "[ ]" in MATLAB notation. + * If the length of rset or cset is negative, it denotes ":" in MATLAB notation. + * + * For permuting a matrix, this routine is an alternative to cholmod_ptranspose + * (which permutes and transposes a matrix and can work on symmetric matrices). + * + * The time taken by this routine is O(A->nrow) if the Common workspace needs + * to be initialized, plus O(C->nrow + C->ncol + nnz (A (:,cset))). Thus, if C + * is small and the workspace is not initialized, the time can be dominated by + * the call to cholmod_allocate_work. However, once the workspace is + * allocated, subsequent calls take less time. + * + * workspace: Iwork (max (A->nrow + length (rset), length (cset))). + * allocates temporary copy of C if it is to be returned sorted. + * + * Future work: A common case occurs where A has sorted columns, and rset is in + * the form lo:hi in MATLAB notation. This routine could exploit that case + * to run even faster when the matrix is sorted, particularly when lo is small. + * + * Only pattern and real matrices are supported. Complex and zomplex matrices + * are supported only when "values" is FALSE. + */ + +#ifndef NMATRIXOPS + +#include "cholmod_internal.h" +#include "cholmod_matrixops.h" + +/* ========================================================================== */ +/* === check_subset ========================================================= */ +/* ========================================================================== */ + +/* Check the rset or cset, and return TRUE if valid, FALSE if invalid */ + +static int check_subset (Int *set, Int len, Int n) +{ + Int k ; + if (set == NULL) + { + return (TRUE) ; + } + for (k = 0 ; k < len ; k++) + { + if (set [k] < 0 || set [k] >= n) + { + return (FALSE) ; + } + } + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_submatrix ==================================================== */ +/* ========================================================================== */ + +cholmod_sparse *CHOLMOD(submatrix) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to subreference */ + Int *rset, /* set of row indices, duplicates OK */ + SuiteSparse_long rsize, /* size of rset, or -1 for ":" */ + Int *cset, /* set of column indices, duplicates OK */ + SuiteSparse_long csize, /* size of cset, or -1 for ":" */ + int values, /* if TRUE compute the numerical values of C */ + int sorted, /* if TRUE then return C with sorted columns */ + /* --------------- */ + cholmod_common *Common +) +{ + double aij = 0 ; + double *Ax, *Cx ; + Int *Ap, *Ai, *Anz, *Ci, *Cp, *Head, *Rlen, *Rnext, *Iwork ; + cholmod_sparse *C ; + Int packed, ancol, anrow, cnrow, cncol, nnz, i, j, csorted, ilast, p, + pend, pdest, ci, cj, head, nr, nc ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + values = (values && (A->xtype != CHOLMOD_PATTERN)) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + if (A->stype != 0) + { + /* A must be unsymmetric */ + ERROR (CHOLMOD_INVALID, "symmetric upper or lower case not supported") ; + return (NULL) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + ancol = A->ncol ; + anrow = A->nrow ; + nr = rsize ; + nc = csize ; + if (rset == NULL) + { + /* nr = 0 denotes rset = [ ], nr < 0 denotes rset = 0:anrow-1 */ + nr = (nr < 0) ? (-1) : 0 ; + } + if (cset == NULL) + { + /* nr = 0 denotes cset = [ ], nr < 0 denotes cset = 0:ancol-1 */ + nc = (nc < 0) ? (-1) : 0 ; + } + cnrow = (nr < 0) ? anrow : nr ; /* negative rset means rset = 0:anrow-1 */ + cncol = (nc < 0) ? ancol : nc ; /* negative cset means cset = 0:ancol-1 */ + + if (nr < 0 && nc < 0) + { + + /* ------------------------------------------------------------------ */ + /* C = A (:,:), use cholmod_copy instead */ + /* ------------------------------------------------------------------ */ + + /* workspace: Iwork (max (C->nrow,C->ncol)) */ + PRINT1 (("submatrix C = A (:,:)\n")) ; + C = CHOLMOD(copy) (A, 0, values, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + return (C) ; + } + PRINT1 (("submatrix nr "ID" nc "ID" Cnrow "ID" Cncol "ID"" + " Anrow "ID" Ancol "ID"\n", nr, nc, cnrow, cncol, anrow, ancol)) ; + + /* s = MAX3 (anrow+MAX(0,nr), cncol, cnrow) ; */ + s = CHOLMOD(add_size_t) (anrow, MAX (0,nr), &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (NULL) ; + } + s = MAX3 (s, ((size_t) cncol), ((size_t) cnrow)) ; + + CHOLMOD(allocate_work) (anrow, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + Anz = A->nz ; + Ai = A->i ; + Ax = A->x ; + packed = A->packed ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Head = Common->Head ; /* size anrow */ + Iwork = Common->Iwork ; + Rlen = Iwork ; /* size anrow (i/i/l) */ + Rnext = Iwork + anrow ; /* size nr (i/i/l), not used if nr < 0 */ + + /* ---------------------------------------------------------------------- */ + /* construct inverse of rset and compute nnz (C) */ + /* ---------------------------------------------------------------------- */ + + PRINT1 (("nr "ID" nc "ID"\n", nr, nc)) ; + PRINT1 (("anrow "ID" ancol "ID"\n", anrow, ancol)) ; + PRINT1 (("cnrow "ID" cncol "ID"\n", cnrow, cncol)) ; + DEBUG (for (i = 0 ; i < nr ; i++) PRINT2 (("rset ["ID"] = "ID"\n", + i, rset [i]))); + DEBUG (for (i = 0 ; i < nc ; i++) PRINT2 (("cset ["ID"] = "ID"\n", + i, cset [i]))); + + /* C is sorted if A and rset are sorted, or if C has one row or less */ + csorted = A->sorted || (cnrow <= 1) ; + + if (!check_subset (rset, nr, anrow)) + { + ERROR (CHOLMOD_INVALID, "invalid rset") ; + return (NULL) ; + } + + if (!check_subset (cset, nc, ancol)) + { + ERROR (CHOLMOD_INVALID, "invalid cset") ; + return (NULL) ; + } + + nnz = 0 ; + if (nr < 0) + { + /* C = A (:,cset) where cset = [ ] or cset is not empty */ + ASSERT (IMPLIES (cncol > 0, cset != NULL)) ; + for (cj = 0 ; cj < cncol ; cj++) + { + /* construct column cj of C, which is column j of A */ + j = cset [cj] ; + nnz += (packed) ? (Ap [j+1] - Ap [j]) : MAX (0, Anz [j]) ; + } + } + else + { + /* C = A (rset,cset), where rset is not empty but cset might be empty */ + /* create link lists in reverse order to preserve natural order */ + ilast = anrow ; + for (ci = nr-1 ; ci >= 0 ; ci--) + { + /* row i of A becomes row ci of C; add ci to ith link list */ + i = rset [ci] ; + head = Head [i] ; + Rlen [i] = (head == EMPTY) ? 1 : (Rlen [i] + 1) ; + Rnext [ci] = head ; + Head [i] = ci ; + if (i > ilast) + { + /* row indices in columns of C will not be sorted */ + csorted = FALSE ; + } + ilast = i ; + } + +#ifndef NDEBUG + for (i = 0 ; i < anrow ; i++) + { + Int k = 0 ; + Int rlen = (Head [i] != EMPTY) ? Rlen [i] : -1 ; + PRINT1 (("Row "ID" Rlen "ID": ", i, rlen)) ; + for (ci = Head [i] ; ci != EMPTY ; ci = Rnext [ci]) + { + k++ ; + PRINT2 ((""ID" ", ci)) ; + } + PRINT1 (("\n")) ; + ASSERT (IMPLIES (Head [i] != EMPTY, k == Rlen [i])) ; + } +#endif + + /* count nonzeros in C */ + for (cj = 0 ; cj < cncol ; cj++) + { + /* count rows in column cj of C, which is column j of A */ + j = (nc < 0) ? cj : (cset [cj]) ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + /* row i of A becomes multiple rows (ci) of C */ + i = Ai [p] ; + ASSERT (i >= 0 && i < anrow) ; + if (Head [i] != EMPTY) + { + nnz += Rlen [i] ; + } + } + } + } + PRINT1 (("nnz (C) "ID"\n", nnz)) ; + + /* rset and cset are now valid */ + DEBUG (CHOLMOD(dump_subset) (rset, rsize, anrow, "rset", Common)) ; + DEBUG (CHOLMOD(dump_subset) (cset, csize, ancol, "cset", Common)) ; + + /* ---------------------------------------------------------------------- */ + /* allocate C */ + /* ---------------------------------------------------------------------- */ + + C = CHOLMOD(allocate_sparse) (cnrow, cncol, nnz, csorted, TRUE, 0, + values ? A->xtype : CHOLMOD_PATTERN, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + for (i = 0 ; i < anrow ; i++) + { + Head [i] = EMPTY ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + return (NULL) ; + } + + Cp = C->p ; + Ci = C->i ; + Cx = C->x ; + + /* ---------------------------------------------------------------------- */ + /* C = A (rset,cset) */ + /* ---------------------------------------------------------------------- */ + + pdest = 0 ; + if (nnz == 0) + { + /* C has no nonzeros */ + for (cj = 0 ; cj <= cncol ; cj++) + { + Cp [cj] = 0 ; + } + } + else if (nr < 0) + { + /* C = A (:,cset), where cset is not empty */ + for (cj = 0 ; cj < cncol ; cj++) + { + /* construct column cj of C, which is column j of A */ + PRINT1 (("construct cj = j = "ID"\n", cj)) ; + j = cset [cj] ; + Cp [cj] = pdest ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + Ci [pdest] = Ai [p] ; + if (values) + { + Cx [pdest] = Ax [p] ; + } + pdest++ ; + ASSERT (pdest <= nnz) ; + } + } + } + else + { + /* C = A (rset,cset), where rset is not empty but cset might be empty */ + for (cj = 0 ; cj < cncol ; cj++) + { + /* construct column cj of C, which is column j of A */ + PRINT1 (("construct cj = "ID"\n", cj)) ; + j = (nc < 0) ? cj : (cset [cj]) ; + PRINT1 (("cj = "ID"\n", j)) ; + Cp [cj] = pdest ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + /* row (Ai [p]) of A becomes multiple rows (ci) of C */ + PRINT2 (("i: "ID" becomes: ", Ai [p])) ; + if (values) + { + aij = Ax [p] ; + } + for (ci = Head [Ai [p]] ; ci != EMPTY ; ci = Rnext [ci]) + { + PRINT3 ((""ID" ", ci)) ; + Ci [pdest] = ci ; + if (values) + { + Cx [pdest] = aij ; + } + pdest++ ; + ASSERT (pdest <= nnz) ; + } + PRINT2 (("\n")) ; + } + } + } + Cp [cncol] = pdest ; + ASSERT (nnz == pdest) ; + + /* ---------------------------------------------------------------------- */ + /* clear workspace */ + /* ---------------------------------------------------------------------- */ + + for (ci = 0 ; ci < nr ; ci++) + { + Head [rset [ci]] = EMPTY ; + } + + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* sort C, if requested */ + /* ---------------------------------------------------------------------- */ + + ASSERT (CHOLMOD(dump_sparse) (C , "C before sort", Common) >= 0) ; + + if (sorted && !csorted) + { + /* workspace: Iwork (max (C->nrow,C->ncol)) */ + if (!CHOLMOD(sort) (C, Common)) + { + /* out of memory */ + CHOLMOD(free_sparse) (&C, Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + return (NULL) ; + } + } + + /* ---------------------------------------------------------------------- */ + /* return result */ + /* ---------------------------------------------------------------------- */ + + ASSERT (CHOLMOD(dump_sparse) (C , "Final C", Common) >= 0) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + return (C) ; +} +#endif diff --git a/src/CHOLMOD/MatrixOps/cholmod_symmetry.c b/src/CHOLMOD/MatrixOps/cholmod_symmetry.c new file mode 100644 index 0000000..6bda9dc --- /dev/null +++ b/src/CHOLMOD/MatrixOps/cholmod_symmetry.c @@ -0,0 +1,488 @@ +/* ========================================================================== */ +/* === MatrixOps/cholmod_symmetry =========================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/MatrixOps Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Determines if a sparse matrix is rectangular, unsymmetric, symmetric, + * skew-symmetric, or Hermitian. It does so by looking at its numerical values + * of both upper and lower triangular parts of a CHOLMOD "unsymmetric" + * matrix, where A->stype == 0. The transpose of A is NOT constructed. + * + * If not unsymmetric, it also determines if the matrix has a diagonal whose + * entries are all real and positive (and thus a candidate for sparse Cholesky + * if A->stype is changed to a nonzero value). + * + * Note that a Matrix Market "general" matrix is either rectangular or + * unsymmetric. + * + * The row indices in the column of each matrix MUST be sorted for this function + * to work properly (A->sorted must be TRUE). This routine returns EMPTY if + * A->stype is not zero, or if A->sorted is FALSE. The exception to this rule + * is if A is rectangular. + * + * If option == 0, then this routine returns immediately when it finds a + * non-positive diagonal entry (or one with nonzero imaginary part). If the + * matrix is not a candidate for sparse Cholesky, it returns the value + * CHOLMOD_MM_UNSYMMETRIC, even if the matrix might in fact be symmetric or + * Hermitian. + * + * This routine is useful inside the MATLAB backslash, which must look at an + * arbitrary matrix (A->stype == 0) and determine if it is a candidate for + * sparse Cholesky. In that case, option should be 0. + * + * This routine is also useful when writing a MATLAB matrix to a file in + * Rutherford/Boeing or Matrix Market format. Those formats require a + * determination as to the symmetry of the matrix, and thus this routine should + * not return upon encountering the first non-positive diagonal. In this case, + * option should be 1. + * + * If option is 2, this function can be used to compute the numerical and + * pattern symmetry, where 0 is a completely unsymmetric matrix, and 1 is a + * perfectly symmetric matrix. This option is used when computing the following + * statistics for the matrices in the UF Sparse Matrix Collection. + * + * numerical symmetry: number of matched offdiagonal nonzeros over + * the total number of offdiagonal entries. A real entry A(i,j), i ~= j, + * is matched if A (j,i) == A (i,j), but this is only counted if both + * A(j,i) and A(i,j) are nonzero. This does not depend on Z. + * (If A is complex, then the above test is modified; A (i,j) is matched + * if conj (A (j,i)) == A (i,j)). + * + * Then numeric symmetry = xmatched / nzoffdiag, or 1 if nzoffdiag = 0. + * + * pattern symmetry: number of matched offdiagonal entries over the + * total number of offdiagonal entries. An entry A(i,j), i ~= j, is + * matched if A (j,i) is also an entry. + * + * Then pattern symmetry = pmatched / nzoffdiag, or 1 if nzoffdiag = 0. + * + * The symmetry of a matrix with no offdiagonal entries is equal to 1. + * + * A workspace of size ncol integers is allocated; EMPTY is returned if this + * allocation fails. + * + * Summary of return values: + * + * EMPTY (-1) out of memory, stype not zero, A not sorted + * CHOLMOD_MM_RECTANGULAR 1 A is rectangular + * CHOLMOD_MM_UNSYMMETRIC 2 A is unsymmetric + * CHOLMOD_MM_SYMMETRIC 3 A is symmetric, but with non-pos. diagonal + * CHOLMOD_MM_HERMITIAN 4 A is Hermitian, but with non-pos. diagonal + * CHOLMOD_MM_SKEW_SYMMETRIC 5 A is skew symmetric + * CHOLMOD_MM_SYMMETRIC_POSDIAG 6 A is symmetric with positive diagonal + * CHOLMOD_MM_HERMITIAN_POSDIAG 7 A is Hermitian with positive diagonal + * + * See also the spsym mexFunction, which is a MATLAB interface for this code. + * + * If the matrix is a candidate for sparse Cholesky, it will return a result + * CHOLMOD_MM_SYMMETRIC_POSDIAG if real, or CHOLMOD_MM_HERMITIAN_POSDIAG if + * complex. Otherwise, it will return a value less than this. This is true + * regardless of the value of the option parameter. + */ + +#ifndef NMATRIXOPS + +#include "cholmod_internal.h" +#include "cholmod_matrixops.h" + + +/* ========================================================================== */ +/* === get_value ============================================================ */ +/* ========================================================================== */ + +/* Get the pth value in the matrix. */ + +static void get_value +( + double *Ax, /* real values, or real/imag. for CHOLMOD_COMPLEX type */ + double *Az, /* imaginary values for CHOLMOD_ZOMPLEX type */ + Int p, /* get the pth entry */ + Int xtype, /* A->xtype: pattern, real, complex, or zomplex */ + double *x, /* the real part */ + double *z /* the imaginary part */ +) +{ + switch (xtype) + { + case CHOLMOD_PATTERN: + *x = 1 ; + *z = 0 ; + break ; + + case CHOLMOD_REAL: + *x = Ax [p] ; + *z = 0 ; + break ; + + case CHOLMOD_COMPLEX: + *x = Ax [2*p] ; + *z = Ax [2*p+1] ; + break ; + + case CHOLMOD_ZOMPLEX: + *x = Ax [p] ; + *z = Az [p] ; + break ; + } +} + + +/* ========================================================================== */ +/* === cholmod_symmetry ===================================================== */ +/* ========================================================================== */ + +/* Determine the symmetry of a matrix, and check its diagonal. + * + * option 0: Do not count # of matched pairs. Quick return if the + * the matrix has a zero, negative, or imaginary diagonal entry. + * + * option 1: Do not count # of matched pairs. Do not return quickly if + * the matrix has a zero, negative, or imaginary diagonal entry. + * The result 1 to 7 is accurately computed: + * + * EMPTY (-1) out of memory, stype not zero, A not sorted + * CHOLMOD_MM_RECTANGULAR 1 A is rectangular + * CHOLMOD_MM_UNSYMMETRIC 2 A is unsymmetric + * CHOLMOD_MM_SYMMETRIC 3 A is symmetric, with non-pos. diagonal + * CHOLMOD_MM_HERMITIAN 4 A is Hermitian, with non-pos. diagonal + * CHOLMOD_MM_SKEW_SYMMETRIC 5 A is skew symmetric + * CHOLMOD_MM_SYMMETRIC_POSDIAG 6 is symmetric with positive diagonal + * CHOLMOD_MM_HERMITIAN_POSDIAG 7 A is Hermitian with positive diagonal + * + * The routine returns as soon as the above is determined (that is, it + * can return as soon as it determines the matrix is unsymmetric). + * + * option 2: All of the above, but also compute the number of matched off- + * diagonal entries (of two types). xmatched is the number of + * nonzero entries for which A(i,j) = conj(A(j,i)). pmatched is + * the number of entries (i,j) for which A(i,j) and A(j,i) are both in + * the pattern of A (the value doesn't matter). nzoffdiag is the total + * number of off-diagonal entries in the pattern. nzdiag is the number of + * diagonal entries in the pattern. + * + * With option 0 or 1, or if the matrix is rectangular, xmatched, pmatched, + * nzoffdiag, and nzdiag are not computed. + * + * Note that a matched pair, A(i,j) and A(j,i) for i != j, is counted twice + * (once per entry). + */ + +int CHOLMOD(symmetry) +( + /* ---- input ---- */ + cholmod_sparse *A, + int option, /* option 0, 1, or 2 (see above) */ + /* ---- output --- */ /* outputs ignored if any are NULL */ + Int *p_xmatched, /* # of matched numerical entries */ + Int *p_pmatched, /* # of matched entries in pattern */ + Int *p_nzoffdiag, /* # of off diagonal entries */ + Int *p_nzdiag, /* # of diagonal entries */ + /* --------------- */ + cholmod_common *Common +) +{ + double aij_real = 0, aij_imag = 0, aji_real = 0, aji_imag = 0 ; + double *Ax, *Az ; + Int *Ap, *Ai, *Anz, *munch ; + Int packed, nrow, ncol, xtype, is_symmetric, is_skew, is_hermitian, posdiag, + j, p, pend, i, piend, result, xmatched, pmatched, nzdiag, i2, found ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (A, EMPTY) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ; + Common->status = CHOLMOD_OK ; + ASSERT (CHOLMOD(dump_sparse) (A, "cholmod_symmetry", Common) >= 0) ; + + if (p_xmatched == NULL || p_pmatched == NULL + || p_nzoffdiag == NULL || p_nzdiag == NULL) + { + /* option 2 is not performed if any output parameter is NULL */ + option = MAX (option, 1) ; + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + Ai = A->i ; + Ax = A->x ; + Az = A->z ; + Anz = A->nz ; + packed = A->packed ; + ncol = A->ncol ; + nrow = A->nrow ; + xtype = A->xtype ; + + /* ---------------------------------------------------------------------- */ + /* check if rectangular, unsorted, or stype is not zero */ + /* ---------------------------------------------------------------------- */ + + if (nrow != ncol) + { + /* matrix is rectangular */ + return (CHOLMOD_MM_RECTANGULAR) ; + } + + if (!(A->sorted) || A->stype != 0) + { + /* this function cannot determine the type or symmetry */ + return (EMPTY) ; + } + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* this function requires uninitialized Int workspace of size ncol */ + CHOLMOD(allocate_work) (0, ncol, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (EMPTY) ; + } + + munch = Common->Iwork ; /* the munch array is size ncol */ + + /* ---------------------------------------------------------------------- */ + /* determine symmetry of a square matrix */ + /* ---------------------------------------------------------------------- */ + + /* a complex or zomplex matrix is Hermitian until proven otherwise */ + is_hermitian = (xtype >= CHOLMOD_COMPLEX) ; + + /* any matrix is symmetric until proven otherwise */ + is_symmetric = TRUE ; + + /* a non-pattern matrix is skew-symmetric until proven otherwise */ + is_skew = (xtype != CHOLMOD_PATTERN) ; + + /* a matrix has positive diagonal entries until proven otherwise */ + posdiag = TRUE ; + + /* munch pointers start at the top of each column */ + for (j = 0 ; j < ncol ; j++) + { + munch [j] = Ap [j] ; + } + + xmatched = 0 ; + pmatched = 0 ; + nzdiag = 0 ; + + for (j = 0 ; j < ncol ; j++) /* examine each column of A */ + { + + /* ------------------------------------------------------------------ */ + /* look at the entire munch column j */ + /* ------------------------------------------------------------------ */ + + /* start at the munch point of column j, and go to end of the column */ + p = munch [j] ; + pend = (packed) ? (Ap [j+1]) : (Ap [j] + Anz [j]) ; + + for ( ; p < pend ; p++) + { + /* get the row index of A(i,j) */ + i = Ai [p] ; + + if (i < j) + { + + /* ---------------------------------------------------------- */ + /* A(i,j) in triu(A), but matching A(j,i) not in tril(A) */ + /* ---------------------------------------------------------- */ + + /* entry A(i,j) is unmatched; it appears in the upper triangular + * part, but not the lower triangular part. The matrix is + * unsymmetric. */ + is_hermitian = FALSE ; + is_symmetric = FALSE ; + is_skew = FALSE ; + + } + else if (i == j) + { + + /* ---------------------------------------------------------- */ + /* the diagonal A(j,j) is present; check its value */ + /* ---------------------------------------------------------- */ + + get_value (Ax, Az, p, xtype, &aij_real, &aij_imag) ; + if (aij_real != 0. || aij_imag != 0.) + { + /* diagonal is nonzero; matrix is not skew-symmetric */ + nzdiag++ ; + is_skew = FALSE ; + } + if (aij_real <= 0. || aij_imag != 0.) + { + /* diagonal negative or imaginary; not chol candidate */ + posdiag = FALSE ; + } + if (aij_imag != 0.) + { + /* imaginary part is present; not Hermitian */ + is_hermitian = FALSE ; + } + + } + else /* i > j */ + { + + /* ---------------------------------------------------------- */ + /* consider column i, up to and including row j */ + /* ---------------------------------------------------------- */ + + /* munch the entry at top of column i up to and incl row j */ + piend = (packed) ? (Ap [i+1]) : (Ap [i] + Anz [i]) ; + + found = FALSE ; + + for ( ; munch [i] < piend ; munch [i]++) + { + + i2 = Ai [munch [i]] ; + + if (i2 < j) + { + + /* -------------------------------------------------- */ + /* A(i2,i) in triu(A) but A(i,i2) not in tril(A) */ + /* -------------------------------------------------- */ + + /* The matrix is unsymmetric. */ + is_hermitian = FALSE ; + is_symmetric = FALSE ; + is_skew = FALSE ; + + } + else if (i2 == j) + { + + /* -------------------------------------------------- */ + /* both A(i,j) and A(j,i) exist in the matrix */ + /* -------------------------------------------------- */ + + /* this is one more matching entry in the pattern */ + pmatched += 2 ; + found = TRUE ; + + /* get the value of A(i,j) */ + get_value (Ax, Az, p, xtype, &aij_real, &aij_imag) ; + + /* get the value of A(j,i) */ + get_value (Ax, Az, munch [i], + xtype, &aji_real, &aji_imag) ; + + /* compare A(i,j) with A(j,i) */ + if (aij_real != aji_real || aij_imag != aji_imag) + { + /* the matrix cannot be symmetric */ + is_symmetric = FALSE ; + } + if (aij_real != -aji_real || aij_imag != aji_imag) + { + /* the matrix cannot be skew-symmetric */ + is_skew = FALSE ; + } + if (aij_real != aji_real || aij_imag != -aji_imag) + { + /* the matrix cannot be Hermitian */ + is_hermitian = FALSE ; + } + else + { + /* A(i,j) and A(j,i) are numerically matched */ + xmatched += 2 ; + } + + } + else /* i2 > j */ + { + + /* -------------------------------------------------- */ + /* entry A(i2,i) is not munched; consider it later */ + /* -------------------------------------------------- */ + + break ; + } + } + + if (!found) + { + /* A(i,j) in tril(A) but A(j,i) not in triu(A). + * The matrix is unsymmetric. */ + is_hermitian = FALSE ; + is_symmetric = FALSE ; + is_skew = FALSE ; + } + } + + if (option < 2 && !(is_symmetric || is_skew || is_hermitian)) + { + /* matrix is unsymmetric; terminate the test */ + return (CHOLMOD_MM_UNSYMMETRIC) ; + } + } + + /* ------------------------------------------------------------------ */ + /* quick return if not Cholesky candidate */ + /* ------------------------------------------------------------------ */ + + if (option < 1 && (!posdiag || nzdiag < ncol)) + { + /* Diagonal entry not present, or present but negative or with + * nonzero imaginary part. Quick return for option 0. */ + return (CHOLMOD_MM_UNSYMMETRIC) ; + } + } + + /* ---------------------------------------------------------------------- */ + /* return the results */ + /* ---------------------------------------------------------------------- */ + + if (nzdiag < ncol) + { + /* not all diagonal entries are present */ + posdiag = FALSE ; + } + + if (option >= 2) + { + *p_xmatched = xmatched ; + *p_pmatched = pmatched ; + *p_nzoffdiag = CHOLMOD(nnz) (A, Common) - nzdiag ; + *p_nzdiag = nzdiag ; + } + + result = CHOLMOD_MM_UNSYMMETRIC ; + if (is_hermitian) + { + /* complex Hermitian matrix, with either pos. or non-pos. diagonal */ + result = posdiag ? CHOLMOD_MM_HERMITIAN_POSDIAG : CHOLMOD_MM_HERMITIAN ; + } + else if (is_symmetric) + { + /* real or complex symmetric matrix, with pos. or non-pos. diagonal */ + result = posdiag ? CHOLMOD_MM_SYMMETRIC_POSDIAG : CHOLMOD_MM_SYMMETRIC ; + } + else if (is_skew) + { + /* real or complex skew-symmetric matrix */ + result = CHOLMOD_MM_SKEW_SYMMETRIC ; + } + return (result) ; +} +#endif diff --git a/src/CHOLMOD/MatrixOps/cholmod_vertcat.c b/src/CHOLMOD/MatrixOps/cholmod_vertcat.c new file mode 100644 index 0000000..4199a91 --- /dev/null +++ b/src/CHOLMOD/MatrixOps/cholmod_vertcat.c @@ -0,0 +1,201 @@ +/* ========================================================================== */ +/* === MatrixOps/cholmod_vertcat ============================================ */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/MatrixOps Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Vertical concatenation, C = [A ; B] in MATLAB notation. + * + * A and B can be up/lo/unsym; C is unsymmetric and packed. + * A and B must have the same number of columns. + * C is sorted if both A and B are sorted. + * + * workspace: Iwork (max (A->nrow, A->ncol, B->nrow, B->ncol)). + * allocates temporary copies of A and B if they are symmetric. + * + * Only pattern and real matrices are supported. Complex and zomplex matrices + * are supported only if "values" is FALSE. + */ + +#ifndef NMATRIXOPS + +#include "cholmod_internal.h" +#include "cholmod_matrixops.h" + + +/* ========================================================================== */ +/* === cholmod_vertcat ====================================================== */ +/* ========================================================================== */ + +cholmod_sparse *CHOLMOD(vertcat) +( + /* ---- input ---- */ + cholmod_sparse *A, /* left matrix to concatenate */ + cholmod_sparse *B, /* right matrix to concatenate */ + int values, /* if TRUE compute the numerical values of C */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Ax, *Bx, *Cx ; + Int *Ap, *Ai, *Anz, *Bp, *Bi, *Bnz, *Cp, *Ci ; + cholmod_sparse *C, *A2, *B2 ; + Int apacked, bpacked, anrow, bnrow, ncol, nrow, anz, bnz, nz, j, p, pend, + pdest ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (NULL) ; + RETURN_IF_NULL (A, NULL) ; + RETURN_IF_NULL (B, NULL) ; + values = values && + (A->xtype != CHOLMOD_PATTERN) && (B->xtype != CHOLMOD_PATTERN) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + RETURN_IF_XTYPE_INVALID (B, CHOLMOD_PATTERN, + values ? CHOLMOD_REAL : CHOLMOD_ZOMPLEX, NULL) ; + if (A->ncol != B->ncol) + { + /* A and B must have the same number of columns */ + ERROR (CHOLMOD_INVALID, "A and B must have same # of columns") ; + return (NULL) ; + } + /* A and B must have the same numerical type if values is TRUE (both must + * be CHOLMOD_REAL, this is implicitly checked above) */ + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + anrow = A->nrow ; + bnrow = B->nrow ; + ncol = A->ncol ; + CHOLMOD(allocate_work) (0, MAX3 (anrow, bnrow, ncol), 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + /* convert A to unsymmetric, if necessary */ + A2 = NULL ; + if (A->stype != 0) + { + /* workspace: Iwork (max (A->nrow,A->ncol)) */ + A2 = CHOLMOD(copy) (A, 0, values, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (NULL) ; + } + A = A2 ; + } + + /* convert B to unsymmetric, if necessary */ + B2 = NULL ; + if (B->stype != 0) + { + /* workspace: Iwork (max (B->nrow,B->ncol)) */ + B2 = CHOLMOD(copy) (B, 0, values, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + CHOLMOD(free_sparse) (&A2, Common) ; + return (NULL) ; + } + B = B2 ; + } + + Ap = A->p ; + Anz = A->nz ; + Ai = A->i ; + Ax = A->x ; + apacked = A->packed ; + + Bp = B->p ; + Bnz = B->nz ; + Bi = B->i ; + Bx = B->x ; + bpacked = B->packed ; + + /* ---------------------------------------------------------------------- */ + /* allocate C */ + /* ---------------------------------------------------------------------- */ + + anz = CHOLMOD(nnz) (A, Common) ; + bnz = CHOLMOD(nnz) (B, Common) ; + nrow = anrow + bnrow ; + nz = anz + bnz ; + + C = CHOLMOD(allocate_sparse) (nrow, ncol, nz, A->sorted && B->sorted, TRUE, + 0, values ? A->xtype : CHOLMOD_PATTERN, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + CHOLMOD(free_sparse) (&A2, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + return (NULL) ; + } + Cp = C->p ; + Ci = C->i ; + Cx = C->x ; + + /* ---------------------------------------------------------------------- */ + /* C = [A ; B] */ + /* ---------------------------------------------------------------------- */ + + pdest = 0 ; + for (j = 0 ; j < ncol ; j++) + { + /* attach A(:,j) as the first part of C(:,j) */ + p = Ap [j] ; + pend = (apacked) ? (Ap [j+1]) : (p + Anz [j]) ; + Cp [j] = pdest ; + for ( ; p < pend ; p++) + { + Ci [pdest] = Ai [p] ; + if (values) + { + Cx [pdest] = Ax [p] ; + } + pdest++ ; + } + + /* attach B(:,j) as the second part of C(:,j) */ + p = Bp [j] ; + pend = (bpacked) ? (Bp [j+1]) : (p + Bnz [j]) ; + for ( ; p < pend ; p++) + { + Ci [pdest] = Bi [p] + anrow ; + if (values) + { + Cx [pdest] = Bx [p] ; + } + pdest++ ; + } + } + Cp [ncol] = pdest ; + ASSERT (pdest == nz) ; + + /* ---------------------------------------------------------------------- */ + /* free the unsymmetric copies of A and B, and return C */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(free_sparse) (&A2, Common) ; + CHOLMOD(free_sparse) (&B2, Common) ; + return (C) ; +} +#endif diff --git a/src/CHOLMOD/MatrixOps/gpl.txt b/src/CHOLMOD/MatrixOps/gpl.txt new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/src/CHOLMOD/MatrixOps/gpl.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/src/CHOLMOD/MatrixOps/t_cholmod_sdmult.c b/src/CHOLMOD/MatrixOps/t_cholmod_sdmult.c new file mode 100644 index 0000000..7ddd3b6 --- /dev/null +++ b/src/CHOLMOD/MatrixOps/t_cholmod_sdmult.c @@ -0,0 +1,726 @@ +/* ========================================================================== */ +/* === MatrixOps/t_cholmod_sdmult =========================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/MatrixOps Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/MatrixOps Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Template routine for cholmod_sdmult */ + +#include "cholmod_template.h" + +#undef ADVANCE + +#ifdef REAL +#define ADVANCE(x,z,d) x += d +#elif defined (COMPLEX) +#define ADVANCE(x,z,d) x += 2*d +#else +#define ADVANCE(x,z,d) x += d ; z += d +#endif + +/* ========================================================================== */ +/* === t_cholmod_sdmult ===================================================== */ +/* ========================================================================== */ + +static void TEMPLATE (cholmod_sdmult) +( + /* ---- input ---- */ + cholmod_sparse *A, /* sparse matrix to multiply */ + int transpose, /* use A if 0, or A' otherwise */ + double alpha [2], /* scale factor for A */ + double beta [2], /* scale factor for Y */ + cholmod_dense *X, /* dense matrix to multiply */ + /* ---- in/out --- */ + cholmod_dense *Y, /* resulting dense matrix */ + /* -- workspace -- */ + double *W /* size 4*nx if needed, twice that for c/zomplex case */ +) +{ + + double yx [8], xx [8], ax [2] ; +#ifdef ZOMPLEX + double yz [4], xz [4], az [1] ; + double betaz [1], alphaz [1] ; +#endif + + double *Ax, *Az, *Xx, *Xz, *Yx, *Yz, *w, *Wz ; + Int *Ap, *Ai, *Anz ; + size_t nx, ny, dx, dy ; + Int packed, nrow, ncol, j, k, p, pend, kcol, i ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + +#ifdef ZOMPLEX + betaz [0] = beta [1] ; + alphaz [0] = alpha [1] ; +#endif + + ny = transpose ? A->ncol : A->nrow ; /* required length of Y */ + nx = transpose ? A->nrow : A->ncol ; /* required length of X */ + + nrow = A->nrow ; + ncol = A->ncol ; + + Ap = A->p ; + Anz = A->nz ; + Ai = A->i ; + Ax = A->x ; + Az = A->z ; + packed = A->packed ; + Xx = X->x ; + Xz = X->z ; + Yx = Y->x ; + Yz = Y->z ; + kcol = X->ncol ; + dy = Y->d ; + dx = X->d ; + w = W ; + Wz = W + 4*nx ; + + /* ---------------------------------------------------------------------- */ + /* Y = beta * Y */ + /* ---------------------------------------------------------------------- */ + + if (ENTRY_IS_ZERO (beta, betaz, 0)) + { + for (k = 0 ; k < kcol ; k++) + { + for (i = 0 ; i < ((Int) ny) ; i++) + { + /* y [i] = 0. ; */ + CLEAR (Yx, Yz, i) ; + } + /* y += dy ; */ + ADVANCE (Yx,Yz,dy) ; + } + } + else if (!ENTRY_IS_ONE (beta, betaz, 0)) + { + for (k = 0 ; k < kcol ; k++) + { + for (i = 0 ; i < ((Int) ny) ; i++) + { + /* y [i] *= beta [0] ; */ + MULT (Yx,Yz,i, Yx,Yz,i, beta,betaz, 0) ; + } + /* y += dy ; */ + ADVANCE (Yx,Yz,dy) ; + } + } + + if (ENTRY_IS_ZERO (alpha, alphaz, 0)) + { + /* nothing else to do */ + return ; + } + + /* ---------------------------------------------------------------------- */ + /* Y += alpha * op(A) * X, where op(A)=A or A' */ + /* ---------------------------------------------------------------------- */ + + Yx = Y->x ; + Yz = Y->z ; + + k = 0 ; + + if (A->stype == 0) + { + + if (transpose) + { + + /* -------------------------------------------------------------- */ + /* Y += alpha * A' * x, unsymmetric case */ + /* -------------------------------------------------------------- */ + + if (kcol % 4 == 1) + { + + for (j = 0 ; j < ncol ; j++) + { + /* yj = 0. ; */ + CLEAR (yx, yz, 0) ; + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + /* yj += conj(Ax [p]) * x [Ai [p]] ; */ + i = Ai [p] ; + ASSIGN_CONJ (ax,az,0, Ax,Az,p) ; + MULTADD (yx,yz,0, ax,az,0, Xx,Xz,i) ; + } + /* y [j] += alpha [0] * yj ; */ + MULTADD (Yx,Yz,j, alpha,alphaz,0, yx,yz,0) ; + } + /* y += dy ; */ + /* x += dx ; */ + ADVANCE (Yx,Yz,dy) ; + ADVANCE (Xx,Xz,dx) ; + k++ ; + + } + else if (kcol % 4 == 2) + { + + for (j = 0 ; j < ncol ; j++) + { + /* yj0 = 0. ; */ + /* yj1 = 0. ; */ + CLEAR (yx,yz,0) ; + CLEAR (yx,yz,1) ; + + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + /* aij = conj (Ax [p]) ; */ + ASSIGN_CONJ (ax,az,0, Ax,Az,p) ; + + /* yj0 += aij * x [i ] ; */ + /* yj1 += aij * x [i+dx] ; */ + MULTADD (yx,yz,0, ax,az,0, Xx,Xz,i) ; + MULTADD (yx,yz,1, ax,az,0, Xx,Xz,i+dx) ; + } + /* y [j ] += alpha [0] * yj0 ; */ + /* y [j+dy] += alpha [0] * yj1 ; */ + MULTADD (Yx,Yz,j, alpha,alphaz,0, yx,yz,0) ; + MULTADD (Yx,Yz,j+dy, alpha,alphaz,0, yx,yz,1) ; + } + /* y += 2*dy ; */ + /* x += 2*dx ; */ + ADVANCE (Yx,Yz,2*dy) ; + ADVANCE (Xx,Xz,2*dx) ; + k += 2 ; + + } + else if (kcol % 4 == 3) + { + + for (j = 0 ; j < ncol ; j++) + { + /* yj0 = 0. ; */ + /* yj1 = 0. ; */ + /* yj2 = 0. ; */ + CLEAR (yx,yz,0) ; + CLEAR (yx,yz,1) ; + CLEAR (yx,yz,2) ; + + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + /* aij = conj (Ax [p]) ; */ + ASSIGN_CONJ (ax,az,0, Ax,Az,p) ; + + /* yj0 += aij * x [i ] ; */ + /* yj1 += aij * x [i+ dx] ; */ + /* yj2 += aij * x [i+2*dx] ; */ + MULTADD (yx,yz,0, ax,az,0, Xx,Xz,i) ; + MULTADD (yx,yz,1, ax,az,0, Xx,Xz,i+dx) ; + MULTADD (yx,yz,2, ax,az,0, Xx,Xz,i+2*dx) ; + } + /* y [j ] += alpha [0] * yj0 ; */ + /* y [j+ dy] += alpha [0] * yj1 ; */ + /* y [j+2*dy] += alpha [0] * yj2 ; */ + MULTADD (Yx,Yz,j, alpha,alphaz,0, yx,yz,0) ; + MULTADD (Yx,Yz,j+dy, alpha,alphaz,0, yx,yz,1) ; + MULTADD (Yx,Yz,j+2*dy, alpha,alphaz,0, yx,yz,2) ; + } + /* y += 3*dy ; */ + /* x += 3*dx ; */ + ADVANCE (Yx,Yz,3*dy) ; + ADVANCE (Xx,Xz,3*dx) ; + k += 3 ; + } + + for ( ; k < kcol ; k += 4) + { + for (j = 0 ; j < ncol ; j++) + { + /* yj0 = 0. ; */ + /* yj1 = 0. ; */ + /* yj2 = 0. ; */ + /* yj3 = 0. ; */ + CLEAR (yx,yz,0) ; + CLEAR (yx,yz,1) ; + CLEAR (yx,yz,2) ; + CLEAR (yx,yz,3) ; + + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + /* aij = conj(Ax [p]) ; */ + ASSIGN_CONJ (ax,az,0, Ax,Az,p) ; + + /* yj0 += aij * x [i ] ; */ + /* yj1 += aij * x [i+ dx] ; */ + /* yj2 += aij * x [i+2*dx] ; */ + /* yj3 += aij * x [i+3*dx] ; */ + MULTADD (yx,yz,0, ax,az,0, Xx,Xz,i) ; + MULTADD (yx,yz,1, ax,az,0, Xx,Xz,i+dx) ; + MULTADD (yx,yz,2, ax,az,0, Xx,Xz,i+2*dx) ; + MULTADD (yx,yz,3, ax,az,0, Xx,Xz,i+3*dx) ; + + } + /* y [j ] += alpha [0] * yj0 ; */ + /* y [j+ dy] += alpha [0] * yj1 ; */ + /* y [j+2*dy] += alpha [0] * yj2 ; */ + /* y [j+3*dy] += alpha [0] * yj3 ; */ + MULTADD (Yx,Yz,j, alpha,alphaz,0, yx,yz,0) ; + MULTADD (Yx,Yz,j+dy, alpha,alphaz,0, yx,yz,1) ; + MULTADD (Yx,Yz,j+2*dy, alpha,alphaz,0, yx,yz,2) ; + MULTADD (Yx,Yz,j+3*dy, alpha,alphaz,0, yx,yz,3) ; + } + /* y += 4*dy ; */ + /* x += 4*dx ; */ + ADVANCE (Yx,Yz,4*dy) ; + ADVANCE (Xx,Xz,4*dx) ; + } + + } + else + { + + /* -------------------------------------------------------------- */ + /* Y += alpha * A * x, unsymmetric case */ + /* -------------------------------------------------------------- */ + + if (kcol % 4 == 1) + { + + for (j = 0 ; j < ncol ; j++) + { + /* xj = alpha [0] * x [j] ; */ + MULT (xx,xz,0, alpha,alphaz,0, Xx,Xz,j) ; + + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + /* y [Ai [p]] += Ax [p] * xj ; */ + i = Ai [p] ; + MULTADD (Yx,Yz,i, Ax,Az,p, xx,xz,0) ; + } + } + /* y += dy ; */ + /* x += dx ; */ + ADVANCE (Yx,Yz,dy) ; + ADVANCE (Xx,Xz,dx) ; + k++ ; + + } + else if (kcol % 4 == 2) + { + + for (j = 0 ; j < ncol ; j++) + { + /* xj0 = alpha [0] * x [j ] ; */ + /* xj1 = alpha [0] * x [j+dx] ; */ + MULT (xx,xz,0, alpha,alphaz,0, Xx,Xz,j) ; + MULT (xx,xz,1, alpha,alphaz,0, Xx,Xz,j+dx) ; + + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + /* aij = Ax [p] ; */ + ASSIGN (ax,az,0, Ax,Az,p) ; + + /* y [i ] += aij * xj0 ; */ + /* y [i+dy] += aij * xj1 ; */ + MULTADD (Yx,Yz,i, ax,az,0, xx,xz,0) ; + MULTADD (Yx,Yz,i+dy, ax,az,0, xx,xz,1) ; + } + } + /* y += 2*dy ; */ + /* x += 2*dx ; */ + ADVANCE (Yx,Yz,2*dy) ; + ADVANCE (Xx,Xz,2*dx) ; + k += 2 ; + + } + else if (kcol % 4 == 3) + { + + for (j = 0 ; j < ncol ; j++) + { + /* xj0 = alpha [0] * x [j ] ; */ + /* xj1 = alpha [0] * x [j+ dx] ; */ + /* xj2 = alpha [0] * x [j+2*dx] ; */ + MULT (xx,xz,0, alpha,alphaz,0, Xx,Xz,j) ; + MULT (xx,xz,1, alpha,alphaz,0, Xx,Xz,j+dx) ; + MULT (xx,xz,2, alpha,alphaz,0, Xx,Xz,j+2*dx) ; + + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + /* aij = Ax [p] ; */ + ASSIGN (ax,az,0, Ax,Az,p) ; + + /* y [i ] += aij * xj0 ; */ + /* y [i+ dy] += aij * xj1 ; */ + /* y [i+2*dy] += aij * xj2 ; */ + MULTADD (Yx,Yz,i, ax,az,0, xx,xz,0) ; + MULTADD (Yx,Yz,i+dy, ax,az,0, xx,xz,1) ; + MULTADD (Yx,Yz,i+2*dy, ax,az,0, xx,xz,2) ; + } + } + /* y += 3*dy ; */ + /* x += 3*dx ; */ + ADVANCE (Yx,Yz,3*dy) ; + ADVANCE (Xx,Xz,3*dx) ; + k += 3 ; + } + + for ( ; k < kcol ; k += 4) + { + for (j = 0 ; j < ncol ; j++) + { + /* xj0 = alpha [0] * x [j ] ; */ + /* xj1 = alpha [0] * x [j+ dx] ; */ + /* xj2 = alpha [0] * x [j+2*dx] ; */ + /* xj3 = alpha [0] * x [j+3*dx] ; */ + MULT (xx,xz,0, alpha,alphaz,0, Xx,Xz,j) ; + MULT (xx,xz,1, alpha,alphaz,0, Xx,Xz,j+dx) ; + MULT (xx,xz,2, alpha,alphaz,0, Xx,Xz,j+2*dx) ; + MULT (xx,xz,3, alpha,alphaz,0, Xx,Xz,j+3*dx) ; + + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + /* aij = Ax [p] ; */ + ASSIGN (ax,az,0, Ax,Az,p) ; + + /* y [i ] += aij * xj0 ; */ + /* y [i+ dy] += aij * xj1 ; */ + /* y [i+2*dy] += aij * xj2 ; */ + /* y [i+3*dy] += aij * xj3 ; */ + MULTADD (Yx,Yz,i, ax,az,0, xx,xz,0) ; + MULTADD (Yx,Yz,i+dy, ax,az,0, xx,xz,1) ; + MULTADD (Yx,Yz,i+2*dy, ax,az,0, xx,xz,2) ; + MULTADD (Yx,Yz,i+3*dy, ax,az,0, xx,xz,3) ; + } + } + /* y += 4*dy ; */ + /* x += 4*dx ; */ + ADVANCE (Yx,Yz,4*dy) ; + ADVANCE (Xx,Xz,4*dx) ; + } + } + + } + else + { + + /* ------------------------------------------------------------------ */ + /* Y += alpha * (A or A') * x, symmetric case (upper/lower) */ + /* ------------------------------------------------------------------ */ + + /* Only the upper/lower triangular part and the diagonal of A is used. + * Since both x and y are written to in the innermost loop, this + * code can experience cache bank conflicts if x is used directly. + * Thus, a copy is made of x, four columns at a time, if x has + * four or more columns. + */ + + if (kcol % 4 == 1) + { + + for (j = 0 ; j < ncol ; j++) + { + /* yj = 0. ; */ + CLEAR (yx,yz,0) ; + + /* xj = alpha [0] * x [j] ; */ + MULT (xx,xz,0, alpha,alphaz,0, Xx,Xz,j) ; + + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i == j) + { + /* y [i] += Ax [p] * xj ; */ + MULTADD (Yx,Yz,i, Ax,Az,p, xx,xz,0) ; + } + else if ((A->stype > 0 && i < j) || (A->stype < 0 && i > j)) + { + /* aij = Ax [p] ; */ + ASSIGN (ax,az,0, Ax,Az,p) ; + + /* y [i] += aij * xj ; */ + /* yj += aij * x [i] ; */ + MULTADD (Yx,Yz,i, ax,az,0, xx,xz,0) ; + MULTADDCONJ (yx,yz,0, ax,az,0, Xx,Xz,i) ; + + + } + } + /* y [j] += alpha [0] * yj ; */ + MULTADD (Yx,Yz,j, alpha,alphaz,0, yx,yz,0) ; + + } + /* y += dy ; */ + /* x += dx ; */ + ADVANCE (Yx,Yz,dy) ; + ADVANCE (Xx,Xz,dx) ; + k++ ; + + } + else if (kcol % 4 == 2) + { + + for (j = 0 ; j < ncol ; j++) + { + /* yj0 = 0. ; */ + /* yj1 = 0. ; */ + CLEAR (yx,yz,0) ; + CLEAR (yx,yz,1) ; + + /* xj0 = alpha [0] * x [j ] ; */ + /* xj1 = alpha [0] * x [j+dx] ; */ + MULT (xx,xz,0, alpha,alphaz,0, Xx,Xz,j) ; + MULT (xx,xz,1, alpha,alphaz,0, Xx,Xz,j+dx) ; + + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i == j) + { + /* aij = Ax [p] ; */ + ASSIGN (ax,az,0, Ax,Az,p) ; + + /* y [i ] += aij * xj0 ; */ + /* y [i+dy] += aij * xj1 ; */ + MULTADD (Yx,Yz,i, ax,az,0, xx,xz,0) ; + MULTADD (Yx,Yz,i+dy, ax,az,0, xx,xz,1) ; + + } + else if ((A->stype > 0 && i < j) || (A->stype < 0 && i > j)) + { + /* aij = Ax [p] ; */ + ASSIGN (ax,az,0, Ax,Az,p) ; + + /* y [i ] += aij * xj0 ; */ + /* y [i+dy] += aij * xj1 ; */ + /* yj0 += aij * x [i ] ; */ + /* yj1 += aij * x [i+dx] ; */ + MULTADD (Yx,Yz,i, ax,az,0, xx,xz,0) ; + MULTADD (Yx,Yz,i+dy, ax,az,0, xx,xz,1) ; + MULTADDCONJ (yx,yz,0, ax,az,0, Xx,Xz,i) ; + MULTADDCONJ (yx,yz,1, ax,az,0, Xx,Xz,i+dx) ; + + } + } + /* y [j ] += alpha [0] * yj0 ; */ + /* y [j+dy] += alpha [0] * yj1 ; */ + MULTADD (Yx,Yz,j, alpha,alphaz,0, yx,yz,0) ; + MULTADD (Yx,Yz,j+dy, alpha,alphaz,0, yx,yz,1) ; + + } + /* y += 2*dy ; */ + /* x += 2*dx ; */ + ADVANCE (Yx,Yz,2*dy) ; + ADVANCE (Xx,Xz,2*dx) ; + k += 2 ; + + } + else if (kcol % 4 == 3) + { + + for (j = 0 ; j < ncol ; j++) + { + /* yj0 = 0. ; */ + /* yj1 = 0. ; */ + /* yj2 = 0. ; */ + CLEAR (yx,yz,0) ; + CLEAR (yx,yz,1) ; + CLEAR (yx,yz,2) ; + + /* xj0 = alpha [0] * x [j ] ; */ + /* xj1 = alpha [0] * x [j+ dx] ; */ + /* xj2 = alpha [0] * x [j+2*dx] ; */ + MULT (xx,xz,0, alpha,alphaz,0, Xx,Xz,j) ; + MULT (xx,xz,1, alpha,alphaz,0, Xx,Xz,j+dx) ; + MULT (xx,xz,2, alpha,alphaz,0, Xx,Xz,j+2*dx) ; + + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i == j) + { + + /* aij = Ax [p] ; */ + ASSIGN (ax,az,0, Ax,Az,p) ; + + /* y [i ] += aij * xj0 ; */ + /* y [i+ dy] += aij * xj1 ; */ + /* y [i+2*dy] += aij * xj2 ; */ + MULTADD (Yx,Yz,i, ax,az,0, xx,xz,0) ; + MULTADD (Yx,Yz,i+dy, ax,az,0, xx,xz,1) ; + MULTADD (Yx,Yz,i+2*dy, ax,az,0, xx,xz,2) ; + + } + else if ((A->stype > 0 && i < j) || (A->stype < 0 && i > j)) + { + + /* aij = Ax [p] ; */ + ASSIGN (ax,az,0, Ax,Az,p) ; + + /* y [i ] += aij * xj0 ; */ + /* y [i+ dy] += aij * xj1 ; */ + /* y [i+2*dy] += aij * xj2 ; */ + /* yj0 += aij * x [i ] ; */ + /* yj1 += aij * x [i+ dx] ; */ + /* yj2 += aij * x [i+2*dx] ; */ + MULTADD (Yx,Yz,i, ax,az,0, xx,xz,0) ; + MULTADD (Yx,Yz,i+dy, ax,az,0, xx,xz,1) ; + MULTADD (Yx,Yz,i+2*dy, ax,az,0, xx,xz,2) ; + MULTADDCONJ (yx,yz,0, ax,az,0, Xx,Xz,i) ; + MULTADDCONJ (yx,yz,1, ax,az,0, Xx,Xz,i+dx) ; + MULTADDCONJ (yx,yz,2, ax,az,0, Xx,Xz,i+2*dx) ; + + } + } + /* y [j ] += alpha [0] * yj0 ; */ + /* y [j+ dy] += alpha [0] * yj1 ; */ + /* y [j+2*dy] += alpha [0] * yj2 ; */ + MULTADD (Yx,Yz,j, alpha,alphaz,0, yx,yz,0) ; + MULTADD (Yx,Yz,j+dy, alpha,alphaz,0, yx,yz,1) ; + MULTADD (Yx,Yz,j+2*dy, alpha,alphaz,0, yx,yz,2) ; + + } + /* y += 3*dy ; */ + /* x += 3*dx ; */ + ADVANCE (Yx,Yz,3*dy) ; + ADVANCE (Xx,Xz,3*dx) ; + + k += 3 ; + } + + /* copy four columns of X into W, and put in row form */ + for ( ; k < kcol ; k += 4) + { + + for (j = 0 ; j < ncol ; j++) + { + /* w [4*j ] = x [j ] ; */ + /* w [4*j+1] = x [j+ dx] ; */ + /* w [4*j+2] = x [j+2*dx] ; */ + /* w [4*j+3] = x [j+3*dx] ; */ + ASSIGN (w,Wz,4*j , Xx,Xz,j ) ; + ASSIGN (w,Wz,4*j+1, Xx,Xz,j+dx ) ; + ASSIGN (w,Wz,4*j+2, Xx,Xz,j+2*dx) ; + ASSIGN (w,Wz,4*j+3, Xx,Xz,j+3*dx) ; + } + + for (j = 0 ; j < ncol ; j++) + { + /* yj0 = 0. ; */ + /* yj1 = 0. ; */ + /* yj2 = 0. ; */ + /* yj3 = 0. ; */ + CLEAR (yx,yz,0) ; + CLEAR (yx,yz,1) ; + CLEAR (yx,yz,2) ; + CLEAR (yx,yz,3) ; + + /* xj0 = alpha [0] * w [4*j ] ; */ + /* xj1 = alpha [0] * w [4*j+1] ; */ + /* xj2 = alpha [0] * w [4*j+2] ; */ + /* xj3 = alpha [0] * w [4*j+3] ; */ + MULT (xx,xz,0, alpha,alphaz,0, w,Wz,4*j) ; + MULT (xx,xz,1, alpha,alphaz,0, w,Wz,4*j+1) ; + MULT (xx,xz,2, alpha,alphaz,0, w,Wz,4*j+2) ; + MULT (xx,xz,3, alpha,alphaz,0, w,Wz,4*j+3) ; + + p = Ap [j] ; + pend = (packed) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i == j) + { + /* aij = Ax [p] ; */ + ASSIGN (ax,az,0, Ax,Az,p) ; + + /* y [i ] += aij * xj0 ; */ + /* y [i+ dy] += aij * xj1 ; */ + /* y [i+2*dy] += aij * xj2 ; */ + /* y [i+3*dy] += aij * xj3 ; */ + MULTADD (Yx,Yz,i , ax,az,0, xx,xz,0) ; + MULTADD (Yx,Yz,i+dy , ax,az,0, xx,xz,1) ; + MULTADD (Yx,Yz,i+2*dy, ax,az,0, xx,xz,2) ; + MULTADD (Yx,Yz,i+3*dy, ax,az,0, xx,xz,3) ; + + } + else if ((A->stype > 0 && i < j) || (A->stype < 0 && i > j)) + { + /* aij = Ax [p] ; */ + ASSIGN (ax,az,0, Ax,Az,p) ; + + /* y [i ] += aij * xj0 ; */ + /* y [i+ dy] += aij * xj1 ; */ + /* y [i+2*dy] += aij * xj2 ; */ + /* y [i+3*dy] += aij * xj3 ; */ + /* yj0 += aij * w [4*i ] ; */ + /* yj1 += aij * w [4*i+1] ; */ + /* yj2 += aij * w [4*i+2] ; */ + /* yj3 += aij * w [4*i+3] ; */ + MULTADD (Yx,Yz,i, ax,az,0, xx,xz,0) ; + MULTADD (Yx,Yz,i+dy, ax,az,0, xx,xz,1) ; + MULTADD (Yx,Yz,i+2*dy, ax,az,0, xx,xz,2) ; + MULTADD (Yx,Yz,i+3*dy, ax,az,0, xx,xz,3) ; + MULTADDCONJ (yx,yz,0, ax,az,0, w,Wz,4*i) ; + MULTADDCONJ (yx,yz,1, ax,az,0, w,Wz,4*i+1) ; + MULTADDCONJ (yx,yz,2, ax,az,0, w,Wz,4*i+2) ; + MULTADDCONJ (yx,yz,3, ax,az,0, w,Wz,4*i+3) ; + + } + } + /* y [j ] += alpha [0] * yj0 ; */ + /* y [j+ dy] += alpha [0] * yj1 ; */ + /* y [j+2*dy] += alpha [0] * yj2 ; */ + /* y [j+3*dy] += alpha [0] * yj3 ; */ + MULTADD (Yx,Yz,j , alpha,alphaz,0, yx,yz,0) ; + MULTADD (Yx,Yz,j+dy , alpha,alphaz,0, yx,yz,1) ; + MULTADD (Yx,Yz,j+2*dy, alpha,alphaz,0, yx,yz,2) ; + MULTADD (Yx,Yz,j+3*dy, alpha,alphaz,0, yx,yz,3) ; + + } + /* y += 4*dy ; */ + /* x += 4*dx ; */ + ADVANCE (Yx,Yz,4*dy) ; + ADVANCE (Xx,Xz,4*dx) ; + + } + } +} + + +#undef PATTERN +#undef REAL +#undef COMPLEX +#undef ZOMPLEX diff --git a/src/CHOLMOD/Modify/License.txt b/src/CHOLMOD/Modify/License.txt new file mode 100644 index 0000000..6e35274 --- /dev/null +++ b/src/CHOLMOD/Modify/License.txt @@ -0,0 +1,25 @@ +CHOLMOD/Modify Module. +Copyright (C) 2005-2006, Timothy A. Davis and William W. Hager +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + +Note that this license is for the CHOLMOD/Modify module only. +All CHOLMOD modules are licensed separately. + + +-------------------------------------------------------------------------------- + + +This Module is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This Module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this Module; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/src/CHOLMOD/Modify/cholmod_rowadd.c b/src/CHOLMOD/Modify/cholmod_rowadd.c new file mode 100644 index 0000000..dd7ecf0 --- /dev/null +++ b/src/CHOLMOD/Modify/cholmod_rowadd.c @@ -0,0 +1,678 @@ +/* ========================================================================== */ +/* === Modify/cholmod_rowadd ================================================ */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Modify Module. + * Copyright (C) 2005-2006, Timothy A. Davis and William W. Hager. + * The CHOLMOD/Modify Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Adds a row and column to an LDL' factorization, and optionally updates the + * solution to Lx=b. + * + * workspace: Flag (nrow), Head (nrow+1), W (2*nrow), Iwork (2*nrow) + * + * Only real matrices are supported. A symbolic L is converted into a + * numeric identity matrix before the row is added. + */ + +#ifndef NMODIFY + +#include "cholmod_internal.h" +#include "cholmod_modify.h" + + +/* ========================================================================== */ +/* === cholmod_rowadd ======================================================= */ +/* ========================================================================== */ + +/* cholmod_rowadd adds a row to the LDL' factorization. It computes the kth + * row and kth column of L, and then updates the submatrix L (k+1:n,k+1:n) + * accordingly. The kth row and column of L should originally be equal to the + * kth row and column of the identity matrix (they are treated as such, if they + * are not). The kth row/column of L is computed as the factorization of the + * kth row/column of the matrix to factorize, which is provided as a single + * n-by-1 sparse matrix R. The sparse vector R need not be sorted. + */ + +int CHOLMOD(rowadd) +( + /* ---- input ---- */ + size_t k, /* row/column index to add */ + cholmod_sparse *R, /* row/column of matrix to factorize (n-by-1) */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) +{ + double bk [2] ; + bk [0] = 0. ; + bk [1] = 0. ; + return (CHOLMOD(rowadd_mark) (k, R, bk, NULL, L, NULL, NULL, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_rowadd_solve ================================================= */ +/* ========================================================================== */ + +/* Does the same as cholmod_rowadd, and also updates the solution to Lx=b + * See cholmod_updown for a description of how Lx=b is updated. There is on + * additional parameter: bk specifies the new kth entry of b. + */ + +int CHOLMOD(rowadd_solve) +( + /* ---- input ---- */ + size_t k, /* row/column index to add */ + cholmod_sparse *R, /* row/column of matrix to factorize (n-by-1) */ + double bk [2], /* kth entry of the right-hand-side b */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) +{ + return (CHOLMOD(rowadd_mark) (k, R, bk, NULL, L, X, DeltaB, Common)) ; +} + + +/* ========================================================================== */ +/* === icomp ================================================================ */ +/* ========================================================================== */ + +/* for sorting by qsort */ +static int icomp (Int *i, Int *j) +{ + if (*i < *j) + { + return (-1) ; + } + else + { + return (1) ; + } +} + + +/* ========================================================================== */ +/* === cholmod_rowadd_mark ================================================== */ +/* ========================================================================== */ + +/* Does the same as cholmod_rowadd_solve, except only part of L is used in + * the update/downdate of the solution to Lx=b. This routine is an "expert" + * routine. It is meant for use in LPDASA only. */ + +int CHOLMOD(rowadd_mark) +( + /* ---- input ---- */ + size_t kadd, /* row/column index to add */ + cholmod_sparse *R, /* row/column of matrix to factorize (n-by-1) */ + double bk [2], /* kth entry of the right hand side, b */ + Int *colmark, /* Int array of size 1. See cholmod_updown.c */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) +{ + double dk, yj, l_kj, lx, l_ij, sqrt_dk, dj, xk, rnz, fl ; + double *Lx, *W, *Cx, *Rx, *Xx, *Nx ; + Int *Li, *Lp, *Lnz, *Flag, *Stack, *Ci, *Rj, *Rp, *Lnext, *Iwork, *Rnz ; + cholmod_sparse *C, Cmatrix ; + Int i, j, p, pend, top, len, kk, li, lnz, mark, k, n, parent, Cp [2], + do_solve, do_update ; + size_t s ; + int ok = TRUE ; + DEBUG (Int lastrow) ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_NULL (R, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_PATTERN, CHOLMOD_REAL, FALSE) ; + RETURN_IF_XTYPE_INVALID (R, CHOLMOD_REAL, CHOLMOD_REAL, FALSE) ; + n = L->n ; + k = kadd ; + if (kadd >= L->n || k < 0) + { + ERROR (CHOLMOD_INVALID, "k invalid") ; + return (FALSE) ; + } + if (R->ncol != 1 || R->nrow != L->n) + { + ERROR (CHOLMOD_INVALID, "R invalid") ; + return (FALSE) ; + } + Rj = R->i ; + Rx = R->x ; + Rp = R->p ; + Rnz = R->nz ; + rnz = (R->packed) ? (Rp [1]) : (Rnz [0]) ; + do_solve = (X != NULL) && (DeltaB != NULL) ; + if (do_solve) + { + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_REAL, FALSE) ; + RETURN_IF_XTYPE_INVALID (DeltaB, CHOLMOD_REAL, CHOLMOD_REAL, FALSE) ; + Xx = X->x ; + Nx = DeltaB->x ; + if (X->nrow != L->n || X->ncol != 1 || DeltaB->nrow != L->n || + DeltaB->ncol != 1 || Xx == NULL || Nx == NULL) + { + ERROR (CHOLMOD_INVALID, "X and/or DeltaB invalid") ; + return (FALSE) ; + } + } + else + { + Xx = NULL ; + Nx = NULL ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* s = 2*n */ + s = CHOLMOD(mult_size_t) (n, 2, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (n, s, s, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, s, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* convert to simplicial numeric LDL' factor, if not already */ + /* ---------------------------------------------------------------------- */ + + if (L->xtype == CHOLMOD_PATTERN || L->is_super || L->is_ll) + { + /* can only update/downdate a simplicial LDL' factorization */ + CHOLMOD(change_factor) (CHOLMOD_REAL, FALSE, FALSE, FALSE, FALSE, L, + Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory, L is returned unchanged */ + return (FALSE) ; + } + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + /* inputs, not modified on output: */ + Lp = L->p ; /* size n+1. input, not modified on output */ + + /* outputs, contents defined on input for incremental case only: */ + Lnz = L->nz ; /* size n */ + Li = L->i ; /* size L->nzmax. Can change in size. */ + Lx = L->x ; /* size L->nzmax. Can change in size. */ + Lnext = L->next ; /* size n+2 */ + + ASSERT (L->nz != NULL) ; + + PRINT1 (("rowadd:\n")) ; + fl = 0 ; + +#if 0 +#ifndef NDEBUG + /* column k of L should be zero, except for the diagonal. This test is + * overly cautious. */ + for (p = Lp [k] + 1 ; p < Lp [k] + Lnz [k] ; p++) ASSERT (Lx [p] == 0) ; +#endif +#endif + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Flag = Common->Flag ; /* size n */ + W = Common->Xwork ; /* size n */ + Cx = W + n ; /* size n (use 2nd column of Xwork for C) */ + Iwork = Common->Iwork ; + Stack = Iwork ; /* size n (i/i/l), also in cholmod_updown */ + Ci = Iwork + n ; /* size n (i/i/l) */ + /* NOTE: cholmod_updown uses Iwork [0..n-1] (i/i/l) as Stack as well */ + + mark = Common->mark ; + + /* copy Rj/Rx into W/Ci */ + for (p = 0 ; p < rnz ; p++) + { + i = Rj [p] ; + ASSERT (i >= 0 && i < n) ; + W [i] = Rx [p] ; + Ci [p] = i ; + } + + /* At this point, W [Ci [0..rnz-1]] holds the sparse vector to add */ + /* The nonzero pattern of column W is held in Ci (it may be unsorted). */ + + /* ---------------------------------------------------------------------- */ + /* symbolic factorization to get pattern of kth row of L */ + /* ---------------------------------------------------------------------- */ + + DEBUG (for (p = 0 ; p < rnz ; p++) + PRINT1 (("C ("ID",%g)\n", Ci [p], W [Ci [p]]))) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* flag the diagonal */ + Flag [k] = mark ; + + /* find the union of all the paths */ + top = n ; + lnz = 0 ; /* # of nonzeros in column k of L, excluding diagonal */ + for (p = 0 ; p < rnz ; p++) + { + i = Ci [p] ; + + if (i < k) + { + + /* walk from i = entry in Ci to root (and stop if i marked)*/ + PRINT2 (("\nwalk from i = "ID" towards k = "ID"\n", i, k)) ; + len = 0 ; + + /* walk up tree, but stop if we go below the diagonal */ + while (i < k && i != EMPTY && Flag [i] < mark) + { + PRINT2 ((" Add "ID" to path\n", i)) ; + ASSERT (i >= 0 && i < k) ; + Stack [len++] = i ; /* place i on the stack */ + Flag [i] = mark ; /* mark i as visited */ + /* parent is the first entry in the column after the diagonal */ + ASSERT (Lnz [i] > 0) ; + parent = (Lnz [i] > 1) ? (Li [Lp [i] + 1]) : EMPTY ; + PRINT2 ((" parent: "ID"\n", parent)) ; + i = parent ; /* go up the tree */ + } + ASSERT (len <= top) ; + + /* move the path down to the bottom of the stack */ + /* this shifts Stack [0..len-1] down to [ ... oldtop-1] */ + while (len > 0) + { + Stack [--top] = Stack [--len] ; + } + } + else if (i > k) + { + /* prune the diagonal and upper triangular entries from Ci */ + Ci [lnz++] = i ; + Flag [i] = mark ; + } + } + +#ifndef NDEBUG + PRINT1 (("length of S after prune: "ID"\n", lnz)) ; + for (p = 0 ; p < lnz ; p++) + { + PRINT1 (("After prune Ci ["ID"] = "ID"\n", p, Ci [p])) ; + ASSERT (Ci [p] > k) ; + } +#endif + + /* ---------------------------------------------------------------------- */ + /* ensure each column of L has enough space to grow */ + /* ---------------------------------------------------------------------- */ + + for (kk = top ; kk < n ; kk++) + { + /* could skip this if we knew column j already included row k */ + j = Stack [kk] ; + if (Lp [j] + Lnz [j] >= Lp [Lnext [j]]) + { + PRINT1 (("Col "ID" realloc, old Lnz "ID"\n", j, Lnz [j])) ; + if (!CHOLMOD(reallocate_column) (j, Lnz [j] + 1, L, Common)) + { + /* out of memory, L is now simplicial symbolic */ + /* CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + for (i = 0 ; i < n ; i++) + { + W [i] = 0 ; + } + return (FALSE) ; + } + /* L->i and L->x may have moved */ + Li = L->i ; + Lx = L->x ; + } + ASSERT (Lp [j] + Lnz [j] < Lp [Lnext [j]] + || (Lp [Lnext [j]] - Lp [j] == n-j)) ; + } + + /* ---------------------------------------------------------------------- */ + /* compute kth row of L and store in column form */ + /* ---------------------------------------------------------------------- */ + + /* solve L (1:k-1, 1:k-1) * y (1:k-1) = b (1:k-1) */ + /* where b (1:k) is in W and Ci */ + + /* L (k, 1:k-1) = y (1:k-1) ./ D (1:k-1) */ + /* D (k) = B (k,k) - L (k, 1:k-1) * y (1:k-1) */ + + PRINT2 (("\nForward solve: "ID" to "ID"\n", top, n)) ; + ASSERT (Lnz [k] >= 1 && Li [Lp [k]] == k) ; + DEBUG (for (i = top ; i < n ; i++) PRINT2 ((" Path: "ID"\n", Stack [i]))) ; + + dk = W [k] ; + W [k] = 0.0 ; + + /* if do_solve: compute x (k) = b (k) - L (k, 1:k-1) * x (1:k-1) */ + xk = bk [0] ; + PRINT2 (("B [k] = %g\n", xk)) ; + + for (kk = top ; kk < n ; kk++) + { + j = Stack [kk] ; + i = j ; + PRINT2 (("Forward solve col j = "ID":\n", j)) ; + ASSERT (j >= 0 && j < k) ; + + /* forward solve using L (j+1:k-1,j) */ + yj = W [j] ; + W [j] = 0.0 ; + p = Lp [j] ; + pend = p + Lnz [j] ; + ASSERT (Lnz [j] > 0) ; + dj = Lx [p++] ; + for ( ; p < pend ; p++) + { + i = Li [p] ; + PRINT2 ((" row "ID"\n", i)) ; + ASSERT (i > j) ; + ASSERT (i < n) ; + /* stop at row k */ + if (i >= k) + { + break ; + } + W [i] -= Lx [p] * yj ; + } + + /* each iteration of the above for loop did 2 flops, and 3 flops + * are done below. so: fl += 2 * (Lp [j] - p - 1) + 3 becomes: */ + fl += 2 * (Lp [j] - p) + 1 ; + + /* scale L (k,1:k-1) and compute dot product for D (k,k) */ + l_kj = yj / dj ; + dk -= l_kj * yj ; + + /* compute dot product for X(k) */ + if (do_solve) + { + xk -= l_kj * Xx [j] ; + } + + /* store l_kj in the jth column of L */ + /* and shift the rest of the column down */ + + li = k ; + lx = l_kj ; + + if (i == k) + { + /* no need to modify the nonzero pattern of L, since it already + * contains row index k. */ + ASSERT (Li [p] == k) ; + Lx [p] = l_kj ; + + for (p++ ; p < pend ; p++) + { + i = Li [p] ; + l_ij = Lx [p] ; + ASSERT (i > k && i < n) ; + PRINT2 ((" apply to row "ID" of column k of L\n", i)) ; + + /* add to the pattern of the kth column of L */ + if (Flag [i] < mark) + { + PRINT2 ((" add Ci["ID"] = "ID"\n", lnz, i)) ; + ASSERT (i > k) ; + Ci [lnz++] = i ; + Flag [i] = mark ; + } + + /* apply the update to the kth column of L */ + /* yj is equal to l_kj * d_j */ + + W [i] -= l_ij * yj ; + } + + } + else + { + + PRINT2 (("Shift col j = "ID", apply saxpy to col k of L\n", j)) ; + for ( ; p < pend ; p++) + { + /* swap (Li [p],Lx [p]) with (li,lx) */ + i = Li [p] ; + l_ij = Lx [p] ; + Li [p] = li ; + Lx [p] = lx ; + li = i ; + lx = l_ij ; + ASSERT (i > k && i < n) ; + PRINT2 ((" apply to row "ID" of column k of L\n", i)) ; + + /* add to the pattern of the kth column of L */ + if (Flag [i] < mark) + { + PRINT2 ((" add Ci["ID"] = "ID"\n", lnz, i)) ; + ASSERT (i > k) ; + Ci [lnz++] = i ; + Flag [i] = mark ; + } + + /* apply the update to the kth column of L */ + /* yj is equal to l_kj * d_j */ + + W [i] -= l_ij * yj ; + } + + /* store the last value in the jth column of L */ + Li [p] = li ; + Lx [p] = lx ; + Lnz [j]++ ; + + } + } + + /* ---------------------------------------------------------------------- */ + /* merge C with the pattern of the existing column of L */ + /* ---------------------------------------------------------------------- */ + + /* This column should be zero, but it may contain explicit zero entries. + * These entries should be kept, not dropped. */ + p = Lp [k] ; + pend = p + Lnz [k] ; + for (p++ ; p < pend ; p++) + { + i = Li [p] ; + /* add to the pattern of the kth column of L */ + if (Flag [i] < mark) + { + PRINT2 ((" add Ci["ID"] = "ID" from existing col k\n", lnz, i)) ; + ASSERT (i > k) ; + Ci [lnz++] = i ; + Flag [i] = mark ; + } + } + + /* ---------------------------------------------------------------------- */ + + if (do_solve) + { + Xx [k] = xk ; + PRINT2 (("Xx [k] = %g\n", Xx [k])) ; + } + + /* ---------------------------------------------------------------------- */ + /* ensure abs (dk) >= dbound, if dbound is given */ + /* ---------------------------------------------------------------------- */ + + dk = (IS_GT_ZERO (Common->dbound)) ? (CHOLMOD(dbound) (dk, Common)) : dk ; + + PRINT2 (("D [k = "ID"] = %g\n", k, dk)) ; + + /* ---------------------------------------------------------------------- */ + /* store the kth column of L */ + /* ---------------------------------------------------------------------- */ + + /* ensure the new column of L has enough space */ + if (Lp [k] + lnz + 1 > Lp [Lnext [k]]) + { + PRINT1 (("New Col "ID" realloc, old Lnz "ID"\n", k, Lnz [k])) ; + if (!CHOLMOD(reallocate_column) (k, lnz + 1, L, Common)) + { + /* out of memory, L is now simplicial symbolic */ + CHOLMOD(clear_flag) (Common) ; + for (i = 0 ; i < n ; i++) + { + W [i] = 0 ; + } + return (FALSE) ; + } + /* L->i and L->x may have moved */ + Li = L->i ; + Lx = L->x ; + } + ASSERT (Lp [k] + lnz + 1 <= Lp [Lnext [k]]) ; + +#ifndef NDEBUG + PRINT2 (("\nPrior to sort: lnz "ID" (excluding diagonal)\n", lnz)) ; + for (kk = 0 ; kk < lnz ; kk++) + { + i = Ci [kk] ; + PRINT2 (("L ["ID"] kept: "ID" %e\n", kk, i, W [i] / dk)) ; + } +#endif + + /* sort Ci */ + qsort (Ci, lnz, sizeof (Int), (int (*) (const void *, const void *)) icomp); + + /* store the kth column of L */ + DEBUG (lastrow = k) ; + p = Lp [k] ; + Lx [p++] = dk ; + Lnz [k] = lnz + 1 ; + fl += lnz ; + for (kk = 0 ; kk < lnz ; kk++, p++) + { + i = Ci [kk] ; + PRINT2 (("L ["ID"] after sort: "ID", %e\n", kk, i, W [i] / dk)) ; + ASSERT (i > lastrow) ; + Li [p] = i ; + Lx [p] = W [i] / dk ; + W [i] = 0.0 ; + DEBUG (lastrow = i) ; + } + + /* compute DeltaB for updown (in DeltaB) */ + if (do_solve) + { + p = Lp [k] ; + pend = p + Lnz [k] ; + for (p++ ; p < pend ; p++) + { + ASSERT (Li [p] > k) ; + Nx [Li [p]] -= Lx [p] * xk ; + } + } + + /* clear the flag for the update */ + mark = CHOLMOD(clear_flag) (Common) ; + + /* workspaces are now cleared */ + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 2*n, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* update/downdate */ + /* ---------------------------------------------------------------------- */ + + /* update or downdate L (k+1:n, k+1:n) with the vector + * C = L (:,k) * sqrt (abs (D [k])). + * Do a numeric update if D[k] < 0, numeric downdate otherwise. + */ + + ok = TRUE ; + Common->modfl = 0 ; + + PRINT1 (("rowadd update lnz = "ID"\n", lnz)) ; + if (lnz > 0) + { + do_update = IS_LT_ZERO (dk) ; + if (do_update) + { + dk = -dk ; + } + sqrt_dk = sqrt (dk) ; + p = Lp [k] + 1 ; + for (kk = 0 ; kk < lnz ; kk++, p++) + { + Cx [kk] = Lx [p] * sqrt_dk ; + } + fl += lnz + 1 ; + + /* create a n-by-1 sparse matrix to hold the single column */ + C = &Cmatrix ; + C->nrow = n ; + C->ncol = 1 ; + C->nzmax = lnz ; + C->sorted = TRUE ; + C->packed = TRUE ; + C->p = Cp ; + C->i = Ci ; + C->x = Cx ; + C->nz = NULL ; + C->itype = L->itype ; + C->xtype = L->xtype ; + C->dtype = L->dtype ; + C->z = NULL ; + C->stype = 0 ; + + Cp [0] = 0 ; + Cp [1] = lnz ; + + /* numeric downdate if dk > 0, and optional Lx=b change */ + /* workspace: Flag (nrow), Head (nrow+1), W (nrow), Iwork (2*nrow) */ + ok = CHOLMOD(updown_mark) (do_update ? (1) : (0), C, colmark, + L, X, DeltaB, Common) ; + + /* clear workspace */ + for (kk = 0 ; kk < lnz ; kk++) + { + Cx [kk] = 0 ; + } + } + + Common->modfl += fl ; + + DEBUG (CHOLMOD(dump_factor) (L, "LDL factorization, L:", Common)) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 2*n, Common)) ; + return (ok) ; +} +#endif diff --git a/src/CHOLMOD/Modify/cholmod_rowdel.c b/src/CHOLMOD/Modify/cholmod_rowdel.c new file mode 100644 index 0000000..ccf1ce7 --- /dev/null +++ b/src/CHOLMOD/Modify/cholmod_rowdel.c @@ -0,0 +1,461 @@ +/* ========================================================================== */ +/* === Modify/cholmod_rowdel ================================================ */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Modify Module. + * Copyright (C) 2005-2006, Timothy A. Davis and William W. Hager. + * The CHOLMOD/Modify Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Deletes a row and column from an LDL' factorization. The row and column k + * is set to the kth row and column of the identity matrix. Optionally + * downdates the solution to Lx=b. + * + * workspace: Flag (nrow), Head (nrow+1), W (nrow*2), Iwork (2*nrow) + * + * Only real matrices are supported (exception: since only the pattern of R + * is used, it can have any valid xtype). + */ + +#ifndef NMODIFY + +#include "cholmod_internal.h" +#include "cholmod_modify.h" + + +/* ========================================================================== */ +/* === cholmod_rowdel ======================================================= */ +/* ========================================================================== */ + +/* Sets the kth row and column of L to be the kth row and column of the identity + * matrix, and updates L(k+1:n,k+1:n) accordingly. To reduce the running time, + * the caller can optionally provide the nonzero pattern (or an upper bound) of + * kth row of L, as the sparse n-by-1 vector R. Provide R as NULL if you want + * CHOLMOD to determine this itself, which is easier for the caller, but takes + * a little more time. + */ + +int CHOLMOD(rowdel) +( + /* ---- input ---- */ + size_t k, /* row/column index to delete */ + cholmod_sparse *R, /* NULL, or the nonzero pattern of kth row of L */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) +{ + double yk [2] ; + yk [0] = 0. ; + yk [1] = 0. ; + return (CHOLMOD(rowdel_mark) (k, R, yk, NULL, L, NULL, NULL, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_rowdel_solve ================================================= */ +/* ========================================================================== */ + +/* Does the same as cholmod_rowdel, but also downdates the solution to Lx=b. + * When row/column k of A is "deleted" from the system A*y=b, this can induce + * a change to x, in addition to changes arising when L and b are modified. + * If this is the case, the kth entry of y is required as input (yk) */ + +int CHOLMOD(rowdel_solve) +( + /* ---- input ---- */ + size_t k, /* row/column index to delete */ + cholmod_sparse *R, /* NULL, or the nonzero pattern of kth row of L */ + double yk [2], /* kth entry in the solution to A*y=b */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) +{ + return (CHOLMOD(rowdel_mark) (k, R, yk, NULL, L, X, DeltaB, Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_rowdel_mark ================================================== */ +/* ========================================================================== */ + +/* Does the same as cholmod_rowdel_solve, except only part of L is used in + * the update/downdate of the solution to Lx=b. This routine is an "expert" + * routine. It is meant for use in LPDASA only. + * + * if R == NULL then columns 0:k-1 of L are searched for row k. Otherwise, it + * searches columns in the set defined by the pattern of the first column of R. + * This is meant to be the pattern of row k of L (a superset of that pattern is + * OK too). R must be a permutation of a subset of 0:k-1. + */ + +int CHOLMOD(rowdel_mark) +( + /* ---- input ---- */ + size_t kdel, /* row/column index to delete */ + cholmod_sparse *R, /* NULL, or the nonzero pattern of kth row of L */ + double yk [2], /* kth entry in the solution to A*y=b */ + Int *colmark, /* Int array of size 1. See cholmod_updown.c */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) +{ + double dk, sqrt_dk, xk, dj, fl ; + double *Lx, *Cx, *W, *Xx, *Nx ; + Int *Li, *Lp, *Lnz, *Ci, *Rj, *Rp, *Iwork ; + cholmod_sparse *C, Cmatrix ; + Int j, p, pend, kk, lnz, n, Cp [2], do_solve, do_update, left, k, + right, middle, i, klast, given_row, rnz ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_PATTERN, CHOLMOD_REAL, FALSE) ; + n = L->n ; + k = kdel ; + if (kdel >= L->n || k < 0) + { + ERROR (CHOLMOD_INVALID, "k invalid") ; + return (FALSE) ; + } + if (R == NULL) + { + Rj = NULL ; + rnz = EMPTY ; + } + else + { + RETURN_IF_XTYPE_INVALID (R, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + if (R->ncol != 1 || R->nrow != L->n) + { + ERROR (CHOLMOD_INVALID, "R invalid") ; + return (FALSE) ; + } + Rj = R->i ; + Rp = R->p ; + rnz = Rp [1] ; + } + do_solve = (X != NULL) && (DeltaB != NULL) ; + if (do_solve) + { + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_REAL, FALSE) ; + RETURN_IF_XTYPE_INVALID (DeltaB, CHOLMOD_REAL, CHOLMOD_REAL, FALSE) ; + Xx = X->x ; + Nx = DeltaB->x ; + if (X->nrow != L->n || X->ncol != 1 || DeltaB->nrow != L->n || + DeltaB->ncol != 1 || Xx == NULL || Nx == NULL) + { + ERROR (CHOLMOD_INVALID, "X and/or DeltaB invalid") ; + return (FALSE) ; + } + } + else + { + Xx = NULL ; + Nx = NULL ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* s = 2*n */ + s = CHOLMOD(mult_size_t) (n, 2, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (n, s, s, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 2*n, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* convert to simplicial numeric LDL' factor, if not already */ + /* ---------------------------------------------------------------------- */ + + if (L->xtype == CHOLMOD_PATTERN || L->is_super || L->is_ll) + { + /* can only update/downdate a simplicial LDL' factorization */ + CHOLMOD(change_factor) (CHOLMOD_REAL, FALSE, FALSE, FALSE, FALSE, L, + Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory, L is returned unchanged */ + return (FALSE) ; + } + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + /* inputs, not modified on output: */ + Lp = L->p ; /* size n+1 */ + + /* outputs, contents defined on input for incremental case only: */ + Lnz = L->nz ; /* size n */ + Li = L->i ; /* size L->nzmax. Can change in size. */ + Lx = L->x ; /* size L->nzmax. Can change in size. */ + + ASSERT (L->nz != NULL) ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + W = Common->Xwork ; /* size n, used only in cholmod_updown */ + Cx = W + n ; /* use 2nd column of Xwork for C (size n) */ + Iwork = Common->Iwork ; + Ci = Iwork + n ; /* size n (i/i/l) */ + /* NOTE: cholmod_updown uses Iwork [0..n-1] (i/i/l) as Stack */ + + /* ---------------------------------------------------------------------- */ + /* prune row k from all columns of L */ + /* ---------------------------------------------------------------------- */ + + given_row = (rnz >= 0) ; + klast = given_row ? rnz : k ; + PRINT2 (("given_row "ID"\n", given_row)) ; + + for (kk = 0 ; kk < klast ; kk++) + { + /* either search j = 0:k-1 or j = Rj [0:rnz-1] */ + j = given_row ? (Rj [kk]) : (kk) ; + + if (j < 0 || j >= k) + { + ERROR (CHOLMOD_INVALID, "R invalid") ; + return (FALSE) ; + } + + PRINT2 (("Prune col j = "ID":\n", j)) ; + + lnz = Lnz [j] ; + dj = Lx [Lp [j]] ; + ASSERT (Lnz [j] > 0 && Li [Lp [j]] == j) ; + + if (lnz > 1) + { + left = Lp [j] ; + pend = left + lnz ; + right = pend - 1 ; + + i = Li [right] ; + + if (i < k) + { + /* row k is not in column j */ + continue ; + } + else if (i == k) + { + /* k is the last row index in this column (quick delete) */ + if (do_solve) + { + Xx [j] -= yk [0] * dj * Lx [right] ; + } + Lx [right] = 0 ; + } + else + { + /* binary search for row k in column j */ + PRINT2 (("\nBinary search: lnz "ID" k = "ID"\n", lnz, k)) ; + while (left < right) + { + middle = (left + right) / 2 ; + PRINT2 (("left "ID" right "ID" middle "ID": ["ID" "ID"" + ""ID"]\n", left, right, middle, + Li [left], Li [middle], Li [right])) ; + if (k > Li [middle]) + { + left = middle + 1 ; + } + else + { + right = middle ; + } + } + ASSERT (left >= Lp [j] && left < pend) ; + +#ifndef NDEBUG + /* brute force, linear-time search */ + { + Int p3 = Lp [j] ; + i = EMPTY ; + PRINT2 (("Brute force:\n")) ; + for ( ; p3 < pend ; p3++) + { + i = Li [p3] ; + PRINT2 (("p "ID" ["ID"]\n", p3, i)) ; + if (i >= k) + { + break ; + } + } + if (i == k) + { + ASSERT (k == Li [p3]) ; + ASSERT (p3 == left) ; + } + } +#endif + + if (k == Li [left]) + { + if (do_solve) + { + Xx [j] -= yk [0] * dj * Lx [left] ; + } + /* found row k in column j. Prune it from the column.*/ + Lx [left] = 0 ; + } + } + } + } + +#ifndef NDEBUG + /* ensure that row k has been deleted from the matrix L */ + for (j = 0 ; j < k ; j++) + { + Int lasti ; + lasti = EMPTY ; + p = Lp [j] ; + pend = p + Lnz [j] ; + /* look for row k in column j */ + PRINT1 (("Pruned column "ID"\n", j)) ; + for ( ; p < pend ; p++) + { + i = Li [p] ; + PRINT2 ((" "ID"", i)) ; + PRINT2 ((" %g\n", Lx [p])) ; + ASSERT (IMPLIES (i == k, Lx [p] == 0)) ; + ASSERT (i > lasti) ; + lasti = i ; + } + PRINT1 (("\n")) ; + } +#endif + + /* ---------------------------------------------------------------------- */ + /* set diagonal and clear column k of L */ + /* ---------------------------------------------------------------------- */ + + lnz = Lnz [k] - 1 ; + ASSERT (Lnz [k] > 0) ; + + /* ---------------------------------------------------------------------- */ + /* update/downdate */ + /* ---------------------------------------------------------------------- */ + + /* update or downdate L (k+1:n, k+1:n) with the vector + * C = L (:,k) * sqrt (abs (D [k])) + * Do a numeric update if D[k] > 0, numeric downdate otherwise. + */ + + PRINT1 (("rowdel downdate lnz = "ID"\n", lnz)) ; + + /* store the new unit diagonal */ + p = Lp [k] ; + pend = p + lnz + 1 ; + dk = Lx [p] ; + Lx [p++] = 1 ; + PRINT2 (("D [k = "ID"] = %g\n", k, dk)) ; + ok = TRUE ; + fl = 0 ; + + if (lnz > 0) + { + /* compute DeltaB for updown (in DeltaB) */ + if (do_solve) + { + xk = Xx [k] - yk [0] * dk ; + for ( ; p < pend ; p++) + { + Nx [Li [p]] += Lx [p] * xk ; + } + } + + do_update = IS_GT_ZERO (dk) ; + if (!do_update) + { + dk = -dk ; + } + sqrt_dk = sqrt (dk) ; + p = Lp [k] + 1 ; + for (kk = 0 ; kk < lnz ; kk++, p++) + { + Ci [kk] = Li [p] ; + Cx [kk] = Lx [p] * sqrt_dk ; + Lx [p] = 0 ; /* clear column k */ + } + fl = lnz + 1 ; + + /* create a n-by-1 sparse matrix to hold the single column */ + C = &Cmatrix ; + C->nrow = n ; + C->ncol = 1 ; + C->nzmax = lnz ; + C->sorted = TRUE ; + C->packed = TRUE ; + C->p = Cp ; + C->i = Ci ; + C->x = Cx ; + C->nz = NULL ; + C->itype = L->itype ; + C->xtype = L->xtype ; + C->dtype = L->dtype ; + C->z = NULL ; + C->stype = 0 ; + + Cp [0] = 0 ; + Cp [1] = lnz ; + + /* numeric update if dk > 0, and with Lx=b change */ + /* workspace: Flag (nrow), Head (nrow+1), W (nrow), Iwork (2*nrow) */ + ok = CHOLMOD(updown_mark) (do_update ? (1) : (0), C, colmark, + L, X, DeltaB, Common) ; + + /* clear workspace */ + for (kk = 0 ; kk < lnz ; kk++) + { + Cx [kk] = 0 ; + } + } + + Common->modfl += fl ; + + if (do_solve) + { + /* kth equation becomes identity, so X(k) is now Y(k) */ + Xx [k] = yk [0] ; + } + + DEBUG (CHOLMOD(dump_factor) (L, "LDL factorization, L:", Common)) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 2*n, Common)) ; + return (ok) ; +} +#endif diff --git a/src/CHOLMOD/Modify/cholmod_updown.c b/src/CHOLMOD/Modify/cholmod_updown.c new file mode 100644 index 0000000..e0ed9cb --- /dev/null +++ b/src/CHOLMOD/Modify/cholmod_updown.c @@ -0,0 +1,1570 @@ +/* ========================================================================== */ +/* === Modify/cholmod_updown ================================================ */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Modify Module. + * Copyright (C) 2005-2006, Timothy A. Davis and William W. Hager. + * The CHOLMOD/Modify Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Updates/downdates the LDL' factorization (symbolic, then numeric), by + * computing a new factorization of + * + * Lnew * Dnew * Lnew' = Lold * Dold * Lold' +/- C*C' + * + * C must be sorted. It can be either packed or unpacked. As in all CHOLMOD + * routines, the columns of L are sorted on input, and also on output. + * + * If the factor is not an unpacked LDL' or dynamic LDL', it is converted + * to an LDL' dynamic factor. An unpacked LDL' factor may be updated, but if + * any one column runs out of space, the factor is converted to an LDL' + * dynamic one. If the initial conversion fails, the factor is returned + * unchanged. + * + * If memory runs out during the update, the factor is returned as a simplicial + * symbolic factor. That is, everything is freed except for the fill-reducing + * ordering and its corresponding column counts (typically computed by + * cholmod_analyze). + * + * Note that the fill-reducing permutation L->Perm is NOT used. The row + * indices of C refer to the rows of L, not A. If your original system is + * LDL' = PAP' (where P = L->Perm), and you want to compute the LDL' + * factorization of A+CC', then you must permute C first. That is: + * + * PAP' = LDL' + * P(A+CC')P' = PAP'+PCC'P' = LDL' + (PC)(PC)' = LDL' + Cnew*Cnew' + * where Cnew = P*C. + * + * You can use the cholmod_submatrix routine in the MatrixOps module + * to permute C, with: + * + * Cnew = cholmod_submatrix (C, L->Perm, L->n, NULL, -1, TRUE, TRUE, Common) ; + * + * Note that the sorted input parameter to cholmod_submatrix must be TRUE, + * because cholmod_updown requires C with sorted columns. + * + * The system Lx=b can also be updated/downdated. The old system was Lold*x=b. + * The new system is Lnew*xnew = b + deltab. The old solution x is overwritten + * with xnew. Note that as in the update/downdate of L itself, the fill- + * reducing permutation L->Perm is not used. x and b are in the permuted + * ordering, not your original ordering. x and b are n-by-1; this routine + * does not handle multiple right-hand-sides. + * + * workspace: Flag (nrow), Head (nrow+1), W (maxrank*nrow), Iwork (nrow), + * where maxrank is 2, 4, or 8. + * + * Only real matrices are supported. A symbolic L is converted into a + * numeric identity matrix. + */ + +#ifndef NMODIFY + +#include "cholmod_internal.h" +#include "cholmod_modify.h" + + +/* ========================================================================== */ +/* === cholmod_updown ======================================================= */ +/* ========================================================================== */ + +/* Compute the new LDL' factorization of LDL'+CC' (an update) or LDL'-CC' + * (a downdate). The factor object L need not be an LDL' factorization; it + * is converted to one if it isn't. */ + +int CHOLMOD(updown) +( + /* ---- input ---- */ + int update, /* TRUE for update, FALSE for downdate */ + cholmod_sparse *C, /* the incoming sparse update */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + /* --------------- */ + cholmod_common *Common +) +{ + return (CHOLMOD(updown_mask) (update, C, NULL, NULL, L, NULL, NULL, + Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_updown_solve ================================================= */ +/* ========================================================================== */ + +/* Does the same as cholmod_updown, except that it also updates/downdates the + * solution to Lx=b+DeltaB. x and b must be n-by-1 dense matrices. b is not + * need as input to this routine, but a sparse change to b is (DeltaB). Only + * entries in DeltaB corresponding to columns modified in L are accessed; the + * rest are ignored. + */ + +int CHOLMOD(updown_solve) +( + /* ---- input ---- */ + int update, /* TRUE for update, FALSE for downdate */ + cholmod_sparse *C, /* the incoming sparse update */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) +{ + return (CHOLMOD(updown_mask) (update, C, NULL, NULL, L, X, DeltaB, + Common)) ; +} + + +/* ========================================================================== */ +/* === Power2 =============================================================== */ +/* ========================================================================== */ + +/* Power2 [i] is smallest power of 2 that is >= i (for i in range 0 to 8) */ + +static Int Power2 [ ] = +{ +/* 0 1 2 3 4 5 6 7 8 */ + 0, 1, 2, 4, 4, 8, 8, 8, 8 +} ; + +/* ========================================================================== */ +/* === debug routines ======================================================= */ +/* ========================================================================== */ + +#ifndef NDEBUG + +static void dump_set (Int s, Int **Set_ps1, Int **Set_ps2, Int j, Int n, + cholmod_common *Common) +{ + Int *p, len, i, ilast ; + + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return ; + } + + len = Set_ps2 [s] - Set_ps1 [s] ; + PRINT2 (("Set s: "ID" len: "ID":", s, len)) ; + ASSERT (len > 0) ; + ilast = j ; + for (p = Set_ps1 [s] ; p < Set_ps2 [s] ; p++) + { + i = *p ; + PRINT3 ((" "ID"", i)) ; + ASSERT (i > ilast && i < n) ; + ilast = i ; + } + PRINT3 (("\n")) ; +} + +static void dump_col +( + char *w, Int j, Int p1, Int p2, Int *Li, double *Lx, Int n, + cholmod_common *Common +) +{ + Int p, row, lastrow ; + + if (CHOLMOD(dump) < -1) + { + /* no checks if debug level is -2 or less */ + return ; + } + + PRINT3 (("\n\nDUMP COL==== j = "ID" %s: p1="ID" p2="ID" \n", j, w, p1,p2)); + lastrow = -1 ; + for (p = p1 ; p < p2 ; p++) + { + PRINT3 ((" "ID": ", p)) ; + row = Li [p] ; + PRINT3 ((""ID" ", Li [p])) ; + PRINT3 (("%g ", Lx [p])) ; + PRINT3 (("\n")) ; + ASSERT (row > lastrow && row < n) ; + lastrow = row ; + } + ASSERT (p1 < p2) ; + ASSERT (Li [p1] == j) ; + PRINT3 (("\n")) ; +} +#endif + + +/* ========================================================================== */ +/* === a path =============================================================== */ +/* ========================================================================== */ + +/* A path is a set of nodes of the etree which are all affected by the same + * columns of C. */ + +typedef struct Path_struct +{ + Int start ; /* column at which to start, or EMPTY if initial */ + Int end ; /* column at which to end, or EMPTY if initial */ + Int ccol ; /* column of C to which path refers */ + Int parent ; /* parent path */ + Int c ; /* child of j along this path */ + Int next ; /* next path in link list */ + Int rank ; /* number of rank-1 paths merged onto this path */ + Int order ; /* dfs order of this path */ + Int wfirst ; /* first column of W to affect this path */ + Int pending ; /* column at which the path is pending */ + Int botrow ; /* for partial update/downdate of solution to Lx=b */ + +} Path_type ; + + +/* ========================================================================== */ +/* === dfs ================================================================== */ +/* ========================================================================== */ + +/* Compute the DFS order of the set of paths. This can be recursive because + * there are at most 23 paths to sort: one for each column of C (8 at most), + * and one for each node in a balanced binary tree with 8 leaves (15). + * Stack overflow is thus not a problem. */ + +static void dfs +( + Path_type *Path, /* the set of Paths */ + Int k, /* the rank of the update/downdate */ + Int path, /* which path to work on */ + Int *path_order, /* the current path order */ + Int *w_order, /* the current order of the columns of W */ + Int depth, + Int npaths /* total number of paths */ +) +{ + Int c ; /* child path */ + + ASSERT (path >= 0 && path < npaths) ; + if (path < k) + { + /* this is a leaf node, corresponding to column W (:,path) */ + /* and column C (:, Path [path].ccol) */ + ASSERT (Path [path].ccol >= 0) ; + Path [path].wfirst = *w_order ; + Path [path].order = *w_order ; + (*w_order)++ ; + } + else + { + /* this is a non-leaf path, within the tree */ + ASSERT (Path [path].c != EMPTY) ; + ASSERT (Path [path].ccol == EMPTY) ; + /* order each child path */ + for (c = Path [path].c ; c != EMPTY ; c = Path [c].next) + { + dfs (Path, k, c, path_order, w_order, depth+1, npaths) ; + if (Path [path].wfirst == EMPTY) + { + Path [path].wfirst = Path [c].wfirst ; + } + } + /* order this path next */ + Path [path].order = (*path_order)++ ; + } +} + + +/* ========================================================================== */ +/* === numeric update/downdate routines ===================================== */ +/* ========================================================================== */ + +#define WDIM 1 +#include "t_cholmod_updown.c" +#define WDIM 2 +#include "t_cholmod_updown.c" +#define WDIM 4 +#include "t_cholmod_updown.c" +#define WDIM 8 +#include "t_cholmod_updown.c" + + +/* ========================================================================== */ +/* === cholmod_updown_mark ================================================== */ +/* ========================================================================== */ + +/* Update/downdate LDL' +/- C*C', and update/downdate selected portions of the + * solution to Lx=b. + * + * The original system is L*x = b. The new system is Lnew*xnew = b + deltab. + * deltab(i) can be nonzero only if column i of L is modified by the update/ + * downdate. If column i is not modified, the deltab(i) is not accessed. + * + * The solution to Lx=b is not modified if either X or DeltaB are NULL. + * + * Rowmark and colmark: + * -------------------- + * + * rowmark and colmark affect which portions of L take part in the update/ + * downdate of the solution to Lx=b. They do not affect how L itself is + * updated/downdated. They are both ignored if X or DeltaB are NULL. + * + * If not NULL, rowmark is an integer array of size n where L is n-by-n. + * rowmark [j] defines the part of column j of L that takes part in the update/ + * downdate of the forward solve, Lx=b. Specifically, if i = rowmark [j], + * then L(j:i-1,j) is used, and L(i:end,j) is ignored. + * + * If not NULL, colmark is an integer array of size C->ncol. colmark [ccol] + * for a column C(:,ccol) redefines those parts of L that take part in the + * update/downdate of Lx=b. Each column of C affects a set of columns of L. + * If column ccol of C affects column j of L, then the new rowmark [j] of + * column j of L is defined as colmark [ccol]. In a multiple-rank update/ + * downdate, if two or more columns of C affect column j, its new rowmark [j] + * is the colmark of the least-numbered column of C. colmark is ignored if + * it is NULL, in which case rowmark is not modified. If colmark [ccol] is + * EMPTY (-1), then rowmark is not modified for that particular column of C. + * colmark is ignored if it is NULL, or rowmark, X, or DeltaB are NULL. + * + * The algorithm for modifying the solution to Lx=b when rowmark and colmark + * are NULL is as follows: + * + * for each column j of L that is modified: + * deltab (j:end) += L (j:end,j) * x(j) + * modify L + * for each column j of L that is modified: + * x (j) = deltab (j) + * deltab (j) = 0 + * deltab (j+1:end) -= L (j+1:end,j) * x(j) + * + * If rowmark is non-NULL but colmark is NULL: + * + * for each column j of L that is modified: + * deltab (j:rowmark(j)-1) += L (j:rowmark(j)-1,j) * x(j) + * modify L + * for each column j of L that is modified: + * x (j) = deltab (j) + * deltab (j) = 0 + * deltab (j+1:rowmark(j)-1) -= L (j+1:rowmark(j)-1,j) * x(j) + * + * If both rowmark and colmark are non-NULL: + * + * for each column j of L that is modified: + * deltab (j:rowmark(j)-1) += L (j:rowmark(j)-1,j) * x(j) + * modify L + * for each column j of L that is modified: + * modify rowmark (j) according to colmark + * for each column j of L that is modified: + * x (j) = deltab (j) + * deltab (j) = 0 + * deltab (j+1:rowmark(j)-1) -= L (j+1:rowmark(j)-1,j) * x(j) + * + * Note that if the rank of C exceeds k = Common->maxrank (which is 2, 4, or 8), + * then the update/downdate is done as a series of rank-k updates. In this + * case, the above algorithm is repeated for each block of k columns of C. + * + * Unless it leads to no changes in rowmark, colmark should be used only if + * C->ncol <= Common->maxrank, because the update/downdate is done with maxrank + * columns at a time. Otherwise, the results are undefined. + * + * This routine is an "expert" routine. It is meant for use in LPDASA only. + */ + +int CHOLMOD(updown_mark) +( + /* ---- input ---- */ + int update, /* TRUE for update, FALSE for downdate */ + cholmod_sparse *C, /* the incoming sparse update */ + Int *colmark, /* Int array of size n. */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) +{ + return (CHOLMOD(updown_mask) (update, C, colmark, NULL, L, X, DeltaB, + Common)) ; +} + + +/* ========================================================================== */ +/* === cholmod_updown_mask ================================================== */ +/* ========================================================================== */ + +int CHOLMOD(updown_mask) +( + /* ---- input ---- */ + int update, /* TRUE for update, FALSE for downdate */ + cholmod_sparse *C, /* the incoming sparse update */ + Int *colmark, /* Int array of size n. See cholmod_updown.c */ + Int *mask, /* size n */ + /* ---- in/out --- */ + cholmod_factor *L, /* factor to modify */ + cholmod_dense *X, /* solution to Lx=b (size n-by-1) */ + cholmod_dense *DeltaB, /* change in b, zero on output */ + /* --------------- */ + cholmod_common *Common +) +{ + double xj, fl ; + double *Lx, *W, *Xx, *Nx ; + Int *Li, *Lp, *Lnz, *Cp, *Ci, *Cnz, *Head, *Flag, *Stack, *Lnext, *Iwork, + *Set_ps1 [32], *Set_ps2 [32], *ps1, *ps2 ; + size_t maxrank ; + Path_type OrderedPath [32], Path [32] ; + Int n, wdim, k1, k2, npaths, i, j, row, packed, ccol, p, cncol, do_solve, + mark, jj, j2, kk, nextj, p1, p2, c, use_colmark, newlnz, + k, newpath, path_order, w_order, scattered, path, newparent, pp1, pp2, + smax, maxrow, row1, nsets, s, p3, newlnz1, Set [32], top, len, lnz, m, + botrow ; + size_t w ; + int ok = TRUE ; + DEBUG (Int oldparent) ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (C, FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_PATTERN, CHOLMOD_REAL, FALSE) ; + RETURN_IF_XTYPE_INVALID (C, CHOLMOD_REAL, CHOLMOD_REAL, FALSE) ; + n = L->n ; + cncol = C->ncol ; + if (!(C->sorted)) + { + ERROR (CHOLMOD_INVALID, "C must have sorted columns") ; + return (FALSE) ; + } + if (n != (Int) (C->nrow)) + { + ERROR (CHOLMOD_INVALID, "C and L dimensions do not match") ; + return (FALSE) ; + } + do_solve = (X != NULL) && (DeltaB != NULL) ; + if (do_solve) + { + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_REAL, FALSE) ; + RETURN_IF_XTYPE_INVALID (DeltaB, CHOLMOD_REAL, CHOLMOD_REAL, FALSE) ; + Xx = X->x ; + Nx = DeltaB->x ; + if (X->nrow != L->n || X->ncol != 1 || DeltaB->nrow != L->n || + DeltaB->ncol != 1 || Xx == NULL || Nx == NULL) + { + ERROR (CHOLMOD_INVALID, "X and/or DeltaB invalid") ; + return (FALSE) ; + } + } + else + { + Xx = NULL ; + Nx = NULL ; + } + Common->status = CHOLMOD_OK ; + Common->modfl = 0 ; + + fl = 0 ; + use_colmark = (colmark != NULL) ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* Note: cholmod_rowadd and cholmod_rowdel use the second n doubles in + * Common->Xwork for Cx, and then perform a rank-1 update here, which uses + * the first n doubles in Common->Xwork. Both the rowadd and rowdel + * routines allocate enough workspace so that Common->Xwork isn't destroyed + * below. Also, both cholmod_rowadd and cholmod_rowdel use the second n + * ints in Common->Iwork for Ci. + */ + + /* make sure maxrank is in the proper range */ + maxrank = CHOLMOD(maxrank) (n, Common) ; + k = MIN (cncol, (Int) maxrank) ; /* maximum k is wdim */ + wdim = Power2 [k] ; /* number of columns needed in W */ + ASSERT (wdim <= (Int) maxrank) ; + PRINT1 (("updown wdim final "ID" k "ID"\n", wdim, k)) ; + + /* w = wdim * n */ + w = CHOLMOD(mult_size_t) (n, wdim, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (n, n, w, Common) ; + if (Common->status < CHOLMOD_OK || maxrank == 0) + { + /* out of memory, L is returned unchanged */ + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* convert to simplicial numeric LDL' factor, if not already */ + /* ---------------------------------------------------------------------- */ + + if (L->xtype == CHOLMOD_PATTERN || L->is_super || L->is_ll) + { + /* can only update/downdate a simplicial LDL' factorization */ + CHOLMOD(change_factor) (CHOLMOD_REAL, FALSE, FALSE, FALSE, FALSE, L, + Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory, L is returned unchanged */ + return (FALSE) ; + } + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + /* mark = CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + mark = Common->mark ; + + PRINT1 (("updown, rank %g update %d\n", (double) C->ncol, update)) ; + DEBUG (CHOLMOD(dump_factor) (L, "input L for updown", Common)) ; + ASSERT (CHOLMOD(dump_sparse) (C, "input C for updown", Common) >= 0) ; + + Ci = C->i ; + Cp = C->p ; + Cnz = C->nz ; + packed = C->packed ; + ASSERT (IMPLIES (!packed, Cnz != NULL)) ; + + /* ---------------------------------------------------------------------- */ + /* quick return */ + /* ---------------------------------------------------------------------- */ + + if (cncol <= 0 || n == 0) + { + /* nothing to do */ + return (TRUE) ; + } + + /* ---------------------------------------------------------------------- */ + /* get L */ + /* ---------------------------------------------------------------------- */ + + Li = L->i ; + Lx = L->x ; + Lp = L->p ; + Lnz = L->nz ; + Lnext = L->next ; + ASSERT (Lnz != NULL) ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Flag = Common->Flag ; /* size n, Flag [i] <= mark must hold */ + Head = Common->Head ; /* size n, Head [i] == EMPTY must hold */ + W = Common->Xwork ; /* size n-by-wdim, zero on input and output*/ + + /* note that Iwork [n .. 2*n-1] (i/i/l) may be in use in rowadd/rowdel: */ + Iwork = Common->Iwork ; + Stack = Iwork ; /* size n, uninitialized (i/i/l) */ + + /* ---------------------------------------------------------------------- */ + /* entire rank-cncol update, done as a sequence of rank-k updates */ + /* ---------------------------------------------------------------------- */ + + ps1 = NULL ; + ps2 = NULL ; + + for (k1 = 0 ; k1 < cncol ; k1 += k) + { + + /* ------------------------------------------------------------------ */ + /* get the next k columns of C for the update/downdate */ + /* ------------------------------------------------------------------ */ + + /* the last update/downdate might be less than rank-k */ + if (k > cncol - k1) + { + k = cncol - k1 ; + wdim = Power2 [k] ; + } + k2 = k1 + k - 1 ; + + /* workspaces are in the following state, on input and output */ + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, wdim, Common)) ; + + /* ------------------------------------------------------------------ */ + /* create a zero-length path for each column of W */ + /* ------------------------------------------------------------------ */ + + nextj = n ; + path = 0 ; + for (ccol = k1 ; ccol <= k2 ; ccol++) + { + PRINT1 (("Column ["ID"]: "ID"\n", path, ccol)) ; + ASSERT (ccol >= 0 && ccol <= cncol) ; + pp1 = Cp [ccol] ; + pp2 = (packed) ? (Cp [ccol+1]) : (pp1 + Cnz [ccol]) ; + /* get the row index j of the first entry in C (:,ccol) */ + if (pp2 > pp1) + { + /* Column ccol of C has at least one entry. */ + j = Ci [pp1] ; + } + else + { + /* Column ccol of C is empty. Pretend it has one entry in + * the last column with numerical value of zero. */ + j = n-1 ; + } + ASSERT (j >= 0 && j < n) ; + + /* find first column to work on */ + nextj = MIN (nextj, j) ; + + Path [path].ccol = ccol ; /* which column of C this path is for */ + Path [path].start = EMPTY ; /* paths for C have zero length */ + Path [path].end = EMPTY ; + Path [path].parent = EMPTY ; /* no parent yet */ + Path [path].rank = 1 ; /* one column of W */ + Path [path].c = EMPTY ; /* no child of this path (case A) */ + Path [path].next = Head [j] ; /* this path is pending at col j */ + Path [path].pending = j ; /* this path is pending at col j */ + Head [j] = path ; /* this path is pending at col j */ + PRINT1(("Path "ID" starts: start "ID" end "ID" parent "ID" c "ID"" + "j "ID" ccol "ID"\n", path, Path [path].start, + Path [path].end, Path [path].parent, + Path [path].c, j, ccol)) ; + + /* initialize botrow for this path */ + Path [path].botrow = (use_colmark) ? colmark [ccol] : n ; + + path++ ; + } + + /* we start with paths 0 to k-1. Next one (now unused) is npaths */ + npaths = k ; + + j = nextj ; + ASSERT (j < n) ; + scattered = FALSE ; + + /* ------------------------------------------------------------------ */ + /* symbolic update of columns of L */ + /* ------------------------------------------------------------------ */ + + while (j < n) + { + ASSERT (j >= 0 && j < n && Lnz [j] > 0) ; + + /* the old column, Li [p1..p2-1]. D (j,j) is stored in Lx [p1] */ + p1 = Lp [j] ; + newlnz = Lnz [j] ; + p2 = p1 + newlnz ; + +#ifndef NDEBUG + PRINT1 (("\n=========Column j="ID" p1 "ID" p2 "ID" lnz "ID" \n", + j, p1, p2, newlnz)) ; + dump_col ("Old", j, p1, p2, Li, Lx, n, Common) ; + oldparent = (Lnz [j] > 1) ? (Li [p1 + 1]) : EMPTY ; + ASSERT (CHOLMOD(dump_work) (TRUE, FALSE, 0, Common)) ; + ASSERT (!scattered) ; + PRINT1 (("Col "ID": Checking paths, npaths: "ID"\n", j, npaths)) ; + for (kk = 0 ; kk < npaths ; kk++) + { + Int kk2, found, j3 = Path [kk].pending ; + PRINT2 (("Path "ID" pending at "ID".\n", kk, j3)) ; + if (j3 != EMPTY) + { + /* Path kk must be somewhere in link list for column j3 */ + ASSERT (Head [j3] != EMPTY) ; + PRINT3 ((" List at "ID": ", j3)) ; + found = FALSE ; + for (kk2 = Head [j3] ; kk2 != EMPTY ; kk2 = Path [kk2].next) + { + PRINT3 ((""ID" ", kk2)) ; + ASSERT (Path [kk2].pending == j3) ; + found = found || (kk2 == kk) ; + } + PRINT3 (("\n")) ; + ASSERT (found) ; + } + } + PRINT1 (("\nCol "ID": Paths at this column, head "ID"\n", + j, Head [j])); + ASSERT (Head [j] != EMPTY) ; + for (kk = Head [j] ; kk != EMPTY ; kk = Path [kk].next) + { + PRINT1 (("path "ID": (c="ID" j="ID") npaths "ID"\n", + kk, Path[kk].c, j, npaths)) ; + ASSERT (kk >= 0 && kk < npaths) ; + ASSERT (Path [kk].pending == j) ; + } +#endif + + /* -------------------------------------------------------------- */ + /* determine the path we're on */ + /* -------------------------------------------------------------- */ + + /* get the first old path at column j */ + path = Head [j] ; + + /* -------------------------------------------------------------- */ + /* update/downdate of forward solve, Lx=b */ + /* -------------------------------------------------------------- */ + + if (do_solve) + { + xj = Xx [j] ; + if (IS_NONZERO (xj)) + { + xj = Xx [j] ; + /* This is first time column j has been seen for entire */ + /* rank-k update/downdate. */ + + /* DeltaB += Lold (j:botrow-1,j) * X (j) */ + Nx [j] += xj ; /* diagonal of L */ + + /* find the botrow for this column */ + botrow = (use_colmark) ? Path [path].botrow : n ; + + for (p = p1 + 1 ; p < p2 ; p++) + { + i = Li [p] ; + if (i >= botrow) + { + break ; + } + Nx [i] += Lx [p] * xj ; + } + + /* clear X[j] to flag col j of Lold as having been seen. If + * X (j) was initially zero, then the above code is never + * executed for column j. This is safe, since if xj=0 the + * code above does not do anything anyway. */ + Xx [j] = 0.0 ; + } + } + + /* -------------------------------------------------------------- */ + /* start a new path at this column if two or more paths merge */ + /* -------------------------------------------------------------- */ + + newpath = + /* start a new path if paths have merged */ + (Path [path].next != EMPTY) + /* or if j is the first node on a path (case A). */ + || (Path [path].c == EMPTY) ; + + if (newpath) + { + /* get the botrow of the first path at column j */ + botrow = (use_colmark) ? Path [path].botrow : n ; + + path = npaths++ ; + ASSERT (npaths <= 3*k) ; + Path [path].ccol = EMPTY ; /* no single col of C for this path*/ + Path [path].start = j ; /* path starts at this column j */ + Path [path].end = EMPTY ; /* don't know yet where it ends */ + Path [path].parent = EMPTY ;/* don't know parent path yet */ + Path [path].rank = 0 ; /* rank is sum of child path ranks */ + PRINT1 (("Path "ID" starts: start "ID" end "ID" parent "ID"\n", + path, Path [path].start, Path [path].end, Path [path].parent)) ; + + /* set the botrow of the new path */ + Path [path].botrow = (use_colmark) ? botrow : n ; + } + + /* -------------------------------------------------------------- */ + /* for each path kk pending at column j */ + /* -------------------------------------------------------------- */ + + /* make a list of the sets that need to be merged into column j */ + nsets = 0 ; + + for (kk = Head [j] ; kk != EMPTY ; kk = Path [kk].next) + { + + /* ---------------------------------------------------------- */ + /* path kk is at (c,j) */ + /* ---------------------------------------------------------- */ + + c = Path [kk].c ; + ASSERT (c < j) ; + PRINT1 (("TUPLE on path "ID" (c="ID" j="ID")\n", kk, c, j)) ; + ASSERT (Path [kk].pending == j) ; + + if (newpath) + { + /* finalize path kk and find rank of this path */ + Path [kk].end = c ; /* end of old path is previous node c */ + Path [kk].parent = path ; /* parent is this path */ + Path [path].rank += Path [kk].rank ; /* sum up ranks */ + Path [kk].pending = EMPTY ; + PRINT1 (("Path "ID" done:start "ID" end "ID" parent "ID"\n", + kk, Path [kk].start, Path [kk].end, Path [kk].parent)) ; + } + + if (c == EMPTY) + { + + /* ------------------------------------------------------ */ + /* CASE A: first node in path */ + /* ------------------------------------------------------ */ + + /* update: add pattern of incoming column */ + + /* Column ccol of C is in Ci [pp1 ... pp2-1] */ + ccol = Path [kk].ccol ; + pp1 = Cp [ccol] ; + pp2 = (packed) ? (Cp [ccol+1]) : (pp1 + Cnz [ccol]) ; + PRINT1 (("Case A, ccol = "ID" len "ID"\n", ccol, pp2-pp1)) ; + ASSERT (IMPLIES (pp2 > pp1, Ci [pp1] == j)) ; + + if (!scattered) + { + /* scatter the original pattern of column j of L */ + for (p = p1 ; p < p2 ; p++) + { + Flag [Li [p]] = mark ; + } + scattered = TRUE ; + } + + /* scatter column ccol of C (skip first entry, j) */ + newlnz1 = newlnz ; + for (p = pp1 + 1 ; p < pp2 ; p++) + { + row = Ci [p] ; + if (Flag [row] < mark) + { + /* this is a new entry in Lj' */ + Flag [row] = mark ; + newlnz++ ; + } + } + if (newlnz1 != newlnz) + { + /* column ccol of C adds something to column j of L */ + Set [nsets++] = FLIP (ccol) ; + } + + } + else if (Head [c] == 1) + { + + /* ------------------------------------------------------ */ + /* CASE B: c is old, but changed, child of j */ + /* CASE C: new child of j */ + /* ------------------------------------------------------ */ + + /* Head [c] is 1 if col c of L has new entries, + * EMPTY otherwise */ + Flag [c] = 0 ; + Head [c] = EMPTY ; + + /* update: add Lc' */ + + /* column c of L is in Li [pp1 .. pp2-1] */ + pp1 = Lp [c] ; + pp2 = pp1 + Lnz [c] ; + PRINT1 (("Case B/C: c = "ID"\n", c)) ; + DEBUG (dump_col ("Child", c, pp1, pp2, Li, Lx, n, Common)) ; + ASSERT (j == Li [pp1 + 1]) ; /* j is new parent of c */ + + if (!scattered) + { + /* scatter the original pattern of column j of L */ + for (p = p1 ; p < p2 ; p++) + { + Flag [Li [p]] = mark ; + } + scattered = TRUE ; + } + + /* scatter column c of L (skip first two entries, c and j)*/ + newlnz1 = newlnz ; + for (p = pp1 + 2 ; p < pp2 ; p++) + { + row = Li [p] ; + if (Flag [row] < mark) + { + /* this is a new entry in Lj' */ + Flag [row] = mark ; + newlnz++ ; + } + } + PRINT2 (("\n")) ; + + if (newlnz1 != newlnz) + { + /* column c of L adds something to column j of L */ + Set [nsets++] = c ; + } + } + } + + /* -------------------------------------------------------------- */ + /* update the pattern of column j of L */ + /* -------------------------------------------------------------- */ + + /* Column j of L will be in Li/Lx [p1 .. p3-1] */ + p3 = p1 + newlnz ; + ASSERT (IMPLIES (nsets == 0, newlnz == Lnz [j])) ; + PRINT1 (("p1 "ID" p2 "ID" p3 "ID" nsets "ID"\n", p1, p2, p3,nsets)); + + /* -------------------------------------------------------------- */ + /* ensure we have enough space for the longer column */ + /* -------------------------------------------------------------- */ + + if (nsets > 0 && p3 > Lp [Lnext [j]]) + { + PRINT1 (("Col realloc: j "ID" newlnz "ID"\n", j, newlnz)) ; + if (!CHOLMOD(reallocate_column) (j, newlnz, L, Common)) + { + /* out of memory, L is now simplicial symbolic */ + CHOLMOD(clear_flag) (Common) ; + for (j = 0 ; j <= n ; j++) + { + Head [j] = EMPTY ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, wdim, Common)) ; + return (FALSE) ; + } + /* L->i and L->x may have moved. Column j has moved too */ + Li = L->i ; + Lx = L->x ; + p1 = Lp [j] ; + p2 = p1 + Lnz [j] ; + p3 = p1 + newlnz ; + } + + /* -------------------------------------------------------------- */ + /* create set pointers */ + /* -------------------------------------------------------------- */ + + for (s = 0 ; s < nsets ; s++) + { + /* Pattern of Set s is *(Set_ps1 [s] ... Set_ps2 [s]-1) */ + c = Set [s] ; + if (c < EMPTY) + { + /* column ccol of C, skip first entry (j) */ + ccol = FLIP (c) ; + pp1 = Cp [ccol] ; + pp2 = (packed) ? (Cp [ccol+1]) : (pp1 + Cnz [ccol]) ; + ASSERT (pp2 - pp1 > 1) ; + Set_ps1 [s] = &(Ci [pp1 + 1]) ; + Set_ps2 [s] = &(Ci [pp2]) ; + PRINT1 (("set "ID" is ccol "ID"\n", s, ccol)) ; + } + else + { + /* column c of L, skip first two entries (c and j) */ + pp1 = Lp [c] ; + pp2 = pp1 + Lnz [c] ; + ASSERT (Lnz [c] > 2) ; + Set_ps1 [s] = &(Li [pp1 + 2]) ; + Set_ps2 [s] = &(Li [pp2]) ; + PRINT1 (("set "ID" is L "ID"\n", s, c)) ; + } + DEBUG (dump_set (s, Set_ps1, Set_ps2, j, n, Common)) ; + } + + /* -------------------------------------------------------------- */ + /* multiset merge */ + /* -------------------------------------------------------------- */ + + /* Merge the sets into a single sorted set, Lj'. Before the merge + * starts, column j is located in Li/Lx [p1 ... p2-1] and the + * space Li/Lx [p2 ... p3-1] is empty. p1 is Lp [j], p2 is + * Lp [j] + Lnz [j] (the old length of the column), and p3 is + * Lp [j] + newlnz (the new and longer length of the column). + * + * The sets 0 to nsets-1 are defined by the Set_ps1 and Set_ps2 + * pointers. Set s is located in *(Set_ps1 [s] ... Set_ps2 [s]-1). + * It may be a column of C, or a column of L. All row indices i in + * the sets are in the range i > j and i < n. All sets are sorted. + * + * The merge into column j of L is done in place. + * + * During the merge, p2 and p3 are updated. Li/Lx [p1..p2-1] + * reflects the indices of the old column j of L that are yet to + * be merged into the new column. Entries in their proper place in + * the new column j of L are located in Li/Lx [p3 ... p1+newlnz-1]. + * The merge finishes when p2 == p3. + * + * During the merge, set s consumed as it is merged into column j of + * L. Its unconsumed contents are *(Set_ps1 [s] ... Set_ps2 [s]-1). + * When a set is completely consumed, it is removed from the set of + * sets, and nsets is decremented. + * + * The multiset merge and 2-set merge finishes when p2 == p3. + */ + + PRINT1 (("Multiset merge p3 "ID" p2 "ID" nsets "ID"\n", + p3, p2, nsets)) ; + + while (p3 > p2 && nsets > 1) + { + +#ifndef NDEBUG + PRINT2 (("\nMultiset merge. nsets = "ID"\n", nsets)) ; + PRINT2 (("Source col p1 = "ID", p2 = "ID", p3= "ID"\n", + p1, p2, p3)) ; + for (p = p1 + 1 ; p < p2 ; p++) + { + PRINT2 ((" p: "ID" source row "ID" %g\n", + p, Li[p], Lx[p])) ; + ASSERT (Li [p] > j && Li [p] < n) ; + } + PRINT2 (("---\n")) ; + for (p = p3 ; p < p1 + newlnz ; p++) + { + PRINT2 ((" p: "ID" target row "ID" %g\n", + p, Li[p], Lx[p])) ; + ASSERT (Li [p] > j && Li [p] < n) ; + } + for (s = 0 ; s < nsets ; s++) + { + dump_set (s, Set_ps1, Set_ps2, j, n, Common) ; + } +#endif + + /* get the entry at the tail end of source column Lj */ + row1 = Li [p2 - 1] ; + ASSERT (row1 >= j && p2 >= p1) ; + + /* find the largest row in all the sets */ + maxrow = row1 ; + smax = EMPTY ; + for (s = nsets-1 ; s >= 0 ; s--) + { + ASSERT (Set_ps1 [s] < Set_ps2 [s]) ; + row = *(Set_ps2 [s] - 1) ; + if (row == maxrow) + { + /* skip past this entry in set s (it is a duplicate) */ + Set_ps2 [s]-- ; + if (Set_ps1 [s] == Set_ps2 [s]) + { + /* nothing more in this set */ + nsets-- ; + Set_ps1 [s] = Set_ps1 [nsets] ; + Set_ps2 [s] = Set_ps2 [nsets] ; + if (smax == nsets) + { + /* Set smax redefined; it is now this set */ + smax = s ; + } + } + } + else if (row > maxrow) + { + maxrow = row ; + smax = s ; + } + } + ASSERT (maxrow > j) ; + + /* move the row onto the stack of the target column */ + if (maxrow == row1) + { + /* next entry is in Lj, move to the bottom of Lj' */ + ASSERT (smax == EMPTY) ; + p2-- ; + p3-- ; + Li [p3] = maxrow ; + Lx [p3] = Lx [p2] ; + } + else + { + /* new entry in Lj' */ + ASSERT (smax >= 0 && smax < nsets) ; + Set_ps2 [smax]-- ; + p3-- ; + Li [p3] = maxrow ; + Lx [p3] = 0.0 ; + if (Set_ps1 [smax] == Set_ps2 [smax]) + { + /* nothing more in this set */ + nsets-- ; + Set_ps1 [smax] = Set_ps1 [nsets] ; + Set_ps2 [smax] = Set_ps2 [nsets] ; + PRINT1 (("Set "ID" now empty\n", smax)) ; + } + } + } + + /* -------------------------------------------------------------- */ + /* 2-set merge: */ + /* -------------------------------------------------------------- */ + + /* This the same as the multi-set merge, except there is only one + * set s = 0 left. The source column j and the set 0 are being + * merged into the target column j. */ + + if (nsets > 0) + { + ps1 = Set_ps1 [0] ; + ps2 = Set_ps2 [0] ; + } + + while (p3 > p2) + { + +#ifndef NDEBUG + PRINT2 (("\n2-set merge.\n")) ; + ASSERT (nsets == 1) ; + PRINT2 (("Source col p1 = "ID", p2 = "ID", p3= "ID"\n", + p1, p2, p3)) ; + for (p = p1 + 1 ; p < p2 ; p++) + { + PRINT2 ((" p: "ID" source row "ID" %g\n", + p, Li[p], Lx[p])) ; + ASSERT (Li [p] > j && Li [p] < n) ; + } + PRINT2 (("---\n")) ; + for (p = p3 ; p < p1 + newlnz ; p++) + { + PRINT2 ((" p: "ID" target row "ID" %g\n", + p, Li[p], Lx[p])) ; + ASSERT (Li [p] > j && Li [p] < n) ; + } + dump_set (0, Set_ps1, Set_ps2, j, n, Common) ; +#endif + + if (p2 == p1 + 1) + { + /* the top of Lj is empty; copy the set and quit */ + while (p3 > p2) + { + /* new entry in Lj' */ + row = *(--ps2) ; + p3-- ; + Li [p3] = row ; + Lx [p3] = 0.0 ; + } + } + else + { + /* get the entry at the tail end of Lj */ + row1 = Li [p2 - 1] ; + ASSERT (row1 > j && row1 < n) ; + /* get the entry at the tail end of the incoming set */ + ASSERT (ps1 < ps2) ; + row = *(ps2-1) ; + ASSERT (row > j && row1 < n) ; + /* move the larger of the two entries to the target set */ + if (row1 >= row) + { + /* next entry is in Lj, move to the bottom */ + if (row1 == row) + { + /* skip past this entry in the set */ + ps2-- ; + } + p2-- ; + p3-- ; + Li [p3] = row1 ; + Lx [p3] = Lx [p2] ; + } + else + { + /* new entry in Lj' */ + ps2-- ; + p3-- ; + Li [p3] = row ; + Lx [p3] = 0.0 ; + } + } + } + + /* -------------------------------------------------------------- */ + /* The new column j of L is now in Li/Lx [p1 ... p2-1] */ + /* -------------------------------------------------------------- */ + + p2 = p1 + newlnz ; + DEBUG (dump_col ("After merge: ", j, p1, p2, Li, Lx, n, Common)) ; + + fl += Path [path].rank * (6 + 4 * (double) newlnz) ; + + /* -------------------------------------------------------------- */ + /* clear Flag; original pattern of column j L no longer marked */ + /* -------------------------------------------------------------- */ + + mark = CHOLMOD(clear_flag) (Common) ; + scattered = FALSE ; + + /* -------------------------------------------------------------- */ + /* find the new parent */ + /* -------------------------------------------------------------- */ + + newparent = (newlnz > 1) ? (Li [p1 + 1]) : EMPTY ; + PRINT1 (("\nNew parent, Lnz: "ID": "ID" "ID"\n", + j, newparent,newlnz)); + ASSERT (oldparent == EMPTY || newparent <= oldparent) ; + + /* -------------------------------------------------------------- */ + /* go to the next node in the path */ + /* -------------------------------------------------------------- */ + + /* path moves to (j,nextj) unless j is a root */ + nextj = (newparent == EMPTY) ? n : newparent ; + + /* place path at head of list for nextj, or terminate the path */ + PRINT1 (("\n j = "ID" nextj = "ID"\n\n", j, nextj)) ; + Path [path].c = j ; + if (nextj < n) + { + /* put path on link list of pending paths at column nextj */ + Path [path].next = Head [nextj] ; + Path [path].pending = nextj ; + Head [nextj] = path ; + PRINT1 (("Path "ID" continues to ("ID","ID"). Rank "ID"\n", + path, Path [path].c, nextj, Path [path].rank)) ; + } + else + { + /* path has ended here, at a root */ + Path [path].next = EMPTY ; + Path [path].pending = EMPTY ; + Path [path].end = j ; + PRINT1 (("Path "ID" ends at root ("ID"). Rank "ID"\n", + path, Path [path].end, Path [path].rank)) ; + } + + /* The link list Head [j] can now be emptied. Set Head [j] to 1 + * if column j has changed (it is no longer used as a link list). */ + PRINT1 (("column "ID", oldlnz = "ID"\n", j, Lnz [j])) ; + Head [j] = (Lnz [j] != newlnz) ? 1 : EMPTY ; + Lnz [j] = newlnz ; + PRINT1 (("column "ID", newlnz = "ID"\n", j, newlnz)) ; + DEBUG (dump_col ("New", j, p1, p2, Li, Lx, n, Common)) ; + + /* move to the next column */ + if (k == Path [path].rank) + { + /* only one path left */ + j = nextj ; + } + else + { + /* The current path is moving from column j to column nextj + * (nextj is n if the path has ended). However, there may be + * other paths pending in columns j+1 to nextj-1. There are + * two methods for looking for the next column with a pending + * update. The first one looks at all columns j+1 to nextj-1 + * for a non-empty link list. This can be costly if j and + * nextj differ by a large amount (it can be O(n), but this + * entire routine may take Omega(1) time). The second method + * looks at all paths and finds the smallest column at which any + * path is pending. It takes O(# of paths), which is bounded + * by 23: one for each column of C (up to 8), and then 15 for a + * balanced binary tree with 8 leaves. However, if j and + * nextj differ by a tiny amount (nextj is often j+1 near + * the end of the matrix), looking at columns j+1 to nextj + * would be faster. Both methods give the same answer. */ + + if (nextj - j < npaths) + { + /* there are fewer columns to search than paths */ + PRINT1 (("check j="ID" to nextj="ID"\n", j, nextj)) ; + for (j2 = j + 1 ; j2 < nextj ; j2++) + { + PRINT1 (("check j="ID" "ID"\n", j2, Head [j2])) ; + if (Head [j2] != EMPTY) + { + PRINT1 (("found, j="ID"\n", j2)) ; + ASSERT (Path [Head [j2]].pending == j2) ; + break ; + } + } + } + else + { + /* there are fewer paths than columns to search */ + j2 = nextj ; + for (kk = 0 ; kk < npaths ; kk++) + { + jj = Path [kk].pending ; + PRINT2 (("Path "ID" pending at "ID"\n", kk, jj)) ; + if (jj != EMPTY) j2 = MIN (j2, jj) ; + } + } + j = j2 ; + } + } + + /* ensure workspaces are back to the values required on input */ + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, TRUE, Common)) ; + + /* ------------------------------------------------------------------ */ + /* depth-first-search of tree to order the paths */ + /* ------------------------------------------------------------------ */ + + /* create lists of child paths */ + PRINT1 (("\n\nDFS search:\n\n")) ; + for (path = 0 ; path < npaths ; path++) + { + Path [path].c = EMPTY ; /* first child of path */ + Path [path].next = EMPTY ; /* next sibling of path */ + Path [path].order = EMPTY ; /* path is not ordered yet */ + Path [path].wfirst = EMPTY ; /* 1st column of W not found yet */ + +#ifndef NDEBUG + j = Path [path].start ; + PRINT1 (("Path "ID" : start "ID" end "ID" parent "ID" ccol "ID"\n", + path, j, Path [path].end, Path [path].parent, Path [path].ccol)) ; + for ( ; ; ) + { + PRINT1 ((" column "ID"\n", j)) ; + ASSERT (j == EMPTY || (j >= 0 && j < n)) ; + if (j == Path [path].end) + { + break ; + } + ASSERT (j >= 0 && j < n) ; + j = (Lnz [j] > 1) ? (Li [Lp [j] + 1]) : EMPTY ; + } +#endif + } + + for (path = 0 ; path < npaths ; path++) + { + p = Path [path].parent ; /* add path to child list of parent */ + if (p != EMPTY) + { + ASSERT (p < npaths) ; + Path [path].next = Path [p].c ; + Path [p].c = path ; + } + } + + path_order = k ; + w_order = 0 ; + for (path = npaths-1 ; path >= 0 ; path--) + { + if (Path [path].order == EMPTY) + { + /* this path is the root of a subtree of Tbar */ + PRINT1 (("Root path "ID"\n", path)) ; + ASSERT (path >= k) ; + dfs (Path, k, path, &path_order, &w_order, 0, npaths) ; + } + } + ASSERT (path_order == npaths) ; + ASSERT (w_order == k) ; + + /* reorder the paths */ + for (path = 0 ; path < npaths ; path++) + { + /* old order is path, new order is Path [path].order */ + OrderedPath [Path [path].order] = Path [path] ; + } + +#ifndef NDEBUG + for (path = 0 ; path < npaths ; path++) + { + PRINT1 (("Ordered Path "ID": start "ID" end "ID" wfirst "ID" rank " + ""ID" ccol "ID"\n", path, OrderedPath [path].start, + OrderedPath [path].end, OrderedPath [path].wfirst, + OrderedPath [path].rank, OrderedPath [path].ccol)) ; + if (path < k) + { + ASSERT (OrderedPath [path].ccol >= 0) ; + } + else + { + ASSERT (OrderedPath [path].ccol == EMPTY) ; + } + } +#endif + + /* ------------------------------------------------------------------ */ + /* numeric update/downdate for all paths */ + /* ------------------------------------------------------------------ */ + + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, wdim, Common)) ; + + switch (wdim) + { + case 1: + updown_1_r (update, C, k, L, W, OrderedPath, npaths, mask, + Common) ; + break ; + case 2: + updown_2_r (update, C, k, L, W, OrderedPath, npaths, mask, + Common) ; + break ; + case 4: + updown_4_r (update, C, k, L, W, OrderedPath, npaths, mask, + Common) ; + break ; + case 8: + updown_8_r (update, C, k, L, W, OrderedPath, npaths, mask, + Common) ; + break ; + } + + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, wdim, Common)) ; + } + + /* ---------------------------------------------------------------------- */ + /* update/downdate the forward solve */ + /* ---------------------------------------------------------------------- */ + + if (do_solve) + { + /* We now have DeltaB += Lold (:,j) * X (j) for all columns j in union + * of all paths seen during the entire rank-cncol update/downdate. For + * each j in path, do DeltaB -= Lnew (:,j)*DeltaB(j) + * in topological order. */ + +#ifndef NDEBUG + PRINT1 (("\ndo_solve, DeltaB + Lold(:,Path)*X(Path):\n")) ; + for (i = 0 ; i < n ; i++) + { + PRINT1 (("do_solve: "ID" %30.20e\n", i, Nx [i])) ; + } +#endif + + /* Note that the downdate, if it deleted entries, would need to compute + * the Stack prior to doing any downdates. */ + + /* find the union of all the paths in the new L */ + top = n ; /* "top" is stack pointer, not a row or column index */ + for (ccol = 0 ; ccol < cncol ; ccol++) + { + + /* -------------------------------------------------------------- */ + /* j = first row index of C (:,ccol) */ + /* -------------------------------------------------------------- */ + + pp1 = Cp [ccol] ; + pp2 = (packed) ? (Cp [ccol+1]) : (pp1 + Cnz [ccol]) ; + if (pp2 > pp1) + { + /* Column ccol of C has at least one entry. */ + j = Ci [pp1] ; + } + else + { + /* Column ccol of C is empty */ + j = n-1 ; + } + PRINT1 (("\ndo_solve: ccol= "ID"\n", ccol)) ; + ASSERT (j >= 0 && j < n) ; + len = 0 ; + + /* -------------------------------------------------------------- */ + /* find the new rowmark */ + /* -------------------------------------------------------------- */ + + /* Each column of C can redefine the region of L that takes part in + * the update/downdate of the triangular solve Lx=b. If + * i = colmark [ccol] for column C(:,ccol), then i = rowmark [j] is + * redefined for all columns along the path modified by C(:,ccol). + * If more than one column modifies any given column j of L, then + * the rowmark of j is determined by the colmark of the least- + * numbered column that affects column j. That is, if both + * C(:,ccol1) and C(:,ccol2) affect column j of L, then + * rowmark [j] = colmark [MIN (ccol1, ccol2)]. + * + * rowmark [j] is not modified if rowmark or colmark are NULL, + * or if colmark [ccol] is EMPTY. + */ + + botrow = (use_colmark) ? (colmark [ccol]) : EMPTY ; + + /* -------------------------------------------------------------- */ + /* traverse from j towards root, stopping if node already visited */ + /* -------------------------------------------------------------- */ + + while (j != EMPTY && Flag [j] < mark) + { + PRINT1 (("do_solve: subpath j= "ID"\n", j)) ; + ASSERT (j >= 0 && j < n) ; + Stack [len++] = j ; /* place j on the stack */ + Flag [j] = mark ; /* flag j as visited */ + + /* if using colmark, mark column j with botrow */ + ASSERT (Li [Lp [j]] == j) ; /* diagonal is always present */ + if (use_colmark) + { + Li [Lp [j]] = botrow ; /* use the space for botrow */ + } + + /* go up the tree, to the parent of j */ + j = (Lnz [j] > 1) ? (Li [Lp [j] + 1]) : EMPTY ; + } + + /* -------------------------------------------------------------- */ + /* move the path down to the bottom of the stack */ + /* -------------------------------------------------------------- */ + + ASSERT (len <= top) ; + while (len > 0) + { + Stack [--top] = Stack [--len] ; + } + } + +#ifndef NDEBUG + /* Union of paths now in Stack [top..n-1] in topological order */ + PRINT1 (("\nTopological order:\n")) ; + for (i = top ; i < n ; i++) + { + PRINT1 (("column "ID" in full path\n", Stack [i])) ; + } +#endif + + /* Do the forward solve for the full path part of L */ + for (m = top ; m < n ; m++) + { + j = Stack [m] ; + ASSERT (j >= 0 && j < n) ; + PRINT1 (("do_solve: path j= "ID"\n", j)) ; + p1 = Lp [j] ; + lnz = Lnz [j] ; + p2 = p1 + lnz ; + xj = Nx [j] ; + + /* copy new solution onto old one, for all cols in full path */ + Xx [j] = xj ; + Nx [j] = 0. ; + + /* DeltaB -= Lnew (j+1:botrow-1,j) * deltab(j) */ + if (use_colmark) + { + botrow = Li [p1] ; /* get botrow */ + Li [p1] = j ; /* restore diagonal entry */ + for (p = p1 + 1 ; p < p2 ; p++) + { + i = Li [p] ; + if (i >= botrow) break ; + Nx [i] -= Lx [p] * xj ; + } + } + else + { + for (p = p1 + 1 ; p < p2 ; p++) + { + Nx [Li [p]] -= Lx [p] * xj ; + } + } + } + + /* clear the Flag */ + mark = CHOLMOD(clear_flag) (Common) ; + } + + /* ---------------------------------------------------------------------- */ + /* successful update/downdate */ + /* ---------------------------------------------------------------------- */ + + Common->modfl = fl ; + DEBUG (for (j = 0 ; j < n ; j++) ASSERT (IMPLIES (do_solve, Nx[j] == 0.))) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, TRUE, Common)) ; + DEBUG (CHOLMOD(dump_factor) (L, "output L for updown", Common)) ; + return (TRUE) ; +} +#endif diff --git a/src/CHOLMOD/Modify/gpl.txt b/src/CHOLMOD/Modify/gpl.txt new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/src/CHOLMOD/Modify/gpl.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/src/CHOLMOD/Modify/t_cholmod_updown.c b/src/CHOLMOD/Modify/t_cholmod_updown.c new file mode 100644 index 0000000..8d63c48 --- /dev/null +++ b/src/CHOLMOD/Modify/t_cholmod_updown.c @@ -0,0 +1,214 @@ +/* ========================================================================== */ +/* === Modify/t_cholmod_updown ============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Modify Module. Copyright (C) 2005-2006, + * Timothy A. Davis and William W. Hager. + * The CHOLMOD/Modify Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Updates/downdates the LDL' factorization, by computing a new factorization of + * + * Lnew * Dnew * Lnew' = Lold * Dold * Lold' +/- C*C' + * + * This file is not compiled separately. It is included into + * cholmod_updown.c. There are no user-callable routines in this file. + * + * The next include statements, below, create the numerical update/downdate + * kernels from t_cholmod_updown_numkr.c. There are 4 compiled versions of this + * file, one for each value of WDIM in the set 1, 2, 4, and 8. Each calls + * multiple versions of t_cholmod_updown_numkr; the number of versions of each + * is equal to WDIM. Each t_cholmod_updown_numkr version is included as a + * static function within its t_cholmod_updown.c caller routine. Thus: + * + * t*_updown.c creates these versions of t_cholmod_updown_numkr.c: + * --------- --------------------------------------------------- + * + * updown_1_r updown_1_1 + * + * updown_2_r updown_2_1 updown_2_2 + * + * updown_4_r updown_4_1 updown_4_2 updown_4_3 updown_4_4 + * + * updown_8_r updown_8_1 updown_8_2 updown_8_3 updown_8_4 + * updown_8_5 updown_8_6 updown_8_7 updown_8_8 + * + * workspace: Xwork (nrow*wdim) + */ + +/* ========================================================================== */ +/* === routines for numeric update/downdate along one path ================== */ +/* ========================================================================== */ + +#undef FORM_NAME +#undef NUMERIC + +#define FORM_NAME(k,rank) updown_ ## k ## _ ## rank +#define NUMERIC(k,rank) FORM_NAME(k,rank) + +#define RANK 1 +#include "t_cholmod_updown_numkr.c" + +#if WDIM >= 2 +#define RANK 2 +#include "t_cholmod_updown_numkr.c" +#endif + +#if WDIM >= 4 +#define RANK 3 +#include "t_cholmod_updown_numkr.c" +#define RANK 4 +#include "t_cholmod_updown_numkr.c" +#endif + +#if WDIM == 8 +#define RANK 5 +#include "t_cholmod_updown_numkr.c" +#define RANK 6 +#include "t_cholmod_updown_numkr.c" +#define RANK 7 +#include "t_cholmod_updown_numkr.c" +#define RANK 8 +#include "t_cholmod_updown_numkr.c" +#endif + + +/* ========================================================================== */ +/* === numeric update/downdate for all paths ================================ */ +/* ========================================================================== */ + +static void NUMERIC (WDIM, r) +( + int update, /* TRUE for update, FALSE for downdate */ + cholmod_sparse *C, /* in packed or unpacked, and sorted form */ + /* no empty columns */ + Int rank, /* rank of the update/downdate */ + cholmod_factor *L, /* with unit diagonal (diagonal not stored) */ + /* temporary workspaces: */ + double W [ ], /* n-by-WDIM dense matrix, initially zero */ + Path_type Path [ ], + Int npaths, + Int mask [ ], /* size n */ + cholmod_common *Common +) +{ + double Alpha [8] ; + double *Cx, *Wpath, *W1, *a ; + Int i, j, p, ccol, pend, wfirst, e, path, packed ; + Int *Ci, *Cp, *Cnz ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Ci = C->i ; + Cx = C->x ; + Cp = C->p ; + Cnz = C->nz ; + packed = C->packed ; + ASSERT (IMPLIES (!packed, Cnz != NULL)) ; + ASSERT (L->n == C->nrow) ; + DEBUG (CHOLMOD(dump_real) ("num_d: in W:", W, WDIM, L->n, FALSE, 1,Common)); + + /* ---------------------------------------------------------------------- */ + /* scatter C into W */ + /* ---------------------------------------------------------------------- */ + + for (path = 0 ; path < rank ; path++) + { + /* W (:, path) = C (:, Path [path].col) */ + ccol = Path [path].ccol ; + Wpath = W + path ; + PRINT1 (("Ordered Columns [path = "ID"] = "ID"\n", path, ccol)) ; + p = Cp [ccol] ; + pend = (packed) ? (Cp [ccol+1]) : (p + Cnz [ccol]) ; + /* column C can be empty */ + for ( ; p < pend ; p++) + { + i = Ci [p] ; + ASSERT (i >= 0 && i < (Int) (C->nrow)) ; + if (mask == NULL || mask [i] < 0) + { + Wpath [WDIM * i] = Cx [p] ; + } + PRINT1 ((" row "ID" : %g mask "ID"\n", i, Cx [p], + (mask) ? mask [i] : 0)) ; + } + Alpha [path] = 1.0 ; + } + DEBUG (CHOLMOD(dump_real) ("num_d: W:", W, WDIM, L->n, FALSE, 1,Common)) ; + + /* ---------------------------------------------------------------------- */ + /* numeric update/downdate of the paths */ + /* ---------------------------------------------------------------------- */ + + /* for each disjoint subpath in Tbar in DFS order do */ + for (path = rank ; path < npaths ; path++) + { + + /* determine which columns of W to use */ + wfirst = Path [path].wfirst ; + e = Path [path].end ; + j = Path [path].start ; + ASSERT (e >= 0 && e < (Int) (L->n)) ; + ASSERT (j >= 0 && j < (Int) (L->n)) ; + + W1 = W + wfirst ; /* pointer to row 0, column wfirst of W */ + a = Alpha + wfirst ; /* pointer to Alpha [wfirst] */ + + PRINT1 (("Numerical update/downdate of path "ID"\n", path)) ; + PRINT1 (("start "ID" end "ID" wfirst "ID" rank "ID" ccol "ID"\n", j, e, + wfirst, Path [path].rank, Path [path].ccol)) ; + +#if WDIM == 1 + NUMERIC (WDIM,1) (update, j, e, a, W1, L, Common) ; +#else + + switch (Path [path].rank) + { + case 1: + NUMERIC (WDIM,1) (update, j, e, a, W1, L, Common) ; + break ; + +#if WDIM >= 2 + case 2: + NUMERIC (WDIM,2) (update, j, e, a, W1, L, Common) ; + break ; +#endif + +#if WDIM >= 4 + case 3: + NUMERIC (WDIM,3) (update, j, e, a, W1, L, Common) ; + break ; + case 4: + NUMERIC (WDIM,4) (update, j, e, a, W1, L, Common) ; + break ; +#endif + +#if WDIM == 8 + case 5: + NUMERIC (WDIM,5) (update, j, e, a, W1, L, Common) ; + break ; + case 6: + NUMERIC (WDIM,6) (update, j, e, a, W1, L, Common) ; + break ; + case 7: + NUMERIC (WDIM,7) (update, j, e, a, W1, L, Common) ; + break ; + case 8: + NUMERIC (WDIM,8) (update, j, e, a, W1, L, Common) ; + break ; +#endif + + } +#endif + + } +} + +/* prepare for the next inclusion of this file in cholmod_updown.c */ +#undef WDIM diff --git a/src/CHOLMOD/Modify/t_cholmod_updown_numkr.c b/src/CHOLMOD/Modify/t_cholmod_updown_numkr.c new file mode 100644 index 0000000..df01dc5 --- /dev/null +++ b/src/CHOLMOD/Modify/t_cholmod_updown_numkr.c @@ -0,0 +1,746 @@ +/* ========================================================================== */ +/* === Modify/t_cholmod_updown_numkr ======================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Modify Module. Copyright (C) 2005-2006, + * Timothy A. Davis and William W. Hager. + * The CHOLMOD/Modify Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Supernodal numerical update/downdate of rank K = RANK, along a single path. + * This routine operates on a simplicial factor, but operates on adjacent + * columns of L that would fit within a single supernode. "Adjacent" means + * along a single path in the elimination tree; they may or may not be + * adjacent in the matrix L. + * + * external defines: NUMERIC, WDIM, RANK. + * + * WDIM is 1, 2, 4, or 8. RANK can be 1 to WDIM. + * + * A simple method is included (#define SIMPLE). The code works, but is slow. + * It is meant only to illustrate what this routine is doing. + * + * A rank-K update proceeds along a single path, using single-column, dual- + * column, or quad-column updates of L. If a column j and the next column + * in the path (its parent) do not have the same nonzero pattern, a single- + * column update is used. If they do, but the 3rd and 4th column from j do + * not have the same pattern, a dual-column update is used, in which the two + * columns are treated as if they were a single supernode of two columns. If + * there are 4 columns in the path that all have the same nonzero pattern, then + * a quad-column update is used. All three kinds of updates can be used along + * a single path, in a single call to this function. + * + * Single-column update: + * + * When updating a single column of L, each iteration of the for loop, + * below, processes four rows of W (all columns involved) and one column + * of L. Suppose we have a rank-5 update, and columns 2 through 6 of W + * are involved. In this case, W in this routine is a pointer to column + * 2 of the matrix W in the caller. W (in the caller, shown as 'W') is + * held in row-major order, and is 8-by-n (a dense matrix storage format), + * but shown below in column form to match the column of L. Suppose there + * are 13 nonzero entries in column 27 of L, with row indices 27 (the + * diagonal, D), 28, 30, 31, 42, 43, 44, 50, 51, 67, 81, 83, and 84. This + * pattern is held in Li [Lp [27] ... Lp [27 + Lnz [27] - 1], where + * Lnz [27] = 13. The modification of the current column j of L is done + * in the following order. A dot (.) means the entry of W is not accessed. + * + * W0 points to row 27 of W, and G is a 1-by-8 temporary vector. + * + * G[0] G[4] + * G x x x x x . . . + * + * W0 + * | + * v + * 27 . . x x x x x . W0 points to W (27,2) + * + * + * row 'W' W column j = 27 + * | | | of L + * v v v | + * first iteration of for loop: v + * + * 28 . . 1 5 9 13 17 . x + * 30 . . 2 6 10 14 18 . x + * 31 . . 3 7 11 15 19 . x + * 42 . . 4 8 12 16 20 . x + * + * second iteration of for loop: + * + * 43 . . 1 5 9 13 17 . x + * 44 . . 2 6 10 14 18 . x + * 50 . . 3 7 11 15 19 . x + * 51 . . 4 8 12 16 20 . x + * + * third iteration of for loop: + * + * 67 . . 1 5 9 13 17 . x + * 81 . . 2 6 10 14 18 . x + * 83 . . 3 7 11 15 19 . x + * 84 . . 4 8 12 16 20 . x + * + * If the number of offdiagonal nonzeros in column j of L is not divisible + * by 4, then the switch-statement does the work for the first nz % 4 rows. + * + * Dual-column update: + * + * In this case, two columns of L that are adjacent in the path are being + * updated, by 1 to 8 columns of W. Suppose columns j=27 and j=28 are + * adjacent columns in the path (they need not be j and j+1). Two rows + * of G and W are used as coefficients during the update: (G0, G1) and + * (W0, W1). + * + * G0 x x x x x . . . + * G1 x x x x x . . . + * + * 27 . . x x x x x . W0 points to W (27,2) + * 28 . . x x x x x . W1 points to W (28,2) + * + * + * row 'W' W0,W1 column j = 27 + * | | | of L + * v v v | + * | |-- column j = 28 of L + * v v + * update L (j1,j): + * + * 28 . . 1 2 3 4 5 . x - ("-" is not stored in L) + * + * cleanup iteration since length is odd: + * + * 30 . . 1 2 3 4 5 . x x + * + * then each iteration does two rows of both columns of L: + * + * 31 . . 1 3 5 7 9 . x x + * 42 . . 2 4 6 8 10 . x x + * + * 43 . . 1 3 5 7 9 . x x + * 44 . . 2 4 6 8 10 . x x + * + * 50 . . 1 3 5 7 9 . x x + * 51 . . 2 4 6 8 10 . x x + * + * 67 . . 1 3 5 7 9 . x x + * 81 . . 2 4 6 8 10 . x x + * + * 83 . . 1 3 5 7 9 . x x + * 84 . . 2 4 6 8 10 . x x + * + * If the number of offdiagonal nonzeros in column j of L is not even, + * then the cleanup iteration does the work for the first row. + * + * Quad-column update: + * + * In this case, four columns of L that are adjacent in the path are being + * updated, by 1 to 8 columns of W. Suppose columns j=27, 28, 30, and 31 + * are adjacent columns in the path (they need not be j, j+1, ...). Four + * rows of G and W are used as coefficients during the update: (G0 through + * G3) and (W0 through W3). j=27, j1=28, j2=30, and j3=31. + * + * G0 x x x x x . . . + * G1 x x x x x . . . + * G3 x x x x x . . . + * G4 x x x x x . . . + * + * 27 . . x x x x x . W0 points to W (27,2) + * 28 . . x x x x x . W1 points to W (28,2) + * 30 . . x x x x x . W2 points to W (30,2) + * 31 . . x x x x x . W3 points to W (31,2) + * + * + * row 'W' W0,W1,.. column j = 27 + * | | | of L + * v v v | + * | |-- column j = 28 of L + * | | |-- column j = 30 of L + * | | | |-- column j = 31 of L + * v v v v + * update L (j1,j): + * 28 . . 1 2 3 4 5 . x - - - + * + * update L (j2,j): + * 30 . . 1 2 3 4 5 . # x - - (# denotes modified) + * + * update L (j2,j1) + * 30 . . 1 2 3 4 5 . x # - - + * + * update L (j3,j) + * 31 . . 1 2 3 4 5 . # x x - + * + * update L (j3,j1) + * 31 . . 1 2 3 4 5 . x # x - + * + * update L (j3,j2) + * 31 . . 1 2 3 4 5 . x x # - + * + * cleanup iteration since length is odd: + * 42 . . 1 2 3 4 5 . x x x x + * + * + * ----- CHOLMOD v1.1.1 did the following -------------------------------------- + * then each iteration does two rows of all four colummns of L: + * + * 43 . . 1 3 5 7 9 . x x x x + * 44 . . 2 4 6 8 10 . x x x x + * + * 50 . . 1 3 5 7 9 . x x x x + * 51 . . 2 4 6 8 10 . x x x x + * + * 67 . . 1 3 5 7 9 . x x x x + * 81 . . 2 4 6 8 10 . x x x x + * + * 83 . . 1 3 5 7 9 . x x x x + * 84 . . 2 4 6 8 10 . x x x x + * + * ----- CHOLMOD v1.2.0 does the following ------------------------------------- + * then each iteration does one rows of all four colummns of L: + * + * 43 . . 1 2 3 4 5 . x x x x + * 44 . . 1 2 3 4 5 . x x x x + * 50 . . 1 3 5 4 5 . x x x x + * 51 . . 1 2 3 4 5 . x x x x + * 67 . . 1 3 5 4 5 . x x x x + * 81 . . 1 2 3 4 5 . x x x x + * 83 . . 1 3 5 4 5 . x x x x + * 84 . . 1 2 3 4 5 . x x x x + * + * This file is included in t_cholmod_updown.c, only. + * It is not compiled separately. It contains no user-callable routines. + * + * workspace: Xwork (WDIM*nrow) + */ + +/* ========================================================================== */ +/* === loop unrolling macros ================================================ */ +/* ========================================================================== */ + +#undef RANK1 +#undef RANK2 +#undef RANK3 +#undef RANK4 +#undef RANK5 +#undef RANK6 +#undef RANK7 +#undef RANK8 + +#define RANK1(statement) statement + +#if RANK < 2 +#define RANK2(statement) +#else +#define RANK2(statement) statement +#endif + +#if RANK < 3 +#define RANK3(statement) +#else +#define RANK3(statement) statement +#endif + +#if RANK < 4 +#define RANK4(statement) +#else +#define RANK4(statement) statement +#endif + +#if RANK < 5 +#define RANK5(statement) +#else +#define RANK5(statement) statement +#endif + +#if RANK < 6 +#define RANK6(statement) +#else +#define RANK6(statement) statement +#endif + +#if RANK < 7 +#define RANK7(statement) +#else +#define RANK7(statement) statement +#endif + +#if RANK < 8 +#define RANK8(statement) +#else +#define RANK8(statement) statement +#endif + +#define FOR_ALL_K \ + RANK1 (DO (0)) \ + RANK2 (DO (1)) \ + RANK3 (DO (2)) \ + RANK4 (DO (3)) \ + RANK5 (DO (4)) \ + RANK6 (DO (5)) \ + RANK7 (DO (6)) \ + RANK8 (DO (7)) + +/* ========================================================================== */ +/* === alpha/gamma ========================================================== */ +/* ========================================================================== */ + +#undef ALPHA_GAMMA + +#define ALPHA_GAMMA(Dj,Alpha,Gamma,W) \ +{ \ + double dj = Dj ; \ + if (update) \ + { \ + for (k = 0 ; k < RANK ; k++) \ + { \ + double w = W [k] ; \ + double alpha = Alpha [k] ; \ + double a = alpha + (w * w) / dj ; \ + dj *= a ; \ + Alpha [k] = a ; \ + Gamma [k] = (- w / dj) ; \ + dj /= alpha ; \ + } \ + } \ + else \ + { \ + for (k = 0 ; k < RANK ; k++) \ + { \ + double w = W [k] ; \ + double alpha = Alpha [k] ; \ + double a = alpha - (w * w) / dj ; \ + dj *= a ; \ + Alpha [k] = a ; \ + Gamma [k] = w / dj ; \ + dj /= alpha ; \ + } \ + } \ + Dj = ((use_dbound) ? (CHOLMOD(dbound) (dj, Common)) : (dj)) ; \ +} + +/* ========================================================================== */ +/* === numeric update/downdate along one path =============================== */ +/* ========================================================================== */ + +static void NUMERIC (WDIM, RANK) +( + int update, /* TRUE for update, FALSE for downdate */ + Int j, /* first column in the path */ + Int e, /* last column in the path */ + double Alpha [ ], /* alpha, for each column of W */ + double W [ ], /* W is an n-by-WDIM array, stored in row-major order */ + cholmod_factor *L, /* with unit diagonal (diagonal not stored) */ + cholmod_common *Common +) +{ + +#ifdef SIMPLE +#define w(row,col) W [WDIM*(row) + (col)] + + /* ---------------------------------------------------------------------- */ + /* concise but slow version for illustration only */ + /* ---------------------------------------------------------------------- */ + + double Gamma [WDIM] ; + double *Lx ; + Int *Li, *Lp, *Lnz ; + Int p, k ; + Int use_dbound = IS_GT_ZERO (Common->dbound) ; + + Li = L->i ; + Lx = L->x ; + Lp = L->p ; + Lnz = L->nz ; + + /* walk up the etree from node j to its ancestor e */ + for ( ; j <= e ; j = (Lnz [j] > 1) ? (Li [Lp [j] + 1]) : Int_max) + { + /* update the diagonal entry D (j,j) with each column of W */ + ALPHA_GAMMA (Lx [Lp [j]], Alpha, Gamma, (&(w (j,0)))) ; + /* update column j of L */ + for (p = Lp [j] + 1 ; p < Lp [j] + Lnz [j] ; p++) + { + /* update row Li [p] of column j of L with each column of W */ + Int i = Li [p] ; + for (k = 0 ; k < RANK ; k++) + { + w (i,k) -= w (j,k) * Lx [p] ; + Lx [p] -= Gamma [k] * w (i,k) ; + } + } + /* clear workspace W */ + for (k = 0 ; k < RANK ; k++) + { + w (j,k) = 0 ; + } + } + +#else + + /* ---------------------------------------------------------------------- */ + /* dynamic supernodal version: supernodes detected dynamically */ + /* ---------------------------------------------------------------------- */ + + double G0 [RANK], G1 [RANK], G2 [RANK], G3 [RANK] ; + double Z0 [RANK], Z1 [RANK], Z2 [RANK], Z3 [RANK] ; + double *W0, *W1, *W2, *W3, *Lx ; + Int *Li, *Lp, *Lnz ; + Int j1, j2, j3, p0, p1, p2, p3, parent, lnz, pend, k ; + Int use_dbound = IS_GT_ZERO (Common->dbound) ; + + Li = L->i ; + Lx = L->x ; + Lp = L->p ; + Lnz = L->nz ; + + /* walk up the etree from node j to its ancestor e */ + for ( ; j <= e ; j = parent) + { + + p0 = Lp [j] ; /* col j is Li,Lx [p0 ... p0+lnz-1] */ + lnz = Lnz [j] ; + + W0 = W + WDIM * j ; /* pointer to row j of W */ + pend = p0 + lnz ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) Z0 [k] = W0 [k] ; + FOR_ALL_K + #undef DO + + /* for k = 0 to RANK-1 do: */ + #define DO(k) W0 [k] = 0 ; + FOR_ALL_K + #undef DO + + /* update D (j,j) */ + ALPHA_GAMMA (Lx [p0], Alpha, G0, Z0) ; + p0++ ; + + /* determine how many columns of L to update at the same time */ + parent = (lnz > 1) ? (Li [p0]) : Int_max ; + if (parent <= e && lnz == Lnz [parent] + 1) + { + + /* -------------------------------------------------------------- */ + /* node j and its parent j1 can be updated at the same time */ + /* -------------------------------------------------------------- */ + + j1 = parent ; + j2 = (lnz > 2) ? (Li [p0+1]) : Int_max ; + j3 = (lnz > 3) ? (Li [p0+2]) : Int_max ; + W1 = W + WDIM * j1 ; /* pointer to row j1 of W */ + p1 = Lp [j1] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) Z1 [k] = W1 [k] ; + FOR_ALL_K + #undef DO + + /* for k = 0 to RANK-1 do: */ + #define DO(k) W1 [k] = 0 ; + FOR_ALL_K + #undef DO + + /* update L (j1,j) */ + { + double lx = Lx [p0] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) \ + Z1 [k] -= Z0 [k] * lx ; \ + lx -= G0 [k] * Z1 [k] ; + FOR_ALL_K + #undef DO + + Lx [p0++] = lx ; + } + + /* update D (j1,j1) */ + ALPHA_GAMMA (Lx [p1], Alpha, G1, Z1) ; + p1++ ; + + /* -------------------------------------------------------------- */ + /* update 2 or 4 columns of L */ + /* -------------------------------------------------------------- */ + + if ((j2 <= e) && /* j2 in the current path */ + (j3 <= e) && /* j3 in the current path */ + (lnz == Lnz [j2] + 2) && /* column j2 matches */ + (lnz == Lnz [j3] + 3)) /* column j3 matches */ + { + + /* ---------------------------------------------------------- */ + /* update 4 columns of L */ + /* ---------------------------------------------------------- */ + + /* p0 and p1 currently point to row j2 in cols j and j1 of L */ + + parent = (lnz > 4) ? (Li [p0+2]) : Int_max ; + W2 = W + WDIM * j2 ; /* pointer to row j2 of W */ + W3 = W + WDIM * j3 ; /* pointer to row j3 of W */ + p2 = Lp [j2] ; + p3 = Lp [j3] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) Z2 [k] = W2 [k] ; + FOR_ALL_K + #undef DO + + /* for k = 0 to RANK-1 do: */ + #define DO(k) Z3 [k] = W3 [k] ; + FOR_ALL_K + #undef DO + + /* for k = 0 to RANK-1 do: */ + #define DO(k) W2 [k] = 0 ; + FOR_ALL_K + #undef DO + + /* for k = 0 to RANK-1 do: */ + #define DO(k) W3 [k] = 0 ; + FOR_ALL_K + #undef DO + + /* update L (j2,j) and update L (j2,j1) */ + { + double lx [2] ; + lx [0] = Lx [p0] ; + lx [1] = Lx [p1] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) \ + Z2 [k] -= Z0 [k] * lx [0] ; lx [0] -= G0 [k] * Z2 [k] ; \ + Z2 [k] -= Z1 [k] * lx [1] ; lx [1] -= G1 [k] * Z2 [k] ; + FOR_ALL_K + #undef DO + + Lx [p0++] = lx [0] ; + Lx [p1++] = lx [1] ; + } + + /* update D (j2,j2) */ + ALPHA_GAMMA (Lx [p2], Alpha, G2, Z2) ; + p2++ ; + + /* update L (j3,j), L (j3,j1), and L (j3,j2) */ + { + double lx [3] ; + lx [0] = Lx [p0] ; + lx [1] = Lx [p1] ; + lx [2] = Lx [p2] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) \ + Z3 [k] -= Z0 [k] * lx [0] ; lx [0] -= G0 [k] * Z3 [k] ; \ + Z3 [k] -= Z1 [k] * lx [1] ; lx [1] -= G1 [k] * Z3 [k] ; \ + Z3 [k] -= Z2 [k] * lx [2] ; lx [2] -= G2 [k] * Z3 [k] ; + FOR_ALL_K + #undef DO + + Lx [p0++] = lx [0] ; + Lx [p1++] = lx [1] ; + Lx [p2++] = lx [2] ; + } + + /* update D (j3,j3) */ + ALPHA_GAMMA (Lx [p3], Alpha, G3, Z3) ; + p3++ ; + + /* each iteration updates L (i, [j j1 j2 j3]) */ + for ( ; p0 < pend ; p0++, p1++, p2++, p3++) + { + double lx [4], *w0 ; + lx [0] = Lx [p0] ; + lx [1] = Lx [p1] ; + lx [2] = Lx [p2] ; + lx [3] = Lx [p3] ; + w0 = W + WDIM * Li [p0] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) \ + w0 [k] -= Z0 [k] * lx [0] ; lx [0] -= G0 [k] * w0 [k] ; \ + w0 [k] -= Z1 [k] * lx [1] ; lx [1] -= G1 [k] * w0 [k] ; \ + w0 [k] -= Z2 [k] * lx [2] ; lx [2] -= G2 [k] * w0 [k] ; \ + w0 [k] -= Z3 [k] * lx [3] ; lx [3] -= G3 [k] * w0 [k] ; + FOR_ALL_K + #undef DO + + Lx [p0] = lx [0] ; + Lx [p1] = lx [1] ; + Lx [p2] = lx [2] ; + Lx [p3] = lx [3] ; + } + } + else + { + + /* ---------------------------------------------------------- */ + /* update 2 columns of L */ + /* ---------------------------------------------------------- */ + + parent = j2 ; + + /* cleanup iteration if length is odd */ + if ((lnz - 2) % 2) + { + double lx [2] , *w0 ; + lx [0] = Lx [p0] ; + lx [1] = Lx [p1] ; + w0 = W + WDIM * Li [p0] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) \ + w0 [k] -= Z0 [k] * lx [0] ; lx [0] -= G0 [k] * w0 [k] ; \ + w0 [k] -= Z1 [k] * lx [1] ; lx [1] -= G1 [k] * w0 [k] ; + FOR_ALL_K + #undef DO + + Lx [p0++] = lx [0] ; + Lx [p1++] = lx [1] ; + } + + for ( ; p0 < pend ; p0 += 2, p1 += 2) + { + double lx [2][2], w [2], *w0, *w1 ; + lx [0][0] = Lx [p0 ] ; + lx [1][0] = Lx [p0+1] ; + lx [0][1] = Lx [p1 ] ; + lx [1][1] = Lx [p1+1] ; + w0 = W + WDIM * Li [p0 ] ; + w1 = W + WDIM * Li [p0+1] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) \ + w [0] = w0 [k] - Z0 [k] * lx [0][0] ; \ + w [1] = w1 [k] - Z0 [k] * lx [1][0] ; \ + lx [0][0] -= G0 [k] * w [0] ; \ + lx [1][0] -= G0 [k] * w [1] ; \ + w0 [k] = w [0] -= Z1 [k] * lx [0][1] ; \ + w1 [k] = w [1] -= Z1 [k] * lx [1][1] ; \ + lx [0][1] -= G1 [k] * w [0] ; \ + lx [1][1] -= G1 [k] * w [1] ; + FOR_ALL_K + #undef DO + + Lx [p0 ] = lx [0][0] ; + Lx [p0+1] = lx [1][0] ; + Lx [p1 ] = lx [0][1] ; + Lx [p1+1] = lx [1][1] ; + } + } + } + else + { + + /* -------------------------------------------------------------- */ + /* update one column of L */ + /* -------------------------------------------------------------- */ + + /* cleanup iteration if length is not a multiple of 4 */ + switch ((lnz - 1) % 4) + { + case 1: + { + double lx , *w0 ; + lx = Lx [p0] ; + w0 = W + WDIM * Li [p0] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) \ + w0 [k] -= Z0 [k] * lx ; lx -= G0 [k] * w0 [k] ; + FOR_ALL_K + #undef DO + + Lx [p0++] = lx ; + } + break ; + + case 2: + { + double lx [2], *w0, *w1 ; + lx [0] = Lx [p0 ] ; + lx [1] = Lx [p0+1] ; + w0 = W + WDIM * Li [p0 ] ; + w1 = W + WDIM * Li [p0+1] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) \ + w0 [k] -= Z0 [k] * lx [0] ; \ + w1 [k] -= Z0 [k] * lx [1] ; \ + lx [0] -= G0 [k] * w0 [k] ; \ + lx [1] -= G0 [k] * w1 [k] ; + FOR_ALL_K + #undef DO + + Lx [p0++] = lx [0] ; + Lx [p0++] = lx [1] ; + } + break ; + + case 3: + { + double lx [3], *w0, *w1, *w2 ; + lx [0] = Lx [p0 ] ; + lx [1] = Lx [p0+1] ; + lx [2] = Lx [p0+2] ; + w0 = W + WDIM * Li [p0 ] ; + w1 = W + WDIM * Li [p0+1] ; + w2 = W + WDIM * Li [p0+2] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) \ + w0 [k] -= Z0 [k] * lx [0] ; \ + w1 [k] -= Z0 [k] * lx [1] ; \ + w2 [k] -= Z0 [k] * lx [2] ; \ + lx [0] -= G0 [k] * w0 [k] ; \ + lx [1] -= G0 [k] * w1 [k] ; \ + lx [2] -= G0 [k] * w2 [k] ; + FOR_ALL_K + #undef DO + + Lx [p0++] = lx [0] ; + Lx [p0++] = lx [1] ; + Lx [p0++] = lx [2] ; + } + } + + for ( ; p0 < pend ; p0 += 4) + { + double lx [4], *w0, *w1, *w2, *w3 ; + lx [0] = Lx [p0 ] ; + lx [1] = Lx [p0+1] ; + lx [2] = Lx [p0+2] ; + lx [3] = Lx [p0+3] ; + w0 = W + WDIM * Li [p0 ] ; + w1 = W + WDIM * Li [p0+1] ; + w2 = W + WDIM * Li [p0+2] ; + w3 = W + WDIM * Li [p0+3] ; + + /* for k = 0 to RANK-1 do: */ + #define DO(k) \ + w0 [k] -= Z0 [k] * lx [0] ; \ + w1 [k] -= Z0 [k] * lx [1] ; \ + w2 [k] -= Z0 [k] * lx [2] ; \ + w3 [k] -= Z0 [k] * lx [3] ; \ + lx [0] -= G0 [k] * w0 [k] ; \ + lx [1] -= G0 [k] * w1 [k] ; \ + lx [2] -= G0 [k] * w2 [k] ; \ + lx [3] -= G0 [k] * w3 [k] ; + FOR_ALL_K + #undef DO + + Lx [p0 ] = lx [0] ; + Lx [p0+1] = lx [1] ; + Lx [p0+2] = lx [2] ; + Lx [p0+3] = lx [3] ; + } + } + } +#endif +} +/* prepare this file for another inclusion in t_cholmod_updown.c: */ +#undef RANK diff --git a/src/CHOLMOD/Partition/License.txt b/src/CHOLMOD/Partition/License.txt new file mode 100644 index 0000000..4beeb59 --- /dev/null +++ b/src/CHOLMOD/Partition/License.txt @@ -0,0 +1,25 @@ +CHOLMOD/Partition Module. +Copyright (C) 2005-2006, Univ. of Florida. Author: Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + +Note that this license is for the CHOLMOD/Partition module only. +All CHOLMOD modules are licensed separately. + + +-------------------------------------------------------------------------------- + + +This Module is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This Module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this Module; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/src/CHOLMOD/Partition/cholmod_camd.c b/src/CHOLMOD/Partition/cholmod_camd.c new file mode 100644 index 0000000..47b0368 --- /dev/null +++ b/src/CHOLMOD/Partition/cholmod_camd.c @@ -0,0 +1,231 @@ +/* ========================================================================== */ +/* === Partition/cholmod_camd =============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Partition Module. Copyright (C) 2005-2013, Timothy A. Davis + * The CHOLMOD/Partition Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* CHOLMOD interface to the CAMD ordering routine. Orders A if the matrix is + * symmetric. On output, Perm [k] = i if row/column i of A is the kth + * row/column of P*A*P'. This corresponds to A(p,p) in MATLAB notation. + * + * If A is unsymmetric, cholmod_camd orders A*A'. On output, Perm [k] = i if + * row/column i of A*A' is the kth row/column of P*A*A'*P'. This corresponds to + * A(p,:)*A(p,:)' in MATLAB notation. If f is present, A(p,f)*A(p,f)' is + * ordered. + * + * Computes the flop count for a subsequent LL' factorization, the number + * of nonzeros in L, and the number of nonzeros in the matrix ordered (A, + * A*A' or A(:,f)*A(:,f)'). + * + * workspace: Iwork (4*nrow). Head (nrow). + * + * Allocates a temporary copy of A+A' or A*A' (with + * both upper and lower triangular parts) as input to CAMD. + * Also allocates 3*(n+1) additional integer workspace (not in Common). + * + * Supports any xtype (pattern, real, complex, or zomplex) + */ + +static int igraph_stfu2(); +static int igraph_stfu1() { return igraph_stfu2(); } +static int igraph_stfu2() { return igraph_stfu1(); } + +#ifndef NCAMD + +#include "cholmod_internal.h" +#include "camd.h" +#include "cholmod_camd.h" + +#if (CAMD_VERSION < CAMD_VERSION_CODE (2,0)) +#error "CAMD v2.0 or later is required" +#endif + +/* ========================================================================== */ +/* === cholmod_camd ========================================================= */ +/* ========================================================================== */ + +int CHOLMOD(camd) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + Int *Cmember, /* size nrow. see cholmod_ccolamd.c for description.*/ + /* ---- output ---- */ + Int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) +{ + double Info [CAMD_INFO], Control2 [CAMD_CONTROL], *Control ; + Int *Cp, *Len, *Nv, *Head, *Elen, *Degree, *Wi, *Next, *BucketSet, + *Work3n, *p ; + cholmod_sparse *C ; + Int j, n, cnz ; + size_t s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + n = A->nrow ; + + /* s = 4*n */ + s = CHOLMOD(mult_size_t) (n, 4, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + RETURN_IF_NULL (Perm, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + Common->status = CHOLMOD_OK ; + if (n == 0) + { + /* nothing to do */ + Common->fl = 0 ; + Common->lnz = 0 ; + Common->anz = 0 ; + return (TRUE) ; + } + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + /* cholmod_analyze has allocated Cmember at Common->Iwork + 5*n+uncol, and + * CParent at Common->Iwork + 4*n+uncol, where uncol is 0 if A is symmetric + * or A->ncol otherwise. Thus, only the first 4n integers in Common->Iwork + * can be used here. */ + + CHOLMOD(allocate_work) (n, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + + p = Common->Iwork ; + Degree = p ; p += n ; /* size n */ + Elen = p ; p += n ; /* size n */ + Len = p ; p += n ; /* size n */ + Nv = p ; p += n ; /* size n */ + + Work3n = CHOLMOD(malloc) (n+1, 3*sizeof (Int), Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + p = Work3n ; + Next = p ; p += n ; /* size n */ + Wi = p ; p += (n+1) ; /* size n+1 */ + BucketSet = p ; /* size n */ + + Head = Common->Head ; /* size n+1 */ + + /* ---------------------------------------------------------------------- */ + /* construct the input matrix for CAMD */ + /* ---------------------------------------------------------------------- */ + + if (A->stype == 0) + { + /* C = A*A' or A(:,f)*A(:,f)', add extra space of nnz(C)/2+n to C */ + C = CHOLMOD(aat) (A, fset, fsize, -2, Common) ; + } + else + { + /* C = A+A', but use only the upper triangular part of A if A->stype = 1 + * and only the lower part of A if A->stype = -1. Add extra space of + * nnz(C)/2+n to C. */ + C = CHOLMOD(copy) (A, 0, -2, Common) ; + } + + if (Common->status < CHOLMOD_OK) + { + /* out of memory, fset invalid, or other error */ + CHOLMOD(free) (n+1, 3*sizeof (Int), Work3n, Common) ; + return (FALSE) ; + } + + Cp = C->p ; + for (j = 0 ; j < n ; j++) + { + Len [j] = Cp [j+1] - Cp [j] ; + } + + /* C does not include the diagonal, and both upper and lower parts. + * Common->anz includes the diagonal, and just the lower part of C */ + cnz = Cp [n] ; + Common->anz = cnz / 2 + n ; + + /* ---------------------------------------------------------------------- */ + /* order C using CAMD */ + /* ---------------------------------------------------------------------- */ + + /* get parameters */ + if (Common->current < 0 || Common->current >= CHOLMOD_MAXMETHODS) + { + /* use CAMD defaults */ + Control = NULL ; + } + else + { + Control = Control2 ; + Control [CAMD_DENSE] = Common->method [Common->current].prune_dense ; + Control [CAMD_AGGRESSIVE] = Common->method [Common->current].aggressive; + } + + /* CAMD_2 does not use camd_malloc and camd_free, but set these pointers + * just be safe. */ + camd_malloc = Common->malloc_memory ; + camd_free = Common->free_memory ; + camd_calloc = Common->calloc_memory ; + camd_realloc = Common->realloc_memory ; + + /* CAMD_2 doesn't print anything either, but future versions might, + * so set the camd_printf pointer too. */ + camd_printf = Common->print_function ; + +#ifdef LONG + /* DEBUG (camd_l_debug_init ("cholmod_l_camd")) ; */ + camd_l2 (n, C->p, C->i, Len, C->nzmax, cnz, Nv, Next, Perm, Head, Elen, + Degree, Wi, Control, Info, Cmember, BucketSet) ; +#else + /* DEBUG (camd_debug_init ("cholmod_camd")) ; */ + camd_2 (n, C->p, C->i, Len, C->nzmax, cnz, Nv, Next, Perm, Head, Elen, + Degree, Wi, Control, Info, Cmember, BucketSet) ; +#endif + + /* LL' flop count. Need to subtract n for LL' flop count. Note that this + * is a slight upper bound which is often exact (see CAMD/Source/camd_2.c + * for details). cholmod_analyze computes an exact flop count and + * fill-in. */ + Common->fl = Info [CAMD_NDIV] + 2 * Info [CAMD_NMULTSUBS_LDL] + n ; + + /* Info [CAMD_LNZ] excludes the diagonal */ + Common->lnz = n + Info [CAMD_LNZ] ; + + /* ---------------------------------------------------------------------- */ + /* free the CAMD workspace and clear the persistent workspace in Common */ + /* ---------------------------------------------------------------------- */ + + ASSERT (IMPLIES (Common->status == CHOLMOD_OK, + CHOLMOD(dump_perm) (Perm, n, n, "CAMD2 perm", Common))) ; + CHOLMOD(free_sparse) (&C, Common) ; + for (j = 0 ; j <= n ; j++) + { + Head [j] = EMPTY ; + } + CHOLMOD(free) (n+1, 3*sizeof (Int), Work3n, Common) ; + return (TRUE) ; +} +#endif diff --git a/src/CHOLMOD/Partition/cholmod_ccolamd.c b/src/CHOLMOD/Partition/cholmod_ccolamd.c new file mode 100644 index 0000000..284a3a8 --- /dev/null +++ b/src/CHOLMOD/Partition/cholmod_ccolamd.c @@ -0,0 +1,208 @@ +/* ========================================================================== */ +/* === Partition/cholmod_ccolamd ============================================ */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Partition Module. + * Copyright (C) 2005-2013, Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Partition Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD interface to the CCOLAMD ordering routine. Finds a permutation + * p such that the Cholesky factorization of PAA'P' is sparser than AA'. + * The column etree is found and postordered, and the ccolamd ordering is then + * combined with its postordering. A must be unsymmetric. + * + * workspace: Iwork (MAX (nrow,ncol)) + * Allocates a copy of its input matrix, which is + * then used as CCOLAMD's workspace. + * + * Supports any xtype (pattern, real, complex, or zomplex). + */ + +static int igraph_stfu2(); +static int igraph_stfu1() { return igraph_stfu2(); } +static int igraph_stfu2() { return igraph_stfu1(); } + +#ifndef NCAMD + +#include "cholmod_internal.h" +#include "ccolamd.h" +#include "cholmod_camd.h" + +#if (CCOLAMD_VERSION < CCOLAMD_VERSION_CODE (2,5)) +#error "CCOLAMD v2.0 or later is required" +#endif + +/* ========================================================================== */ +/* === ccolamd_interface ==================================================== */ +/* ========================================================================== */ + +/* Order with ccolamd */ + +static int ccolamd_interface +( + cholmod_sparse *A, + size_t alen, + Int *Perm, + Int *Cmember, + Int *fset, + Int fsize, + cholmod_sparse *C, + cholmod_common *Common +) +{ + double knobs [CCOLAMD_KNOBS] ; + Int *Cp = NULL ; + Int ok, k, nrow, ncol, stats [CCOLAMD_STATS] ; + + nrow = A->nrow ; + ncol = A->ncol ; + + /* ---------------------------------------------------------------------- */ + /* copy (and transpose) the input matrix A into the ccolamd workspace */ + /* ---------------------------------------------------------------------- */ + + /* C = A (:,f)', which also packs A if needed. */ + /* workspace: Iwork (nrow if no fset; MAX (nrow,ncol) if fset non-NULL) */ + ok = CHOLMOD(transpose_unsym) (A, 0, NULL, fset, fsize, C, Common) ; + + /* ---------------------------------------------------------------------- */ + /* order the matrix (destroys the contents of C->i and C->p) */ + /* ---------------------------------------------------------------------- */ + + /* get parameters */ +#ifdef LONG + ccolamd_l_set_defaults (knobs) ; +#else + ccolamd_set_defaults (knobs) ; +#endif + + if (Common->current < 0 || Common->current >= CHOLMOD_MAXMETHODS) + { + /* this is the CHOLMOD default, not the CCOLAMD default */ + knobs [CCOLAMD_DENSE_ROW] = -1 ; + } + else + { + /* get the knobs from the Common parameters */ + knobs [CCOLAMD_DENSE_COL] =Common->method[Common->current].prune_dense ; + knobs [CCOLAMD_DENSE_ROW] =Common->method[Common->current].prune_dense2; + knobs [CCOLAMD_AGGRESSIVE]=Common->method[Common->current].aggressive ; + knobs [CCOLAMD_LU] =Common->method[Common->current].order_for_lu; + } + + if (ok) + { + +#ifdef LONG + ccolamd_l (ncol, nrow, alen, C->i, C->p, knobs, stats, Cmember) ; +#else + ccolamd (ncol, nrow, alen, C->i, C->p, knobs, stats, Cmember) ; +#endif + + ok = stats [CCOLAMD_STATUS] ; + + ok = (ok == CCOLAMD_OK || ok == CCOLAMD_OK_BUT_JUMBLED) ; + /* permutation returned in C->p, if the ordering succeeded */ + Cp = C->p ; + for (k = 0 ; k < nrow ; k++) + { + Perm [k] = Cp [k] ; + } + } + + return (ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_ccolamd ====================================================== */ +/* ========================================================================== */ + +/* Order AA' or A(:,f)*A(:,f)' using CCOLAMD. */ + +int CHOLMOD(ccolamd) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + Int *Cmember, /* size A->nrow. Cmember [i] = c if row i is in the + * constraint set c. c must be >= 0. The # of + * constraint sets is max (Cmember) + 1. If Cmember is + * NULL, then it is interpretted as Cmember [i] = 0 for + * all i */ + /* ---- output --- */ + Int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_sparse *C ; + Int ok, nrow, ncol ; + size_t alen ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (Perm, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + if (A->stype != 0) + { + ERROR (CHOLMOD_INVALID, "matrix must be unsymmetric") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nrow = A->nrow ; + ncol = A->ncol ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + +#ifdef LONG + alen = ccolamd_l_recommended (A->nzmax, ncol, nrow) ; +#else + alen = ccolamd_recommended (A->nzmax, ncol, nrow) ; +#endif + + if (alen == 0) + { + ERROR (CHOLMOD_TOO_LARGE, "matrix invalid or too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (0, MAX (nrow,ncol), 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + + C = CHOLMOD(allocate_sparse) (ncol, nrow, alen, TRUE, TRUE, 0, + CHOLMOD_PATTERN, Common) ; + + /* ---------------------------------------------------------------------- */ + /* order with ccolamd */ + /* ---------------------------------------------------------------------- */ + + ok = ccolamd_interface (A, alen, Perm, Cmember, fset, fsize, C, Common) ; + + /* ---------------------------------------------------------------------- */ + /* free the workspace and return result */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(free_sparse) (&C, Common) ; + return (ok) ; +} +#endif diff --git a/src/CHOLMOD/Partition/cholmod_csymamd.c b/src/CHOLMOD/Partition/cholmod_csymamd.c new file mode 100644 index 0000000..b3f1303 --- /dev/null +++ b/src/CHOLMOD/Partition/cholmod_csymamd.c @@ -0,0 +1,144 @@ +/* ========================================================================== */ +/* === Partition/cholmod_csymamd ============================================ */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Partition Module. + * Copyright (C) 2005-2013, Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Partition Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD interface to the CSYMAMD ordering routine. Finds a permutation + * p such that the Cholesky factorization of PAP' is sparser than A. + * The column etree is found and postordered, and the CSYMAMD + * ordering is then combined with its postordering. If A is unsymmetric, + * A+A' is ordered (A must be square). + * + * workspace: Head (nrow+1) + * + * Supports any xtype (pattern, real, complex, or zomplex). + */ + +static int igraph_stfu2(); +static int igraph_stfu1() { return igraph_stfu2(); } +static int igraph_stfu2() { return igraph_stfu1(); } + +#ifndef NCAMD + +#include "cholmod_internal.h" +#include "ccolamd.h" +#include "cholmod_camd.h" + +#if (CCOLAMD_VERSION < CCOLAMD_VERSION_CODE (2,5)) +#error "CCOLAMD v2.0 or later is required" +#endif + +/* ========================================================================== */ +/* === cholmod_csymamd ====================================================== */ +/* ========================================================================== */ + +int CHOLMOD(csymamd) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + /* ---- output --- */ + Int *Cmember, /* size nrow. see cholmod_ccolamd.c for description */ + Int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) +{ + double knobs [CCOLAMD_KNOBS] ; + Int *perm, *Head ; + Int ok, i, nrow, stats [CCOLAMD_STATS] ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (Perm, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + Common->status = CHOLMOD_OK ; + + if (A->nrow != A->ncol || !(A->packed)) + { + ERROR (CHOLMOD_INVALID, "matrix must be square and packed") ; + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nrow = A->nrow ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + CHOLMOD(allocate_work) (nrow, 0, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* order the matrix (does not affect A->p or A->i) */ + /* ---------------------------------------------------------------------- */ + + perm = Common->Head ; /* size nrow+1 (i/l/l) */ + + /* get parameters */ +#ifdef LONG + ccolamd_l_set_defaults (knobs) ; +#else + ccolamd_set_defaults (knobs) ; +#endif + if (Common->current >= 0 && Common->current < CHOLMOD_MAXMETHODS) + { + /* get the knobs from the Common parameters */ + knobs [CCOLAMD_DENSE_ROW] =Common->method[Common->current].prune_dense ; + knobs [CCOLAMD_AGGRESSIVE]=Common->method[Common->current].aggressive ; + } + + { +#ifdef LONG + csymamd_l (nrow, A->i, A->p, perm, knobs, stats, Common->calloc_memory, + Common->free_memory, Cmember, A->stype) ; +#else + csymamd (nrow, A->i, A->p, perm, knobs, stats, Common->calloc_memory, + Common->free_memory, Cmember, A->stype) ; +#endif + ok = stats [CCOLAMD_STATUS] ; + } + + if (ok == CCOLAMD_ERROR_out_of_memory) + { + ERROR (CHOLMOD_OUT_OF_MEMORY, "out of memory") ; + } + ok = (ok == CCOLAMD_OK || ok == CCOLAMD_OK_BUT_JUMBLED) ; + + /* ---------------------------------------------------------------------- */ + /* free the workspace and return result */ + /* ---------------------------------------------------------------------- */ + + /* permutation returned in perm [0..n-1] */ + for (i = 0 ; i < nrow ; i++) + { + Perm [i] = perm [i] ; + } + + /* clear Head workspace (used for perm, in csymamd): */ + Head = Common->Head ; + for (i = 0 ; i <= nrow ; i++) + { + Head [i] = EMPTY ; + } + + return (ok) ; +} +#endif diff --git a/src/CHOLMOD/Partition/cholmod_metis.c b/src/CHOLMOD/Partition/cholmod_metis.c new file mode 100644 index 0000000..a4a0524 --- /dev/null +++ b/src/CHOLMOD/Partition/cholmod_metis.c @@ -0,0 +1,795 @@ +/* ========================================================================== */ +/* === Partition/cholmod_metis ============================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Partition Module. + * Copyright (C) 2005-2006, Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Partition Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD interface to the METIS package (Version 4.0.1): + * + * cholmod_metis_bisector: + * + * Wrapper for METIS_NodeComputeSeparator. Finds a set of nodes that + * partitions the graph into two parts. + * + * cholmod_metis: + * + * Wrapper for METIS_NodeND, METIS's own nested dissection algorithm. + * Typically faster than cholmod_nested_dissection, mostly because it + * uses minimum degree on just the leaves of the separator tree, rather + * than the whole matrix. + * + * Note that METIS does not return an error if it runs out of memory. Instead, + * it terminates the program. This interface attempts to avoid that problem + * by preallocating space that should be large enough for any memory allocations + * within METIS, and then freeing that space, just before the call to METIS. + * While this is not guaranteed to work, it is very unlikely to fail. If you + * encounter this problem, increase Common->metis_memory. If you don't mind + * having your program terminated, set Common->metis_memory to zero (a value of + * 2.0 is usually safe). Several other METIS workarounds are made in the + * routines in this file. See the description of metis_memory_ok, just below, + * for more details. + * + * FUTURE WORK: interfaces to other partitioners (CHACO, SCOTCH, JOSTLE, ... ) + * + * workspace: several size-nz and size-n temporary arrays. Uses no workspace + * in Common. + * + * Supports any xtype (pattern, real, complex, or zomplex). + */ + +static int igraph_stfu2(); +static int igraph_stfu1() { return igraph_stfu2(); } +static int igraph_stfu2() { return igraph_stfu1(); } + +#ifndef NPARTITION + +#include "cholmod_internal.h" +#undef ASSERT + +#include "metis.h" +/* METIS has its own ASSERT that it reveals to the user, so remove it here: */ +#undef ASSERT + +/* and redefine it back again */ +#ifndef NDEBUG +#define ASSERT(expression) (assert (expression)) +#else +#define ASSERT(expression) +#endif + +#include "cholmod_partition.h" +#include "cholmod_cholesky.h" + + +/* ========================================================================== */ +/* === dumpgraph ============================================================ */ +/* ========================================================================== */ + +/* For dumping the input graph to METIS_NodeND, to check with METIS's onmetis + * and graphchk programs. For debugging only. To use, uncomment this #define: +#define DUMP_GRAPH + */ + +#ifdef DUMP_GRAPH +#include +/* After dumping the graph with this routine, run "onmetis metisgraph" */ +static void dumpgraph (idxtype *Mp, idxtype *Mi, SuiteSparse_long n, + cholmod_common *Common) +{ + SuiteSparse_long i, j, p, nz ; + FILE *f ; + nz = Mp [n] ; + printf ("Dumping METIS graph n %ld nz %ld\n", n, nz) ; /* DUMP_GRAPH */ + f = fopen ("metisgraph", "w") ; + if (f == NULL) + { + ERROR (-99, "cannot open metisgraph") ; + return ; + } + fprintf (f, "%ld %ld\n", n, nz/2) ; /* DUMP_GRAPH */ + for (j = 0 ; j < n ; j++) + { + for (p = Mp [j] ; p < Mp [j+1] ; p++) + { + i = Mi [p] ; + fprintf (f, " %ld", i+1) ; /* DUMP_GRAPH */ + } + fprintf (f, "\n") ; /* DUMP_GRAPH */ + } + fclose (f) ; +} +#endif + + +/* ========================================================================== */ +/* === metis_memory_ok ====================================================== */ +/* ========================================================================== */ + +/* METIS_NodeND and METIS_NodeComputeSeparator will terminate your program it + * they run out of memory. In an attempt to workaround METIS' behavior, this + * routine allocates a single block of memory of size equal to an observed + * upper bound on METIS' memory usage. It then immediately deallocates the + * block. If the allocation fails, METIS is not called. + * + * Median memory usage for a graph with n nodes and nz edges (counting each + * edge twice, or both upper and lower triangular parts of a matrix) is + * 4*nz + 40*n + 4096 integers. A "typical" upper bound is 10*nz + 50*n + 4096 + * integers. Nearly all matrices tested fit within that upper bound, with the + * exception two in the UF sparse matrix collection: Schenk_IBMNA/c-64 and + * Gupta/gupta2. The latter exceeds the "upper bound" by a factor of just less + * than 2. + * + * If you do not mind having your program terminated if it runs out of memory, + * set Common->metis_memory to zero. Its default value is 2, which allows for + * some memory fragmentation, and also accounts for the Gupta/gupta2 matrix. + * + * An alternative, if CHOLMOD is used in MATLAB, is to use a version of METIS + * (4.0.2, perhaps) proposed to George Karypis. This version uses function + * pointer for malloc and free. They can be set to mxMalloc and mxFree + * (see sputil_config in MATLAB/sputil.c). On Linux, with gcc, you must also + * compile CHOLMOD, METIS, AMD, COLAMD, and CCOLAMD with the -fexceptions + * compiler flag. With this configuration, mxMalloc safely aborts the + * mexFunction, frees all memory allocted by METIS, and safely returns to + * MATLAB. You may then set Common->metis_memory = 0. + */ + +#define GUESS(nz,n) (10 * (nz) + 50 * (n) + 4096) + +static int metis_memory_ok +( + Int n, + Int nz, + cholmod_common *Common +) +{ + double s ; + void *p ; + size_t metis_guard ; + + if (Common->metis_memory <= 0) + { + /* do not prevent METIS from running out of memory */ + return (TRUE) ; + } + + n = MAX (1, n) ; + nz = MAX (0, nz) ; + + /* compute in double, to avoid integer overflow */ + s = GUESS ((double) nz, (double) n) ; + s *= Common->metis_memory ; + + if (s * sizeof (idxtype) >= ((double) Size_max)) + { + /* don't even attempt to malloc such a large block */ + return (FALSE) ; + } + + /* recompute in size_t */ + metis_guard = GUESS ((size_t) nz, (size_t) n) ; + metis_guard *= Common->metis_memory ; + + /* attempt to malloc the block */ + p = CHOLMOD(malloc) (metis_guard, sizeof (idxtype), Common) ; + if (p == NULL) + { + /* failure - return out-of-memory condition */ + return (FALSE) ; + } + + /* success - free the block */ + CHOLMOD(free) (metis_guard, sizeof (idxtype), p, Common) ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === cholmod_metis_bisector =============================================== */ +/* ========================================================================== */ + +/* Finds a set of nodes that bisects the graph of A or AA' (direct interface + * to METIS_NodeComputeSeparator). + * + * The input matrix A must be square, symmetric (with both upper and lower + * parts present) and with no diagonal entries. These conditions are NOT + * checked. + */ + +SuiteSparse_long CHOLMOD(metis_bisector) /* returns separator size */ +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to bisect */ + Int *Anw, /* size A->nrow, node weights */ + Int *Aew, /* size nz, edge weights */ + /* ---- output --- */ + Int *Partition, /* size A->nrow */ + /* --------------- */ + cholmod_common *Common +) +{ + Int *Ap, *Ai ; + idxtype *Mp, *Mi, *Mnw, *Mew, *Mpart ; + Int n, nleft, nright, j, p, csep, total_weight, lightest, nz ; + int Opt [8], nn, csp ; + size_t n1 ; + DEBUG (Int nsep) ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (A, EMPTY) ; + RETURN_IF_NULL (Anw, EMPTY) ; + RETURN_IF_NULL (Aew, EMPTY) ; + RETURN_IF_NULL (Partition, EMPTY) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ; + if (A->stype || A->nrow != A->ncol) + { + /* A must be square, with both upper and lower parts present */ + ERROR (CHOLMOD_INVALID, "matrix must be square, symmetric," + " and with both upper/lower parts present") ; + return (EMPTY) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* quick return */ + /* ---------------------------------------------------------------------- */ + + n = A->nrow ; + if (n == 0) + { + return (0) ; + } + n1 = ((size_t) n) + 1 ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Ap = A->p ; + Ai = A->i ; + nz = Ap [n] ; + + /* ---------------------------------------------------------------------- */ + /* METIS does not have a 64-bit integer version */ + /* ---------------------------------------------------------------------- */ + +#ifdef LONG + if (sizeof (Int) > sizeof (idxtype) && MAX (n,nz) > INT_MAX / sizeof (int)) + { + /* CHOLMOD's matrix is too large for METIS */ + return (EMPTY) ; + } +#endif + + /* ---------------------------------------------------------------------- */ + /* set default options */ + /* ---------------------------------------------------------------------- */ + + Opt [0] = 0 ; /* use defaults */ + Opt [1] = 3 ; /* matching type */ + Opt [2] = 1 ; /* init. partitioning algo*/ + Opt [3] = 2 ; /* refinement algorithm */ + Opt [4] = 0 ; /* no debug */ + Opt [5] = 0 ; /* unused */ + Opt [6] = 0 ; /* unused */ + Opt [7] = -1 ; /* random seed */ + + DEBUG (for (j = 0 ; j < n ; j++) ASSERT (Anw [j] > 0)) ; + + /* ---------------------------------------------------------------------- */ + /* copy Int to METIS idxtype, if necessary */ + /* ---------------------------------------------------------------------- */ + + DEBUG (for (j = 0 ; j < nz ; j++) ASSERT (Aew [j] > 0)) ; + if (sizeof (Int) == sizeof (idxtype)) + { + /* this is the typical case */ + Mi = (idxtype *) Ai ; + Mew = (idxtype *) Aew ; + Mp = (idxtype *) Ap ; + Mnw = (idxtype *) Anw ; + Mpart = (idxtype *) Partition ; + } + else + { + /* idxtype and Int differ; copy the graph into the METIS idxtype */ + Mi = CHOLMOD(malloc) (nz, sizeof (idxtype), Common) ; + Mew = CHOLMOD(malloc) (nz, sizeof (idxtype), Common) ; + Mp = CHOLMOD(malloc) (n1, sizeof (idxtype), Common) ; + Mnw = CHOLMOD(malloc) (n, sizeof (idxtype), Common) ; + Mpart = CHOLMOD(malloc) (n, sizeof (idxtype), Common) ; + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free) (nz, sizeof (idxtype), Mi, Common) ; + CHOLMOD(free) (nz, sizeof (idxtype), Mew, Common) ; + CHOLMOD(free) (n1, sizeof (idxtype), Mp, Common) ; + CHOLMOD(free) (n, sizeof (idxtype), Mnw, Common) ; + CHOLMOD(free) (n, sizeof (idxtype), Mpart, Common) ; + return (EMPTY) ; + } + for (p = 0 ; p < nz ; p++) + { + Mi [p] = Ai [p] ; + } + for (p = 0 ; p < nz ; p++) + { + Mew [p] = Aew [p] ; + } + for (j = 0 ; j <= n ; j++) + { + Mp [j] = Ap [j] ; + } + for (j = 0 ; j < n ; j++) + { + Mnw [j] = Anw [j] ; + } + } + + /* ---------------------------------------------------------------------- */ + /* METIS workaround: try to ensure METIS doesn't run out of memory */ + /* ---------------------------------------------------------------------- */ + + if (!metis_memory_ok (n, nz, Common)) + { + /* METIS might ask for too much memory and thus terminate the program */ + if (sizeof (Int) != sizeof (idxtype)) + { + CHOLMOD(free) (nz, sizeof (idxtype), Mi, Common) ; + CHOLMOD(free) (nz, sizeof (idxtype), Mew, Common) ; + CHOLMOD(free) (n1, sizeof (idxtype), Mp, Common) ; + CHOLMOD(free) (n, sizeof (idxtype), Mnw, Common) ; + CHOLMOD(free) (n, sizeof (idxtype), Mpart, Common) ; + } + return (EMPTY) ; + } + + /* ---------------------------------------------------------------------- */ + /* partition the graph */ + /* ---------------------------------------------------------------------- */ + +#ifndef NDEBUG + PRINT1 (("Metis graph, n = "ID"\n", n)) ; + for (j = 0 ; j < n ; j++) + { + Int ppp ; + PRINT2 (("M(:,"ID") node weight "ID"\n", j, (Int) Mnw [j])) ; + ASSERT (Mnw [j] > 0) ; + for (ppp = Mp [j] ; ppp < Mp [j+1] ; ppp++) + { + PRINT3 ((" "ID" : "ID"\n", (Int) Mi [ppp], (Int) Mew [ppp])) ; + ASSERT (Mi [ppp] != j) ; + ASSERT (Mew [ppp] > 0) ; + } + } +#endif + + nn = n ; + METIS_NodeComputeSeparator (&nn, Mp, Mi, Mnw, Mew, Opt, &csp, Mpart) ; + n = nn ; + csep = csp ; + + PRINT1 (("METIS csep "ID"\n", csep)) ; + + /* ---------------------------------------------------------------------- */ + /* copy the results back from idxtype, if required */ + /* ---------------------------------------------------------------------- */ + + if (sizeof (Int) != sizeof (idxtype)) + { + for (j = 0 ; j < n ; j++) + { + Partition [j] = Mpart [j] ; + } + CHOLMOD(free) (nz, sizeof (idxtype), Mi, Common) ; + CHOLMOD(free) (nz, sizeof (idxtype), Mew, Common) ; + CHOLMOD(free) (n1, sizeof (idxtype), Mp, Common) ; + CHOLMOD(free) (n, sizeof (idxtype), Mnw, Common) ; + CHOLMOD(free) (n, sizeof (idxtype), Mpart, Common) ; + } + + /* ---------------------------------------------------------------------- */ + /* ensure a reasonable separator */ + /* ---------------------------------------------------------------------- */ + + /* METIS can return a valid separator with no nodes in (for example) the + * left part. In this case, there really is no separator. CHOLMOD + * prefers, in this case, for all nodes to be in the separator (and both + * left and right parts to be empty). Also, if the graph is unconnected, + * METIS can return a valid empty separator. CHOLMOD prefers at least one + * node in the separator. Note that cholmod_nested_dissection only calls + * this routine on connected components, but cholmod_bisect can call this + * routine for any graph. */ + + if (csep == 0) + { + /* The separator is empty, select lightest node as separator. If + * ties, select the highest numbered node. */ + lightest = 0 ; + for (j = 0 ; j < n ; j++) + { + if (Anw [j] <= Anw [lightest]) + { + lightest = j ; + } + } + PRINT1 (("Force "ID" as sep\n", lightest)) ; + Partition [lightest] = 2 ; + csep = Anw [lightest] ; + } + + /* determine the node weights in the left and right part of the graph */ + nleft = 0 ; + nright = 0 ; + DEBUG (nsep = 0) ; + for (j = 0 ; j < n ; j++) + { + PRINT1 (("Partition ["ID"] = "ID"\n", j, Partition [j])) ; + if (Partition [j] == 0) + { + nleft += Anw [j] ; + } + else if (Partition [j] == 1) + { + nright += Anw [j] ; + } +#ifndef NDEBUG + else + { + ASSERT (Partition [j] == 2) ; + nsep += Anw [j] ; + } +#endif + } + ASSERT (csep == nsep) ; + + total_weight = nleft + nright + csep ; + + if (csep < total_weight) + { + /* The separator is less than the whole graph. Make sure the left and + * right parts are either both empty or both non-empty. */ + PRINT1 (("nleft "ID" nright "ID" csep "ID" tot "ID"\n", + nleft, nright, csep, total_weight)) ; + ASSERT (nleft + nright + csep == total_weight) ; + ASSERT (nleft > 0 || nright > 0) ; + if ((nleft == 0 && nright > 0) || (nleft > 0 && nright == 0)) + { + /* left or right is empty; put all nodes in the separator */ + PRINT1 (("Force all in sep\n")) ; + csep = total_weight ; + for (j = 0 ; j < n ; j++) + { + Partition [j] = 2 ; + } + } + } + + ASSERT (CHOLMOD(dump_partition) (n, Ap, Ai, Anw, Partition, csep, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* return the sum of the weights of nodes in the separator */ + /* ---------------------------------------------------------------------- */ + + return (csep) ; +} + + +/* ========================================================================== */ +/* === cholmod_metis ======================================================== */ +/* ========================================================================== */ + +/* CHOLMOD wrapper for the METIS_NodeND ordering routine. Creates A+A', + * A*A' or A(:,f)*A(:,f)' and then calls METIS_NodeND on the resulting graph. + * This routine is comparable to cholmod_nested_dissection, except that it + * calls METIS_NodeND directly, and it does not return the separator tree. + * + * workspace: Flag (nrow), Iwork (4*n+uncol) + * Allocates a temporary matrix B=A*A' or B=A. + */ + +int CHOLMOD(metis) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int postorder, /* if TRUE, follow with etree or coletree postorder */ + /* ---- output --- */ + Int *Perm, /* size A->nrow, output permutation */ + /* --------------- */ + cholmod_common *Common +) +{ + double d ; + Int *Iperm, *Iwork, *Bp, *Bi ; + idxtype *Mp, *Mi, *Mperm, *Miperm ; + cholmod_sparse *B ; + Int i, j, n, nz, p, identity, uncol ; + int Opt [8], nn, zero = 0 ; + size_t n1, s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (Perm, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* quick return */ + /* ---------------------------------------------------------------------- */ + + n = A->nrow ; + if (n == 0) + { + return (TRUE) ; + } + n1 = ((size_t) n) + 1 ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* s = 4*n + uncol */ + uncol = (A->stype == 0) ? A->ncol : 0 ; + s = CHOLMOD(mult_size_t) (n, 4, &ok) ; + s = CHOLMOD(add_size_t) (s, uncol, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (n, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* convert the matrix to adjacency list form */ + /* ---------------------------------------------------------------------- */ + + /* The input graph for METIS must be symmetric, with both upper and lower + * parts present, and with no diagonal entries. The columns need not be + * sorted. + * B = A+A', A*A', or A(:,f)*A(:,f)', upper and lower parts present */ + if (A->stype) + { + /* Add the upper/lower part to a symmetric lower/upper matrix by + * converting to unsymmetric mode */ + /* workspace: Iwork (nrow) */ + B = CHOLMOD(copy) (A, 0, -1, Common) ; + } + else + { + /* B = A*A' or A(:,f)*A(:,f)', no diagonal */ + /* workspace: Flag (nrow), Iwork (max (nrow,ncol)) */ + B = CHOLMOD(aat) (A, fset, fsize, -1, Common) ; + } + ASSERT (CHOLMOD(dump_sparse) (B, "B for NodeND", Common) >= 0) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + ASSERT (B->nrow == A->nrow) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + Iwork = Common->Iwork ; + Iperm = Iwork ; /* size n (i/i/l) */ + + Bp = B->p ; + Bi = B->i ; + nz = Bp [n] ; + + /* ---------------------------------------------------------------------- */ + /* METIS does not have a SuiteSparse_long integer version */ + /* ---------------------------------------------------------------------- */ + +#ifdef LONG + if (sizeof (Int) > sizeof (idxtype) && MAX (n,nz) > INT_MAX / sizeof (int)) + { + /* CHOLMOD's matrix is too large for METIS */ + CHOLMOD(free_sparse) (&B, Common) ; + return (FALSE) ; + } +#endif + + /* B does not include the diagonal, and both upper and lower parts. + * Common->anz includes the diagonal, and just the lower part of B */ + Common->anz = nz / 2 + n ; + + /* ---------------------------------------------------------------------- */ + /* set control parameters for METIS_NodeND */ + /* ---------------------------------------------------------------------- */ + + Opt [0] = 0 ; /* use defaults */ + Opt [1] = 3 ; /* matching type */ + Opt [2] = 1 ; /* init. partitioning algo*/ + Opt [3] = 2 ; /* refinement algorithm */ + Opt [4] = 0 ; /* no debug */ + Opt [5] = 1 ; /* initial compression */ + Opt [6] = 0 ; /* no dense node removal */ + Opt [7] = 1 ; /* number of separators @ each step */ + + /* ---------------------------------------------------------------------- */ + /* allocate the METIS input arrays, if needed */ + /* ---------------------------------------------------------------------- */ + + if (sizeof (Int) == sizeof (idxtype)) + { + /* This is the typical case. */ + Miperm = (idxtype *) Iperm ; + Mperm = (idxtype *) Perm ; + Mp = (idxtype *) Bp ; + Mi = (idxtype *) Bi ; + } + else + { + /* allocate graph for METIS only if Int and idxtype differ */ + Miperm = CHOLMOD(malloc) (n, sizeof (idxtype), Common) ; + Mperm = CHOLMOD(malloc) (n, sizeof (idxtype), Common) ; + Mp = CHOLMOD(malloc) (n1, sizeof (idxtype), Common) ; + Mi = CHOLMOD(malloc) (nz, sizeof (idxtype), Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + CHOLMOD(free_sparse) (&B, Common) ; + CHOLMOD(free) (n, sizeof (idxtype), Miperm, Common) ; + CHOLMOD(free) (n, sizeof (idxtype), Mperm, Common) ; + CHOLMOD(free) (n1, sizeof (idxtype), Mp, Common) ; + CHOLMOD(free) (nz, sizeof (idxtype), Mi, Common) ; + return (FALSE) ; + } + for (j = 0 ; j <= n ; j++) + { + Mp [j] = Bp [j] ; + } + for (p = 0 ; p < nz ; p++) + { + Mi [p] = Bi [p] ; + } + } + + /* ---------------------------------------------------------------------- */ + /* METIS workarounds */ + /* ---------------------------------------------------------------------- */ + + identity = FALSE ; + if (nz == 0) + { + /* The matrix has no off-diagonal entries. METIS_NodeND fails in this + * case, so avoid using it. The best permutation is identity anyway, + * so this is an easy fix. */ + identity = TRUE ; + PRINT1 (("METIS:: no nz\n")) ; + } + else if (Common->metis_nswitch > 0) + { + /* METIS_NodeND in METIS 4.0.1 gives a seg fault with one matrix of + * order n = 3005 and nz = 6,036,025, including the diagonal entries. + * The workaround is to return the identity permutation instead of using + * METIS for matrices of dimension 3000 or more and with density of 66% + * or more - admittedly an uncertain fix, but such matrices are so dense + * that any reasonable ordering will do, even identity (n^2 is only 50% + * higher than nz in this case). CHOLMOD's nested dissection method + * (cholmod_nested_dissection) has no problems with the same matrix, + * even though it too uses METIS_NodeComputeSeparator. The matrix is + * derived from LPnetlib/lpi_cplex1 in the UF sparse matrix collection. + * If C is the lpi_cplex matrix (of order 3005-by-5224), A = (C*C')^2 + * results in the seg fault. The seg fault also occurs in the stand- + * alone onmetis program that comes with METIS. If a future version of + * METIS fixes this problem, then set Common->metis_nswitch to zero. + */ + d = ((double) nz) / (((double) n) * ((double) n)) ; + if (n > (Int) (Common->metis_nswitch) && d > Common->metis_dswitch) + { + identity = TRUE ; + PRINT1 (("METIS:: nswitch/dswitch activated\n")) ; + } + } + + if (!identity && !metis_memory_ok (n, nz, Common)) + { + /* METIS might ask for too much memory and thus terminate the program */ + identity = TRUE ; + } + + /* ---------------------------------------------------------------------- */ + /* find the permutation */ + /* ---------------------------------------------------------------------- */ + + if (identity) + { + /* no need to do the postorder */ + postorder = FALSE ; + for (i = 0 ; i < n ; i++) + { + Mperm [i] = i ; + } + } + else + { +#ifdef DUMP_GRAPH + /* DUMP_GRAPH */ printf ("Calling METIS_NodeND n "ID" nz "ID"" + "density %g\n", n, nz, ((double) nz) / (((double) n) * ((double) n))); + dumpgraph (Mp, Mi, n, Common) ; +#endif + + nn = n ; + METIS_NodeND (&nn, Mp, Mi, &zero, Opt, Mperm, Miperm) ; + n = nn ; + + PRINT0 (("METIS_NodeND done\n")) ; + } + + /* ---------------------------------------------------------------------- */ + /* free the METIS input arrays */ + /* ---------------------------------------------------------------------- */ + + if (sizeof (Int) != sizeof (idxtype)) + { + for (i = 0 ; i < n ; i++) + { + Perm [i] = (Int) (Mperm [i]) ; + } + CHOLMOD(free) (n, sizeof (idxtype), Miperm, Common) ; + CHOLMOD(free) (n, sizeof (idxtype), Mperm, Common) ; + CHOLMOD(free) (n+1, sizeof (idxtype), Mp, Common) ; + CHOLMOD(free) (nz, sizeof (idxtype), Mi, Common) ; + } + + CHOLMOD(free_sparse) (&B, Common) ; + + /* ---------------------------------------------------------------------- */ + /* etree or column-etree postordering, using the Cholesky Module */ + /* ---------------------------------------------------------------------- */ + + if (postorder) + { + Int *Parent, *Post, *NewPerm ; + Int k ; + + Parent = Iwork + 2*((size_t) n) + uncol ; /* size n = nrow */ + Post = Parent + n ; /* size n */ + + /* workspace: Iwork (2*nrow+uncol), Flag (nrow), Head (nrow+1) */ + CHOLMOD(analyze_ordering) (A, CHOLMOD_METIS, Perm, fset, fsize, + Parent, Post, NULL, NULL, NULL, Common) ; + if (Common->status == CHOLMOD_OK) + { + /* combine the METIS permutation with its postordering */ + NewPerm = Parent ; /* use Parent as workspace */ + for (k = 0 ; k < n ; k++) + { + NewPerm [k] = Perm [Post [k]] ; + } + for (k = 0 ; k < n ; k++) + { + Perm [k] = NewPerm [k] ; + } + } + } + + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + PRINT1 (("cholmod_metis done\n")) ; + return (Common->status == CHOLMOD_OK) ; +} +#endif diff --git a/src/CHOLMOD/Partition/cholmod_nesdis.c b/src/CHOLMOD/Partition/cholmod_nesdis.c new file mode 100644 index 0000000..2e5434e --- /dev/null +++ b/src/CHOLMOD/Partition/cholmod_nesdis.c @@ -0,0 +1,2168 @@ +/* ========================================================================== */ +/* === Partition/cholmod_nesdis ============================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Partition Module. + * Copyright (C) 2005-2006, Univ. of Florida. Author: Timothy A. Davis + * The CHOLMOD/Partition Module is licensed under Version 2.1 of the GNU + * Lesser General Public License. See lesser.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * -------------------------------------------------------------------------- */ + +/* CHOLMOD nested dissection and graph partitioning. + * + * cholmod_bisect: + * + * Finds a set of nodes that partitions the graph into two parts. + * Compresses the graph first. Requires METIS. + * + * cholmod_nested_dissection: + * + * Nested dissection, using its own compression and connected-commponents + * algorithms, an external graph partitioner (METIS), and a constrained + * minimum degree ordering algorithm (CCOLAMD or CSYMAMD). Typically + * gives better orderings than METIS_NodeND (about 5% to 10% fewer + * nonzeros in L). + * + * cholmod_collapse_septree: + * + * Prune the separator tree returned by cholmod_nested_dissection. + * + * This file contains several routines private to this file: + * + * partition compress and partition a graph + * clear_flag clear Common->Flag, but do not modify negative entries + * find_components find the connected components of a graph + * + * Supports any xtype (pattern, real, complex, or zomplex). + */ + +static int igraph_stfu2(); +static int igraph_stfu1() { return igraph_stfu2(); } +static int igraph_stfu2() { return igraph_stfu1(); } + +#ifndef NPARTITION + +#include "cholmod_internal.h" +#include "cholmod_partition.h" +#include "cholmod_cholesky.h" + +/* ========================================================================== */ +/* === partition ============================================================ */ +/* ========================================================================== */ + +/* Find a set of nodes that partition a graph. The graph must be symmetric + * with no diagonal entries. To compress the graph first, compress is TRUE + * and on input Hash [j] holds the hash key for node j, which must be in the + * range 0 to csize-1. The input graph (Cp, Ci) is destroyed. Cew is all 1's + * on input and output. Cnw [j] > 0 is the initial weight of node j. On + * output, Cnw [i] = 0 if node i is absorbed into j and the original weight + * Cnw [i] is added to Cnw [j]. If compress is FALSE, the graph is not + * compressed and Cnw and Hash are unmodified. The partition itself is held in + * the output array Part of size n. Part [j] is 0, 1, or 2, depending on + * whether node j is in the left part of the graph, the right part, or the + * separator, respectively. Note that the input graph need not be connected, + * and the output subgraphs (the three parts) may also be unconnected. + * + * Returns the size of the separator, in terms of the sum of the weights of + * the nodes. It is guaranteed to be between 1 and the total weight of all + * the nodes. If it is of size less than the total weight, then both the left + * and right parts are guaranteed to be non-empty (this guarantee depends on + * cholmod_metis_bisector). + */ + +static SuiteSparse_long partition /* size of separator or -1 if failure */ +( + /* inputs, not modified on output */ +#ifndef NDEBUG + Int csize, /* upper bound on # of edges in the graph; + * csize >= MAX (n, nnz(C)) must hold. */ +#endif + int compress, /* if TRUE the compress the graph first */ + + /* input/output */ + Int Hash [ ], /* Hash [i] = hash >= 0 is the hash function for node + * i on input. On output, Hash [i] = FLIP (j) if node + * i is absorbed into j. Hash [i] >= 0 if i has not + * been absorbed. */ + + /* input graph, compressed graph of cn nodes on output */ + cholmod_sparse *C, + + /* input/output */ + Int Cnw [ ], /* size n. Cnw [j] > 0 is the weight of node j on + * input. On output, if node i is absorbed into + * node j, then Cnw [i] = 0 and the original weight of + * node i is added to Cnw [j]. The sum of Cnw [0..n-1] + * is not modified. */ + + /* workspace */ + Int Cew [ ], /* size csize, all 1's on input and output */ + + /* more workspace, undefined on input and output */ + Int Cmap [ ], /* size n (i/i/l) */ + + /* output */ + Int Part [ ], /* size n, Part [j] = 0, 1, or 2. */ + + cholmod_common *Common +) +{ + Int n, hash, head, i, j, k, p, pend, ilen, ilast, pi, piend, + jlen, ok, cn, csep, pdest, nodes_pruned, nz, total_weight, jscattered ; + Int *Cp, *Ci, *Next, *Hhead ; + +#ifndef NDEBUG + Int cnt, pruned ; + double work = 0, goodwork = 0 ; +#endif + + /* ---------------------------------------------------------------------- */ + /* quick return for small or empty graphs */ + /* ---------------------------------------------------------------------- */ + + n = C->nrow ; + Cp = C->p ; + Ci = C->i ; + nz = Cp [n] ; + + PRINT2 (("Partition start, n "ID" nz "ID"\n", n, nz)) ; + + total_weight = 0 ; + for (j = 0 ; j < n ; j++) + { + ASSERT (Cnw [j] > 0) ; + total_weight += Cnw [j] ; + } + + if (n <= 2) + { + /* very small graph */ + for (j = 0 ; j < n ; j++) + { + Part [j] = 2 ; + } + return (total_weight) ; + } + else if (nz <= 0) + { + /* no edges, this is easy */ + PRINT2 (("diagonal matrix\n")) ; + k = n/2 ; + for (j = 0 ; j < k ; j++) + { + Part [j] = 0 ; + } + for ( ; j < n ; j++) + { + Part [j] = 1 ; + } + /* ensure the separator is not empty (required by nested dissection) */ + Part [n-1] = 2 ; + return (Cnw [n-1]) ; + } + +#ifndef NDEBUG + ASSERT (n > 1 && nz > 0) ; + PRINT2 (("original graph:\n")) ; + for (j = 0 ; j < n ; j++) + { + PRINT2 ((""ID": ", j)) ; + for (p = Cp [j] ; p < Cp [j+1] ; p++) + { + i = Ci [p] ; + PRINT3 ((""ID" ", i)) ; + ASSERT (i >= 0 && i < n && i != j) ; + } + PRINT2 (("hash: "ID"\n", Hash [j])) ; + } + DEBUG (for (p = 0 ; p < csize ; p++) ASSERT (Cew [p] == 1)) ; +#endif + + nodes_pruned = 0 ; + + if (compress) + { + + /* ------------------------------------------------------------------ */ + /* get workspace */ + /* ------------------------------------------------------------------ */ + + Next = Part ; /* use Part as workspace for Next [ */ + Hhead = Cew ; /* use Cew as workspace for Hhead [ */ + + /* ------------------------------------------------------------------ */ + /* create the hash buckets */ + /* ------------------------------------------------------------------ */ + + for (j = 0 ; j < n ; j++) + { + /* get the hash key for node j */ + hash = Hash [j] ; + ASSERT (hash >= 0 && hash < csize) ; + head = Hhead [hash] ; + if (head > EMPTY) + { + /* hash bucket for this hash key is empty. */ + head = EMPTY ; + } + else + { + /* hash bucket for this hash key is not empty. get old head */ + head = FLIP (head) ; + ASSERT (head >= 0 && head < n) ; + } + /* node j becomes the new head of the hash bucket. FLIP it so that + * we can tell the difference between an empty or non-empty hash + * bucket. */ + Hhead [hash] = FLIP (j) ; + Next [j] = head ; + ASSERT (head >= EMPTY && head < n) ; + } + +#ifndef NDEBUG + for (cnt = 0, k = 0 ; k < n ; k++) + { + ASSERT (Hash [k] >= 0 && Hash [k] < csize) ; /* k is alive */ + hash = Hash [k] ; + ASSERT (hash >= 0 && hash < csize) ; + head = Hhead [hash] ; + ASSERT (head < EMPTY) ; /* hash bucket not empty */ + j = FLIP (head) ; + ASSERT (j >= 0 && j < n) ; + if (j == k) + { + PRINT2 (("hash "ID": ", hash)) ; + for ( ; j != EMPTY ; j = Next [j]) + { + PRINT3 ((" "ID"", j)) ; + ASSERT (j >= 0 && j < n) ; + ASSERT (Hash [j] == hash) ; + cnt++ ; + ASSERT (cnt <= n) ; + } + PRINT2 (("\n")) ; + } + } + ASSERT (cnt == n) ; +#endif + + /* ------------------------------------------------------------------ */ + /* scan the non-empty hash buckets for indistinguishable nodes */ + /* ------------------------------------------------------------------ */ + + /* If there are no hash collisions and no compression occurs, this takes + * O(n) time. If no hash collisions, but some nodes are removed, this + * takes time O(n+e) where e is the sum of the degress of the nodes + * that are removed. Even with many hash collisions (a rare case), + * this algorithm has never been observed to perform more than nnz(A) + * useless work. + * + * Cmap is used as workspace to mark nodes of the graph, [ + * for comparing the nonzero patterns of two nodes i and j. + */ + +#define Cmap_MARK(i) Cmap [i] = j +#define Cmap_MARKED(i) (Cmap [i] == j) + + for (i = 0 ; i < n ; i++) + { + Cmap [i] = EMPTY ; + } + + for (k = 0 ; k < n ; k++) + { + hash = Hash [k] ; + ASSERT (hash >= FLIP (n-1) && hash < csize) ; + if (hash < 0) + { + /* node k has already been absorbed into some other node */ + ASSERT (FLIP (Hash [k]) >= 0 && FLIP (Hash [k] < n)) ; + continue ; + } + head = Hhead [hash] ; + ASSERT (head < EMPTY || head == 1) ; + if (head == 1) + { + /* hash bucket is already empty */ + continue ; + } + PRINT2 (("\n--------------------hash "ID":\n", hash)) ; + for (j = FLIP (head) ; j != EMPTY && Next[j] > EMPTY ; j = Next [j]) + { + /* compare j with all nodes i following it in hash bucket */ + ASSERT (j >= 0 && j < n && Hash [j] == hash) ; + p = Cp [j] ; + pend = Cp [j+1] ; + jlen = pend - p ; + jscattered = FALSE ; + DEBUG (for (i = 0 ; i < n ; i++) ASSERT (!Cmap_MARKED (i))) ; + DEBUG (pruned = FALSE) ; + ilast = j ; + for (i = Next [j] ; i != EMPTY ; i = Next [i]) + { + ASSERT (i >= 0 && i < n && Hash [i] == hash && i != j) ; + pi = Cp [i] ; + piend = Cp [i+1] ; + ilen = piend - pi ; + DEBUG (work++) ; + if (ilen != jlen) + { + /* i and j have different degrees */ + ilast = i ; + continue ; + } + /* scatter the pattern of node j, if not already */ + if (!jscattered) + { + Cmap_MARK (j) ; + for ( ; p < pend ; p++) + { + Cmap_MARK (Ci [p]) ; + } + jscattered = TRUE ; + DEBUG (work += jlen) ; + } + for (ok = Cmap_MARKED (i) ; ok && pi < piend ; pi++) + { + ok = Cmap_MARKED (Ci [pi]) ; + DEBUG (work++) ; + } + if (ok) + { + /* found it. kill node i and merge it into j */ + PRINT2 (("found "ID" absorbed into "ID"\n", i, j)) ; + Hash [i] = FLIP (j) ; + Cnw [j] += Cnw [i] ; + Cnw [i] = 0 ; + ASSERT (ilast != i && ilast >= 0 && ilast < n) ; + Next [ilast] = Next [i] ; /* delete i from bucket */ + nodes_pruned++ ; + DEBUG (goodwork += (ilen+1)) ; + DEBUG (pruned = TRUE) ; + } + else + { + /* i and j are different */ + ilast = i ; + } + } + DEBUG (if (pruned) goodwork += jlen) ; + } + /* empty the hash bucket, restoring Cew */ + Hhead [hash] = 1 ; + } + + DEBUG (if (((work - goodwork) / (double) nz) > 0.20) PRINT0 (( + "work %12g good %12g nz %12g (wasted work/nz: %6.2f )\n", + work, goodwork, (double) nz, (work - goodwork) / ((double) nz)))) ; + + /* All hash buckets now empty. Cmap no longer needed as workspace. ] + * Cew no longer needed as Hhead; Cew is now restored to all ones. ] + * Part no longer needed as workspace for Next. ] */ + } + + /* Edge weights are all one, node weights reflect node absorption */ + DEBUG (for (p = 0 ; p < csize ; p++) ASSERT (Cew [p] == 1)) ; + DEBUG (for (cnt = 0, j = 0 ; j < n ; j++) cnt += Cnw [j]) ; + ASSERT (cnt == total_weight) ; + + /* ---------------------------------------------------------------------- */ + /* compress and partition the graph */ + /* ---------------------------------------------------------------------- */ + + if (nodes_pruned == 0) + { + + /* ------------------------------------------------------------------ */ + /* no pruning done at all. Do not create the compressed graph */ + /* ------------------------------------------------------------------ */ + + /* FUTURE WORK: could call CHACO, SCOTCH, ... here too */ + csep = CHOLMOD(metis_bisector) (C, Cnw, Cew, Part, Common) ; + + } + else if (nodes_pruned == n-1) + { + + /* ------------------------------------------------------------------ */ + /* only one node left. This is a dense graph */ + /* ------------------------------------------------------------------ */ + + PRINT2 (("completely dense graph\n")) ; + csep = total_weight ; + for (j = 0 ; j < n ; j++) + { + Part [j] = 2 ; + } + + } + else + { + + /* ------------------------------------------------------------------ */ + /* compress the graph and partition the compressed graph */ + /* ------------------------------------------------------------------ */ + + /* ------------------------------------------------------------------ */ + /* create the map from the uncompressed graph to the compressed graph */ + /* ------------------------------------------------------------------ */ + + /* Cmap [j] = k if node j is alive and the kth node of compressed graph. + * The mapping is done monotonically (that is, k <= j) to simplify the + * uncompression later on. Cmap [j] = EMPTY if node j is dead. */ + + for (j = 0 ; j < n ; j++) + { + Cmap [j] = EMPTY ; + } + k = 0 ; + for (j = 0 ; j < n ; j++) + { + if (Cnw [j] > 0) + { + ASSERT (k <= j) ; + Cmap [j] = k++ ; + } + } + cn = k ; /* # of nodes in compressed graph */ + PRINT2 (("compressed graph from "ID" to "ID" nodes\n", n, cn)) ; + ASSERT (cn > 1 && cn == n - nodes_pruned) ; + + /* ------------------------------------------------------------------ */ + /* create the compressed graph */ + /* ------------------------------------------------------------------ */ + + k = 0 ; + pdest = 0 ; + for (j = 0 ; j < n ; j++) + { + if (Cnw [j] > 0) + { + /* node j in the full graph is node k in the compressed graph */ + ASSERT (k <= j && Cmap [j] == k) ; + p = Cp [j] ; + pend = Cp [j+1] ; + Cp [k] = pdest ; + Cnw [k] = Cnw [j] ; + for ( ; p < pend ; p++) + { + /* prune dead nodes, and remap to new node numbering */ + i = Ci [p] ; + ASSERT (i >= 0 && i < n && i != j) ; + i = Cmap [i] ; + ASSERT (i >= EMPTY && i < cn && i != k) ; + if (i > EMPTY) + { + ASSERT (pdest <= p) ; + Ci [pdest++] = i ; + } + } + k++ ; + } + } + Cp [cn] = pdest ; + C->nrow = cn ; + C->ncol = cn ; /* affects mem stats unless restored when C free'd */ + +#ifndef NDEBUG + PRINT2 (("pruned graph ("ID"/"ID") nodes, ("ID"/"ID") edges\n", + cn, n, pdest, nz)) ; + PRINT2 (("compressed graph:\n")) ; + for (cnt = 0, j = 0 ; j < cn ; j++) + { + PRINT2 ((""ID": ", j)) ; + for (p = Cp [j] ; p < Cp [j+1] ; p++) + { + i = Ci [p] ; + PRINT3 ((""ID" ", i)) ; + ASSERT (i >= 0 && i < cn && i != j) ; + } + PRINT2 (("weight: "ID"\n", Cnw [j])) ; + ASSERT (Cnw [j] > 0) ; + cnt += Cnw [j] ; + } + ASSERT (cnt == total_weight) ; + for (j = 0 ; j < n ; j++) PRINT2 (("Cmap ["ID"] = "ID"\n", j, Cmap[j])); + ASSERT (k == cn) ; +#endif + + /* ------------------------------------------------------------------ */ + /* find the separator of the compressed graph */ + /* ------------------------------------------------------------------ */ + + /* FUTURE WORK: could call CHACO, SCOTCH, ... here too */ + csep = CHOLMOD(metis_bisector) (C, Cnw, Cew, Part, Common) ; + + if (csep < 0) + { + /* failed */ + return (-1) ; + } + + PRINT2 (("Part: ")) ; + DEBUG (for (j = 0 ; j < cn ; j++) PRINT2 ((""ID" ", Part [j]))) ; + PRINT2 (("\n")) ; + + /* Cp and Ci no longer needed */ + + /* ------------------------------------------------------------------ */ + /* find the separator of the uncompressed graph */ + /* ------------------------------------------------------------------ */ + + /* expand the separator to live nodes in the uncompressed graph */ + for (j = n-1 ; j >= 0 ; j--) + { + /* do this in reverse order so that Cnw can be expanded in place */ + k = Cmap [j] ; + ASSERT (k >= EMPTY && k < n) ; + if (k > EMPTY) + { + /* node k in compressed graph and is node j in full graph */ + ASSERT (k <= j) ; + ASSERT (Hash [j] >= EMPTY) ; + Part [j] = Part [k] ; + Cnw [j] = Cnw [k] ; + } + else + { + /* node j is a dead node */ + Cnw [j] = 0 ; + DEBUG (Part [j] = EMPTY) ; + ASSERT (Hash [j] < EMPTY) ; + } + } + + /* find the components for the dead nodes */ + for (i = 0 ; i < n ; i++) + { + if (Hash [i] < EMPTY) + { + /* node i has been absorbed into node j */ + j = FLIP (Hash [i]) ; + ASSERT (Part [i] == EMPTY && j >= 0 && j < n && Cnw [i] == 0) ; + Part [i] = Part [j] ; + } + ASSERT (Part [i] >= 0 && Part [i] <= 2) ; + } + +#ifndef NDEBUG + PRINT2 (("Part: ")) ; + for (cnt = 0, j = 0 ; j < n ; j++) + { + ASSERT (Part [j] != EMPTY) ; + PRINT2 ((""ID" ", Part [j])) ; + if (Part [j] == 2) cnt += Cnw [j] ; + } + PRINT2 (("\n")) ; + PRINT2 (("csep "ID" "ID"\n", cnt, csep)) ; + ASSERT (cnt == csep) ; + for (cnt = 0, j = 0 ; j < n ; j++) cnt += Cnw [j] ; + ASSERT (cnt == total_weight) ; +#endif + + } + + /* ---------------------------------------------------------------------- */ + /* return the separator (or -1 if error) */ + /* ---------------------------------------------------------------------- */ + + PRINT2 (("Partition done, n "ID" csep "ID"\n", n, csep)) ; + return (csep) ; +} + + +/* ========================================================================== */ +/* === clear_flag =========================================================== */ +/* ========================================================================== */ + +/* A node j has been removed from the graph if Flag [j] < EMPTY. + * If Flag [j] >= EMPTY && Flag [j] < mark, then node j is alive but unmarked. + * Flag [j] == mark means that node j is alive and marked. Incrementing mark + * means that all nodes are either (still) dead, or live but unmarked. + * + * If Map is NULL, then on output, Common->mark < Common->Flag [i] for all i + * from 0 to Common->nrow. This is the same output condition as + * cholmod_clear_flag, except that this routine maintains the Flag [i] < EMPTY + * condition as well, if that condition was true on input. + * + * If Map is non-NULL, then on output, Common->mark < Common->Flag [i] for all + * i in the set Map [0..cn-1]. + * + * workspace: Flag (nrow) + */ + +static SuiteSparse_long clear_flag (Int *Map, Int cn, cholmod_common *Common) +{ + Int nrow, i ; + Int *Flag ; + PRINT2 (("old mark %ld\n", Common->mark)) ; + Common->mark++ ; + PRINT2 (("new mark %ld\n", Common->mark)) ; + if (Common->mark <= 0) + { + nrow = Common->nrow ; + Flag = Common->Flag ; + if (Map != NULL) + { + for (i = 0 ; i < cn ; i++) + { + /* if Flag [Map [i]] < EMPTY, leave it alone */ + if (Flag [Map [i]] >= EMPTY) + { + Flag [Map [i]] = EMPTY ; + } + } + /* now Flag [Map [i]] <= EMPTY for all i */ + } + else + { + for (i = 0 ; i < nrow ; i++) + { + /* if Flag [i] < EMPTY, leave it alone */ + if (Flag [i] >= EMPTY) + { + Flag [i] = EMPTY ; + } + } + /* now Flag [i] <= EMPTY for all i */ + } + Common->mark = 0 ; + } + return (Common->mark) ; +} + + +/* ========================================================================== */ +/* === find_components ====================================================== */ +/* ========================================================================== */ + +/* Find all connected components of the current subgraph C. The subgraph C + * consists of the nodes of B that appear in the set Map [0..cn-1]. If Map + * is NULL, then it is assumed to be the identity mapping + * (Map [0..cn-1] = 0..cn-1). + * + * A node j does not appear in B if it has been ordered (Flag [j] < EMPTY, + * which means that j has been ordered and is "deleted" from B). + * + * If the size of a component is large, it is placed on the component stack, + * Cstack. Otherwise, its nodes are ordered and it is not placed on the Cstack. + * + * A component S is defined by a "representative node" (repnode for short) + * called the snode, which is one of the nodes in the subgraph. Likewise, the + * subgraph C is defined by its repnode, called cnode. + * + * If Part is not NULL on input, then Part [i] determines how the components + * are placed on the stack. Components containing nodes i with Part [i] == 0 + * are placed first, followed by components with Part [i] == 1. + * + * The first node placed in each of the two parts is flipped when placed in + * the Cstack. This allows the components of the two parts to be found simply + * by traversing the Cstack. + * + * workspace: Flag (nrow) + */ + +static void find_components +( + /* inputs, not modified on output */ + cholmod_sparse *B, + Int Map [ ], /* size n, only Map [0..cn-1] used */ + Int cn, /* # of nodes in C */ + Int cnode, /* root node of component C, or EMPTY if C is the + * entire graph B */ + + Int Part [ ], /* size cn, optional */ + + /* input/output */ + Int Bnz [ ], /* size n. Bnz [j] = # nonzeros in column j of B. + * Reduce since B is pruned of dead nodes. */ + + Int CParent [ ], /* CParent [i] = j if component with repnode j is + * the parent of the component with repnode i. + * CParent [i] = EMPTY if the component with + * repnode i is a root of the separator tree. + * CParent [i] is -2 if i is not a repnode. */ + Int Cstack [ ], /* component stack for nested dissection */ + Int *top, /* Cstack [0..top] contains root nodes of the + * the components currently in the stack */ + + /* workspace, undefined on input and output: */ + Int Queue [ ], /* size n, for breadth-first search */ + + cholmod_common *Common +) +{ + Int n, mark, cj, j, sj, sn, p, i, snode, pstart, pdest, pend, nd_components, + part, first, save_mark ; + Int *Bp, *Bi, *Flag ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + PRINT2 (("find components: cn %d\n", cn)) ; + Flag = Common->Flag ; /* size n */ + + /* force initialization of Flag [Map [0..cn-1]] */ + save_mark = Common->mark ; /* save the current mark */ + Common->mark = EMPTY ; + + /* clear Flag; preserve Flag [Map [i]] if Flag [Map [i]] already < EMPTY */ + /* this takes O(cn) time */ + mark = clear_flag (Map, cn, Common) ; + + Bp = B->p ; + Bi = B->i ; + n = B->nrow ; + ASSERT (cnode >= EMPTY && cnode < n) ; + ASSERT (IMPLIES (cnode >= 0, Flag [cnode] < EMPTY)) ; + + /* get ordering parameters */ + nd_components = Common->method [Common->current].nd_components ; + + /* ---------------------------------------------------------------------- */ + /* find the connected components of C via a breadth-first search */ + /* ---------------------------------------------------------------------- */ + + part = (Part == NULL) ? 0 : 1 ; + + /* examine each part (part 1 and then part 0) */ + for (part = (Part == NULL) ? 0 : 1 ; part >= 0 ; part--) + { + + /* first is TRUE for the first connected component in each part */ + first = TRUE ; + + /* find all connected components in the current part */ + for (cj = 0 ; cj < cn ; cj++) + { + /* get node snode, which is node cj of C. It might already be in + * the separator of C (and thus ordered, with Flag [snode] < EMPTY) + */ + snode = (Map == NULL) ? (cj) : (Map [cj]) ; + ASSERT (snode >= 0 && snode < n) ; + + if (Flag [snode] >= EMPTY && Flag [snode] < mark + && ((Part == NULL) || Part [cj] == part)) + { + + /* ---------------------------------------------------------- */ + /* find new connected component S */ + /* ---------------------------------------------------------- */ + + /* node snode is the repnode of a connected component S, the + * parent of which is cnode, the repnode of C. If cnode is + * EMPTY then C is the original graph B. */ + PRINT2 (("----------:::snode "ID" cnode "ID"\n", snode, cnode)); + + ASSERT (CParent [snode] == -2) ; + if (first || nd_components) + { + /* If this is the first node in this part, then it becomes + * the repnode of all components in this part, and all + * components in this part form a single node in the + * separator tree. If nd_components is TRUE, then all + * connected components form their own node in the + * separator tree. + */ + CParent [snode] = cnode ; + } + + /* place j in the queue and mark it */ + Queue [0] = snode ; + Flag [snode] = mark ; + sn = 1 ; + + /* breadth-first traversal, starting at node j */ + for (sj = 0 ; sj < sn ; sj++) + { + /* get node j from head of Queue and traverse its edges */ + j = Queue [sj] ; + PRINT2 ((" j: "ID"\n", j)) ; + ASSERT (j >= 0 && j < n) ; + ASSERT (Flag [j] == mark) ; + pstart = Bp [j] ; + pdest = pstart ; + pend = pstart + Bnz [j] ; + for (p = pstart ; p < pend ; p++) + { + i = Bi [p] ; + if (i != j && Flag [i] >= EMPTY) + { + /* node is still in the graph */ + Bi [pdest++] = i ; + if (Flag [i] < mark) + { + /* node i is in this component S, and unflagged + * (first time node i has been seen in this BFS) + * place node i in the queue and mark it */ + Queue [sn++] = i ; + Flag [i] = mark ; + } + } + } + /* edges to dead nodes have been removed */ + Bnz [j] = pdest - pstart ; + } + + /* ---------------------------------------------------------- */ + /* order S if it is small; place it on Cstack otherwise */ + /* ---------------------------------------------------------- */ + + PRINT2 (("sn "ID"\n", sn)) ; + + /* place the new component on the Cstack. Flip the node if + * is the first connected component of the current part, + * or if all components are treated as their own node in + * the separator tree. */ + Cstack [++(*top)] = + (first || nd_components) ? FLIP (snode) : snode ; + first = FALSE ; + } + } + } + + /* restore the flag (normally taking O(1) time except for Int overflow) */ + Common->mark = save_mark++ ; + clear_flag (NULL, 0, Common) ; + DEBUG (for (i = 0 ; i < n ; i++) ASSERT (Flag [i] < Common->mark)) ; +} + + +/* ========================================================================== */ +/* === cholmod_bisect ======================================================= */ +/* ========================================================================== */ + +/* Finds a node bisector of A, A*A', A(:,f)*A(:,f)'. + * + * workspace: Flag (nrow), + * Iwork (nrow if symmetric, max (nrow,ncol) if unsymmetric). + * Allocates a temporary matrix B=A*A' or B=A, + * and O(nnz(A)) temporary memory space. + */ + +SuiteSparse_long CHOLMOD(bisect) /* returns # of nodes in separator */ +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to bisect */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + int compress, /* if TRUE, compress the graph first */ + /* ---- output --- */ + Int *Partition, /* size A->nrow. Node i is in the left graph if + * Partition [i] = 0, the right graph if 1, and in the + * separator if 2. */ + /* --------------- */ + cholmod_common *Common +) +{ + Int *Bp, *Bi, *Hash, *Cmap, *Bnw, *Bew, *Iwork ; + cholmod_sparse *B ; + unsigned Int hash ; + Int j, n, bnz, sepsize, p, pend ; + size_t csize, s ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (A, EMPTY) ; + RETURN_IF_NULL (Partition, EMPTY) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* quick return */ + /* ---------------------------------------------------------------------- */ + + n = A->nrow ; + if (n == 0) + { + return (0) ; + } + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* s = n + MAX (n, A->ncol) */ + s = CHOLMOD(add_size_t) (A->nrow, MAX (A->nrow, A->ncol), &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (EMPTY) ; + } + + CHOLMOD(allocate_work) (n, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (EMPTY) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + Iwork = Common->Iwork ; + Hash = Iwork ; /* size n, (i/l/l) */ + Cmap = Iwork + n ; /* size n, (i/i/l) */ + + /* ---------------------------------------------------------------------- */ + /* convert the matrix to adjacency list form */ + /* ---------------------------------------------------------------------- */ + + /* The input graph to must be symmetric, with no diagonal entries + * present. The columns need not be sorted. */ + + /* B = A, A*A', or A(:,f)*A(:,f)', upper and lower parts present */ + + if (A->stype) + { + /* Add the upper/lower part to a symmetric lower/upper matrix by + * converting to unsymmetric mode */ + /* workspace: Iwork (nrow) */ + B = CHOLMOD(copy) (A, 0, -1, Common) ; + } + else + { + /* B = A*A' or A(:,f)*A(:,f)', no diagonal */ + /* workspace: Flag (nrow), Iwork (max (nrow,ncol)) */ + B = CHOLMOD(aat) (A, fset, fsize, -1, Common) ; + } + + if (Common->status < CHOLMOD_OK) + { + return (EMPTY) ; + } + Bp = B->p ; + Bi = B->i ; + bnz = Bp [n] ; + ASSERT ((Int) (B->nrow) == n && (Int) (B->ncol) == n) ; + + /* B does not include the diagonal, and both upper and lower parts. + * Common->anz includes the diagonal, and just the lower part of B */ + Common->anz = bnz / 2 + ((double) n) ; + + /* Bew should be at least size n for the hash function to work well */ + /* this cannot cause overflow, because the matrix is already created */ + csize = MAX (((size_t) n) + 1, (size_t) bnz) ; + + /* create the graph using Flag as workspace for node weights [ */ + Bnw = Common->Flag ; /* size n workspace */ + + /* compute hash for each node if compression requested */ + if (compress) + { + for (j = 0 ; j < n ; j++) + { + hash = j ; + pend = Bp [j+1] ; + for (p = Bp [j] ; p < pend ; p++) + { + hash += Bi [p] ; + ASSERT (Bi [p] != j) ; + } + /* finalize the hash key for node j */ + hash %= csize ; + Hash [j] = (Int) hash ; + ASSERT (Hash [j] >= 0 && Hash [j] < csize) ; + } + } + + /* allocate edge weights */ + Bew = CHOLMOD(malloc) (csize, sizeof (Int), Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + CHOLMOD(free_sparse) (&B, Common) ; + CHOLMOD(free) (csize, sizeof (Int), Bew, Common) ; + return (EMPTY) ; + } + + /* graph has unit node and edge weights */ + for (j = 0 ; j < n ; j++) + { + Bnw [j] = 1 ; + } + for (s = 0 ; s < csize ; s++) + { + Bew [s] = 1 ; + } + + /* ---------------------------------------------------------------------- */ + /* compress and partition the graph */ + /* ---------------------------------------------------------------------- */ + + sepsize = partition ( +#ifndef NDEBUG + csize, +#endif + compress, Hash, B, Bnw, Bew, Cmap, Partition, Common) ; + + /* contents of Bp, Bi, Bnw, and Bew no longer needed ] */ + + /* If partition fails, free the workspace below and return sepsize < 0 */ + + /* ---------------------------------------------------------------------- */ + /* free workspace */ + /* ---------------------------------------------------------------------- */ + + B->ncol = n ; /* restore size for memory usage statistics */ + CHOLMOD(free_sparse) (&B, Common) ; + Common->mark = EMPTY ; + CHOLMOD_CLEAR_FLAG (Common) ; + CHOLMOD(free) (csize, sizeof (Int), Bew, Common) ; + return (sepsize) ; +} + + +/* ========================================================================== */ +/* === cholmod_nested_dissection ============================================ */ +/* ========================================================================== */ + +/* This method uses a node bisector, applied recursively (but using a + * non-recursive algorithm). Once the graph is partitioned, it calls a + * constrained min degree code (CAMD or CSYMAMD for A+A', and CCOLAMD for A*A') + * to order all the nodes in the graph - but obeying the constraints determined + * by the separators. This routine is similar to METIS_NodeND, except for how + * it treats the leaf nodes. METIS_NodeND orders the leaves of the separator + * tree with MMD, ignoring the rest of the matrix when ordering a single leaf. + * This routine orders the whole matrix with CSYMAMD or CCOLAMD, all at once, + * when the graph partitioning is done. + * + * This function also returns a postorderd separator tree (CParent), and a + * mapping of nodes in the graph to nodes in the separator tree (Cmember). + * + * workspace: Flag (nrow), Head (nrow+1), Iwork (4*nrow + (ncol if unsymmetric)) + * Allocates a temporary matrix B=A*A' or B=A, + * and O(nnz(A)) temporary memory space. + * Allocates an additional 3*n*sizeof(Int) temporary workspace + */ + +SuiteSparse_long CHOLMOD(nested_dissection) + /* returns # of components, or -1 if error */ +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to order */ + Int *fset, /* subset of 0:(A->ncol)-1 */ + size_t fsize, /* size of fset */ + /* ---- output --- */ + Int *Perm, /* size A->nrow, output permutation */ + Int *CParent, /* size A->nrow. On output, CParent [c] is the parent + * of component c, or EMPTY if c is a root, and where + * c is in the range 0 to # of components minus 1 */ + Int *Cmember, /* size A->nrow. Cmember [j] = c if node j of A is + * in component c */ + /* --------------- */ + cholmod_common *Common +) +{ + double prune_dense, nd_oksep ; + Int *Bp, *Bi, *Bnz, *Cstack, *Imap, *Map, *Flag, *Head, *Next, *Bnw, *Iwork, + *Ipost, *NewParent, *Hash, *Cmap, *Cp, *Ci, *Cew, *Cnw, *Part, *Post, + *Work3n ; + unsigned Int hash ; + Int n, bnz, top, i, j, k, cnode, cdense, p, cj, cn, ci, cnz, mark, c, uncol, + sepsize, parent, ncomponents, threshold, ndense, pstart, pdest, pend, + nd_compress, nd_camd, csize, jnext, nd_small, total_weight, + nchild, child = EMPTY ; + cholmod_sparse *B, *C ; + size_t s ; + int ok = TRUE ; + DEBUG (Int cnt) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (A, EMPTY) ; + RETURN_IF_NULL (Perm, EMPTY) ; + RETURN_IF_NULL (CParent, EMPTY) ; + RETURN_IF_NULL (Cmember, EMPTY) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, EMPTY) ; + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* quick return */ + /* ---------------------------------------------------------------------- */ + + n = A->nrow ; + if (n == 0) + { + return (1) ; + } + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + /* get ordering parameters */ + prune_dense = Common->method [Common->current].prune_dense ; + nd_compress = Common->method [Common->current].nd_compress ; + nd_oksep = Common->method [Common->current].nd_oksep ; + nd_oksep = MAX (0, nd_oksep) ; + nd_oksep = MIN (1, nd_oksep) ; + nd_camd = Common->method [Common->current].nd_camd ; + nd_small = Common->method [Common->current].nd_small ; + nd_small = MAX (4, nd_small) ; + + PRINT0 (("nd_components %d nd_small %d nd_oksep %g\n", + Common->method [Common->current].nd_components, + nd_small, nd_oksep)) ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* s = 4*n + uncol */ + uncol = (A->stype == 0) ? A->ncol : 0 ; + s = CHOLMOD(mult_size_t) (n, 4, &ok) ; + s = CHOLMOD(add_size_t) (s, uncol, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (EMPTY) ; + } + + CHOLMOD(allocate_work) (n, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (EMPTY) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + Flag = Common->Flag ; /* size n */ + Head = Common->Head ; /* size n+1, all equal to -1 */ + + Iwork = Common->Iwork ; + Imap = Iwork ; /* size n, same as Queue in find_components */ + Map = Iwork + n ; /* size n */ + Bnz = Iwork + 2*((size_t) n) ; /* size n */ + Hash = Iwork + 3*((size_t) n) ; /* size n */ + + Work3n = CHOLMOD(malloc) (n, 3*sizeof (Int), Common) ; + Part = Work3n ; /* size n */ + Bnw = Part + n ; /* size n */ + Cnw = Bnw + n ; /* size n */ + + Cstack = Perm ; /* size n, use Perm as workspace for Cstack [ */ + Cmap = Cmember ; /* size n, use Cmember as workspace [ */ + + if (Common->status < CHOLMOD_OK) + { + return (EMPTY) ; + } + + /* ---------------------------------------------------------------------- */ + /* convert B to symmetric form with both upper/lower parts present */ + /* ---------------------------------------------------------------------- */ + + /* B = A+A', A*A', or A(:,f)*A(:,f)', upper and lower parts present */ + + if (A->stype) + { + /* Add the upper/lower part to a symmetric lower/upper matrix by + * converting to unsymmetric mode */ + /* workspace: Iwork (nrow) */ + B = CHOLMOD(copy) (A, 0, -1, Common) ; + } + else + { + /* B = A*A' or A(:,f)*A(:,f)', no diagonal */ + /* workspace: Flag (nrow), Iwork (max (nrow,ncol)) */ + B = CHOLMOD(aat) (A, fset, fsize, -1, Common) ; + } + + if (Common->status < CHOLMOD_OK) + { + CHOLMOD(free) (3*n, sizeof (Int), Work3n, Common) ; + return (EMPTY) ; + } + Bp = B->p ; + Bi = B->i ; + bnz = CHOLMOD(nnz) (B, Common) ; + ASSERT ((Int) (B->nrow) == n && (Int) (B->ncol) == n) ; + csize = MAX (n, bnz) ; + ASSERT (CHOLMOD(dump_sparse) (B, "B for nd:", Common) >= 0) ; + + /* ---------------------------------------------------------------------- */ + /* initializations */ + /* ---------------------------------------------------------------------- */ + + /* all nodes start out unmarked and unordered (Type 4, see below) */ + Common->mark = EMPTY ; + CHOLMOD_CLEAR_FLAG (Common) ; + ASSERT (Flag == Common->Flag) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + for (j = 0 ; j < n ; j++) + { + CParent [j] = -2 ; + } + + /* prune dense nodes from B */ + if (IS_NAN (prune_dense) || prune_dense < 0) + { + /* only remove completely dense nodes */ + threshold = n-2 ; + } + else + { + /* remove nodes with degree more than threshold */ + threshold = (Int) (MAX (16, prune_dense * sqrt ((double) (n)))) ; + threshold = MIN (n, threshold) ; + } + ndense = 0 ; + cnode = EMPTY ; + cdense = EMPTY ; + + for (j = 0 ; j < n ; j++) + { + Bnz [j] = Bp [j+1] - Bp [j] ; + if (Bnz [j] > threshold) + { + /* node j is dense, prune it from B */ + PRINT2 (("j is dense %d\n", j)) ; + ndense++ ; + if (cnode == EMPTY) + { + /* first dense node found becomes root of this component, + * which contains all of the dense nodes found here */ + cdense = j ; + cnode = j ; + CParent [cnode] = EMPTY ; + } + Flag [j] = FLIP (cnode) ; + } + } + B->packed = FALSE ; + ASSERT (B->nz == NULL) ; + + if (ndense == n) + { + /* all nodes removed: Perm is identity, all nodes in component zero, + * and the separator tree has just one node. */ + PRINT2 (("all nodes are dense\n")) ; + for (k = 0 ; k < n ; k++) + { + Perm [k] = k ; + Cmember [k] = 0 ; + } + CParent [0] = EMPTY ; + CHOLMOD(free_sparse) (&B, Common) ; + CHOLMOD(free) (3*n, sizeof (Int), Work3n, Common) ; + Common->mark = EMPTY ; + CHOLMOD_CLEAR_FLAG (Common) ; + return (1) ; + } + + /* Cp and Ci are workspace to construct the subgraphs to partition */ + C = CHOLMOD(allocate_sparse) (n, n, csize, FALSE, TRUE, 0, CHOLMOD_PATTERN, + Common) ; + Cew = CHOLMOD(malloc) (csize, sizeof (Int), Common) ; + + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + CHOLMOD(free_sparse) (&C, Common) ; + CHOLMOD(free_sparse) (&B, Common) ; + CHOLMOD(free) (csize, sizeof (Int), Cew, Common) ; + CHOLMOD(free) (3*n, sizeof (Int), Work3n, Common) ; + Common->mark = EMPTY ; + CHOLMOD_CLEAR_FLAG (Common) ; + PRINT2 (("out of memory for C, etc\n")) ; + return (EMPTY) ; + } + + Cp = C->p ; + Ci = C->i ; + + /* create initial unit node and edge weights */ + for (j = 0 ; j < n ; j++) + { + Bnw [j] = 1 ; + } + for (p = 0 ; p < csize ; p++) + { + Cew [p] = 1 ; + } + + /* push the initial connnected components of B onto the Cstack */ + top = EMPTY ; /* Cstack is empty */ + /* workspace: Flag (nrow), Iwork (nrow); use Imap as workspace for Queue [*/ + find_components (B, NULL, n, cnode, NULL, + Bnz, CParent, Cstack, &top, Imap, Common) ; + /* done using Imap as workspace for Queue ] */ + + /* Nodes can now be of Type 0, 1, 2, or 4 (see definition below) */ + + /* ---------------------------------------------------------------------- */ + /* while Cstack is not empty, do: */ + /* ---------------------------------------------------------------------- */ + + while (top >= 0) + { + + /* clear the Flag array, but do not modify negative entries in Flag */ + mark = clear_flag (NULL, 0, Common) ; + + DEBUG (for (i = 0 ; i < n ; i++) Imap [i] = EMPTY) ; + + /* ------------------------------------------------------------------ */ + /* get node(s) from the top of the Cstack */ + /* ------------------------------------------------------------------ */ + + /* i is the repnode of its (unordered) connected component. Get + * all repnodes for all connected components of a single part. If + * each connected component is to be ordered separately (nd_components + * is TRUE), then this while loop iterates just once. */ + + cnode = EMPTY ; + cn = 0 ; + while (cnode == EMPTY) + { + i = Cstack [top--] ; + + if (i < 0) + { + /* this is the last node in this component */ + i = FLIP (i) ; + cnode = i ; + } + + ASSERT (i >= 0 && i < n && Flag [i] >= EMPTY) ; + + /* place i in the queue and mark it */ + Map [cn] = i ; + Flag [i] = mark ; + Imap [i] = cn ; + cn++ ; + } + + ASSERT (cnode != EMPTY) ; + + /* During ordering, there are five kinds of nodes in the graph of B, + * based on Flag [j] and CParent [j] for nodes j = 0 to n-1: + * + * Type 0: If cnode is a repnode of an unordered component, then + * CParent [cnode] is in the range EMPTY to n-1 and + * Flag [cnode] >= EMPTY. This is a "live" node. + * + * Type 1: If cnode is a repnode of an ordered separator component, + * then Flag [cnode] < EMPTY and FLAG [cnode] = FLIP (cnode). + * CParent [cnode] is in the range EMPTY to n-1. cnode is a root of + * the separator tree if CParent [cnode] == EMPTY. This node is dead. + * + * Type 2: If node j isn't a repnode, has not been absorbed via + * graph compression into another node, but is in an ordered separator + * component, then cnode = FLIP (Flag [j]) gives the repnode of the + * component that contains j and CParent [j] is -2. This node is dead. + * Note that Flag [j] < EMPTY. + * + * Type 3: If node i has been absorbed via graph compression into some + * other node j = FLIP (Flag [i]) where j is not a repnode. + * CParent [j] is -2. Node i may or may not be in an ordered + * component. This node is dead. Note that Flag [j] < EMPTY. + * + * Type 4: If node j is "live" (not in an ordered component, and not + * absorbed into any other node), then Flag [j] >= EMPTY. + * + * Only "live" nodes (of type 0 or 4) are placed in a subgraph to be + * partitioned. Node j is alive if Flag [j] >= EMPTY, and dead if + * Flag [j] < EMPTY. + */ + + /* ------------------------------------------------------------------ */ + /* create the subgraph for this connected component C */ + /* ------------------------------------------------------------------ */ + + /* Do a breadth-first search of the graph starting at cnode. + * use Map [0..cn-1] for nodes in the component C [ + * use Cnw and Cew for node and edge weights of the resulting subgraph [ + * use Cp and Ci for the resulting subgraph [ + * use Imap [i] for all nodes i in B that are in the component C [ + */ + + cnz = 0 ; + total_weight = 0 ; + for (cj = 0 ; cj < cn ; cj++) + { + /* get node j from the head of the queue; it is node cj of C */ + j = Map [cj] ; + ASSERT (Flag [j] == mark) ; + Cp [cj] = cnz ; + Cnw [cj] = Bnw [j] ; + ASSERT (Cnw [cj] >= 0) ; + total_weight += Cnw [cj] ; + pstart = Bp [j] ; + pdest = pstart ; + pend = pstart + Bnz [j] ; + hash = cj ; + for (p = pstart ; p < pend ; p++) + { + i = Bi [p] ; + /* prune diagonal entries and dead edges from B */ + if (i != j && Flag [i] >= EMPTY) + { + /* live node i is in the current component */ + Bi [pdest++] = i ; + if (Flag [i] != mark) + { + /* First time node i has been seen, it is a new node + * of C. place node i in the queue and mark it */ + Map [cn] = i ; + Flag [i] = mark ; + Imap [i] = cn ; + cn++ ; + } + /* place the edge (cj,ci) in the adjacency list of cj */ + ci = Imap [i] ; + ASSERT (ci >= 0 && ci < cn && ci != cj && cnz < csize) ; + Ci [cnz++] = ci ; + hash += ci ; + } + } + /* edges to dead nodes have been removed */ + Bnz [j] = pdest - pstart ; + /* finalize the hash key for column j */ + hash %= csize ; + Hash [cj] = (Int) hash ; + ASSERT (Hash [cj] >= 0 && Hash [cj] < csize) ; + } + Cp [cn] = cnz ; + C->nrow = cn ; + C->ncol = cn ; /* affects mem stats unless restored when C free'd */ + + /* contents of Imap no longer needed ] */ + +#ifndef NDEBUG + for (cj = 0 ; cj < cn ; cj++) + { + j = Map [cj] ; + PRINT2 (("----------------------------C column cj: "ID" j: "ID"\n", + cj, j)) ; + ASSERT (j >= 0 && j < n) ; + ASSERT (Flag [j] >= EMPTY) ; + for (p = Cp [cj] ; p < Cp [cj+1] ; p++) + { + ci = Ci [p] ; + i = Map [ci] ; + PRINT3 (("ci: "ID" i: "ID"\n", ci, i)) ; + ASSERT (ci != cj && ci >= 0 && ci < cn) ; + ASSERT (i != j && i >= 0 && i < n) ; + ASSERT (Flag [i] >= EMPTY) ; + } + } +#endif + + PRINT0 (("consider cn %d nd_small %d ", cn, nd_small)) ; + if (cn < nd_small) /* could be 'total_weight < nd_small' instead */ + { + /* place all nodes in the separator */ + PRINT0 ((" too small\n")) ; + sepsize = total_weight ; + } + else + { + + /* Cp and Ci now contain the component, with cn nodes and cnz + * nonzeros. The mapping of a node cj into node j the main graph + * B is given by Map [cj] = j */ + PRINT0 ((" cut\n")) ; + + /* -------------------------------------------------------------- */ + /* compress and partition the graph C */ + /* -------------------------------------------------------------- */ + + /* The edge weights Cew [0..csize-1] are all 1's on input to and + * output from the partition routine. */ + + sepsize = partition ( +#ifndef NDEBUG + csize, +#endif + nd_compress, Hash, C, Cnw, Cew, + Cmap, Part, Common) ; + + /* contents of Cp and Ci no longer needed ] */ + + if (sepsize < 0) + { + /* failed */ + C->ncol = n ; /* restore size for memory usage statistics */ + CHOLMOD(free_sparse) (&C, Common) ; + CHOLMOD(free_sparse) (&B, Common) ; + CHOLMOD(free) (csize, sizeof (Int), Cew, Common) ; + CHOLMOD(free) (3*n, sizeof (Int), Work3n, Common) ; + Common->mark = EMPTY ; + CHOLMOD_CLEAR_FLAG (Common) ; + return (EMPTY) ; + } + + /* -------------------------------------------------------------- */ + /* compress B based on how C was compressed */ + /* -------------------------------------------------------------- */ + + for (ci = 0 ; ci < cn ; ci++) + { + if (Hash [ci] < EMPTY) + { + /* ci is dead in C, having been absorbed into cj */ + cj = FLIP (Hash [ci]) ; + PRINT2 (("In C, "ID" absorbed into "ID" (wgt now "ID")\n", + ci, cj, Cnw [cj])) ; + /* i is dead in B, having been absorbed into j */ + i = Map [ci] ; + j = Map [cj] ; + PRINT2 (("In B, "ID" (wgt "ID") => "ID" (wgt "ID")\n", + i, Bnw [i], j, Bnw [j], Cnw [cj])) ; + /* more than one node may be absorbed into j. This is + * accounted for in Cnw [cj]. Assign it here rather + * than += Bnw [i] */ + Bnw [i] = 0 ; + Bnw [j] = Cnw [cj] ; + Flag [i] = FLIP (j) ; + } + } + + DEBUG (for (cnt = 0, j = 0 ; j < n ; j++) cnt += Bnw [j]) ; + ASSERT (cnt == n) ; + } + + /* contents of Cnw [0..cn-1] no longer needed ] */ + + /* ------------------------------------------------------------------ */ + /* order the separator, and stack the components when C is split */ + /* ------------------------------------------------------------------ */ + + /* one more component has been found: either the separator of C, + * or all of C */ + + ASSERT (sepsize >= 0 && sepsize <= total_weight) ; + + PRINT0 (("sepsize %d tot %d : %8.4f ", sepsize, total_weight, + ((double) sepsize) / ((double) total_weight))) ; + + if (sepsize == total_weight || sepsize == 0 || + sepsize > nd_oksep * total_weight) + { + /* Order the nodes in the component. The separator is too large, + * or empty. Note that the partition routine cannot return a + * sepsize of zero, but it can return a separator consisting of the + * whole graph. The "sepsize == 0" test is kept, above, in case the + * partition routine changes. In either case, this component + * remains unsplit, and becomes a leaf of the separator tree. */ + PRINT2 (("cnode %d sepsize zero or all of graph: "ID"\n", + cnode, sepsize)) ; + for (cj = 0 ; cj < cn ; cj++) + { + j = Map [cj] ; + Flag [j] = FLIP (cnode) ; + PRINT2 ((" node cj: "ID" j: "ID" ordered\n", cj, j)) ; + } + ASSERT (Flag [cnode] == FLIP (cnode)) ; + ASSERT (cnode != EMPTY && Flag [cnode] < EMPTY) ; + PRINT0 (("discarded\n")) ; + + } + else + { + + /* Order the nodes in the separator of C and find a new repnode + * cnode that is in the separator of C. This requires the separator + * to be non-empty. */ + PRINT0 (("sepsize not tiny: "ID"\n", sepsize)) ; + parent = CParent [cnode] ; + ASSERT (parent >= EMPTY && parent < n) ; + CParent [cnode] = -2 ; + cnode = EMPTY ; + for (cj = 0 ; cj < cn ; cj++) + { + j = Map [cj] ; + if (Part [cj] == 2) + { + /* All nodes in the separator become part of a component + * whose repnode is cnode */ + PRINT2 (("node cj: "ID" j: "ID" ordered\n", cj, j)) ; + if (cnode == EMPTY) + { + PRINT2(("------------new cnode: cj "ID" j "ID"\n", + cj, j)) ; + cnode = j ; + } + Flag [j] = FLIP (cnode) ; + } + else + { + PRINT2 ((" node cj: "ID" j: "ID" not ordered\n", + cj, j)) ; + } + } + ASSERT (cnode != EMPTY && Flag [cnode] < EMPTY) ; + ASSERT (CParent [cnode] == -2) ; + CParent [cnode] = parent ; + + /* find the connected components when C is split, and push + * them on the Cstack. Use Imap as workspace for Queue. [ */ + /* workspace: Flag (nrow) */ + find_components (B, Map, cn, cnode, Part, Bnz, + CParent, Cstack, &top, Imap, Common) ; + /* done using Imap as workspace for Queue ] */ + } + /* contents of Map [0..cn-1] no longer needed ] */ + } + + /* done using Cmember as workspace for Cmap ] */ + /* done using Perm as workspace for Cstack ] */ + + /* ---------------------------------------------------------------------- */ + /* place nodes removed via compression into their proper component */ + /* ---------------------------------------------------------------------- */ + + /* At this point, all nodes are of Type 1, 2, or 3, as defined above. */ + + for (i = 0 ; i < n ; i++) + { + /* find the repnode cnode that contains node i */ + j = FLIP (Flag [i]) ; + PRINT2 (("\nfind component for "ID", in: "ID"\n", i, j)) ; + ASSERT (j >= 0 && j < n) ; + DEBUG (cnt = 0) ; + while (CParent [j] == -2) + { + j = FLIP (Flag [j]) ; + PRINT2 ((" walk up to "ID" ", j)) ; + ASSERT (j >= 0 && j < n) ; + PRINT2 ((" CParent "ID"\n", CParent [j])) ; + ASSERT (cnt < n) ; + DEBUG (cnt++) ; + } + cnode = j ; + ASSERT (cnode >= 0 && cnode < n) ; + ASSERT (CParent [cnode] >= EMPTY && CParent [cnode] < n) ; + PRINT2 (("i "ID" is in component with cnode "ID"\n", i, cnode)) ; + ASSERT (Flag [cnode] == FLIP (cnode)) ; + + /* Mark all nodes along the path from i to cnode as being in the + * component whos repnode is cnode. Perform path compression. */ + j = FLIP (Flag [i]) ; + Flag [i] = FLIP (cnode) ; + DEBUG (cnt = 0) ; + while (CParent [j] == -2) + { + ASSERT (j >= 0 && j < n) ; + jnext = FLIP (Flag [j]) ; + PRINT2 ((" "ID" walk "ID" set cnode to "ID"\n", i, j, cnode)) ; + ASSERT (cnt < n) ; + DEBUG (cnt++) ; + Flag [j] = FLIP (cnode) ; + j = jnext ; + } + } + + /* At this point, all nodes fall into Types 1 or 2, as defined above. */ + +#ifndef NDEBUG + for (j = 0 ; j < n ; j++) + { + PRINT2 (("j %d CParent %d ", j, CParent [j])) ; + if (CParent [j] >= EMPTY && CParent [j] < n) + { + /* case 1: j is a repnode of a component */ + cnode = j ; + PRINT2 ((" a repnode\n")) ; + } + else + { + /* case 2: j is not a repnode of a component */ + cnode = FLIP (Flag [j]) ; + PRINT2 ((" repnode is %d\n", cnode)) ; + ASSERT (cnode >= 0 && cnode < n) ; + ASSERT (CParent [cnode] >= EMPTY && CParent [cnode] < n) ; + } + ASSERT (Flag [cnode] == FLIP (cnode)) ; + /* case 3 no longer holds */ + } +#endif + + /* ---------------------------------------------------------------------- */ + /* free workspace */ + /* ---------------------------------------------------------------------- */ + + C->ncol = n ; /* restore size for memory usage statistics */ + CHOLMOD(free_sparse) (&C, Common) ; + CHOLMOD(free_sparse) (&B, Common) ; + CHOLMOD(free) (csize, sizeof (Int), Cew, Common) ; + CHOLMOD(free) (3*n, sizeof (Int), Work3n, Common) ; + + /* ---------------------------------------------------------------------- */ + /* handle dense nodes */ + /* ---------------------------------------------------------------------- */ + + /* The separator tree has nodes with either no children or two or more + * children - with one exception. There may exist a single root node with + * exactly one child, which holds the dense rows/columns of the matrix. + * Delete this node if it exists. */ + + if (ndense > 0) + { + ASSERT (CParent [cdense] == EMPTY) ; /* cdense has no parent */ + /* find the children of cdense */ + nchild = 0 ; + for (j = 0 ; j < n ; j++) + { + if (CParent [j] == cdense) + { + nchild++ ; + child = j ; + } + } + if (nchild == 1) + { + /* the cdense node has just one child; merge the two nodes */ + PRINT1 (("root has one child\n")) ; + CParent [cdense] = -2 ; /* cdense is deleted */ + CParent [child] = EMPTY ; /* child becomes a root */ + for (j = 0 ; j < n ; j++) + { + if (Flag [j] == FLIP (cdense)) + { + /* j is a dense node */ + PRINT1 (("dense %d\n", j)) ; + Flag [j] = FLIP (child) ; + } + } + } + } + + /* ---------------------------------------------------------------------- */ + /* postorder the components */ + /* ---------------------------------------------------------------------- */ + + DEBUG (for (cnt = 0, j = 0 ; j < n ; j++) if (CParent [j] != -2) cnt++) ; + + /* use Cmember as workspace for Post [ */ + Post = Cmember ; + + /* cholmod_postorder uses Head and Iwork [0..2n]. It does not use Flag, + * which here holds the mapping of nodes to repnodes. It ignores all nodes + * for which CParent [j] < -1, so it operates just on the repnodes. */ + /* workspace: Head (n), Iwork (2*n) */ + ncomponents = CHOLMOD(postorder) (CParent, n, NULL, Post, Common) ; + ASSERT (cnt == ncomponents) ; + + /* use Iwork [0..n-1] as workspace for Ipost ( */ + Ipost = Iwork ; + DEBUG (for (j = 0 ; j < n ; j++) Ipost [j] = EMPTY) ; + + /* compute inverse postorder */ + for (c = 0 ; c < ncomponents ; c++) + { + cnode = Post [c] ; + ASSERT (cnode >= 0 && cnode < n) ; + Ipost [cnode] = c ; + ASSERT (Head [c] == EMPTY) ; + } + + /* adjust the parent array */ + /* Iwork [n..2n-1] used for NewParent [ */ + NewParent = Iwork + n ; + for (c = 0 ; c < ncomponents ; c++) + { + parent = CParent [Post [c]] ; + NewParent [c] = (parent == EMPTY) ? EMPTY : (Ipost [parent]) ; + } + for (c = 0 ; c < ncomponents ; c++) + { + CParent [c] = NewParent [c] ; + } + ASSERT (CHOLMOD(dump_parent) (CParent, ncomponents, "CParent", Common)) ; + + /* Iwork [n..2n-1] no longer needed for NewParent ] */ + /* Cmember no longer needed for Post ] */ + +#ifndef NDEBUG + /* count the number of children of each node */ + for (c = 0 ; c < ncomponents ; c++) + { + Cmember [c] = 0 ; + } + for (c = 0 ; c < ncomponents ; c++) + { + if (CParent [c] != EMPTY) Cmember [CParent [c]]++ ; + } + for (c = 0 ; c < ncomponents ; c++) + { + /* a node is either a leaf, or has 2 or more children */ + ASSERT (Cmember [c] == 0 || Cmember [c] >= 2) ; + } +#endif + + /* ---------------------------------------------------------------------- */ + /* place each node in its component */ + /* ---------------------------------------------------------------------- */ + + for (j = 0 ; j < n ; j++) + { + /* node j is in the cth component, whose repnode is cnode */ + cnode = FLIP (Flag [j]) ; + PRINT2 (("j "ID" flag "ID" cnode "ID"\n", + j, Flag [j], FLIP (Flag [j]))) ; + ASSERT (cnode >= 0 && cnode < n) ; + c = Ipost [cnode] ; + ASSERT (c >= 0 && c < ncomponents) ; + Cmember [j] = c ; + } + + /* Flag no longer needed for the node-to-component mapping */ + + /* done using Iwork [0..n-1] as workspace for Ipost ) */ + + /* ---------------------------------------------------------------------- */ + /* clear the Flag array */ + /* ---------------------------------------------------------------------- */ + + Common->mark = EMPTY ; + CHOLMOD_CLEAR_FLAG (Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* find the permutation */ + /* ---------------------------------------------------------------------- */ + + PRINT1 (("nd_camd: %d A->stype %d\n", nd_camd, A->stype)) ; + + if (nd_camd) + { + + /* ------------------------------------------------------------------ */ + /* apply camd, csymamd, or ccolamd using the Cmember constraints */ + /* ------------------------------------------------------------------ */ + + if (A->stype != 0) + { + /* ordering A+A', so fset and fsize are ignored. + * Add the upper/lower part to a symmetric lower/upper matrix by + * converting to unsymmetric mode + * workspace: Iwork (nrow) */ + B = CHOLMOD(copy) (A, 0, -1, Common) ; + if (Common->status < CHOLMOD_OK) + { + PRINT0 (("make symmetric failed\n")) ; + return (EMPTY) ; + } + ASSERT ((Int) (B->nrow) == n && (Int) (B->ncol) == n) ; + PRINT2 (("nested dissection (2)\n")) ; + B->stype = -1 ; + if (nd_camd == 2) + { + /* workspace: Head (nrow+1), Iwork (nrow) if symmetric-upper */ + ok = CHOLMOD(csymamd) (B, Cmember, Perm, Common) ; + } + else + { + /* workspace: Head (nrow), Iwork (4*nrow) */ + ok = CHOLMOD(camd) (B, NULL, 0, Cmember, Perm, Common) ; + } + CHOLMOD(free_sparse) (&B, Common) ; + if (!ok) + { + /* failed */ + PRINT0 (("camd/csymamd failed\n")) ; + return (EMPTY) ; + } + } + else + { + /* ordering A*A' or A(:,f)*A(:,f)' */ + /* workspace: Iwork (nrow if no fset; MAX(nrow,ncol) if fset) */ + if (!CHOLMOD(ccolamd) (A, fset, fsize, Cmember, Perm, Common)) + { + /* ccolamd failed */ + PRINT2 (("ccolamd failed\n")) ; + return (EMPTY) ; + } + } + + } + else + { + + /* ------------------------------------------------------------------ */ + /* natural ordering of each component */ + /* ------------------------------------------------------------------ */ + + /* use Iwork [0..n-1] for Next [ */ + Next = Iwork ; + + /* ------------------------------------------------------------------ */ + /* place the nodes in link lists, one list per component */ + /* ------------------------------------------------------------------ */ + + /* do so in reverse order, to preserve original ordering */ + for (j = n-1 ; j >= 0 ; j--) + { + /* node j is in the cth component */ + c = Cmember [j] ; + ASSERT (c >= 0 && c < ncomponents) ; + /* place node j in link list for component c */ + Next [j] = Head [c] ; + Head [c] = j ; + } + + /* ------------------------------------------------------------------ */ + /* order each node in each component */ + /* ------------------------------------------------------------------ */ + + k = 0 ; + for (c = 0 ; c < ncomponents ; c++) + { + for (j = Head [c] ; j != EMPTY ; j = Next [j]) + { + Perm [k++] = j ; + } + Head [c] = EMPTY ; + } + ASSERT (k == n) ; + + /* done using Iwork [0..n-1] for Next ] */ + } + + /* ---------------------------------------------------------------------- */ + /* clear workspace and return number of components */ + /* ---------------------------------------------------------------------- */ + + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + return (ncomponents) ; +} + +/* ========================================================================== */ +/* === cholmod_collapse_septree ============================================= */ +/* ========================================================================== */ + +/* cholmod_nested_dissection returns the separator tree that was used in the + * constrained minimum degree algorithm. Parameter settings (nd_small, + * nd_oksep, etc) that give a good fill-reducing ordering may give too fine of + * a separator tree for other uses (parallelism, multi-level LPDASA, etc). This + * function takes as input the separator tree computed by + * cholmod_nested_dissection, and collapses selected subtrees into single + * nodes. A subtree is collapsed if its root node (the separator) is large + * compared to the total number of nodes in the subtree, or if the subtree is + * small. Note that the separator tree may actually be a forest. + * + * nd_oksep and nd_small act just like the ordering parameters in Common. + * Returns the new number of nodes in the separator tree. + */ + +SuiteSparse_long CHOLMOD(collapse_septree) +( + /* ---- input ---- */ + size_t n, /* # of nodes in the graph */ + size_t ncomponents, /* # of nodes in the separator tree (must be <= n) */ + double nd_oksep, /* collapse if #sep >= nd_oksep * #nodes in subtree */ + size_t nd_small, /* collapse if #nodes in subtree < nd_small */ + /* ---- in/out --- */ + Int *CParent, /* size ncomponents; from cholmod_nested_dissection */ + Int *Cmember, /* size n; from cholmod_nested_dissection */ + /* --------------- */ + cholmod_common *Common +) +{ + Int *First, *Count, *Csubtree, *W, *Map ; + Int c, j, k, nc, sepsize, total_weight, parent, nc_new, first ; + int collapse = FALSE, ok = TRUE ; + size_t s ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (EMPTY) ; + RETURN_IF_NULL (CParent, EMPTY) ; + RETURN_IF_NULL (Cmember, EMPTY) ; + if (n < ncomponents) + { + ERROR (CHOLMOD_INVALID, "invalid separator tree") ; + return (EMPTY) ; + } + Common->status = CHOLMOD_OK ; + nc = ncomponents ; + if (n <= 1 || ncomponents <= 1) + { + /* no change; tree is one node already */ + return (nc) ; + } + + nd_oksep = MAX (0, nd_oksep) ; + nd_oksep = MIN (1, nd_oksep) ; + nd_small = MAX (4, nd_small) ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + /* s = 3*ncomponents */ + s = CHOLMOD(mult_size_t) (ncomponents, 3, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (EMPTY) ; + } + CHOLMOD(allocate_work) (0, s, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (EMPTY) ; + } + W = Common->Iwork ; + Count = W ; W += ncomponents ; /* size ncomponents */ + Csubtree = W ; W += ncomponents ; /* size ncomponents */ + First = W ; W += ncomponents ; /* size ncomponents */ + + /* ---------------------------------------------------------------------- */ + /* find the first descendant of each node of the separator tree */ + /* ---------------------------------------------------------------------- */ + + for (c = 0 ; c < nc ; c++) + { + First [c] = EMPTY ; + } + for (k = 0 ; k < nc ; k++) + { + for (c = k ; c != EMPTY && First [c] == -1 ; c = CParent [c]) + { + ASSERT (c >= 0 && c < nc) ; + First [c] = k ; + } + } + + /* ---------------------------------------------------------------------- */ + /* find the number of nodes of the graph in each node of the tree */ + /* ---------------------------------------------------------------------- */ + + for (c = 0 ; c < nc ; c++) + { + Count [c] = 0 ; + } + for (j = 0 ; j < (Int) n ; j++) + { + ASSERT (Cmember [j] >= 0 && Cmember [j] < nc) ; + Count [Cmember [j]]++ ; + } + + /* ---------------------------------------------------------------------- */ + /* find the number of nodes in each subtree */ + /* ---------------------------------------------------------------------- */ + + for (c = 0 ; c < nc ; c++) + { + /* each subtree includes its root */ + Csubtree [c] = Count [c] ; + PRINT1 ((ID" size "ID" parent "ID" first "ID"\n", + c, Count [c], CParent [c], First [c])) ; + } + + for (c = 0 ; c < nc ; c++) + { + /* add the subtree of the child, c, into the count of its parent */ + parent = CParent [c] ; + ASSERT (parent >= EMPTY && parent < nc) ; + if (parent != EMPTY) + { + Csubtree [parent] += Csubtree [c] ; + } + } + +#ifndef NDEBUG + /* the sum of the roots should be n */ + j = 0 ; + for (c = 0 ; c < nc ; c++) if (CParent [c] == EMPTY) j += Csubtree [c] ; + ASSERT (j == (Int) n) ; +#endif + + /* ---------------------------------------------------------------------- */ + /* find subtrees to collapse */ + /* ---------------------------------------------------------------------- */ + + /* consider all nodes in reverse post-order */ + for (c = nc-1 ; c >= 0 ; c--) + { + /* consider the subtree rooted at node c */ + sepsize = Count [c] ; + total_weight = Csubtree [c] ; + PRINT1 (("Node "ID" sepsize "ID" subtree "ID" ratio %g\n", c, sepsize, + total_weight, ((double) sepsize)/((double) total_weight))) ; + first = First [c] ; + if (first < c && /* c must not be a leaf */ + (sepsize > nd_oksep * total_weight || total_weight < (int) nd_small)) + { + /* this separator is too large, or the subtree is too small. + * collapse the tree, by converting the entire subtree rooted at + * c into a single node. The subtree consists of all nodes from + * First[c] to the root c. Flag all nodes from First[c] to c-1 + * as dead. + */ + collapse = TRUE ; + for (k = first ; k < c ; k++) + { + CParent [k] = -2 ; + PRINT1 ((" collapse node "ID"\n", k)) ; + } + /* continue at the next node, first-1 */ + c = first ; + } + } + + PRINT1 (("collapse: %d\n", collapse)) ; + + /* ---------------------------------------------------------------------- */ + /* compress the tree */ + /* ---------------------------------------------------------------------- */ + + Map = Count ; /* Count no longer needed */ + + nc_new = nc ; + if (collapse) + { + nc_new = 0 ; + for (c = 0 ; c < nc ; c++) + { + Map [c] = nc_new ; + if (CParent [c] >= EMPTY) + { + /* node c is alive, and becomes node Map[c] in the new tree. + * Increment nc_new for the next node c. */ + nc_new++ ; + } + } + PRINT1 (("Collapse the tree from "ID" to "ID" nodes\n", nc, nc_new)) ; + ASSERT (nc_new > 0) ; + for (c = 0 ; c < nc ; c++) + { + parent = CParent [c] ; + if (parent >= EMPTY) + { + /* node c is alive */ + CParent [Map [c]] = (parent == EMPTY) ? EMPTY : Map [parent] ; + } + } + for (j = 0 ; j < (Int) n ; j++) + { + PRINT1 (("j "ID" Cmember[j] "ID" Map[Cmember[j]] "ID"\n", + j, Cmember [j], Map [Cmember [j]])) ; + Cmember [j] = Map [Cmember [j]] ; + } + } + + /* ---------------------------------------------------------------------- */ + /* return new size of separator tree */ + /* ---------------------------------------------------------------------- */ + + return (nc_new) ; +} +#endif diff --git a/src/CHOLMOD/Partition/lesser.txt b/src/CHOLMOD/Partition/lesser.txt new file mode 100644 index 0000000..8add30a --- /dev/null +++ b/src/CHOLMOD/Partition/lesser.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/src/CHOLMOD/README.txt b/src/CHOLMOD/README.txt new file mode 100644 index 0000000..08eb0a2 --- /dev/null +++ b/src/CHOLMOD/README.txt @@ -0,0 +1,81 @@ +CHOLMOD: a sparse CHOLesky MODification package, Copyright (c) 2005-2012. +http://www.suitesparse.com +----------------------------------------------- + + CHOLMOD is a set of routines for factorizing sparse symmetric positive + definite matrices of the form A or AA', updating/downdating a sparse + Cholesky factorization, solving linear systems, updating/downdating + the solution to the triangular system Lx=b, and many other sparse matrix + functions for both symmetric and unsymmetric matrices. Its supernodal + Cholesky factorization relies on LAPACK and the Level-3 BLAS, and obtains + a substantial fraction of the peak performance of the BLAS. Both real and + complex matrices are supported. CHOLMOD is written in ANSI/ISO C, with both + C and MATLAB interfaces. This code works on Microsoft Windows and many + versions of Unix and Linux. + + +Some Modules of CHOLMOD are copyrighted by the University of Florida (the +Core and Partition Modules). The rest are copyrighted by the authors: +Timothy A. Davis (all of them), and William W. Hager (the Modify Module). + +CHOLMOD relies on several other packages: AMD, CAMD, COLAMD, CCOLAMD, +SuiteSparse_config, METIS, the BLAS, and LAPACK. All but METIS, the BLAS, and +LAPACK are part of SuiteSparse. + +AMD is authored by T. Davis, Iain Duff, and Patrick Amestoy. +COLAMD is authored by T. Davis and Stefan Larimore, with algorithmic design +in collaboration with John Gilbert and Esmond Ng. +CCOLAMD is authored by T. Davis and Siva Rajamanickam. +CAMD is authored by T. Davis and Y. Chen. + +LAPACK and the BLAS are authored by Jack Dongarra and many others. +LAPACK is available at http://www.netlib.org/lapack + +METIS is authored by George Karypis, Univ. of Minnesota. Its use in CHOLMOD +is optional. See http://www-users.cs.umn.edu/~karypis/metis. +Place a copy of the metis-4.0 directory in the same directory that +contains the CHOLMOD, AMD, COLAMD, and CCOLAMD directories prior to compiling +with "make". + +If you do not wish to use METIS, you must edit SuiteSparse_config and change +the line: + + CHOLMOD_CONFIG = + +to + + CHOLMOD_CONFIG = -DNPARTITION + +The CHOLMOD, AMD, COLAMD, CCOLAMD, and SuiteSparse)config directories must all +reside in a common parent directory. To compile all these libraries, edit +SuiteSparse)config/SuiteSparse)config.mk to reflect your environment (C +compiler, location of the BLAS, and so on) and then type "make" in either the +CHOLMOD directory or in the parent directory of CHOLMOD. See each package for +more details on how to compile them. + +For use in MATLAB (on any system, including Windows): start MATLAB, +cd to the CHOLMOD/MATLAB directory, and type cholmod_make in the MATLAB +Command Window. This is the best way to compile CHOLMOD for MATLAB; it +provides a workaround for a METIS design feature, in which METIS terminates +your program (and thus MATLAB) if it runs out of memory. Using cholmod_make +also ensures your mexFunctions are compiled with -fexceptions, so that +exceptions are handled properly (when hitting control-C in the MATLAB command +window, for example). + +On the Pentium, do NOT use the Intel MKL BLAS prior to MKL Version 8.0 with +CHOLMOD. Older versions (prior to 8.0) have a bug in dgemm when computing +A*B'. The bug generates a NaN result, when the inputs are well-defined. Use +the Goto BLAS or the MKL v8.0 BLAS instead. The Goto BLAS is faster and more +reliable. See http://www.tacc.utexas.edu/~kgoto/ or +http://www.cs.utexas.edu/users/flame/goto/. +Sadly, the Intel MKL BLAS 7.x is the default for MATLAB 7.0.4. See +http://www.mathworks.com/support/bugreports/details.html?rp=252103 for more +details. To workaround this problem on Linux, set environment variable +BLAS_VERSION to libmkl_p3.so:libguide.so. On Windows, set environment variable +BLAS_VERSION to mkl_p3.dll. Better yet, get MATLAB 7sp3 (MATLAB 7.1) or later. + +Acknowledgements: this work was supported in part by the National Science +Foundation (NFS CCR-0203270 and DMS-9803599), and a grant from Sandia National +Laboratories (Dept. of Energy) which supported the development of CHOLMOD's +Partition Module. + diff --git a/src/CHOLMOD/Supernodal/License.txt b/src/CHOLMOD/Supernodal/License.txt new file mode 100644 index 0000000..d6b6abf --- /dev/null +++ b/src/CHOLMOD/Supernodal/License.txt @@ -0,0 +1,25 @@ +CHOLMOD/Supernodal Module. +Copyright (C) 2005-2006, Timothy A. Davis +CHOLMOD is also available under other licenses; contact authors for details. +http://www.suitesparse.com + +Note that this license is for the CHOLMOD/Supernodal module only. +All CHOLMOD modules are licensed separately. + + +-------------------------------------------------------------------------------- + + +This Module is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This Module is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this Module; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/src/CHOLMOD/Supernodal/cholmod_super_numeric.c b/src/CHOLMOD/Supernodal/cholmod_super_numeric.c new file mode 100644 index 0000000..a8ce274 --- /dev/null +++ b/src/CHOLMOD/Supernodal/cholmod_super_numeric.c @@ -0,0 +1,307 @@ +/* ========================================================================== */ +/* === Supernodal/cholmod_super_numeric ===================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Supernodal Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Supernodal Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Computes the Cholesky factorization of A+beta*I or A*F+beta*I. Only the + * the lower triangular part of A+beta*I or A*F+beta*I is accessed. The + * matrices A and F must already be permuted according to the fill-reduction + * permutation L->Perm. cholmod_factorize is an "easy" wrapper for this code + * which applies that permutation. beta is real. + * + * Symmetric case: A is a symmetric (lower) matrix. F is not accessed. + * With a fill-reducing permutation, A(p,p) should be passed instead, where is + * p is L->Perm. + * + * Unsymmetric case: A is unsymmetric, and F must be present. Normally, F=A'. + * With a fill-reducing permutation, A(p,f) and A(p,f)' should be passed as A + * and F, respectively, where f is a list of the subset of the columns of A. + * + * The input factorization L must be supernodal (L->is_super is TRUE). It can + * either be symbolic or numeric. In the first case, L has been analyzed by + * cholmod_analyze or cholmod_super_symbolic, but the matrix has not yet been + * numerically factorized. The numerical values are allocated here and the + * factorization is computed. In the second case, a prior matrix has been + * analyzed and numerically factorized, and a new matrix is being factorized. + * The numerical values of L are replaced with the new numerical factorization. + * + * L->is_ll is ignored, and set to TRUE. This routine always computes an LL' + * factorization. Supernodal LDL' factorization is not (yet) supported. + * FUTURE WORK: perform a supernodal LDL' factorization if L->is_ll is FALSE. + * + * Uses BLAS routines dsyrk, dgemm, dtrsm, and the LAPACK routine dpotrf. + * The supernodal solver uses BLAS routines dtrsv, dgemv, dtrsm, and dgemm. + * + * If the matrix is not positive definite the routine returns TRUE, but sets + * Common->status to CHOLMOD_NOT_POSDEF and L->minor is set to the column at + * which the failure occurred. The supernode containing the non-positive + * diagonal entry is set to zero (this includes columns to the left of L->minor + * in the same supernode), as are all subsequent supernodes. + * + * workspace: Flag (nrow), Head (nrow+1), Iwork (2*nrow + 4*nsuper). + * Allocates temporary space of size L->maxcsize * sizeof(double) + * (twice that for the complex/zomplex case). + * + * If L is supernodal symbolic on input, it is converted to a supernodal numeric + * factor on output, with an xtype of real if A is real, or complex if A is + * complex or zomplex. If L is supernodal numeric on input, its xtype must + * match A (except that L can be complex and A zomplex). The xtype of A and F + * must match. + */ + +#ifndef NSUPERNODAL + +#include "cholmod_internal.h" +#include "cholmod_supernodal.h" +#include "igraph_blas_internal.h" +#include "igraph_lapack_internal.h" + +/* ========================================================================== */ +/* === TEMPLATE codes for GPU and regular numeric factorization ============= */ +/* ========================================================================== */ + +#ifdef GPU_BLAS +#define REAL +#include "t_cholmod_gpu.c" +#define COMPLEX +#include "t_cholmod_gpu.c" +#define ZOMPLEX +#include "t_cholmod_gpu.c" +#endif + +#define REAL +#include "t_cholmod_super_numeric.c" +/* #define COMPLEX */ +/* #include "t_cholmod_super_numeric.c" */ +/* #define ZOMPLEX */ +/* #include "t_cholmod_super_numeric.c" */ + +/* ========================================================================== */ +/* === cholmod_super_numeric ================================================ */ +/* ========================================================================== */ + +/* Returns TRUE if successful, or if the matrix is not positive definite. + * Returns FALSE if out of memory, inputs are invalid, or other fatal error + * occurs. + */ + +int CHOLMOD(super_numeric) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + cholmod_sparse *F, /* F = A' or A(:,f)' */ + double beta [2], /* beta*I is added to diagonal of matrix to factorize */ + /* ---- in/out --- */ + cholmod_factor *L, /* factorization */ + /* --------------- */ + cholmod_common *Common +) +{ + cholmod_dense *C ; + Int *Super, *Map, *SuperMap ; + size_t maxcsize ; + Int nsuper, n, i, k, s, stype, nrow ; + int ok = TRUE, symbolic ; + size_t t, w ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_PATTERN, CHOLMOD_COMPLEX, FALSE) ; + stype = A->stype ; + if (stype < 0) + { + if (A->nrow != A->ncol || A->nrow != L->n) + { + ERROR (CHOLMOD_INVALID, "invalid dimensions") ; + return (FALSE) ; + } + } + else if (stype == 0) + { + if (A->nrow != L->n) + { + ERROR (CHOLMOD_INVALID, "invalid dimensions") ; + return (FALSE) ; + } + RETURN_IF_NULL (F, FALSE) ; + RETURN_IF_XTYPE_INVALID (F, CHOLMOD_REAL, CHOLMOD_ZOMPLEX, FALSE) ; + if (A->nrow != F->ncol || A->ncol != F->nrow || F->stype != 0) + { + ERROR (CHOLMOD_INVALID, "F invalid") ; + return (FALSE) ; + } + if (A->xtype != F->xtype) + { + ERROR (CHOLMOD_INVALID, "A and F must have same xtype") ; + return (FALSE) ; + } + } + else + { + /* symmetric upper case not suppored */ + ERROR (CHOLMOD_INVALID, "symmetric upper case not supported") ; + return (FALSE) ; + } + if (!(L->is_super)) + { + ERROR (CHOLMOD_INVALID, "L not supernodal") ; + return (FALSE) ; + } + if (L->xtype != CHOLMOD_PATTERN) + { + if (! ((A->xtype == CHOLMOD_REAL && L->xtype == CHOLMOD_REAL) + || (A->xtype == CHOLMOD_COMPLEX && L->xtype == CHOLMOD_COMPLEX) + || (A->xtype == CHOLMOD_ZOMPLEX && L->xtype == CHOLMOD_COMPLEX))) + { + ERROR (CHOLMOD_INVALID, "complex type mismatch") ; + return (FALSE) ; + } + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace in Common */ + /* ---------------------------------------------------------------------- */ + + nsuper = L->nsuper ; + maxcsize = L->maxcsize ; + nrow = A->nrow ; + n = nrow ; + + PRINT1 (("nsuper "ID" maxcsize %g\n", nsuper, (double) maxcsize)) ; + ASSERT (nsuper >= 0 && maxcsize > 0) ; + + /* w = 2*n + 4*nsuper */ + w = CHOLMOD(mult_size_t) (n, 2, &ok) ; + t = CHOLMOD(mult_size_t) (nsuper, 4, &ok) ; + w = CHOLMOD(add_size_t) (w, t, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (n, w, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + return (FALSE) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get the current factor L and allocate numerical part, if needed */ + /* ---------------------------------------------------------------------- */ + + Super = L->super ; + symbolic = (L->xtype == CHOLMOD_PATTERN) ; + if (symbolic) + { + /* convert to supernodal numeric by allocating L->x */ + CHOLMOD(change_factor) ( + (A->xtype == CHOLMOD_REAL) ? CHOLMOD_REAL : CHOLMOD_COMPLEX, + TRUE, TRUE, TRUE, TRUE, L, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* the factor L remains in symbolic supernodal form */ + return (FALSE) ; + } + } + ASSERT (L->dtype == DTYPE) ; + ASSERT (L->xtype == CHOLMOD_REAL || L->xtype == CHOLMOD_COMPLEX) ; + + /* supernodal LDL' is not supported */ + L->is_ll = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* get more workspace */ + /* ---------------------------------------------------------------------- */ + + C = CHOLMOD(allocate_dense) (maxcsize, 1, maxcsize, L->xtype, Common) ; + if (Common->status < CHOLMOD_OK) + { + int status = Common->status ; + if (symbolic) + { + /* Change L back to symbolic, since the numeric values are not + * initialized. This cannot fail. */ + CHOLMOD(change_factor) (CHOLMOD_PATTERN, TRUE, TRUE, TRUE, TRUE, + L, Common) ; + } + /* the factor L is now back to the form it had on input */ + Common->status = status ; + return (FALSE) ; + } + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + SuperMap = Common->Iwork ; /* size n (i/i/l) */ + Map = Common->Flag ; /* size n, use Flag as workspace for Map array */ + for (i = 0 ; i < n ; i++) + { + Map [i] = EMPTY ; + } + + /* ---------------------------------------------------------------------- */ + /* find the mapping of nodes to relaxed supernodes */ + /* ---------------------------------------------------------------------- */ + + /* SuperMap [k] = s if column k is contained in supernode s */ + for (s = 0 ; s < nsuper ; s++) + { + PRINT1 (("Super ["ID"] "ID" ncols "ID"\n", + s, Super[s], Super[s+1]-Super[s])); + for (k = Super [s] ; k < Super [s+1] ; k++) + { + SuperMap [k] = s ; + PRINT2 (("relaxed SuperMap ["ID"] = "ID"\n", k, SuperMap [k])) ; + } + } + + /* ---------------------------------------------------------------------- */ + /* supernodal numerical factorization, using template routine */ + /* ---------------------------------------------------------------------- */ + + switch (A->xtype) + { + case CHOLMOD_REAL: + ok = r_cholmod_super_numeric (A, F, beta, L, C, Common) ; + break ; + + /* case CHOLMOD_COMPLEX: */ + /* ok = c_cholmod_super_numeric (A, F, beta, L, C, Common) ; */ + /* break ; */ + + /* case CHOLMOD_ZOMPLEX: */ + /* /\* This operates on complex L, not zomplex *\/ */ + /* ok = z_cholmod_super_numeric (A, F, beta, L, C, Common) ; */ + /* break ; */ + } + + /* ---------------------------------------------------------------------- */ + /* clear Common workspace, free temp workspace C, and return */ + /* ---------------------------------------------------------------------- */ + + /* Flag array was used as workspace, clear it */ + Common->mark = EMPTY ; + /* CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + CHOLMOD(free_dense) (&C, Common) ; + return (ok) ; +} +#endif diff --git a/src/CHOLMOD/Supernodal/cholmod_super_solve.c b/src/CHOLMOD/Supernodal/cholmod_super_solve.c new file mode 100644 index 0000000..33fffab --- /dev/null +++ b/src/CHOLMOD/Supernodal/cholmod_super_solve.c @@ -0,0 +1,217 @@ +/* ========================================================================== */ +/* === Supernodal/cholmod_super_solve ======================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Supernodal Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Supernodal Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Solve Lx=b or L'x=b for a supernodal factorization. These routines do not + * apply the permutation L->Perm. See cholmod_solve for a more general + * interface that performs that operation. + */ + +#ifndef NSUPERNODAL + +#include "cholmod_internal.h" +#include "cholmod_supernodal.h" +#include "igraph_blas_internal.h" + +/* ========================================================================== */ +/* === TEMPLATE ============================================================= */ +/* ========================================================================== */ + +#define REAL +#include "t_cholmod_super_solve.c" +/* #define COMPLEX */ +/* #include "t_cholmod_super_solve.c" */ + +/* ========================================================================== */ +/* === cholmod_super_lsolve ================================================= */ +/* ========================================================================== */ + +/* Solve Lx=b where x and b are of size n-by-nrhs. b is overwritten by the + * solution x. On input, b is stored in col-major order with leading dimension + * of d, and on output x is stored in the same manner. + * + * The contents of the workspace E are undefined on both input and output. + * + * workspace: none + */ + +int CHOLMOD(super_lsolve) /* TRUE if OK, FALSE if BLAS overflow occured */ +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to use for the forward solve */ + /* ---- output ---- */ + cholmod_dense *X, /* b on input, solution to Lx=b on output */ + /* ---- workspace ---- */ + cholmod_dense *E, /* workspace of size nrhs*(L->maxesize) */ + /* --------------- */ + cholmod_common *Common +) +{ + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_NULL (X, FALSE) ; + RETURN_IF_NULL (E, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_COMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_COMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (E, CHOLMOD_REAL, CHOLMOD_COMPLEX, FALSE) ; + if (L->xtype != X->xtype) + { + ERROR (CHOLMOD_INVALID, "L and X must have the same xtype") ; + return (FALSE) ; + } + if (L->xtype != E->xtype) + { + ERROR (CHOLMOD_INVALID, "L and E must have the same xtype") ; + return (FALSE) ; + } + if (X->d < X->nrow || L->n != X->nrow) + { + ERROR (CHOLMOD_INVALID, "X and L dimensions must match") ; + return (FALSE) ; + } + if (E->nzmax < X->ncol * (L->maxesize)) + { + ERROR (CHOLMOD_INVALID, "workspace E not large enough") ; + return (FALSE) ; + } + if (!(L->is_ll) || !(L->is_super)) + { + ERROR (CHOLMOD_INVALID, "L not supernodal") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + ASSERT (IMPLIES (L->n == 0, L->nsuper == 0)) ; + if (L->n == 0 || X->ncol == 0) + { + /* nothing to do */ + return (TRUE) ; + } + + /* ---------------------------------------------------------------------- */ + /* solve Lx=b using template routine */ + /* ---------------------------------------------------------------------- */ + + switch (L->xtype) + { + + case CHOLMOD_REAL: + r_cholmod_super_lsolve (L, X, E, Common) ; + break ; + + /* case CHOLMOD_COMPLEX: */ + /* c_cholmod_super_lsolve (L, X, E, Common) ; */ + /* break ; */ + } + + if (CHECK_BLAS_INT && !Common->blas_ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large for the BLAS") ; + } + return (Common->blas_ok) ; +} + + +/* ========================================================================== */ +/* === cholmod_super_ltsolve ================================================ */ +/* ========================================================================== */ + +/* Solve L'x=b where x and b are of size n-by-nrhs. b is overwritten by the + * solution x. On input, b is stored in col-major order with leading dimension + * of d, and on output x is stored in the same manner. + * + * The contents of the workspace E are undefined on both input and output. + * + * workspace: none + */ + +int CHOLMOD(super_ltsolve) /* TRUE if OK, FALSE if BLAS overflow occured */ +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to use for the backsolve */ + /* ---- output ---- */ + cholmod_dense *X, /* b on input, solution to L'x=b on output */ + /* ---- workspace ---- */ + cholmod_dense *E, /* workspace of size nrhs*(L->maxesize) */ + /* --------------- */ + cholmod_common *Common +) +{ + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_NULL (X, FALSE) ; + RETURN_IF_NULL (E, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_REAL, CHOLMOD_COMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (X, CHOLMOD_REAL, CHOLMOD_COMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (E, CHOLMOD_REAL, CHOLMOD_COMPLEX, FALSE) ; + if (L->xtype != X->xtype) + { + ERROR (CHOLMOD_INVALID, "L and X must have the same xtype") ; + return (FALSE) ; + } + if (L->xtype != E->xtype) + { + ERROR (CHOLMOD_INVALID, "L and E must have the same xtype") ; + return (FALSE) ; + } + if (X->d < X->nrow || L->n != X->nrow) + { + ERROR (CHOLMOD_INVALID, "X and L dimensions must match") ; + return (FALSE) ; + } + if (E->nzmax < X->ncol * (L->maxesize)) + { + ERROR (CHOLMOD_INVALID, "workspace E not large enough") ; + return (FALSE) ; + } + if (!(L->is_ll) || !(L->is_super)) + { + ERROR (CHOLMOD_INVALID, "L not supernodal") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + ASSERT (IMPLIES (L->n == 0, L->nsuper == 0)) ; + if (L->n == 0 || X->ncol == 0) + { + /* nothing to do */ + return (TRUE) ; + } + + /* ---------------------------------------------------------------------- */ + /* solve Lx=b using template routine */ + /* ---------------------------------------------------------------------- */ + + switch (L->xtype) + { + + case CHOLMOD_REAL: + r_cholmod_super_ltsolve (L, X, E, Common) ; + break ; + + /* case CHOLMOD_COMPLEX: */ + /* c_cholmod_super_ltsolve (L, X, E, Common) ; */ + /* break ; */ + } + + if (CHECK_BLAS_INT && !Common->blas_ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large for the BLAS") ; + } + return (Common->blas_ok) ; +} +#endif diff --git a/src/CHOLMOD/Supernodal/cholmod_super_symbolic.c b/src/CHOLMOD/Supernodal/cholmod_super_symbolic.c new file mode 100644 index 0000000..70ea728 --- /dev/null +++ b/src/CHOLMOD/Supernodal/cholmod_super_symbolic.c @@ -0,0 +1,862 @@ +/* ========================================================================== */ +/* === Supernodal/cholmod_super_symbolic ==================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Supernodal Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Supernodal Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Supernodal symbolic analysis of the LL' factorization of A, A*A', + * A(:,f)*A(:,f)'. + * + * This routine must be preceded by a simplicial symbolic analysis + * (cholmod_rowcolcounts). See cholmod_analyze.c for an example of how to use + * this routine. + * + * The user need not call this directly; cholmod_analyze is a "simple" wrapper + * for this routine. + * + * Symmetric case: + * + * A is stored in column form, with entries stored in the upper triangular + * part. Entries in the lower triangular part are ignored. + * + * Unsymmetric case: + * + * A is stored in column form. If F is equal to the transpose of A, then + * A*A' is analyzed. F can include a subset of the columns of A + * (F=A(:,f)'), in which case F*F' is analyzed. + * + * Requires Parent and L->ColCount to be defined on input; these are the + * simplicial Parent and ColCount arrays as computed by cholmod_rowcolcounts. + * Does not use L->Perm; the input matrices A and F must already be properly + * permuted. Allocates and computes the supernodal pattern of L (L->super, + * L->pi, L->px, and L->s). Does not allocate the real part (L->x). + * + * Supports any xtype (pattern, real, complex, or zomplex). + */ + +#ifndef NSUPERNODAL + +#include "cholmod_internal.h" +#include "cholmod_supernodal.h" + + +/* ========================================================================== */ +/* === subtree ============================================================== */ +/* ========================================================================== */ + +/* In the symmetric case, traverse the kth row subtree from the nonzeros in + * A (0:k1-1,k) and add the new entries found to the pattern of the kth row + * of L. The current supernode s contains the diagonal block k1:k2-1, so it + * can be skipped. + * + * In the unsymmetric case, the nonzero pattern of A*F is computed one column + * at a time (thus, the total time spent in this function is bounded below by + * the time taken to multiply A*F, which can be high if A is tall and thin). + * The kth column is A*F(:,k), or the set union of all columns A(:,j) for which + * F(j,k) is nonzero. This routine is called once for each entry j. Only the + * upper triangular part is needed, so only A (0:k1-1,j) is accessed, where + * k1:k2-1 are the columns of the current supernode s (k is in the range k1 to + * k2-1). + * + * If A is sorted, then the total time taken by this function is proportional + * to the number of nonzeros in the strictly block upper triangular part of A, + * plus the number of entries in the strictly block lower triangular part of + * the supernodal part of L. This excludes entries in the diagonal blocks + * corresponding to the columns in each supernode. That is, if k1:k2-1 are + * in a single supernode, then only A (0:k1-1,k1:k2-1) are accessed. + * + * For the unsymmetric case, only the strictly block upper triangular part + * of A*F is constructed. + * + * Only adds column indices corresponding to the leading columns of each + * relaxed supernode. + */ + +static void subtree +( + /* inputs, not modified: */ + Int j, /* j = k for symmetric case */ + Int k, + Int Ap [ ], + Int Ai [ ], + Int Anz [ ], + Int SuperMap [ ], + Int Sparent [ ], + Int mark, + Int sorted, /* true if the columns of A are sorted */ + Int k1, /* only consider A (0:k1-1,k) */ + + /* input/output: */ + Int Flag [ ], + Int Ls [ ], + Int Lpi2 [ ] +) +{ + Int p, pend, i, si ; + p = Ap [j] ; + pend = (Anz == NULL) ? (Ap [j+1]) : (p + Anz [j]) ; + + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i < k1) + { + /* (i,k) is an entry in the upper triangular part of A or A*F'. + * symmetric case: A(i,k) is nonzero (j=k). + * unsymmetric case: A(i,j) and F(j,k) are both nonzero. + * + * Column i is in supernode si = SuperMap [i]. Follow path from si + * to root of supernodal etree, stopping at the first flagged + * supernode. The root of the row subtree is supernode SuperMap[k], + * which is flagged already. This traversal will stop there, or it + * might stop earlier if supernodes have been flagged by previous + * calls to this routine for the same k. */ + for (si = SuperMap [i] ; Flag [si] < mark ; si = Sparent [si]) + { + ASSERT (si <= SuperMap [k]) ; + Ls [Lpi2 [si]++] = k ; + Flag [si] = mark ; + } + } + else if (sorted) + { + break ; + } + } +} + + +/* clear workspace used by cholmod_super_symbolic */ +#define FREE_WORKSPACE \ +{ \ + /* CHOLMOD(clear_flag) (Common) ; */ \ + CHOLMOD_CLEAR_FLAG (Common) ; \ + for (k = 0 ; k <= nfsuper ; k++) \ + { \ + Head [k] = EMPTY ; \ + } \ + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; \ +} \ + + +/* ========================================================================== */ +/* === cholmod_super_symbolic2 ============================================== */ +/* ========================================================================== */ + +/* Analyze for supernodal Cholesky or multifrontal QR. CHOLMOD itself always + * analyzes for supernodal Cholesky, of course. The "for_cholesky = TRUE" + * option is used by SuiteSparseQR only. */ + +int CHOLMOD(super_symbolic2) +( + /* ---- input ---- */ + int for_cholesky, /* Cholesky if TRUE, QR if FALSE */ + cholmod_sparse *A, /* matrix to analyze */ + cholmod_sparse *F, /* F = A' or A(:,f)' */ + Int *Parent, /* elimination tree */ + /* ---- in/out --- */ + cholmod_factor *L, /* simplicial symbolic on input, + * supernodal symbolic on output */ + /* --------------- */ + cholmod_common *Common +) +{ + double zrelax0, zrelax1, zrelax2, xxsize ; + Int *Wi, *Wj, *Super, *Snz, *Ap, *Ai, *Flag, *Head, *Ls, *Lpi, *Lpx, *Fnz, + *Sparent, *Anz, *SuperMap, *Merged, *Nscol, *Zeros, *Fp, *Fj, + *ColCount, *Lpi2, *Lsuper, *Iwork ; + Int nsuper, d, n, j, k, s, mark, parent, p, pend, k1, k2, packed, nscol, + nsrow, ndrow1, ndrow2, stype, ssize, xsize, sparent, plast, slast, + csize, maxcsize, ss, nscol0, nscol1, ns, nfsuper, newzeros, totzeros, + merge, snext, esize, maxesize, nrelax0, nrelax1, nrelax2, Asorted ; + size_t w ; + int ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* check inputs */ + /* ---------------------------------------------------------------------- */ + + RETURN_IF_NULL_COMMON (FALSE) ; + RETURN_IF_NULL (A, FALSE) ; + RETURN_IF_NULL (L, FALSE) ; + RETURN_IF_NULL (Parent, FALSE) ; + RETURN_IF_XTYPE_INVALID (A, CHOLMOD_PATTERN, CHOLMOD_ZOMPLEX, FALSE) ; + RETURN_IF_XTYPE_INVALID (L, CHOLMOD_PATTERN, CHOLMOD_PATTERN, FALSE) ; + stype = A->stype ; + if (stype < 0) + { + /* invalid symmetry; symmetric lower form not supported */ + ERROR (CHOLMOD_INVALID, "symmetric lower not supported") ; + return (FALSE) ; + } + if (stype == 0) + { + /* F must be present in the unsymmetric case */ + RETURN_IF_NULL (F, FALSE) ; + } + if (L->is_super) + { + /* L must be a simplicial symbolic factor */ + ERROR (CHOLMOD_INVALID, "L must be symbolic on input") ; + return (FALSE) ; + } + Common->status = CHOLMOD_OK ; + + /* ---------------------------------------------------------------------- */ + /* allocate workspace */ + /* ---------------------------------------------------------------------- */ + + n = A->nrow ; + + /* w = 5*n */ + w = CHOLMOD(mult_size_t) (n, 5, &ok) ; + if (!ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + return (FALSE) ; + } + + CHOLMOD(allocate_work) (n, w, 0, Common) ; + if (Common->status < CHOLMOD_OK) + { + /* out of memory */ + return (FALSE) ; + } + ASSERT (CHOLMOD(dump_work) (TRUE, TRUE, 0, Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + /* A is now either A or triu(A(p,p)) for the symmetric case. It is either + * A or A(p,f) for the unsymmetric case (both in column form). It can be + * either packed or unpacked, and either sorted or unsorted. Entries in + * the lower triangular part may be present if A is symmetric, but these + * are ignored. */ + + Ap = A->p ; + Ai = A->i ; + Anz = A->nz ; + + if (stype != 0) + { + /* F not accessed */ + Fp = NULL ; + Fj = NULL ; + Fnz = NULL ; + packed = TRUE ; + } + else + { + /* F = A(:,f) or A(p,f) in packed row form, either sorted or unsorted */ + Fp = F->p ; + Fj = F->i ; + Fnz = F->nz ; + packed = F->packed ; + } + + ColCount = L->ColCount ; + + nrelax0 = Common->nrelax [0] ; + nrelax1 = Common->nrelax [1] ; + nrelax2 = Common->nrelax [2] ; + + zrelax0 = Common->zrelax [0] ; + zrelax1 = Common->zrelax [1] ; + zrelax2 = Common->zrelax [2] ; + + zrelax0 = IS_NAN (zrelax0) ? 0 : zrelax0 ; + zrelax1 = IS_NAN (zrelax1) ? 0 : zrelax1 ; + zrelax2 = IS_NAN (zrelax2) ? 0 : zrelax2 ; + + ASSERT (CHOLMOD(dump_parent) (Parent, n, "Parent", Common)) ; + + /* ---------------------------------------------------------------------- */ + /* get workspace */ + /* ---------------------------------------------------------------------- */ + + /* Sparent, Snz, and Merged could be allocated later, of size nfsuper */ + + Iwork = Common->Iwork ; + Wi = Iwork ; /* size n (i/l/l). Lpi2 is i/l/l */ + Wj = Iwork + n ; /* size n (i/l/l). Zeros is i/l/l */ + Sparent = Iwork + 2*((size_t) n) ; /* size nfsuper <= n [ */ + Snz = Iwork + 3*((size_t) n) ; /* size nfsuper <= n [ */ + Merged = Iwork + 4*((size_t) n) ; /* size nfsuper <= n [ */ + + Flag = Common->Flag ; /* size n */ + Head = Common->Head ; /* size n+1 */ + + /* ---------------------------------------------------------------------- */ + /* find the fundamental supernodes */ + /* ---------------------------------------------------------------------- */ + + /* count the number of children of each node, using Wi [ */ + for (j = 0 ; j < n ; j++) + { + Wi [j] = 0 ; + } + for (j = 0 ; j < n ; j++) + { + parent = Parent [j] ; + if (parent != EMPTY) + { + Wi [parent]++ ; + } + } + + Super = Head ; /* use Head [0..nfsuper] as workspace for Super list ( */ + + /* column 0 always starts a new supernode */ + nfsuper = (n == 0) ? 0 : 1 ; /* number of fundamental supernodes */ + Super [0] = 0 ; + + for (j = 1 ; j < n ; j++) + { + /* check if j starts new supernode, or in the same supernode as j-1 */ + if (Parent [j-1] != j /* parent of j-1 is not j */ + || (ColCount [j-1] != ColCount [j] + 1) /* j-1 not subset of j*/ + || Wi [j] > 1) /* j has more than one child */ + { + /* j is the leading node of a supernode */ + Super [nfsuper++] = j ; + } + } + Super [nfsuper] = n ; + + /* contents of Wi no longer needed for child count ] */ + + Nscol = Wi ; /* use Wi as size-nfsuper workspace for Nscol [ */ + + /* ---------------------------------------------------------------------- */ + /* find the mapping of fundamental nodes to supernodes */ + /* ---------------------------------------------------------------------- */ + + SuperMap = Wj ; /* use Wj as workspace for SuperMap [ */ + + /* SuperMap [k] = s if column k is contained in supernode s */ + for (s = 0 ; s < nfsuper ; s++) + { + for (k = Super [s] ; k < Super [s+1] ; k++) + { + SuperMap [k] = s ; + } + } + + /* ---------------------------------------------------------------------- */ + /* construct the fundamental supernodal etree */ + /* ---------------------------------------------------------------------- */ + + for (s = 0 ; s < nfsuper ; s++) + { + j = Super [s+1] - 1 ; /* last node in supernode s */ + parent = Parent [j] ; /* parent of last node */ + Sparent [s] = (parent == EMPTY) ? EMPTY : SuperMap [parent] ; + PRINT1 (("Sparent ["ID"] = "ID"\n", s, Sparent [s])) ; + } + + /* contents of Wj no longer needed as workspace for SuperMap ] + * SuperMap will be recomputed below, for the relaxed supernodes. */ + + Zeros = Wj ; /* use Wj for Zeros, workspace of size nfsuper [ */ + + /* ---------------------------------------------------------------------- */ + /* relaxed amalgamation */ + /* ---------------------------------------------------------------------- */ + + for (s = 0 ; s < nfsuper ; s++) + { + Merged [s] = EMPTY ; /* s not merged into another */ + Nscol [s] = Super [s+1] - Super [s] ; /* # of columns in s */ + Zeros [s] = 0 ; /* # of zero entries in s */ + ASSERT (s <= Super [s]) ; + Snz [s] = ColCount [Super [s]] ; /* # of entries in leading col of s */ + PRINT2 (("lnz ["ID"] "ID"\n", s, Snz [s])) ; + } + + for (s = nfsuper-2 ; s >= 0 ; s--) + { + /* should supernodes s and s+1 merge into a new node s? */ + PRINT1 (("\n========= Check relax of s "ID" and s+1 "ID"\n", s, s+1)) ; + + ss = Sparent [s] ; + if (ss == EMPTY) + { + PRINT1 (("s "ID" is a root, no merge with s+1 = "ID"\n", s, s+1)) ; + continue ; + } + + /* find the current parent of s (perform path compression as needed) */ + for (ss = Sparent [s] ; Merged [ss] != EMPTY ; ss = Merged [ss]) ; + sparent = ss ; + PRINT2 (("Current sparent of s "ID" is "ID"\n", s, sparent)) ; + + /* ss is the current parent of s */ + for (ss = Sparent [s] ; Merged [ss] != EMPTY ; ss = snext) + { + snext = Merged [ss] ; + PRINT2 (("ss "ID" is dead, merged into snext "ID"\n", ss, snext)) ; + Merged [ss] = sparent ; + } + + /* if s+1 is not the current parent of s, do not merge */ + if (sparent != s+1) + { + continue ; + } + + nscol0 = Nscol [s] ; /* # of columns in s */ + nscol1 = Nscol [s+1] ; /* # of columns in s+1 */ + ns = nscol0 + nscol1 ; + PRINT2 (("ns "ID" nscol0 "ID" nscol1 "ID"\n", ns, nscol0, nscol1)) ; + + totzeros = Zeros [s+1] ; /* current # of zeros in s+1 */ + + /* determine if supernodes s and s+1 should merge */ + if (ns <= nrelax0) + { + PRINT2 (("ns is tiny ("ID"), so go ahead and merge\n", ns)) ; + merge = TRUE ; + } + else + { + /* use double to avoid integer overflow */ + double lnz0 = Snz [s] ; /* # entries in leading column of s */ + double lnz1 = Snz [s+1] ; /* # entries in leading column of s+1 */ + double xnewzeros = nscol0 * (lnz1 + nscol0 - lnz0) ; + + /* use Int for the final update of Zeros [s] below */ + newzeros = nscol0 * (Snz [s+1] + nscol0 - Snz [s]) ; + ASSERT (newzeros == xnewzeros) ; + + PRINT2 (("lnz0 %g lnz1 %g xnewzeros %g\n", lnz0, lnz1, xnewzeros)) ; + if (xnewzeros == 0) + { + /* no new zeros, so go ahead and merge */ + PRINT2 (("no new fillin, so go ahead and merge\n")) ; + merge = TRUE ; + } + else + { + /* # of zeros if merged */ + double xtotzeros = ((double) totzeros) + xnewzeros ; + + /* xtotsize: total size of merged supernode, if merged: */ + double xns = (double) ns ; + double xtotsize = (xns * (xns+1) / 2) + xns * (lnz1 - nscol1) ; + double z = xtotzeros / xtotsize ; + + Int totsize ; + totsize = (ns * (ns+1) / 2) + ns * (Snz [s+1] - nscol1) ; + + PRINT2 (("oldzeros "ID" newzeros "ID" xtotsize %g z %g\n", + Zeros [s+1], newzeros, xtotsize, z)) ; + + /* use Int for the final update of Zeros [s] below */ + totzeros += newzeros ; + + /* do not merge if supernode would become too big + * (Int overflow). Continue computing; not (yet) an error. */ + /* fl.pt. compare, but no NaN's can occur here */ + merge = ((ns <= nrelax1 && z < zrelax0) || + (ns <= nrelax2 && z < zrelax1) || + (z < zrelax2)) && + (xtotsize < Int_max / sizeof (double)) ; + + } + } + + if (merge) + { + PRINT1 (("Merge node s ("ID") and s+1 ("ID")\n", s, s+1)) ; + Zeros [s] = totzeros ; + Merged [s+1] = s ; + Snz [s] = nscol0 + Snz [s+1] ; + Nscol [s] += Nscol [s+1] ; + } + } + + /* contents of Wj no longer needed for Zeros ] */ + /* contents of Wi no longer needed for Nscol ] */ + /* contents of Sparent no longer needed (recomputed below) */ + + /* ---------------------------------------------------------------------- */ + /* construct the relaxed supernode list */ + /* ---------------------------------------------------------------------- */ + + nsuper = 0 ; + for (s = 0 ; s < nfsuper ; s++) + { + if (Merged [s] == EMPTY) + { + PRINT1 (("live supernode: "ID" snz "ID"\n", s, Snz [s])) ; + Super [nsuper] = Super [s] ; + Snz [nsuper] = Snz [s] ; + nsuper++ ; + } + } + Super [nsuper] = n ; + PRINT1 (("Fundamental supernodes: "ID" relaxed "ID"\n", nfsuper, nsuper)) ; + + /* Merged no longer needed ] */ + + /* ---------------------------------------------------------------------- */ + /* find the mapping of relaxed nodes to supernodes */ + /* ---------------------------------------------------------------------- */ + + /* use Wj as workspace for SuperMap { */ + + /* SuperMap [k] = s if column k is contained in supernode s */ + for (s = 0 ; s < nsuper ; s++) + { + for (k = Super [s] ; k < Super [s+1] ; k++) + { + SuperMap [k] = s ; + } + } + + /* ---------------------------------------------------------------------- */ + /* construct the relaxed supernodal etree */ + /* ---------------------------------------------------------------------- */ + + for (s = 0 ; s < nsuper ; s++) + { + j = Super [s+1] - 1 ; /* last node in supernode s */ + parent = Parent [j] ; /* parent of last node */ + Sparent [s] = (parent == EMPTY) ? EMPTY : SuperMap [parent] ; + PRINT1 (("new Sparent ["ID"] = "ID"\n", s, Sparent [s])) ; + } + + /* ---------------------------------------------------------------------- */ + /* determine the size of L->s and L->x */ + /* ---------------------------------------------------------------------- */ + + ssize = 0 ; + xsize = 0 ; + xxsize = 0 ; + for (s = 0 ; s < nsuper ; s++) + { + nscol = Super [s+1] - Super [s] ; + nsrow = Snz [s] ; + ASSERT (nscol > 0) ; + ssize += nsrow ; + if (for_cholesky) + { + xsize += nscol * nsrow ; + /* also compute xsize in double to guard against Int overflow */ + xxsize += ((double) nscol) * ((double) nsrow) ; + } + if (ssize < 0 || (for_cholesky && xxsize > Int_max)) + { + /* Int overflow, clear workspace and return. + QR factorization will not use xxsize, so that error is ignored. + For Cholesky factorization, however, memory of space xxsize + will be allocated, so this is a failure. Both QR and Cholesky + fail if ssize overflows. */ + ERROR (CHOLMOD_TOO_LARGE, "problem too large") ; + FREE_WORKSPACE ; + return (FALSE) ; + } + ASSERT (ssize > 0) ; + ASSERT (IMPLIES (for_cholesky, xsize > 0)) ; + } + xsize = MAX (1, xsize) ; + ssize = MAX (1, ssize) ; + PRINT1 (("ix sizes: "ID" "ID" nsuper "ID"\n", ssize, xsize, nsuper)) ; + + /* ---------------------------------------------------------------------- */ + /* allocate L (all except real part L->x) */ + /* ---------------------------------------------------------------------- */ + + L->ssize = ssize ; + L->xsize = xsize ; + L->nsuper = nsuper ; + + CHOLMOD(change_factor) (CHOLMOD_PATTERN, TRUE, TRUE, TRUE, TRUE, L, Common); + + if (Common->status < CHOLMOD_OK) + { + /* out of memory; L is still a valid simplicial symbolic factor */ + FREE_WORKSPACE ; + return (FALSE) ; + } + + DEBUG (CHOLMOD(dump_factor) (L, "L to symbolic super", Common)) ; + ASSERT (L->is_ll && L->xtype == CHOLMOD_PATTERN && L->is_super) ; + + Lpi = L->pi ; + Lpx = L->px ; + Ls = L->s ; + Ls [0] = 0 ; /* flag for cholmod_check_factor; supernodes are defined */ + Lpx [0] = for_cholesky ? 0 : 123456 ; /* magic number for sparse QR */ + Lsuper = L->super ; + + /* copy the list of relaxed supernodes into the final list in L */ + for (s = 0 ; s <= nsuper ; s++) + { + Lsuper [s] = Super [s] ; + } + + /* Head no longer needed as workspace for fundamental Super list ) */ + + Super = Lsuper ; /* Super is now the list of relaxed supernodes */ + + /* ---------------------------------------------------------------------- */ + /* construct column pointers of relaxed supernodal pattern (L->pi) */ + /* ---------------------------------------------------------------------- */ + + p = 0 ; + for (s = 0 ; s < nsuper ; s++) + { + Lpi [s] = p ; + p += Snz [s] ; + PRINT1 (("Snz ["ID"] = "ID", Super ["ID"] = "ID"\n", + s, Snz [s], s, Super[s])) ; + } + Lpi [nsuper] = p ; + ASSERT ((Int) (L->ssize) == MAX (1,p)) ; + + /* ---------------------------------------------------------------------- */ + /* construct pointers for supernodal values (L->px) */ + /* ---------------------------------------------------------------------- */ + + if (for_cholesky) + { + /* L->px is not needed for QR factorization (it may lead to Int + overflow, anyway, if xsize caused Int overflow above) */ + p = 0 ; + for (s = 0 ; s < nsuper ; s++) + { + nscol = Super [s+1] - Super [s] ; /* number of columns in s */ + nsrow = Snz [s] ; /* # of rows, incl triangular part*/ + Lpx [s] = p ; /* pointer to numerical part of s */ + p += nscol * nsrow ; + } + Lpx [s] = p ; + ASSERT ((Int) (L->xsize) == MAX (1,p)) ; + } + + /* Snz no longer needed ] */ + + /* ---------------------------------------------------------------------- */ + /* symbolic analysis to construct the relaxed supernodal pattern (L->s) */ + /* ---------------------------------------------------------------------- */ + + Lpi2 = Wi ; /* copy Lpi into Lpi2, using Wi as workspace for Lpi2 [ */ + for (s = 0 ; s < nsuper ; s++) + { + Lpi2 [s] = Lpi [s] ; + } + + Asorted = A->sorted ; + + for (s = 0 ; s < nsuper ; s++) + { + /* sth supernode is in columns k1 to k2-1. + * compute nonzero pattern of L (k1:k2-1,:). */ + + /* place rows k1 to k2-1 in leading column of supernode s */ + k1 = Super [s] ; + k2 = Super [s+1] ; + PRINT1 (("=========>>> Supernode "ID" k1 "ID" k2-1 "ID"\n", + s, k1, k2-1)) ; + for (k = k1 ; k < k2 ; k++) + { + Ls [Lpi2 [s]++] = k ; + } + + /* compute nonzero pattern each row k1 to k2-1 */ + for (k = k1 ; k < k2 ; k++) + { + /* compute row k of L. In the symmetric case, the pattern of L(k,:) + * is the set of nodes reachable in the supernodal etree from any + * row i in the nonzero pattern of A(0:k,k). In the unsymmetric + * case, the pattern of the kth column of A*A' is the set union + * of all columns A(0:k,j) for each nonzero F(j,k). */ + + /* clear the Flag array and mark the current supernode */ + /* mark = CHOLMOD(clear_flag) (Common) ; */ + CHOLMOD_CLEAR_FLAG (Common) ; + mark = Common->mark ; + Flag [s] = mark ; + ASSERT (s == SuperMap [k]) ; + + /* traverse the row subtree for each nonzero in A or AA' */ + if (stype != 0) + { + subtree (k, k, Ap, Ai, Anz, SuperMap, Sparent, mark, + Asorted, k1, Flag, Ls, Lpi2) ; + } + else + { + /* for each j nonzero in F (:,k) do */ + p = Fp [k] ; + pend = (packed) ? (Fp [k+1]) : (p + Fnz [k]) ; + for ( ; p < pend ; p++) + { + subtree (Fj [p], k, Ap, Ai, Anz, SuperMap, Sparent, mark, + Asorted, k1, Flag, Ls, Lpi2) ; + } + } + } + } +#ifndef NDEBUG + for (s = 0 ; s < nsuper ; s++) + { + PRINT1 (("Lpi2[s] "ID" Lpi[s+1] "ID"\n", Lpi2 [s], Lpi [s+1])) ; + ASSERT (Lpi2 [s] == Lpi [s+1]) ; + CHOLMOD(dump_super) (s, Super, Lpi, Ls, NULL, NULL, 0, Common) ; + } +#endif + + /* contents of Wi no longer needed for Lpi2 ] */ + /* Sparent no longer needed ] */ + + /* ---------------------------------------------------------------------- */ + /* determine the largest update matrix (L->maxcsize) */ + /* ---------------------------------------------------------------------- */ + + /* maxcsize could be determined before L->s is allocated and defined, which + * would mean that all memory requirements for both the symbolic and numeric + * factorizations could be computed using O(nnz(A)+O(n)) space. However, it + * would require a lot of extra work. The analysis phase, above, would need + * to be duplicated, but with Ls not kept; instead, the algorithm would keep + * track of the current s and slast for each supernode d, and update them + * when a new row index appears in supernode d. An alternative would be to + * do this computation only if the allocation of L->s failed, in which case + * the following code would be skipped. + * + * The csize for a supernode is the size of its largest contribution to + * a subsequent ancestor supernode. For example, suppose the rows of #'s + * in the figure below correspond to the columns of a subsequent supernode, + * and the dots are the entries in that ancestore. + * + * c + * c c + * c c c + * x x x + * x x x + * # # # . + * # # # . . + * * * * . . + * * * * . . + * * * * . . + * . . + * + * Then for this update, the csize is 3-by-2, or 6, because there are 3 + * rows of *'s which is the number of rows in the update, and there are + * 2 rows of #'s, which is the number columns in the update. The csize + * of a supernode is the largest such contribution for any ancestor + * supernode. maxcsize, for the whole matrix, has a rough upper bound of + * the maximum size of any supernode. This bound is loose, because the + * the contribution must be less than the size of the ancestor supernodal + * that it's updating. maxcsize of a completely dense matrix, with one + * supernode, is zero. + * + * maxesize is the column dimension for the workspace E needed for the + * solve. E is of size nrhs-by-maxesize, where the nrhs is the number of + * columns in the right-hand-side. The maxesize is the largest esize of + * any supernode. The esize of a supernode is the number of row indices + * it contains, excluding the column indices of the supernode itself. + * For the following example, esize is 4: + * + * c + * c c + * c c c + * x x x + * x x x + * x x x + * x x x + * + * maxesize can be no bigger than n. + */ + + maxcsize = 1 ; + maxesize = 1 ; + + /* Do not need to guard csize against Int overflow since xsize is OK. */ + + if (for_cholesky) + { + /* this is not needed for QR factorization */ + for (d = 0 ; d < nsuper ; d++) + { + nscol = Super [d+1] - Super [d] ; + p = Lpi [d] + nscol ; + plast = p ; + pend = Lpi [d+1] ; + esize = pend - p ; + maxesize = MAX (maxesize, esize) ; + slast = (p == pend) ? (EMPTY) : (SuperMap [Ls [p]]) ; + for ( ; p <= pend ; p++) + { + s = (p == pend) ? (EMPTY) : (SuperMap [Ls [p]]) ; + if (s != slast) + { + /* row i is the start of a new supernode */ + ndrow1 = p - plast ; + ndrow2 = pend - plast ; + csize = ndrow2 * ndrow1 ; + PRINT1 (("Supernode "ID" ancestor "ID" C: "ID"-by-"ID + " csize "ID"\n", d, slast, ndrow1, ndrow2, csize)) ; + maxcsize = MAX (maxcsize, csize) ; + plast = p ; + slast = s ; + } + } + } + PRINT1 (("max csize "ID"\n", maxcsize)) ; + } + + /* Wj no longer needed for SuperMap } */ + + L->maxcsize = maxcsize ; + L->maxesize = maxesize ; + L->is_super = TRUE ; + ASSERT (L->xtype == CHOLMOD_PATTERN && L->is_ll) ; + + /* ---------------------------------------------------------------------- */ + /* supernodal symbolic factorization is complete */ + /* ---------------------------------------------------------------------- */ + + FREE_WORKSPACE ; + return (TRUE) ; +} + +/* ========================================================================== */ +/* === cholmod_super_symbolic =============================================== */ +/* ========================================================================== */ + +/* Analyzes A, AA', or A(:,f)*A(:,f)' in preparation for a supernodal numeric + * factorization. The user need not call this directly; cholmod_analyze is + * a "simple" wrapper for this routine. + * + * This function does all the analysis for a supernodal Cholesky factorization. + * + * workspace: Flag (nrow), Head (nrow), Iwork (2*nrow), + * and temporary space of size 3*nfsuper*sizeof(Int), where nfsuper <= n + * is the number of fundamental supernodes. + */ + +int CHOLMOD(super_symbolic) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to analyze */ + cholmod_sparse *F, /* F = A' or A(:,f)' */ + Int *Parent, /* elimination tree */ + /* ---- in/out --- */ + cholmod_factor *L, /* simplicial symbolic on input, + * supernodal symbolic on output */ + /* --------------- */ + cholmod_common *Common +) +{ + return (CHOLMOD(super_symbolic2) (TRUE, A, F, Parent, L, Common)) ; +} +#endif diff --git a/src/CHOLMOD/Supernodal/gpl.txt b/src/CHOLMOD/Supernodal/gpl.txt new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/src/CHOLMOD/Supernodal/gpl.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/src/CHOLMOD/Supernodal/t_cholmod_gpu.c b/src/CHOLMOD/Supernodal/t_cholmod_gpu.c new file mode 100644 index 0000000..9a4c6a8 --- /dev/null +++ b/src/CHOLMOD/Supernodal/t_cholmod_gpu.c @@ -0,0 +1,972 @@ +/* ========================================================================== */ +/* === Supernodal/t_cholmod_gpu ============================================= */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Supernodal Module. Copyright (C) 2005-2012, Timothy A. Davis + * The CHOLMOD/Supernodal Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* GPU BLAS template routine for cholmod_super_numeric. */ + +/* ========================================================================== */ +/* === include files and definitions ======================================== */ +/* ========================================================================== */ + +#include "cholmod_template.h" + +#undef L_ENTRY +#ifdef REAL +#define L_ENTRY 1 +#else +#define L_ENTRY 2 +#endif + +/* +#define GPU_Printf printf +*/ +#define GPU_Printf + +#define PAGE_SIZE (4*1024) +#define OK(cuda_operation) ((cuda_operation) == cudaSuccess) + +/* ========================================================================== */ +/* === gpu_init ============================================================= */ +/* ========================================================================== */ + +void TEMPLATE (CHOLMOD (gpu_init)) +( + void *Cwork, + Int maxSize, + cholmod_common *Common +) +{ + Int i ; + cublasStatus_t cublasError ; + cudaError_t cudaErr ; + size_t maxBytesSize, HostPinnedSize ; + + Common->GemmUsed = 0 ; + + GPU_Printf ("gpu_init : %p\n", (void *) ((size_t) Cwork & ~(PAGE_SIZE-1))) ; + + if (!(Common->cublasHandle)) + { + + /* ------------------------------------------------------------------ */ + /* create the CUDA BLAS handle */ + /* ------------------------------------------------------------------ */ + + cublasError = cublasCreate (&(Common->cublasHandle)) ; + if (cublasError != CUBLAS_STATUS_SUCCESS) + { + ERROR (CHOLMOD_GPU_PROBLEM, "CUBLAS initialization") ; + return ; + } + + /* ------------------------------------------------------------------ */ + /* create each CUDA stream */ + /* ------------------------------------------------------------------ */ + + cudaErr = cudaStreamCreate (&(Common->cudaStreamSyrk)) ; + if (cudaErr != cudaSuccess) + { + ERROR (CHOLMOD_GPU_PROBLEM, "CUDA stream initialization") ; + return ; + } + + cudaErr = cudaStreamCreate (&(Common->cudaStreamGemm)) ; + if (cudaErr != cudaSuccess) + { + ERROR (CHOLMOD_GPU_PROBLEM, "CUDA stream initialization") ; + return ; + } + + cudaErr = cudaStreamCreate (&(Common->cudaStreamTrsm)) ; + if (cudaErr != cudaSuccess) + { + ERROR (CHOLMOD_GPU_PROBLEM, "CUDA stream initialization") ; + return ; + } + + for (i = 0 ; i < 3 ; i++) + { + cudaErr = cudaStreamCreate (&(Common->cudaStreamPotrf [i])) ; + if (cudaErr != cudaSuccess) + { + ERROR (CHOLMOD_GPU_PROBLEM, "CUDA stream initialization") ; + return ; + } + } + + /* ------------------------------------------------------------------ */ + /* create each CUDA event */ + /* ------------------------------------------------------------------ */ + + for (i = 0 ; i < 2 ; i++) + { + cudaErr = cudaEventCreateWithFlags + (&(Common->cublasEventPotrf [i]), cudaEventDisableTiming) ; + if (cudaErr != cudaSuccess) + { + ERROR (CHOLMOD_GPU_PROBLEM, "CUDA event") ; + return ; + } + } + } + + /* ---------------------------------------------------------------------- */ + /* pin the Host memory */ + /* ---------------------------------------------------------------------- */ + + Common->HostPinnedMemory = (void *) ((size_t) Cwork & ~(PAGE_SIZE-1)) ; + maxBytesSize = sizeof (double)*L_ENTRY*maxSize ; + + /* Align on a 4K page boundary (it is no more necessary in 4.1 */ + HostPinnedSize = + (((size_t) Cwork + maxBytesSize + PAGE_SIZE-1) & ~(PAGE_SIZE-1)) + - (size_t) (Common->HostPinnedMemory) ; + + GPU_Printf ("gpu HostPinnedSize: %g %p\n", (double) HostPinnedSize, + Common->HostPinnedMemory) ; + cudaErr = cudaHostRegister (Common->HostPinnedMemory, + HostPinnedSize, 0) ; + + if (cudaErr != cudaSuccess) + { + ERROR (CHOLMOD_GPU_PROBLEM, "CUDA Pinning Memory") ; + Common->HostPinnedMemory = NULL ; + } +} + + +/* ========================================================================== */ +/* === gpu_end ============================================================== */ +/* ========================================================================== */ + +void TEMPLATE (CHOLMOD (gpu_end)) +( + cholmod_common *Common +) +{ + int i; + /* unpin the Host memory */ + GPU_Printf ("gpu_end %p\n", Common->HostPinnedMemory) ; + cudaError_t cudaErr = cudaHostUnregister (Common->HostPinnedMemory) ; + if (cudaErr != cudaSuccess) + { + ERROR (CHOLMOD_GPU_PROBLEM, "CUDA Unpinning Memory") ; + Common->HostPinnedMemory = NULL ; + } + /* ------------------------------------------------------------------ */ + /* destroy Cublas Handle */ + /* ------------------------------------------------------------------ */ + + if (Common->cublasHandle) { + cublasDestroy(Common->cublasHandle); + Common->cublasHandle = NULL ; + } + /* ------------------------------------------------------------------ */ + /* destroy each CUDA stream */ + /* ------------------------------------------------------------------ */ + if (Common->cudaStreamSyrk) + { + cudaStreamDestroy (Common->cudaStreamSyrk) ; + Common->cudaStreamSyrk = NULL ; + } + if (Common->cudaStreamGemm) + { + cudaStreamDestroy (Common->cudaStreamGemm) ; + } + if (Common->cudaStreamTrsm) + { + cudaStreamDestroy (Common->cudaStreamTrsm) ; + Common->cudaStreamTrsm = NULL ; + } + + for (i = 0 ; i < 3 ; i++) + { + if (Common->cudaStreamPotrf [i]) + { + cudaStreamDestroy(Common->cudaStreamPotrf [i]) ; + Common->cudaStreamPotrf [i] = NULL ; + } + } + + /* ------------------------------------------------------------------ */ + /* destroy each CUDA event */ + /* ------------------------------------------------------------------ */ + + for (i = 0 ; i < 2 ; i++) + { + if (Common->cublasEventPotrf [i]) + { + cudaEventDestroy( Common->cublasEventPotrf [i] ) ; + Common->cublasEventPotrf [i] = NULL ; + } + } +} + + +/* ========================================================================== */ +/* === gpu_updateC ========================================================== */ +/* ========================================================================== */ + +/* C = L (k1:n-1, kd1:kd2-1) * L (k1:k2-1, kd1:kd2-1)', except that k1:n-1 + * refers to all of the rows in L, but many of the rows are all zero. + * Supernode d holds columns kd1 to kd2-1 of L. Nonzero rows in the range + * k1:k2-1 are in the list Ls [pdi1 ... pdi2-1], of size ndrow1. Nonzero rows + * in the range k2:n-1 are in the list Ls [pdi2 ... pdend], of size ndrow2. + * Let L1 = L (Ls [pdi1 ... pdi2-1], kd1:kd2-1), and let L2 = L (Ls [pdi2 ... + * pdend], kd1:kd2-1). C is ndrow2-by-ndrow1. Let C1 be the first ndrow1 + * rows of C and let C2 be the last ndrow2-ndrow1 rows of C. Only the lower + * triangular part of C1 needs to be computed since C1 is symmetric. + */ + +int TEMPLATE (CHOLMOD (gpu_updateC)) +( + Int ndrow1, /* C is ndrow2-by-ndrow2 */ + Int ndrow2, + Int ndrow, /* leading dimension of Lx */ + Int ndcol, /* L1 is ndrow1-by-ndcol */ + Int pdx1, /* L1 starts at Lx + L_ENTRY*pdx1 */ + /* L2 starts at Lx + L_ENTRY*(pdx1 + ndrow1) */ + double *Lx, + double *C, + cholmod_common *Common +) +{ + double *devPtrLx, *devPtrC ; + double alpha, beta ; + cublasStatus_t cublasStatus ; + cudaError_t cudaStat [2] ; + Int ndrow3 ; + + Common->SyrkUsed = 0 ; + Common->GemmUsed = 0 ; + + if ((ndrow2 < 512) || (ndcol < 128)) + { + /* too small for the CUDA BLAS; use the CPU instead */ + return (0) ; + } + + ndrow3 = ndrow2 - ndrow1 ; + +#ifndef NTIMER + Common->syrkStart = SuiteSparse_time ( ) ; +#endif + + /* ---------------------------------------------------------------------- */ + /* allocate workspace on the GPU */ + /* ---------------------------------------------------------------------- */ + + cudaStat [0] = cudaMalloc ((void **) &devPtrLx, + ndrow2 * ndcol * L_ENTRY * sizeof (devPtrLx [0])) ; + cudaStat [1] = cudaMalloc ((void **) &devPtrC, + ndrow2 * ndrow1 * L_ENTRY * sizeof (devPtrC [0])) ; + Common->devSyrkGemmPtrLx = devPtrLx ; + Common->devSyrkGemmPtrC = devPtrC ; + + if (cudaStat [0] || cudaStat [1]) + { + /* one or both cudaMalloc's failed */ + if (devPtrLx) cudaFree (devPtrLx) ; + if (devPtrC) cudaFree (devPtrC) ; + GPU_Printf ("gpu malloc failed =%d,%d ndrow1=%d ndrow2=%d ndcol=%d\n", + cudaStat [0], cudaStat [1], (int) ndrow1, + (int) ndrow2, (int) ndcol) ; + /* cudaMalloc failure is not an error, just bypass the GPU */ + return (0) ; + } + Common->SyrkUsed = 1 ; +#ifndef NTIMER + Common->CHOLMOD_GPU_SYRK_CALLS++ ; +#endif + + /* ---------------------------------------------------------------------- */ + /* copy Lx to the GPU */ + /* ---------------------------------------------------------------------- */ + + /* copy Lx in two steps on different streams. + * (ldLx is shortened from ndrow to ndrow2) */ + cudaStat [0] = cudaMemcpy2DAsync (devPtrLx, + ndrow2 * L_ENTRY * sizeof (devPtrLx [0]), + Lx + L_ENTRY * pdx1, ndrow * L_ENTRY * sizeof (Lx [0]), + ndrow1 * L_ENTRY * sizeof (devPtrLx [0]), + ndcol, cudaMemcpyHostToDevice, Common->cudaStreamSyrk) ; + if (cudaStat [0]) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU memcopy to device") ; + } + + if (ndrow3 > 0) + { + Common->GemmUsed = 1 ; + cudaStat [1] = cudaMemcpy2DAsync (devPtrLx + L_ENTRY*ndrow1, + ndrow2 * L_ENTRY * sizeof (devPtrLx [0]), + Lx + L_ENTRY * (pdx1 + ndrow1), ndrow * L_ENTRY * sizeof (Lx [0]), + ndrow3 * L_ENTRY * sizeof (devPtrLx [0]), + ndcol, cudaMemcpyHostToDevice, Common->cudaStreamGemm) ; + if (cudaStat [1]) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU memcopy to device") ; + } + } + + /* ---------------------------------------------------------------------- */ + /* do the CUDA SYRK */ + /* ---------------------------------------------------------------------- */ + + cublasStatus = cublasSetStream (Common->cublasHandle, + Common->cudaStreamSyrk) ; + if (cublasStatus != CUBLAS_STATUS_SUCCESS) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU CUBLAS stream") ; + } + + alpha = 1.0 ; + beta = 0.0 ; +#ifdef REAL + cublasStatus = cublasDsyrk (Common->cublasHandle, + CUBLAS_FILL_MODE_LOWER, CUBLAS_OP_N, + (int) ndrow1, (int) ndcol, /* N, K: L1 is ndrow1-by-ndcol */ + &alpha, /* ALPHA: 1 */ + devPtrLx, ndrow2, /* A, LDA: L1, ndrow2 */ + &beta, /* BETA: 0 */ + devPtrC, ndrow2) ; /* C, LDC: C1 */ +#else + cublasStatus = cublasZherk (Common->cublasHandle, + CUBLAS_FILL_MODE_LOWER, CUBLAS_OP_N, + (int) ndrow1, (int) ndcol, /* N, K: L1 is ndrow1-by-ndcol*/ + &alpha, /* ALPHA: 1 */ + (const cuDoubleComplex *) devPtrLx, ndrow2, /* A, LDA: L1, ndrow2 */ + &beta, /* BETA: 0 */ + (cuDoubleComplex *) devPtrC, ndrow2) ; /* C, LDC: C1 */ +#endif + + if (cublasStatus != CUBLAS_STATUS_SUCCESS) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU CUBLAS routine failure") ; + } + + /* ---------------------------------------------------------------------- */ + /* partial copy of C to the GPU */ + /* ---------------------------------------------------------------------- */ + + cudaStat [0] = cudaMemcpy2DAsync (C, ndrow2 * L_ENTRY * sizeof (C [0]), + devPtrC, ndrow2 * L_ENTRY * sizeof (devPtrC [0]), + ndrow1 * L_ENTRY * sizeof (devPtrC [0]), + ndrow1, cudaMemcpyDeviceToHost, Common->cudaStreamSyrk) ; + if (cudaStat [0]) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU memcopy from device") ; + } + + /* ---------------------------------------------------------------------- */ + /* compute remaining (ndrow2-ndrow1)-by-ndrow1 block of C, C2 = L2*L1' */ + /* ---------------------------------------------------------------------- */ + + if (ndrow3 > 0) + { +#ifndef REAL + cuDoubleComplex calpha = {1.0,0.0} ; + cuDoubleComplex cbeta = {0.0,0.0} ; +#endif + +#ifndef NTIMER + Common->CHOLMOD_GPU_GEMM_CALLS++ ; +#endif + cublasStatus = cublasSetStream (Common->cublasHandle, + Common->cudaStreamGemm) ; + if (cublasStatus != CUBLAS_STATUS_SUCCESS) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU CUBLAS stream") ; + } + + /* ------------------------------------------------------------------ */ + /* do the CUDA BLAS dgemm */ + /* ------------------------------------------------------------------ */ + +#ifdef REAL + alpha = 1.0 ; + beta = 0.0 ; + cublasStatus = cublasDgemm (Common->cublasHandle, + CUBLAS_OP_N, CUBLAS_OP_T, + ndrow3, ndrow1, ndcol, /* M, N, K */ + &alpha, /* ALPHA: 1 */ + devPtrLx + L_ENTRY*(ndrow1), /* A, LDA: L2, ndrow */ + ndrow2, + devPtrLx, /* B, LDB: L1, ndrow */ + ndrow2, + &beta, /* BETA: 0 */ + devPtrC + L_ENTRY*ndrow1, /* C, LDC: C2 */ + ndrow2) ; +#else + cublasStatus = cublasZgemm (Common->cublasHandle, + CUBLAS_OP_N, CUBLAS_OP_C, + ndrow3, ndrow1, ndcol, /* M, N, K */ + &calpha, /* ALPHA: 1 */ + (const cuDoubleComplex *) devPtrLx + ndrow1, /* A, LDA: L2, ndrow */ + ndrow2, + (const cuDoubleComplex *) devPtrLx, /* B, LDB: L1, ndrow */ + ndrow2, + &cbeta, /* BETA: 0 */ + (cuDoubleComplex *)devPtrC + ndrow1, /* C, LDC: C2 */ + ndrow2) ; +#endif + + if (cublasStatus != CUBLAS_STATUS_SUCCESS) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU CUBLAS routine failure") ; + } + + /* ------------------------------------------------------------------ */ + /* finish copy of C */ + /* ------------------------------------------------------------------ */ + + cudaStat [0] = cudaMemcpy2DAsync (C + L_ENTRY*ndrow1, + ndrow2 * L_ENTRY * sizeof (C [0]), + devPtrC+ L_ENTRY*ndrow1, ndrow2 * L_ENTRY * sizeof (devPtrC [0]), + ndrow3 * L_ENTRY * sizeof (devPtrC [0]), + ndrow1, cudaMemcpyDeviceToHost, Common->cudaStreamGemm) ; + if (cudaStat [0]) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU memcopy from device") ; + } + } + + return (1) ; +} + + +/* ========================================================================== */ +/* === gpu_syncSyrk ========================================================= */ +/* ========================================================================== */ + +/* synchronize with the CUDA BLAS dsyrk stream */ + +void TEMPLATE (CHOLMOD (gpu_syncSyrk)) +( + cholmod_common *Common +) +{ + if (Common->SyrkUsed) + { + cudaStreamSynchronize (Common->cudaStreamSyrk) ; + if (!Common->GemmUsed) + { + cudaFree (Common->devSyrkGemmPtrLx) ; + cudaFree (Common->devSyrkGemmPtrC) ; + Common->devSyrkGemmPtrLx = NULL ; + Common->devSyrkGemmPtrC = NULL ; +#ifndef NTIMER + /* this actually sums time spend on Syrk and Gemm */ + Common->CHOLMOD_GPU_SYRK_TIME += + SuiteSparse_time ( ) - Common->syrkStart ; +#endif + } + } +} + + +/* ========================================================================== */ +/* === gpu_syncGemm ========================================================= */ +/* ========================================================================== */ + +/* synchronize with the CUDA BLAS dgemm stream */ + +void TEMPLATE (CHOLMOD (gpu_syncGemm)) +( + cholmod_common *Common +) +{ + if (Common->GemmUsed) + { + cudaStreamSynchronize (Common->cudaStreamGemm) ; + cudaFree (Common->devSyrkGemmPtrLx) ; + cudaFree (Common->devSyrkGemmPtrC) ; + Common->devSyrkGemmPtrLx = NULL ; + Common->devSyrkGemmPtrC = NULL ; +#ifndef NTIMER + /* this actually sums time spend on Syrk and Gemm */ + Common->CHOLMOD_GPU_SYRK_TIME += + SuiteSparse_time ( ) - Common->syrkStart ; +#endif + } +} + + +/* ========================================================================== */ +/* === gpu_lower_potrf ====================================================== */ +/* ========================================================================== */ + +/* Cholesky factorzation (dpotrf) of a matrix S, operating on the lower + * triangular part only. S is nscol2-by-nscol2 with leading dimension nsrow. + * + * S is the top part of the supernode (the lower triangular matrx). + * This function also copies the bottom rectangular part of the supernode (B) + * onto the GPU, in preparation for gpu_triangular_solve. + */ + +int TEMPLATE (CHOLMOD (gpu_lower_potrf)) +( + Int nscol2, /* S is nscol2-by-nscol2 */ + Int nsrow, /* leading dimension of S */ + Int psx, /* S is located at Lx + L_Entry*psx */ + double *Lx, /* contains S; overwritten with Cholesky factor */ + Int *info, /* BLAS info return value */ + cholmod_common *Common +) +{ + double *devPtrA, *devPtrB, *A ; + double alpha, beta ; + cudaError_t cudaStat ; + cublasStatus_t cublasStatus ; + Int j, nsrow2, nb, n, gpu_lda, lda, gpu_ldb ; + int ilda, ijb, iinfo ; +#ifndef NTIMER + double tstart = SuiteSparse_time ( ) ; +#endif + + if (nscol2 < 256) + { + /* too small for the CUDA BLAS; use the CPU instead */ + return (0) ; + } + + nsrow2 = nsrow - nscol2 ; + + /* ---------------------------------------------------------------------- */ + /* heuristic to get the block size depending of the problem size */ + /* ---------------------------------------------------------------------- */ + + nb = 128 ; + if (nscol2 > 4096) nb = 256 ; + if (nscol2 > 8192) nb = 384 ; + n = nscol2 ; + gpu_lda = ((nscol2+31)/32)*32 ; + lda = nsrow ; + A = Lx + L_ENTRY*psx ; + + /* ---------------------------------------------------------------------- */ + /* free the dpotrf workspace, if allocated */ + /* ---------------------------------------------------------------------- */ + + if (Common->devPotrfWork) + { + cudaFree (Common->devPotrfWork) ; + Common->devPotrfWork = NULL ; + } + + /* ---------------------------------------------------------------------- */ + /* determine the GPU leading dimension of B */ + /* ---------------------------------------------------------------------- */ + + gpu_ldb = 0 ; + if (nsrow2 > 0) + { + gpu_ldb = ((nsrow2+31)/32)*32 ; + } + + /* ---------------------------------------------------------------------- */ + /* allocate device memory for the factorization and for potential solve */ + /* ---------------------------------------------------------------------- */ + + cudaStat = cudaMalloc ((void **) &devPtrA, + gpu_lda * (gpu_lda + gpu_ldb) * L_ENTRY * sizeof (devPtrA [0])) ; + if (cudaStat) + { + GPU_Printf ("@@gpu_lower_potrf cudaMalloc failed =%d gpu_lda=%d\n", + cudaStat, (int) (gpu_lda)) ; + /* cudaMalloc failure not fatal, GPU bypassed */ + return (0) ; + } +#ifndef NTIMER + Common->CHOLMOD_GPU_POTRF_CALLS++ ; +#endif + + /* ---------------------------------------------------------------------- */ + /* remember where device memory is, to be used by triangular solve later */ + /* ---------------------------------------------------------------------- */ + + Common->devPotrfWork = devPtrA ; + devPtrB = devPtrA + gpu_lda * gpu_lda * L_ENTRY ; + + /* ---------------------------------------------------------------------- */ + /* copy B in advance, for gpu_triangular_solve */ + /* ---------------------------------------------------------------------- */ + + if (nsrow2 > 0) + { + cudaStat = cudaMemcpy2DAsync (devPtrB, + gpu_ldb * L_ENTRY * sizeof (devPtrB [0]), + Lx + L_ENTRY * (psx + nscol2), + nsrow * L_ENTRY * sizeof (Lx [0]), + nsrow2 * L_ENTRY * sizeof (devPtrB [0]), + nscol2, cudaMemcpyHostToDevice, Common->cudaStreamTrsm) ; + if (cudaStat) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU memcopy to device") ; + } + } + + /* ---------------------------------------------------------------------- */ + /* block Cholesky factorization of S */ + /* ---------------------------------------------------------------------- */ + + for (j = 0 ; j < n ; j += nb) + { + Int jb = nb < (n-j) ? nb : (n-j) ; + + /* ------------------------------------------------------------------ */ + /* copy jb columns starting at the diagonal to the GPU */ + /* ------------------------------------------------------------------ */ + + cudaStat = cudaMemcpy2DAsync (devPtrA + (j + j*gpu_lda)*L_ENTRY, + gpu_lda * L_ENTRY * sizeof (devPtrA [0]), + A + L_ENTRY*(j + j*lda), + lda * L_ENTRY * sizeof (A [0]), + (n-j) * L_ENTRY * sizeof (devPtrA [0]), + jb, cudaMemcpyHostToDevice, Common->cudaStreamPotrf [0]) ; + if (cudaStat) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU memcopy to device") ; + } + + /* ------------------------------------------------------------------ */ + /* define the dpotrf stream */ + /* ------------------------------------------------------------------ */ + + cublasStatus = cublasSetStream (Common->cublasHandle, + Common->cudaStreamPotrf [0]) ; + if (cublasStatus != CUBLAS_STATUS_SUCCESS) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU CUBLAS stream") ; + } + + /* ------------------------------------------------------------------ */ + /* record the end of the copy of block L22 | L32 */ + /* ------------------------------------------------------------------ */ + + cudaStat = cudaEventRecord (Common->cublasEventPotrf [0], + Common->cudaStreamPotrf [0]) ; + if (cudaStat) + { + ERROR (CHOLMOD_GPU_PROBLEM, "CUDA event failure") ; + } + + /* ------------------------------------------------------------------ */ + /* do the CUDA BLAS dsyrk */ + /* ------------------------------------------------------------------ */ + + alpha = -1.0 ; + beta = 1.0 ; +#ifdef REAL + cublasStatus = cublasDsyrk (Common->cublasHandle, + CUBLAS_FILL_MODE_LOWER, CUBLAS_OP_N, jb, j, + &alpha, devPtrA + j, gpu_lda, + &beta, devPtrA + j + j*gpu_lda, gpu_lda) ; +#else + cublasStatus = cublasZherk (Common->cublasHandle, + CUBLAS_FILL_MODE_LOWER, CUBLAS_OP_N, jb, j, + &alpha, (cuDoubleComplex*)devPtrA + j, gpu_lda, + &beta, (cuDoubleComplex*)devPtrA + j + j*gpu_lda, gpu_lda) ; +#endif + if (cublasStatus != CUBLAS_STATUS_SUCCESS) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU CUBLAS routine failure") ; + } + + /* ------------------------------------------------------------------ */ + + cudaStat = cudaEventRecord (Common->cublasEventPotrf [1], + Common->cudaStreamPotrf [0]) ; + if (cudaStat) + { + ERROR (CHOLMOD_GPU_PROBLEM, "CUDA event failure") ; + } + + cudaStat = cudaStreamWaitEvent (Common->cudaStreamPotrf [1], + Common->cublasEventPotrf [1], 0) ; + if (cudaStat) + { + ERROR (CHOLMOD_GPU_PROBLEM, "CUDA event failure") ; + } + + /* ------------------------------------------------------------------ */ + /* copy back the jb columns on two different streams */ + /* ------------------------------------------------------------------ */ + + cudaStat = cudaMemcpy2DAsync (A + L_ENTRY*(j + j*lda), + lda * L_ENTRY * sizeof (double), + devPtrA + L_ENTRY*(j + j*gpu_lda), + gpu_lda * L_ENTRY * sizeof (double), + L_ENTRY * sizeof (double)*jb, jb, + cudaMemcpyDeviceToHost, Common->cudaStreamPotrf [1]) ; + if (cudaStat) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU memcopy from device") ; + } + + cudaStat = cudaMemcpy2DAsync (A + L_ENTRY*j, + lda * L_ENTRY * sizeof (double), + devPtrA + L_ENTRY*j, + gpu_lda * L_ENTRY * sizeof (double), + L_ENTRY * sizeof (double)*jb, j, + cudaMemcpyDeviceToHost, Common->cudaStreamPotrf [0]) ; + if (cudaStat) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU memcopy to device") ; + } + + /* ------------------------------------------------------------------ */ + /* do the CUDA BLAS dgemm */ + /* ------------------------------------------------------------------ */ + + if ((j+jb) < n) + { +#ifdef REAL + alpha = -1.0 ; + beta = 1.0 ; + cublasStatus = cublasDgemm (Common->cublasHandle, + CUBLAS_OP_N, CUBLAS_OP_T, + (n-j-jb), jb, j, + &alpha, + devPtrA + (j+jb), gpu_lda, + devPtrA + (j) , gpu_lda, + &beta, + devPtrA + (j+jb + j*gpu_lda), gpu_lda) ; +#else + cuDoubleComplex calpha = {-1.0,0.0} ; + cuDoubleComplex cbeta = { 1.0,0.0} ; + cublasStatus = cublasZgemm (Common->cublasHandle, + CUBLAS_OP_N, CUBLAS_OP_C, + (n-j-jb), jb, j, + &calpha, + (cuDoubleComplex*)devPtrA + (j+jb), gpu_lda, + (cuDoubleComplex*)devPtrA + (j) , gpu_lda, + &cbeta, + (cuDoubleComplex*)devPtrA + (j+jb + j*gpu_lda), gpu_lda) ; +#endif + if (cublasStatus != CUBLAS_STATUS_SUCCESS) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU CUBLAS routine failure") ; + } + } + + cudaStat = cudaStreamSynchronize (Common->cudaStreamPotrf [1]) ; + if (cudaStat) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU memcopy to device") ; + } + + /* ------------------------------------------------------------------ */ + /* compute the Cholesky factorization of the jbxjb block on the CPU */ + /* ------------------------------------------------------------------ */ + + ilda = (int) lda ; + ijb = jb ; +#ifdef REAL + LAPACK_DPOTRF ("L", &ijb, A + L_ENTRY * (j + j*lda), &ilda, &iinfo) ; +#else + LAPACK_ZPOTRF ("L", &ijb, A + L_ENTRY * (j + j*lda), &ilda, &iinfo) ; +#endif + *info = iinfo ; + + if (*info != 0) + { + *info = *info + j ; + break ; + } + + /* ------------------------------------------------------------------ */ + /* copy the result back to the GPU */ + /* ------------------------------------------------------------------ */ + + cudaStat = cudaMemcpy2DAsync (devPtrA + L_ENTRY*(j + j*gpu_lda), + gpu_lda * L_ENTRY * sizeof (double), + A + L_ENTRY * (j + j*lda), + lda * L_ENTRY * sizeof (double), + L_ENTRY * sizeof (double) * jb, jb, + cudaMemcpyHostToDevice, Common->cudaStreamPotrf [0]) ; + if (cudaStat) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU memcopy to device") ; + } + + /* ------------------------------------------------------------------ */ + /* do the CUDA BLAS dtrsm */ + /* ------------------------------------------------------------------ */ + + if ((j+jb) < n) + { +#ifdef REAL + alpha = 1.0 ; + cublasStatus = cublasDtrsm (Common->cublasHandle, + CUBLAS_SIDE_RIGHT, CUBLAS_FILL_MODE_LOWER, + CUBLAS_OP_T, CUBLAS_DIAG_NON_UNIT, + (n-j-jb), jb, + &alpha, + devPtrA + (j + j*gpu_lda), gpu_lda, + devPtrA + (j+jb + j*gpu_lda), gpu_lda) ; +#else + cuDoubleComplex calpha = {1.0,0.0}; + cublasStatus = cublasZtrsm (Common->cublasHandle, + CUBLAS_SIDE_RIGHT, CUBLAS_FILL_MODE_LOWER, + CUBLAS_OP_C, CUBLAS_DIAG_NON_UNIT, + (n-j-jb), jb, + &calpha, + (cuDoubleComplex *)devPtrA + (j + j*gpu_lda), gpu_lda, + (cuDoubleComplex *)devPtrA + (j+jb + j*gpu_lda), gpu_lda) ; +#endif + if (cublasStatus != CUBLAS_STATUS_SUCCESS) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU CUBLAS routine failure") ; + } + } + } + + if (nsrow2 <= 0) + { + /* No TRSM necessary */ + cudaFree (Common->devPotrfWork) ; + Common->devPotrfWork = NULL ; + } + +#ifndef NTIMER + Common->CHOLMOD_GPU_POTRF_TIME += SuiteSparse_time ( ) - tstart ; +#endif + return (1) ; +} + + +/* ========================================================================== */ +/* === gpu_triangular_solve ================================================= */ +/* ========================================================================== */ + +/* The current supernode is columns k1 to k2-1 of L. Let L1 be the diagonal + * block (factorized by dpotrf/zpotrf above; rows/cols k1:k2-1), and L2 be rows + * k2:n-1 and columns k1:k2-1 of L. The triangular system to solve is L2*L1' = + * S2, where S2 is overwritten with L2. More precisely, L2 = S2 / L1' in + * MATLAB notation. + */ + +/* Version with pre-allocation in POTRF */ + +int TEMPLATE (CHOLMOD (gpu_triangular_solve)) +( + Int nsrow2, /* L1 and S2 are nsrow2-by-nscol2 */ + Int nscol2, /* L1 is nscol2-by-nscol2 */ + Int nsrow, /* leading dimension of L1, L2, and S2 */ + Int psx, /* L1 is at Lx+L_ENTRY*psx; L2 at Lx+L_ENTRY*(psx+nscol2)*/ + double *Lx, /* holds L1, L2, and S2 */ + cholmod_common *Common +) +{ + double *devPtrA, *devPtrB ; + cudaError_t cudaStat ; + cublasStatus_t cublasStatus ; + Int gpu_lda, gpu_ldb ; +#ifdef REAL + double alpha = 1.0 ; +#else + cuDoubleComplex calpha = {1.0,0.0} ; +#endif + + if (!Common->devPotrfWork) + { + /* no workspace for triangular solve */ + return (0) ; + } + +#ifndef NTIMER + double tstart = SuiteSparse_time ( ) ; + Common->CHOLMOD_GPU_TRSM_CALLS++ ; +#endif + + gpu_lda = ((nscol2+31)/32)*32 ; + gpu_ldb = ((nsrow2+31)/32)*32 ; + + devPtrA = Common->devPotrfWork ; + devPtrB = devPtrA + gpu_lda * gpu_lda * L_ENTRY ; + + /* ---------------------------------------------------------------------- */ + /* start the trsm stream */ + /* ---------------------------------------------------------------------- */ + + cublasStatus = cublasSetStream (Common->cublasHandle, + Common->cudaStreamTrsm) ; + if (cublasStatus != CUBLAS_STATUS_SUCCESS) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU CUBLAS stream") ; + } + + /* ---------------------------------------------------------------------- */ + /* do the CUDA BLAS dtrsm */ + /* ---------------------------------------------------------------------- */ + +#ifdef REAL + cublasStatus = cublasDtrsm (Common->cublasHandle, + CUBLAS_SIDE_RIGHT, CUBLAS_FILL_MODE_LOWER, + CUBLAS_OP_T, CUBLAS_DIAG_NON_UNIT, + nsrow2, nscol2, /* M, N */ + &alpha, /* ALPHA: 1 */ + devPtrA, gpu_lda, /* A, LDA */ + devPtrB, gpu_ldb) ; /* B, LDB */ +#else + cublasStatus = cublasZtrsm (Common->cublasHandle, + CUBLAS_SIDE_RIGHT, CUBLAS_FILL_MODE_LOWER, + CUBLAS_OP_C, CUBLAS_DIAG_NON_UNIT, + nsrow2, nscol2, /* M, N */ + &calpha, /* ALPHA: 1 */ + (const cuDoubleComplex *) devPtrA, gpu_lda, /* A, LDA */ + (cuDoubleComplex *) devPtrB, gpu_ldb) ; /* B, LDB: nsrow2 */ +#endif + if (cublasStatus != CUBLAS_STATUS_SUCCESS) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU CUBLAS routine failure") ; + } + + /* ---------------------------------------------------------------------- */ + /* copy result back to the CPU */ + /* ---------------------------------------------------------------------- */ + + cudaStat = cudaMemcpy2DAsync (Lx + L_ENTRY*(psx + nscol2), + nsrow * L_ENTRY * sizeof (Lx [0]), + devPtrB, gpu_ldb * L_ENTRY * sizeof (devPtrB [0]), + nsrow2 * L_ENTRY * sizeof (devPtrB [0]), + nscol2, cudaMemcpyDeviceToHost, Common->cudaStreamTrsm) ; + if (cudaStat) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU memcopy from device") ; + } + + /* ---------------------------------------------------------------------- */ + /* synchronize with the GPU */ + /* ---------------------------------------------------------------------- */ + + cudaStat = cudaThreadSynchronize ( ) ; + if (cudaStat) + { + ERROR (CHOLMOD_GPU_PROBLEM, "GPU synchronization failure") ; + } + + /* ---------------------------------------------------------------------- */ + /* free workspace and return */ + /* ---------------------------------------------------------------------- */ + + cudaFree (Common->devPotrfWork) ; + Common->devPotrfWork = NULL ; +#ifndef NTIMER + Common->CHOLMOD_GPU_TRSM_TIME += SuiteSparse_time ( ) - tstart ; +#endif + return (1) ; +} + +#undef REAL +#undef COMPLEX +#undef ZOMPLEX diff --git a/src/CHOLMOD/Supernodal/t_cholmod_super_numeric.c b/src/CHOLMOD/Supernodal/t_cholmod_super_numeric.c new file mode 100644 index 0000000..7ddd780 --- /dev/null +++ b/src/CHOLMOD/Supernodal/t_cholmod_super_numeric.c @@ -0,0 +1,912 @@ +/* ========================================================================== */ +/* === Supernodal/t_cholmod_super_numeric =================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Supernodal Module. Copyright (C) 2005-2012, Timothy A. Davis + * The CHOLMOD/Supernodal Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Template routine for cholmod_super_numeric. All xtypes supported, except + * that a zomplex A and F result in a complex L (there is no supernodal + * zomplex L). + */ + +/* ========================================================================== */ +/* === complex arithmetic =================================================== */ +/* ========================================================================== */ + +#include "cholmod_template.h" + +#ifdef USING_R +#include +#ifdef HAVE_F77_UNDERSCORE +# define F77_CALL(x) x ## _ +#else +# define F77_CALL(x) x +#endif +#define F77_NAME(x) F77_CALL(x) +#define F77_SUB(x) F77_CALL(x) +#define F77_COM(x) F77_CALL(x) +#define F77_COMDECL(x) F77_CALL(x) +void F77_NAME(dsyrk)(const char *uplo, const char *trans, + const int *n, const int *k, + const double *alpha, const double *a, const int *lda, + const double *beta, double *c, const int *ldc); + +void F77_NAME(dpotrf)(const char* uplo, const int* n, + double* a, const int* lda, int* info); + +void F77_NAME(dtrsm)(const char *side, const char *uplo, + const char *transa, const char *diag, + const int *m, const int *n, const double *alpha, + const double *a, const int *lda, + double *b, const int *ldb); + +void F77_NAME(dtrsv)(const char *uplo, const char *trans, + const char *diag, const int *n, + const double *a, const int *lda, + double *x, const int *incx); +#endif + +#undef L_ENTRY +#undef L_CLEAR +#undef L_ASSIGN +#undef L_MULTADD +#undef L_ASSEMBLE +#undef L_ASSEMBLESUB + +#ifdef REAL + +/* -------------------------------------------------------------------------- */ +/* A, F, and L are all real */ +/* -------------------------------------------------------------------------- */ + +#define L_ENTRY 1 +#define L_CLEAR(Lx,p) Lx [p] = 0 +#define L_ASSIGN(Lx,q, Ax,Az,p) Lx [q] = Ax [p] +#define L_MULTADD(Lx,q, Ax,Az,p, f) Lx [q] += Ax [p] * f [0] +#define L_ASSEMBLE(Lx,q,b) Lx [q] += b [0] +#define L_ASSEMBLESUB(Lx,q,C,p) Lx [q] -= C [p] + +#else + +/* -------------------------------------------------------------------------- */ +/* A and F are complex or zomplex, L and C are complex */ +/* -------------------------------------------------------------------------- */ + +#define L_ENTRY 2 +#define L_CLEAR(Lx,p) Lx [2*(p)] = 0 ; Lx [2*(p)+1] = 0 +#define L_ASSEMBLE(Lx,q,b) Lx [2*(q)] += b [0] ; +#define L_ASSEMBLESUB(Lx,q,C,p) \ + Lx [2*(q) ] -= C [2*(p) ] ; \ + Lx [2*(q)+1] -= C [2*(p)+1] ; + +#ifdef COMPLEX + +/* -------------------------------------------------------------------------- */ +/* A, F, L, and C are all complex */ +/* -------------------------------------------------------------------------- */ + +#define L_ASSIGN(Lx,q, Ax,Az,p) \ + Lx [2*(q) ] = Ax [2*(p) ] ; \ + Lx [2*(q)+1] = Ax [2*(p)+1] + +#define L_MULTADD(Lx,q, Ax,Az,p, f) \ + Lx [2*(q) ] += Ax [2*(p) ] * f [0] - Ax [2*(p)+1] * f [1] ; \ + Lx [2*(q)+1] += Ax [2*(p)+1] * f [0] + Ax [2*(p) ] * f [1] + +#else + +/* -------------------------------------------------------------------------- */ +/* A and F are zomplex, L and C is complex */ +/* -------------------------------------------------------------------------- */ + +#define L_ASSIGN(Lx,q, Ax,Az,p) \ + Lx [2*(q) ] = Ax [p] ; \ + Lx [2*(q)+1] = Az [p] ; + +#define L_MULTADD(Lx,q, Ax,Az,p, f) \ + Lx [2*(q) ] += Ax [p] * f [0] - Az [p] * f [1] ; \ + Lx [2*(q)+1] += Az [p] * f [0] + Ax [p] * f [1] + +#endif +#endif + + +/* ========================================================================== */ +/* === t_cholmod_super_numeric ============================================== */ +/* ========================================================================== */ + +/* This function returns FALSE only if integer overflow occurs in the BLAS. + * It returns TRUE otherwise whether or not the matrix is positive definite. */ + +static int TEMPLATE (cholmod_super_numeric) +( + /* ---- input ---- */ + cholmod_sparse *A, /* matrix to factorize */ + cholmod_sparse *F, /* F = A' or A(:,f)' */ + double beta [2], /* beta*I is added to diagonal of matrix to factorize */ + /* ---- in/out --- */ + cholmod_factor *L, /* factorization */ + /* -- workspace -- */ + cholmod_dense *Cwork, /* size (L->maxcsize)-by-1 */ + /* --------------- */ + cholmod_common *Common +) +{ + double one [2], zero [2], fjk [2], tstart ; + double *Lx, *Ax, *Fx, *Az, *Fz, *C ; + Int *Super, *Head, *Ls, *Lpi, *Lpx, *Map, *SuperMap, *RelativeMap, *Next, + *Lpos, *Fp, *Fi, *Fnz, *Ap, *Ai, *Anz, *Iwork, *Next_save, *Lpos_save ; + Int nsuper, n, j, i, k, s, p, pend, k1, k2, nscol, psi, psx, psend, nsrow, + pj, d, kd1, kd2, info, ndcol, ndrow, pdi, pdx, pdend, pdi1, pdi2, pdx1, + ndrow1, ndrow2, px, dancestor, sparent, dnext, nsrow2, ndrow3, pk, pf, + pfend, stype, Apacked, Fpacked, q, imap, repeat_supernode, nscol2, ss, + nscol_new = 0 ; + + /* If integer overflow occurs in the BLAS, Common->status is set to + * CHOLMOD_TOO_LARGE, and the contents of Lx are undefined. */ + Common->blas_ok = TRUE ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nsuper = L->nsuper ; + n = L->n ; + + C = Cwork->x ; /* workspace of size L->maxcsize */ + + one [0] = 1.0 ; /* ALPHA for *syrk, *herk, *gemm, and *trsm */ + one [1] = 0. ; + + zero [0] = 0. ; /* BETA for *syrk, *herk, and *gemm */ + zero [1] = 0. ; + + Iwork = Common->Iwork ; + SuperMap = Iwork ; /* size n (i/i/l) */ + RelativeMap = Iwork + n ; /* size n (i/i/l) */ + Next = Iwork + 2*((size_t) n) ; /* size nsuper*/ + Lpos = Iwork + 2*((size_t) n) + nsuper ; /* size nsuper*/ + Next_save = Iwork + 2*((size_t) n) + 2*((size_t) nsuper) ;/* size nsuper*/ + Lpos_save = Iwork + 2*((size_t) n) + 3*((size_t) nsuper) ;/* size nsuper*/ + + Map = Common->Flag ; /* size n, use Flag as workspace for Map array */ + Head = Common->Head ; /* size n+1, only Head [0..nsuper-1] used */ + + Ls = L->s ; + Lpi = L->pi ; + Lpx = L->px ; + + Super = L->super ; + + Lx = L->x ; + +#ifdef GPU_BLAS + TEMPLATE (CHOLMOD (gpu_init)) (C, L->maxcsize, Common) ; +#endif + +#ifndef NTIMER + /* clear GPU / CPU statistics */ + Common->CHOLMOD_CPU_GEMM_CALLS = 0 ; + Common->CHOLMOD_CPU_SYRK_CALLS = 0 ; + Common->CHOLMOD_CPU_TRSM_CALLS = 0 ; + Common->CHOLMOD_CPU_POTRF_CALLS = 0 ; + Common->CHOLMOD_GPU_GEMM_CALLS = 0 ; + Common->CHOLMOD_GPU_SYRK_CALLS = 0 ; + Common->CHOLMOD_GPU_TRSM_CALLS = 0 ; + Common->CHOLMOD_GPU_POTRF_CALLS = 0 ; + Common->CHOLMOD_CPU_GEMM_TIME = 0 ; + Common->CHOLMOD_CPU_SYRK_TIME = 0 ; + Common->CHOLMOD_CPU_TRSM_TIME = 0 ; + Common->CHOLMOD_CPU_POTRF_TIME = 0 ; + Common->CHOLMOD_GPU_GEMM_TIME = 0 ; + Common->CHOLMOD_GPU_SYRK_TIME = 0 ; + Common->CHOLMOD_GPU_TRSM_TIME = 0 ; + Common->CHOLMOD_GPU_POTRF_TIME = 0 ; + Common->CHOLMOD_ASSEMBLE_TIME = 0 ; + Common->CHOLMOD_ASSEMBLE_TIME2 = 0 ; +#endif + + stype = A->stype ; + + if (stype != 0) + { + /* F not accessed */ + Fp = NULL ; + Fi = NULL ; + Fx = NULL ; + Fz = NULL ; + Fnz = NULL ; + Fpacked = TRUE ; + } + else + { + Fp = F->p ; + Fi = F->i ; + Fx = F->x ; + Fz = F->z ; + Fnz = F->nz ; + Fpacked = F->packed ; + } + + Ap = A->p ; + Ai = A->i ; + Ax = A->x ; + Az = A->z ; + Anz = A->nz ; + Apacked = A->packed ; + + /* clear the Map so that changes in the pattern of A can be detected */ + for (i = 0 ; i < n ; i++) + { + Map [i] = EMPTY ; + } + + /* If the matrix is not positive definite, the supernode s containing the + * first zero or negative diagonal entry of L is repeated (but factorized + * only up to just before the problematic diagonal entry). The purpose is + * to provide MATLAB with [R,p]=chol(A); columns 1 to p-1 of L=R' are + * required, where L(p,p) is the problematic diagonal entry. The + * repeat_supernode flag tells us whether this is the repeated supernode. + * Once supernode s is repeated, the factorization is terminated. */ + repeat_supernode = FALSE ; + + /* ---------------------------------------------------------------------- */ + /* supernodal numerical factorization */ + /* ---------------------------------------------------------------------- */ + + for (s = 0 ; s < nsuper ; s++) + { + + /* ------------------------------------------------------------------ */ + /* get the size of supernode s */ + /* ------------------------------------------------------------------ */ + + k1 = Super [s] ; /* s contains columns k1 to k2-1 of L */ + k2 = Super [s+1] ; + nscol = k2 - k1 ; /* # of columns in all of s */ + psi = Lpi [s] ; /* pointer to first row of s in Ls */ + psx = Lpx [s] ; /* pointer to first row of s in Lx */ + psend = Lpi [s+1] ; /* pointer just past last row of s in Ls */ + nsrow = psend - psi ; /* # of rows in all of s */ + + PRINT1 (("====================================================\n" + "S "ID" k1 "ID" k2 "ID" nsrow "ID" nscol "ID" psi "ID" psend " + ""ID" psx "ID"\n", s, k1, k2, nsrow, nscol, psi, psend, psx)) ; + + /* ------------------------------------------------------------------ */ + /* zero the supernode s */ + /* ------------------------------------------------------------------ */ + + ASSERT ((size_t) (psx + nsrow*nscol) <= L->xsize) ; + + pend = psx + nsrow * nscol ; /* s is nsrow-by-nscol */ + for (p = psx ; p < pend ; p++) + { + /* Lx [p] = 0 ; */ + L_CLEAR (Lx,p) ; + } + + /* ------------------------------------------------------------------ */ + /* construct the scattered Map for supernode s */ + /* ------------------------------------------------------------------ */ + + /* If row i is the kth row in s, then Map [i] = k. Similarly, if + * column j is the kth column in s, then Map [j] = k. */ + + for (k = 0 ; k < nsrow ; k++) + { + PRINT1 ((" "ID" map "ID"\n", Ls [psi+k], k)) ; + Map [Ls [psi + k]] = k ; + } + + /* ------------------------------------------------------------------ */ + /* copy matrix into supernode s (lower triangular part only) */ + /* ------------------------------------------------------------------ */ + + pk = psx ; + for (k = k1 ; k < k2 ; k++) + { + if (stype != 0) + { + /* copy the kth column of A into the supernode */ + p = Ap [k] ; + pend = (Apacked) ? (Ap [k+1]) : (p + Anz [k]) ; + for ( ; p < pend ; p++) + { + /* row i of L is located in row Map [i] of s */ + i = Ai [p] ; + if (i >= k) + { + /* This test is here simply to avoid a segfault. If + * the test is false, the numeric factorization of A + * is undefined. It does not detect all invalid + * entries, only some of them (when debugging is + * enabled, and Map is cleared after each step, then + * all entries not in the pattern of L are detected). */ + imap = Map [i] ; + if (imap >= 0 && imap < nsrow) + { + /* Lx [Map [i] + pk] = Ax [p] ; */ + L_ASSIGN (Lx,(imap+pk), Ax,Az,p) ; + } + } + } + } + else + { + /* copy the kth column of A*F into the supernode */ + pf = Fp [k] ; + pfend = (Fpacked) ? (Fp [k+1]) : (p + Fnz [k]) ; + for ( ; pf < pfend ; pf++) + { + j = Fi [pf] ; + + /* fjk = Fx [pf] ; */ + L_ASSIGN (fjk,0, Fx,Fz,pf) ; + + p = Ap [j] ; + pend = (Apacked) ? (Ap [j+1]) : (p + Anz [j]) ; + for ( ; p < pend ; p++) + { + i = Ai [p] ; + if (i >= k) + { + /* See the discussion of imap above. */ + imap = Map [i] ; + if (imap >= 0 && imap < nsrow) + { + /* Lx [Map [i] + pk] += Ax [p] * fjk ; */ + L_MULTADD (Lx,(imap+pk), Ax,Az,p, fjk) ; + } + } + } + } + } + pk += nsrow ; /* advance to the next column of the supernode */ + } + + /* add beta to the diagonal of the supernode, if nonzero */ + if (beta [0] != 0.0) + { + /* note that only the real part of beta is used */ + pk = psx ; + for (k = k1 ; k < k2 ; k++) + { + /* Lx [pk] += beta [0] ; */ + L_ASSEMBLE (Lx,pk, beta) ; + pk += nsrow + 1 ; /* advance to the next diagonal entry */ + } + } + + PRINT1 (("Supernode with just A: repeat: "ID"\n", repeat_supernode)) ; + DEBUG (CHOLMOD(dump_super) (s, Super, Lpi, Ls, Lpx, Lx, L_ENTRY, + Common)) ; + PRINT1 (("\n\n")) ; + + /* ------------------------------------------------------------------ */ + /* save/restore the list of supernodes */ + /* ------------------------------------------------------------------ */ + + if (!repeat_supernode) + { + /* Save the list of pending descendants in case s is not positive + * definite. Also save Lpos for each descendant d, so that we can + * find which part of d is used to update s. */ + for (d = Head [s] ; d != EMPTY ; d = Next [d]) + { + Lpos_save [d] = Lpos [d] ; + Next_save [d] = Next [d] ; + } + } + else + { + /* s is not positive definite, and is being repeated. Restore + * the list of supernodes. This can be done with pointer assignment + * because all 4 arrays are held within Common->Iwork. */ + Lpos = Lpos_save ; + Next = Next_save ; + } + + /* ------------------------------------------------------------------ */ + /* update supernode s with each pending descendant d */ + /* ------------------------------------------------------------------ */ + +#ifndef NDEBUG + for (d = Head [s] ; d != EMPTY ; d = Next [d]) + { + PRINT1 (("\nWill update "ID" with Child: "ID"\n", s, d)) ; + DEBUG (CHOLMOD(dump_super) (d, Super, Lpi, Ls, Lpx, Lx, L_ENTRY, + Common)) ; + } + PRINT1 (("\nNow factorizing supernode "ID":\n", s)) ; +#endif + + for (d = Head [s] ; d != EMPTY ; d = dnext) + { + + /* -------------------------------------------------------------- */ + /* get the size of supernode d */ + /* -------------------------------------------------------------- */ + + kd1 = Super [d] ; /* d contains cols kd1 to kd2-1 of L */ + kd2 = Super [d+1] ; + ndcol = kd2 - kd1 ; /* # of columns in all of d */ + pdi = Lpi [d] ; /* pointer to first row of d in Ls */ + pdx = Lpx [d] ; /* pointer to first row of d in Lx */ + pdend = Lpi [d+1] ; /* pointer just past last row of d in Ls */ + ndrow = pdend - pdi ; /* # rows in all of d */ + + PRINT1 (("Child: ")) ; + DEBUG (CHOLMOD(dump_super) (d, Super, Lpi, Ls, Lpx, Lx, L_ENTRY, + Common)) ; + + /* -------------------------------------------------------------- */ + /* find the range of rows of d that affect rows k1 to k2-1 of s */ + /* -------------------------------------------------------------- */ + + p = Lpos [d] ; /* offset of 1st row of d affecting s */ + pdi1 = pdi + p ; /* ptr to 1st row of d affecting s in Ls */ + pdx1 = pdx + p ; /* ptr to 1st row of d affecting s in Lx */ + + /* there must be at least one row remaining in d to update s */ + ASSERT (pdi1 < pdend) ; + PRINT1 (("Lpos[d] "ID" pdi1 "ID" Ls[pdi1] "ID"\n", + Lpos[d], pdi1, Ls [pdi1])) ; + ASSERT (Ls [pdi1] >= k1 && Ls [pdi1] < k2) ; + + for (pdi2 = pdi1 ; pdi2 < pdend && Ls [pdi2] < k2 ; pdi2++) ; + ndrow1 = pdi2 - pdi1 ; /* # rows in first part of d */ + ndrow2 = pdend - pdi1 ; /* # rows in remaining d */ + + /* rows Ls [pdi1 ... pdi2-1] are in the range k1 to k2-1. Since d + * affects s, this set cannot be empty. */ + ASSERT (pdi1 < pdi2 && pdi2 <= pdend) ; + PRINT1 (("ndrow1 "ID" ndrow2 "ID"\n", ndrow1, ndrow2)) ; + DEBUG (for (p = pdi1 ; p < pdi2 ; p++) + PRINT1 (("Ls["ID"] "ID"\n", p, Ls[p]))) ; + + /* -------------------------------------------------------------- */ + /* construct the update matrix C for this supernode d */ + /* -------------------------------------------------------------- */ + + /* C = L (k1:n-1, kd1:kd2-1) * L (k1:k2-1, kd1:kd2-1)', except + * that k1:n-1 refers to all of the rows in L, but many of the + * rows are all zero. Supernode d holds columns kd1 to kd2-1 of L. + * Nonzero rows in the range k1:k2-1 are in the list + * Ls [pdi1 ... pdi2-1], of size ndrow1. Nonzero rows in the range + * k2:n-1 are in the list Ls [pdi2 ... pdend], of size ndrow2. Let + * L1 = L (Ls [pdi1 ... pdi2-1], kd1:kd2-1), and let + * L2 = L (Ls [pdi2 ... pdend], kd1:kd2-1). C is ndrow2-by-ndrow1. + * Let C1 be the first ndrow1 rows of C and let C2 be the last + * ndrow2-ndrow1 rows of C. Only the lower triangular part of C1 + * needs to be computed since C1 is symmetric. + */ + + /* maxcsize is the largest size of C for all pairs (d,s) */ + ASSERT (ndrow2 * ndrow1 <= ((Int) L->maxcsize)) ; + + /* compute leading ndrow1-by-ndrow1 lower triangular block of C, + * C1 = L1*L1' */ + + ndrow3 = ndrow2 - ndrow1 ; /* number of rows of C2 */ + ASSERT (ndrow3 >= 0) ; + +#ifdef GPU_BLAS + if (!TEMPLATE (CHOLMOD (gpu_updateC)) + (ndrow1, ndrow2, ndrow, ndcol, pdx1, Lx, C, Common)) +#endif + { +#ifndef NTIMER + Common->CHOLMOD_CPU_SYRK_CALLS++ ; + tstart = SuiteSparse_time () ; +#endif +#ifdef REAL + BLAS_dsyrk ("L", "N", + ndrow1, ndcol, /* N, K: L1 is ndrow1-by-ndcol*/ + one, /* ALPHA: 1 */ + Lx + L_ENTRY*pdx1, ndrow, /* A, LDA: L1, ndrow */ + zero, /* BETA: 0 */ + C, ndrow2) ; /* C, LDC: C1 */ +#else + BLAS_zherk ("L", "N", + ndrow1, ndcol, /* N, K: L1 is ndrow1-by-ndcol*/ + one, /* ALPHA: 1 */ + Lx + L_ENTRY*pdx1, ndrow, /* A, LDA: L1, ndrow */ + zero, /* BETA: 0 */ + C, ndrow2) ; /* C, LDC: C1 */ +#endif +#ifndef NTIMER + Common->CHOLMOD_CPU_SYRK_TIME += SuiteSparse_time () - tstart ; +#endif + /* compute remaining (ndrow2-ndrow1)-by-ndrow1 block of C, + * C2 = L2*L1' */ + if (ndrow3 > 0) + { +#ifndef NTIMER + Common->CHOLMOD_CPU_GEMM_CALLS++ ; + tstart = SuiteSparse_time () ; +#endif +#ifdef REAL + BLAS_dgemm ("N", "C", + ndrow3, ndrow1, ndcol, /* M, N, K */ + one, /* ALPHA: 1 */ + Lx + L_ENTRY*(pdx1 + ndrow1), /* A, LDA: L2, ndrow */ + ndrow, + Lx + L_ENTRY*pdx1, /* B, LDB: L1, ndrow */ + ndrow, + zero, /* BETA: 0 */ + C + L_ENTRY*ndrow1, /* C, LDC: C2 */ + ndrow2) ; +#else + BLAS_zgemm ("N", "C", + ndrow3, ndrow1, ndcol, /* M, N, K */ + one, /* ALPHA: 1 */ + Lx + L_ENTRY*(pdx1 + ndrow1),/* A, LDA: L2, ndrow */ + ndrow, + Lx + L_ENTRY*pdx1, /* B, LDB: L1, ndrow */ + ndrow, + zero, /* BETA: 0 */ + C + L_ENTRY*ndrow1, /* C, LDC: C2 */ + ndrow2) ; +#endif +#ifndef NTIMER + Common->CHOLMOD_CPU_GEMM_TIME += + SuiteSparse_time () - tstart ; +#endif + } + } + + DEBUG (CHOLMOD(dump_real) ("C", C, ndrow2, ndrow1, TRUE, L_ENTRY, + Common)) ; + + /* -------------------------------------------------------------- */ + /* construct relative map to assemble d into s */ + /* -------------------------------------------------------------- */ + + for (i = 0 ; i < ndrow2 ; i++) + { + RelativeMap [i] = Map [Ls [pdi1 + i]] ; + ASSERT (RelativeMap [i] >= 0 && RelativeMap [i] < nsrow) ; + } + + + /* -------------------------------------------------------------- */ + /* assemble C into supernode s using the relative map */ + /* -------------------------------------------------------------- */ + +#ifdef GPU_BLAS + TEMPLATE (CHOLMOD (gpu_syncSyrk)) (Common) ; + if (ndrow3 <= 0) + { +#endif + /* non-GPU version, or GPU version when ndrow3 is zero */ + pj = 0 ; + for (j = 0 ; j < ndrow1 ; j++) /* cols k1:k2-1 */ + { + ASSERT (RelativeMap [j] == Map [Ls [pdi1 + j]]) ; + ASSERT (RelativeMap [j] >= 0 && RelativeMap [j] < nscol) ; + px = psx + RelativeMap [j] * nsrow ; + for (i = j ; i < ndrow2 ; i++) /* rows k1:n-1 */ + { + ASSERT (RelativeMap [i] == Map [Ls [pdi1 + i]]) ; + ASSERT (RelativeMap [i] >= j && RelativeMap[i] < nsrow); + /* Lx [px + RelativeMap [i]] -= C [i + pj] ; */ + q = px + RelativeMap [i] ; + L_ASSEMBLESUB (Lx,q, C, i+pj) ; + } + pj += ndrow2 ; + } +#ifdef GPU_BLAS + } + else + { + /* GPU version when ndrow3 > zero, splits into two parts */ +#ifndef NTIMER + tstart = SuiteSparse_time () ; +#endif + pj = 0 ; + for (j = 0 ; j < ndrow1 ; j++) /* cols k1:k2-1 */ + { + ASSERT (RelativeMap [j] == Map [Ls [pdi1 + j]]) ; + ASSERT (RelativeMap [j] >= 0 && RelativeMap [j] < nscol) ; + px = psx + RelativeMap [j] * nsrow ; + for (i = j ; i < ndrow1 ; i++) /* rows k1:k2-1 */ + { + ASSERT (RelativeMap [i] == Map [Ls [pdi1 + i]]) ; + ASSERT (RelativeMap [i] >= j && RelativeMap[i] < nsrow); + /* Lx [px + RelativeMap [i]] -= C [i + pj] ; */ + q = px + RelativeMap [i] ; + L_ASSEMBLESUB (Lx,q, C, i+pj) ; + } + pj += ndrow2 ; + } +#ifndef NTIMER + Common->CHOLMOD_ASSEMBLE_TIME2 += SuiteSparse_time () - tstart ; +#endif + /* wait for dgemm to finish */ + TEMPLATE (CHOLMOD (gpu_syncGemm)) (Common) ; + pj = 0 ; + for (j = 0 ; j < ndrow1 ; j++) /* cols k1:k2-1 */ + { + ASSERT (RelativeMap [j] == Map [Ls [pdi1 + j]]) ; + ASSERT (RelativeMap [j] >= 0 && RelativeMap [j] < nscol) ; + px = psx + RelativeMap [j] * nsrow ; + for (i = ndrow1 ; i < ndrow2 ; i++) /* rows k2:n-1 */ + { + ASSERT (RelativeMap [i] == Map [Ls [pdi1 + i]]) ; + ASSERT (RelativeMap [i] >= j && RelativeMap[i] < nsrow); + /* Lx [px + RelativeMap [i]] -= C [i + pj] ; */ + q = px + RelativeMap [i] ; + L_ASSEMBLESUB (Lx,q, C, i+pj) ; + } + pj += ndrow2 ; + } +#ifndef NTIMER + Common->CHOLMOD_ASSEMBLE_TIME += SuiteSparse_time () - tstart ; +#endif + } +#endif + + /* -------------------------------------------------------------- */ + /* prepare this supernode d for its next ancestor */ + /* -------------------------------------------------------------- */ + + dnext = Next [d] ; + + if (!repeat_supernode) + { + /* If node s is being repeated, Head [dancestor] has already + * been cleared (set to EMPTY). It must remain EMPTY. The + * dancestor will not be factorized since the factorization + * terminates at node s. */ + Lpos [d] = pdi2 - pdi ; + if (Lpos [d] < ndrow) + { + dancestor = SuperMap [Ls [pdi2]] ; + ASSERT (dancestor > s && dancestor < nsuper) ; + /* place d in the link list of its next ancestor */ + Next [d] = Head [dancestor] ; + Head [dancestor] = d ; + } + } + } + + PRINT1 (("\nSupernode with contributions A: repeat: "ID"\n", + repeat_supernode)) ; + DEBUG (CHOLMOD(dump_super) (s, Super, Lpi, Ls, Lpx, Lx, L_ENTRY, + Common)) ; + PRINT1 (("\n\n")) ; + + /* ------------------------------------------------------------------ */ + /* factorize diagonal block of supernode s in LL' */ + /* ------------------------------------------------------------------ */ + + /* The current supernode s is ready to factorize. It has been updated + * by all descendant supernodes. Let S = the current supernode, which + * holds rows k1:n-1 and columns k1:k2-1 of the updated matrix. It + * splits into two parts: the square diagonal block S1, and the + * rectangular part S2. Here, S1 is factorized into L1*L1' and + * overwritten by L1. + * + * If supernode s is being repeated, only factorize it up to but not + * including the column containing the problematic entry. + */ + + nscol2 = (repeat_supernode) ? (nscol_new) : (nscol) ; + +#ifdef GPU_BLAS + if (!TEMPLATE (CHOLMOD (gpu_lower_potrf)) + (nscol2, nsrow, psx, Lx, &info, Common)) +#endif + { +#ifndef NTIMER + Common->CHOLMOD_CPU_POTRF_CALLS++ ; + tstart = SuiteSparse_time () ; +#endif +#ifdef REAL + LAPACK_dpotrf ("L", + nscol2, /* N: nscol2 */ + Lx + L_ENTRY*psx, nsrow, /* A, LDA: S1, nsrow */ + info) ; /* INFO */ +#else + LAPACK_zpotrf ("L", + nscol2, /* N: nscol2 */ + Lx + L_ENTRY*psx, nsrow, /* A, LDA: S1, nsrow */ + info) ; /* INFO */ +#endif +#ifndef NTIMER + Common->CHOLMOD_CPU_POTRF_TIME += SuiteSparse_time ()- tstart ; +#endif + } + + /* ------------------------------------------------------------------ */ + /* check if the matrix is not positive definite */ + /* ------------------------------------------------------------------ */ + + if (repeat_supernode) + { + /* the leading part has been refactorized; it must have succeeded */ + info = 0 ; + + /* zero out the rest of this supernode */ + p = psx + nsrow * nscol_new ; + pend = psx + nsrow * nscol ; /* s is nsrow-by-nscol */ + for ( ; p < pend ; p++) + { + /* Lx [p] = 0 ; */ + L_CLEAR (Lx,p) ; + } + } + + /* info is set to one in LAPACK_*potrf if blas_ok is FALSE. It is + * set to zero in dpotrf/zpotrf if the factorization was successful. */ + if (CHECK_BLAS_INT && !Common->blas_ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large for the BLAS") ; + } + + if (info != 0) + { + /* Matrix is not positive definite. dpotrf/zpotrf do NOT report an + * error if the diagonal of L has NaN's, only if it has a zero. */ + if (Common->status == CHOLMOD_OK) + { + ERROR (CHOLMOD_NOT_POSDEF, "matrix not positive definite") ; + } + + /* L->minor is the column of L that contains a zero or negative + * diagonal term. */ + L->minor = k1 + info - 1 ; + + /* clear the link lists of all subsequent supernodes */ + for (ss = s+1 ; ss < nsuper ; ss++) + { + Head [ss] = EMPTY ; + } + + /* zero this supernode, and all remaining supernodes */ + pend = L->xsize ; + for (p = psx ; p < pend ; p++) + { + /* Lx [p] = 0. ; */ + L_CLEAR (Lx,p) ; + } + + /* If L is indefinite, it still contains useful information. + * Supernodes 0 to s-1 are valid, similar to MATLAB [R,p]=chol(A), + * where the 1-based p is identical to the 0-based L->minor. Since + * L->minor is in the current supernode s, it and any columns to the + * left of it in supernode s are also all zero. This differs from + * [R,p]=chol(A), which contains nonzero rows 1 to p-1. Fix this + * by setting repeat_supernode to TRUE, and repeating supernode s. + * + * If Common->quick_return_if_not_posdef is true, then the entire + * supernode s is not factorized; it is left as all zero. + */ + + if (info == 1 || Common->quick_return_if_not_posdef) + { + /* If the first column of supernode s contains a zero or + * negative diagonal entry, then it is already properly set to + * zero. Also, info will be 1 if integer overflow occured in + * the BLAS. */ + Head [s] = EMPTY ; +#ifdef GPU_BLAS + TEMPLATE (CHOLMOD (gpu_end)) (Common) ; +#endif + return (Common->status >= CHOLMOD_OK) ; + } + else + { + /* Repeat supernode s, but only factorize it up to but not + * including the column containing the problematic diagonal + * entry. */ + repeat_supernode = TRUE ; + s-- ; + nscol_new = info - 1 ; + continue ; + } + } + + /* ------------------------------------------------------------------ */ + /* compute the subdiagonal block and prepare supernode for its parent */ + /* ------------------------------------------------------------------ */ + + nsrow2 = nsrow - nscol2 ; + if (nsrow2 > 0) + { + /* The current supernode is columns k1 to k2-1 of L. Let L1 be the + * diagonal block (factorized by dpotrf/zpotrf above; rows/cols + * k1:k2-1), and L2 be rows k2:n-1 and columns k1:k2-1 of L. The + * triangular system to solve is L2*L1' = S2, where S2 is + * overwritten with L2. More precisely, L2 = S2 / L1' in MATLAB + * notation. + */ + +#ifdef GPU_BLAS + if (!TEMPLATE (CHOLMOD (gpu_triangular_solve)) + (nsrow2, nscol2, nsrow, psx, Lx, Common)) +#endif + { +#ifndef NTIMER + Common->CHOLMOD_CPU_TRSM_CALLS++ ; + tstart = SuiteSparse_time () ; +#endif +#ifdef REAL + BLAS_dtrsm ("R", "L", "C", "N", + nsrow2, nscol2, /* M, N */ + one, /* ALPHA: 1 */ + Lx + L_ENTRY*psx, nsrow, /* A, LDA: L1, nsrow */ + Lx + L_ENTRY*(psx + nscol2), /* B, LDB, L2, nsrow */ + nsrow) ; +#else + BLAS_ztrsm ("R", "L", "C", "N", + nsrow2, nscol2, /* M, N */ + one, /* ALPHA: 1 */ + Lx + L_ENTRY*psx, nsrow, /* A, LDA: L1, nsrow */ + Lx + L_ENTRY*(psx + nscol2), /* B, LDB, L2, nsrow */ + nsrow) ; +#endif +#ifndef NTIMER + Common->CHOLMOD_CPU_TRSM_TIME += SuiteSparse_time () - tstart ; +#endif + } + + if (CHECK_BLAS_INT && !Common->blas_ok) + { + ERROR (CHOLMOD_TOO_LARGE, "problem too large for the BLAS") ; + } + + if (!repeat_supernode) + { + /* Lpos [s] is offset of first row of s affecting its parent */ + Lpos [s] = nscol ; + sparent = SuperMap [Ls [psi + nscol]] ; + ASSERT (sparent != EMPTY) ; + ASSERT (Ls [psi + nscol] >= Super [sparent]) ; + ASSERT (Ls [psi + nscol] < Super [sparent+1]) ; + ASSERT (SuperMap [Ls [psi + nscol]] == sparent) ; + ASSERT (sparent > s && sparent < nsuper) ; + /* place s in link list of its parent */ + Next [s] = Head [sparent] ; + Head [sparent] = s ; + } + } + + Head [s] = EMPTY ; /* link list for supernode s no longer needed */ + + /* clear the Map (debugging only, to detect changes in pattern of A) */ + DEBUG (for (k = 0 ; k < nsrow ; k++) Map [Ls [psi + k]] = EMPTY) ; + DEBUG (CHOLMOD(dump_super) (s, Super, Lpi, Ls, Lpx, Lx, L_ENTRY, + Common)) ; + + if (repeat_supernode) + { + /* matrix is not positive definite; finished clean-up for supernode + * containing negative diagonal */ + +#ifdef GPU_BLAS + TEMPLATE (CHOLMOD (gpu_end)) (Common) ; +#endif + return (Common->status >= CHOLMOD_OK) ; + } + } + + /* success; matrix is positive definite */ + L->minor = n ; +#ifdef GPU_BLAS + TEMPLATE (CHOLMOD (gpu_end)) (Common) ; +#endif + return (Common->status >= CHOLMOD_OK) ; +} + +#undef PATTERN +#undef REAL +#undef COMPLEX +#undef ZOMPLEX diff --git a/src/CHOLMOD/Supernodal/t_cholmod_super_solve.c b/src/CHOLMOD/Supernodal/t_cholmod_super_solve.c new file mode 100644 index 0000000..4907397 --- /dev/null +++ b/src/CHOLMOD/Supernodal/t_cholmod_super_solve.c @@ -0,0 +1,450 @@ +/* ========================================================================== */ +/* === Supernodal/t_cholmod_super_solve ===================================== */ +/* ========================================================================== */ + +/* ----------------------------------------------------------------------------- + * CHOLMOD/Supernodal Module. Copyright (C) 2005-2006, Timothy A. Davis + * The CHOLMOD/Supernodal Module is licensed under Version 2.0 of the GNU + * General Public License. See gpl.txt for a text of the license. + * CHOLMOD is also available under other licenses; contact authors for details. + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Template routine for cholmod_super_solve. Supports real or complex L. */ + +#include "cholmod_template.h" + +#ifdef USING_R +#include +#ifdef HAVE_F77_UNDERSCORE +# define F77_CALL(x) x ## _ +#else +# define F77_CALL(x) x +#endif +#define F77_NAME(x) F77_CALL(x) +#define F77_SUB(x) F77_CALL(x) +#define F77_COM(x) F77_CALL(x) +#define F77_COMDECL(x) F77_CALL(x) +void F77_NAME(dsyrk)(const char *uplo, const char *trans, + const int *n, const int *k, + const double *alpha, const double *a, const int *lda, + const double *beta, double *c, const int *ldc); + +void F77_NAME(dpotrf)(const char* uplo, const int* n, + double* a, const int* lda, int* info); + +void F77_NAME(dtrsm)(const char *side, const char *uplo, + const char *transa, const char *diag, + const int *m, const int *n, const double *alpha, + const double *a, const int *lda, + double *b, const int *ldb); + +void F77_NAME(dtrsv)(const char *uplo, const char *trans, + const char *diag, const int *n, + const double *a, const int *lda, + double *x, const int *incx); +#endif + +static void TEMPLATE (cholmod_super_lsolve) +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to use for the forward solve */ + /* ---- output ---- */ + cholmod_dense *X, /* b on input, solution to Lx=b on output */ + /* ---- workspace ---- */ + cholmod_dense *E, /* workspace of size nrhs*(L->maxesize) */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Lx, *Xx, *Ex ; + double minus_one [2], one [2] ; + Int *Lpi, *Lpx, *Ls, *Super ; + Int nsuper, k1, k2, psi, psend, psx, nsrow, nscol, ii, s, + nsrow2, n, ps2, j, i, d, nrhs ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nrhs = X->ncol ; + Ex = E->x ; + Xx = X->x ; + n = L->n ; + d = X->d ; + + nsuper = L->nsuper ; + Lpi = L->pi ; + Lpx = L->px ; + Ls = L->s ; + Super = L->super ; + Lx = L->x ; + minus_one [0] = -1.0 ; + minus_one [1] = 0 ; + one [0] = 1.0 ; + one [1] = 0 ; + + /* ---------------------------------------------------------------------- */ + /* solve Lx=b */ + /* ---------------------------------------------------------------------- */ + + if (nrhs == 1) + { + + for (s = 0 ; s < nsuper ; s++) + { + k1 = Super [s] ; + k2 = Super [s+1] ; + psi = Lpi [s] ; + psend = Lpi [s+1] ; + psx = Lpx [s] ; + nsrow = psend - psi ; + nscol = k2 - k1 ; + nsrow2 = nsrow - nscol ; + ps2 = psi + nscol ; + ASSERT ((size_t) nsrow2 <= L->maxesize) ; + + /* L1 is nscol-by-nscol, lower triangular with non-unit diagonal. + * L2 is nsrow2-by-nscol. L1 and L2 have leading dimension of + * nsrow. x1 is nscol-by-nsrow, with leading dimension n. + * E is nsrow2-by-1, with leading dimension nsrow2. + */ + + /* gather X into E */ + for (ii = 0 ; ii < nsrow2 ; ii++) + { + /* Ex [ii] = Xx [Ls [ps2 + ii]] ; */ + ASSIGN (Ex,-,ii, Xx,-,Ls [ps2 + ii]) ; + } + +#ifdef REAL + + /* solve L1*x1 (that is, x1 = L1\x1) */ + BLAS_dtrsv ("L", "N", "N", + nscol, /* N: L1 is nscol-by-nscol */ + Lx + ENTRY_SIZE*psx, nsrow, /* A, LDA: L1 */ + Xx + ENTRY_SIZE*k1, 1) ; /* X, INCX: x1 */ + + /* E = E - L2*x1 */ + BLAS_dgemv ("N", + nsrow2, nscol, /* M, N: L2 is nsrow2-by-nscol */ + minus_one, /* ALPHA: -1 */ + Lx + ENTRY_SIZE*(psx + nscol), /* A, LDA: L2 */ + nsrow, + Xx + ENTRY_SIZE*k1, 1, /* X, INCX: x1 */ + one, /* BETA: 1 */ + Ex, 1) ; /* Y, INCY: E */ + +#else + + /* solve L1*x1 (that is, x1 = L1\x1) */ + BLAS_ztrsv ("L", "N", "N", + nscol, /* N: L1 is nscol-by-nscol */ + Lx + ENTRY_SIZE*psx, nsrow, /* A, LDA: L1 */ + Xx + ENTRY_SIZE*k1, 1) ; /* X, INCX: x1 */ + + /* E = E - L2*x1 */ + BLAS_zgemv ("N", + nsrow2, nscol, /* M, N: L2 is nsrow2-by-nscol */ + minus_one, /* ALPHA: -1 */ + Lx + ENTRY_SIZE*(psx + nscol), /* A, LDA: L2 */ + nsrow, + Xx + ENTRY_SIZE*k1, 1, /* X, INCX: x1 */ + one, /* BETA: 1 */ + Ex, 1) ; /* Y, INCY: E */ + +#endif + + /* scatter E back into X */ + for (ii = 0 ; ii < nsrow2 ; ii++) + { + /* Xx [Ls [ps2 + ii]] = Ex [ii] ; */ + ASSIGN (Xx,-,Ls [ps2 + ii], Ex,-,ii) ; + } + } + } + else + { + + for (s = 0 ; s < nsuper ; s++) + { + k1 = Super [s] ; + k2 = Super [s+1] ; + psi = Lpi [s] ; + psend = Lpi [s+1] ; + psx = Lpx [s] ; + nsrow = psend - psi ; + nscol = k2 - k1 ; + nsrow2 = nsrow - nscol ; + ps2 = psi + nscol ; + ASSERT ((size_t) nsrow2 <= L->maxesize) ; + + /* E is nsrow2-by-nrhs, with leading dimension nsrow2. */ + + /* gather X into E */ + for (ii = 0 ; ii < nsrow2 ; ii++) + { + i = Ls [ps2 + ii] ; + for (j = 0 ; j < nrhs ; j++) + { + /* Ex [ii + j*nsrow2] = Xx [i + j*d] ; */ + ASSIGN (Ex,-,ii+j*nsrow2, Xx,-,i+j*d) ; + } + } + +#ifdef REAL + + /* solve L1*x1 */ + BLAS_dtrsm ("L", "L", "N", "N", + nscol, nrhs, /* M, N: x1 is nscol-by-nrhs */ + one, /* ALPHA: 1 */ + Lx + ENTRY_SIZE*psx, nsrow, /* A, LDA: L1 */ + Xx + ENTRY_SIZE*k1, d) ; /* B, LDB: x1 */ + + /* E = E - L2*x1 */ + if (nsrow2 > 0) + { + BLAS_dgemm ("N", "N", + nsrow2, nrhs, nscol, /* M, N, K */ + minus_one, /* ALPHA: -1 */ + Lx + ENTRY_SIZE*(psx + nscol), /* A, LDA: L2 */ + nsrow, + Xx + ENTRY_SIZE*k1, d, /* B, LDB: X1 */ + one, /* BETA: 1 */ + Ex, nsrow2) ; /* C, LDC: E */ + } + +#else + + /* solve L1*x1 */ + BLAS_ztrsm ("L", "L", "N", "N", + nscol, nrhs, /* M, N: x1 is nscol-by-nrhs */ + one, /* ALPHA: 1 */ + Lx + ENTRY_SIZE*psx, nsrow, /* A, LDA: L1 */ + Xx + ENTRY_SIZE*k1, d) ; /* B, LDB: x1 */ + + /* E = E - L2*x1 */ + if (nsrow2 > 0) + { + BLAS_zgemm ("N", "N", + nsrow2, nrhs, nscol, /* M, N, K */ + minus_one, /* ALPHA: -1 */ + Lx + ENTRY_SIZE*(psx + nscol), /* A, LDA: L2 */ + nsrow, + Xx + ENTRY_SIZE*k1, d, /* B, LDB: X1 */ + one, /* BETA: 1 */ + Ex, nsrow2) ; /* C, LDC: E */ + } + +#endif + + /* scatter E back into X */ + for (ii = 0 ; ii < nsrow2 ; ii++) + { + i = Ls [ps2 + ii] ; + for (j = 0 ; j < nrhs ; j++) + { + /* Xx [i + j*d] = Ex [ii + j*nsrow2] ; */ + ASSIGN (Xx,-,i+j*d, Ex,-,ii+j*nsrow2) ; + } + } + } + } +} + + +static void TEMPLATE (cholmod_super_ltsolve) +( + /* ---- input ---- */ + cholmod_factor *L, /* factor to use for the forward solve */ + /* ---- output ---- */ + cholmod_dense *X, /* b on input, solution to Lx=b on output */ + /* ---- workspace ---- */ + cholmod_dense *E, /* workspace of size nrhs*(L->maxesize) */ + /* --------------- */ + cholmod_common *Common +) +{ + double *Lx, *Xx, *Ex ; + double minus_one [2], one [2] ; + Int *Lpi, *Lpx, *Ls, *Super ; + Int nsuper, k1, k2, psi, psend, psx, nsrow, nscol, ii, s, + nsrow2, n, ps2, j, i, d, nrhs ; + + /* ---------------------------------------------------------------------- */ + /* get inputs */ + /* ---------------------------------------------------------------------- */ + + nrhs = X->ncol ; + Ex = E->x ; + Xx = X->x ; + n = L->n ; + d = X->d ; + + nsuper = L->nsuper ; + Lpi = L->pi ; + Lpx = L->px ; + Ls = L->s ; + Super = L->super ; + Lx = L->x ; + minus_one [0] = -1.0 ; + minus_one [1] = 0 ; + one [0] = 1.0 ; + one [1] = 0 ; + + /* ---------------------------------------------------------------------- */ + /* solve L'x=b */ + /* ---------------------------------------------------------------------- */ + + if (nrhs == 1) + { + + for (s = nsuper-1 ; s >= 0 ; s--) + { + k1 = Super [s] ; + k2 = Super [s+1] ; + psi = Lpi [s] ; + psend = Lpi [s+1] ; + psx = Lpx [s] ; + nsrow = psend - psi ; + nscol = k2 - k1 ; + nsrow2 = nsrow - nscol ; + ps2 = psi + nscol ; + ASSERT ((size_t) nsrow2 <= L->maxesize) ; + + /* L1 is nscol-by-nscol, lower triangular with non-unit diagonal. + * L2 is nsrow2-by-nscol. L1 and L2 have leading dimension of + * nsrow. x1 is nscol-by-nsrow, with leading dimension n. + * E is nsrow2-by-1, with leading dimension nsrow2. + */ + + /* gather X into E */ + for (ii = 0 ; ii < nsrow2 ; ii++) + { + /* Ex [ii] = Xx [Ls [ps2 + ii]] ; */ + ASSIGN (Ex,-,ii, Xx,-,Ls [ps2 + ii]) ; + } + +#ifdef REAL + + /* x1 = x1 - L2'*E */ + BLAS_dgemv ("C", + nsrow2, nscol, /* M, N: L2 is nsrow2-by-nscol */ + minus_one, /* ALPHA: -1 */ + Lx + ENTRY_SIZE*(psx + nscol), /* A, LDA: L2 */ + nsrow, + Ex, 1, /* X, INCX: Ex */ + one, /* BETA: 1 */ + Xx + ENTRY_SIZE*k1, 1) ; /* Y, INCY: x1 */ + + /* solve L1'*x1 */ + BLAS_dtrsv ("L", "C", "N", + nscol, /* N: L1 is nscol-by-nscol */ + Lx + ENTRY_SIZE*psx, nsrow, /* A, LDA: L1 */ + Xx + ENTRY_SIZE*k1, 1) ; /* X, INCX: x1 */ + +#else + + /* x1 = x1 - L2'*E */ + BLAS_zgemv ("C", + nsrow2, nscol, /* M, N: L2 is nsrow2-by-nscol */ + minus_one, /* ALPHA: -1 */ + Lx + ENTRY_SIZE*(psx + nscol), /* A, LDA: L2 */ + nsrow, + Ex, 1, /* X, INCX: Ex */ + one, /* BETA: 1 */ + Xx + ENTRY_SIZE*k1, 1) ; /* Y, INCY: x1 */ + + /* solve L1'*x1 */ + BLAS_ztrsv ("L", "C", "N", + nscol, /* N: L1 is nscol-by-nscol */ + Lx + ENTRY_SIZE*psx, nsrow, /* A, LDA: L1 */ + Xx + ENTRY_SIZE*k1, 1) ; /* X, INCX: x1 */ + +#endif + + } + } + else + { + + for (s = nsuper-1 ; s >= 0 ; s--) + { + k1 = Super [s] ; + k2 = Super [s+1] ; + psi = Lpi [s] ; + psend = Lpi [s+1] ; + psx = Lpx [s] ; + nsrow = psend - psi ; + nscol = k2 - k1 ; + nsrow2 = nsrow - nscol ; + ps2 = psi + nscol ; + ASSERT ((size_t) nsrow2 <= L->maxesize) ; + + /* E is nsrow2-by-nrhs, with leading dimension nsrow2. */ + + /* gather X into E */ + for (ii = 0 ; ii < nsrow2 ; ii++) + { + i = Ls [ps2 + ii] ; + for (j = 0 ; j < nrhs ; j++) + { + /* Ex [ii + j*nsrow2] = Xx [i + j*d] ; */ + ASSIGN (Ex,-,ii+j*nsrow2, Xx,-,i+j*d) ; + } + } + +#ifdef REAL + + /* x1 = x1 - L2'*E */ + if (nsrow2 > 0) + { + BLAS_dgemm ("C", "N", + nscol, nrhs, nsrow2, /* M, N, K */ + minus_one, /* ALPHA: -1 */ + Lx + ENTRY_SIZE*(psx + nscol), /* A, LDA: L2 */ + nsrow, + Ex, nsrow2, /* B, LDB: E */ + one, /* BETA: 1 */ + Xx + ENTRY_SIZE*k1, d) ; /* C, LDC: x1 */ + } + + /* solve L1'*x1 */ + BLAS_dtrsm ("L", "L", "C", "N", + nscol, nrhs, /* M, N: x1 is nscol-by-nrhs */ + one, /* ALPHA: 1 */ + Lx + ENTRY_SIZE*psx, nsrow, /* A, LDA: L1 */ + Xx + ENTRY_SIZE*k1, d) ; /* B, LDB: x1 */ + +#else + + /* x1 = x1 - L2'*E */ + if (nsrow2 > 0) + { + BLAS_zgemm ("C", "N", + nscol, nrhs, nsrow2, /* M, N, K */ + minus_one, /* ALPHA: -1 */ + Lx + ENTRY_SIZE*(psx + nscol), /* A, LDA: L2 */ + nsrow, + Ex, nsrow2, /* B, LDB: E */ + one, /* BETA: 1 */ + Xx + ENTRY_SIZE*k1, d) ; /* C, LDC: x1 */ + } + + /* solve L1'*x1 */ + BLAS_ztrsm ("L", "L", "C", "N", + nscol, nrhs, /* M, N: x1 is nscol-by-nrhs */ + one, /* ALPHA: 1 */ + Lx + ENTRY_SIZE*psx, nsrow, /* A, LDA: L1 */ + Xx + ENTRY_SIZE*k1, d) ; /* B, LDB: x1 */ + +#endif + + } + } +} + +#undef PATTERN +#undef REAL +#undef COMPLEX +#undef ZOMPLEX diff --git a/src/COLAMD/Include/colamd.h b/src/COLAMD/Include/colamd.h new file mode 100644 index 0000000..59e6c6b --- /dev/null +++ b/src/COLAMD/Include/colamd.h @@ -0,0 +1,251 @@ +/* ========================================================================== */ +/* === colamd/symamd prototypes and definitions ============================= */ +/* ========================================================================== */ + +/* COLAMD / SYMAMD include file + + You must include this file (colamd.h) in any routine that uses colamd, + symamd, or the related macros and definitions. + + Authors: + + The authors of the code itself are Stefan I. Larimore and Timothy A. + Davis (DrTimothyAldenDavis@gmail.com). The algorithm was + developed in collaboration with John Gilbert, Xerox PARC, and Esmond + Ng, Oak Ridge National Laboratory. + + Acknowledgements: + + This work was supported by the National Science Foundation, under + grants DMS-9504974 and DMS-9803599. + + Notice: + + Copyright (c) 1998-2007, Timothy A. Davis, All Rights Reserved. + + THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY + EXPRESSED OR IMPLIED. ANY USE IS AT YOUR OWN RISK. + + Permission is hereby granted to use, copy, modify, and/or distribute + this program, provided that the Copyright, this License, and the + Availability of the original version is retained on all copies and made + accessible to the end-user of any code or package that includes COLAMD + or any modified version of COLAMD. + + Availability: + + The colamd/symamd library is available at http://www.suitesparse.com + This file is required by the colamd.c, colamdmex.c, and symamdmex.c + files, and by any C code that calls the routines whose prototypes are + listed below, or that uses the colamd/symamd definitions listed below. + +*/ + +#ifndef COLAMD_H +#define COLAMD_H + +/* make it easy for C++ programs to include COLAMD */ +#ifdef __cplusplus +extern "C" { +#endif + +/* ========================================================================== */ +/* === Include files ======================================================== */ +/* ========================================================================== */ + +#include + +/* ========================================================================== */ +/* === COLAMD version ======================================================= */ +/* ========================================================================== */ + +/* COLAMD Version 2.4 and later will include the following definitions. + * As an example, to test if the version you are using is 2.4 or later: + * + * #ifdef COLAMD_VERSION + * if (COLAMD_VERSION >= COLAMD_VERSION_CODE (2,4)) ... + * #endif + * + * This also works during compile-time: + * + * #if defined(COLAMD_VERSION) && (COLAMD_VERSION >= COLAMD_VERSION_CODE (2,4)) + * printf ("This is version 2.4 or later\n") ; + * #else + * printf ("This is an early version\n") ; + * #endif + * + * Versions 2.3 and earlier of COLAMD do not include a #define'd version number. + */ + +#define COLAMD_DATE "Jun 1, 2012" +#define COLAMD_VERSION_CODE(main,sub) ((main) * 1000 + (sub)) +#define COLAMD_MAIN_VERSION 2 +#define COLAMD_SUB_VERSION 8 +#define COLAMD_SUBSUB_VERSION 0 +#define COLAMD_VERSION \ + COLAMD_VERSION_CODE(COLAMD_MAIN_VERSION,COLAMD_SUB_VERSION) + +/* ========================================================================== */ +/* === Knob and statistics definitions ====================================== */ +/* ========================================================================== */ + +/* size of the knobs [ ] array. Only knobs [0..1] are currently used. */ +#define COLAMD_KNOBS 20 + +/* number of output statistics. Only stats [0..6] are currently used. */ +#define COLAMD_STATS 20 + +/* knobs [0] and stats [0]: dense row knob and output statistic. */ +#define COLAMD_DENSE_ROW 0 + +/* knobs [1] and stats [1]: dense column knob and output statistic. */ +#define COLAMD_DENSE_COL 1 + +/* knobs [2]: aggressive absorption */ +#define COLAMD_AGGRESSIVE 2 + +/* stats [2]: memory defragmentation count output statistic */ +#define COLAMD_DEFRAG_COUNT 2 + +/* stats [3]: colamd status: zero OK, > 0 warning or notice, < 0 error */ +#define COLAMD_STATUS 3 + +/* stats [4..6]: error info, or info on jumbled columns */ +#define COLAMD_INFO1 4 +#define COLAMD_INFO2 5 +#define COLAMD_INFO3 6 + +/* error codes returned in stats [3]: */ +#define COLAMD_OK (0) +#define COLAMD_OK_BUT_JUMBLED (1) +#define COLAMD_ERROR_A_not_present (-1) +#define COLAMD_ERROR_p_not_present (-2) +#define COLAMD_ERROR_nrow_negative (-3) +#define COLAMD_ERROR_ncol_negative (-4) +#define COLAMD_ERROR_nnz_negative (-5) +#define COLAMD_ERROR_p0_nonzero (-6) +#define COLAMD_ERROR_A_too_small (-7) +#define COLAMD_ERROR_col_length_negative (-8) +#define COLAMD_ERROR_row_index_out_of_bounds (-9) +#define COLAMD_ERROR_out_of_memory (-10) +#define COLAMD_ERROR_internal_error (-999) + + +/* ========================================================================== */ +/* === Prototypes of user-callable routines ================================= */ +/* ========================================================================== */ + +#include "SuiteSparse_config.h" + +size_t colamd_recommended /* returns recommended value of Alen, */ + /* or 0 if input arguments are erroneous */ +( + int nnz, /* nonzeros in A */ + int n_row, /* number of rows in A */ + int n_col /* number of columns in A */ +) ; + +size_t colamd_l_recommended /* returns recommended value of Alen, */ + /* or 0 if input arguments are erroneous */ +( + SuiteSparse_long nnz, /* nonzeros in A */ + SuiteSparse_long n_row, /* number of rows in A */ + SuiteSparse_long n_col /* number of columns in A */ +) ; + +void colamd_set_defaults /* sets default parameters */ +( /* knobs argument is modified on output */ + double knobs [COLAMD_KNOBS] /* parameter settings for colamd */ +) ; + +void colamd_l_set_defaults /* sets default parameters */ +( /* knobs argument is modified on output */ + double knobs [COLAMD_KNOBS] /* parameter settings for colamd */ +) ; + +int colamd /* returns (1) if successful, (0) otherwise*/ +( /* A and p arguments are modified on output */ + int n_row, /* number of rows in A */ + int n_col, /* number of columns in A */ + int Alen, /* size of the array A */ + int A [], /* row indices of A, of size Alen */ + int p [], /* column pointers of A, of size n_col+1 */ + double knobs [COLAMD_KNOBS],/* parameter settings for colamd */ + int stats [COLAMD_STATS] /* colamd output statistics and error codes */ +) ; + +SuiteSparse_long colamd_l /* returns (1) if successful, (0) otherwise*/ +( /* A and p arguments are modified on output */ + SuiteSparse_long n_row, /* number of rows in A */ + SuiteSparse_long n_col, /* number of columns in A */ + SuiteSparse_long Alen, /* size of the array A */ + SuiteSparse_long A [], /* row indices of A, of size Alen */ + SuiteSparse_long p [], /* column pointers of A, of size n_col+1 */ + double knobs [COLAMD_KNOBS],/* parameter settings for colamd */ + SuiteSparse_long stats [COLAMD_STATS] /* colamd output statistics + * and error codes */ +) ; + +int symamd /* return (1) if OK, (0) otherwise */ +( + int n, /* number of rows and columns of A */ + int A [], /* row indices of A */ + int p [], /* column pointers of A */ + int perm [], /* output permutation, size n_col+1 */ + double knobs [COLAMD_KNOBS], /* parameters (uses defaults if NULL) */ + int stats [COLAMD_STATS], /* output statistics and error codes */ + void * (*allocate) (size_t, size_t), + /* pointer to calloc (ANSI C) or */ + /* mxCalloc (for MATLAB mexFunction) */ + void (*release) (void *) + /* pointer to free (ANSI C) or */ + /* mxFree (for MATLAB mexFunction) */ +) ; + +SuiteSparse_long symamd_l /* return (1) if OK, (0) otherwise */ +( + SuiteSparse_long n, /* number of rows and columns of A */ + SuiteSparse_long A [], /* row indices of A */ + SuiteSparse_long p [], /* column pointers of A */ + SuiteSparse_long perm [], /* output permutation, size n_col+1 */ + double knobs [COLAMD_KNOBS], /* parameters (uses defaults if NULL) */ + SuiteSparse_long stats [COLAMD_STATS], /* output stats and error codes */ + void * (*allocate) (size_t, size_t), + /* pointer to calloc (ANSI C) or */ + /* mxCalloc (for MATLAB mexFunction) */ + void (*release) (void *) + /* pointer to free (ANSI C) or */ + /* mxFree (for MATLAB mexFunction) */ +) ; + +void colamd_report +( + int stats [COLAMD_STATS] +) ; + +void colamd_l_report +( + SuiteSparse_long stats [COLAMD_STATS] +) ; + +void symamd_report +( + int stats [COLAMD_STATS] +) ; + +void symamd_l_report +( + SuiteSparse_long stats [COLAMD_STATS] +) ; + +#ifndef EXTERN +#define EXTERN extern +#endif + +EXTERN int (*colamd_printf) (const char *, ...) ; + +#ifdef __cplusplus +} +#endif + +#endif /* COLAMD_H */ diff --git a/src/COLAMD/Makefile b/src/COLAMD/Makefile new file mode 100644 index 0000000..622f793 --- /dev/null +++ b/src/COLAMD/Makefile @@ -0,0 +1,56 @@ +#------------------------------------------------------------------------------ +# COLAMD Makefile +#------------------------------------------------------------------------------ + +VERSION = 2.8.0 + +default: all + +include ../SuiteSparse_config/SuiteSparse_config.mk + +demos: all + +# Compile all C code +all: + ( cd Lib ; $(MAKE) ) + ( cd Demo ; $(MAKE) ) + +# compile just the C-callable libraries (not Demos) +library: + ( cd Lib ; $(MAKE) ) + +# remove object files, but keep the compiled programs and library archives +clean: + ( cd Lib ; $(MAKE) clean ) + ( cd Demo ; $(MAKE) clean ) + ( cd MATLAB ; $(RM) $(CLEAN) ) + +# clean, and then remove compiled programs and library archives +purge: + ( cd Lib ; $(MAKE) purge ) + ( cd Demo ; $(MAKE) purge ) + ( cd MATLAB ; $(RM) $(CLEAN) ; $(RM) *.mex* ) + +distclean: purge + +# get ready for distribution +dist: purge + ( cd Demo ; $(MAKE) dist ) + +ccode: library + +lib: library + +# install COLAMD +install: + $(CP) Lib/libcolamd.a $(INSTALL_LIB)/libcolamd.$(VERSION).a + ( cd $(INSTALL_LIB) ; ln -sf libcolamd.$(VERSION).a libcolamd.a ) + $(CP) Include/colamd.h $(INSTALL_INCLUDE) + chmod 644 $(INSTALL_LIB)/libcolamd*.a + chmod 644 $(INSTALL_INCLUDE)/colamd.h + +# uninstall COLAMD +uninstall: + $(RM) $(INSTALL_LIB)/libcolamd*.a + $(RM) $(INSTALL_INCLUDE)/colamd.h + diff --git a/src/COLAMD/README.txt b/src/COLAMD/README.txt new file mode 100644 index 0000000..6c5edf0 --- /dev/null +++ b/src/COLAMD/README.txt @@ -0,0 +1,118 @@ +COLAMD, Copyright 1998-2012, Timothy A. Davis. http://www.suitesparse.com +------------------------------------------------------------------------------- + +The COLAMD column approximate minimum degree ordering algorithm computes +a permutation vector P such that the LU factorization of A (:,P) +tends to be sparser than that of A. The Cholesky factorization of +(A (:,P))'*(A (:,P)) will also tend to be sparser than that of A'*A. +SYMAMD is a symmetric minimum degree ordering method based on COLAMD, +available as a MATLAB-callable function. It constructs a matrix M such +that M'*M has the same pattern as A, and then uses COLAMD to compute a column +ordering of M. Colamd and symamd tend to be faster and generate better +orderings than their MATLAB counterparts, colmmd and symmmd. + +To compile and test the colamd m-files and mexFunctions, just unpack the +COLAMD/ directory from the COLAMD.tar.gz file, and run MATLAB from +within that directory. Next, type colamd_test to compile and test colamd +and symamd. This will work on any computer with MATLAB (Unix, PC, or Mac). +Alternatively, type "make" (in Unix) to compile and run a simple example C +code, without using MATLAB. + +To compile and install the colamd m-files and mexFunctions, just cd to +COLAMD/MATLAB and type colamd_install in the MATLAB command window. +A short demo will run. Optionally, type colamd_test to run an extensive tests. +Type "make" in Unix in the COLAMD directory to compile the C-callable +library and to run a short demo. + +Colamd is a built-in routine in MATLAB, available from The +Mathworks, Inc. Under most cases, the compiled COLAMD from Versions 2.0 to the +current version do not differ. Colamd Versions 2.2 and 2.3 differ only in their +mexFunction interaces to MATLAB. v2.4 fixes a bug in the symamd routine in +v2.3. The bug (in v2.3 and earlier) has no effect on the MATLAB symamd +mexFunction. v2.5 adds additional checks for integer overflow, so that +the "int" version can be safely used with 64-bit pointers. Refer to the +ChangeLog for more details. + +To use colamd and symamd within an application written in C, all you need are +colamd.c, colamd_global.c, and colamd.h, which are the C-callable +colamd/symamd codes. See colamd.c for more information on how to call +colamd from a C program. + +Requires SuiteSparse_config, in the ../SuiteSparse_config directory relative to +this directory. + +See the colamd.c file or http://www.suitesparse.com for the license to COLAMD. + +Related papers: + + T. A. Davis, J. R. Gilbert, S. Larimore, E. Ng, An approximate column + minimum degree ordering algorithm, ACM Transactions on Mathematical + Software, vol. 30, no. 3., pp. 353-376, 2004. + + T. A. Davis, J. R. Gilbert, S. Larimore, E. Ng, Algorithm 836: COLAMD, + an approximate column minimum degree ordering algorithm, ACM + Transactions on Mathematical Software, vol. 30, no. 3., pp. 377-380, + 2004. + + "An approximate minimum degree column ordering algorithm", + S. I. Larimore, MS Thesis, Dept. of Computer and Information + Science and Engineering, University of Florida, Gainesville, FL, + 1998. CISE Tech Report TR-98-016. + + Approximate Deficiency for Ordering the Columns of a Matrix, + J. L. Kern, Senior Thesis, Dept. of Computer and Information + Science and Engineering, University of Florida, Gainesville, FL, + 1999. + + +Authors: Stefan I. Larimore and Timothy A. Davis, +in collaboration with John Gilbert, Xerox PARC (now at UC Santa Barbara), +and Esmong Ng, Lawrence Berkeley National Laboratory (much of this work +he did while at Oak Ridge National Laboratory). + +COLAMD files: + + Demo simple demo + Doc additional documentation (see colamd.c for more) + Include include file + Lib compiled C-callable library + Makefile primary Unix Makefile + MATLAB MATLAB functions + README.txt this file + Source C source code + + ./Demo: + colamd_example.c simple example + colamd_example.out output of colamd_example.c + colamd_l_example.c simple example, long integers + colamd_l_example.out output of colamd_l_example.c + Makefile Makefile for C demos + + ./Doc: + ChangeLog change log + lesser.txt license + + ./Include: + colamd.h include file + + ./Lib: + Makefile Makefile for C-callable library + + ./MATLAB: + colamd2.m MATLAB interface for colamd2 + colamd_demo.m simple demo + colamd_install.m compile and install colamd2 and symamd2 + colamd_make.m compile colamd2 and symamd2 + colamdmex.ca MATLAB mexFunction for colamd2 + colamd_test.m extensive test + colamdtestmex.c test function for colamd + Contents.m contents of the MATLAB directory + luflops.m test code + Makefile Makefile for MATLAB functions + symamd2.m MATLAB interface for symamd2 + symamdmex.c MATLAB mexFunction for symamd2 + symamdtestmex.c test function for symamd + + ./Source: + colamd.c primary source code + colamd_global.c globally defined function pointers (malloc, free, ...) diff --git a/src/COLAMD/Source/colamd.c b/src/COLAMD/Source/colamd.c new file mode 100644 index 0000000..a20e9e1 --- /dev/null +++ b/src/COLAMD/Source/colamd.c @@ -0,0 +1,3604 @@ +/* ========================================================================== */ +/* === colamd/symamd - a sparse matrix column ordering algorithm ============ */ +/* ========================================================================== */ + +/* COLAMD / SYMAMD + + colamd: an approximate minimum degree column ordering algorithm, + for LU factorization of symmetric or unsymmetric matrices, + QR factorization, least squares, interior point methods for + linear programming problems, and other related problems. + + symamd: an approximate minimum degree ordering algorithm for Cholesky + factorization of symmetric matrices. + + Purpose: + + Colamd computes a permutation Q such that the Cholesky factorization of + (AQ)'(AQ) has less fill-in and requires fewer floating point operations + than A'A. This also provides a good ordering for sparse partial + pivoting methods, P(AQ) = LU, where Q is computed prior to numerical + factorization, and P is computed during numerical factorization via + conventional partial pivoting with row interchanges. Colamd is the + column ordering method used in SuperLU, part of the ScaLAPACK library. + It is also available as built-in function in MATLAB Version 6, + available from MathWorks, Inc. (http://www.mathworks.com). This + routine can be used in place of colmmd in MATLAB. + + Symamd computes a permutation P of a symmetric matrix A such that the + Cholesky factorization of PAP' has less fill-in and requires fewer + floating point operations than A. Symamd constructs a matrix M such + that M'M has the same nonzero pattern of A, and then orders the columns + of M using colmmd. The column ordering of M is then returned as the + row and column ordering P of A. + + Authors: + + The authors of the code itself are Stefan I. Larimore and Timothy A. + Davis (DrTimothyAldenDavis@gmail.com). The algorithm was + developed in collaboration with John Gilbert, Xerox PARC, and Esmond + Ng, Oak Ridge National Laboratory. + + Acknowledgements: + + This work was supported by the National Science Foundation, under + grants DMS-9504974 and DMS-9803599. + + Copyright and License: + + Copyright (c) 1998-2007, Timothy A. Davis, All Rights Reserved. + COLAMD is also available under alternate licenses, contact T. Davis + for details. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 + USA + + Permission is hereby granted to use or copy this program under the + terms of the GNU LGPL, provided that the Copyright, this License, + and the Availability of the original version is retained on all copies. + User documentation of any code that uses this code or any modified + version of this code must cite the Copyright, this License, the + Availability note, and "Used by permission." Permission to modify + the code and to distribute modified code is granted, provided the + Copyright, this License, and the Availability note are retained, + and a notice that the code was modified is included. + + Availability: + + The colamd/symamd library is available at http://www.suitesparse.com + Appears as ACM Algorithm 836. + + See the ChangeLog file for changes since Version 1.0. + + References: + + T. A. Davis, J. R. Gilbert, S. Larimore, E. Ng, An approximate column + minimum degree ordering algorithm, ACM Transactions on Mathematical + Software, vol. 30, no. 3., pp. 353-376, 2004. + + T. A. Davis, J. R. Gilbert, S. Larimore, E. Ng, Algorithm 836: COLAMD, + an approximate column minimum degree ordering algorithm, ACM + Transactions on Mathematical Software, vol. 30, no. 3., pp. 377-380, + 2004. + +*/ + +/* ========================================================================== */ +/* === Description of user-callable routines ================================ */ +/* ========================================================================== */ + +/* COLAMD includes both int and SuiteSparse_long versions of all its routines. + The description below is for the int version. For SuiteSparse_long, all + int arguments become SuiteSparse_long. SuiteSparse_long is normally + defined as long, except for WIN64. + + ---------------------------------------------------------------------------- + colamd_recommended: + ---------------------------------------------------------------------------- + + C syntax: + + #include "colamd.h" + size_t colamd_recommended (int nnz, int n_row, int n_col) ; + size_t colamd_l_recommended (SuiteSparse_long nnz, + SuiteSparse_long n_row, SuiteSparse_long n_col) ; + + Purpose: + + Returns recommended value of Alen for use by colamd. Returns 0 + if any input argument is negative. The use of this routine + is optional. Not needed for symamd, which dynamically allocates + its own memory. + + Note that in v2.4 and earlier, these routines returned int or long. + They now return a value of type size_t. + + Arguments (all input arguments): + + int nnz ; Number of nonzeros in the matrix A. This must + be the same value as p [n_col] in the call to + colamd - otherwise you will get a wrong value + of the recommended memory to use. + + int n_row ; Number of rows in the matrix A. + + int n_col ; Number of columns in the matrix A. + + ---------------------------------------------------------------------------- + colamd_set_defaults: + ---------------------------------------------------------------------------- + + C syntax: + + #include "colamd.h" + colamd_set_defaults (double knobs [COLAMD_KNOBS]) ; + colamd_l_set_defaults (double knobs [COLAMD_KNOBS]) ; + + Purpose: + + Sets the default parameters. The use of this routine is optional. + + Arguments: + + double knobs [COLAMD_KNOBS] ; Output only. + + NOTE: the meaning of the dense row/col knobs has changed in v2.4 + + knobs [0] and knobs [1] control dense row and col detection: + + Colamd: rows with more than + max (16, knobs [COLAMD_DENSE_ROW] * sqrt (n_col)) + entries are removed prior to ordering. Columns with more than + max (16, knobs [COLAMD_DENSE_COL] * sqrt (MIN (n_row,n_col))) + entries are removed prior to + ordering, and placed last in the output column ordering. + + Symamd: uses only knobs [COLAMD_DENSE_ROW], which is knobs [0]. + Rows and columns with more than + max (16, knobs [COLAMD_DENSE_ROW] * sqrt (n)) + entries are removed prior to ordering, and placed last in the + output ordering. + + COLAMD_DENSE_ROW and COLAMD_DENSE_COL are defined as 0 and 1, + respectively, in colamd.h. Default values of these two knobs + are both 10. Currently, only knobs [0] and knobs [1] are + used, but future versions may use more knobs. If so, they will + be properly set to their defaults by the future version of + colamd_set_defaults, so that the code that calls colamd will + not need to change, assuming that you either use + colamd_set_defaults, or pass a (double *) NULL pointer as the + knobs array to colamd or symamd. + + knobs [2]: aggressive absorption + + knobs [COLAMD_AGGRESSIVE] controls whether or not to do + aggressive absorption during the ordering. Default is TRUE. + + + ---------------------------------------------------------------------------- + colamd: + ---------------------------------------------------------------------------- + + C syntax: + + #include "colamd.h" + int colamd (int n_row, int n_col, int Alen, int *A, int *p, + double knobs [COLAMD_KNOBS], int stats [COLAMD_STATS]) ; + SuiteSparse_long colamd_l (SuiteSparse_long n_row, + SuiteSparse_long n_col, SuiteSparse_long Alen, + SuiteSparse_long *A, SuiteSparse_long *p, double knobs + [COLAMD_KNOBS], SuiteSparse_long stats [COLAMD_STATS]) ; + + Purpose: + + Computes a column ordering (Q) of A such that P(AQ)=LU or + (AQ)'AQ=LL' have less fill-in and require fewer floating point + operations than factorizing the unpermuted matrix A or A'A, + respectively. + + Returns: + + TRUE (1) if successful, FALSE (0) otherwise. + + Arguments: + + int n_row ; Input argument. + + Number of rows in the matrix A. + Restriction: n_row >= 0. + Colamd returns FALSE if n_row is negative. + + int n_col ; Input argument. + + Number of columns in the matrix A. + Restriction: n_col >= 0. + Colamd returns FALSE if n_col is negative. + + int Alen ; Input argument. + + Restriction (see note): + Alen >= 2*nnz + 6*(n_col+1) + 4*(n_row+1) + n_col + Colamd returns FALSE if these conditions are not met. + + Note: this restriction makes an modest assumption regarding + the size of the two typedef's structures in colamd.h. + We do, however, guarantee that + + Alen >= colamd_recommended (nnz, n_row, n_col) + + will be sufficient. Note: the macro version does not check + for integer overflow, and thus is not recommended. Use + the colamd_recommended routine instead. + + int A [Alen] ; Input argument, undefined on output. + + A is an integer array of size Alen. Alen must be at least as + large as the bare minimum value given above, but this is very + low, and can result in excessive run time. For best + performance, we recommend that Alen be greater than or equal to + colamd_recommended (nnz, n_row, n_col), which adds + nnz/5 to the bare minimum value given above. + + On input, the row indices of the entries in column c of the + matrix are held in A [(p [c]) ... (p [c+1]-1)]. The row indices + in a given column c need not be in ascending order, and + duplicate row indices may be be present. However, colamd will + work a little faster if both of these conditions are met + (Colamd puts the matrix into this format, if it finds that the + the conditions are not met). + + The matrix is 0-based. That is, rows are in the range 0 to + n_row-1, and columns are in the range 0 to n_col-1. Colamd + returns FALSE if any row index is out of range. + + The contents of A are modified during ordering, and are + undefined on output. + + int p [n_col+1] ; Both input and output argument. + + p is an integer array of size n_col+1. On input, it holds the + "pointers" for the column form of the matrix A. Column c of + the matrix A is held in A [(p [c]) ... (p [c+1]-1)]. The first + entry, p [0], must be zero, and p [c] <= p [c+1] must hold + for all c in the range 0 to n_col-1. The value p [n_col] is + thus the total number of entries in the pattern of the matrix A. + Colamd returns FALSE if these conditions are not met. + + On output, if colamd returns TRUE, the array p holds the column + permutation (Q, for P(AQ)=LU or (AQ)'(AQ)=LL'), where p [0] is + the first column index in the new ordering, and p [n_col-1] is + the last. That is, p [k] = j means that column j of A is the + kth pivot column, in AQ, where k is in the range 0 to n_col-1 + (p [0] = j means that column j of A is the first column in AQ). + + If colamd returns FALSE, then no permutation is returned, and + p is undefined on output. + + double knobs [COLAMD_KNOBS] ; Input argument. + + See colamd_set_defaults for a description. + + int stats [COLAMD_STATS] ; Output argument. + + Statistics on the ordering, and error status. + See colamd.h for related definitions. + Colamd returns FALSE if stats is not present. + + stats [0]: number of dense or empty rows ignored. + + stats [1]: number of dense or empty columns ignored (and + ordered last in the output permutation p) + Note that a row can become "empty" if it + contains only "dense" and/or "empty" columns, + and similarly a column can become "empty" if it + only contains "dense" and/or "empty" rows. + + stats [2]: number of garbage collections performed. + This can be excessively high if Alen is close + to the minimum required value. + + stats [3]: status code. < 0 is an error code. + > 1 is a warning or notice. + + 0 OK. Each column of the input matrix contained + row indices in increasing order, with no + duplicates. + + 1 OK, but columns of input matrix were jumbled + (unsorted columns or duplicate entries). Colamd + had to do some extra work to sort the matrix + first and remove duplicate entries, but it + still was able to return a valid permutation + (return value of colamd was TRUE). + + stats [4]: highest numbered column that + is unsorted or has duplicate + entries. + stats [5]: last seen duplicate or + unsorted row index. + stats [6]: number of duplicate or + unsorted row indices. + + -1 A is a null pointer + + -2 p is a null pointer + + -3 n_row is negative + + stats [4]: n_row + + -4 n_col is negative + + stats [4]: n_col + + -5 number of nonzeros in matrix is negative + + stats [4]: number of nonzeros, p [n_col] + + -6 p [0] is nonzero + + stats [4]: p [0] + + -7 A is too small + + stats [4]: required size + stats [5]: actual size (Alen) + + -8 a column has a negative number of entries + + stats [4]: column with < 0 entries + stats [5]: number of entries in col + + -9 a row index is out of bounds + + stats [4]: column with bad row index + stats [5]: bad row index + stats [6]: n_row, # of rows of matrx + + -10 (unused; see symamd.c) + + -999 (unused; see symamd.c) + + Future versions may return more statistics in the stats array. + + Example: + + See colamd_example.c for a complete example. + + To order the columns of a 5-by-4 matrix with 11 nonzero entries in + the following nonzero pattern + + x 0 x 0 + x 0 x x + 0 x x 0 + 0 0 x x + x x 0 0 + + with default knobs and no output statistics, do the following: + + #include "colamd.h" + #define ALEN 100 + int A [ALEN] = {0, 1, 4, 2, 4, 0, 1, 2, 3, 1, 3} ; + int p [ ] = {0, 3, 5, 9, 11} ; + int stats [COLAMD_STATS] ; + colamd (5, 4, ALEN, A, p, (double *) NULL, stats) ; + + The permutation is returned in the array p, and A is destroyed. + + ---------------------------------------------------------------------------- + symamd: + ---------------------------------------------------------------------------- + + C syntax: + + #include "colamd.h" + int symamd (int n, int *A, int *p, int *perm, + double knobs [COLAMD_KNOBS], int stats [COLAMD_STATS], + void (*allocate) (size_t, size_t), void (*release) (void *)) ; + SuiteSparse_long symamd_l (SuiteSparse_long n, SuiteSparse_long *A, + SuiteSparse_long *p, SuiteSparse_long *perm, double knobs + [COLAMD_KNOBS], SuiteSparse_long stats [COLAMD_STATS], void + (*allocate) (size_t, size_t), void (*release) (void *)) ; + + Purpose: + + The symamd routine computes an ordering P of a symmetric sparse + matrix A such that the Cholesky factorization PAP' = LL' remains + sparse. It is based on a column ordering of a matrix M constructed + so that the nonzero pattern of M'M is the same as A. The matrix A + is assumed to be symmetric; only the strictly lower triangular part + is accessed. You must pass your selected memory allocator (usually + calloc/free or mxCalloc/mxFree) to symamd, for it to allocate + memory for the temporary matrix M. + + Returns: + + TRUE (1) if successful, FALSE (0) otherwise. + + Arguments: + + int n ; Input argument. + + Number of rows and columns in the symmetrix matrix A. + Restriction: n >= 0. + Symamd returns FALSE if n is negative. + + int A [nnz] ; Input argument. + + A is an integer array of size nnz, where nnz = p [n]. + + The row indices of the entries in column c of the matrix are + held in A [(p [c]) ... (p [c+1]-1)]. The row indices in a + given column c need not be in ascending order, and duplicate + row indices may be present. However, symamd will run faster + if the columns are in sorted order with no duplicate entries. + + The matrix is 0-based. That is, rows are in the range 0 to + n-1, and columns are in the range 0 to n-1. Symamd + returns FALSE if any row index is out of range. + + The contents of A are not modified. + + int p [n+1] ; Input argument. + + p is an integer array of size n+1. On input, it holds the + "pointers" for the column form of the matrix A. Column c of + the matrix A is held in A [(p [c]) ... (p [c+1]-1)]. The first + entry, p [0], must be zero, and p [c] <= p [c+1] must hold + for all c in the range 0 to n-1. The value p [n] is + thus the total number of entries in the pattern of the matrix A. + Symamd returns FALSE if these conditions are not met. + + The contents of p are not modified. + + int perm [n+1] ; Output argument. + + On output, if symamd returns TRUE, the array perm holds the + permutation P, where perm [0] is the first index in the new + ordering, and perm [n-1] is the last. That is, perm [k] = j + means that row and column j of A is the kth column in PAP', + where k is in the range 0 to n-1 (perm [0] = j means + that row and column j of A are the first row and column in + PAP'). The array is used as a workspace during the ordering, + which is why it must be of length n+1, not just n. + + double knobs [COLAMD_KNOBS] ; Input argument. + + See colamd_set_defaults for a description. + + int stats [COLAMD_STATS] ; Output argument. + + Statistics on the ordering, and error status. + See colamd.h for related definitions. + Symamd returns FALSE if stats is not present. + + stats [0]: number of dense or empty row and columns ignored + (and ordered last in the output permutation + perm). Note that a row/column can become + "empty" if it contains only "dense" and/or + "empty" columns/rows. + + stats [1]: (same as stats [0]) + + stats [2]: number of garbage collections performed. + + stats [3]: status code. < 0 is an error code. + > 1 is a warning or notice. + + 0 OK. Each column of the input matrix contained + row indices in increasing order, with no + duplicates. + + 1 OK, but columns of input matrix were jumbled + (unsorted columns or duplicate entries). Symamd + had to do some extra work to sort the matrix + first and remove duplicate entries, but it + still was able to return a valid permutation + (return value of symamd was TRUE). + + stats [4]: highest numbered column that + is unsorted or has duplicate + entries. + stats [5]: last seen duplicate or + unsorted row index. + stats [6]: number of duplicate or + unsorted row indices. + + -1 A is a null pointer + + -2 p is a null pointer + + -3 (unused, see colamd.c) + + -4 n is negative + + stats [4]: n + + -5 number of nonzeros in matrix is negative + + stats [4]: # of nonzeros (p [n]). + + -6 p [0] is nonzero + + stats [4]: p [0] + + -7 (unused) + + -8 a column has a negative number of entries + + stats [4]: column with < 0 entries + stats [5]: number of entries in col + + -9 a row index is out of bounds + + stats [4]: column with bad row index + stats [5]: bad row index + stats [6]: n_row, # of rows of matrx + + -10 out of memory (unable to allocate temporary + workspace for M or count arrays using the + "allocate" routine passed into symamd). + + Future versions may return more statistics in the stats array. + + void * (*allocate) (size_t, size_t) + + A pointer to a function providing memory allocation. The + allocated memory must be returned initialized to zero. For a + C application, this argument should normally be a pointer to + calloc. For a MATLAB mexFunction, the routine mxCalloc is + passed instead. + + void (*release) (size_t, size_t) + + A pointer to a function that frees memory allocated by the + memory allocation routine above. For a C application, this + argument should normally be a pointer to free. For a MATLAB + mexFunction, the routine mxFree is passed instead. + + + ---------------------------------------------------------------------------- + colamd_report: + ---------------------------------------------------------------------------- + + C syntax: + + #include "colamd.h" + colamd_report (int stats [COLAMD_STATS]) ; + colamd_l_report (SuiteSparse_long stats [COLAMD_STATS]) ; + + Purpose: + + Prints the error status and statistics recorded in the stats + array on the standard error output (for a standard C routine) + or on the MATLAB output (for a mexFunction). + + Arguments: + + int stats [COLAMD_STATS] ; Input only. Statistics from colamd. + + + ---------------------------------------------------------------------------- + symamd_report: + ---------------------------------------------------------------------------- + + C syntax: + + #include "colamd.h" + symamd_report (int stats [COLAMD_STATS]) ; + symamd_l_report (SuiteSparse_long stats [COLAMD_STATS]) ; + + Purpose: + + Prints the error status and statistics recorded in the stats + array on the standard error output (for a standard C routine) + or on the MATLAB output (for a mexFunction). + + Arguments: + + int stats [COLAMD_STATS] ; Input only. Statistics from symamd. + + +*/ + +/* ========================================================================== */ +/* === Scaffolding code definitions ======================================== */ +/* ========================================================================== */ + +/* Ensure that debugging is turned off: */ +#ifndef NDEBUG +#define NDEBUG +#endif + +/* turn on debugging by uncommenting the following line + #undef NDEBUG +*/ + +/* + Our "scaffolding code" philosophy: In our opinion, well-written library + code should keep its "debugging" code, and just normally have it turned off + by the compiler so as not to interfere with performance. This serves + several purposes: + + (1) assertions act as comments to the reader, telling you what the code + expects at that point. All assertions will always be true (unless + there really is a bug, of course). + + (2) leaving in the scaffolding code assists anyone who would like to modify + the code, or understand the algorithm (by reading the debugging output, + one can get a glimpse into what the code is doing). + + (3) (gasp!) for actually finding bugs. This code has been heavily tested + and "should" be fully functional and bug-free ... but you never know... + + The code will become outrageously slow when debugging is + enabled. To control the level of debugging output, set an environment + variable D to 0 (little), 1 (some), 2, 3, or 4 (lots). When debugging, + you should see the following message on the standard output: + + colamd: debug version, D = 1 (THIS WILL BE SLOW!) + + or a similar message for symamd. If you don't, then debugging has not + been enabled. + +*/ + +/* ========================================================================== */ +/* === Include files ======================================================== */ +/* ========================================================================== */ + +#include "colamd.h" +#include +#include + +#ifdef MATLAB_MEX_FILE +#include "mex.h" +#include "matrix.h" +#endif /* MATLAB_MEX_FILE */ + +#if !defined (NPRINT) || !defined (NDEBUG) +#include +#endif + +#ifndef NULL +#define NULL ((void *) 0) +#endif + +/* ========================================================================== */ +/* === int or SuiteSparse_long ============================================== */ +/* ========================================================================== */ + +#ifdef DLONG + +#define Int SuiteSparse_long +#define ID SuiteSparse_long_id +#define Int_MAX SuiteSparse_long_max + +#define COLAMD_recommended colamd_l_recommended +#define COLAMD_set_defaults colamd_l_set_defaults +#define COLAMD_MAIN colamd_l +#define SYMAMD_MAIN symamd_l +#define COLAMD_report colamd_l_report +#define SYMAMD_report symamd_l_report + +#else + +#define Int int +#define ID "%d" +#define Int_MAX INT_MAX + +#define COLAMD_recommended colamd_recommended +#define COLAMD_set_defaults colamd_set_defaults +#define COLAMD_MAIN colamd +#define SYMAMD_MAIN symamd +#define COLAMD_report colamd_report +#define SYMAMD_report symamd_report + +#endif + +/* ========================================================================== */ +/* === Row and Column structures ============================================ */ +/* ========================================================================== */ + +/* User code that makes use of the colamd/symamd routines need not directly */ +/* reference these structures. They are used only for colamd_recommended. */ + +typedef struct Colamd_Col_struct +{ + Int start ; /* index for A of first row in this column, or DEAD */ + /* if column is dead */ + Int length ; /* number of rows in this column */ + union + { + Int thickness ; /* number of original columns represented by this */ + /* col, if the column is alive */ + Int parent ; /* parent in parent tree super-column structure, if */ + /* the column is dead */ + } shared1 ; + union + { + Int score ; /* the score used to maintain heap, if col is alive */ + Int order ; /* pivot ordering of this column, if col is dead */ + } shared2 ; + union + { + Int headhash ; /* head of a hash bucket, if col is at the head of */ + /* a degree list */ + Int hash ; /* hash value, if col is not in a degree list */ + Int prev ; /* previous column in degree list, if col is in a */ + /* degree list (but not at the head of a degree list) */ + } shared3 ; + union + { + Int degree_next ; /* next column, if col is in a degree list */ + Int hash_next ; /* next column, if col is in a hash list */ + } shared4 ; + +} Colamd_Col ; + +typedef struct Colamd_Row_struct +{ + Int start ; /* index for A of first col in this row */ + Int length ; /* number of principal columns in this row */ + union + { + Int degree ; /* number of principal & non-principal columns in row */ + Int p ; /* used as a row pointer in init_rows_cols () */ + } shared1 ; + union + { + Int mark ; /* for computing set differences and marking dead rows*/ + Int first_column ;/* first column in row (used in garbage collection) */ + } shared2 ; + +} Colamd_Row ; + +/* ========================================================================== */ +/* === Definitions ========================================================== */ +/* ========================================================================== */ + +/* Routines are either PUBLIC (user-callable) or PRIVATE (not user-callable) */ +#define PUBLIC +#define PRIVATE static + +#define DENSE_DEGREE(alpha,n) \ + ((Int) MAX (16.0, (alpha) * sqrt ((double) (n)))) + +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) + +#define ONES_COMPLEMENT(r) (-(r)-1) + +/* -------------------------------------------------------------------------- */ +/* Change for version 2.1: define TRUE and FALSE only if not yet defined */ +/* -------------------------------------------------------------------------- */ + +#ifndef TRUE +#define TRUE (1) +#endif + +#ifndef FALSE +#define FALSE (0) +#endif + +/* -------------------------------------------------------------------------- */ + +#define EMPTY (-1) + +/* Row and column status */ +#define ALIVE (0) +#define DEAD (-1) + +/* Column status */ +#define DEAD_PRINCIPAL (-1) +#define DEAD_NON_PRINCIPAL (-2) + +/* Macros for row and column status update and checking. */ +#define ROW_IS_DEAD(r) ROW_IS_MARKED_DEAD (Row[r].shared2.mark) +#define ROW_IS_MARKED_DEAD(row_mark) (row_mark < ALIVE) +#define ROW_IS_ALIVE(r) (Row [r].shared2.mark >= ALIVE) +#define COL_IS_DEAD(c) (Col [c].start < ALIVE) +#define COL_IS_ALIVE(c) (Col [c].start >= ALIVE) +#define COL_IS_DEAD_PRINCIPAL(c) (Col [c].start == DEAD_PRINCIPAL) +#define KILL_ROW(r) { Row [r].shared2.mark = DEAD ; } +#define KILL_PRINCIPAL_COL(c) { Col [c].start = DEAD_PRINCIPAL ; } +#define KILL_NON_PRINCIPAL_COL(c) { Col [c].start = DEAD_NON_PRINCIPAL ; } + +/* ========================================================================== */ +/* === Colamd reporting mechanism =========================================== */ +/* ========================================================================== */ + +#if defined (MATLAB_MEX_FILE) || defined (MATHWORKS) +/* In MATLAB, matrices are 1-based to the user, but 0-based internally */ +#define INDEX(i) ((i)+1) +#else +/* In C, matrices are 0-based and indices are reported as such in *_report */ +#define INDEX(i) (i) +#endif + +/* All output goes through the PRINTF macro. */ +#define PRINTF(params) { if (colamd_printf != NULL) (void) colamd_printf params ; } + +/* ========================================================================== */ +/* === Prototypes of PRIVATE routines ======================================= */ +/* ========================================================================== */ + +PRIVATE Int init_rows_cols +( + Int n_row, + Int n_col, + Colamd_Row Row [], + Colamd_Col Col [], + Int A [], + Int p [], + Int stats [COLAMD_STATS] +) ; + +PRIVATE void init_scoring +( + Int n_row, + Int n_col, + Colamd_Row Row [], + Colamd_Col Col [], + Int A [], + Int head [], + double knobs [COLAMD_KNOBS], + Int *p_n_row2, + Int *p_n_col2, + Int *p_max_deg +) ; + +PRIVATE Int find_ordering +( + Int n_row, + Int n_col, + Int Alen, + Colamd_Row Row [], + Colamd_Col Col [], + Int A [], + Int head [], + Int n_col2, + Int max_deg, + Int pfree, + Int aggressive +) ; + +PRIVATE void order_children +( + Int n_col, + Colamd_Col Col [], + Int p [] +) ; + +PRIVATE void detect_super_cols +( + +#ifndef NDEBUG + Int n_col, + Colamd_Row Row [], +#endif /* NDEBUG */ + + Colamd_Col Col [], + Int A [], + Int head [], + Int row_start, + Int row_length +) ; + +PRIVATE Int garbage_collection +( + Int n_row, + Int n_col, + Colamd_Row Row [], + Colamd_Col Col [], + Int A [], + Int *pfree +) ; + +PRIVATE Int clear_mark +( + Int tag_mark, + Int max_mark, + Int n_row, + Colamd_Row Row [] +) ; + +PRIVATE void print_report +( + char *method, + Int stats [COLAMD_STATS] +) ; + +/* ========================================================================== */ +/* === Debugging prototypes and definitions ================================= */ +/* ========================================================================== */ + +#ifndef NDEBUG + +#include + +/* colamd_debug is the *ONLY* global variable, and is only */ +/* present when debugging */ + +PRIVATE Int colamd_debug = 0 ; /* debug print level */ + +#define DEBUG0(params) { PRINTF (params) ; } +#define DEBUG1(params) { if (colamd_debug >= 1) PRINTF (params) ; } +#define DEBUG2(params) { if (colamd_debug >= 2) PRINTF (params) ; } +#define DEBUG3(params) { if (colamd_debug >= 3) PRINTF (params) ; } +#define DEBUG4(params) { if (colamd_debug >= 4) PRINTF (params) ; } + +#ifdef MATLAB_MEX_FILE +#define ASSERT(expression) (mxAssert ((expression), "")) +#else +#define ASSERT(expression) (assert (expression)) +#endif /* MATLAB_MEX_FILE */ + +PRIVATE void colamd_get_debug /* gets the debug print level from getenv */ +( + char *method +) ; + +PRIVATE void debug_deg_lists +( + Int n_row, + Int n_col, + Colamd_Row Row [], + Colamd_Col Col [], + Int head [], + Int min_score, + Int should, + Int max_deg +) ; + +PRIVATE void debug_mark +( + Int n_row, + Colamd_Row Row [], + Int tag_mark, + Int max_mark +) ; + +PRIVATE void debug_matrix +( + Int n_row, + Int n_col, + Colamd_Row Row [], + Colamd_Col Col [], + Int A [] +) ; + +PRIVATE void debug_structures +( + Int n_row, + Int n_col, + Colamd_Row Row [], + Colamd_Col Col [], + Int A [], + Int n_col2 +) ; + +#else /* NDEBUG */ + +/* === No debugging ========================================================= */ + +#define DEBUG0(params) ; +#define DEBUG1(params) ; +#define DEBUG2(params) ; +#define DEBUG3(params) ; +#define DEBUG4(params) ; + +#define ASSERT(expression) + +#endif /* NDEBUG */ + +/* ========================================================================== */ +/* === USER-CALLABLE ROUTINES: ============================================== */ +/* ========================================================================== */ + +/* ========================================================================== */ +/* === colamd_recommended =================================================== */ +/* ========================================================================== */ + +/* + The colamd_recommended routine returns the suggested size for Alen. This + value has been determined to provide good balance between the number of + garbage collections and the memory requirements for colamd. If any + argument is negative, or if integer overflow occurs, a 0 is returned as an + error condition. 2*nnz space is required for the row and column + indices of the matrix. COLAMD_C (n_col) + COLAMD_R (n_row) space is + required for the Col and Row arrays, respectively, which are internal to + colamd (roughly 6*n_col + 4*n_row). An additional n_col space is the + minimal amount of "elbow room", and nnz/5 more space is recommended for + run time efficiency. + + Alen is approximately 2.2*nnz + 7*n_col + 4*n_row + 10. + + This function is not needed when using symamd. +*/ + +/* add two values of type size_t, and check for integer overflow */ +static size_t t_add (size_t a, size_t b, int *ok) +{ + (*ok) = (*ok) && ((a + b) >= MAX (a,b)) ; + return ((*ok) ? (a + b) : 0) ; +} + +/* compute a*k where k is a small integer, and check for integer overflow */ +static size_t t_mult (size_t a, size_t k, int *ok) +{ + size_t i, s = 0 ; + for (i = 0 ; i < k ; i++) + { + s = t_add (s, a, ok) ; + } + return (s) ; +} + +/* size of the Col and Row structures */ +#define COLAMD_C(n_col,ok) \ + ((t_mult (t_add (n_col, 1, ok), sizeof (Colamd_Col), ok) / sizeof (Int))) + +#define COLAMD_R(n_row,ok) \ + ((t_mult (t_add (n_row, 1, ok), sizeof (Colamd_Row), ok) / sizeof (Int))) + + +PUBLIC size_t COLAMD_recommended /* returns recommended value of Alen. */ +( + /* === Parameters ======================================================= */ + + Int nnz, /* number of nonzeros in A */ + Int n_row, /* number of rows in A */ + Int n_col /* number of columns in A */ +) +{ + size_t s, c, r ; + int ok = TRUE ; + if (nnz < 0 || n_row < 0 || n_col < 0) + { + return (0) ; + } + s = t_mult (nnz, 2, &ok) ; /* 2*nnz */ + c = COLAMD_C (n_col, &ok) ; /* size of column structures */ + r = COLAMD_R (n_row, &ok) ; /* size of row structures */ + s = t_add (s, c, &ok) ; + s = t_add (s, r, &ok) ; + s = t_add (s, n_col, &ok) ; /* elbow room */ + s = t_add (s, nnz/5, &ok) ; /* elbow room */ + ok = ok && (s < Int_MAX) ; + return (ok ? s : 0) ; +} + + +/* ========================================================================== */ +/* === colamd_set_defaults ================================================== */ +/* ========================================================================== */ + +/* + The colamd_set_defaults routine sets the default values of the user- + controllable parameters for colamd and symamd: + + Colamd: rows with more than max (16, knobs [0] * sqrt (n_col)) + entries are removed prior to ordering. Columns with more than + max (16, knobs [1] * sqrt (MIN (n_row,n_col))) entries are removed + prior to ordering, and placed last in the output column ordering. + + Symamd: Rows and columns with more than max (16, knobs [0] * sqrt (n)) + entries are removed prior to ordering, and placed last in the + output ordering. + + knobs [0] dense row control + + knobs [1] dense column control + + knobs [2] if nonzero, do aggresive absorption + + knobs [3..19] unused, but future versions might use this + +*/ + +PUBLIC void COLAMD_set_defaults +( + /* === Parameters ======================================================= */ + + double knobs [COLAMD_KNOBS] /* knob array */ +) +{ + /* === Local variables ================================================== */ + + Int i ; + + if (!knobs) + { + return ; /* no knobs to initialize */ + } + for (i = 0 ; i < COLAMD_KNOBS ; i++) + { + knobs [i] = 0 ; + } + knobs [COLAMD_DENSE_ROW] = 10 ; + knobs [COLAMD_DENSE_COL] = 10 ; + knobs [COLAMD_AGGRESSIVE] = TRUE ; /* default: do aggressive absorption*/ +} + + +/* ========================================================================== */ +/* === symamd =============================================================== */ +/* ========================================================================== */ + +PUBLIC Int SYMAMD_MAIN /* return TRUE if OK, FALSE otherwise */ +( + /* === Parameters ======================================================= */ + + Int n, /* number of rows and columns of A */ + Int A [], /* row indices of A */ + Int p [], /* column pointers of A */ + Int perm [], /* output permutation, size n+1 */ + double knobs [COLAMD_KNOBS], /* parameters (uses defaults if NULL) */ + Int stats [COLAMD_STATS], /* output statistics and error codes */ + void * (*allocate) (size_t, size_t), + /* pointer to calloc (ANSI C) or */ + /* mxCalloc (for MATLAB mexFunction) */ + void (*release) (void *) + /* pointer to free (ANSI C) or */ + /* mxFree (for MATLAB mexFunction) */ +) +{ + /* === Local variables ================================================== */ + + Int *count ; /* length of each column of M, and col pointer*/ + Int *mark ; /* mark array for finding duplicate entries */ + Int *M ; /* row indices of matrix M */ + size_t Mlen ; /* length of M */ + Int n_row ; /* number of rows in M */ + Int nnz ; /* number of entries in A */ + Int i ; /* row index of A */ + Int j ; /* column index of A */ + Int k ; /* row index of M */ + Int mnz ; /* number of nonzeros in M */ + Int pp ; /* index into a column of A */ + Int last_row ; /* last row seen in the current column */ + Int length ; /* number of nonzeros in a column */ + + double cknobs [COLAMD_KNOBS] ; /* knobs for colamd */ + double default_knobs [COLAMD_KNOBS] ; /* default knobs for colamd */ + +#ifndef NDEBUG + colamd_get_debug ("symamd") ; +#endif /* NDEBUG */ + + /* === Check the input arguments ======================================== */ + + if (!stats) + { + DEBUG0 (("symamd: stats not present\n")) ; + return (FALSE) ; + } + for (i = 0 ; i < COLAMD_STATS ; i++) + { + stats [i] = 0 ; + } + stats [COLAMD_STATUS] = COLAMD_OK ; + stats [COLAMD_INFO1] = -1 ; + stats [COLAMD_INFO2] = -1 ; + + if (!A) + { + stats [COLAMD_STATUS] = COLAMD_ERROR_A_not_present ; + DEBUG0 (("symamd: A not present\n")) ; + return (FALSE) ; + } + + if (!p) /* p is not present */ + { + stats [COLAMD_STATUS] = COLAMD_ERROR_p_not_present ; + DEBUG0 (("symamd: p not present\n")) ; + return (FALSE) ; + } + + if (n < 0) /* n must be >= 0 */ + { + stats [COLAMD_STATUS] = COLAMD_ERROR_ncol_negative ; + stats [COLAMD_INFO1] = n ; + DEBUG0 (("symamd: n negative %d\n", n)) ; + return (FALSE) ; + } + + nnz = p [n] ; + if (nnz < 0) /* nnz must be >= 0 */ + { + stats [COLAMD_STATUS] = COLAMD_ERROR_nnz_negative ; + stats [COLAMD_INFO1] = nnz ; + DEBUG0 (("symamd: number of entries negative %d\n", nnz)) ; + return (FALSE) ; + } + + if (p [0] != 0) + { + stats [COLAMD_STATUS] = COLAMD_ERROR_p0_nonzero ; + stats [COLAMD_INFO1] = p [0] ; + DEBUG0 (("symamd: p[0] not zero %d\n", p [0])) ; + return (FALSE) ; + } + + /* === If no knobs, set default knobs =================================== */ + + if (!knobs) + { + COLAMD_set_defaults (default_knobs) ; + knobs = default_knobs ; + } + + /* === Allocate count and mark ========================================== */ + + count = (Int *) ((*allocate) (n+1, sizeof (Int))) ; + if (!count) + { + stats [COLAMD_STATUS] = COLAMD_ERROR_out_of_memory ; + DEBUG0 (("symamd: allocate count (size %d) failed\n", n+1)) ; + return (FALSE) ; + } + + mark = (Int *) ((*allocate) (n+1, sizeof (Int))) ; + if (!mark) + { + stats [COLAMD_STATUS] = COLAMD_ERROR_out_of_memory ; + (*release) ((void *) count) ; + DEBUG0 (("symamd: allocate mark (size %d) failed\n", n+1)) ; + return (FALSE) ; + } + + /* === Compute column counts of M, check if A is valid ================== */ + + stats [COLAMD_INFO3] = 0 ; /* number of duplicate or unsorted row indices*/ + + for (i = 0 ; i < n ; i++) + { + mark [i] = -1 ; + } + + for (j = 0 ; j < n ; j++) + { + last_row = -1 ; + + length = p [j+1] - p [j] ; + if (length < 0) + { + /* column pointers must be non-decreasing */ + stats [COLAMD_STATUS] = COLAMD_ERROR_col_length_negative ; + stats [COLAMD_INFO1] = j ; + stats [COLAMD_INFO2] = length ; + (*release) ((void *) count) ; + (*release) ((void *) mark) ; + DEBUG0 (("symamd: col %d negative length %d\n", j, length)) ; + return (FALSE) ; + } + + for (pp = p [j] ; pp < p [j+1] ; pp++) + { + i = A [pp] ; + if (i < 0 || i >= n) + { + /* row index i, in column j, is out of bounds */ + stats [COLAMD_STATUS] = COLAMD_ERROR_row_index_out_of_bounds ; + stats [COLAMD_INFO1] = j ; + stats [COLAMD_INFO2] = i ; + stats [COLAMD_INFO3] = n ; + (*release) ((void *) count) ; + (*release) ((void *) mark) ; + DEBUG0 (("symamd: row %d col %d out of bounds\n", i, j)) ; + return (FALSE) ; + } + + if (i <= last_row || mark [i] == j) + { + /* row index is unsorted or repeated (or both), thus col */ + /* is jumbled. This is a notice, not an error condition. */ + stats [COLAMD_STATUS] = COLAMD_OK_BUT_JUMBLED ; + stats [COLAMD_INFO1] = j ; + stats [COLAMD_INFO2] = i ; + (stats [COLAMD_INFO3]) ++ ; + DEBUG1 (("symamd: row %d col %d unsorted/duplicate\n", i, j)) ; + } + + if (i > j && mark [i] != j) + { + /* row k of M will contain column indices i and j */ + count [i]++ ; + count [j]++ ; + } + + /* mark the row as having been seen in this column */ + mark [i] = j ; + + last_row = i ; + } + } + + /* v2.4: removed free(mark) */ + + /* === Compute column pointers of M ===================================== */ + + /* use output permutation, perm, for column pointers of M */ + perm [0] = 0 ; + for (j = 1 ; j <= n ; j++) + { + perm [j] = perm [j-1] + count [j-1] ; + } + for (j = 0 ; j < n ; j++) + { + count [j] = perm [j] ; + } + + /* === Construct M ====================================================== */ + + mnz = perm [n] ; + n_row = mnz / 2 ; + Mlen = COLAMD_recommended (mnz, n_row, n) ; + M = (Int *) ((*allocate) (Mlen, sizeof (Int))) ; + DEBUG0 (("symamd: M is %d-by-%d with %d entries, Mlen = %g\n", + n_row, n, mnz, (double) Mlen)) ; + + if (!M) + { + stats [COLAMD_STATUS] = COLAMD_ERROR_out_of_memory ; + (*release) ((void *) count) ; + (*release) ((void *) mark) ; + DEBUG0 (("symamd: allocate M (size %g) failed\n", (double) Mlen)) ; + return (FALSE) ; + } + + k = 0 ; + + if (stats [COLAMD_STATUS] == COLAMD_OK) + { + /* Matrix is OK */ + for (j = 0 ; j < n ; j++) + { + ASSERT (p [j+1] - p [j] >= 0) ; + for (pp = p [j] ; pp < p [j+1] ; pp++) + { + i = A [pp] ; + ASSERT (i >= 0 && i < n) ; + if (i > j) + { + /* row k of M contains column indices i and j */ + M [count [i]++] = k ; + M [count [j]++] = k ; + k++ ; + } + } + } + } + else + { + /* Matrix is jumbled. Do not add duplicates to M. Unsorted cols OK. */ + DEBUG0 (("symamd: Duplicates in A.\n")) ; + for (i = 0 ; i < n ; i++) + { + mark [i] = -1 ; + } + for (j = 0 ; j < n ; j++) + { + ASSERT (p [j+1] - p [j] >= 0) ; + for (pp = p [j] ; pp < p [j+1] ; pp++) + { + i = A [pp] ; + ASSERT (i >= 0 && i < n) ; + if (i > j && mark [i] != j) + { + /* row k of M contains column indices i and j */ + M [count [i]++] = k ; + M [count [j]++] = k ; + k++ ; + mark [i] = j ; + } + } + } + /* v2.4: free(mark) moved below */ + } + + /* count and mark no longer needed */ + (*release) ((void *) count) ; + (*release) ((void *) mark) ; /* v2.4: free (mark) moved here */ + ASSERT (k == n_row) ; + + /* === Adjust the knobs for M =========================================== */ + + for (i = 0 ; i < COLAMD_KNOBS ; i++) + { + cknobs [i] = knobs [i] ; + } + + /* there are no dense rows in M */ + cknobs [COLAMD_DENSE_ROW] = -1 ; + cknobs [COLAMD_DENSE_COL] = knobs [COLAMD_DENSE_ROW] ; + + /* === Order the columns of M =========================================== */ + + /* v2.4: colamd cannot fail here, so the error check is removed */ + (void) COLAMD_MAIN (n_row, n, (Int) Mlen, M, perm, cknobs, stats) ; + + /* Note that the output permutation is now in perm */ + + /* === get the statistics for symamd from colamd ======================== */ + + /* a dense column in colamd means a dense row and col in symamd */ + stats [COLAMD_DENSE_ROW] = stats [COLAMD_DENSE_COL] ; + + /* === Free M =========================================================== */ + + (*release) ((void *) M) ; + DEBUG0 (("symamd: done.\n")) ; + return (TRUE) ; + +} + +/* ========================================================================== */ +/* === colamd =============================================================== */ +/* ========================================================================== */ + +/* + The colamd routine computes a column ordering Q of a sparse matrix + A such that the LU factorization P(AQ) = LU remains sparse, where P is + selected via partial pivoting. The routine can also be viewed as + providing a permutation Q such that the Cholesky factorization + (AQ)'(AQ) = LL' remains sparse. +*/ + +PUBLIC Int COLAMD_MAIN /* returns TRUE if successful, FALSE otherwise*/ +( + /* === Parameters ======================================================= */ + + Int n_row, /* number of rows in A */ + Int n_col, /* number of columns in A */ + Int Alen, /* length of A */ + Int A [], /* row indices of A */ + Int p [], /* pointers to columns in A */ + double knobs [COLAMD_KNOBS],/* parameters (uses defaults if NULL) */ + Int stats [COLAMD_STATS] /* output statistics and error codes */ +) +{ + /* === Local variables ================================================== */ + + Int i ; /* loop index */ + Int nnz ; /* nonzeros in A */ + size_t Row_size ; /* size of Row [], in integers */ + size_t Col_size ; /* size of Col [], in integers */ + size_t need ; /* minimum required length of A */ + Colamd_Row *Row ; /* pointer into A of Row [0..n_row] array */ + Colamd_Col *Col ; /* pointer into A of Col [0..n_col] array */ + Int n_col2 ; /* number of non-dense, non-empty columns */ + Int n_row2 ; /* number of non-dense, non-empty rows */ + Int ngarbage ; /* number of garbage collections performed */ + Int max_deg ; /* maximum row degree */ + double default_knobs [COLAMD_KNOBS] ; /* default knobs array */ + Int aggressive ; /* do aggressive absorption */ + int ok ; + +#ifndef NDEBUG + colamd_get_debug ("colamd") ; +#endif /* NDEBUG */ + + /* === Check the input arguments ======================================== */ + + if (!stats) + { + DEBUG0 (("colamd: stats not present\n")) ; + return (FALSE) ; + } + for (i = 0 ; i < COLAMD_STATS ; i++) + { + stats [i] = 0 ; + } + stats [COLAMD_STATUS] = COLAMD_OK ; + stats [COLAMD_INFO1] = -1 ; + stats [COLAMD_INFO2] = -1 ; + + if (!A) /* A is not present */ + { + stats [COLAMD_STATUS] = COLAMD_ERROR_A_not_present ; + DEBUG0 (("colamd: A not present\n")) ; + return (FALSE) ; + } + + if (!p) /* p is not present */ + { + stats [COLAMD_STATUS] = COLAMD_ERROR_p_not_present ; + DEBUG0 (("colamd: p not present\n")) ; + return (FALSE) ; + } + + if (n_row < 0) /* n_row must be >= 0 */ + { + stats [COLAMD_STATUS] = COLAMD_ERROR_nrow_negative ; + stats [COLAMD_INFO1] = n_row ; + DEBUG0 (("colamd: nrow negative %d\n", n_row)) ; + return (FALSE) ; + } + + if (n_col < 0) /* n_col must be >= 0 */ + { + stats [COLAMD_STATUS] = COLAMD_ERROR_ncol_negative ; + stats [COLAMD_INFO1] = n_col ; + DEBUG0 (("colamd: ncol negative %d\n", n_col)) ; + return (FALSE) ; + } + + nnz = p [n_col] ; + if (nnz < 0) /* nnz must be >= 0 */ + { + stats [COLAMD_STATUS] = COLAMD_ERROR_nnz_negative ; + stats [COLAMD_INFO1] = nnz ; + DEBUG0 (("colamd: number of entries negative %d\n", nnz)) ; + return (FALSE) ; + } + + if (p [0] != 0) + { + stats [COLAMD_STATUS] = COLAMD_ERROR_p0_nonzero ; + stats [COLAMD_INFO1] = p [0] ; + DEBUG0 (("colamd: p[0] not zero %d\n", p [0])) ; + return (FALSE) ; + } + + /* === If no knobs, set default knobs =================================== */ + + if (!knobs) + { + COLAMD_set_defaults (default_knobs) ; + knobs = default_knobs ; + } + + aggressive = (knobs [COLAMD_AGGRESSIVE] != FALSE) ; + + /* === Allocate the Row and Col arrays from array A ===================== */ + + ok = TRUE ; + Col_size = COLAMD_C (n_col, &ok) ; /* size of Col array of structs */ + Row_size = COLAMD_R (n_row, &ok) ; /* size of Row array of structs */ + + /* need = 2*nnz + n_col + Col_size + Row_size ; */ + need = t_mult (nnz, 2, &ok) ; + need = t_add (need, n_col, &ok) ; + need = t_add (need, Col_size, &ok) ; + need = t_add (need, Row_size, &ok) ; + + if (!ok || need > (size_t) Alen || need > Int_MAX) + { + /* not enough space in array A to perform the ordering */ + stats [COLAMD_STATUS] = COLAMD_ERROR_A_too_small ; + stats [COLAMD_INFO1] = need ; + stats [COLAMD_INFO2] = Alen ; + DEBUG0 (("colamd: Need Alen >= %d, given only Alen = %d\n", need,Alen)); + return (FALSE) ; + } + + Alen -= Col_size + Row_size ; + Col = (Colamd_Col *) &A [Alen] ; + Row = (Colamd_Row *) &A [Alen + Col_size] ; + + /* === Construct the row and column data structures ===================== */ + + if (!init_rows_cols (n_row, n_col, Row, Col, A, p, stats)) + { + /* input matrix is invalid */ + DEBUG0 (("colamd: Matrix invalid\n")) ; + return (FALSE) ; + } + + /* === Initialize scores, kill dense rows/columns ======================= */ + + init_scoring (n_row, n_col, Row, Col, A, p, knobs, + &n_row2, &n_col2, &max_deg) ; + + /* === Order the supercolumns =========================================== */ + + ngarbage = find_ordering (n_row, n_col, Alen, Row, Col, A, p, + n_col2, max_deg, 2*nnz, aggressive) ; + + /* === Order the non-principal columns ================================== */ + + order_children (n_col, Col, p) ; + + /* === Return statistics in stats ======================================= */ + + stats [COLAMD_DENSE_ROW] = n_row - n_row2 ; + stats [COLAMD_DENSE_COL] = n_col - n_col2 ; + stats [COLAMD_DEFRAG_COUNT] = ngarbage ; + DEBUG0 (("colamd: done.\n")) ; + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === colamd_report ======================================================== */ +/* ========================================================================== */ + +PUBLIC void COLAMD_report +( + Int stats [COLAMD_STATS] +) +{ + print_report ("colamd", stats) ; +} + + +/* ========================================================================== */ +/* === symamd_report ======================================================== */ +/* ========================================================================== */ + +PUBLIC void SYMAMD_report +( + Int stats [COLAMD_STATS] +) +{ + print_report ("symamd", stats) ; +} + + + +/* ========================================================================== */ +/* === NON-USER-CALLABLE ROUTINES: ========================================== */ +/* ========================================================================== */ + +/* There are no user-callable routines beyond this point in the file */ + + +/* ========================================================================== */ +/* === init_rows_cols ======================================================= */ +/* ========================================================================== */ + +/* + Takes the column form of the matrix in A and creates the row form of the + matrix. Also, row and column attributes are stored in the Col and Row + structs. If the columns are un-sorted or contain duplicate row indices, + this routine will also sort and remove duplicate row indices from the + column form of the matrix. Returns FALSE if the matrix is invalid, + TRUE otherwise. Not user-callable. +*/ + +PRIVATE Int init_rows_cols /* returns TRUE if OK, or FALSE otherwise */ +( + /* === Parameters ======================================================= */ + + Int n_row, /* number of rows of A */ + Int n_col, /* number of columns of A */ + Colamd_Row Row [], /* of size n_row+1 */ + Colamd_Col Col [], /* of size n_col+1 */ + Int A [], /* row indices of A, of size Alen */ + Int p [], /* pointers to columns in A, of size n_col+1 */ + Int stats [COLAMD_STATS] /* colamd statistics */ +) +{ + /* === Local variables ================================================== */ + + Int col ; /* a column index */ + Int row ; /* a row index */ + Int *cp ; /* a column pointer */ + Int *cp_end ; /* a pointer to the end of a column */ + Int *rp ; /* a row pointer */ + Int *rp_end ; /* a pointer to the end of a row */ + Int last_row ; /* previous row */ + + /* === Initialize columns, and check column pointers ==================== */ + + for (col = 0 ; col < n_col ; col++) + { + Col [col].start = p [col] ; + Col [col].length = p [col+1] - p [col] ; + + if (Col [col].length < 0) + { + /* column pointers must be non-decreasing */ + stats [COLAMD_STATUS] = COLAMD_ERROR_col_length_negative ; + stats [COLAMD_INFO1] = col ; + stats [COLAMD_INFO2] = Col [col].length ; + DEBUG0 (("colamd: col %d length %d < 0\n", col, Col [col].length)) ; + return (FALSE) ; + } + + Col [col].shared1.thickness = 1 ; + Col [col].shared2.score = 0 ; + Col [col].shared3.prev = EMPTY ; + Col [col].shared4.degree_next = EMPTY ; + } + + /* p [0..n_col] no longer needed, used as "head" in subsequent routines */ + + /* === Scan columns, compute row degrees, and check row indices ========= */ + + stats [COLAMD_INFO3] = 0 ; /* number of duplicate or unsorted row indices*/ + + for (row = 0 ; row < n_row ; row++) + { + Row [row].length = 0 ; + Row [row].shared2.mark = -1 ; + } + + for (col = 0 ; col < n_col ; col++) + { + last_row = -1 ; + + cp = &A [p [col]] ; + cp_end = &A [p [col+1]] ; + + while (cp < cp_end) + { + row = *cp++ ; + + /* make sure row indices within range */ + if (row < 0 || row >= n_row) + { + stats [COLAMD_STATUS] = COLAMD_ERROR_row_index_out_of_bounds ; + stats [COLAMD_INFO1] = col ; + stats [COLAMD_INFO2] = row ; + stats [COLAMD_INFO3] = n_row ; + DEBUG0 (("colamd: row %d col %d out of bounds\n", row, col)) ; + return (FALSE) ; + } + + if (row <= last_row || Row [row].shared2.mark == col) + { + /* row index are unsorted or repeated (or both), thus col */ + /* is jumbled. This is a notice, not an error condition. */ + stats [COLAMD_STATUS] = COLAMD_OK_BUT_JUMBLED ; + stats [COLAMD_INFO1] = col ; + stats [COLAMD_INFO2] = row ; + (stats [COLAMD_INFO3]) ++ ; + DEBUG1 (("colamd: row %d col %d unsorted/duplicate\n",row,col)); + } + + if (Row [row].shared2.mark != col) + { + Row [row].length++ ; + } + else + { + /* this is a repeated entry in the column, */ + /* it will be removed */ + Col [col].length-- ; + } + + /* mark the row as having been seen in this column */ + Row [row].shared2.mark = col ; + + last_row = row ; + } + } + + /* === Compute row pointers ============================================= */ + + /* row form of the matrix starts directly after the column */ + /* form of matrix in A */ + Row [0].start = p [n_col] ; + Row [0].shared1.p = Row [0].start ; + Row [0].shared2.mark = -1 ; + for (row = 1 ; row < n_row ; row++) + { + Row [row].start = Row [row-1].start + Row [row-1].length ; + Row [row].shared1.p = Row [row].start ; + Row [row].shared2.mark = -1 ; + } + + /* === Create row form ================================================== */ + + if (stats [COLAMD_STATUS] == COLAMD_OK_BUT_JUMBLED) + { + /* if cols jumbled, watch for repeated row indices */ + for (col = 0 ; col < n_col ; col++) + { + cp = &A [p [col]] ; + cp_end = &A [p [col+1]] ; + while (cp < cp_end) + { + row = *cp++ ; + if (Row [row].shared2.mark != col) + { + A [(Row [row].shared1.p)++] = col ; + Row [row].shared2.mark = col ; + } + } + } + } + else + { + /* if cols not jumbled, we don't need the mark (this is faster) */ + for (col = 0 ; col < n_col ; col++) + { + cp = &A [p [col]] ; + cp_end = &A [p [col+1]] ; + while (cp < cp_end) + { + A [(Row [*cp++].shared1.p)++] = col ; + } + } + } + + /* === Clear the row marks and set row degrees ========================== */ + + for (row = 0 ; row < n_row ; row++) + { + Row [row].shared2.mark = 0 ; + Row [row].shared1.degree = Row [row].length ; + } + + /* === See if we need to re-create columns ============================== */ + + if (stats [COLAMD_STATUS] == COLAMD_OK_BUT_JUMBLED) + { + DEBUG0 (("colamd: reconstructing column form, matrix jumbled\n")) ; + +#ifndef NDEBUG + /* make sure column lengths are correct */ + for (col = 0 ; col < n_col ; col++) + { + p [col] = Col [col].length ; + } + for (row = 0 ; row < n_row ; row++) + { + rp = &A [Row [row].start] ; + rp_end = rp + Row [row].length ; + while (rp < rp_end) + { + p [*rp++]-- ; + } + } + for (col = 0 ; col < n_col ; col++) + { + ASSERT (p [col] == 0) ; + } + /* now p is all zero (different than when debugging is turned off) */ +#endif /* NDEBUG */ + + /* === Compute col pointers ========================================= */ + + /* col form of the matrix starts at A [0]. */ + /* Note, we may have a gap between the col form and the row */ + /* form if there were duplicate entries, if so, it will be */ + /* removed upon the first garbage collection */ + Col [0].start = 0 ; + p [0] = Col [0].start ; + for (col = 1 ; col < n_col ; col++) + { + /* note that the lengths here are for pruned columns, i.e. */ + /* no duplicate row indices will exist for these columns */ + Col [col].start = Col [col-1].start + Col [col-1].length ; + p [col] = Col [col].start ; + } + + /* === Re-create col form =========================================== */ + + for (row = 0 ; row < n_row ; row++) + { + rp = &A [Row [row].start] ; + rp_end = rp + Row [row].length ; + while (rp < rp_end) + { + A [(p [*rp++])++] = row ; + } + } + } + + /* === Done. Matrix is not (or no longer) jumbled ====================== */ + + return (TRUE) ; +} + + +/* ========================================================================== */ +/* === init_scoring ========================================================= */ +/* ========================================================================== */ + +/* + Kills dense or empty columns and rows, calculates an initial score for + each column, and places all columns in the degree lists. Not user-callable. +*/ + +PRIVATE void init_scoring +( + /* === Parameters ======================================================= */ + + Int n_row, /* number of rows of A */ + Int n_col, /* number of columns of A */ + Colamd_Row Row [], /* of size n_row+1 */ + Colamd_Col Col [], /* of size n_col+1 */ + Int A [], /* column form and row form of A */ + Int head [], /* of size n_col+1 */ + double knobs [COLAMD_KNOBS],/* parameters */ + Int *p_n_row2, /* number of non-dense, non-empty rows */ + Int *p_n_col2, /* number of non-dense, non-empty columns */ + Int *p_max_deg /* maximum row degree */ +) +{ + /* === Local variables ================================================== */ + + Int c ; /* a column index */ + Int r, row ; /* a row index */ + Int *cp ; /* a column pointer */ + Int deg ; /* degree of a row or column */ + Int *cp_end ; /* a pointer to the end of a column */ + Int *new_cp ; /* new column pointer */ + Int col_length ; /* length of pruned column */ + Int score ; /* current column score */ + Int n_col2 ; /* number of non-dense, non-empty columns */ + Int n_row2 ; /* number of non-dense, non-empty rows */ + Int dense_row_count ; /* remove rows with more entries than this */ + Int dense_col_count ; /* remove cols with more entries than this */ + Int min_score ; /* smallest column score */ + Int max_deg ; /* maximum row degree */ + Int next_col ; /* Used to add to degree list.*/ + +#ifndef NDEBUG + Int debug_count ; /* debug only. */ +#endif /* NDEBUG */ + + /* === Extract knobs ==================================================== */ + + /* Note: if knobs contains a NaN, this is undefined: */ + if (knobs [COLAMD_DENSE_ROW] < 0) + { + /* only remove completely dense rows */ + dense_row_count = n_col-1 ; + } + else + { + dense_row_count = DENSE_DEGREE (knobs [COLAMD_DENSE_ROW], n_col) ; + } + if (knobs [COLAMD_DENSE_COL] < 0) + { + /* only remove completely dense columns */ + dense_col_count = n_row-1 ; + } + else + { + dense_col_count = + DENSE_DEGREE (knobs [COLAMD_DENSE_COL], MIN (n_row, n_col)) ; + } + + DEBUG1 (("colamd: densecount: %d %d\n", dense_row_count, dense_col_count)) ; + max_deg = 0 ; + n_col2 = n_col ; + n_row2 = n_row ; + + /* === Kill empty columns =============================================== */ + + /* Put the empty columns at the end in their natural order, so that LU */ + /* factorization can proceed as far as possible. */ + for (c = n_col-1 ; c >= 0 ; c--) + { + deg = Col [c].length ; + if (deg == 0) + { + /* this is a empty column, kill and order it last */ + Col [c].shared2.order = --n_col2 ; + KILL_PRINCIPAL_COL (c) ; + } + } + DEBUG1 (("colamd: null columns killed: %d\n", n_col - n_col2)) ; + + /* === Kill dense columns =============================================== */ + + /* Put the dense columns at the end, in their natural order */ + for (c = n_col-1 ; c >= 0 ; c--) + { + /* skip any dead columns */ + if (COL_IS_DEAD (c)) + { + continue ; + } + deg = Col [c].length ; + if (deg > dense_col_count) + { + /* this is a dense column, kill and order it last */ + Col [c].shared2.order = --n_col2 ; + /* decrement the row degrees */ + cp = &A [Col [c].start] ; + cp_end = cp + Col [c].length ; + while (cp < cp_end) + { + Row [*cp++].shared1.degree-- ; + } + KILL_PRINCIPAL_COL (c) ; + } + } + DEBUG1 (("colamd: Dense and null columns killed: %d\n", n_col - n_col2)) ; + + /* === Kill dense and empty rows ======================================== */ + + for (r = 0 ; r < n_row ; r++) + { + deg = Row [r].shared1.degree ; + ASSERT (deg >= 0 && deg <= n_col) ; + if (deg > dense_row_count || deg == 0) + { + /* kill a dense or empty row */ + KILL_ROW (r) ; + --n_row2 ; + } + else + { + /* keep track of max degree of remaining rows */ + max_deg = MAX (max_deg, deg) ; + } + } + DEBUG1 (("colamd: Dense and null rows killed: %d\n", n_row - n_row2)) ; + + /* === Compute initial column scores ==================================== */ + + /* At this point the row degrees are accurate. They reflect the number */ + /* of "live" (non-dense) columns in each row. No empty rows exist. */ + /* Some "live" columns may contain only dead rows, however. These are */ + /* pruned in the code below. */ + + /* now find the initial matlab score for each column */ + for (c = n_col-1 ; c >= 0 ; c--) + { + /* skip dead column */ + if (COL_IS_DEAD (c)) + { + continue ; + } + score = 0 ; + cp = &A [Col [c].start] ; + new_cp = cp ; + cp_end = cp + Col [c].length ; + while (cp < cp_end) + { + /* get a row */ + row = *cp++ ; + /* skip if dead */ + if (ROW_IS_DEAD (row)) + { + continue ; + } + /* compact the column */ + *new_cp++ = row ; + /* add row's external degree */ + score += Row [row].shared1.degree - 1 ; + /* guard against integer overflow */ + score = MIN (score, n_col) ; + } + /* determine pruned column length */ + col_length = (Int) (new_cp - &A [Col [c].start]) ; + if (col_length == 0) + { + /* a newly-made null column (all rows in this col are "dense" */ + /* and have already been killed) */ + DEBUG2 (("Newly null killed: %d\n", c)) ; + Col [c].shared2.order = --n_col2 ; + KILL_PRINCIPAL_COL (c) ; + } + else + { + /* set column length and set score */ + ASSERT (score >= 0) ; + ASSERT (score <= n_col) ; + Col [c].length = col_length ; + Col [c].shared2.score = score ; + } + } + DEBUG1 (("colamd: Dense, null, and newly-null columns killed: %d\n", + n_col-n_col2)) ; + + /* At this point, all empty rows and columns are dead. All live columns */ + /* are "clean" (containing no dead rows) and simplicial (no supercolumns */ + /* yet). Rows may contain dead columns, but all live rows contain at */ + /* least one live column. */ + +#ifndef NDEBUG + debug_structures (n_row, n_col, Row, Col, A, n_col2) ; +#endif /* NDEBUG */ + + /* === Initialize degree lists ========================================== */ + +#ifndef NDEBUG + debug_count = 0 ; +#endif /* NDEBUG */ + + /* clear the hash buckets */ + for (c = 0 ; c <= n_col ; c++) + { + head [c] = EMPTY ; + } + min_score = n_col ; + /* place in reverse order, so low column indices are at the front */ + /* of the lists. This is to encourage natural tie-breaking */ + for (c = n_col-1 ; c >= 0 ; c--) + { + /* only add principal columns to degree lists */ + if (COL_IS_ALIVE (c)) + { + DEBUG4 (("place %d score %d minscore %d ncol %d\n", + c, Col [c].shared2.score, min_score, n_col)) ; + + /* === Add columns score to DList =============================== */ + + score = Col [c].shared2.score ; + + ASSERT (min_score >= 0) ; + ASSERT (min_score <= n_col) ; + ASSERT (score >= 0) ; + ASSERT (score <= n_col) ; + ASSERT (head [score] >= EMPTY) ; + + /* now add this column to dList at proper score location */ + next_col = head [score] ; + Col [c].shared3.prev = EMPTY ; + Col [c].shared4.degree_next = next_col ; + + /* if there already was a column with the same score, set its */ + /* previous pointer to this new column */ + if (next_col != EMPTY) + { + Col [next_col].shared3.prev = c ; + } + head [score] = c ; + + /* see if this score is less than current min */ + min_score = MIN (min_score, score) ; + +#ifndef NDEBUG + debug_count++ ; +#endif /* NDEBUG */ + + } + } + +#ifndef NDEBUG + DEBUG1 (("colamd: Live cols %d out of %d, non-princ: %d\n", + debug_count, n_col, n_col-debug_count)) ; + ASSERT (debug_count == n_col2) ; + debug_deg_lists (n_row, n_col, Row, Col, head, min_score, n_col2, max_deg) ; +#endif /* NDEBUG */ + + /* === Return number of remaining columns, and max row degree =========== */ + + *p_n_col2 = n_col2 ; + *p_n_row2 = n_row2 ; + *p_max_deg = max_deg ; +} + + +/* ========================================================================== */ +/* === find_ordering ======================================================== */ +/* ========================================================================== */ + +/* + Order the principal columns of the supercolumn form of the matrix + (no supercolumns on input). Uses a minimum approximate column minimum + degree ordering method. Not user-callable. +*/ + +PRIVATE Int find_ordering /* return the number of garbage collections */ +( + /* === Parameters ======================================================= */ + + Int n_row, /* number of rows of A */ + Int n_col, /* number of columns of A */ + Int Alen, /* size of A, 2*nnz + n_col or larger */ + Colamd_Row Row [], /* of size n_row+1 */ + Colamd_Col Col [], /* of size n_col+1 */ + Int A [], /* column form and row form of A */ + Int head [], /* of size n_col+1 */ + Int n_col2, /* Remaining columns to order */ + Int max_deg, /* Maximum row degree */ + Int pfree, /* index of first free slot (2*nnz on entry) */ + Int aggressive +) +{ + /* === Local variables ================================================== */ + + Int k ; /* current pivot ordering step */ + Int pivot_col ; /* current pivot column */ + Int *cp ; /* a column pointer */ + Int *rp ; /* a row pointer */ + Int pivot_row ; /* current pivot row */ + Int *new_cp ; /* modified column pointer */ + Int *new_rp ; /* modified row pointer */ + Int pivot_row_start ; /* pointer to start of pivot row */ + Int pivot_row_degree ; /* number of columns in pivot row */ + Int pivot_row_length ; /* number of supercolumns in pivot row */ + Int pivot_col_score ; /* score of pivot column */ + Int needed_memory ; /* free space needed for pivot row */ + Int *cp_end ; /* pointer to the end of a column */ + Int *rp_end ; /* pointer to the end of a row */ + Int row ; /* a row index */ + Int col ; /* a column index */ + Int max_score ; /* maximum possible score */ + Int cur_score ; /* score of current column */ + unsigned Int hash ; /* hash value for supernode detection */ + Int head_column ; /* head of hash bucket */ + Int first_col ; /* first column in hash bucket */ + Int tag_mark ; /* marker value for mark array */ + Int row_mark ; /* Row [row].shared2.mark */ + Int set_difference ; /* set difference size of row with pivot row */ + Int min_score ; /* smallest column score */ + Int col_thickness ; /* "thickness" (no. of columns in a supercol) */ + Int max_mark ; /* maximum value of tag_mark */ + Int pivot_col_thickness ; /* number of columns represented by pivot col */ + Int prev_col ; /* Used by Dlist operations. */ + Int next_col ; /* Used by Dlist operations. */ + Int ngarbage ; /* number of garbage collections performed */ + +#ifndef NDEBUG + Int debug_d ; /* debug loop counter */ + Int debug_step = 0 ; /* debug loop counter */ +#endif /* NDEBUG */ + + /* === Initialization and clear mark ==================================== */ + + max_mark = INT_MAX - n_col ; /* INT_MAX defined in */ + tag_mark = clear_mark (0, max_mark, n_row, Row) ; + min_score = 0 ; + ngarbage = 0 ; + DEBUG1 (("colamd: Ordering, n_col2=%d\n", n_col2)) ; + + /* === Order the columns ================================================ */ + + for (k = 0 ; k < n_col2 ; /* 'k' is incremented below */) + { + +#ifndef NDEBUG + if (debug_step % 100 == 0) + { + DEBUG2 (("\n... Step k: %d out of n_col2: %d\n", k, n_col2)) ; + } + else + { + DEBUG3 (("\n----------Step k: %d out of n_col2: %d\n", k, n_col2)) ; + } + debug_step++ ; + debug_deg_lists (n_row, n_col, Row, Col, head, + min_score, n_col2-k, max_deg) ; + debug_matrix (n_row, n_col, Row, Col, A) ; +#endif /* NDEBUG */ + + /* === Select pivot column, and order it ============================ */ + + /* make sure degree list isn't empty */ + ASSERT (min_score >= 0) ; + ASSERT (min_score <= n_col) ; + ASSERT (head [min_score] >= EMPTY) ; + +#ifndef NDEBUG + for (debug_d = 0 ; debug_d < min_score ; debug_d++) + { + ASSERT (head [debug_d] == EMPTY) ; + } +#endif /* NDEBUG */ + + /* get pivot column from head of minimum degree list */ + while (head [min_score] == EMPTY && min_score < n_col) + { + min_score++ ; + } + pivot_col = head [min_score] ; + ASSERT (pivot_col >= 0 && pivot_col <= n_col) ; + next_col = Col [pivot_col].shared4.degree_next ; + head [min_score] = next_col ; + if (next_col != EMPTY) + { + Col [next_col].shared3.prev = EMPTY ; + } + + ASSERT (COL_IS_ALIVE (pivot_col)) ; + + /* remember score for defrag check */ + pivot_col_score = Col [pivot_col].shared2.score ; + + /* the pivot column is the kth column in the pivot order */ + Col [pivot_col].shared2.order = k ; + + /* increment order count by column thickness */ + pivot_col_thickness = Col [pivot_col].shared1.thickness ; + k += pivot_col_thickness ; + ASSERT (pivot_col_thickness > 0) ; + DEBUG3 (("Pivot col: %d thick %d\n", pivot_col, pivot_col_thickness)) ; + + /* === Garbage_collection, if necessary ============================= */ + + needed_memory = MIN (pivot_col_score, n_col - k) ; + if (pfree + needed_memory >= Alen) + { + pfree = garbage_collection (n_row, n_col, Row, Col, A, &A [pfree]) ; + ngarbage++ ; + /* after garbage collection we will have enough */ + ASSERT (pfree + needed_memory < Alen) ; + /* garbage collection has wiped out the Row[].shared2.mark array */ + tag_mark = clear_mark (0, max_mark, n_row, Row) ; + +#ifndef NDEBUG + debug_matrix (n_row, n_col, Row, Col, A) ; +#endif /* NDEBUG */ + } + + /* === Compute pivot row pattern ==================================== */ + + /* get starting location for this new merged row */ + pivot_row_start = pfree ; + + /* initialize new row counts to zero */ + pivot_row_degree = 0 ; + + /* tag pivot column as having been visited so it isn't included */ + /* in merged pivot row */ + Col [pivot_col].shared1.thickness = -pivot_col_thickness ; + + /* pivot row is the union of all rows in the pivot column pattern */ + cp = &A [Col [pivot_col].start] ; + cp_end = cp + Col [pivot_col].length ; + while (cp < cp_end) + { + /* get a row */ + row = *cp++ ; + DEBUG4 (("Pivot col pattern %d %d\n", ROW_IS_ALIVE (row), row)) ; + /* skip if row is dead */ + if (ROW_IS_ALIVE (row)) + { + rp = &A [Row [row].start] ; + rp_end = rp + Row [row].length ; + while (rp < rp_end) + { + /* get a column */ + col = *rp++ ; + /* add the column, if alive and untagged */ + col_thickness = Col [col].shared1.thickness ; + if (col_thickness > 0 && COL_IS_ALIVE (col)) + { + /* tag column in pivot row */ + Col [col].shared1.thickness = -col_thickness ; + ASSERT (pfree < Alen) ; + /* place column in pivot row */ + A [pfree++] = col ; + pivot_row_degree += col_thickness ; + } + } + } + } + + /* clear tag on pivot column */ + Col [pivot_col].shared1.thickness = pivot_col_thickness ; + max_deg = MAX (max_deg, pivot_row_degree) ; + +#ifndef NDEBUG + DEBUG3 (("check2\n")) ; + debug_mark (n_row, Row, tag_mark, max_mark) ; +#endif /* NDEBUG */ + + /* === Kill all rows used to construct pivot row ==================== */ + + /* also kill pivot row, temporarily */ + cp = &A [Col [pivot_col].start] ; + cp_end = cp + Col [pivot_col].length ; + while (cp < cp_end) + { + /* may be killing an already dead row */ + row = *cp++ ; + DEBUG3 (("Kill row in pivot col: %d\n", row)) ; + KILL_ROW (row) ; + } + + /* === Select a row index to use as the new pivot row =============== */ + + pivot_row_length = pfree - pivot_row_start ; + if (pivot_row_length > 0) + { + /* pick the "pivot" row arbitrarily (first row in col) */ + pivot_row = A [Col [pivot_col].start] ; + DEBUG3 (("Pivotal row is %d\n", pivot_row)) ; + } + else + { + /* there is no pivot row, since it is of zero length */ + pivot_row = EMPTY ; + ASSERT (pivot_row_length == 0) ; + } + ASSERT (Col [pivot_col].length > 0 || pivot_row_length == 0) ; + + /* === Approximate degree computation =============================== */ + + /* Here begins the computation of the approximate degree. The column */ + /* score is the sum of the pivot row "length", plus the size of the */ + /* set differences of each row in the column minus the pattern of the */ + /* pivot row itself. The column ("thickness") itself is also */ + /* excluded from the column score (we thus use an approximate */ + /* external degree). */ + + /* The time taken by the following code (compute set differences, and */ + /* add them up) is proportional to the size of the data structure */ + /* being scanned - that is, the sum of the sizes of each column in */ + /* the pivot row. Thus, the amortized time to compute a column score */ + /* is proportional to the size of that column (where size, in this */ + /* context, is the column "length", or the number of row indices */ + /* in that column). The number of row indices in a column is */ + /* monotonically non-decreasing, from the length of the original */ + /* column on input to colamd. */ + + /* === Compute set differences ====================================== */ + + DEBUG3 (("** Computing set differences phase. **\n")) ; + + /* pivot row is currently dead - it will be revived later. */ + + DEBUG3 (("Pivot row: ")) ; + /* for each column in pivot row */ + rp = &A [pivot_row_start] ; + rp_end = rp + pivot_row_length ; + while (rp < rp_end) + { + col = *rp++ ; + ASSERT (COL_IS_ALIVE (col) && col != pivot_col) ; + DEBUG3 (("Col: %d\n", col)) ; + + /* clear tags used to construct pivot row pattern */ + col_thickness = -Col [col].shared1.thickness ; + ASSERT (col_thickness > 0) ; + Col [col].shared1.thickness = col_thickness ; + + /* === Remove column from degree list =========================== */ + + cur_score = Col [col].shared2.score ; + prev_col = Col [col].shared3.prev ; + next_col = Col [col].shared4.degree_next ; + ASSERT (cur_score >= 0) ; + ASSERT (cur_score <= n_col) ; + ASSERT (cur_score >= EMPTY) ; + if (prev_col == EMPTY) + { + head [cur_score] = next_col ; + } + else + { + Col [prev_col].shared4.degree_next = next_col ; + } + if (next_col != EMPTY) + { + Col [next_col].shared3.prev = prev_col ; + } + + /* === Scan the column ========================================== */ + + cp = &A [Col [col].start] ; + cp_end = cp + Col [col].length ; + while (cp < cp_end) + { + /* get a row */ + row = *cp++ ; + row_mark = Row [row].shared2.mark ; + /* skip if dead */ + if (ROW_IS_MARKED_DEAD (row_mark)) + { + continue ; + } + ASSERT (row != pivot_row) ; + set_difference = row_mark - tag_mark ; + /* check if the row has been seen yet */ + if (set_difference < 0) + { + ASSERT (Row [row].shared1.degree <= max_deg) ; + set_difference = Row [row].shared1.degree ; + } + /* subtract column thickness from this row's set difference */ + set_difference -= col_thickness ; + ASSERT (set_difference >= 0) ; + /* absorb this row if the set difference becomes zero */ + if (set_difference == 0 && aggressive) + { + DEBUG3 (("aggressive absorption. Row: %d\n", row)) ; + KILL_ROW (row) ; + } + else + { + /* save the new mark */ + Row [row].shared2.mark = set_difference + tag_mark ; + } + } + } + +#ifndef NDEBUG + debug_deg_lists (n_row, n_col, Row, Col, head, + min_score, n_col2-k-pivot_row_degree, max_deg) ; +#endif /* NDEBUG */ + + /* === Add up set differences for each column ======================= */ + + DEBUG3 (("** Adding set differences phase. **\n")) ; + + /* for each column in pivot row */ + rp = &A [pivot_row_start] ; + rp_end = rp + pivot_row_length ; + while (rp < rp_end) + { + /* get a column */ + col = *rp++ ; + ASSERT (COL_IS_ALIVE (col) && col != pivot_col) ; + hash = 0 ; + cur_score = 0 ; + cp = &A [Col [col].start] ; + /* compact the column */ + new_cp = cp ; + cp_end = cp + Col [col].length ; + + DEBUG4 (("Adding set diffs for Col: %d.\n", col)) ; + + while (cp < cp_end) + { + /* get a row */ + row = *cp++ ; + ASSERT(row >= 0 && row < n_row) ; + row_mark = Row [row].shared2.mark ; + /* skip if dead */ + if (ROW_IS_MARKED_DEAD (row_mark)) + { + DEBUG4 ((" Row %d, dead\n", row)) ; + continue ; + } + DEBUG4 ((" Row %d, set diff %d\n", row, row_mark-tag_mark)); + ASSERT (row_mark >= tag_mark) ; + /* compact the column */ + *new_cp++ = row ; + /* compute hash function */ + hash += row ; + /* add set difference */ + cur_score += row_mark - tag_mark ; + /* integer overflow... */ + cur_score = MIN (cur_score, n_col) ; + } + + /* recompute the column's length */ + Col [col].length = (Int) (new_cp - &A [Col [col].start]) ; + + /* === Further mass elimination ================================= */ + + if (Col [col].length == 0) + { + DEBUG4 (("further mass elimination. Col: %d\n", col)) ; + /* nothing left but the pivot row in this column */ + KILL_PRINCIPAL_COL (col) ; + pivot_row_degree -= Col [col].shared1.thickness ; + ASSERT (pivot_row_degree >= 0) ; + /* order it */ + Col [col].shared2.order = k ; + /* increment order count by column thickness */ + k += Col [col].shared1.thickness ; + } + else + { + /* === Prepare for supercolumn detection ==================== */ + + DEBUG4 (("Preparing supercol detection for Col: %d.\n", col)) ; + + /* save score so far */ + Col [col].shared2.score = cur_score ; + + /* add column to hash table, for supercolumn detection */ + hash %= n_col + 1 ; + + DEBUG4 ((" Hash = %d, n_col = %d.\n", hash, n_col)) ; + ASSERT (((Int) hash) <= n_col) ; + + head_column = head [hash] ; + if (head_column > EMPTY) + { + /* degree list "hash" is non-empty, use prev (shared3) of */ + /* first column in degree list as head of hash bucket */ + first_col = Col [head_column].shared3.headhash ; + Col [head_column].shared3.headhash = col ; + } + else + { + /* degree list "hash" is empty, use head as hash bucket */ + first_col = - (head_column + 2) ; + head [hash] = - (col + 2) ; + } + Col [col].shared4.hash_next = first_col ; + + /* save hash function in Col [col].shared3.hash */ + Col [col].shared3.hash = (Int) hash ; + ASSERT (COL_IS_ALIVE (col)) ; + } + } + + /* The approximate external column degree is now computed. */ + + /* === Supercolumn detection ======================================== */ + + DEBUG3 (("** Supercolumn detection phase. **\n")) ; + + detect_super_cols ( + +#ifndef NDEBUG + n_col, Row, +#endif /* NDEBUG */ + + Col, A, head, pivot_row_start, pivot_row_length) ; + + /* === Kill the pivotal column ====================================== */ + + KILL_PRINCIPAL_COL (pivot_col) ; + + /* === Clear mark =================================================== */ + + tag_mark = clear_mark (tag_mark+max_deg+1, max_mark, n_row, Row) ; + +#ifndef NDEBUG + DEBUG3 (("check3\n")) ; + debug_mark (n_row, Row, tag_mark, max_mark) ; +#endif /* NDEBUG */ + + /* === Finalize the new pivot row, and column scores ================ */ + + DEBUG3 (("** Finalize scores phase. **\n")) ; + + /* for each column in pivot row */ + rp = &A [pivot_row_start] ; + /* compact the pivot row */ + new_rp = rp ; + rp_end = rp + pivot_row_length ; + while (rp < rp_end) + { + col = *rp++ ; + /* skip dead columns */ + if (COL_IS_DEAD (col)) + { + continue ; + } + *new_rp++ = col ; + /* add new pivot row to column */ + A [Col [col].start + (Col [col].length++)] = pivot_row ; + + /* retrieve score so far and add on pivot row's degree. */ + /* (we wait until here for this in case the pivot */ + /* row's degree was reduced due to mass elimination). */ + cur_score = Col [col].shared2.score + pivot_row_degree ; + + /* calculate the max possible score as the number of */ + /* external columns minus the 'k' value minus the */ + /* columns thickness */ + max_score = n_col - k - Col [col].shared1.thickness ; + + /* make the score the external degree of the union-of-rows */ + cur_score -= Col [col].shared1.thickness ; + + /* make sure score is less or equal than the max score */ + cur_score = MIN (cur_score, max_score) ; + ASSERT (cur_score >= 0) ; + + /* store updated score */ + Col [col].shared2.score = cur_score ; + + /* === Place column back in degree list ========================= */ + + ASSERT (min_score >= 0) ; + ASSERT (min_score <= n_col) ; + ASSERT (cur_score >= 0) ; + ASSERT (cur_score <= n_col) ; + ASSERT (head [cur_score] >= EMPTY) ; + next_col = head [cur_score] ; + Col [col].shared4.degree_next = next_col ; + Col [col].shared3.prev = EMPTY ; + if (next_col != EMPTY) + { + Col [next_col].shared3.prev = col ; + } + head [cur_score] = col ; + + /* see if this score is less than current min */ + min_score = MIN (min_score, cur_score) ; + + } + +#ifndef NDEBUG + debug_deg_lists (n_row, n_col, Row, Col, head, + min_score, n_col2-k, max_deg) ; +#endif /* NDEBUG */ + + /* === Resurrect the new pivot row ================================== */ + + if (pivot_row_degree > 0) + { + /* update pivot row length to reflect any cols that were killed */ + /* during super-col detection and mass elimination */ + Row [pivot_row].start = pivot_row_start ; + Row [pivot_row].length = (Int) (new_rp - &A[pivot_row_start]) ; + ASSERT (Row [pivot_row].length > 0) ; + Row [pivot_row].shared1.degree = pivot_row_degree ; + Row [pivot_row].shared2.mark = 0 ; + /* pivot row is no longer dead */ + + DEBUG1 (("Resurrect Pivot_row %d deg: %d\n", + pivot_row, pivot_row_degree)) ; + } + } + + /* === All principal columns have now been ordered ====================== */ + + return (ngarbage) ; +} + + +/* ========================================================================== */ +/* === order_children ======================================================= */ +/* ========================================================================== */ + +/* + The find_ordering routine has ordered all of the principal columns (the + representatives of the supercolumns). The non-principal columns have not + yet been ordered. This routine orders those columns by walking up the + parent tree (a column is a child of the column which absorbed it). The + final permutation vector is then placed in p [0 ... n_col-1], with p [0] + being the first column, and p [n_col-1] being the last. It doesn't look + like it at first glance, but be assured that this routine takes time linear + in the number of columns. Although not immediately obvious, the time + taken by this routine is O (n_col), that is, linear in the number of + columns. Not user-callable. +*/ + +PRIVATE void order_children +( + /* === Parameters ======================================================= */ + + Int n_col, /* number of columns of A */ + Colamd_Col Col [], /* of size n_col+1 */ + Int p [] /* p [0 ... n_col-1] is the column permutation*/ +) +{ + /* === Local variables ================================================== */ + + Int i ; /* loop counter for all columns */ + Int c ; /* column index */ + Int parent ; /* index of column's parent */ + Int order ; /* column's order */ + + /* === Order each non-principal column ================================== */ + + for (i = 0 ; i < n_col ; i++) + { + /* find an un-ordered non-principal column */ + ASSERT (COL_IS_DEAD (i)) ; + if (!COL_IS_DEAD_PRINCIPAL (i) && Col [i].shared2.order == EMPTY) + { + parent = i ; + /* once found, find its principal parent */ + do + { + parent = Col [parent].shared1.parent ; + } while (!COL_IS_DEAD_PRINCIPAL (parent)) ; + + /* now, order all un-ordered non-principal columns along path */ + /* to this parent. collapse tree at the same time */ + c = i ; + /* get order of parent */ + order = Col [parent].shared2.order ; + + do + { + ASSERT (Col [c].shared2.order == EMPTY) ; + + /* order this column */ + Col [c].shared2.order = order++ ; + /* collaps tree */ + Col [c].shared1.parent = parent ; + + /* get immediate parent of this column */ + c = Col [c].shared1.parent ; + + /* continue until we hit an ordered column. There are */ + /* guarranteed not to be anymore unordered columns */ + /* above an ordered column */ + } while (Col [c].shared2.order == EMPTY) ; + + /* re-order the super_col parent to largest order for this group */ + Col [parent].shared2.order = order ; + } + } + + /* === Generate the permutation ========================================= */ + + for (c = 0 ; c < n_col ; c++) + { + p [Col [c].shared2.order] = c ; + } +} + + +/* ========================================================================== */ +/* === detect_super_cols ==================================================== */ +/* ========================================================================== */ + +/* + Detects supercolumns by finding matches between columns in the hash buckets. + Check amongst columns in the set A [row_start ... row_start + row_length-1]. + The columns under consideration are currently *not* in the degree lists, + and have already been placed in the hash buckets. + + The hash bucket for columns whose hash function is equal to h is stored + as follows: + + if head [h] is >= 0, then head [h] contains a degree list, so: + + head [h] is the first column in degree bucket h. + Col [head [h]].headhash gives the first column in hash bucket h. + + otherwise, the degree list is empty, and: + + -(head [h] + 2) is the first column in hash bucket h. + + For a column c in a hash bucket, Col [c].shared3.prev is NOT a "previous + column" pointer. Col [c].shared3.hash is used instead as the hash number + for that column. The value of Col [c].shared4.hash_next is the next column + in the same hash bucket. + + Assuming no, or "few" hash collisions, the time taken by this routine is + linear in the sum of the sizes (lengths) of each column whose score has + just been computed in the approximate degree computation. + Not user-callable. +*/ + +PRIVATE void detect_super_cols +( + /* === Parameters ======================================================= */ + +#ifndef NDEBUG + /* these two parameters are only needed when debugging is enabled: */ + Int n_col, /* number of columns of A */ + Colamd_Row Row [], /* of size n_row+1 */ +#endif /* NDEBUG */ + + Colamd_Col Col [], /* of size n_col+1 */ + Int A [], /* row indices of A */ + Int head [], /* head of degree lists and hash buckets */ + Int row_start, /* pointer to set of columns to check */ + Int row_length /* number of columns to check */ +) +{ + /* === Local variables ================================================== */ + + Int hash ; /* hash value for a column */ + Int *rp ; /* pointer to a row */ + Int c ; /* a column index */ + Int super_c ; /* column index of the column to absorb into */ + Int *cp1 ; /* column pointer for column super_c */ + Int *cp2 ; /* column pointer for column c */ + Int length ; /* length of column super_c */ + Int prev_c ; /* column preceding c in hash bucket */ + Int i ; /* loop counter */ + Int *rp_end ; /* pointer to the end of the row */ + Int col ; /* a column index in the row to check */ + Int head_column ; /* first column in hash bucket or degree list */ + Int first_col ; /* first column in hash bucket */ + + /* === Consider each column in the row ================================== */ + + rp = &A [row_start] ; + rp_end = rp + row_length ; + while (rp < rp_end) + { + col = *rp++ ; + if (COL_IS_DEAD (col)) + { + continue ; + } + + /* get hash number for this column */ + hash = Col [col].shared3.hash ; + ASSERT (hash <= n_col) ; + + /* === Get the first column in this hash bucket ===================== */ + + head_column = head [hash] ; + if (head_column > EMPTY) + { + first_col = Col [head_column].shared3.headhash ; + } + else + { + first_col = - (head_column + 2) ; + } + + /* === Consider each column in the hash bucket ====================== */ + + for (super_c = first_col ; super_c != EMPTY ; + super_c = Col [super_c].shared4.hash_next) + { + ASSERT (COL_IS_ALIVE (super_c)) ; + ASSERT (Col [super_c].shared3.hash == hash) ; + length = Col [super_c].length ; + + /* prev_c is the column preceding column c in the hash bucket */ + prev_c = super_c ; + + /* === Compare super_c with all columns after it ================ */ + + for (c = Col [super_c].shared4.hash_next ; + c != EMPTY ; c = Col [c].shared4.hash_next) + { + ASSERT (c != super_c) ; + ASSERT (COL_IS_ALIVE (c)) ; + ASSERT (Col [c].shared3.hash == hash) ; + + /* not identical if lengths or scores are different */ + if (Col [c].length != length || + Col [c].shared2.score != Col [super_c].shared2.score) + { + prev_c = c ; + continue ; + } + + /* compare the two columns */ + cp1 = &A [Col [super_c].start] ; + cp2 = &A [Col [c].start] ; + + for (i = 0 ; i < length ; i++) + { + /* the columns are "clean" (no dead rows) */ + ASSERT (ROW_IS_ALIVE (*cp1)) ; + ASSERT (ROW_IS_ALIVE (*cp2)) ; + /* row indices will same order for both supercols, */ + /* no gather scatter nessasary */ + if (*cp1++ != *cp2++) + { + break ; + } + } + + /* the two columns are different if the for-loop "broke" */ + if (i != length) + { + prev_c = c ; + continue ; + } + + /* === Got it! two columns are identical =================== */ + + ASSERT (Col [c].shared2.score == Col [super_c].shared2.score) ; + + Col [super_c].shared1.thickness += Col [c].shared1.thickness ; + Col [c].shared1.parent = super_c ; + KILL_NON_PRINCIPAL_COL (c) ; + /* order c later, in order_children() */ + Col [c].shared2.order = EMPTY ; + /* remove c from hash bucket */ + Col [prev_c].shared4.hash_next = Col [c].shared4.hash_next ; + } + } + + /* === Empty this hash bucket ======================================= */ + + if (head_column > EMPTY) + { + /* corresponding degree list "hash" is not empty */ + Col [head_column].shared3.headhash = EMPTY ; + } + else + { + /* corresponding degree list "hash" is empty */ + head [hash] = EMPTY ; + } + } +} + + +/* ========================================================================== */ +/* === garbage_collection =================================================== */ +/* ========================================================================== */ + +/* + Defragments and compacts columns and rows in the workspace A. Used when + all avaliable memory has been used while performing row merging. Returns + the index of the first free position in A, after garbage collection. The + time taken by this routine is linear is the size of the array A, which is + itself linear in the number of nonzeros in the input matrix. + Not user-callable. +*/ + +PRIVATE Int garbage_collection /* returns the new value of pfree */ +( + /* === Parameters ======================================================= */ + + Int n_row, /* number of rows */ + Int n_col, /* number of columns */ + Colamd_Row Row [], /* row info */ + Colamd_Col Col [], /* column info */ + Int A [], /* A [0 ... Alen-1] holds the matrix */ + Int *pfree /* &A [0] ... pfree is in use */ +) +{ + /* === Local variables ================================================== */ + + Int *psrc ; /* source pointer */ + Int *pdest ; /* destination pointer */ + Int j ; /* counter */ + Int r ; /* a row index */ + Int c ; /* a column index */ + Int length ; /* length of a row or column */ + +#ifndef NDEBUG + Int debug_rows ; + DEBUG2 (("Defrag..\n")) ; + for (psrc = &A[0] ; psrc < pfree ; psrc++) ASSERT (*psrc >= 0) ; + debug_rows = 0 ; +#endif /* NDEBUG */ + + /* === Defragment the columns =========================================== */ + + pdest = &A[0] ; + for (c = 0 ; c < n_col ; c++) + { + if (COL_IS_ALIVE (c)) + { + psrc = &A [Col [c].start] ; + + /* move and compact the column */ + ASSERT (pdest <= psrc) ; + Col [c].start = (Int) (pdest - &A [0]) ; + length = Col [c].length ; + for (j = 0 ; j < length ; j++) + { + r = *psrc++ ; + if (ROW_IS_ALIVE (r)) + { + *pdest++ = r ; + } + } + Col [c].length = (Int) (pdest - &A [Col [c].start]) ; + } + } + + /* === Prepare to defragment the rows =================================== */ + + for (r = 0 ; r < n_row ; r++) + { + if (ROW_IS_DEAD (r) || (Row [r].length == 0)) + { + /* This row is already dead, or is of zero length. Cannot compact + * a row of zero length, so kill it. NOTE: in the current version, + * there are no zero-length live rows. Kill the row (for the first + * time, or again) just to be safe. */ + KILL_ROW (r) ; + } + else + { + /* save first column index in Row [r].shared2.first_column */ + psrc = &A [Row [r].start] ; + Row [r].shared2.first_column = *psrc ; + ASSERT (ROW_IS_ALIVE (r)) ; + /* flag the start of the row with the one's complement of row */ + *psrc = ONES_COMPLEMENT (r) ; +#ifndef NDEBUG + debug_rows++ ; +#endif /* NDEBUG */ + } + } + + /* === Defragment the rows ============================================== */ + + psrc = pdest ; + while (psrc < pfree) + { + /* find a negative number ... the start of a row */ + if (*psrc++ < 0) + { + psrc-- ; + /* get the row index */ + r = ONES_COMPLEMENT (*psrc) ; + ASSERT (r >= 0 && r < n_row) ; + /* restore first column index */ + *psrc = Row [r].shared2.first_column ; + ASSERT (ROW_IS_ALIVE (r)) ; + ASSERT (Row [r].length > 0) ; + /* move and compact the row */ + ASSERT (pdest <= psrc) ; + Row [r].start = (Int) (pdest - &A [0]) ; + length = Row [r].length ; + for (j = 0 ; j < length ; j++) + { + c = *psrc++ ; + if (COL_IS_ALIVE (c)) + { + *pdest++ = c ; + } + } + Row [r].length = (Int) (pdest - &A [Row [r].start]) ; + ASSERT (Row [r].length > 0) ; +#ifndef NDEBUG + debug_rows-- ; +#endif /* NDEBUG */ + } + } + /* ensure we found all the rows */ + ASSERT (debug_rows == 0) ; + + /* === Return the new value of pfree ==================================== */ + + return ((Int) (pdest - &A [0])) ; +} + + +/* ========================================================================== */ +/* === clear_mark =========================================================== */ +/* ========================================================================== */ + +/* + Clears the Row [].shared2.mark array, and returns the new tag_mark. + Return value is the new tag_mark. Not user-callable. +*/ + +PRIVATE Int clear_mark /* return the new value for tag_mark */ +( + /* === Parameters ======================================================= */ + + Int tag_mark, /* new value of tag_mark */ + Int max_mark, /* max allowed value of tag_mark */ + + Int n_row, /* number of rows in A */ + Colamd_Row Row [] /* Row [0 ... n_row-1].shared2.mark is set to zero */ +) +{ + /* === Local variables ================================================== */ + + Int r ; + + if (tag_mark <= 0 || tag_mark >= max_mark) + { + for (r = 0 ; r < n_row ; r++) + { + if (ROW_IS_ALIVE (r)) + { + Row [r].shared2.mark = 0 ; + } + } + tag_mark = 1 ; + } + + return (tag_mark) ; +} + + +/* ========================================================================== */ +/* === print_report ========================================================= */ +/* ========================================================================== */ + +PRIVATE void print_report +( + char *method, + Int stats [COLAMD_STATS] +) +{ + + Int i1, i2, i3 ; + + PRINTF (("\n%s version %d.%d, %s: ", method, + COLAMD_MAIN_VERSION, COLAMD_SUB_VERSION, COLAMD_DATE)) ; + + if (!stats) + { + PRINTF (("No statistics available.\n")) ; + return ; + } + + i1 = stats [COLAMD_INFO1] ; + i2 = stats [COLAMD_INFO2] ; + i3 = stats [COLAMD_INFO3] ; + + if (stats [COLAMD_STATUS] >= 0) + { + PRINTF (("OK. ")) ; + } + else + { + PRINTF (("ERROR. ")) ; + } + + switch (stats [COLAMD_STATUS]) + { + + case COLAMD_OK_BUT_JUMBLED: + + PRINTF(("Matrix has unsorted or duplicate row indices.\n")) ; + + PRINTF(("%s: number of duplicate or out-of-order row indices: %d\n", + method, i3)) ; + + PRINTF(("%s: last seen duplicate or out-of-order row index: %d\n", + method, INDEX (i2))) ; + + PRINTF(("%s: last seen in column: %d", + method, INDEX (i1))) ; + + /* no break - fall through to next case instead */ + + case COLAMD_OK: + + PRINTF(("\n")) ; + + PRINTF(("%s: number of dense or empty rows ignored: %d\n", + method, stats [COLAMD_DENSE_ROW])) ; + + PRINTF(("%s: number of dense or empty columns ignored: %d\n", + method, stats [COLAMD_DENSE_COL])) ; + + PRINTF(("%s: number of garbage collections performed: %d\n", + method, stats [COLAMD_DEFRAG_COUNT])) ; + break ; + + case COLAMD_ERROR_A_not_present: + + PRINTF(("Array A (row indices of matrix) not present.\n")) ; + break ; + + case COLAMD_ERROR_p_not_present: + + PRINTF(("Array p (column pointers for matrix) not present.\n")) ; + break ; + + case COLAMD_ERROR_nrow_negative: + + PRINTF(("Invalid number of rows (%d).\n", i1)) ; + break ; + + case COLAMD_ERROR_ncol_negative: + + PRINTF(("Invalid number of columns (%d).\n", i1)) ; + break ; + + case COLAMD_ERROR_nnz_negative: + + PRINTF(("Invalid number of nonzero entries (%d).\n", i1)) ; + break ; + + case COLAMD_ERROR_p0_nonzero: + + PRINTF(("Invalid column pointer, p [0] = %d, must be zero.\n", i1)); + break ; + + case COLAMD_ERROR_A_too_small: + + PRINTF(("Array A too small.\n")) ; + PRINTF((" Need Alen >= %d, but given only Alen = %d.\n", + i1, i2)) ; + break ; + + case COLAMD_ERROR_col_length_negative: + + PRINTF + (("Column %d has a negative number of nonzero entries (%d).\n", + INDEX (i1), i2)) ; + break ; + + case COLAMD_ERROR_row_index_out_of_bounds: + + PRINTF + (("Row index (row %d) out of bounds (%d to %d) in column %d.\n", + INDEX (i2), INDEX (0), INDEX (i3-1), INDEX (i1))) ; + break ; + + case COLAMD_ERROR_out_of_memory: + + PRINTF(("Out of memory.\n")) ; + break ; + + /* v2.4: internal-error case deleted */ + } +} + + + + +/* ========================================================================== */ +/* === colamd debugging routines ============================================ */ +/* ========================================================================== */ + +/* When debugging is disabled, the remainder of this file is ignored. */ + +#ifndef NDEBUG + + +/* ========================================================================== */ +/* === debug_structures ===================================================== */ +/* ========================================================================== */ + +/* + At this point, all empty rows and columns are dead. All live columns + are "clean" (containing no dead rows) and simplicial (no supercolumns + yet). Rows may contain dead columns, but all live rows contain at + least one live column. +*/ + +PRIVATE void debug_structures +( + /* === Parameters ======================================================= */ + + Int n_row, + Int n_col, + Colamd_Row Row [], + Colamd_Col Col [], + Int A [], + Int n_col2 +) +{ + /* === Local variables ================================================== */ + + Int i ; + Int c ; + Int *cp ; + Int *cp_end ; + Int len ; + Int score ; + Int r ; + Int *rp ; + Int *rp_end ; + Int deg ; + + /* === Check A, Row, and Col ============================================ */ + + for (c = 0 ; c < n_col ; c++) + { + if (COL_IS_ALIVE (c)) + { + len = Col [c].length ; + score = Col [c].shared2.score ; + DEBUG4 (("initial live col %5d %5d %5d\n", c, len, score)) ; + ASSERT (len > 0) ; + ASSERT (score >= 0) ; + ASSERT (Col [c].shared1.thickness == 1) ; + cp = &A [Col [c].start] ; + cp_end = cp + len ; + while (cp < cp_end) + { + r = *cp++ ; + ASSERT (ROW_IS_ALIVE (r)) ; + } + } + else + { + i = Col [c].shared2.order ; + ASSERT (i >= n_col2 && i < n_col) ; + } + } + + for (r = 0 ; r < n_row ; r++) + { + if (ROW_IS_ALIVE (r)) + { + i = 0 ; + len = Row [r].length ; + deg = Row [r].shared1.degree ; + ASSERT (len > 0) ; + ASSERT (deg > 0) ; + rp = &A [Row [r].start] ; + rp_end = rp + len ; + while (rp < rp_end) + { + c = *rp++ ; + if (COL_IS_ALIVE (c)) + { + i++ ; + } + } + ASSERT (i > 0) ; + } + } +} + + +/* ========================================================================== */ +/* === debug_deg_lists ====================================================== */ +/* ========================================================================== */ + +/* + Prints the contents of the degree lists. Counts the number of columns + in the degree list and compares it to the total it should have. Also + checks the row degrees. +*/ + +PRIVATE void debug_deg_lists +( + /* === Parameters ======================================================= */ + + Int n_row, + Int n_col, + Colamd_Row Row [], + Colamd_Col Col [], + Int head [], + Int min_score, + Int should, + Int max_deg +) +{ + /* === Local variables ================================================== */ + + Int deg ; + Int col ; + Int have ; + Int row ; + + /* === Check the degree lists =========================================== */ + + if (n_col > 10000 && colamd_debug <= 0) + { + return ; + } + have = 0 ; + DEBUG4 (("Degree lists: %d\n", min_score)) ; + for (deg = 0 ; deg <= n_col ; deg++) + { + col = head [deg] ; + if (col == EMPTY) + { + continue ; + } + DEBUG4 (("%d:", deg)) ; + while (col != EMPTY) + { + DEBUG4 ((" %d", col)) ; + have += Col [col].shared1.thickness ; + ASSERT (COL_IS_ALIVE (col)) ; + col = Col [col].shared4.degree_next ; + } + DEBUG4 (("\n")) ; + } + DEBUG4 (("should %d have %d\n", should, have)) ; + ASSERT (should == have) ; + + /* === Check the row degrees ============================================ */ + + if (n_row > 10000 && colamd_debug <= 0) + { + return ; + } + for (row = 0 ; row < n_row ; row++) + { + if (ROW_IS_ALIVE (row)) + { + ASSERT (Row [row].shared1.degree <= max_deg) ; + } + } +} + + +/* ========================================================================== */ +/* === debug_mark =========================================================== */ +/* ========================================================================== */ + +/* + Ensures that the tag_mark is less that the maximum and also ensures that + each entry in the mark array is less than the tag mark. +*/ + +PRIVATE void debug_mark +( + /* === Parameters ======================================================= */ + + Int n_row, + Colamd_Row Row [], + Int tag_mark, + Int max_mark +) +{ + /* === Local variables ================================================== */ + + Int r ; + + /* === Check the Row marks ============================================== */ + + ASSERT (tag_mark > 0 && tag_mark <= max_mark) ; + if (n_row > 10000 && colamd_debug <= 0) + { + return ; + } + for (r = 0 ; r < n_row ; r++) + { + ASSERT (Row [r].shared2.mark < tag_mark) ; + } +} + + +/* ========================================================================== */ +/* === debug_matrix ========================================================= */ +/* ========================================================================== */ + +/* + Prints out the contents of the columns and the rows. +*/ + +PRIVATE void debug_matrix +( + /* === Parameters ======================================================= */ + + Int n_row, + Int n_col, + Colamd_Row Row [], + Colamd_Col Col [], + Int A [] +) +{ + /* === Local variables ================================================== */ + + Int r ; + Int c ; + Int *rp ; + Int *rp_end ; + Int *cp ; + Int *cp_end ; + + /* === Dump the rows and columns of the matrix ========================== */ + + if (colamd_debug < 3) + { + return ; + } + DEBUG3 (("DUMP MATRIX:\n")) ; + for (r = 0 ; r < n_row ; r++) + { + DEBUG3 (("Row %d alive? %d\n", r, ROW_IS_ALIVE (r))) ; + if (ROW_IS_DEAD (r)) + { + continue ; + } + DEBUG3 (("start %d length %d degree %d\n", + Row [r].start, Row [r].length, Row [r].shared1.degree)) ; + rp = &A [Row [r].start] ; + rp_end = rp + Row [r].length ; + while (rp < rp_end) + { + c = *rp++ ; + DEBUG4 ((" %d col %d\n", COL_IS_ALIVE (c), c)) ; + } + } + + for (c = 0 ; c < n_col ; c++) + { + DEBUG3 (("Col %d alive? %d\n", c, COL_IS_ALIVE (c))) ; + if (COL_IS_DEAD (c)) + { + continue ; + } + DEBUG3 (("start %d length %d shared1 %d shared2 %d\n", + Col [c].start, Col [c].length, + Col [c].shared1.thickness, Col [c].shared2.score)) ; + cp = &A [Col [c].start] ; + cp_end = cp + Col [c].length ; + while (cp < cp_end) + { + r = *cp++ ; + DEBUG4 ((" %d row %d\n", ROW_IS_ALIVE (r), r)) ; + } + } +} + +PRIVATE void colamd_get_debug +( + char *method +) +{ + FILE *f ; + colamd_debug = 0 ; /* no debug printing */ + f = fopen ("debug", "r") ; + if (f == (FILE *) NULL) + { + colamd_debug = 0 ; + } + else + { + fscanf (f, "%d", &colamd_debug) ; + fclose (f) ; + } + DEBUG0 (("%s: debug version, D = %d (THIS WILL BE SLOW!)\n", + method, colamd_debug)) ; +} + +#endif /* NDEBUG */ diff --git a/src/COLAMD/Source/colamd_global.c b/src/COLAMD/Source/colamd_global.c new file mode 100644 index 0000000..88a2aed --- /dev/null +++ b/src/COLAMD/Source/colamd_global.c @@ -0,0 +1,24 @@ +/* ========================================================================== */ +/* === colamd_global.c ====================================================== */ +/* ========================================================================== */ + +/* ---------------------------------------------------------------------------- + * COLAMD, Copyright (C) 2007, Timothy A. Davis. + * See License.txt for the Version 2.1 of the GNU Lesser General Public License + * http://www.suitesparse.com + * -------------------------------------------------------------------------- */ + +/* Global variables for COLAMD */ + +#ifndef NPRINT +#ifdef MATLAB_MEX_FILE +#include "mex.h" +int (*colamd_printf) (const char *, ...) = mexPrintf ; +#else +#include +int (*colamd_printf) (const char *, ...) = printf ; +#endif +#else +int (*colamd_printf) (const char *, ...) = ((void *) 0) ; +#endif + diff --git a/src/DensityGrid.cpp b/src/DensityGrid.cpp new file mode 100644 index 0000000..466f5de --- /dev/null +++ b/src/DensityGrid.cpp @@ -0,0 +1,281 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains the member definitions of the DensityGrid.h class +// This code is modified from the original code by B.N. Wylie + +#include "drl_Node.h" +#include "DensityGrid.h" +#include "igraph_error.h" + +#include +#include + +using namespace std; + +#define GET_BIN(y, x) (Bins[y*GRID_SIZE+x]) + +namespace drl { + +//******************************************************* +// Density Grid Destructor -- deallocates memory used +// for Density matrix, fall_off matrix, and node deque. + +DensityGrid::~DensityGrid () { + delete[] Density; + delete[] fall_off; + delete[] Bins; +} + +/********************************************* +* Function: Density_Grid::Reset * +* Description: Reset the density grid * +*********************************************/ +// changed from reset to init since we will only +// call this once in the parallel version of layout + +void DensityGrid::Init() { + + try { + Density = new float[GRID_SIZE][GRID_SIZE]; + fall_off = new float[RADIUS * 2 + 1][RADIUS * 2 + 1]; + Bins = new deque[GRID_SIZE * GRID_SIZE]; + } catch (bad_alloc errora) { + // cout << "Error: Out of memory! Program stopped." << endl; +#ifdef MUSE_MPI + MPI_Abort ( MPI_COMM_WORLD, 1 ); +#else + igraph_error("DrL is out of memory", __FILE__, __LINE__, + IGRAPH_ENOMEM); + return; +#endif + } + + // Clear Grid + int i; + for (i = 0; i < GRID_SIZE; i++) + for (int j = 0; j < GRID_SIZE; j++) { + Density[i][j] = 0; + GET_BIN(i, j).erase(GET_BIN(i, j).begin(), GET_BIN(i, j).end()); + } + + // Compute fall off + for (i = -RADIUS; i <= RADIUS; i++) + for (int j = -RADIUS; j <= RADIUS; j++) { + fall_off[i + RADIUS][j + RADIUS] = (float)((RADIUS - fabs((float)i)) / RADIUS) * + (float)((RADIUS - fabs((float)j)) / RADIUS); + } + +} + +/*************************************************** + * Function: DensityGrid::GetDensity * + * Description: Get_Density from density grid * + **************************************************/ +float DensityGrid::GetDensity(float Nx, float Ny, bool fineDensity) { + deque::iterator BI; + int x_grid, y_grid; + float x_dist, y_dist, distance, density = 0; + int boundary = 10; // boundary around plane + + + /* Where to look */ + x_grid = (int)((Nx + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((Ny + HALF_VIEW + .5) * VIEW_TO_GRID); + + // Check for edges of density grid (10000 is arbitrary high density) + if (x_grid > GRID_SIZE - boundary || x_grid < boundary) { + return 10000; + } + if (y_grid > GRID_SIZE - boundary || y_grid < boundary) { + return 10000; + } + + // Fine density? + if (fineDensity) { + + // Go through nearest bins + for (int i = y_grid - 1; i <= y_grid + 1; i++) + for (int j = x_grid - 1; j <= x_grid + 1; j++) { + + // Look through bin and add fine repulsions + for (BI = GET_BIN(i, j).begin(); BI != GET_BIN(i, j).end(); ++BI) { + x_dist = Nx - (BI->x); + y_dist = Ny - (BI->y); + distance = x_dist * x_dist + y_dist * y_dist; + density += 1e-4 / (distance + 1e-50); + } + } + // Course density + } else { + + // Add rough estimate + density = Density[y_grid][x_grid]; + density *= density; + } + + return density; +} + +/// Wrapper functions for the Add and subtract methods +/// Nodes should all be passed by constant ref + +void DensityGrid::Add(Node &n, bool fineDensity) { + if (fineDensity) { + fineAdd(n); + } else { + Add(n); + } +} + +void DensityGrid::Subtract( Node &n, bool first_add, + bool fine_first_add, bool fineDensity) { + if ( fineDensity && !fine_first_add ) { + fineSubtract (n); + } else if ( !first_add ) { + Subtract(n); + } +} + + +/*************************************************** + * Function: DensityGrid::Subtract * + * Description: Subtract a node from density grid * + **************************************************/ +void DensityGrid::Subtract(Node &N) { + int x_grid, y_grid, diam; + float *den_ptr, *fall_ptr; + + /* Where to subtract */ + x_grid = (int)((N.sub_x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.sub_y + HALF_VIEW + .5) * VIEW_TO_GRID); + x_grid -= RADIUS; + y_grid -= RADIUS; + diam = 2 * RADIUS; + + // check to see that we are inside grid + if ( (x_grid >= GRID_SIZE) || (x_grid < 0) || + (y_grid >= GRID_SIZE) || (y_grid < 0) ) { +#ifdef MUSE_MPI + MPI_Abort ( MPI_COMM_WORLD, 1 ); +#else + igraph_error("Exceeded density grid in DrL", __FILE__, + __LINE__, IGRAPH_EDRL); + return; +#endif + } + + /* Subtract density values */ + den_ptr = &Density[y_grid][x_grid]; + fall_ptr = &fall_off[0][0]; + for (int i = 0; i <= diam; i++) { + for (int j = 0; j <= diam; j++) { + *den_ptr++ -= *fall_ptr++; + } + den_ptr += GRID_SIZE - (diam + 1); + } +} + +/*************************************************** + * Function: DensityGrid::Add * + * Description: Add a node to the density grid * + **************************************************/ +void DensityGrid::Add(Node &N) { + + int x_grid, y_grid, diam; + float *den_ptr, *fall_ptr; + + + /* Where to add */ + x_grid = (int)((N.x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.y + HALF_VIEW + .5) * VIEW_TO_GRID); + + N.sub_x = N.x; + N.sub_y = N.y; + + x_grid -= RADIUS; + y_grid -= RADIUS; + diam = 2 * RADIUS; + + // check to see that we are inside grid + if ( (x_grid >= GRID_SIZE) || (x_grid < 0) || + (y_grid >= GRID_SIZE) || (y_grid < 0) ) { +#ifdef MUSE_MPI + MPI_Abort ( MPI_COMM_WORLD, 1 ); +#else + igraph_error("Exceeded density grid in DrL", __FILE__, + __LINE__, IGRAPH_EDRL); + return; +#endif + } + + /* Add density values */ + den_ptr = &Density[y_grid][x_grid]; + fall_ptr = &fall_off[0][0]; + for (int i = 0; i <= diam; i++) { + for (int j = 0; j <= diam; j++) { + *den_ptr++ += *fall_ptr++; + } + den_ptr += GRID_SIZE - (diam + 1); + } + +} + +/*************************************************** + * Function: DensityGrid::fineSubtract * + * Description: Subtract a node from bins * + **************************************************/ +void DensityGrid::fineSubtract(Node &N) { + int x_grid, y_grid; + + /* Where to subtract */ + x_grid = (int)((N.sub_x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.sub_y + HALF_VIEW + .5) * VIEW_TO_GRID); + GET_BIN(y_grid, x_grid).pop_front(); +} + +/*************************************************** + * Function: DensityGrid::fineAdd * + * Description: Add a node to the bins * + **************************************************/ +void DensityGrid::fineAdd(Node &N) { + int x_grid, y_grid; + + /* Where to add */ + x_grid = (int)((N.x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.y + HALF_VIEW + .5) * VIEW_TO_GRID); + N.sub_x = N.x; + N.sub_y = N.y; + GET_BIN(y_grid, x_grid).push_back(N); +} + +} // namespace drl diff --git a/src/DensityGrid.h b/src/DensityGrid.h new file mode 100644 index 0000000..69b8e01 --- /dev/null +++ b/src/DensityGrid.h @@ -0,0 +1,85 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __DENSITY_GRID_H__ +#define __DENSITY_GRID_H__ + + +// Compile time adjustable parameters + +#include "drl_layout.h" +#include "drl_Node.h" +#ifdef MUSE_MPI + #include +#endif + +#include + +namespace drl { + +class DensityGrid { + +public: + + // Methods + void Init(); + void Subtract(Node &n, bool first_add, bool fine_first_add, bool fineDensity); + void Add(Node &n, bool fineDensity ); + float GetDensity(float Nx, float Ny, bool fineDensity); + + // Contructor/Destructor + DensityGrid() {}; + ~DensityGrid(); + +private: + + // Private Members + void Subtract( Node &N ); + void Add( Node &N ); + void fineSubtract( Node &N ); + void fineAdd( Node &N ); + + // new dynamic variables -- SBM + float (*fall_off)[RADIUS * 2 + 1]; + float (*Density)[GRID_SIZE]; + std::deque* Bins; + + // old static variables + //float fall_off[RADIUS*2+1][RADIUS*2+1]; + //float Density[GRID_SIZE][GRID_SIZE]; + //deque Bins[GRID_SIZE][GRID_SIZE]; +}; + +} // namespace drl + +#endif // __DENSITY_GRID_H__ + diff --git a/src/DensityGrid_3d.cpp b/src/DensityGrid_3d.cpp new file mode 100644 index 0000000..cc321f9 --- /dev/null +++ b/src/DensityGrid_3d.cpp @@ -0,0 +1,305 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains the member definitions of the DensityGrid.h class +// This code is modified from the original code by B.N. Wylie + +#include "drl_Node_3d.h" +#include "DensityGrid_3d.h" +#include "igraph_error.h" + +#include +#include + +using namespace std; + +#define GET_BIN(z, y, x) (Bins[(z*GRID_SIZE+y)*GRID_SIZE+x]) + +namespace drl3d { + +//******************************************************* +// Density Grid Destructor -- deallocates memory used +// for Density matrix, fall_off matrix, and node deque. + +DensityGrid::~DensityGrid () { + delete[] Density; + delete[] fall_off; + delete[] Bins; +} + +/********************************************* +* Function: Density_Grid::Reset * +* Description: Reset the density grid * +*********************************************/ +// changed from reset to init since we will only +// call this once in the parallel version of layout + +void DensityGrid::Init() { + + try { + Density = new float[GRID_SIZE][GRID_SIZE][GRID_SIZE]; + fall_off = new float[RADIUS * 2 + 1][RADIUS * 2 + 1][RADIUS * 2 + 1]; + Bins = new deque[GRID_SIZE * GRID_SIZE * GRID_SIZE]; + } catch (bad_alloc errora) { + // cout << "Error: Out of memory! Program stopped." << endl; +#ifdef MUSE_MPI + MPI_Abort ( MPI_COMM_WORLD, 1 ); +#else + igraph_error("DrL is out of memory", __FILE__, __LINE__, + IGRAPH_ENOMEM); + return; +#endif + } + + // Clear Grid + int i; + for (i = 0; i < GRID_SIZE; i++) + for (int j = 0; j < GRID_SIZE; j++) + for (int k = 0; k < GRID_SIZE; k++) { + Density[i][j][k] = 0; + GET_BIN(i, j, k).erase(GET_BIN(i, j, k).begin(), GET_BIN(i, j, k).end()); + } + + // Compute fall off + for (i = -RADIUS; i <= RADIUS; i++) + for (int j = -RADIUS; j <= RADIUS; j++) + for (int k = -RADIUS; k <= RADIUS; k++) { + fall_off[i + RADIUS][j + RADIUS][k + RADIUS] = + (float)((RADIUS - fabs((float)i)) / RADIUS) * + (float)((RADIUS - fabs((float)j)) / RADIUS) * + (float)((RADIUS - fabs((float)k)) / RADIUS); + } + +} + + +/*************************************************** + * Function: DensityGrid::GetDensity * + * Description: Get_Density from density grid * + **************************************************/ +float DensityGrid::GetDensity(float Nx, float Ny, float Nz, bool fineDensity) { + deque::iterator BI; + int x_grid, y_grid, z_grid; + float x_dist, y_dist, z_dist, distance, density = 0; + int boundary = 10; // boundary around plane + + + /* Where to look */ + x_grid = (int)((Nx + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((Ny + HALF_VIEW + .5) * VIEW_TO_GRID); + z_grid = (int)((Nz + HALF_VIEW + .5) * VIEW_TO_GRID); + + // Check for edges of density grid (10000 is arbitrary high density) + if (x_grid > GRID_SIZE - boundary || x_grid < boundary) { + return 10000; + } + if (y_grid > GRID_SIZE - boundary || y_grid < boundary) { + return 10000; + } + if (z_grid > GRID_SIZE - boundary || z_grid < boundary) { + return 10000; + } + + // Fine density? + if (fineDensity) { + + // Go through nearest bins + for (int k = z_grid - 1; k <= z_grid + 1; k++) + for (int i = y_grid - 1; i <= y_grid + 1; i++) + for (int j = x_grid - 1; j <= x_grid + 1; j++) { + + // Look through bin and add fine repulsions + for (BI = GET_BIN(k, i, j).begin(); BI < GET_BIN(k, i, j).end(); ++BI) { + x_dist = Nx - (BI->x); + y_dist = Ny - (BI->y); + z_dist = Nz - (BI->z); + distance = x_dist * x_dist + y_dist * y_dist + z_dist * z_dist; + density += 1e-4 / (distance + 1e-50); + } + } + + // Course density + } else { + + // Add rough estimate + density = Density[z_grid][y_grid][x_grid]; + density *= density; + } + + return density; +} + +/// Wrapper functions for the Add and subtract methods +/// Nodes should all be passed by constant ref + +void DensityGrid::Add(Node &n, bool fineDensity) { + if (fineDensity) { + fineAdd(n); + } else { + Add(n); + } +} + +void DensityGrid::Subtract( Node &n, bool first_add, + bool fine_first_add, bool fineDensity) { + if ( fineDensity && !fine_first_add ) { + fineSubtract (n); + } else if ( !first_add ) { + Subtract(n); + } +} + + +/*************************************************** + * Function: DensityGrid::Subtract * + * Description: Subtract a node from density grid * + **************************************************/ +void DensityGrid::Subtract(Node &N) { + int x_grid, y_grid, z_grid, diam; + float *den_ptr, *fall_ptr; + + /* Where to subtract */ + x_grid = (int)((N.sub_x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.sub_y + HALF_VIEW + .5) * VIEW_TO_GRID); + z_grid = (int)((N.sub_z + HALF_VIEW + .5) * VIEW_TO_GRID); + x_grid -= RADIUS; + y_grid -= RADIUS; + z_grid -= RADIUS; + diam = 2 * RADIUS; + + // check to see that we are inside grid + if ( (x_grid >= GRID_SIZE) || (x_grid < 0) || + (y_grid >= GRID_SIZE) || (y_grid < 0) || + (z_grid >= GRID_SIZE) || (z_grid < 0) ) { +#ifdef MUSE_MPI + MPI_Abort ( MPI_COMM_WORLD, 1 ); +#else + igraph_error("Exceeded density grid in DrL", __FILE__, + __LINE__, IGRAPH_EDRL); + return; +#endif + } + + /* Subtract density values */ + den_ptr = &Density[z_grid][y_grid][x_grid]; + fall_ptr = &fall_off[0][0][0]; + for (int i = 0; i <= diam; i++) { + for (int j = 0; j <= diam; j++) + for (int k = 0; k <= diam; k++) { + *den_ptr++ -= *fall_ptr++; + } + den_ptr += GRID_SIZE - (diam + 1); + } +} + +/*************************************************** + * Function: DensityGrid::Add * + * Description: Add a node to the density grid * + **************************************************/ +void DensityGrid::Add(Node &N) { + + int x_grid, y_grid, z_grid, diam; + float *den_ptr, *fall_ptr; + + + /* Where to add */ + x_grid = (int)((N.x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.y + HALF_VIEW + .5) * VIEW_TO_GRID); + z_grid = (int)((N.z + HALF_VIEW + .5) * VIEW_TO_GRID); + + N.sub_x = N.x; + N.sub_y = N.y; + N.sub_z = N.z; + + x_grid -= RADIUS; + y_grid -= RADIUS; + z_grid -= RADIUS; + diam = 2 * RADIUS; + + // check to see that we are inside grid + if ( (x_grid >= GRID_SIZE) || (x_grid < 0) || + (y_grid >= GRID_SIZE) || (y_grid < 0) || + (z_grid >= GRID_SIZE) || (z_grid < 0) ) { +#ifdef MUSE_MPI + MPI_Abort ( MPI_COMM_WORLD, 1 ); +#else + igraph_error("Exceeded density grid in DrL", __FILE__, + __LINE__, IGRAPH_EDRL); + return; +#endif + } + + /* Add density values */ + den_ptr = &Density[z_grid][y_grid][x_grid]; + fall_ptr = &fall_off[0][0][0]; + for (int i = 0; i <= diam; i++) { + for (int j = 0; j <= diam; j++) + for (int k = 0; k <= diam; k++) { + *den_ptr++ += *fall_ptr++; + } + den_ptr += GRID_SIZE - (diam + 1); + } + +} + +/*************************************************** + * Function: DensityGrid::fineSubtract * + * Description: Subtract a node from bins * + **************************************************/ +void DensityGrid::fineSubtract(Node &N) { + int x_grid, y_grid, z_grid; + + /* Where to subtract */ + x_grid = (int)((N.sub_x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.sub_y + HALF_VIEW + .5) * VIEW_TO_GRID); + z_grid = (int)((N.sub_z + HALF_VIEW + .5) * VIEW_TO_GRID); + GET_BIN(z_grid, y_grid, x_grid).pop_front(); +} + +/*************************************************** + * Function: DensityGrid::fineAdd * + * Description: Add a node to the bins * + **************************************************/ +void DensityGrid::fineAdd(Node &N) { + int x_grid, y_grid, z_grid; + + /* Where to add */ + x_grid = (int)((N.x + HALF_VIEW + .5) * VIEW_TO_GRID); + y_grid = (int)((N.y + HALF_VIEW + .5) * VIEW_TO_GRID); + z_grid = (int)((N.z + HALF_VIEW + .5) * VIEW_TO_GRID); + N.sub_x = N.x; + N.sub_y = N.y; + N.sub_z = N.z; + GET_BIN(z_grid, y_grid, x_grid).push_back(N); +} + +} // namespace drl3d diff --git a/src/DensityGrid_3d.h b/src/DensityGrid_3d.h new file mode 100644 index 0000000..e80fa85 --- /dev/null +++ b/src/DensityGrid_3d.h @@ -0,0 +1,85 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __DENSITY_GRID_H__ +#define __DENSITY_GRID_H__ + + +// Compile time adjustable parameters + +#include "drl_layout_3d.h" +#include "drl_Node_3d.h" +#ifdef MUSE_MPI + #include +#endif + +#include + +namespace drl3d { + +class DensityGrid { + +public: + + // Methods + void Init(); + void Subtract(Node &n, bool first_add, bool fine_first_add, bool fineDensity); + void Add(Node &n, bool fineDensity ); + float GetDensity(float Nx, float Ny, float Nz, bool fineDensity); + + // Contructor/Destructor + DensityGrid() {}; + ~DensityGrid(); + +private: + + // Private Members + void Subtract( Node &N ); + void Add( Node &N ); + void fineSubtract( Node &N ); + void fineAdd( Node &N ); + + // new dynamic variables -- SBM + float (*fall_off)[RADIUS * 2 + 1][RADIUS * 2 + 1]; + float (*Density)[GRID_SIZE][GRID_SIZE]; + std::deque* Bins; + + // old static variables + //float fall_off[RADIUS*2+1][RADIUS*2+1]; + //float Density[GRID_SIZE][GRID_SIZE]; + //deque Bins[GRID_SIZE][GRID_SIZE]; +}; + +} // namespace drl3d + +#endif // __DENSITY_GRID_H__ + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..f9d02d2 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,438 @@ +# This is to make sure that the headers get built before they are included +BUILT_SOURCES = foreign-ncol-parser.h foreign-lgl-parser.h \ + foreign-dl-parser.h foreign-gml-parser.h \ + foreign-pajek-parser.h + +# This is needed to ensure that yacc (or bison) builds the header files +# Unfortunately this is not the default behaviour in MinGW/MSYS +AM_YFLAGS = -d + +lib_LTLIBRARIES = libigraph.la + +include lapack/blas.inc +include lapack/lapack.inc +include lapack/arpack.inc +include plfit/plfit.inc + +F2C = f2c/abort_.c f2c/dolio.c f2c/r_sin.c\ + f2c/dummy.c f2c/dtime_.c f2c/iio.c f2c/r_sinh.c\ + f2c/backspac.c f2c/due.c f2c/ilnw.c f2c/r_sqrt.c\ + f2c/c_abs.c f2c/ef1asc_.c f2c/inquire.c f2c/r_tan.c\ + f2c/c_cos.c f2c/ef1cmc_.c f2c/l_ge.c f2c/r_tanh.c\ + f2c/c_div.c f2c/endfile.c f2c/l_gt.c f2c/rdfmt.c\ + f2c/c_exp.c f2c/erf_.c f2c/l_le.c f2c/rewind.c\ + f2c/c_log.c f2c/erfc_.c f2c/l_lt.c f2c/rsfe.c\ + f2c/c_sin.c f2c/err.c f2c/lbitbits.c f2c/rsli.c\ + f2c/c_sqrt.c f2c/etime_.c f2c/lbitshft.c f2c/rsne.c\ + f2c/cabs.c f2c/exit_.c f2c/lread.c f2c/s_cat.c\ + f2c/close.c f2c/f77_aloc.c f2c/lwrite.c f2c/s_cmp.c\ + f2c/ctype.c f2c/f77vers.c f2c/s_copy.c\ + f2c/d_abs.c f2c/fmt.c f2c/open.c f2c/s_paus.c\ + f2c/d_acos.c f2c/fmtlib.c f2c/pow_ci.c f2c/s_rnge.c\ + f2c/d_asin.c f2c/ftell_.c f2c/pow_dd.c f2c/s_stop.c\ + f2c/d_atan.c f2c/pow_di.c f2c/sfe.c\ + f2c/d_atn2.c f2c/getenv_.c f2c/pow_hh.c f2c/sig_die.c\ + f2c/d_cnjg.c f2c/h_abs.c f2c/pow_ii.c f2c/signal_.c\ + f2c/d_cos.c f2c/h_dim.c f2c/pow_ri.c f2c/signbit.c\ + f2c/d_cosh.c f2c/h_dnnt.c f2c/pow_zi.c f2c/sue.c\ + f2c/d_dim.c f2c/h_indx.c f2c/pow_zz.c f2c/system_.c\ + f2c/d_exp.c f2c/h_len.c f2c/r_abs.c f2c/typesize.c\ + f2c/d_imag.c f2c/h_mod.c f2c/r_acos.c f2c/uio.c\ + f2c/d_int.c f2c/h_nint.c f2c/r_asin.c f2c/uninit.c\ + f2c/d_lg10.c f2c/h_sign.c f2c/r_atan.c f2c/util.c\ + f2c/d_log.c f2c/hl_ge.c f2c/r_atn2.c f2c/wref.c\ + f2c/d_mod.c f2c/hl_gt.c f2c/r_cnjg.c f2c/wrtfmt.c\ + f2c/d_nint.c f2c/hl_le.c f2c/r_cos.c f2c/wsfe.c\ + f2c/d_prod.c f2c/hl_lt.c f2c/r_cosh.c f2c/wsle.c\ + f2c/d_sign.c f2c/i77vers.c f2c/r_dim.c f2c/wsne.c\ + f2c/d_sin.c f2c/i_abs.c f2c/r_exp.c f2c/xwsne.c\ + f2c/d_sinh.c f2c/i_dim.c f2c/r_imag.c f2c/z_abs.c\ + f2c/d_sqrt.c f2c/i_dnnt.c f2c/r_int.c f2c/z_cos.c\ + f2c/d_tan.c f2c/i_indx.c f2c/r_lg10.c f2c/z_div.c\ + f2c/d_tanh.c f2c/i_len.c f2c/r_log.c f2c/z_exp.c\ + f2c/derf_.c f2c/i_mod.c f2c/r_mod.c f2c/z_log.c\ + f2c/derfc_.c f2c/i_nint.c f2c/r_nint.c f2c/z_sin.c\ + f2c/dfe.c f2c/i_sign.c f2c/r_sign.c f2c/z_sqrt.c + +# We also have to pack f2c/arithchk.c in the distribution in case the +# user wants to compile it using the internal f2c. f2c/arithchk.c is +# not linked into libf2c.la (hence we cannot add it to libf2c_la_SOURCES) +# but is needed to build f2c/arith.h +EXTRA_DIST = f2c/arithchk.c + +if INTERNAL_F2C + libf2c_la_SOURCES = f2c.h f2c/fio.h f2c/fmt.h f2c/sysdep1.h\ + f2c/sysdep1.h0 f2c/lio.h f2c/fp.h f2c/signal1.h\ + f2c/signal1.h0 $(F2C) + libf2c_la_CFLAGS = -DSkip_f2c_Undefs -I. -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)/src/f2c $(WARNING_CFLAGS) + f2c/arith.h: f2c/arithchk.c + $(CC) $(CFLAGS) -DNO_FPINIT $(top_srcdir)/src/f2c/arithchk.c -lm -o f2c/arith || \ + $(CC) -DNO_LONG_LONG $(CFLAGS) -DNO_FPINIT $(top_srcdir)/src/f2c/arithchk.c \ + $(WARNING_CFLAGS) -lm -o f2c/arith + f2c/arith > f2c/arith.h + f2c/sysdep1.h: f2c/sysdep1.h0 + cp f2c/sysdep1.h0 f2c/sysdep1.h + f2c/signal1.h: f2c/signal1.h0 + cp f2c/signal1.h0 f2c/signal1.h + F2C_LIB = libf2c.la + + BUILT_SOURCES += f2c/arith.h +endif + +if INTERNAL_BLAS + libblas_la_SOURCES = f2c.h $(BLAS) + libblas_la_CFLAGS = -I. -I$(top_srcdir)/include -I$(top_builddir)/include $(WARNING_CFLAGS) + BLAS_LIB = libblas.la +endif + +if INTERNAL_LAPACK + liblapack_la_SOURCES = f2c.h $(LAPACK) + liblapack_la_CFLAGS = -I. -I$(top_srcdir)/include -I$(top_builddir)/include $(WARNING_CFLAGS) + libdlamch_la_SOURCES = lapack/dlamch.c + libdlamch_la_CFLAGS = $(FLOATSTORE) -I. -I$(top_srcdir)/include -I$(top_builddir)/include $(WARNING_CFLAGS) + LAPACK_LIB = liblapack.la libdlamch.la +endif + +if INTERNAL_ARPACK + libarpack_la_SOURCES = f2c.h $(ARPACK) + libarpack_la_CFLAGS = -I. -I$(top_srcdir)/include -I$(top_builddir)/include $(WARNING_CFLAGS) + ARPACK_LIB = libarpack.la +endif + +include ../optional/glpk/glpk.inc + +if INTERNAL_GLPK + libglpk_la_SOURCES = $(GLPK) + libglpk_la_CFLAGS = -I$(top_srcdir)/optional/glpk + libglpk_la_CPPFLAGS = -I$(top_srcdir)/include + GLPK_LIB = libglpk.la +endif + +include prpack/prpack.inc + +libprpack_la_SOURCES = $(PRPACK) +libprpack_la_CFLAGS = -I$(top_srcdir)/include -DPRPACK_IGRAPH_SUPPORT +libprpack_la_CPPFLAGS = -I$(top_srcdir)/include -DPRPACK_IGRAPH_SUPPORT +PRPACK_LIB = libprpack.la + +libplfit_la_SOURCES = $(PLFIT) +PLFIT_LIB = libplfit.la + +noinst_LTLIBRARIES = $(F2C_LIB) $(BLAS_LIB) $(LAPACK_LIB) $(ARPACK_LIB) \ + $(GLPK_LIB) $(PLFIT_LIB) $(PRPACK_LIB) + +CS = cs/cs_add.c cs/cs_happly.c cs/cs_pvec.c \ + cs/cs_amd.c cs/cs_house.c cs/cs_qr.c \ + cs/cs_chol.c cs/cs_ipvec.c cs/cs_qrsol.c \ + cs/cs_cholsol.c cs/cs_leaf.c cs/cs_randperm.c \ + cs/cs_compress.c cs/cs_load.c cs/cs_reach.c \ + cs/cs_counts.c cs/cs_lsolve.c cs/cs_scatter.c \ + cs/cs_cumsum.c cs/cs_ltsolve.c cs/cs_scc.c \ + cs/cs_dfs.c cs/cs_lu.c cs/cs_schol.c \ + cs/cs_dmperm.c cs/cs_lusol.c cs/cs_spsolve.c \ + cs/cs_droptol.c cs/cs_malloc.c cs/cs_sqr.c \ + cs/cs_dropzeros.c cs/cs_maxtrans.c cs/cs_symperm.c \ + cs/cs_dupl.c cs/cs_multiply.c cs/cs_tdfs.c \ + cs/cs_entry.c cs/cs_norm.c cs/cs_transpose.c \ + cs/cs_ereach.c cs/cs_permute.c cs/cs_updown.c \ + cs/cs_etree.c cs/cs_pinv.c cs/cs_usolve.c \ + cs/cs_fkeep.c cs/cs_post.c cs/cs_util.c \ + cs/cs_gaxpy.c cs/cs_print.c cs/cs_utsolve.c \ + cs/cs.h cs/UFconfig.h + +CHOLMOD = CHOLMOD/Check/cholmod_check.c \ + CHOLMOD/Check/cholmod_read.c \ + CHOLMOD/Check/cholmod_write.c \ + CHOLMOD/Cholesky/cholmod_amd.c \ + CHOLMOD/Cholesky/cholmod_analyze.c \ + CHOLMOD/Cholesky/cholmod_colamd.c \ + CHOLMOD/Cholesky/cholmod_etree.c \ + CHOLMOD/Cholesky/cholmod_factorize.c \ + CHOLMOD/Cholesky/cholmod_postorder.c \ + CHOLMOD/Cholesky/cholmod_rcond.c \ + CHOLMOD/Cholesky/cholmod_resymbol.c \ + CHOLMOD/Cholesky/cholmod_rowcolcounts.c \ + CHOLMOD/Cholesky/cholmod_rowfac.c \ + CHOLMOD/Cholesky/cholmod_solve.c \ + CHOLMOD/Cholesky/cholmod_spsolve.c \ + CHOLMOD/Core/cholmod_aat.c \ + CHOLMOD/Core/cholmod_add.c \ + CHOLMOD/Core/cholmod_band.c \ + CHOLMOD/Core/cholmod_change_factor.c \ + CHOLMOD/Core/cholmod_common.c \ + CHOLMOD/Core/cholmod_complex.c \ + CHOLMOD/Core/cholmod_copy.c \ + CHOLMOD/Core/cholmod_dense.c \ + CHOLMOD/Core/cholmod_error.c \ + CHOLMOD/Core/cholmod_factor.c \ + CHOLMOD/Core/cholmod_memory.c \ + CHOLMOD/Core/cholmod_sparse.c \ + CHOLMOD/Core/cholmod_transpose.c \ + CHOLMOD/Core/cholmod_triplet.c \ + CHOLMOD/Core/cholmod_version.c \ + CHOLMOD/MatrixOps/cholmod_drop.c \ + CHOLMOD/MatrixOps/cholmod_horzcat.c \ + CHOLMOD/MatrixOps/cholmod_norm.c \ + CHOLMOD/MatrixOps/cholmod_scale.c \ + CHOLMOD/MatrixOps/cholmod_sdmult.c \ + CHOLMOD/MatrixOps/cholmod_ssmult.c \ + CHOLMOD/MatrixOps/cholmod_submatrix.c \ + CHOLMOD/MatrixOps/cholmod_symmetry.c \ + CHOLMOD/MatrixOps/cholmod_vertcat.c \ + CHOLMOD/Modify/cholmod_rowadd.c \ + CHOLMOD/Modify/cholmod_rowdel.c \ + CHOLMOD/Modify/cholmod_updown.c \ + CHOLMOD/Partition/cholmod_camd.c \ + CHOLMOD/Partition/cholmod_ccolamd.c \ + CHOLMOD/Partition/cholmod_csymamd.c \ + CHOLMOD/Partition/cholmod_metis.c \ + CHOLMOD/Partition/cholmod_nesdis.c \ + CHOLMOD/Supernodal/cholmod_super_numeric.c \ + CHOLMOD/Supernodal/cholmod_super_solve.c \ + CHOLMOD/Supernodal/cholmod_super_symbolic.c \ + CHOLMOD/Include/cholmod.h \ + CHOLMOD/Include/cholmod_blas.h \ + CHOLMOD/Include/cholmod_camd.h \ + CHOLMOD/Include/cholmod_check.h \ + CHOLMOD/Include/cholmod_cholesky.h \ + CHOLMOD/Include/cholmod_complexity.h \ + CHOLMOD/Include/cholmod_config.h \ + CHOLMOD/Include/cholmod_core.h \ + CHOLMOD/Include/cholmod_internal.h \ + CHOLMOD/Include/cholmod_io64.h \ + CHOLMOD/Include/cholmod_matrixops.h \ + CHOLMOD/Include/cholmod_modify.h \ + CHOLMOD/Include/cholmod_partition.h \ + CHOLMOD/Include/cholmod_supernodal.h \ + CHOLMOD/Include/cholmod_template.h + +EXTRA_DIST += CHOLMOD/Cholesky/t_cholmod_lsolve.c \ + CHOLMOD/Cholesky/t_cholmod_ltsolve.c \ + CHOLMOD/Cholesky/t_cholmod_rowfac.c \ + CHOLMOD/Cholesky/t_cholmod_solve.c \ + CHOLMOD/Core/t_cholmod_change_factor.c \ + CHOLMOD/Core/t_cholmod_dense.c \ + CHOLMOD/Core/t_cholmod_transpose.c \ + CHOLMOD/Core/t_cholmod_triplet.c \ + CHOLMOD/MatrixOps/t_cholmod_sdmult.c \ + CHOLMOD/Modify/t_cholmod_updown.c \ + CHOLMOD/Modify/t_cholmod_updown_numkr.c \ + CHOLMOD/Supernodal/t_cholmod_gpu.c \ + CHOLMOD/Supernodal/t_cholmod_super_numeric.c \ + CHOLMOD/Supernodal/t_cholmod_super_solve.c + + +AMD = AMD/Source/amd_1.c \ + AMD/Source/amd_2.c \ + AMD/Source/amd_aat.c \ + AMD/Source/amd_control.c \ + AMD/Source/amd_defaults.c \ + AMD/Source/amd_dump.c \ + AMD/Source/amd_global.c \ + AMD/Source/amd_info.c \ + AMD/Source/amd_order.c \ + AMD/Source/amd_post_tree.c \ + AMD/Source/amd_postorder.c \ + AMD/Source/amd_preprocess.c \ + AMD/Source/amd_valid.c \ + AMD/Include/amd.h \ + AMD/Include/amd_internal.h + +COLAMD = COLAMD/Source/colamd.c \ + COLAMD/Source/colamd_global.c \ + COLAMD/Include/colamd.h + +SPCONFIG = SuiteSparse_config/SuiteSparse_config.c \ + SuiteSparse_config/SuiteSparse_config.h + +HEADERS_PRIVATE = atlas-edges.h \ + bliss/bignum.hh bliss/defs.hh \ + bliss/graph.hh bliss/uintseqhash.hh \ + bliss/heap.hh bliss/kqueue.hh \ + bliss/kstack.hh bliss/orbit.hh \ + bliss/partition.hh bliss/utils.hh \ + NetDataTypes.h NetRoutines.h \ + pottsmodel_2.h \ + igraph_gml_tree.h \ + walktrap_graph.h walktrap_communities.h \ + walktrap_heap.h \ + infomap_Greedy.h infomap_Node.h infomap_Greedy.h infomap_FlowGraph.h \ + igraph_math.h \ + drl_layout.h drl_parse.h drl_graph.h \ + drl_graph_3d.h drl_layout_3d.h \ + drl_Node.h drl_Node_3d.h \ + DensityGrid.h DensityGrid_3d.h \ + igraph_flow_internal.h \ + vector.pmt matrix.pmt stack.pmt dqueue.pmt heap.pmt array.pmt \ + igraph_types_internal.h \ + foreign-dl-header.h bignum.h bigint.h \ + gengraph_box_list.h gengraph_definitions.h \ + gengraph_degree_sequence.h gengraph_graph_molloy_hash.h \ + gengraph_graph_molloy_optimized.h \ + gengraph_hash.h gengraph_header.h gengraph_powerlaw.h \ + gengraph_qsort.h gengraph_random.h gengraph_vertex_cover.h \ + igraph_blas_internal.h igraph_arpack_internal.h \ + igraph_lapack_internal.h igraph_glpk_support.h \ + igraph_marked_queue.h igraph_estack.h \ + hrg_dendro.h hrg_graph.h hrg_rbtree.h hrg_splittree_eq.h \ + hrg_graph_simp.h foreign-gml-header.h \ + foreign-ncol-header.h foreign-lgl-header.h \ + foreign-pajek-header.h igraph_interrupt_internal.h \ + scg_headers.h igraph_hacks_internal.h triangles_template.h \ + triangles_template1.h maximal_cliques_template.h prpack.h \ + igraph_cliquer.h cliquer/graph.h cliquer/cliquer.h cliquer/misc.h \ + cliquer/cliquerconf.h cliquer/reorder.h cliquer/set.h \ + structural_properties_internal.h + +HEADERS_PUBLIC =../include/igraph.h ../include/igraph_memory.h \ + ../include/igraph_random.h ../include/igraph_types.h \ + ../include/igraph_vector.h ../include/igraph_matrix.h \ + ../include/igraph_array.h ../include/igraph_dqueue.h \ + ../include/igraph_stack.h ../include/igraph_heap.h \ + ../include/igraph_arpack.h \ + ../include/igraph_attributes.h ../include/igraph_error.h \ + ../include/igraph_pmt.h ../include/igraph_pmt_off.h \ + ../include/igraph_adjlist.h ../include/igraph_iterators.h \ + ../include/igraph_bipartite.h ../include/igraph_layout.h \ + ../include/igraph_centrality.h ../include/igraph_motifs.h \ + ../include/igraph_cliques.h ../include/igraph_neighborhood.h \ + ../include/igraph_cocitation.h ../include/igraph_nongraph.h \ + ../include/igraph_community.h ../include/igraph_operators.h \ + ../include/igraph_components.h ../include/igraph_paths.h \ + ../include/igraph_constructors.h ../include/igraph_progress.h \ + ../include/igraph_conversion.h \ + ../include/igraph_datatype.h ../include/igraph_structural.h\ + ../include/igraph_flow.h ../include/igraph_topology.h \ + ../include/igraph_foreign.h ../include/igraph_transitivity.h \ + ../include/igraph_games.h ../include/igraph_visitor.h \ + ../include/igraph_interface.h ../include/igraph_constants.h \ + ../include/igraph_vector_pmt.h ../include/igraph_matrix_pmt.h\ + ../include/igraph_array_pmt.h ../include/igraph_dqueue_pmt.h\ + ../include/igraph_stack_pmt.h ../include/igraph_heap_pmt.h \ + ../include/igraph_vector_ptr.h ../include/igraph_spmatrix.h \ + ../include/igraph_strvector.h ../include/igraph_psumtree.h \ + ../include/igraph_sparsemat.h ../include/igraph_mixing.h \ + ../include/igraph_version.h ../include/igraph_blas.h \ + ../include/igraph_separators.h ../include/igraph_cohesive_blocks.h \ + ../include/igraph_lapack.h ../include/igraph_complex.h \ + ../include/igraph_eigen.h ../include/igraph_statusbar.h \ + ../include/igraph_hrg.h ../include/igraph_microscopic_update.h \ + ../include/igraph_interrupt.h ../include/igraph_threading.h \ + ../include/igraph_scg.h ../include/igraph_qsort.h \ + ../include/igraph_matching.h ../include/igraph_embedding.h \ + ../include/igraph_scan.h ../include/igraph_graphlets.h \ + ../include/igraph_vector_type.h ../include/igraph_epidemics.h \ + ../include/igraph_lsap.h ../include/igraph_decls.h \ + ../include/igraph_coloring.h + +SOURCES = basic_query.c games.c cocitation.c iterators.c \ + structural_properties.c components.c layout.c \ + structure_generators.c conversion.c \ + type_indexededgelist.c spanning_trees.c \ + igraph_error.c interrupt.c other.c foreign.c random.c \ + attributes.c \ + foreign-ncol-parser.y foreign-ncol-lexer.l \ + foreign-lgl-parser.y foreign-lgl-lexer.l \ + foreign-pajek-parser.y foreign-pajek-lexer.l \ + foreign-gml-parser.y foreign-gml-lexer.l \ + dqueue.c heap.c igraph_heap.c igraph_stack.c \ + igraph_strvector.c igraph_trie.c matrix.c \ + vector.c vector_ptr.c memory.c adjlist.c \ + visitors.c igraph_grid.c atlas.c topology.c \ + motifs.c progress.c operators.c \ + igraph_psumtree.c array.c igraph_hashtable.c \ + foreign-graphml.c flow.c igraph_buckets.c \ + NetDataTypes.cpp NetRoutines.cpp clustertool.cpp \ + pottsmodel_2.cpp spectral_properties.c cores.c \ + igraph_set.c cliques.c \ + walktrap.cpp walktrap_heap.cpp \ + walktrap_graph.cpp walktrap_communities.cpp \ + infomap.cc infomap_Greedy.cc infomap_Node.cc infomap_FlowGraph.cc \ + spmatrix.c community.c fast_community.c community_leiden.c \ + gml_tree.c \ + bliss/orbit.cc bliss/defs.cc bliss/uintseqhash.cc \ + bliss/partition.cc bliss/graph.cc \ + bliss/bliss_heap.cc bliss/utils.cc bliss.cc \ + cattributes.c zeroin.c bfgs.c math.c \ + forestfire.c microscopic_update.c \ + blas.c arpack.c centrality.c drl_layout.cpp drl_parse.cpp \ + drl_graph.cpp DensityGrid.cpp \ + gengraph_box_list.cpp gengraph_degree_sequence.cpp \ + gengraph_graph_molloy_hash.cpp \ + gengraph_graph_molloy_optimized.cpp \ + gengraph_mr-connected.cpp gengraph_powerlaw.cpp \ + gengraph_random.cpp decomposition.c bipartite.c \ + drl_layout_3d.cpp drl_graph_3d.cpp \ + DensityGrid_3d.cpp \ + foreign-dl-parser.y foreign-dl-lexer.l \ + $(CS) sparsemat.c mixing.c bigint.c bignum.c \ + version.c optimal_modularity.c \ + igraph_fixed_vectorlist.c separators.c \ + igraph_marked_queue.c igraph_estack.c st-cuts.c \ + cohesive_blocks.c statusbar.c \ + lapack.c complex.c eigen.c feedback_arc_set.c \ + sugiyama.c glpk_support.c \ + igraph_hrg_types.cc igraph_hrg.cc \ + distances.c fortran_intrinsics.c matching.c \ + scg.c scg_approximate_methods.c scg_exact_scg.c \ + scg_kmeans.c scg_utils.c scg_optimal_method.c \ + qsort.c qsort_r.c types.c lad.c hacks.c \ + embedding.c scan.c triangles.c glet.c \ + maximal_cliques.c sbm.c dotproduct.c sir.c \ + prpack.cpp $(CHOLMOD) $(AMD) $(COLAMD) \ + $(SPCONFIG) layout_gem.c layout_dh.c lsap.c \ + layout_fr.c layout_kk.c paths.c \ + random_walk.c \ + igraph_cliquer.c cliquer/cliquer.c cliquer/cliquer_graph.c cliquer/reorder.c \ + coloring.c \ + degree_sequence.cpp + +if INTERNAL_F2C +else + SOURCES += f2c/dummy.c +endif + +libigraph_la_SOURCES = $(SOURCES) $(HEADERS_PRIVATE) +libigraph_la_CFLAGS = -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + -I$(top_srcdir)/src/CHOLMOD/Include \ + -I$(top_builddir)/src/CHOLMOD/Include \ + -I$(top_srcdir)/src/AMD/Include \ + -I$(top_builddir)/src/AMD/Include \ + -I$(top_srcdir)/src/COLAMD/Include \ + -I$(top_builddir)/src/COLAMD/Include \ + -I$(top_srcdir)/src/SuiteSparse_config \ + -I$(top_builddir)/src/SuiteSparse_config \ + -DNPARTITION -DNTIMER -DNCAMD $(WARNING_CFLAGS) +libigraph_la_CXXFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include $(WARNING_CFLAGS) +libigraph_la_LDFLAGS = -no-undefined +libigraph_la_LIBADD = -lm $(XML2_LIBS) $(F2C_LIB) $(BLAS_LIB) \ + $(LAPACK_LIB) $(ARPACK_LIB) $(GLPK_LIB) $(PRPACK_LIB) \ + $(PLFIT_LIB) + +if INTERNAL_GLPK + libigraph_la_CFLAGS += -I$(top_srcdir)/optional/glpk + libigraph_la_CXXFLAGS += -I$(top_srcdir)/optional/glpk +endif + +libigraph_la_CFLAGS += -I$(top_srcdir)/src/prpack -DPRPACK_IGRAPH_SUPPORT +libigraph_la_CXXFLAGS += -I$(top_srcdir)/src/prpack -DPRPACK_IGRAPH_SUPPORT + +igraphincludedir = $(includedir)/igraph +igraphinclude_HEADERS = $(HEADERS_PUBLIC) + +MAINTAINERCLEANFILES = Makefile.in foreign-lgl-parser.h foreign-ncol-parser.h + +echosources: + $(info $(SOURCES) $(LAPACK) $(ARPACK) $(BLAS) $(libf2c_la_SOURCES) \ + $(libdlamch_la_SOURCES) $(libplfit_la_SOURCES) $(PRPACK)) + +echoheaders: + $(info $(HEADERS_PUBLIC)) + +echoheadersprivate: + $(info $(HEADERS_PRIVATE)) + +.PHONY: sources + +parsersources: $(DIST_COMMON) diff --git a/src/NetDataTypes.cpp b/src/NetDataTypes.cpp new file mode 100644 index 0000000..acc1217 --- /dev/null +++ b/src/NetDataTypes.cpp @@ -0,0 +1,221 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + The original copyright notice follows here */ + +/*************************************************************************** + NetDataTypes.cpp - description + ------------------- + begin : Mon Oct 6 2003 + copyright : (C) 2003 by Joerg Reichardt + email : reichardt@mitte + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifdef HAVE_CONFIG_H + #include +#endif + +#include "NetDataTypes.h" +#include + +//################################################################################# +//############################################################################### +//Constructor +NNode::NNode(unsigned long ind, unsigned long c_ind, DLList *ll, char* n, int states) { + index = ind; + cluster_index = c_ind; + neighbours = new DLList(); + n_links = new DLList(); + global_link_list = ll; + strcpy(name, n); + color.red = 0; + color.green = 0; + color.blue = 0; + strcpy(color.pajek_c, "Green"); + clustering = 0.0; + marker = 0; + affiliations = 0; + weight = 0.0; + affinity = 0.0; + distance = 0; + max_states = states; + state_history = new unsigned long[states + 1]; +} + +//Destructor +NNode::~NNode() { + Disconnect_From_All(); + delete neighbours; + delete n_links; + delete [] state_history; + neighbours = NULL; + n_links = NULL; + state_history = NULL; +} + +void NNode::Add_StateHistory(unsigned int state) { + if (max_states >= state) { + state_history[state]++; + } +} + +void NNode::Set_Color(RGBcolor c) { + color.red = c.red; color.blue = c.blue; color.green = c.green; + strcpy(color.pajek_c, c.pajek_c); +} + +int NNode::Connect_To(NNode* neighbour, double weight) { + NLink *link; + //sollen doppelte Links erlaubt sein?? NEIN + if (!neighbour) { + return 0; + } + if (!(neighbours->Is_In_List(neighbour)) && (neighbour != this)) { + neighbours->Push(neighbour); // nachbar hier eintragen + neighbour->neighbours->Push(this); // diesen knoten beim nachbarn eintragen + + link = new NLink(this, neighbour, weight); //link erzeugen + global_link_list->Push(link); // in globaler liste eintragen + n_links->Push(link); // bei diesem Knoten eintragen + neighbour->n_links->Push(link); // beim nachbarn eintragen + + return (1); + } + return (0); +} + +NLink *NNode::Get_LinkToNeighbour(NNode* neighbour) { + DLList_Iter iter; + NLink *l_cur, *link = 0; + bool found = false; + // finde einen bestimmten Link aus der Liste der links eines Knotens + l_cur = iter.First(n_links); + while (!iter.End() && !found) { + if (((l_cur->Get_Start() == this) && (l_cur->Get_End() == neighbour)) || ((l_cur->Get_End() == this) && (l_cur->Get_Start() == neighbour))) { + found = true; + link = l_cur; + } + l_cur = iter.Next(); + } + if (found) { + return link; + } else { + return NULL; + } +} + +int NNode::Disconnect_From(NNode* neighbour) { + //sollen doppelte Links erlaubt sein?? s.o. + if (!neighbours) { + return 0; + } + neighbours->fDelete(neighbour); + n_links->fDelete(Get_LinkToNeighbour(neighbour)); + neighbour->n_links->fDelete(neighbour->Get_LinkToNeighbour(this)); + neighbour->neighbours->fDelete(this); + return 1; +} + +int NNode::Disconnect_From_All() { + int number_of_neighbours = 0; + while (neighbours->Size()) { + Disconnect_From(neighbours->Pop()); + number_of_neighbours++; + } + return (number_of_neighbours) ; +} + +/* +int NNode::Disconnect_From_All_Grandchildren() +{ + int n_l=links->Size(); + unsigned long pos=0; + while ((n_l--)>1) { //alle bis auf das erste loeschen + pos=(links->Get(n_l+1))->links->Is_In_List(this); + // printf("%d %d\n",n_l,pos); + (links->Get(n_l+1))->links->Delete(pos); + } + return(pos) ; +} +*/ + +double NNode::Get_Links_Among_Neigbours(void) { +// long neighbours1, neighbours2; + double lam = 0; + DLList_Iter iter1, iter2; +// neighbours1=neighbours->Size(); //so viele Nachbarn hat die Betrachtete Node + NNode *step1, *step2; + step1 = iter1.First(neighbours); + while (!iter1.End()) { // for (int n1=1;n1<=neighbours1; n1++) + //step1=neighbours->Get(n1); + //neighbours2=step1->neighbours->Size(); //so viele Nachbarn hat der n1-ste Nachbar + step2 = iter2.First(step1->Get_Neighbours()); + while (!iter2.End()) { //for (int n2=1;n2<=neighbours2; n2++) + //step2=step1->neighbours->Get(n2); + if (step2->Get_Neighbours()->Is_In_List(this)) { + lam++; + } + step2 = iter2.Next(); + } + step1 = iter1.Next(); + } + return (lam / 2.0); +} + + +double NNode::Get_Clustering() { + double c; + unsigned long k; + k = neighbours->Size(); + if (k <= 1) { + return (0); + } + c = 2.0 * Get_Links_Among_Neigbours() / double(k * k - k); + return (c); +} +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +//Constructor +NLink::NLink(NNode *s, NNode *e, double w) { + start = s; + end = e; + weight = w; + old_weight = 0; + marker = 0; +} + +//Destructor +NLink::~NLink() { + if (start && end) { + start->Disconnect_From(end); + } +} diff --git a/src/NetDataTypes.h b/src/NetDataTypes.h new file mode 100644 index 0000000..8c950e2 --- /dev/null +++ b/src/NetDataTypes.h @@ -0,0 +1,926 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + The original copyright notice follows here */ + +/*************************************************************************** + NetDataTypes.h - description + ------------------- + begin : Mon Oct 6 2003 + copyright : (C) 2003 by Joerg Reichardt + email : reichardt@mitte + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef NETDATATYPES_H +#define NETDATATYPES_H + +#include + +//########################################################################################### + +struct HUGE_INDEX { + unsigned int field_index; + unsigned long in_field_index; +}; + +template class HugeArray { +private: + unsigned long int size; + unsigned int highest_field_index; + unsigned long max_bit_left; + unsigned long max_index; + DATA *data; + DATA *fields[32]; +public: + HUGE_INDEX get_huge_index(unsigned long); + DATA &Set(unsigned long); + DATA Get(unsigned long); + HugeArray(void); + ~HugeArray(void); + DATA &operator[](unsigned long); + unsigned long Size(void) { + return max_index; + } +} ; +//############################################################################################### +template class DLList; +template class DL_Indexed_List; +template class ClusterList; +template class DLList_Iter; + +template +class DLItem { + friend class DLList ; + friend class DL_Indexed_List; + friend class DLList_Iter; +private: + L_DATA item; + unsigned long index; + DLItem *previous; + DLItem *next; + DLItem(L_DATA i, unsigned long ind); + DLItem(L_DATA i, unsigned long ind, DLItem *p, DLItem *n); + ~DLItem(); +public: + void del() { + delete item; + } +}; + +template +class DLList { + friend class DLList_Iter; +protected: + DLItem *head; + DLItem *tail; + unsigned long number_of_items; + DLItem *pInsert(L_DATA, DLItem*); + L_DATA pDelete(DLItem*); +public: + DLList(void); + ~DLList(); + unsigned long Size(void) { + return number_of_items; + } + int Insert(L_DATA, unsigned long); + int Delete(unsigned long); + int fDelete(L_DATA); + L_DATA Push(L_DATA); + L_DATA Pop(void); + L_DATA Get(unsigned long); + int Enqueue(L_DATA); + L_DATA Dequeue(void); + unsigned long Is_In_List(L_DATA); + void delete_items(); +}; + +template +class DL_Indexed_List : virtual public DLList { + friend class DLList_Iter; +private: + DLItem *pInsert(L_DATA, DLItem*); + L_DATA pDelete(DLItem*); + HugeArray*> array; + unsigned long last_index; +public: + DL_Indexed_List(void); + ~DL_Indexed_List(); + L_DATA Push(L_DATA); + L_DATA Pop(void); + L_DATA Get(unsigned long); +}; + +//##################################################################################################### + +template class DLList_Iter { +private: + DLList *list; + DLItem *current; + bool end_reached; +public: + DLList_Iter(void); + ~DLList_Iter() { + end_reached = true; + }; + L_DATA Next(void); + L_DATA Previous(void); + L_DATA First(DLList *l); + L_DATA Last(DLList *l); + bool End(void) { + return end_reached; + } + DLItem *Get_Current(void) { + return current; + } + L_DATA Get_Current_Item(void) { + return current->item; + } + void Set_Current(DLItem *c) { + current = c; + } + void Set_Status(bool s) { + end_reached = s; + } + bool Swap(DLList_Iter); //swapt die beiden Elemente, wenn sie in der gleichen Liste stehen!! + +}; + +//##################################################################################################### +struct RGBcolor { + unsigned int red; + unsigned int green; + unsigned int blue; + char pajek_c[20]; +}; +//------------------------------------------------------------------------------- + +class NLink; + +class NNode { + friend class NLink; +private : + unsigned long index; + unsigned long cluster_index; + unsigned long marker, affiliations; + unsigned long *state_history; + unsigned int max_states; + long distance; + double clustering; + double weight; + double affinity; +// double old_weight; + + DLList *neighbours; //list with pointers to neighbours + DLList *n_links; + DLList *global_link_list; + char name[255]; + RGBcolor color; +public : + NNode(unsigned long, unsigned long, DLList*, char*, int); + ~NNode(); + unsigned long Get_Index(void) { + return (index); + } + unsigned long Get_ClusterIndex(void) { + return (cluster_index); + } + unsigned long Get_Marker(void) { + return marker; + } + void Set_Marker(unsigned long m) { + marker = m; + } + unsigned long Get_Affiliations(void) { + return affiliations; + } + void Set_Affiliations(unsigned long m) { + affiliations = m; + } + void Set_ClusterIndex(unsigned long ci) { + cluster_index = ci; + return; + } + void Set_Index(unsigned long i) { + index = i; + return; + } + unsigned long Get_Degree(void) { + return (neighbours->Size()); + } + char *Get_Name(void) { + return name; + } + void Set_Name(char* n) { + strcpy(name, n); + } + double Get_Links_Among_Neigbours(void); + double Get_Clustering(void); + double Get_Weight(void) { + return weight; + } + double Get_Affinity(void) { + return affinity; + } + unsigned long *Get_StateHistory(void) { + return state_history; + } + void Add_StateHistory(unsigned int q); + // double Get_OldWeight(void) {return old_weight;} + void Set_Weight(double w) { + weight = w; + } + void Set_Affinity(double w) { + affinity = w; + } + + // void Set_OldWeight(double w) {old_weight=w;} + long Get_Distance(void) { + return distance; + } + void Set_Distance(long d) { + distance = d; + } + int Connect_To(NNode*, double); + DLList *Get_Neighbours(void) { + return neighbours; + } + DLList *Get_Links(void) { + return n_links; + } + int Disconnect_From(NNode*); + int Disconnect_From_All(void); + bool Is_Linked_To(NNode*); + RGBcolor Get_Color(void) { + return color; + } + void Set_Color(RGBcolor c); + NLink *Get_LinkToNeighbour(NNode *neighbour); +}; + +//##################################################################################################### + +class NLink { + friend class NNode; +private : + NNode *start; + NNode *end; + double weight; + double old_weight; + unsigned long index; + unsigned long marker; +public : + NLink( NNode*, NNode*, double); + ~NLink(); + unsigned long Get_Start_Index(void) { + return (start->Get_Index()); + } + unsigned long Get_End_Index(void) { + return (end->Get_Index()); + } + NNode *Get_Start(void) { + return (start); + } + NNode *Get_End(void) { + return (end); + } + double Get_Weight(void) { + return weight; + } + void Set_Weight(double w) { + weight = w; + } + double Get_OldWeight(void) { + return old_weight; + } + void Set_OldWeight(double w) { + old_weight = w; + } + unsigned long Get_Marker(void) { + return marker; + } + void Set_Marker(unsigned long m) { + marker = m; + } + unsigned long Get_Index() { + return index; + } + void Set_Index(unsigned long i) { + index = i; + } +}; + +//##################################################################################################### + +template class ClusterList : public DLList { + friend class DLList_Iter; +private: + long links_out_of_cluster; + unsigned long links_inside_cluster; + unsigned long frequency; + double cluster_energy; + DLList *candidates; + long marker; +public: + ClusterList(void); + ~ClusterList(); + long Get_Links_OOC(void) { + return (links_out_of_cluster); + } + void Set_Links_OOC(long looc) { + links_out_of_cluster = looc; + } + unsigned long Get_Links_IC(void) { + return (links_inside_cluster); + } + unsigned long Get_Frequency(void) { + return (frequency); + } + void IncreaseFrequency(void) { + frequency++; + } + void Set_Links_IC(unsigned long lic) { + links_inside_cluster = lic; + } + double Get_Energy(void) { + return (cluster_energy); + } + void Set_Energy(double e) { + cluster_energy = e; + } + DLList *Get_Candidates(void) { + return candidates; + } + bool operator<(ClusterList &b); + bool operator==(ClusterList &b); + long Get_Marker(void) { + return marker; + } + void Set_Marker(long m) { + marker = m; + } +}; +//##################################################################################################### +template +class DL_Node_List : virtual public DL_Indexed_List { + friend class DLList_Iter; +private: + DLItem *pInsert(NNode*, DLItem*); + NNode* pDelete(DLItem*); + HugeArray*> array; + unsigned long last_index; +public: + DL_Node_List(void); + ~DL_Node_List(); + NNode* Push(NNode*); + NNode* Pop(void); + NNode* Get(unsigned long); + int Delete(unsigned long); + +}; +//##################################################################################################### + + + +struct cluster_join_move { + ClusterList *c1; + ClusterList *c2; + double joint_energy; + long joint_looc; + unsigned long joint_lic; +} ; + +struct network { + DL_Indexed_List *node_list; + DL_Indexed_List *link_list; + DL_Indexed_List*> *cluster_list; + DL_Indexed_List *moveset; + unsigned long max_k; + unsigned long min_k; + unsigned long diameter; + double av_weight; + double max_weight; + double min_weight; + double sum_weights; + double av_k; + double av_bids; + unsigned long max_bids; + unsigned long min_bids; + unsigned long sum_bids; +} ; + +/* +struct network +{ + DLList *node_list; + DLList *link_list; + DLList*> *cluster_list; + DLList *moveset; +} ; +*/ + +template +HugeArray::HugeArray(void) { + max_bit_left = 1 << 31; //wir setzen das 31. Bit auf 1 + size = 2; + max_index = 0; + highest_field_index = 0; + data = new DATA[2]; //ein extra Platz fuer das Nullelement + data[0] = 0; + data[1] = 0; + for (int i = 0; i < 32; i++) { + fields[i] = NULL; + } + fields[highest_field_index] = data; +} + +template HugeArray::~HugeArray(void) { + for (unsigned int i = 0; i <= highest_field_index; i++) { + data = fields[i]; + delete [] data; + } +} + +template +HUGE_INDEX HugeArray::get_huge_index(unsigned long index) { + HUGE_INDEX h_index; + unsigned int shift_index = 0; + unsigned long help_index; + help_index = index; + if (index < 2) { + h_index.field_index = 0; + h_index.in_field_index = index; + return h_index; + } + // wie oft muessen wir help_index nach links shiften, damit das 31. Bit gesetzt ist?? + while (!(max_bit_left & help_index)) { + help_index <<= 1; + shift_index++; + } + h_index.field_index = 31 - shift_index; // das hoechste besetzte Bit im Index + help_index = 1 << h_index.field_index; // in help_index wird das hoechste besetzte Bit von Index gesetzt + h_index.in_field_index = (index ^ help_index); // index XOR help_index, womit alle bits unter dem hoechsten erhalten bleiben + return h_index; +} + +template +DATA &HugeArray::Set(unsigned long int index) { + HUGE_INDEX h_index; + unsigned long data_size; + while (size < index + 1) { + highest_field_index++; + data_size = 1 << highest_field_index; + data = new DATA[data_size]; + for (unsigned long i = 0; i < data_size; i++) { + data[i] = 0; + } + size = size + data_size; //overflow noch abfangen + //printf("Vergroesserung auf: %u bei index %u\n",size,index); + fields[highest_field_index] = data; + } + h_index = get_huge_index(index); +//printf("index %lu = %lu . %lu\n",index,h_index.field_index,h_index.in_field_index); + data = fields[h_index.field_index]; + if (max_index < index) { + max_index = index; + } + return (data[h_index.in_field_index]); +} + +template +DATA HugeArray::Get(unsigned long index) { + return (Set(index)); +} + + +template +DATA &HugeArray::operator[](unsigned long index) { + return (Set(index)); +} + + +//############################################################################### +template +DLItem::DLItem(L_DATA i, unsigned long ind) : item(i), index(ind), previous(0), next(0) { +} + +template +DLItem::DLItem(L_DATA i, unsigned long ind, DLItem *p, DLItem *n) : item(i), index(ind), previous(p), next(n) { +} + +template +DLItem::~DLItem() { +//delete item; //eigentlich muessten wir pruefen, ob item ueberhaupt ein Pointer ist... +//previous=NULL; +//next=NULL; +} + + +//###################################################################################################################### +template +DLList::DLList(void) { + head = tail = NULL; + number_of_items = 0; + head = new DLItem(NULL, 0); //fuer head und Tail gibt es das gleiche Array-Element!! Vorsicht!! + tail = new DLItem(NULL, 0); + if ( !head || !tail ) { + if (head) { + delete (head); + } + if (tail) { + delete (tail); + } + return; + } else { + head->next = tail; + tail->previous = head; + } +} + +template +DLList::~DLList() { + DLItem *cur = head, *next; + while (cur) { + next = cur->next; + delete (cur); + cur = next; + } + number_of_items = 0; + // printf("Liste Zerstoert!\n"); +} + +template +void DLList::delete_items() { + DLItem *cur, *next; + cur = this->head; + while (cur) { + next = cur->next; + cur->del(); + cur = next; + } + this->number_of_items = 0; +} + +//privates Insert +template +DLItem *DLList::pInsert(L_DATA data, DLItem *pos) { + DLItem *i = new DLItem(data, number_of_items + 1, pos->previous, pos); + if (i) { + pos->previous->next = i; + pos->previous = i; + number_of_items++; + return (i); + } else { + return (0); + } +} +//privates delete +template +L_DATA DLList::pDelete(DLItem *i) { + L_DATA data = i->item; + i->previous->next = i->next; + i->next->previous = i->previous; +// array[i->index]=0; + delete (i); + number_of_items--; + return (data); +} +//oeffentliches Insert +template +int DLList::Insert(L_DATA data, unsigned long pos) { + if ((pos < 0) || (pos > (number_of_items))) { + return (0); + } + DLItem *cur = head; + while (pos--) { + cur = cur->next; + } + return (pInsert(data, cur) != 0); +} +//oeffentliche Delete +template +int DLList::Delete(unsigned long pos) { + if ((pos < 0) || (pos > (number_of_items))) { + return (0); + } + DLItem *cur = head; + while (pos--) { + cur = cur->next; + } + return (pDelete(cur) != 0); +} + +//oeffentliche Delete +template +int DLList::fDelete(L_DATA data) { + if ((number_of_items == 0) || (!data)) { + return (0); + } + DLItem *cur; + cur = head->next; + while ((cur != tail) && (cur->item != data)) { + cur = cur->next; + } + if (cur != tail) { + return (pDelete(cur) != 0); + } + return (0); +} + +template +L_DATA DLList::Push(L_DATA data) { + DLItem *tmp; + tmp = pInsert(data, tail); + if (tmp) { + return (tmp->item); + } + return (0); +} + +template +L_DATA DLList::Pop(void) { + return (pDelete(tail->previous)); +} + + +template +L_DATA DLList::Get(unsigned long pos) { + if ((pos < 1) || (pos > (number_of_items + 1))) { + return (0); + } +// return(array[pos]->item); + DLItem *cur = head; + while (pos--) { + cur = cur->next; + } + return (cur->item); +} + + +template +int DLList::Enqueue(L_DATA data) { + return (pInsert(data, tail) != 0); +} + +template +L_DATA DLList::Dequeue(void) { + return (pDelete(head->next)); +} + +//gibt Index des gesuchte Listenelement zurueck, besser waere eigentlich zeiger +template +unsigned long DLList::Is_In_List(L_DATA data) { + DLItem *cur = head, *next; + unsigned long pos = 0; + while (cur) { + next = cur->next; + if (cur->item == data) { + return (pos) ; + } + cur = next; + pos++; + } + return (0); +} + +//###################################################################################################################### +template +DL_Indexed_List::DL_Indexed_List(void) : DLList() { + last_index = 0; +} + +template +DL_Indexed_List::~DL_Indexed_List() { + /* This is already done by the DLList destructor */ + /* DLItem *cur, *next; */ + /* cur=this->head; */ + /* while (cur) */ + /* { */ + /* next=cur->next; */ + /* delete(cur); */ + /* cur=next; */ + /* } */ + /* this->number_of_items=0; */ + // printf("Liste Zerstoert!\n"); +} + +//privates Insert +template +DLItem *DL_Indexed_List::pInsert(L_DATA data, DLItem *pos) { + DLItem *i = new DLItem(data, last_index, pos->previous, pos); + if (i) { + pos->previous->next = i; + pos->previous = i; + this->number_of_items++; + array[last_index] = i; + last_index++; + return (i); + } else { + return (0); + } +} +//privates delete +template +L_DATA DL_Indexed_List::pDelete(DLItem *i) { + L_DATA data = i->item; + i->previous->next = i->next; + i->next->previous = i->previous; + array[i->index] = 0; + last_index = i->index; + delete (i); + this->number_of_items--; + return (data); +} +template +L_DATA DL_Indexed_List::Push(L_DATA data) { + DLItem *tmp; + tmp = pInsert(data, this->tail); + if (tmp) { + return (tmp->item); + } + return (0); +} + +template +L_DATA DL_Indexed_List::Pop(void) { + return (pDelete(this->tail->previous)); +} + +template +L_DATA DL_Indexed_List::Get(unsigned long pos) { + if (pos > this->number_of_items - 1) { + return (0); + } + return (array[pos]->item); +} + +//####################################################################################### + +//************************************************************************************************************ +template +ClusterList::ClusterList(void) : DLList() { + links_out_of_cluster = 0; + links_inside_cluster = 0; + frequency = 1; + cluster_energy = 1e30; + candidates = new DLList(); + marker = 0; +} + +template +ClusterList::~ClusterList() { + while (candidates->Size()) { + candidates->Pop(); + } + delete candidates; +} + + +template +bool ClusterList::operator==(ClusterList &b) { + bool found = false; + L_DATA n_cur, n_cur_b; + DLList_Iter a_iter, b_iter; + + if (this->Size() != b.Size()) { + return false; + } + + n_cur = a_iter.First(this); + while (!(a_iter.End())) { + found = false; + n_cur_b = b_iter.First(&b); + while (!(b_iter.End()) && !found) { + if (n_cur == n_cur_b) { + found = true; + } + n_cur_b = b_iter.Next(); + } + if (!found) { + return false; + } + n_cur = a_iter.Next(); + } + return (found); +} +//A +bool ClusterList::operator<(ClusterList &b) { + bool found = false; + L_DATA n_cur, n_cur_b; + DLList_Iter a_iter, b_iter; + + if (this->Size() >= b.Size()) { + return false; + } + n_cur = a_iter.First(this); + while (!(a_iter.End())) { + found = false; + n_cur_b = b_iter.First(&b); + while (!(b_iter.End()) && !found) { + if (n_cur == n_cur_b) { + found = true; + } + n_cur_b = b_iter.Next(); + } + if (!found) { + return false; + } + n_cur = a_iter.Next(); + } + return (found); +} + +//##################################################################################### +template +DLList_Iter::DLList_Iter() { + list = NULL; + current = NULL; + end_reached = true; +} + +template +L_DATA DLList_Iter::Next(void) { + current = current->next; + if (current == (list->tail)) { + end_reached = true; + } + return (current->item); +} + +template +L_DATA DLList_Iter::Previous(void) { + current = current->previous; + if (current == (list->head)) { + end_reached = true; + } + return (current->item); +} + +template +L_DATA DLList_Iter::First(DLList *l) { + list = l; + current = list->head->next; + if (current == (list->tail)) { + end_reached = true; + } else { + end_reached = false; + } + return (current->item); +} + +template +L_DATA DLList_Iter::Last(DLList *l) { + list = l; + current = list->tail->previous; + if (current == (list->head)) { + end_reached = true; // falls die List leer ist + } else { + end_reached = false; + } + return (current->item); +} + +template +bool DLList_Iter::Swap(DLList_Iter b) { + L_DATA h; + if (list != b.list) { + return false; //elemeten muessen aus der gleichen List stammen + } + if (end_reached || b.end_reached) { + return false; + } + h = current->item; current->item = b.current->item; b.current->item = h; + return true; +} + +#endif + diff --git a/src/NetRoutines.cpp b/src/NetRoutines.cpp new file mode 100644 index 0000000..abca740 --- /dev/null +++ b/src/NetRoutines.cpp @@ -0,0 +1,286 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + The original copyright notice follows here */ + +/*************************************************************************** + NetRoutines.cpp - description + ------------------- + begin : Tue Oct 28 2003 + copyright : (C) 2003 by Joerg Reichardt + email : reichardt@mitte + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "NetRoutines.h" +#include "NetDataTypes.h" + +#include "igraph_types.h" +#include "igraph_interface.h" +#include "igraph_conversion.h" + +#include + +int igraph_i_read_network(const igraph_t *graph, + const igraph_vector_t *weights, + network *net, igraph_bool_t use_weights, + unsigned int states) { + + double av_k = 0.0, sum_weight = 0.0, min_weight = 1e60, max_weight = -1e60; + unsigned long min_k = 999999999, max_k = 0; + long max_index = 0; + char name[255]; + NNode *node1, *node2; + DLList_Iter iter; + igraph_vector_t edgelist; + long int no_of_edges = (long int)igraph_ecount(graph); + long int ii; + char *empty = new char[1]; + empty[0] = '\0'; + + IGRAPH_VECTOR_INIT_FINALLY(&edgelist, no_of_edges * 2); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edgelist, 0 /* rowwise */)); + + for (ii = 0; ii < no_of_edges; ii++) { + long int i1 = (long int)VECTOR(edgelist)[2 * ii] + 1; + long int i2 = (long int)VECTOR(edgelist)[2 * ii + 1] + 1; + igraph_real_t Links; + if (use_weights) { + Links = VECTOR(*weights)[ii]; + } else { + Links = 1.0; + } + // From the original source + if (max_index < i1) { + for (int i = max_index; i < i1; i++) { + net->node_list->Push(new NNode(i, 0, net->link_list, empty, states)); + } + max_index = i1; + } + if (max_index < i2) { + for (int i = max_index; i < i2; i++) { + net->node_list->Push(new NNode(i, 0, net->link_list, empty, states)); + } + max_index = i2; + } + + node1 = net->node_list->Get(i1 - 1); + sprintf(name, "%li", i1); + node1->Set_Name(name); + + node2 = net->node_list->Get(i2 - 1); + sprintf(name, "%li", i2); + node2->Set_Name(name); + + node1->Connect_To(node2, Links); + + if (Links < min_weight) { + min_weight = Links; + } + if (Links > max_weight) { + max_weight = Links; + } + sum_weight += Links; + } + + IGRAPH_FINALLY_CLEAN(1); + igraph_vector_destroy(&edgelist); + + node1 = iter.First(net->node_list); + while (!iter.End()) { + if (node1->Get_Degree() > max_k) { + max_k = node1->Get_Degree(); + } + if (node1->Get_Degree() < min_k) { + min_k = node1->Get_Degree(); + } + av_k += node1->Get_Degree(); + node1 = iter.Next(); + } + net->av_k = av_k / double(net->node_list->Size()); + net->sum_weights = sum_weight; + net->av_weight = sum_weight / double(net->link_list->Size()); + net->min_k = min_k; + net->max_k = max_k; + net->min_weight = min_weight; + net->max_weight = max_weight; + net->sum_bids = 0; + net->min_bids = 0; + net->max_bids = 0; + + delete [] empty; + + return 0; +} + +//############################################################################################################### +void reduce_cliques(DLList*> *global_cluster_list, FILE *file) { + unsigned long size; + ClusterList *c_cur, *largest_c = 0; + DLList*> *subsets; + DLList_Iter*> c_iter, sub_iter; + DLList_Iter iter; + NNode *n_cur; + + if (!(global_cluster_list->Size())) { + return; + } + //wir suchen den groessten Cluster + + c_cur = c_iter.First(global_cluster_list); + size = 0; + while (!(c_iter.End())) { + if (c_cur->Size() > size) { + size = c_cur->Size(); + largest_c = c_cur; + } + c_cur = c_iter.Next(); + } +// printf("Groesster Cluster hat %u Elemente.\n",largest_c->Size()); + + //Schauen, ob es Teilmengen gibt, die ebenfalls gefunden wurden + subsets = new DLList*>(); + c_cur = c_iter.First(global_cluster_list); + while (!(c_iter.End())) { + if ((*c_cur < *largest_c || *c_cur == *largest_c) && c_cur != largest_c) { //alle echten Teilcluster von largest_c und die doppelten + subsets->Push(c_cur); + } + c_cur = c_iter.Next(); + } + // die gefundenen Subsets werden aus der cluster_liste geloescht + while (subsets->Size()) { + global_cluster_list->fDelete(subsets->Pop()); + } + delete subsets; + // Dann schreiben wir den groessten Cluster in das File + fprintf(file, "Energie: %1.12f Nodes:%3lu - ", largest_c->Get_Energy(), largest_c->Size()); + + n_cur = iter.First(largest_c); + while (!(iter.End())) { + fprintf(file, "%s", n_cur->Get_Name()); + n_cur = iter.Next(); + if (n_cur) { + fprintf(file, ", "); + } + } + fprintf(file, "\n"); + + + //Schliesslich schmeissen wir noch den eben gefundenen groessten Cluster raus + global_cluster_list->fDelete(largest_c); + //und dann geht es von vorn mit der Reduzierten ClusterListe los + reduce_cliques(global_cluster_list, file); + +} +//################################################################################## +void reduce_cliques2(network *net, bool only_double, long marker) { + unsigned long size; + ClusterList *c_cur, *largest_c = 0; + DLList_Iter*> c_iter; + do { + //wir suchen den groessten, nicht markierten Cluster + size = 0; + c_cur = c_iter.First(net->cluster_list); + while (!(c_iter.End())) { + if ((c_cur->Size() > size) && (c_cur->Get_Marker() != marker)) { + size = c_cur->Size(); + largest_c = c_cur; + } + c_cur = c_iter.Next(); + } + // printf("Groesster Cluster hat %u Elemente.\n",largest_c->Size()); + //Schauen, ob es Teilmengen gibt, die ebenfalls gefunden wurden + c_cur = c_iter.First(net->cluster_list); + while (!(c_iter.End())) { + if (((!only_double && (*c_cur < *largest_c)) || (*c_cur == *largest_c)) && (c_cur != largest_c)) { //alle echten Teilcluster von largest_c und die doppelten + net->cluster_list->fDelete(c_cur); + while (c_cur->Get_Candidates()->Size()) { + c_cur->Get_Candidates()->Pop(); + } + while (c_cur->Size()) { + c_cur->Pop(); // die knoten aber nicht loeschen!! + } + delete c_cur; // nicht vergessen, die global geloeschte Clusterliste zu loeschen + } + c_cur = c_iter.Next(); + } + //Schliesslich markieren wir noch den eben gefundenen groessten Cluster + largest_c->Set_Marker(marker); + } while (size); +} + +//################################################################################################## +unsigned long iterate_nsf_hierarchy(NNode *parent, unsigned long depth, FILE *file) { + NNode* next_node; + unsigned long newdepth, maxdepth; + bool first = true; + DLList_Iter *iter; + maxdepth = newdepth = depth; + iter = new DLList_Iter; + next_node = iter->First(parent->Get_Neighbours()); + while (!(iter->End())) { + if (next_node->Get_Marker() > parent->Get_Marker()) { // wir gehen nach unten + if (first) { + fprintf(file, ",("); // eine Neue Klammer auf + } + if (first) { + fprintf(file, "%s", next_node->Get_Name()); // nur vor dem ersten kein Komma + } else { + fprintf(file, ",%s", next_node->Get_Name()); // sonst immer mit Komma + } + first = false; + newdepth = iterate_nsf_hierarchy(next_node, depth + 1, file); + if (maxdepth < newdepth) { + maxdepth = newdepth; + } + } + next_node = iter->Next(); + } + if (!first) { + fprintf(file, ")"); //hat es ueberhaupt einen gegeben? + } + //dann klamer zu! + delete iter; + return maxdepth; +} + +//################################################################ +void clear_all_markers(network *net) { + DLList_Iter iter; + NNode *n_cur; + n_cur = iter.First(net->node_list); + while (!iter.End()) { + n_cur->Set_Marker(0); + n_cur = iter.Next(); + } +} + diff --git a/src/NetRoutines.h b/src/NetRoutines.h new file mode 100644 index 0000000..433ed76 --- /dev/null +++ b/src/NetRoutines.h @@ -0,0 +1,61 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + The original copyright notice follows here */ + +/*************************************************************************** + NetRoutines.h - description + ------------------- + begin : Tue Oct 28 2003 + copyright : (C) 2003 by Joerg Reichardt + email : reichardt@mitte + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef NETROUTINES_H +#define NETROUTINES_H + +#include "NetDataTypes.h" +#include "igraph_types.h" +#include "igraph_datatype.h" + +int igraph_i_read_network(const igraph_t *graph, + const igraph_vector_t *weights, + network *net, igraph_bool_t use_weights, + unsigned int states); + +void reduce_cliques(DLList*>*, FILE *file); +void reduce_cliques2(network*, bool, long ); +void clear_all_markers(network *net); + +#endif + diff --git a/src/SuiteSparse_config/Makefile b/src/SuiteSparse_config/Makefile new file mode 100644 index 0000000..3c621bd --- /dev/null +++ b/src/SuiteSparse_config/Makefile @@ -0,0 +1,43 @@ +#------------------------------------------------------------------------------- +# SuiteSparse_config Makefile +#------------------------------------------------------------------------------- + +VERSION = 4.2.1 + +default: ccode + +include SuiteSparse_config.mk + +ccode: libsuitesparseconfig.a + +all: libsuitesparseconfig.a + +library: libsuitesparseconfig.a + +libsuitesparseconfig.a: SuiteSparse_config.c SuiteSparse_config.h + $(CC) $(CF) -c SuiteSparse_config.c + $(ARCHIVE) libsuitesparseconfig.a SuiteSparse_config.o + $(RANLIB) libsuitesparseconfig.a + - $(RM) SuiteSparse_config.o + +distclean: purge + +purge: clean + - $(RM) *.o *.a + +clean: + - $(RM) -r $(CLEAN) + +# install SuiteSparse_config +install: + $(CP) libsuitesparseconfig.a $(INSTALL_LIB)/libsuitesparseconfig.$(VERSION).a + ( cd $(INSTALL_LIB) ; ln -sf libsuitesparseconfig.$(VERSION).a libsuitesparseconfig.a ) + $(CP) SuiteSparse_config.h $(INSTALL_INCLUDE) + chmod 644 $(INSTALL_LIB)/libsuitesparseconfig*.a + chmod 644 $(INSTALL_INCLUDE)/SuiteSparse_config.h + +# uninstall SuiteSparse_config +uninstall: + $(RM) $(INSTALL_LIB)/libsuitesparseconfig*.a + $(RM) $(INSTALL_INCLUDE)/SuiteSparse_config.h + diff --git a/src/SuiteSparse_config/README.txt b/src/SuiteSparse_config/README.txt new file mode 100644 index 0000000..bc85ace --- /dev/null +++ b/src/SuiteSparse_config/README.txt @@ -0,0 +1,48 @@ +SuiteSparse_config, 2013, Timothy A. Davis, http://www.suitesparse.com +(formerly the UFconfig package) + +SuiteSparse_config contains configuration settings for all many of the software +packages that I develop or co-author. Note that older versions of some of +these packages do not require SuiteSparse_config. + + Package Description + ------- ----------- + AMD approximate minimum degree ordering + CAMD constrained AMD + COLAMD column approximate minimum degree ordering + CCOLAMD constrained approximate minimum degree ordering + UMFPACK sparse LU factorization, with the BLAS + CXSparse int/long/real/complex version of CSparse + CHOLMOD sparse Cholesky factorization, update/downdate + KLU sparse LU factorization, BLAS-free + BTF permutation to block triangular form + LDL concise sparse LDL' + LPDASA LP Dual Active Set Algorithm + RBio read/write files in Rutherford/Boeing format + SPQR sparse QR factorization (full name: SuiteSparseQR) + +SuiteSparse_config is not required by these packages: + + CSparse a Concise Sparse matrix package + MATLAB_Tools toolboxes for use in MATLAB + +In addition, the xerbla/ directory contains Fortan and C versions of the +BLAS/LAPACK xerbla routine, which is called when an invalid input is passed to +the BLAS or LAPACK. The xerbla provided here does not print any message, so +the entire Fortran I/O library does not need to be linked into a C application. +Most versions of the BLAS contain xerbla, but those from K. Goto do not. Use +this if you need too. + +If you edit this directory (SuiteSparse_config.mk in particular) then you +must do "make purge ; make" in the parent directory to recompile all of +SuiteSparse. Otherwise, the changes will not necessarily be applied. + +-------------------------------------------------------------------------------- +A note on the update to SuiteSparse Version 4.0.0: The SuiteSparse_long macro +defines an integer that is 64-bits in size on 64-bit platforms, and 32-bits on +32-bit platforms. It was formerly called UF_long, but UF_long has been removed +because of potential name conflicts. UF_long is still available to user codes, +but it can now be safely #undef'd in case of name conflicts in user code. +Future codes should use SuiteSparse_long in place of UF_long. +-------------------------------------------------------------------------------- + diff --git a/src/SuiteSparse_config/SuiteSparse_config.c b/src/SuiteSparse_config/SuiteSparse_config.c new file mode 100644 index 0000000..4387b4b --- /dev/null +++ b/src/SuiteSparse_config/SuiteSparse_config.c @@ -0,0 +1,191 @@ +/* ========================================================================== */ +/* === SuiteSparse_config =================================================== */ +/* ========================================================================== */ + +/* Copyright (c) 2012, Timothy A. Davis. No licensing restrictions + * apply to this file or to the SuiteSparse_config directory. + * Author: Timothy A. Davis. + */ + +#include "SuiteSparse_config.h" + +/* -------------------------------------------------------------------------- */ +/* SuiteSparse_malloc: malloc wrapper */ +/* -------------------------------------------------------------------------- */ + +void *SuiteSparse_malloc /* pointer to allocated block of memory */ +( + size_t nitems, /* number of items to malloc (>=1 is enforced) */ + size_t size_of_item, /* sizeof each item */ + int *ok, /* TRUE if successful, FALSE otherwise */ + SuiteSparse_config *config /* SuiteSparse-wide configuration */ +) +{ + void *p ; + if (nitems < 1) nitems = 1 ; + if (nitems * size_of_item != ((double) nitems) * size_of_item) + { + /* Int overflow */ + *ok = 0 ; + return (NULL) ; + } + if (!config || config->malloc_memory == NULL) + { + /* use malloc by default */ + p = (void *) malloc (nitems * size_of_item) ; + } + else + { + /* use the pointer to malloc in the config */ + p = (void *) (config->malloc_memory) (nitems * size_of_item) ; + } + *ok = (p != NULL) ; + return (p) ; +} + + +/* -------------------------------------------------------------------------- */ +/* SuiteSparse_free: free wrapper */ +/* -------------------------------------------------------------------------- */ + +void *SuiteSparse_free /* always returns NULL */ +( + void *p, /* block to free */ + SuiteSparse_config *config /* SuiteSparse-wide configuration */ +) +{ + if (p) + { + if (!config || config->free_memory == NULL) + { + /* use free by default */ + free (p) ; + } + else + { + /* use the pointer to free in the config */ + (config->free_memory) (p) ; + } + } + return (NULL) ; +} + + +/* -------------------------------------------------------------------------- */ +/* SuiteSparse_tic: return current wall clock time */ +/* -------------------------------------------------------------------------- */ + +/* Returns the number of seconds (tic [0]) and nanoseconds (tic [1]) since some + * unspecified but fixed time in the past. If no timer is installed, zero is + * returned. A scalar double precision value for 'tic' could be used, but this + * might cause loss of precision because clock_getttime returns the time from + * some distant time in the past. Thus, an array of size 2 is used. + * + * The timer is enabled by default. To disable the timer, compile with + * -DNTIMER. If enabled on a POSIX C 1993 system, the timer requires linking + * with the -lrt library. + * + * example: + * + * double tic [2], r, s, t ; + * SuiteSparse_tic (tic) ; // start the timer + * // do some work A + * t = SuiteSparse_toc (tic) ; // t is time for work A, in seconds + * // do some work B + * s = SuiteSparse_toc (tic) ; // s is time for work A and B, in seconds + * SuiteSparse_tic (tic) ; // restart the timer + * // do some work C + * r = SuiteSparse_toc (tic) ; // s is time for work C, in seconds + * + * A double array of size 2 is used so that this routine can be more easily + * ported to non-POSIX systems. The caller does not rely on the POSIX + * include file. + */ + +#ifdef SUITESPARSE_TIMER_ENABLED + +#include + +void SuiteSparse_tic +( + double tic [2] /* output, contents undefined on input */ +) +{ + /* POSIX C 1993 timer, requires -librt */ + struct timespec t ; + clock_gettime (CLOCK_MONOTONIC, &t) ; + tic [0] = (double) (t.tv_sec) ; + tic [1] = (double) (t.tv_nsec) ; +} + +#else + +void SuiteSparse_tic +( + double tic [2] /* output, contents undefined on input */ +) +{ + /* no timer installed */ + tic [0] = 0 ; + tic [1] = 0 ; +} + +#endif + + +/* -------------------------------------------------------------------------- */ +/* SuiteSparse_toc: return time since last tic */ +/* -------------------------------------------------------------------------- */ + +/* Assuming SuiteSparse_tic is accurate to the nanosecond, this function is + * accurate down to the nanosecond for 2^53 nanoseconds since the last call to + * SuiteSparse_tic, which is sufficient for SuiteSparse (about 104 days). If + * additional accuracy is required, the caller can use two calls to + * SuiteSparse_tic and do the calculations differently. + */ + +double SuiteSparse_toc /* returns time in seconds since last tic */ +( + double tic [2] /* input, not modified from last call to SuiteSparse_tic */ +) +{ + double toc [2] ; + SuiteSparse_tic (toc) ; + return ((toc [0] - tic [0]) + 1e-9 * (toc [1] - tic [1])) ; +} + + +/* -------------------------------------------------------------------------- */ +/* SuiteSparse_time: return current wallclock time in seconds */ +/* -------------------------------------------------------------------------- */ + +/* This function might not be accurate down to the nanosecond. */ + +double SuiteSparse_time /* returns current wall clock time in seconds */ +( + void +) +{ + double toc [2] ; + SuiteSparse_tic (toc) ; + return (toc [0] + 1e-9 * toc [1]) ; +} + + +/* -------------------------------------------------------------------------- */ +/* SuiteSparse_version: return the current version of SuiteSparse */ +/* -------------------------------------------------------------------------- */ + +int SuiteSparse_version +( + int version [3] +) +{ + if (version != NULL) + { + version [0] = SUITESPARSE_MAIN_VERSION ; + version [1] = SUITESPARSE_SUB_VERSION ; + version [2] = SUITESPARSE_SUBSUB_VERSION ; + } + return (SUITESPARSE_VERSION) ; +} diff --git a/src/SuiteSparse_config/SuiteSparse_config.h b/src/SuiteSparse_config/SuiteSparse_config.h new file mode 100644 index 0000000..fff5ea0 --- /dev/null +++ b/src/SuiteSparse_config/SuiteSparse_config.h @@ -0,0 +1,202 @@ +/* ========================================================================== */ +/* === SuiteSparse_config =================================================== */ +/* ========================================================================== */ + +/* Configuration file for SuiteSparse: a Suite of Sparse matrix packages + * (AMD, COLAMD, CCOLAMD, CAMD, CHOLMOD, UMFPACK, CXSparse, and others). + * + * SuiteSparse_config.h provides the definition of the long integer. On most + * systems, a C program can be compiled in LP64 mode, in which long's and + * pointers are both 64-bits, and int's are 32-bits. Windows 64, however, uses + * the LLP64 model, in which int's and long's are 32-bits, and long long's and + * pointers are 64-bits. + * + * SuiteSparse packages that include long integer versions are + * intended for the LP64 mode. However, as a workaround for Windows 64 + * (and perhaps other systems), the long integer can be redefined. + * + * If _WIN64 is defined, then the __int64 type is used instead of long. + * + * The long integer can also be defined at compile time. For example, this + * could be added to SuiteSparse_config.mk: + * + * CFLAGS = -O -D'SuiteSparse_long=long long' \ + * -D'SuiteSparse_long_max=9223372036854775801' -D'SuiteSparse_long_idd="lld"' + * + * This file defines SuiteSparse_long as either long (on all but _WIN64) or + * __int64 on Windows 64. The intent is that a SuiteSparse_long is always a + * 64-bit integer in a 64-bit code. ptrdiff_t might be a better choice than + * long; it is always the same size as a pointer. + * + * This file also defines the SUITESPARSE_VERSION and related definitions. + * + * Copyright (c) 2012, Timothy A. Davis. No licensing restrictions apply + * to this file or to the SuiteSparse_config directory. + * Author: Timothy A. Davis. + */ + +#ifndef _SUITESPARSECONFIG_H +#define _SUITESPARSECONFIG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/* ========================================================================== */ +/* === SuiteSparse_long ===================================================== */ +/* ========================================================================== */ + +#ifndef SuiteSparse_long + +#ifdef _WIN64 + +#define SuiteSparse_long __int64 +#define SuiteSparse_long_max _I64_MAX +#define SuiteSparse_long_idd "I64d" + +#else + +#define SuiteSparse_long long +#define SuiteSparse_long_max LONG_MAX +#define SuiteSparse_long_idd "ld" + +#endif +#define SuiteSparse_long_id "%" SuiteSparse_long_idd +#endif + +/* For backward compatibility with prior versions of SuiteSparse. The UF_* + * macros are deprecated and will be removed in a future version. */ +#ifndef UF_long +#define UF_long SuiteSparse_long +#define UF_long_max SuiteSparse_long_max +#define UF_long_idd SuiteSparse_long_idd +#define UF_long_id SuiteSparse_long_id +#endif + +/* ========================================================================== */ +/* === SuiteSparse_config parameters and functions ========================== */ +/* ========================================================================== */ + +/* SuiteSparse-wide parameters will be placed in this struct. */ + +typedef struct SuiteSparse_config_struct +{ + void *(*malloc_memory) (size_t) ; /* pointer to malloc */ + void *(*realloc_memory) (void *, size_t) ; /* pointer to realloc */ + void (*free_memory) (void *) ; /* pointer to free */ + void *(*calloc_memory) (size_t, size_t) ; /* pointer to calloc */ + +} SuiteSparse_config ; + +void *SuiteSparse_malloc /* pointer to allocated block of memory */ +( + size_t nitems, /* number of items to malloc (>=1 is enforced) */ + size_t size_of_item, /* sizeof each item */ + int *ok, /* TRUE if successful, FALSE otherwise */ + SuiteSparse_config *config /* SuiteSparse-wide configuration */ +) ; + +void *SuiteSparse_free /* always returns NULL */ +( + void *p, /* block to free */ + SuiteSparse_config *config /* SuiteSparse-wide configuration */ +) ; + +void SuiteSparse_tic /* start the timer */ +( + double tic [2] /* output, contents undefined on input */ +) ; + +double SuiteSparse_toc /* return time in seconds since last tic */ +( + double tic [2] /* input: from last call to SuiteSparse_tic */ +) ; + +double SuiteSparse_time /* returns current wall clock time in seconds */ +( + void +) ; + +/* determine which timer to use, if any */ +#ifndef NTIMER +#ifdef _POSIX_C_SOURCE +#if _POSIX_C_SOURCE >= 199309L +#define SUITESPARSE_TIMER_ENABLED +#endif +#endif +#endif + +/* ========================================================================== */ +/* === SuiteSparse version ================================================== */ +/* ========================================================================== */ + +/* SuiteSparse is not a package itself, but a collection of packages, some of + * which must be used together (UMFPACK requires AMD, CHOLMOD requires AMD, + * COLAMD, CAMD, and CCOLAMD, etc). A version number is provided here for the + * collection itself. The versions of packages within each version of + * SuiteSparse are meant to work together. Combining one packge from one + * version of SuiteSparse, with another package from another version of + * SuiteSparse, may or may not work. + * + * SuiteSparse contains the following packages: + * + * SuiteSparse_config version 4.2.1 (version always the same as SuiteSparse) + * AMD version 2.3.1 + * BTF version 1.2.0 + * CAMD version 2.3.1 + * CCOLAMD version 2.8.0 + * CHOLMOD version 2.1.2 + * COLAMD version 2.8.0 + * CSparse version 3.1.2 + * CXSparse version 3.1.2 + * KLU version 1.2.1 + * LDL version 2.1.0 + * RBio version 2.1.1 + * SPQR version 1.3.1 (full name is SuiteSparseQR) + * UMFPACK version 5.6.2 + * MATLAB_Tools various packages & M-files + * + * Other package dependencies: + * BLAS required by CHOLMOD and UMFPACK + * LAPACK required by CHOLMOD + * METIS 4.0.1 required by CHOLMOD (optional) and KLU (optional) + */ + + +int SuiteSparse_version /* returns SUITESPARSE_VERSION */ +( + /* output, not defined on input. Not used if NULL. Returns + the three version codes in version [0..2]: + version [0] is SUITESPARSE_MAIN_VERSION + version [1] is SUITESPARSE_SUB_VERSION + version [2] is SUITESPARSE_SUBSUB_VERSION + */ + int version [3] +) ; + +/* Versions prior to 4.2.0 do not have the above function. The following + code fragment will work with any version of SuiteSparse: + + #ifdef SUITESPARSE_HAS_VERSION_FUNCTION + v = SuiteSparse_version (NULL) ; + #else + v = SUITESPARSE_VERSION ; + #endif +*/ +#define SUITESPARSE_HAS_VERSION_FUNCTION + +#define SUITESPARSE_DATE "April 25, 2013" +#define SUITESPARSE_VER_CODE(main,sub) ((main) * 1000 + (sub)) +#define SUITESPARSE_MAIN_VERSION 4 +#define SUITESPARSE_SUB_VERSION 2 +#define SUITESPARSE_SUBSUB_VERSION 1 +#define SUITESPARSE_VERSION \ + SUITESPARSE_VER_CODE(SUITESPARSE_MAIN_VERSION,SUITESPARSE_SUB_VERSION) + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/SuiteSparse_config/SuiteSparse_config.mk b/src/SuiteSparse_config/SuiteSparse_config.mk new file mode 100644 index 0000000..52faeff --- /dev/null +++ b/src/SuiteSparse_config/SuiteSparse_config.mk @@ -0,0 +1,393 @@ +#=============================================================================== +# SuiteSparse_config.mk: common configuration file for the SuiteSparse +#=============================================================================== + +# This file contains all configuration settings for all packages authored or +# co-authored by Tim Davis: +# +# Package Version Description +# ------- ------- ----------- +# AMD 1.2 or later approximate minimum degree ordering +# COLAMD 2.4 or later column approximate minimum degree ordering +# CCOLAMD 1.0 or later constrained column approximate minimum degree ordering +# CAMD any constrained approximate minimum degree ordering +# UMFPACK 4.5 or later sparse LU factorization, with the BLAS +# CHOLMOD any sparse Cholesky factorization, update/downdate +# KLU 0.8 or later sparse LU factorization, BLAS-free +# BTF 0.8 or later permutation to block triangular form +# LDL 1.2 or later concise sparse LDL' +# CXSparse any extended version of CSparse (int/long, real/complex) +# SuiteSparseQR any sparse QR factorization +# RBio 2.0 or later read/write sparse matrices in Rutherford-Boeing format +# +# By design, this file is NOT included in the CSparse makefile. +# That package is fully stand-alone. CSparse is primarily for teaching; +# production code should use CXSparse. +# +# The SuiteSparse_config directory and the above packages should all appear in +# a single directory, in order for the Makefile's within each package to find +# this file. +# +# To enable an option of the form "# OPTION = ...", edit this file and +# delete the "#" in the first column of the option you wish to use. +# +# The use of METIS 4.0.1 is optional. To exclude METIS, you must compile with +# CHOLMOD_CONFIG set to -DNPARTITION. See below for details. However, if you +# do not have a metis-4.0 directory inside the SuiteSparse directory, the +# */Makefile's that optionally rely on METIS will automatically detect this +# and compile without METIS. + +#------------------------------------------------------------------------------ +# Generic configuration +#------------------------------------------------------------------------------ + +# Using standard definitions from the make environment, typically: +# +# CC cc C compiler +# CXX g++ C++ compiler +# CFLAGS [ ] flags for C and C++ compiler +# CPPFLAGS [ ] flags for C and C++ compiler +# TARGET_ARCH [ ] target architecture +# FFLAGS [ ] flags for Fortran compiler +# RM rm -f delete a file +# AR ar create a static *.a library archive +# ARFLAGS rv flags for ar +# MAKE make make itself (sometimes called gmake) +# +# You can redefine them here, but by default they are used from the +# default make environment. + +# C and C++ compiler flags. The first three are standard for *.c and *.cpp +# Add -DNTIMER if you do use any timing routines (otherwise -lrt is required). +# CF = $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -O3 -fexceptions -fPIC -DNTIMER + CF = $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -O3 -fexceptions -fPIC + +# ranlib, and ar, for generating libraries. If you don't need ranlib, +# just change it to RANLAB = echo +RANLIB = ranlib +ARCHIVE = $(AR) $(ARFLAGS) + +# copy and delete a file +CP = cp -f +MV = mv -f + +# Fortran compiler (not required for 'make' or 'make library') +F77 = gfortran +F77FLAGS = $(FFLAGS) -O +F77LIB = + +# C and Fortran libraries. Remove -lrt if you don't have it. + LIB = -lm -lrt +# Using the following requires CF = ... -DNTIMER on POSIX C systems. +# LIB = -lm + +# For "make install" +INSTALL_LIB = /usr/local/lib +INSTALL_INCLUDE = /usr/local/include + +# Which version of MAKE you are using (default is "make") +# MAKE = make +# MAKE = gmake + +#------------------------------------------------------------------------------ +# BLAS and LAPACK configuration: +#------------------------------------------------------------------------------ + +# UMFPACK and CHOLMOD both require the BLAS. CHOLMOD also requires LAPACK. +# See Kazushige Goto's BLAS at http://www.cs.utexas.edu/users/flame/goto/ or +# http://www.tacc.utexas.edu/~kgoto/ for the best BLAS to use with CHOLMOD. +# LAPACK is at http://www.netlib.org/lapack/ . You can use the standard +# Fortran LAPACK along with Goto's BLAS to obtain very good performance. +# CHOLMOD gets a peak numeric factorization rate of 3.6 Gflops on a 3.2 GHz +# Pentium 4 (512K cache, 4GB main memory) with the Goto BLAS, and 6 Gflops +# on a 2.5Ghz dual-core AMD Opteron. + +# These settings will probably not work, since there is no fixed convention for +# naming the BLAS and LAPACK library (*.a or *.so) files. + +# This is probably slow ... it might connect to the Standard Reference BLAS: +BLAS = -lblas -lgfortran +LAPACK = -llapack + +# NOTE: this next option for the "Goto BLAS" has nothing to do with a "goto" +# statement. Rather, the Goto BLAS is written by Dr. Kazushige Goto. +# Using the Goto BLAS: +# BLAS = -lgoto -lgfortran -lgfortranbegin +# BLAS = -lgoto2 -lgfortran -lgfortranbegin -lpthread + +# Using non-optimized versions: +# BLAS = -lblas_plain -lgfortran -lgfortranbegin +# LAPACK = -llapack_plain + +# BLAS = -lblas_plain -lgfortran -lgfortranbegin +# LAPACK = -llapack + +# The BLAS might not contain xerbla, an error-handling routine for LAPACK and +# the BLAS. Also, the standard xerbla requires the Fortran I/O library, and +# stops the application program if an error occurs. A C version of xerbla +# distributed with this software (SuiteSparse_config/xerbla/libcerbla.a) +# includes a Fortran-callable xerbla routine that prints nothing and does not +# stop the application program. This is optional. + +# XERBLA = ../../SuiteSparse_config/xerbla/libcerbla.a + +# If you wish to use the XERBLA in LAPACK and/or the BLAS instead, +# use this option: +XERBLA = + +# If you wish to use the Fortran SuiteSparse_config/xerbla/xerbla.f instead, +# use this: + +# XERBLA = ../../SuiteSparse_config/xerbla/libxerbla.a + +#------------------------------------------------------------------------------ +# GPU configuration for CHOLMOD, using the CUDA BLAS +#------------------------------------------------------------------------------ + +# no cuda +GPU_BLAS_PATH = +GPU_CONFIG = + +# with cuda BLAS acceleration for CHOLMOD +# GPU_BLAS_PATH=/usr/local/cuda +# GPU_CONFIG=-DGPU_BLAS -I$(GPU_BLAS_PATH)/include + +#------------------------------------------------------------------------------ +# METIS, optionally used by CHOLMOD +#------------------------------------------------------------------------------ + +# If you do not have METIS, or do not wish to use it in CHOLMOD, you must +# compile CHOLMOD with the -DNPARTITION flag. + +# The path is relative to where it is used, in CHOLMOD/Lib, CHOLMOD/MATLAB, etc. +# You may wish to use an absolute path. METIS is optional. Compile +# CHOLMOD with -DNPARTITION if you do not wish to use METIS. +METIS_PATH = ../../metis-4.0 +METIS = ../../metis-4.0/libmetis.a + +#------------------------------------------------------------------------------ +# UMFPACK configuration: +#------------------------------------------------------------------------------ + +# Configuration flags for UMFPACK. See UMFPACK/Source/umf_config.h for details. +# +# -DNBLAS do not use the BLAS. UMFPACK will be very slow. +# -D'LONGBLAS=long' or -DLONGBLAS='long long' defines the integers used by +# LAPACK and the BLAS (defaults to 'int') +# -DNSUNPERF do not use the Sun Perf. Library (default is use it on Solaris) +# -DNRECIPROCAL do not multiply by the reciprocal +# -DNO_DIVIDE_BY_ZERO do not divide by zero +# -DNCHOLMOD do not use CHOLMOD as a ordering method. If -DNCHOLMOD is +# included in UMFPACK_CONFIG, then UMFPACK does not rely on +# CHOLMOD, CAMD, CCOLAMD, COLAMD, and METIS. + +UMFPACK_CONFIG = + +# uncomment this line to compile UMFPACK without CHOLMOD: +# UMFPACK_CONFIG = -DNCHOLMOD + +#------------------------------------------------------------------------------ +# CHOLMOD configuration +#------------------------------------------------------------------------------ + +# CHOLMOD Library Modules, which appear in libcholmod.a: +# Core requires: none +# Check requires: Core +# Cholesky requires: Core, AMD, COLAMD. optional: Partition, Supernodal +# MatrixOps requires: Core +# Modify requires: Core +# Partition requires: Core, CCOLAMD, METIS. optional: Cholesky +# Supernodal requires: Core, BLAS, LAPACK +# +# CHOLMOD test/demo Modules (all are GNU GPL, do not appear in libcholmod.a): +# Tcov requires: Core, Check, Cholesky, MatrixOps, Modify, Supernodal +# optional: Partition +# Valgrind same as Tcov +# Demo requires: Core, Check, Cholesky, MatrixOps, Supernodal +# optional: Partition +# +# Configuration flags: +# -DNCHECK do not include the Check module. License GNU LGPL +# -DNCHOLESKY do not include the Cholesky module. License GNU LGPL +# -DNPARTITION do not include the Partition module. License GNU LGPL +# also do not include METIS. +# -DNCAMD do not use CAMD, etc from Partition module. GNU LGPL +# -DNGPL do not include any GNU GPL Modules in the CHOLMOD library: +# -DNMATRIXOPS do not include the MatrixOps module. License GNU GPL +# -DNMODIFY do not include the Modify module. License GNU GPL +# -DNSUPERNODAL do not include the Supernodal module. License GNU GPL +# +# -DNPRINT do not print anything. +# -D'LONGBLAS=long' or -DLONGBLAS='long long' defines the integers used by +# LAPACK and the BLAS (defaults to 'int') +# -DNSUNPERF for Solaris only. If defined, do not use the Sun +# Performance Library + +CHOLMOD_CONFIG = $(GPU_CONFIG) + +# uncomment this line to compile CHOLMOD without METIS: +# CHOLMOD_CONFIG = -DNPARTITION + +#------------------------------------------------------------------------------ +# SuiteSparseQR configuration: +#------------------------------------------------------------------------------ + +# The SuiteSparseQR library can be compiled with the following options: +# +# -DNPARTITION do not include the CHOLMOD partition module +# -DNEXPERT do not include the functions in SuiteSparseQR_expert.cpp +# -DHAVE_TBB enable the use of Intel's Threading Building Blocks (TBB) + +# default, without timing, without TBB: +SPQR_CONFIG = +# with TBB: +# SPQR_CONFIG = -DHAVE_TBB + +# This is needed for IBM AIX: (but not for and C codes, just C++) +# SPQR_CONFIG = -DBLAS_NO_UNDERSCORE + +# with TBB, you must select this: +# TBB = -ltbb +# without TBB: +TBB = + +#------------------------------------------------------------------------------ +# Linux +#------------------------------------------------------------------------------ + +# Using default compilers: +# CC = gcc +# CF = $(CFLAGS) -O3 -fexceptions + +# alternatives: +# CF = $(CFLAGS) -g -fexceptions \ + -Wall -W -Wshadow -Wmissing-prototypes -Wstrict-prototypes \ + -Wredundant-decls -Wnested-externs -Wdisabled-optimization -ansi \ + -funit-at-a-time +# CF = $(CFLAGS) -O3 -fexceptions \ + -Wall -W -Werror -Wshadow -Wmissing-prototypes -Wstrict-prototypes \ + -Wredundant-decls -Wnested-externs -Wdisabled-optimization -ansi +# CF = $(CFLAGS) -O3 -fexceptions -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE +# CF = $(CFLAGS) -O3 +# CF = $(CFLAGS) -O3 -g -fexceptions +# CF = $(CFLAGS) -g -fexceptions \ + -Wall -W -Wshadow \ + -Wredundant-decls -Wdisabled-optimization -ansi + +# consider: +# -fforce-addr -fmove-all-movables -freduce-all-givs -ftsp-ordering +# -frename-registers -ffast-math -funroll-loops + +# Using the Goto BLAS: +# BLAS = -lgoto -lfrtbegin -lg2c $(XERBLA) -lpthread + +# Using Intel's icc and ifort compilers: +# (does not work for mexFunctions unless you add a mexopts.sh file) +# F77 = ifort +# CC = icc +# CF = $(CFLAGS) -O3 -xN -vec_report=0 +# CF = $(CFLAGS) -g + +# 64bit: +# F77FLAGS = -O -m64 +# CF = $(CFLAGS) -O3 -fexceptions -m64 +# BLAS = -lgoto64 -lfrtbegin -lg2c -lpthread $(XERBLA) +# LAPACK = -llapack64 + +# SUSE Linux 10.1, AMD Opteron, with GOTO Blas +# F77 = gfortran +# BLAS = -lgoto_opteron64 -lgfortran + +# SUSE Linux 10.1, Intel Pentium, with GOTO Blas +# F77 = gfortran +# BLAS = -lgoto -lgfortran + +#------------------------------------------------------------------------------ +# Mac +#------------------------------------------------------------------------------ + +# As recommended by macports, http://suitesparse.darwinports.com/ +# I've tested them myself on Mac OSX 10.6.1 and 10.6.8 (Snow Leopard), +# on my MacBook Air, and they work fine. + +# F77 = gfortran +# CF = $(CFLAGS) -O3 -fno-common -fexceptions -DNTIMER +# BLAS = -framework Accelerate +# LAPACK = -framework Accelerate +# LIB = -lm + +#------------------------------------------------------------------------------ +# Solaris +#------------------------------------------------------------------------------ + +# 32-bit +# CF = $(CFLAGS) -KPIC -dalign -xc99=%none -Xc -xlibmieee -xO5 -xlibmil -m32 + +# 64-bit +# CF = $(CFLAGS) -fast -KPIC -xc99=%none -xlibmieee -xlibmil -m64 -Xc + +# FFLAGS = -fast -KPIC -dalign -xlibmil -m64 + +# The Sun Performance Library includes both LAPACK and the BLAS: +# BLAS = -xlic_lib=sunperf +# LAPACK = + + +#------------------------------------------------------------------------------ +# Compaq Alpha +#------------------------------------------------------------------------------ + +# 64-bit mode only +# CF = $(CFLAGS) -O2 -std1 +# BLAS = -ldxml +# LAPACK = + +#------------------------------------------------------------------------------ +# IBM RS 6000 +#------------------------------------------------------------------------------ + +# BLAS = -lessl +# LAPACK = + +# 32-bit mode: +# CF = $(CFLAGS) -O4 -qipa -qmaxmem=16384 -qproto +# F77FLAGS = -O4 -qipa -qmaxmem=16384 + +# 64-bit mode: +# CF = $(CFLAGS) -O4 -qipa -qmaxmem=16384 -q64 -qproto +# F77FLAGS = -O4 -qipa -qmaxmem=16384 -q64 + +#------------------------------------------------------------------------------ +# SGI IRIX +#------------------------------------------------------------------------------ + +# BLAS = -lscsl +# LAPACK = + +# 32-bit mode +# CF = $(CFLAGS) -O + +# 64-bit mode (32 bit int's and 64-bit long's): +# CF = $(CFLAGS) -64 +# F77FLAGS = -64 + +# SGI doesn't have ranlib +# RANLIB = echo + +#------------------------------------------------------------------------------ +# AMD Opteron (64 bit) +#------------------------------------------------------------------------------ + +# BLAS = -lgoto_opteron64 -lg2c +# LAPACK = -llapack_opteron64 + +# SUSE Linux 10.1, AMD Opteron +# F77 = gfortran +# BLAS = -lgoto_opteron64 -lgfortran +# LAPACK = -llapack_opteron64 + +#------------------------------------------------------------------------------ +# remove object files and profile output +#------------------------------------------------------------------------------ + +CLEAN = *.o *.obj *.ln *.bb *.bbg *.da *.tcov *.gcov gmon.out *.bak *.d *.gcda *.gcno diff --git a/src/SuiteSparse_config/SuiteSparse_config_GPU.mk b/src/SuiteSparse_config/SuiteSparse_config_GPU.mk new file mode 100644 index 0000000..b56f209 --- /dev/null +++ b/src/SuiteSparse_config/SuiteSparse_config_GPU.mk @@ -0,0 +1,393 @@ +#=============================================================================== +# SuiteSparse_config.mk: common configuration file for the SuiteSparse +#=============================================================================== + +# This file contains all configuration settings for all packages authored or +# co-authored by Tim Davis: +# +# Package Version Description +# ------- ------- ----------- +# AMD 1.2 or later approximate minimum degree ordering +# COLAMD 2.4 or later column approximate minimum degree ordering +# CCOLAMD 1.0 or later constrained column approximate minimum degree ordering +# CAMD any constrained approximate minimum degree ordering +# UMFPACK 4.5 or later sparse LU factorization, with the BLAS +# CHOLMOD any sparse Cholesky factorization, update/downdate +# KLU 0.8 or later sparse LU factorization, BLAS-free +# BTF 0.8 or later permutation to block triangular form +# LDL 1.2 or later concise sparse LDL' +# CXSparse any extended version of CSparse (int/long, real/complex) +# SuiteSparseQR any sparse QR factorization +# RBio 2.0 or later read/write sparse matrices in Rutherford-Boeing format +# +# By design, this file is NOT included in the CSparse makefile. +# That package is fully stand-alone. CSparse is primarily for teaching; +# production code should use CXSparse. +# +# The SuiteSparse_config directory and the above packages should all appear in +# a single directory, in order for the Makefile's within each package to find +# this file. +# +# To enable an option of the form "# OPTION = ...", edit this file and +# delete the "#" in the first column of the option you wish to use. +# +# The use of METIS 4.0.1 is optional. To exclude METIS, you must compile with +# CHOLMOD_CONFIG set to -DNPARTITION. See below for details. However, if you +# do not have a metis-4.0 directory inside the SuiteSparse directory, the +# */Makefile's that optionally rely on METIS will automatically detect this +# and compile without METIS. + +#------------------------------------------------------------------------------ +# Generic configuration +#------------------------------------------------------------------------------ + +# Using standard definitions from the make environment, typically: +# +# CC cc C compiler +# CXX g++ C++ compiler +# CFLAGS [ ] flags for C and C++ compiler +# CPPFLAGS [ ] flags for C and C++ compiler +# TARGET_ARCH [ ] target architecture +# FFLAGS [ ] flags for Fortran compiler +# RM rm -f delete a file +# AR ar create a static *.a library archive +# ARFLAGS rv flags for ar +# MAKE make make itself (sometimes called gmake) +# +# You can redefine them here, but by default they are used from the +# default make environment. + +# C and C++ compiler flags. The first three are standard for *.c and *.cpp +# Add -DNTIMER if you do use any timing routines (otherwise -lrt is required). +# CF = $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -O3 -fexceptions -fPIC -DNTIMER + CF = $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -O3 -fexceptions -fPIC + +# ranlib, and ar, for generating libraries. If you don't need ranlib, +# just change it to RANLAB = echo +RANLIB = ranlib +ARCHIVE = $(AR) $(ARFLAGS) + +# copy and delete a file +CP = cp -f +MV = mv -f + +# Fortran compiler (not required for 'make' or 'make library') +F77 = gfortran +F77FLAGS = $(FFLAGS) -O +F77LIB = + +# C and Fortran libraries. Remove -lrt if you don't have it. + LIB = -lm -lrt +# Using the following requires CF = ... -DNTIMER on POSIX C systems. +# LIB = -lm + +# For "make install" +INSTALL_LIB = /usr/local/lib +INSTALL_INCLUDE = /usr/local/include + +# Which version of MAKE you are using (default is "make") +# MAKE = make +# MAKE = gmake + +#------------------------------------------------------------------------------ +# BLAS and LAPACK configuration: +#------------------------------------------------------------------------------ + +# UMFPACK and CHOLMOD both require the BLAS. CHOLMOD also requires LAPACK. +# See Kazushige Goto's BLAS at http://www.cs.utexas.edu/users/flame/goto/ or +# http://www.tacc.utexas.edu/~kgoto/ for the best BLAS to use with CHOLMOD. +# LAPACK is at http://www.netlib.org/lapack/ . You can use the standard +# Fortran LAPACK along with Goto's BLAS to obtain very good performance. +# CHOLMOD gets a peak numeric factorization rate of 3.6 Gflops on a 3.2 GHz +# Pentium 4 (512K cache, 4GB main memory) with the Goto BLAS, and 6 Gflops +# on a 2.5Ghz dual-core AMD Opteron. + +# These settings will probably not work, since there is no fixed convention for +# naming the BLAS and LAPACK library (*.a or *.so) files. + +# This is probably slow ... it might connect to the Standard Reference BLAS: +BLAS = -lblas -lgfortran +LAPACK = -llapack + +# NOTE: this next option for the "Goto BLAS" has nothing to do with a "goto" +# statement. Rather, the Goto BLAS is written by Dr. Kazushige Goto. +# Using the Goto BLAS: +# BLAS = -lgoto -lgfortran -lgfortranbegin +# BLAS = -lgoto2 -lgfortran -lgfortranbegin -lpthread + +# Using non-optimized versions: +# BLAS = -lblas_plain -lgfortran -lgfortranbegin +# LAPACK = -llapack_plain + +# BLAS = -lblas_plain -lgfortran -lgfortranbegin +# LAPACK = -llapack + +# The BLAS might not contain xerbla, an error-handling routine for LAPACK and +# the BLAS. Also, the standard xerbla requires the Fortran I/O library, and +# stops the application program if an error occurs. A C version of xerbla +# distributed with this software (SuiteSparse_config/xerbla/libcerbla.a) +# includes a Fortran-callable xerbla routine that prints nothing and does not +# stop the application program. This is optional. + +# XERBLA = ../../SuiteSparse_config/xerbla/libcerbla.a + +# If you wish to use the XERBLA in LAPACK and/or the BLAS instead, +# use this option: +XERBLA = + +# If you wish to use the Fortran SuiteSparse_config/xerbla/xerbla.f instead, +# use this: + +# XERBLA = ../../SuiteSparse_config/xerbla/libxerbla.a + +#------------------------------------------------------------------------------ +# GPU configuration for CHOLMOD, using the CUDA BLAS +#------------------------------------------------------------------------------ + +# no cuda +# GPU_BLAS_PATH = +# GPU_CONFIG = + +# with cuda BLAS acceleration for CHOLMOD + GPU_BLAS_PATH=/usr/local/cuda + GPU_CONFIG=-DGPU_BLAS -I$(GPU_BLAS_PATH)/include + +#------------------------------------------------------------------------------ +# METIS, optionally used by CHOLMOD +#------------------------------------------------------------------------------ + +# If you do not have METIS, or do not wish to use it in CHOLMOD, you must +# compile CHOLMOD with the -DNPARTITION flag. + +# The path is relative to where it is used, in CHOLMOD/Lib, CHOLMOD/MATLAB, etc. +# You may wish to use an absolute path. METIS is optional. Compile +# CHOLMOD with -DNPARTITION if you do not wish to use METIS. +METIS_PATH = ../../metis-4.0 +METIS = ../../metis-4.0/libmetis.a + +#------------------------------------------------------------------------------ +# UMFPACK configuration: +#------------------------------------------------------------------------------ + +# Configuration flags for UMFPACK. See UMFPACK/Source/umf_config.h for details. +# +# -DNBLAS do not use the BLAS. UMFPACK will be very slow. +# -D'LONGBLAS=long' or -DLONGBLAS='long long' defines the integers used by +# LAPACK and the BLAS (defaults to 'int') +# -DNSUNPERF do not use the Sun Perf. Library (default is use it on Solaris) +# -DNRECIPROCAL do not multiply by the reciprocal +# -DNO_DIVIDE_BY_ZERO do not divide by zero +# -DNCHOLMOD do not use CHOLMOD as a ordering method. If -DNCHOLMOD is +# included in UMFPACK_CONFIG, then UMFPACK does not rely on +# CHOLMOD, CAMD, CCOLAMD, COLAMD, and METIS. + +UMFPACK_CONFIG = + +# uncomment this line to compile UMFPACK without CHOLMOD: +# UMFPACK_CONFIG = -DNCHOLMOD + +#------------------------------------------------------------------------------ +# CHOLMOD configuration +#------------------------------------------------------------------------------ + +# CHOLMOD Library Modules, which appear in libcholmod.a: +# Core requires: none +# Check requires: Core +# Cholesky requires: Core, AMD, COLAMD. optional: Partition, Supernodal +# MatrixOps requires: Core +# Modify requires: Core +# Partition requires: Core, CCOLAMD, METIS. optional: Cholesky +# Supernodal requires: Core, BLAS, LAPACK +# +# CHOLMOD test/demo Modules (all are GNU GPL, do not appear in libcholmod.a): +# Tcov requires: Core, Check, Cholesky, MatrixOps, Modify, Supernodal +# optional: Partition +# Valgrind same as Tcov +# Demo requires: Core, Check, Cholesky, MatrixOps, Supernodal +# optional: Partition +# +# Configuration flags: +# -DNCHECK do not include the Check module. License GNU LGPL +# -DNCHOLESKY do not include the Cholesky module. License GNU LGPL +# -DNPARTITION do not include the Partition module. License GNU LGPL +# also do not include METIS. +# -DNCAMD do not use CAMD, etc from Partition module. GNU LGPL +# -DNGPL do not include any GNU GPL Modules in the CHOLMOD library: +# -DNMATRIXOPS do not include the MatrixOps module. License GNU GPL +# -DNMODIFY do not include the Modify module. License GNU GPL +# -DNSUPERNODAL do not include the Supernodal module. License GNU GPL +# +# -DNPRINT do not print anything. +# -D'LONGBLAS=long' or -DLONGBLAS='long long' defines the integers used by +# LAPACK and the BLAS (defaults to 'int') +# -DNSUNPERF for Solaris only. If defined, do not use the Sun +# Performance Library + +CHOLMOD_CONFIG = $(GPU_CONFIG) + +# uncomment this line to compile CHOLMOD without METIS: +# CHOLMOD_CONFIG = -DNPARTITION + +#------------------------------------------------------------------------------ +# SuiteSparseQR configuration: +#------------------------------------------------------------------------------ + +# The SuiteSparseQR library can be compiled with the following options: +# +# -DNPARTITION do not include the CHOLMOD partition module +# -DNEXPERT do not include the functions in SuiteSparseQR_expert.cpp +# -DHAVE_TBB enable the use of Intel's Threading Building Blocks (TBB) + +# default, without timing, without TBB: +SPQR_CONFIG = +# with TBB: +# SPQR_CONFIG = -DHAVE_TBB + +# This is needed for IBM AIX: (but not for and C codes, just C++) +# SPQR_CONFIG = -DBLAS_NO_UNDERSCORE + +# with TBB, you must select this: +# TBB = -ltbb +# without TBB: +TBB = + +#------------------------------------------------------------------------------ +# Linux +#------------------------------------------------------------------------------ + +# Using default compilers: +# CC = gcc +# CF = $(CFLAGS) -O3 -fexceptions + +# alternatives: +# CF = $(CFLAGS) -g -fexceptions \ + -Wall -W -Wshadow -Wmissing-prototypes -Wstrict-prototypes \ + -Wredundant-decls -Wnested-externs -Wdisabled-optimization -ansi \ + -funit-at-a-time +# CF = $(CFLAGS) -O3 -fexceptions \ + -Wall -W -Werror -Wshadow -Wmissing-prototypes -Wstrict-prototypes \ + -Wredundant-decls -Wnested-externs -Wdisabled-optimization -ansi +# CF = $(CFLAGS) -O3 -fexceptions -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE +# CF = $(CFLAGS) -O3 +# CF = $(CFLAGS) -O3 -g -fexceptions +# CF = $(CFLAGS) -g -fexceptions \ + -Wall -W -Wshadow \ + -Wredundant-decls -Wdisabled-optimization -ansi + +# consider: +# -fforce-addr -fmove-all-movables -freduce-all-givs -ftsp-ordering +# -frename-registers -ffast-math -funroll-loops + +# Using the Goto BLAS: +# BLAS = -lgoto -lfrtbegin -lg2c $(XERBLA) -lpthread + +# Using Intel's icc and ifort compilers: +# (does not work for mexFunctions unless you add a mexopts.sh file) +# F77 = ifort +# CC = icc +# CF = $(CFLAGS) -O3 -xN -vec_report=0 +# CF = $(CFLAGS) -g + +# 64bit: +# F77FLAGS = -O -m64 +# CF = $(CFLAGS) -O3 -fexceptions -m64 +# BLAS = -lgoto64 -lfrtbegin -lg2c -lpthread $(XERBLA) +# LAPACK = -llapack64 + +# SUSE Linux 10.1, AMD Opteron, with GOTO Blas +# F77 = gfortran +# BLAS = -lgoto_opteron64 -lgfortran + +# SUSE Linux 10.1, Intel Pentium, with GOTO Blas +# F77 = gfortran +# BLAS = -lgoto -lgfortran + +#------------------------------------------------------------------------------ +# Mac +#------------------------------------------------------------------------------ + +# As recommended by macports, http://suitesparse.darwinports.com/ +# I've tested them myself on Mac OSX 10.6.1 and 10.6.8 (Snow Leopard), +# on my MacBook Air, and they work fine. + +# F77 = gfortran +# CF = $(CFLAGS) -O3 -fno-common -fexceptions -DNTIMER +# BLAS = -framework Accelerate +# LAPACK = -framework Accelerate +# LIB = -lm + +#------------------------------------------------------------------------------ +# Solaris +#------------------------------------------------------------------------------ + +# 32-bit +# CF = $(CFLAGS) -KPIC -dalign -xc99=%none -Xc -xlibmieee -xO5 -xlibmil -m32 + +# 64-bit +# CF = $(CFLAGS) -fast -KPIC -xc99=%none -xlibmieee -xlibmil -m64 -Xc + +# FFLAGS = -fast -KPIC -dalign -xlibmil -m64 + +# The Sun Performance Library includes both LAPACK and the BLAS: +# BLAS = -xlic_lib=sunperf +# LAPACK = + + +#------------------------------------------------------------------------------ +# Compaq Alpha +#------------------------------------------------------------------------------ + +# 64-bit mode only +# CF = $(CFLAGS) -O2 -std1 +# BLAS = -ldxml +# LAPACK = + +#------------------------------------------------------------------------------ +# IBM RS 6000 +#------------------------------------------------------------------------------ + +# BLAS = -lessl +# LAPACK = + +# 32-bit mode: +# CF = $(CFLAGS) -O4 -qipa -qmaxmem=16384 -qproto +# F77FLAGS = -O4 -qipa -qmaxmem=16384 + +# 64-bit mode: +# CF = $(CFLAGS) -O4 -qipa -qmaxmem=16384 -q64 -qproto +# F77FLAGS = -O4 -qipa -qmaxmem=16384 -q64 + +#------------------------------------------------------------------------------ +# SGI IRIX +#------------------------------------------------------------------------------ + +# BLAS = -lscsl +# LAPACK = + +# 32-bit mode +# CF = $(CFLAGS) -O + +# 64-bit mode (32 bit int's and 64-bit long's): +# CF = $(CFLAGS) -64 +# F77FLAGS = -64 + +# SGI doesn't have ranlib +# RANLIB = echo + +#------------------------------------------------------------------------------ +# AMD Opteron (64 bit) +#------------------------------------------------------------------------------ + +# BLAS = -lgoto_opteron64 -lg2c +# LAPACK = -llapack_opteron64 + +# SUSE Linux 10.1, AMD Opteron +# F77 = gfortran +# BLAS = -lgoto_opteron64 -lgfortran +# LAPACK = -llapack_opteron64 + +#------------------------------------------------------------------------------ +# remove object files and profile output +#------------------------------------------------------------------------------ + +CLEAN = *.o *.obj *.ln *.bb *.bbg *.da *.tcov *.gcov gmon.out *.bak *.d *.gcda *.gcno diff --git a/src/SuiteSparse_config/SuiteSparse_config_Mac.mk b/src/SuiteSparse_config/SuiteSparse_config_Mac.mk new file mode 100644 index 0000000..811e99c --- /dev/null +++ b/src/SuiteSparse_config/SuiteSparse_config_Mac.mk @@ -0,0 +1,395 @@ +#=============================================================================== +# SuiteSparse_config_Mac.mk: Mac configuration file for the SuiteSparse +# To use this configuration, delete the SuiteSparse_config.mk file that +# comes with SuiteSparse and rename this file as SuiteSparse_config.mk . +#=============================================================================== + +# This file contains all configuration settings for all packages authored or +# co-authored by Tim Davis: +# +# Package Version Description +# ------- ------- ----------- +# AMD 1.2 or later approximate minimum degree ordering +# COLAMD 2.4 or later column approximate minimum degree ordering +# CCOLAMD 1.0 or later constrained column approximate minimum degree ordering +# CAMD any constrained approximate minimum degree ordering +# UMFPACK 4.5 or later sparse LU factorization, with the BLAS +# CHOLMOD any sparse Cholesky factorization, update/downdate +# KLU 0.8 or later sparse LU factorization, BLAS-free +# BTF 0.8 or later permutation to block triangular form +# LDL 1.2 or later concise sparse LDL' +# CXSparse any extended version of CSparse (int/long, real/complex) +# SuiteSparseQR any sparse QR factorization +# RBio 2.0 or later read/write sparse matrices in Rutherford-Boeing format +# +# By design, this file is NOT included in the CSparse makefile. +# That package is fully stand-alone. CSparse is primarily for teaching; +# production code should use CXSparse. +# +# The SuiteSparse_config directory and the above packages should all appear in +# a single directory, in order for the Makefile's within each package to find +# this file. +# +# To enable an option of the form "# OPTION = ...", edit this file and +# delete the "#" in the first column of the option you wish to use. +# +# The use of METIS 4.0.1 is optional. To exclude METIS, you must compile with +# CHOLMOD_CONFIG set to -DNPARTITION. See below for details. However, if you +# do not have a metis-4.0 directory inside the SuiteSparse directory, the +# */Makefile's that optionally rely on METIS will automatically detect this +# and compile without METIS. + +#------------------------------------------------------------------------------ +# Generic configuration +#------------------------------------------------------------------------------ + +# Using standard definitions from the make environment, typically: +# +# CC cc C compiler +# CXX g++ C++ compiler +# CFLAGS [ ] flags for C and C++ compiler +# CPPFLAGS [ ] flags for C and C++ compiler +# TARGET_ARCH [ ] target architecture +# FFLAGS [ ] flags for Fortran compiler +# RM rm -f delete a file +# AR ar create a static *.a library archive +# ARFLAGS rv flags for ar +# MAKE make make itself (sometimes called gmake) +# +# You can redefine them here, but by default they are used from the +# default make environment. + +# C and C++ compiler flags. The first three are standard for *.c and *.cpp +# Add -DNTIMER if you do use any timing routines (otherwise -lrt is required). +# CF = $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -O3 -fexceptions -fPIC -DNTIMER + CF = $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -O3 -fexceptions -fPIC + +# ranlib, and ar, for generating libraries. If you don't need ranlib, +# just change it to RANLAB = echo +RANLIB = ranlib +ARCHIVE = $(AR) $(ARFLAGS) + +# copy and delete a file +CP = cp -f +MV = mv -f + +# Fortran compiler (not required for 'make' or 'make library') +F77 = gfortran +F77FLAGS = $(FFLAGS) -O +F77LIB = + +# C and Fortran libraries. Remove -lrt if you don't have it. + LIB = -lm -lrt +# Using the following requires CF = ... -DNTIMER on POSIX C systems. +# LIB = -lm + +# For "make install" +INSTALL_LIB = /usr/local/lib +INSTALL_INCLUDE = /usr/local/include + +# Which version of MAKE you are using (default is "make") +# MAKE = make +# MAKE = gmake + +#------------------------------------------------------------------------------ +# BLAS and LAPACK configuration: +#------------------------------------------------------------------------------ + +# UMFPACK and CHOLMOD both require the BLAS. CHOLMOD also requires LAPACK. +# See Kazushige Goto's BLAS at http://www.cs.utexas.edu/users/flame/goto/ or +# http://www.tacc.utexas.edu/~kgoto/ for the best BLAS to use with CHOLMOD. +# LAPACK is at http://www.netlib.org/lapack/ . You can use the standard +# Fortran LAPACK along with Goto's BLAS to obtain very good performance. +# CHOLMOD gets a peak numeric factorization rate of 3.6 Gflops on a 3.2 GHz +# Pentium 4 (512K cache, 4GB main memory) with the Goto BLAS, and 6 Gflops +# on a 2.5Ghz dual-core AMD Opteron. + +# These settings will probably not work, since there is no fixed convention for +# naming the BLAS and LAPACK library (*.a or *.so) files. + +# This is probably slow ... it might connect to the Standard Reference BLAS: +BLAS = -lblas -lgfortran +LAPACK = -llapack + +# NOTE: this next option for the "Goto BLAS" has nothing to do with a "goto" +# statement. Rather, the Goto BLAS is written by Dr. Kazushige Goto. +# Using the Goto BLAS: +# BLAS = -lgoto -lgfortran -lgfortranbegin +# BLAS = -lgoto2 -lgfortran -lgfortranbegin -lpthread + +# Using non-optimized versions: +# BLAS = -lblas_plain -lgfortran -lgfortranbegin +# LAPACK = -llapack_plain + +# BLAS = -lblas_plain -lgfortran -lgfortranbegin +# LAPACK = -llapack + +# The BLAS might not contain xerbla, an error-handling routine for LAPACK and +# the BLAS. Also, the standard xerbla requires the Fortran I/O library, and +# stops the application program if an error occurs. A C version of xerbla +# distributed with this software (SuiteSparse_config/xerbla/libcerbla.a) +# includes a Fortran-callable xerbla routine that prints nothing and does not +# stop the application program. This is optional. + +# XERBLA = ../../SuiteSparse_config/xerbla/libcerbla.a + +# If you wish to use the XERBLA in LAPACK and/or the BLAS instead, +# use this option: +XERBLA = + +# If you wish to use the Fortran SuiteSparse_config/xerbla/xerbla.f instead, +# use this: + +# XERBLA = ../../SuiteSparse_config/xerbla/libxerbla.a + +#------------------------------------------------------------------------------ +# GPU configuration for CHOLMOD, using the CUDA BLAS +#------------------------------------------------------------------------------ + +# no cuda +GPU_BLAS_PATH = +GPU_CONFIG = + +# with cuda BLAS acceleration for CHOLMOD +# GPU_BLAS_PATH=/usr/local/cuda +# GPU_CONFIG=-DGPU_BLAS -I$(GPU_BLAS_PATH)/include + +#------------------------------------------------------------------------------ +# METIS, optionally used by CHOLMOD +#------------------------------------------------------------------------------ + +# If you do not have METIS, or do not wish to use it in CHOLMOD, you must +# compile CHOLMOD with the -DNPARTITION flag. + +# The path is relative to where it is used, in CHOLMOD/Lib, CHOLMOD/MATLAB, etc. +# You may wish to use an absolute path. METIS is optional. Compile +# CHOLMOD with -DNPARTITION if you do not wish to use METIS. +METIS_PATH = ../../metis-4.0 +METIS = ../../metis-4.0/libmetis.a + +#------------------------------------------------------------------------------ +# UMFPACK configuration: +#------------------------------------------------------------------------------ + +# Configuration flags for UMFPACK. See UMFPACK/Source/umf_config.h for details. +# +# -DNBLAS do not use the BLAS. UMFPACK will be very slow. +# -D'LONGBLAS=long' or -DLONGBLAS='long long' defines the integers used by +# LAPACK and the BLAS (defaults to 'int') +# -DNSUNPERF do not use the Sun Perf. Library (default is use it on Solaris) +# -DNRECIPROCAL do not multiply by the reciprocal +# -DNO_DIVIDE_BY_ZERO do not divide by zero +# -DNCHOLMOD do not use CHOLMOD as a ordering method. If -DNCHOLMOD is +# included in UMFPACK_CONFIG, then UMFPACK does not rely on +# CHOLMOD, CAMD, CCOLAMD, COLAMD, and METIS. + +UMFPACK_CONFIG = + +# uncomment this line to compile UMFPACK without CHOLMOD: +# UMFPACK_CONFIG = -DNCHOLMOD + +#------------------------------------------------------------------------------ +# CHOLMOD configuration +#------------------------------------------------------------------------------ + +# CHOLMOD Library Modules, which appear in libcholmod.a: +# Core requires: none +# Check requires: Core +# Cholesky requires: Core, AMD, COLAMD. optional: Partition, Supernodal +# MatrixOps requires: Core +# Modify requires: Core +# Partition requires: Core, CCOLAMD, METIS. optional: Cholesky +# Supernodal requires: Core, BLAS, LAPACK +# +# CHOLMOD test/demo Modules (all are GNU GPL, do not appear in libcholmod.a): +# Tcov requires: Core, Check, Cholesky, MatrixOps, Modify, Supernodal +# optional: Partition +# Valgrind same as Tcov +# Demo requires: Core, Check, Cholesky, MatrixOps, Supernodal +# optional: Partition +# +# Configuration flags: +# -DNCHECK do not include the Check module. License GNU LGPL +# -DNCHOLESKY do not include the Cholesky module. License GNU LGPL +# -DNPARTITION do not include the Partition module. License GNU LGPL +# also do not include METIS. +# -DNCAMD do not use CAMD, etc from Partition module. GNU LGPL +# -DNGPL do not include any GNU GPL Modules in the CHOLMOD library: +# -DNMATRIXOPS do not include the MatrixOps module. License GNU GPL +# -DNMODIFY do not include the Modify module. License GNU GPL +# -DNSUPERNODAL do not include the Supernodal module. License GNU GPL +# +# -DNPRINT do not print anything. +# -D'LONGBLAS=long' or -DLONGBLAS='long long' defines the integers used by +# LAPACK and the BLAS (defaults to 'int') +# -DNSUNPERF for Solaris only. If defined, do not use the Sun +# Performance Library + +CHOLMOD_CONFIG = $(GPU_CONFIG) + +# uncomment this line to compile CHOLMOD without METIS: +# CHOLMOD_CONFIG = -DNPARTITION + +#------------------------------------------------------------------------------ +# SuiteSparseQR configuration: +#------------------------------------------------------------------------------ + +# The SuiteSparseQR library can be compiled with the following options: +# +# -DNPARTITION do not include the CHOLMOD partition module +# -DNEXPERT do not include the functions in SuiteSparseQR_expert.cpp +# -DHAVE_TBB enable the use of Intel's Threading Building Blocks (TBB) + +# default, without timing, without TBB: +SPQR_CONFIG = +# with TBB: +# SPQR_CONFIG = -DHAVE_TBB + +# This is needed for IBM AIX: (but not for and C codes, just C++) +# SPQR_CONFIG = -DBLAS_NO_UNDERSCORE + +# with TBB, you must select this: +# TBB = -ltbb +# without TBB: +TBB = + +#------------------------------------------------------------------------------ +# Linux +#------------------------------------------------------------------------------ + +# Using default compilers: +# CC = gcc +# CF = $(CFLAGS) -O3 -fexceptions + +# alternatives: +# CF = $(CFLAGS) -g -fexceptions \ + -Wall -W -Wshadow -Wmissing-prototypes -Wstrict-prototypes \ + -Wredundant-decls -Wnested-externs -Wdisabled-optimization -ansi \ + -funit-at-a-time +# CF = $(CFLAGS) -O3 -fexceptions \ + -Wall -W -Werror -Wshadow -Wmissing-prototypes -Wstrict-prototypes \ + -Wredundant-decls -Wnested-externs -Wdisabled-optimization -ansi +# CF = $(CFLAGS) -O3 -fexceptions -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE +# CF = $(CFLAGS) -O3 +# CF = $(CFLAGS) -O3 -g -fexceptions +# CF = $(CFLAGS) -g -fexceptions \ + -Wall -W -Wshadow \ + -Wredundant-decls -Wdisabled-optimization -ansi + +# consider: +# -fforce-addr -fmove-all-movables -freduce-all-givs -ftsp-ordering +# -frename-registers -ffast-math -funroll-loops + +# Using the Goto BLAS: +# BLAS = -lgoto -lfrtbegin -lg2c $(XERBLA) -lpthread + +# Using Intel's icc and ifort compilers: +# (does not work for mexFunctions unless you add a mexopts.sh file) +# F77 = ifort +# CC = icc +# CF = $(CFLAGS) -O3 -xN -vec_report=0 +# CF = $(CFLAGS) -g + +# 64bit: +# F77FLAGS = -O -m64 +# CF = $(CFLAGS) -O3 -fexceptions -m64 +# BLAS = -lgoto64 -lfrtbegin -lg2c -lpthread $(XERBLA) +# LAPACK = -llapack64 + +# SUSE Linux 10.1, AMD Opteron, with GOTO Blas +# F77 = gfortran +# BLAS = -lgoto_opteron64 -lgfortran + +# SUSE Linux 10.1, Intel Pentium, with GOTO Blas +# F77 = gfortran +# BLAS = -lgoto -lgfortran + +#------------------------------------------------------------------------------ +# Mac +#------------------------------------------------------------------------------ + +# As recommended by macports, http://suitesparse.darwinports.com/ +# I've tested them myself on Mac OSX 10.6.1 and 10.6.8 (Snow Leopard), +# on my MacBook Air, and they work fine. + + F77 = gfortran + CF = $(CFLAGS) -O3 -fno-common -fexceptions -DNTIMER + BLAS = -framework Accelerate + LAPACK = -framework Accelerate + LIB = -lm + +#------------------------------------------------------------------------------ +# Solaris +#------------------------------------------------------------------------------ + +# 32-bit +# CF = $(CFLAGS) -KPIC -dalign -xc99=%none -Xc -xlibmieee -xO5 -xlibmil -m32 + +# 64-bit +# CF = $(CFLAGS) -fast -KPIC -xc99=%none -xlibmieee -xlibmil -m64 -Xc + +# FFLAGS = -fast -KPIC -dalign -xlibmil -m64 + +# The Sun Performance Library includes both LAPACK and the BLAS: +# BLAS = -xlic_lib=sunperf +# LAPACK = + + +#------------------------------------------------------------------------------ +# Compaq Alpha +#------------------------------------------------------------------------------ + +# 64-bit mode only +# CF = $(CFLAGS) -O2 -std1 +# BLAS = -ldxml +# LAPACK = + +#------------------------------------------------------------------------------ +# IBM RS 6000 +#------------------------------------------------------------------------------ + +# BLAS = -lessl +# LAPACK = + +# 32-bit mode: +# CF = $(CFLAGS) -O4 -qipa -qmaxmem=16384 -qproto +# F77FLAGS = -O4 -qipa -qmaxmem=16384 + +# 64-bit mode: +# CF = $(CFLAGS) -O4 -qipa -qmaxmem=16384 -q64 -qproto +# F77FLAGS = -O4 -qipa -qmaxmem=16384 -q64 + +#------------------------------------------------------------------------------ +# SGI IRIX +#------------------------------------------------------------------------------ + +# BLAS = -lscsl +# LAPACK = + +# 32-bit mode +# CF = $(CFLAGS) -O + +# 64-bit mode (32 bit int's and 64-bit long's): +# CF = $(CFLAGS) -64 +# F77FLAGS = -64 + +# SGI doesn't have ranlib +# RANLIB = echo + +#------------------------------------------------------------------------------ +# AMD Opteron (64 bit) +#------------------------------------------------------------------------------ + +# BLAS = -lgoto_opteron64 -lg2c +# LAPACK = -llapack_opteron64 + +# SUSE Linux 10.1, AMD Opteron +# F77 = gfortran +# BLAS = -lgoto_opteron64 -lgfortran +# LAPACK = -llapack_opteron64 + +#------------------------------------------------------------------------------ +# remove object files and profile output +#------------------------------------------------------------------------------ + +CLEAN = *.o *.obj *.ln *.bb *.bbg *.da *.tcov *.gcov gmon.out *.bak *.d *.gcda *.gcno diff --git a/src/adjlist.c b/src/adjlist.c new file mode 100644 index 0000000..b69b6f2 --- /dev/null +++ b/src/adjlist.c @@ -0,0 +1,930 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_adjlist.h" +#include "igraph_memory.h" +#include "igraph_interface.h" +#include "igraph_interrupt_internal.h" +#include "config.h" + +#include /* memset */ +#include + +/** + * \section about_adjlists + * Sometimes it is easier to work with a graph which is in + * adjacency list format: a list of vectors; each vector contains the + * neighbor vertices or incident edges of a given vertex. Typically, + * this representation is good if we need to iterate over the neighbors + * of all vertices many times. E.g. when finding the shortest paths + * between every pairs of vertices or calculating closeness centrality + * for all the vertices. + * + * The igraph_adjlist_t stores the adjacency lists + * of a graph. After creation it is independent of the original graph, + * it can be modified freely with the usual vector operations, the + * graph is not affected. E.g. the adjacency list can be used to + * rewire the edges of a graph efficiently. If one used the + * straightforward \ref igraph_delete_edges() and \ref + * igraph_add_edges() combination for this that needs O(|V|+|E|) time + * for every single deletion and insertion operation, it is thus very + * slow if many edges are rewired. Extracting the graph into an + * adjacency list, do all the rewiring operations on the vectors of + * the adjacency list and then creating a new graph needs (depending + * on how exactly the rewiring is done) typically O(|V|+|E|) time for + * the whole rewiring process. + * + * Lazy adjacency lists are a bit different. When creating a + * lazy adjacency list, the neighbors of the vertices are not queried, + * only some memory is allocated for the vectors. When \ref + * igraph_lazy_adjlist_get() is called for vertex v the first time, + * the neighbors of v are queried and stored in a vector of the + * adjacency list, so they don't need to be queried again. Lazy + * adjacency lists are handy if you have an at least linear operation + * (because initialization is generally linear in terms of number of + * vertices), but you don't know how many vertices you will visit + * during the computation. + * + * + * + * \example examples/simple/adjlist.c + * + */ + +/** + * \function igraph_adjlist_init + * Initialize an adjacency list of vertices from a given graph + * + * Create a list of vectors containing the neighbors of all vertices + * in a graph. The adjacency list is independent of the graph after + * creation, e.g. the graph can be destroyed and modified, the + * adjacency list contains the state of the graph at the time of its + * initialization. + * \param graph The input graph. + * \param al Pointer to an uninitialized igraph_adjlist_t object. + * \param mode Constant specifying whether outgoing + * (IGRAPH_OUT), incoming (IGRAPH_IN), + * or both (IGRAPH_ALL) types of neighbors to include + * in the adjacency list. It is ignored for undirected networks. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ + +int igraph_adjlist_init(const igraph_t *graph, igraph_adjlist_t *al, + igraph_neimode_t mode) { + igraph_integer_t i; + igraph_vector_t tmp; + + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannot create adjlist view", IGRAPH_EINVMODE); + } + + igraph_vector_init(&tmp, 0); + IGRAPH_FINALLY(igraph_vector_destroy, &tmp); + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + al->length = igraph_vcount(graph); + al->adjs = igraph_Calloc(al->length, igraph_vector_int_t); + if (al->adjs == 0) { + IGRAPH_ERROR("Cannot create adjlist view", IGRAPH_ENOMEM); + } + + IGRAPH_FINALLY(igraph_adjlist_destroy, al); + for (i = 0; i < al->length; i++) { + int j, n; + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_CHECK(igraph_neighbors(graph, &tmp, i, mode)); + n = igraph_vector_size(&tmp); + IGRAPH_CHECK(igraph_vector_int_init(&al->adjs[i], n)); + for (j = 0; j < n; j++) { + VECTOR(al->adjs[i])[j] = VECTOR(tmp)[j]; + } + } + + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/** + * \function igraph_adjlist_init_empty + * Initialize an empty adjacency list + * + * Creates a list of vectors, one for each vertex. This is useful when you + * are \em constructing a graph using an adjacency list representation as + * it does not require your graph to exist yet. + * \param no_of_nodes The number of vertices + * \param al Pointer to an uninitialized igraph_adjlist_t object. + * \return Error code. + * + * Time complexity: O(|V|), linear in the number of vertices. + */ + +int igraph_adjlist_init_empty(igraph_adjlist_t *al, igraph_integer_t no_of_nodes) { + long int i; + + al->length = no_of_nodes; + al->adjs = igraph_Calloc(al->length, igraph_vector_int_t); + if (al->adjs == 0) { + IGRAPH_ERROR("Cannot create adjlist view", IGRAPH_ENOMEM); + } + + IGRAPH_FINALLY(igraph_adjlist_destroy, al); + for (i = 0; i < al->length; i++) { + IGRAPH_CHECK(igraph_vector_int_init(&al->adjs[i], 0)); + } + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_adjlist_init_complementer + * Adjacency lists for the complementer graph + * + * This function creates adjacency lists for the complementer + * of the input graph. In the complementer graph all edges are present + * which are not present in the original graph. Multiple edges in the + * input graph are ignored. + * \param graph The input graph. + * \param al Pointer to a not yet initialized adjacency list. + * \param mode Constant specifying whether outgoing + * (IGRAPH_OUT), incoming (IGRAPH_IN), + * or both (IGRAPH_ALL) types of neighbors (in the + * complementer graph) to include in the adjacency list. It is + * ignored for undirected networks. + * \param loops Whether to consider loop edges. + * \return Error code. + * + * Time complexity: O(|V|^2+|E|), quadratic in the number of vertices. + */ + +int igraph_adjlist_init_complementer(const igraph_t *graph, + igraph_adjlist_t *al, + igraph_neimode_t mode, + igraph_bool_t loops) { + igraph_integer_t i, j, k, n; + igraph_bool_t* seen; + igraph_vector_t vec; + + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannot create complementer adjlist view", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + al->length = igraph_vcount(graph); + al->adjs = igraph_Calloc(al->length, igraph_vector_int_t); + if (al->adjs == 0) { + IGRAPH_ERROR("Cannot create complementer adjlist view", IGRAPH_ENOMEM); + } + + IGRAPH_FINALLY(igraph_adjlist_destroy, al); + + n = al->length; + seen = igraph_Calloc(n, igraph_bool_t); + if (seen == 0) { + IGRAPH_ERROR("Cannot create complementer adjlist view", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, seen); + + IGRAPH_VECTOR_INIT_FINALLY(&vec, 0); + + for (i = 0; i < al->length; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + igraph_neighbors(graph, &vec, i, mode); + memset(seen, 0, sizeof(igraph_bool_t) * (unsigned) al->length); + n = al->length; + if (!loops) { + seen[i] = 1; + n--; + } + for (j = 0; j < igraph_vector_size(&vec); j++) { + if (! seen [ (long int) VECTOR(vec)[j] ] ) { + n--; + seen[ (long int) VECTOR(vec)[j] ] = 1; + } + } + IGRAPH_CHECK(igraph_vector_int_init(&al->adjs[i], n)); + for (j = 0, k = 0; k < n; j++) { + if (!seen[j]) { + VECTOR(al->adjs[i])[k++] = j; + } + } + } + + igraph_Free(seen); + igraph_vector_destroy(&vec); + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +/** + * \function igraph_adjlist_destroy + * Deallocate memory + * + * Free all memory allocated for an adjacency list. + * \param al The adjacency list to destroy. + * + * Time complexity: depends on memory management. + */ + +void igraph_adjlist_destroy(igraph_adjlist_t *al) { + long int i; + for (i = 0; i < al->length; i++) { + if (&al->adjs[i]) { + igraph_vector_int_destroy(&al->adjs[i]); + } + } + igraph_Free(al->adjs); +} + +/** + * \function igraph_adjlist_clear + * Removes all edges from an adjacency list. + * + * \param al The adjacency list. + * Time complexity: depends on memory management, typically O(n), where n is + * the total number of elements in the adjacency list. + */ +void igraph_adjlist_clear(igraph_adjlist_t *al) { + long int i; + for (i = 0; i < al->length; i++) { + igraph_vector_int_clear(&al->adjs[i]); + } +} + +/** + * \function igraph_adjlist_size + * Number of vertices in an adjacency list. + * + * \param al The adjacency list. + * \return The number of elements. + * + * Time complexity: O(1). + */ + +igraph_integer_t igraph_adjlist_size(const igraph_adjlist_t *al) { + return al->length; +} + +/* igraph_vector_int_t *igraph_adjlist_get(igraph_adjlist_t *al, igraph_integer_t no) { */ +/* return &al->adjs[(long int)no]; */ +/* } */ + +/** + * \function igraph_adjlist_sort + * Sort each vector in an adjacency list. + * + * Sorts every vector of the adjacency list. + * \param al The adjacency list. + * + * Time complexity: O(n log n), n is the total number of elements in + * the adjacency list. + */ + +void igraph_adjlist_sort(igraph_adjlist_t *al) { + long int i; + for (i = 0; i < al->length; i++) { + igraph_vector_int_sort(&al->adjs[i]); + } +} + +/** + * \function igraph_adjlist_simplify + * Simplify + * + * Simplify an adjacency list, ie. remove loop and multiple edges. + * \param al The adjacency list. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of edges and + * vertices. + */ + +int igraph_adjlist_simplify(igraph_adjlist_t *al) { + long int i; + long int n = al->length; + igraph_vector_int_t mark; + igraph_vector_int_init(&mark, n); + IGRAPH_FINALLY(igraph_vector_int_destroy, &mark); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->adjs[i]; + long int j, l = igraph_vector_int_size(v); + VECTOR(mark)[i] = i + 1; + for (j = 0; j < l; /* nothing */) { + long int e = (long int) VECTOR(*v)[j]; + if (VECTOR(mark)[e] != i + 1) { + VECTOR(mark)[e] = i + 1; + j++; + } else { + VECTOR(*v)[j] = igraph_vector_int_tail(v); + igraph_vector_int_pop_back(v); + l--; + } + } + } + + igraph_vector_int_destroy(&mark); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +int igraph_adjlist_remove_duplicate(const igraph_t *graph, + igraph_adjlist_t *al) { + long int i; + long int n = al->length; + IGRAPH_UNUSED(graph); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->adjs[i]; + long int j, p = 1, l = igraph_vector_int_size(v); + for (j = 1; j < l; j++) { + long int e = (long int) VECTOR(*v)[j]; + /* Non-loop edges, and one end of loop edges are fine. */ + /* We use here, that the vector is sorted and we also keep it sorted */ + if (e != i || VECTOR(*v)[j - 1] != e) { + VECTOR(*v)[p++] = e; + } + } + igraph_vector_int_resize(v, p); + } + + return 0; +} + +#ifndef USING_R +int igraph_adjlist_print(const igraph_adjlist_t *al) { + long int i; + long int n = al->length; + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->adjs[i]; + igraph_vector_int_print(v); + } + return 0; +} +#endif + +int igraph_adjlist_fprint(const igraph_adjlist_t *al, FILE *outfile) { + long int i; + long int n = al->length; + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->adjs[i]; + igraph_vector_int_fprint(v, outfile); + } + return 0; +} + +#define ADJLIST_CANON_EDGE(from, to, directed) \ + do { \ + igraph_integer_t temp; \ + if((!directed) && from < to) { \ + temp = to; \ + to = from; \ + from = temp; \ + } \ + } while(0); + +igraph_bool_t igraph_adjlist_has_edge(igraph_adjlist_t* al, igraph_integer_t from, igraph_integer_t to, igraph_bool_t directed) { + igraph_vector_int_t* fromvec; + ADJLIST_CANON_EDGE(from, to, directed); + fromvec = igraph_adjlist_get(al, from); + return igraph_vector_int_binsearch2(fromvec, to); + +} + +int igraph_adjlist_replace_edge(igraph_adjlist_t* al, igraph_integer_t from, igraph_integer_t oldto, igraph_integer_t newto, igraph_bool_t directed) { + igraph_vector_int_t *oldfromvec, *newfromvec; + int err1, err2; + long int oldpos, newpos; + igraph_integer_t oldfrom = from, newfrom = from; + ADJLIST_CANON_EDGE(oldfrom, oldto, directed); + ADJLIST_CANON_EDGE(newfrom, newto, directed); + + oldfromvec = igraph_adjlist_get(al, oldfrom); + newfromvec = igraph_adjlist_get(al, newfrom); + + + err1 = igraph_vector_int_binsearch(oldfromvec, oldto, &oldpos); + err2 = igraph_vector_int_binsearch(newfromvec, newto, &newpos); + + /* oldfrom -> oldto should exist; newfrom -> newto should not. */ + if ((!err1) || err2) { + return 1; + } + + igraph_vector_int_remove(oldfromvec, oldpos); + if (oldfromvec == newfromvec && oldpos < newpos) { + --newpos; + } + IGRAPH_CHECK(igraph_vector_int_insert(newfromvec, newpos, newto)); + + return 0; + +} + +int igraph_adjedgelist_remove_duplicate(const igraph_t *graph, + igraph_inclist_t *al) { + IGRAPH_WARNING("igraph_adjedgelist_remove_duplicate() is deprecated, use " + "igraph_inclist_remove_duplicate() instead"); + return igraph_inclist_remove_duplicate(graph, al); +} + +#ifndef USING_R +int igraph_adjedgelist_print(const igraph_inclist_t *al, FILE *outfile) { + IGRAPH_WARNING("igraph_adjedgelist_print() is deprecated, use " + "igraph_inclist_print() instead"); + return igraph_inclist_fprint(al, outfile); +} +#endif + +/** + * \function igraph_adjedgelist_init + * Initialize an incidence list of edges + * + * This function was superseded by \ref igraph_inclist_init() in igraph 0.6. + * Please use \ref igraph_inclist_init() instead of this function. + * + * + * Deprecated in version 0.6. + */ +int igraph_adjedgelist_init(const igraph_t *graph, + igraph_inclist_t *il, + igraph_neimode_t mode) { + IGRAPH_WARNING("igraph_adjedgelist_init() is deprecated, use " + "igraph_inclist_init() instead"); + return igraph_inclist_init(graph, il, mode); +} + +/** + * \function igraph_adjedgelist_destroy + * Frees all memory allocated for an incidence list. + * + * This function was superseded by \ref igraph_inclist_destroy() in igraph 0.6. + * Please use \ref igraph_inclist_destroy() instead of this function. + * + * + * Deprecated in version 0.6. + */ +void igraph_adjedgelist_destroy(igraph_inclist_t *il) { + IGRAPH_WARNING("igraph_adjedgelist_destroy() is deprecated, use " + "igraph_inclist_destroy() instead"); + igraph_inclist_destroy(il); +} + +int igraph_inclist_remove_duplicate(const igraph_t *graph, + igraph_inclist_t *al) { + long int i; + long int n = al->length; + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->incs[i]; + long int j, p = 1, l = igraph_vector_int_size(v); + for (j = 1; j < l; j++) { + long int e = (long int) VECTOR(*v)[j]; + /* Non-loop edges and one end of loop edges are fine. */ + /* We use here, that the vector is sorted and we also keep it sorted */ + if (IGRAPH_FROM(graph, e) != IGRAPH_TO(graph, e) || + VECTOR(*v)[j - 1] != e) { + VECTOR(*v)[p++] = e; + } + } + igraph_vector_int_resize(v, p); + } + + return 0; +} + +#ifndef USING_R +int igraph_inclist_print(const igraph_inclist_t *al) { + long int i; + long int n = al->length; + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->incs[i]; + igraph_vector_int_print(v); + } + return 0; +} +#endif + +int igraph_inclist_fprint(const igraph_inclist_t *al, FILE *outfile) { + long int i; + long int n = al->length; + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->incs[i]; + igraph_vector_int_fprint(v, outfile); + } + return 0; +} + +/** + * \function igraph_inclist_init + * Initialize an incidence list of edges + * + * Create a list of vectors containing the incident edges for all + * vertices. The incidence list is independent of the graph after + * creation, subsequent changes of the graph object do not update the + * incidence list, and changes to the incidence list do not update the + * graph. + * \param graph The input graph. + * \param il Pointer to an uninitialized incidence list. + * \param mode Constant specifying whether incoming edges + * (IGRAPH_IN), outgoing edges (IGRAPH_OUT) or + * both (IGRAPH_ALL) to include in the incidence lists + * of directed graphs. It is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ + +int igraph_inclist_init(const igraph_t *graph, + igraph_inclist_t *il, + igraph_neimode_t mode) { + igraph_integer_t i; + igraph_vector_t tmp; + + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannot create incidence list view", IGRAPH_EINVMODE); + } + + igraph_vector_init(&tmp, 0); + IGRAPH_FINALLY(igraph_vector_destroy, &tmp); + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + il->length = igraph_vcount(graph); + il->incs = igraph_Calloc(il->length, igraph_vector_int_t); + if (il->incs == 0) { + IGRAPH_ERROR("Cannot create incidence list view", IGRAPH_ENOMEM); + } + + IGRAPH_FINALLY(igraph_inclist_destroy, il); + for (i = 0; i < il->length; i++) { + int j, n; + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_CHECK(igraph_incident(graph, &tmp, i, mode)); + n = igraph_vector_size(&tmp); + IGRAPH_CHECK(igraph_vector_int_init(&il->incs[i], n)); + for (j = 0; j < n; j++) { + VECTOR(il->incs[i])[j] = VECTOR(tmp)[j]; + } + } + + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/** + * \function igraph_inclist_init_empty + * \brief Initialize an incidence list corresponding to an empty graph. + * + * This function essentially creates a list of empty vectors that may + * be treated as an incidence list for a graph with a given number of + * vertices. + * + * \param il Pointer to an uninitialized incidence list. + * \param n The number of vertices in the incidence list. + * \return Error code. + * + * Time complexity: O(|V|), linear in the number of vertices. + */ + +int igraph_inclist_init_empty(igraph_inclist_t *il, igraph_integer_t n) { + long int i; + + il->length = n; + il->incs = igraph_Calloc(il->length, igraph_vector_int_t); + if (il->incs == 0) { + IGRAPH_ERROR("Cannot create incidence list view", IGRAPH_ENOMEM); + } + + IGRAPH_FINALLY(igraph_inclist_destroy, il); + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_vector_int_init(&il->incs[i], 0)); + } + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_inclist_destroy + * Frees all memory allocated for an incidence list. + * + * \param eal The incidence list to destroy. + * + * Time complexity: depends on memory management. + */ + +void igraph_inclist_destroy(igraph_inclist_t *il) { + long int i; + for (i = 0; i < il->length; i++) { + /* This works if some igraph_vector_int_t's are 0, + because igraph_vector_destroy can handle this. */ + igraph_vector_int_destroy(&il->incs[i]); + } + igraph_Free(il->incs); +} + +/** + * \function igraph_inclist_clear + * Removes all edges from an incidence list. + * + * \param il The incidence list. + * Time complexity: depends on memory management, typically O(n), where n is + * the total number of elements in the incidence list. + */ +void igraph_inclist_clear(igraph_inclist_t *il) { + long int i; + for (i = 0; i < il->length; i++) { + igraph_vector_int_clear(&il->incs[i]); + } +} + +/** + * \function igraph_lazy_adjlist_init + * Constructor + * + * Create a lazy adjacency list for vertices. This function only + * allocates some memory for storing the vectors of an adjacency list, + * but the neighbor vertices are not queried, only at the \ref + * igraph_lazy_adjlist_get() calls. + * \param graph The input graph. + * \param al Pointer to an uninitialized adjacency list object. + * \param mode Constant, it gives whether incoming edges + * (IGRAPH_IN), outgoing edges + * (IGRPAH_OUT) or both types of edges + * (IGRAPH_ALL) are considered. It is ignored for + * undirected graphs. + * \param simplify Constant, it gives whether to simplify the vectors + * in the adjacency list (IGRAPH_SIMPLIFY) or not + * (IGRAPH_DONT_SIMPLIFY). + * \return Error code. + * + * Time complexity: O(|V|), the number of vertices, possibly, but + * depends on the underlying memory management too. + */ + +int igraph_lazy_adjlist_init(const igraph_t *graph, + igraph_lazy_adjlist_t *al, + igraph_neimode_t mode, + igraph_lazy_adlist_simplify_t simplify) { + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannor create adjlist view", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + al->mode = mode; + al->simplify = simplify; + al->graph = graph; + + al->length = igraph_vcount(graph); + al->adjs = igraph_Calloc(al->length, igraph_vector_t*); + if (al->adjs == 0) { + IGRAPH_ERROR("Cannot create lazy adjlist view", IGRAPH_ENOMEM); + } + + return 0; +} + +/** + * \function igraph_lazy_adjlist_destroy + * Deallocate memory + * + * Free all allocated memory for a lazy adjacency list. + * \param al The adjacency list to deallocate. + * + * Time complexity: depends on the memory management. + */ + +void igraph_lazy_adjlist_destroy(igraph_lazy_adjlist_t *al) { + igraph_lazy_adjlist_clear(al); + igraph_Free(al->adjs); +} + +/** + * \function igraph_lazy_adjlist_clear + * Removes all edges from a lazy adjacency list. + * + * \param al The lazy adjacency list. + * Time complexity: depends on memory management, typically O(n), where n is + * the total number of elements in the adjacency list. + */ +void igraph_lazy_adjlist_clear(igraph_lazy_adjlist_t *al) { + long int i, n = al->length; + for (i = 0; i < n; i++) { + if (al->adjs[i] != 0) { + igraph_vector_destroy(al->adjs[i]); + igraph_Free(al->adjs[i]); + } + } +} + +igraph_vector_t *igraph_lazy_adjlist_get_real(igraph_lazy_adjlist_t *al, + igraph_integer_t pno) { + igraph_integer_t no = pno; + int ret; + if (al->adjs[no] == 0) { + al->adjs[no] = igraph_Calloc(1, igraph_vector_t); + if (al->adjs[no] == 0) { + igraph_error("Lazy adjlist failed", __FILE__, __LINE__, + IGRAPH_ENOMEM); + } + ret = igraph_vector_init(al->adjs[no], 0); + if (ret != 0) { + igraph_error("", __FILE__, __LINE__, ret); + } + ret = igraph_neighbors(al->graph, al->adjs[no], no, al->mode); + if (ret != 0) { + igraph_error("", __FILE__, __LINE__, ret); + } + + if (al->simplify == IGRAPH_SIMPLIFY) { + igraph_vector_t *v = al->adjs[no]; + long int i, p = 0, n = igraph_vector_size(v); + for (i = 0; i < n; i++) { + if (VECTOR(*v)[i] != no && + (i == n - 1 || VECTOR(*v)[i + 1] != VECTOR(*v)[i])) { + VECTOR(*v)[p] = VECTOR(*v)[i]; + p++; + } + } + igraph_vector_resize(v, p); + } + } + + return al->adjs[no]; +} + +/** + * \function igraph_lazy_adjedgelist_init + * Initializes a lazy incidence list of edges + * + * This function was superseded by \ref igraph_lazy_inclist_init() in igraph 0.6. + * Please use \ref igraph_lazy_inclist_init() instead of this function. + * + * + * Deprecated in version 0.6. + */ +int igraph_lazy_adjedgelist_init(const igraph_t *graph, + igraph_lazy_inclist_t *il, + igraph_neimode_t mode) { + IGRAPH_WARNING("igraph_lazy_adjedgelist_init() is deprecated, use " + "igraph_lazy_inclist_init() instead"); + return igraph_lazy_inclist_init(graph, il, mode); +} + +/** + * \function igraph_lazy_adjedgelist_destroy + * Frees all memory allocated for an incidence list. + * + * This function was superseded by \ref igraph_lazy_inclist_destroy() in igraph 0.6. + * Please use \ref igraph_lazy_inclist_destroy() instead of this function. + * + * + * Deprecated in version 0.6. + */ +void igraph_lazy_adjedgelist_destroy(igraph_lazy_inclist_t *il) { + IGRAPH_WARNING("igraph_lazy_adjedgelist_destroy() is deprecated, use " + "igraph_lazy_inclist_destroy() instead"); + igraph_lazy_inclist_destroy(il); +} + +igraph_vector_t *igraph_lazy_adjedgelist_get_real(igraph_lazy_adjedgelist_t *il, + igraph_integer_t pno) { + IGRAPH_WARNING("igraph_lazy_adjedgelist_get_real() is deprecated, use " + "igraph_lazy_inclist_get_real() instead"); + return igraph_lazy_inclist_get_real(il, pno); +} + +/** + * \function igraph_lazy_inclist_init + * Initializes a lazy incidence list of edges + * + * Create a lazy incidence list for edges. This function only + * allocates some memory for storing the vectors of an incidence list, + * but the incident edges are not queried, only when \ref + * igraph_lazy_inclist_get() is called. + * \param graph The input graph. + * \param al Pointer to an uninitialized incidence list. + * \param mode Constant, it gives whether incoming edges + * (IGRAPH_IN), outgoing edges + * (IGRPAH_OUT) or both types of edges + * (IGRAPH_ALL) are considered. It is ignored for + * undirected graphs. + * \return Error code. + * + * Time complexity: O(|V|), the number of vertices, possibly. But it + * also depends on the underlying memory management. + */ + +int igraph_lazy_inclist_init(const igraph_t *graph, + igraph_lazy_inclist_t *al, + igraph_neimode_t mode) { + + if (mode != IGRAPH_IN && mode != IGRAPH_OUT && mode != IGRAPH_ALL) { + IGRAPH_ERROR("Cannot create lazy incidence list view", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + al->mode = mode; + al->graph = graph; + + al->length = igraph_vcount(graph); + al->incs = igraph_Calloc(al->length, igraph_vector_t*); + if (al->incs == 0) { + IGRAPH_ERROR("Cannot create lazy incidence list view", IGRAPH_ENOMEM); + } + + return 0; + +} + +/** + * \function igraph_lazy_inclist_destroy + * Deallocates memory + * + * Frees all allocated memory for a lazy incidence list. + * \param al The incidence list to deallocate. + * + * Time complexity: depends on memory management. + */ + +void igraph_lazy_inclist_destroy(igraph_lazy_inclist_t *il) { + igraph_lazy_inclist_clear(il); + igraph_Free(il->incs); +} + +/** + * \function igraph_lazy_inclist_clear + * Removes all edges from a lazy incidence list. + * + * \param il The lazy incidence list. + * Time complexity: depends on memory management, typically O(n), where n is + * the total number of elements in the incidence list. + */ +void igraph_lazy_inclist_clear(igraph_lazy_inclist_t *il) { + long int i, n = il->length; + for (i = 0; i < n; i++) { + if (il->incs[i] != 0) { + igraph_vector_destroy(il->incs[i]); + igraph_Free(il->incs[i]); + } + } +} + +igraph_vector_t *igraph_lazy_inclist_get_real(igraph_lazy_inclist_t *il, + igraph_integer_t pno) { + igraph_integer_t no = pno; + int ret; + if (il->incs[no] == 0) { + il->incs[no] = igraph_Calloc(1, igraph_vector_t); + if (il->incs[no] == 0) { + igraph_error("Lazy incidence list query failed", __FILE__, __LINE__, + IGRAPH_ENOMEM); + } + ret = igraph_vector_init(il->incs[no], 0); + if (ret != 0) { + igraph_error("", __FILE__, __LINE__, ret); + } + ret = igraph_incident(il->graph, il->incs[no], no, il->mode); + if (ret != 0) { + igraph_error("", __FILE__, __LINE__, ret); + } + } + return il->incs[no]; +} diff --git a/src/arpack.c b/src/arpack.c new file mode 100644 index 0000000..47275de --- /dev/null +++ b/src/arpack.c @@ -0,0 +1,1429 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 noet: */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_arpack.h" +#include "igraph_arpack_internal.h" +#include "igraph_memory.h" + +#include +#include +#include + +/* The ARPACK example file dssimp.f is used as a template */ + +static int igraph_i_arpack_err_dsaupd(int error) { + switch (error) { + case 1: return IGRAPH_ARPACK_MAXIT; + case 3: return IGRAPH_ARPACK_NOSHIFT; + case -1: return IGRAPH_ARPACK_NPOS; + case -2: return IGRAPH_ARPACK_NEVNPOS; + case -3: return IGRAPH_ARPACK_NCVSMALL; + case -4: return IGRAPH_ARPACK_NONPOSI; + case -5: return IGRAPH_ARPACK_WHICHINV; + case -6: return IGRAPH_ARPACK_BMATINV; + case -7: return IGRAPH_ARPACK_WORKLSMALL; + case -8: return IGRAPH_ARPACK_TRIDERR; + case -9: return IGRAPH_ARPACK_ZEROSTART; + case -10: return IGRAPH_ARPACK_MODEINV; + case -11: return IGRAPH_ARPACK_MODEBMAT; + case -12: return IGRAPH_ARPACK_ISHIFT; + case -13: return IGRAPH_ARPACK_NEVBE; + case -9999: return IGRAPH_ARPACK_NOFACT; + default: return IGRAPH_ARPACK_UNKNOWN; + } +} + +static int igraph_i_arpack_err_dseupd(int error) { + switch (error) { + case -1: return IGRAPH_ARPACK_NPOS; + case -2: return IGRAPH_ARPACK_NEVNPOS; + case -3: return IGRAPH_ARPACK_NCVSMALL; + case -5: return IGRAPH_ARPACK_WHICHINV; + case -6: return IGRAPH_ARPACK_BMATINV; + case -7: return IGRAPH_ARPACK_WORKLSMALL; + case -8: return IGRAPH_ARPACK_TRIDERR; + case -9: return IGRAPH_ARPACK_ZEROSTART; + case -10: return IGRAPH_ARPACK_MODEINV; + case -11: return IGRAPH_ARPACK_MODEBMAT; + case -12: return IGRAPH_ARPACK_NEVBE; + case -14: return IGRAPH_ARPACK_FAILED; + case -15: return IGRAPH_ARPACK_HOWMNY; + case -16: return IGRAPH_ARPACK_HOWMNYS; + case -17: return IGRAPH_ARPACK_EVDIFF; + default: return IGRAPH_ARPACK_UNKNOWN; + } + +} + +static int igraph_i_arpack_err_dnaupd(int error) { + switch (error) { + case 1: return IGRAPH_ARPACK_MAXIT; + case 3: return IGRAPH_ARPACK_NOSHIFT; + case -1: return IGRAPH_ARPACK_NPOS; + case -2: return IGRAPH_ARPACK_NEVNPOS; + case -3: return IGRAPH_ARPACK_NCVSMALL; + case -4: return IGRAPH_ARPACK_NONPOSI; + case -5: return IGRAPH_ARPACK_WHICHINV; + case -6: return IGRAPH_ARPACK_BMATINV; + case -7: return IGRAPH_ARPACK_WORKLSMALL; + case -8: return IGRAPH_ARPACK_TRIDERR; + case -9: return IGRAPH_ARPACK_ZEROSTART; + case -10: return IGRAPH_ARPACK_MODEINV; + case -11: return IGRAPH_ARPACK_MODEBMAT; + case -12: return IGRAPH_ARPACK_ISHIFT; + case -9999: return IGRAPH_ARPACK_NOFACT; + default: return IGRAPH_ARPACK_UNKNOWN; + } +} + +static int igraph_i_arpack_err_dneupd(int error) { + switch (error) { + case 1: return IGRAPH_ARPACK_REORDER; + case -1: return IGRAPH_ARPACK_NPOS; + case -2: return IGRAPH_ARPACK_NEVNPOS; + case -3: return IGRAPH_ARPACK_NCVSMALL; + case -5: return IGRAPH_ARPACK_WHICHINV; + case -6: return IGRAPH_ARPACK_BMATINV; + case -7: return IGRAPH_ARPACK_WORKLSMALL; + case -8: return IGRAPH_ARPACK_SHUR; + case -9: return IGRAPH_ARPACK_LAPACK; + case -10: return IGRAPH_ARPACK_MODEINV; + case -11: return IGRAPH_ARPACK_MODEBMAT; + case -12: return IGRAPH_ARPACK_HOWMNYS; + case -13: return IGRAPH_ARPACK_HOWMNY; + case -14: return IGRAPH_ARPACK_FAILED; + case -15: return IGRAPH_ARPACK_EVDIFF; + default: return IGRAPH_ARPACK_UNKNOWN; + } +} + +/** + * \function igraph_arpack_options_init + * Initialize ARPACK options + * + * Initializes ARPACK options, set them to default values. + * You can always pass the initialized \ref igraph_arpack_options_t + * object to built-in igraph functions without any modification. The + * built-in igraph functions modify the options to perform their + * calculation, e.g. \ref igraph_pagerank() always searches for the + * eigenvalue with the largest magnitude, regardless of the supplied + * value. + * + * If you want to implement your own function involving eigenvalue + * calculation using ARPACK, however, you will likely need to set up + * the fields for yourself. + * \param o The \ref igraph_arpack_options_t object to initialize. + * + * Time complexity: O(1). + */ + +void igraph_arpack_options_init(igraph_arpack_options_t *o) { + o->bmat[0] = 'I'; + o->n = 0; /* needs to be updated! */ + o->which[0] = 'X'; o->which[1] = 'X'; + o->nev = 1; + o->tol = 0; + o->ncv = 0; /* 0 means "automatic" */ + o->ldv = o->n; /* will be updated to (real) n */ + o->ishift = 1; + o->mxiter = 3000; + o->nb = 1; + o->mode = 1; + o->start = 0; + o->lworkl = 0; + o->sigma = 0; + o->sigmai = 0; + o->info = o->start; + + o->iparam[0] = o->ishift; o->iparam[1] = 0; o->iparam[2] = o->mxiter; o->iparam[3] = o->nb; + o->iparam[4] = 0; o->iparam[5] = 0; o->iparam[6] = o->mode; o->iparam[7] = 0; + o->iparam[8] = 0; o->iparam[9] = 0; o->iparam[10] = 0; +} + +/** + * \function igraph_arpack_storage_init + * Initialize ARPACK storage + * + * You only need this function if you want to run multiple eigenvalue + * calculations using ARPACK, and want to spare the memory + * allocation/deallocation between each two runs. Otherwise it is safe + * to supply a null pointer as the \c storage argument of both \ref + * igraph_arpack_rssolve() and \ref igraph_arpack_rnsolve() to make + * memory allocated and deallocated automatically. + * + * Don't forget to call the \ref + * igraph_arpack_storage_destroy() function on the storage object if + * you don't need it any more. + * \param s The \ref igraph_arpack_storage_t object to initialize. + * \param maxn The maximum order of the matrices. + * \param maxncv The maximum NCV parameter intended to use. + * \param maxldv The maximum LDV parameter intended to use. + * \param symm Whether symmetric or non-symmetric problems will be + * solved using this \ref igraph_arpack_storage_t. (You cannot use + * the same storage both with symmetric and non-symmetric solvers.) + * \return Error code. + * + * Time complexity: O(maxncv*(maxldv+maxn)). + */ + +int igraph_arpack_storage_init(igraph_arpack_storage_t *s, long int maxn, + long int maxncv, long int maxldv, + igraph_bool_t symm) { + + /* TODO: check arguments */ + s->maxn = (int) maxn; + s->maxncv = (int) maxncv; + s->maxldv = (int) maxldv; + +#define CHECKMEM(x) \ + if (!x) { \ + IGRAPH_ERROR("Cannot allocate memory for ARPACK", IGRAPH_ENOMEM); \ + } \ + IGRAPH_FINALLY(igraph_free, x); + + s->v = igraph_Calloc(maxldv * maxncv, igraph_real_t); CHECKMEM(s->v); + s->workd = igraph_Calloc(3 * maxn, igraph_real_t); CHECKMEM(s->workd); + s->d = igraph_Calloc(2 * maxncv, igraph_real_t); CHECKMEM(s->d); + s->resid = igraph_Calloc(maxn, igraph_real_t); CHECKMEM(s->resid); + s->ax = igraph_Calloc(maxn, igraph_real_t); CHECKMEM(s->ax); + s->select = igraph_Calloc(maxncv, int); CHECKMEM(s->select); + + if (symm) { + s->workl = igraph_Calloc(maxncv * (maxncv + 8), igraph_real_t); CHECKMEM(s->workl); + s->di = 0; + s->workev = 0; + } else { + s->workl = igraph_Calloc(3 * maxncv * (maxncv + 2), igraph_real_t); CHECKMEM(s->workl); + s->di = igraph_Calloc(2 * maxncv, igraph_real_t); CHECKMEM(s->di); + s->workev = igraph_Calloc(3 * maxncv, igraph_real_t); CHECKMEM(s->workev); + IGRAPH_FINALLY_CLEAN(2); + } + +#undef CHECKMEM + + IGRAPH_FINALLY_CLEAN(7); + return 0; +} + +/** + * \function igraph_arpack_storage_destroy + * Deallocate ARPACK storage + * + * \param s The \ref igraph_arpack_storage_t object for which the + * memory will be deallocated. + * + * Time complexity: operating system dependent. + */ + +void igraph_arpack_storage_destroy(igraph_arpack_storage_t *s) { + + if (s->di) { + igraph_Free(s->di); + } + if (s->workev) { + igraph_Free(s->workev); + } + + igraph_Free(s->workl); + igraph_Free(s->select); + igraph_Free(s->ax); + igraph_Free(s->resid); + igraph_Free(s->d); + igraph_Free(s->workd); + igraph_Free(s->v); +} + +/** + * "Solver" for 1x1 eigenvalue problems since ARPACK sometimes blows up with + * these. + */ +static int igraph_i_arpack_rssolve_1x1(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t* options, + igraph_vector_t* values, igraph_matrix_t* vectors) { + igraph_real_t a, b; + int nev = options->nev; + + if (nev <= 0) { + IGRAPH_ERROR("ARPACK error", IGRAPH_ARPACK_NEVNPOS); + } + + /* Probe the value in the matrix */ + a = 1; + if (fun(&b, &a, 1, extra)) { + IGRAPH_ERROR("ARPACK error while evaluating matrix-vector product", + IGRAPH_ARPACK_PROD); + } + + options->nconv = nev; + + if (values != 0) { + IGRAPH_CHECK(igraph_vector_resize(values, 1)); + VECTOR(*values)[0] = b; + } + + if (vectors != 0) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, 1, 1)); + MATRIX(*vectors, 0, 0) = 1; + } + + return IGRAPH_SUCCESS; +} + +/** + * "Solver" for 1x1 eigenvalue problems since ARPACK sometimes blows up with + * these. + */ +static int igraph_i_arpack_rnsolve_1x1(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t* options, + igraph_matrix_t* values, igraph_matrix_t* vectors) { + igraph_real_t a, b; + int nev = options->nev; + + if (nev <= 0) { + IGRAPH_ERROR("ARPACK error", IGRAPH_ARPACK_NEVNPOS); + } + + /* Probe the value in the matrix */ + a = 1; + if (fun(&b, &a, 1, extra)) { + IGRAPH_ERROR("ARPACK error while evaluating matrix-vector product", + IGRAPH_ARPACK_PROD); + } + + options->nconv = nev; + + if (values != 0) { + IGRAPH_CHECK(igraph_matrix_resize(values, 1, 2)); + MATRIX(*values, 0, 0) = b; MATRIX(*values, 0, 1) = 0; + } + + if (vectors != 0) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, 1, 1)); + MATRIX(*vectors, 0, 0) = 1; + } + + return IGRAPH_SUCCESS; +} + +/** + * "Solver" for 2x2 nonsymmetric eigenvalue problems since ARPACK sometimes + * blows up with these. + */ +static int igraph_i_arpack_rnsolve_2x2(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t* options, igraph_matrix_t* values, + igraph_matrix_t* vectors) { + igraph_real_t vec[2], mat[4]; + igraph_real_t a, b, c, d; + igraph_real_t trace, det, tsq4_minus_d; + igraph_complex_t eval1, eval2; + igraph_complex_t evec1[2], evec2[2]; + igraph_bool_t swap_evals = 0; + igraph_bool_t complex_evals = 0; + int nev = options->nev; + + if (nev <= 0) { + IGRAPH_ERROR("ARPACK error", IGRAPH_ARPACK_NEVNPOS); + } + if (nev > 2) { + nev = 2; + } + + /* Probe the values in the matrix */ + vec[0] = 1; vec[1] = 0; + if (fun(mat, vec, 2, extra)) { + IGRAPH_ERROR("ARPACK error while evaluating matrix-vector product", + IGRAPH_ARPACK_PROD); + } + vec[0] = 0; vec[1] = 1; + if (fun(mat + 2, vec, 2, extra)) { + IGRAPH_ERROR("ARPACK error while evaluating matrix-vector product", + IGRAPH_ARPACK_PROD); + } + a = mat[0]; b = mat[2]; c = mat[1]; d = mat[3]; + + /* Get the trace and the determinant */ + trace = a + d; + det = a * d - b * c; + tsq4_minus_d = trace * trace / 4 - det; + + /* Calculate the eigenvalues */ + complex_evals = tsq4_minus_d < 0; + eval1 = igraph_complex_sqrt_real(tsq4_minus_d); + if (complex_evals) { + eval2 = igraph_complex_mul_real(eval1, -1); + } else { + /* to avoid having -0 in the imaginary part */ + eval2 = igraph_complex(-IGRAPH_REAL(eval1), 0); + } + eval1 = igraph_complex_add_real(eval1, trace / 2); + eval2 = igraph_complex_add_real(eval2, trace / 2); + + if (c != 0) { + evec1[0] = igraph_complex_sub_real(eval1, d); + evec1[1] = igraph_complex(c, 0); + evec2[0] = igraph_complex_sub_real(eval2, d); + evec2[1] = igraph_complex(c, 0); + } else if (b != 0) { + evec1[0] = igraph_complex(b, 0); + evec1[1] = igraph_complex_sub_real(eval1, a); + evec2[0] = igraph_complex(b, 0); + evec2[1] = igraph_complex_sub_real(eval2, a); + } else { + evec1[0] = igraph_complex(1, 0); + evec1[1] = igraph_complex(0, 0); + evec2[0] = igraph_complex(0, 0); + evec2[1] = igraph_complex(1, 0); + } + + /* Sometimes we have to swap eval1 with eval2 and evec1 with eval2; + * determine whether we have to do it now */ + if (options->which[0] == 'S') { + if (options->which[1] == 'M') { + /* eval1 must be the one with the smallest magnitude */ + swap_evals = (igraph_complex_mod(eval1) > igraph_complex_mod(eval2)); + } else if (options->which[1] == 'R') { + /* eval1 must be the one with the smallest real part */ + swap_evals = (IGRAPH_REAL(eval1) > IGRAPH_REAL(eval2)); + } else if (options->which[1] == 'I') { + /* eval1 must be the one with the smallest imaginary part */ + swap_evals = (IGRAPH_IMAG(eval1) > IGRAPH_IMAG(eval2)); + } else { + IGRAPH_ERROR("ARPACK error", IGRAPH_ARPACK_WHICHINV); + } + } else if (options->which[0] == 'L') { + if (options->which[1] == 'M') { + /* eval1 must be the one with the largest magnitude */ + swap_evals = (igraph_complex_mod(eval1) < igraph_complex_mod(eval2)); + } else if (options->which[1] == 'R') { + /* eval1 must be the one with the largest real part */ + swap_evals = (IGRAPH_REAL(eval1) < IGRAPH_REAL(eval2)); + } else if (options->which[1] == 'I') { + /* eval1 must be the one with the largest imaginary part */ + swap_evals = (IGRAPH_IMAG(eval1) < IGRAPH_IMAG(eval2)); + } else { + IGRAPH_ERROR("ARPACK error", IGRAPH_ARPACK_WHICHINV); + } + } else if (options->which[0] == 'X' && options->which[1] == 'X') { + /* No preference on the ordering of eigenvectors */ + } else { + /* fprintf(stderr, "%c%c\n", options->which[0], options->which[1]); */ + IGRAPH_ERROR("ARPACK error", IGRAPH_ARPACK_WHICHINV); + } + + options->nconv = nev; + + if (swap_evals) { + igraph_complex_t dummy; + dummy = eval1; eval1 = eval2; eval2 = dummy; + dummy = evec1[0]; evec1[0] = evec2[0]; evec2[0] = dummy; + dummy = evec1[1]; evec1[1] = evec2[1]; evec2[1] = dummy; + } + + if (complex_evals) { + /* The eigenvalues are conjugate pairs, so we store only the + * one with positive imaginary part */ + if (IGRAPH_IMAG(eval1) < 0) { + eval1 = eval2; + evec1[0] = evec2[0]; evec1[1] = evec2[1]; + } + } + + if (values != 0) { + IGRAPH_CHECK(igraph_matrix_resize(values, nev, 2)); + MATRIX(*values, 0, 0) = IGRAPH_REAL(eval1); + MATRIX(*values, 0, 1) = IGRAPH_IMAG(eval1); + if (nev > 1) { + MATRIX(*values, 1, 0) = IGRAPH_REAL(eval2); + MATRIX(*values, 1, 1) = IGRAPH_IMAG(eval2); + } + } + + if (vectors != 0) { + if (complex_evals) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, 2, 2)); + MATRIX(*vectors, 0, 0) = IGRAPH_REAL(evec1[0]); + MATRIX(*vectors, 1, 0) = IGRAPH_REAL(evec1[1]); + MATRIX(*vectors, 0, 1) = IGRAPH_IMAG(evec1[0]); + MATRIX(*vectors, 1, 1) = IGRAPH_IMAG(evec1[1]); + } else { + IGRAPH_CHECK(igraph_matrix_resize(vectors, 2, nev)); + MATRIX(*vectors, 0, 0) = IGRAPH_REAL(evec1[0]); + MATRIX(*vectors, 1, 0) = IGRAPH_REAL(evec1[1]); + if (nev > 1) { + MATRIX(*vectors, 0, 1) = IGRAPH_REAL(evec2[0]); + MATRIX(*vectors, 1, 1) = IGRAPH_REAL(evec2[1]); + } + } + } + + return IGRAPH_SUCCESS; +} + +/** + * "Solver" for symmetric 2x2 eigenvalue problems since ARPACK sometimes blows + * up with these. + */ +static int igraph_i_arpack_rssolve_2x2(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t* options, igraph_vector_t* values, + igraph_matrix_t* vectors) { + igraph_real_t vec[2], mat[4]; + igraph_real_t a, b, c, d; + igraph_real_t trace, det, tsq4_minus_d; + igraph_real_t eval1, eval2; + int nev = options->nev; + + if (nev <= 0) { + IGRAPH_ERROR("ARPACK error", IGRAPH_ARPACK_NEVNPOS); + } + if (nev > 2) { + nev = 2; + } + + /* Probe the values in the matrix */ + vec[0] = 1; vec[1] = 0; + if (fun(mat, vec, 2, extra)) { + IGRAPH_ERROR("ARPACK error while evaluating matrix-vector product", + IGRAPH_ARPACK_PROD); + } + vec[0] = 0; vec[1] = 1; + if (fun(mat + 2, vec, 2, extra)) { + IGRAPH_ERROR("ARPACK error while evaluating matrix-vector product", + IGRAPH_ARPACK_PROD); + } + a = mat[0]; b = mat[2]; c = mat[1]; d = mat[3]; + + /* Get the trace and the determinant */ + trace = a + d; + det = a * d - b * c; + tsq4_minus_d = trace * trace / 4 - det; + + if (tsq4_minus_d >= 0) { + /* Both eigenvalues are real */ + eval1 = trace / 2 + sqrt(tsq4_minus_d); + eval2 = trace / 2 - sqrt(tsq4_minus_d); + if (c != 0) { + mat[0] = eval1 - d; mat[2] = eval2 - d; + mat[1] = c; mat[3] = c; + } else if (b != 0) { + mat[0] = b; mat[2] = b; + mat[1] = eval1 - a; mat[3] = eval2 - a; + } else { + mat[0] = 1; mat[2] = 0; + mat[1] = 0; mat[3] = 1; + } + } else { + /* Both eigenvalues are complex. Should not happen with symmetric + * matrices. */ + IGRAPH_ERROR("ARPACK error, 2x2 matrix is not symmetric", IGRAPH_EINVAL); + } + + /* eval1 is always the larger eigenvalue. If we want the smaller + * one, we have to swap eval1 with eval2 and also the columns of mat */ + if (options->which[0] == 'S') { + trace = eval1; eval1 = eval2; eval2 = trace; + trace = mat[0]; mat[0] = mat[2]; mat[2] = trace; + trace = mat[1]; mat[1] = mat[3]; mat[3] = trace; + } else if (options->which[0] == 'L' || options->which[0] == 'B') { + /* Nothing to do here */ + } else if (options->which[0] == 'X' && options->which[1] == 'X') { + /* No preference on the ordering of eigenvectors */ + } else { + IGRAPH_ERROR("ARPACK error", IGRAPH_ARPACK_WHICHINV); + } + + options->nconv = nev; + + if (values != 0) { + IGRAPH_CHECK(igraph_vector_resize(values, nev)); + VECTOR(*values)[0] = eval1; + if (nev > 1) { + VECTOR(*values)[1] = eval2; + } + } + + if (vectors != 0) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, 2, nev)); + MATRIX(*vectors, 0, 0) = mat[0]; + MATRIX(*vectors, 1, 0) = mat[1]; + if (nev > 1) { + MATRIX(*vectors, 0, 1) = mat[2]; + MATRIX(*vectors, 1, 1) = mat[3]; + } + } + + return IGRAPH_SUCCESS; +} + +int igraph_arpack_rssort(igraph_vector_t *values, igraph_matrix_t *vectors, + const igraph_arpack_options_t *options, + igraph_real_t *d, const igraph_real_t *v) { + + igraph_vector_t order; + char sort[2]; + int apply = 1; + unsigned int n = (unsigned int) options->n; + int nconv = options->nconv; + int nev = options->nev; + unsigned int nans = (unsigned int) (nconv < nev ? nconv : nev); + +#define which(a,b) (options->which[0]==a && options->which[1]==b) + + if (which('L', 'A')) { + sort[0] = 'S'; sort[1] = 'A'; + } else if (which('S', 'A')) { + sort[0] = 'L'; sort[1] = 'A'; + } else if (which('L', 'M')) { + sort[0] = 'S'; sort[1] = 'M'; + } else if (which('S', 'M')) { + sort[0] = 'L'; sort[1] = 'M'; + } else if (which('B', 'E')) { + sort[0] = 'L'; sort[1] = 'A'; + } + + IGRAPH_CHECK(igraph_vector_init_seq(&order, 0, nconv - 1)); + IGRAPH_FINALLY(igraph_vector_destroy, &order); +#ifdef HAVE_GFORTRAN + igraphdsortr_(sort, &apply, &nconv, d, VECTOR(order), /*which_len=*/ 2); +#else + igraphdsortr_(sort, &apply, &nconv, d, VECTOR(order)); +#endif + + /* BE is special */ + if (which('B', 'E')) { + int w = 0, l1 = 0, l2 = nev - 1; + igraph_vector_t order2, d2; + IGRAPH_VECTOR_INIT_FINALLY(&order2, nev); + IGRAPH_VECTOR_INIT_FINALLY(&d2, nev); + while (l1 <= l2) { + VECTOR(order2)[w] = VECTOR(order)[l1]; + VECTOR(d2)[w] = d[l1]; + w++; l1++; + if (l1 <= l2) { + VECTOR(order2)[w] = VECTOR(order)[l2]; + VECTOR(d2)[w] = d[l2]; + w++; l2--; + } + } + igraph_vector_update(&order, &order2); + igraph_vector_copy_to(&d2, d); + igraph_vector_destroy(&order2); + igraph_vector_destroy(&d2); + IGRAPH_FINALLY_CLEAN(2); + } + +#undef which + + /* Copy values */ + if (values) { + IGRAPH_CHECK(igraph_vector_resize(values, nans)); + memcpy(VECTOR(*values), d, sizeof(igraph_real_t) * nans); + } + + /* Reorder vectors */ + if (vectors) { + int i; + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, nans)); + for (i = 0; i < nans; i++) { + unsigned int idx = (unsigned int) VECTOR(order)[i]; + const igraph_real_t *ptr = v + n * idx; + memcpy(&MATRIX(*vectors, 0, i), ptr, sizeof(igraph_real_t) * n); + } + } + + igraph_vector_destroy(&order); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +int igraph_arpack_rnsort(igraph_matrix_t *values, igraph_matrix_t *vectors, + const igraph_arpack_options_t *options, + igraph_real_t *dr, igraph_real_t *di, + igraph_real_t *v) { + + igraph_vector_t order; + char sort[2]; + int apply = 1, i; + unsigned int n = (unsigned int) options->n; + int nconv = options->nconv; + int nev = options->nev; + unsigned int nans = (unsigned int) (nconv < nev ? nconv : nev); + +#define which(a,b) (options->which[0]==a && options->which[1]==b) + + if (which('L', 'M')) { + sort[0] = 'S'; sort[1] = 'M'; + } else if (which('S', 'M')) { + sort[0] = 'L'; sort[1] = 'M'; + } else if (which('L', 'R')) { + sort[0] = 'S'; sort[1] = 'R'; + } else if (which('S', 'R')) { + sort[0] = 'L'; sort[1] = 'R'; + } else if (which('L', 'I')) { + sort[0] = 'S'; sort[1] = 'I'; + } else if (which('S', 'I')) { + sort[0] = 'L'; sort[1] = 'I'; + } + +#undef which + + IGRAPH_CHECK(igraph_vector_init_seq(&order, 0, nconv - 1)); + IGRAPH_FINALLY(igraph_vector_destroy, &order); +#ifdef HAVE_GFORTRAN + igraphdsortc_(sort, &apply, &nconv, dr, di, VECTOR(order), /*which_len=*/ 2); +#else + igraphdsortc_(sort, &apply, &nconv, dr, di, VECTOR(order)); +#endif + + if (values) { + IGRAPH_CHECK(igraph_matrix_resize(values, nans, 2)); + memcpy(&MATRIX(*values, 0, 0), dr, sizeof(igraph_real_t) * nans); + memcpy(&MATRIX(*values, 0, 1), di, sizeof(igraph_real_t) * nans); + } + + if (vectors) { + int nc = 0, nr = 0, ncol, vx = 0; + for (i = 0; i < nans; i++) { + if (di[i] == 0) { + nr++; + } else { + nc++; + } + } + ncol = (nc / 2) * 2 + (nc % 2) * 2 + nr; + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, ncol)); + + for (i = 0; i < nans; i++) { + unsigned int idx; + + idx = (unsigned int) VECTOR(order)[i]; + + if (di[i] == 0) { + /* real eigenvalue, single eigenvector */ + memcpy(&MATRIX(*vectors, 0, vx), v + n * idx, sizeof(igraph_real_t) * n); + vx++; + } else if (di[i] > 0) { + /* complex eigenvalue, positive imaginary part encountered first. + * ARPACK stores its eigenvector directly in two consecutive columns. + * The complex conjugate pair of the eigenvalue (if any) will be in + * the next column and we will skip it because we advance 'i' below */ + memcpy(&MATRIX(*vectors, 0, vx), v + n * idx, sizeof(igraph_real_t) * 2 * n); + vx += 2; + i++; + } else { + /* complex eigenvalue, negative imaginary part encountered first. + * The positive one will be the next one, but we need to copy the + * eigenvector corresponding to the eigenvalue with the positive + * imaginary part. */ + idx = (unsigned int) VECTOR(order)[i + 1]; + memcpy(&MATRIX(*vectors, 0, vx), v + n * idx, sizeof(igraph_real_t) * 2 * n); + vx += 2; + i++; + } + } + } + + igraph_vector_destroy(&order); + IGRAPH_FINALLY_CLEAN(1); + + if (values) { + /* Strive to include complex conjugate eigenvalue pairs in a way that the + * positive imaginary part comes first */ + for (i = 0; i < nans; i++) { + if (MATRIX(*values, i, 1) == 0) { + /* Real eigenvalue, nothing to do */ + } else if (MATRIX(*values, i, 1) < 0) { + /* Negative imaginary part came first; negate the imaginary part for + * this eigenvalue and the next one (which is the complex conjugate + * pair), and skip it */ + MATRIX(*values, i, 1) *= -1; + i++; + if (i < nans) { + MATRIX(*values, i, 1) *= -1; + } + } else { + /* Positive imaginary part; skip the next eigenvalue, which is the + * complex conjugate pair */ + i++; + } + } + } + + return 0; +} + +/** + * \function igraph_i_arpack_auto_ncv + * \brief Tries to set up the value of \c ncv in an \c igraph_arpack_options_t + * automagically. + */ +static void igraph_i_arpack_auto_ncv(igraph_arpack_options_t* options) { + /* This is similar to how Octave determines the value of ncv, with some + * modifications. */ + int min_ncv = options->nev * 2 + 1; + + /* Use twice the number of desired eigenvectors plus one by default */ + options->ncv = min_ncv; + /* ...but use at least 20 Lanczos vectors... */ + if (options->ncv < 20) { + options->ncv = 20; + } + /* ...but having ncv close to n leads to some problems with small graphs + * (example: PageRank of "A <--> C, D <--> E, B"), so we don't let it + * to be larger than n / 2... + */ + if (options->ncv > options->n / 2) { + options->ncv = options->n / 2; + } + /* ...but we need at least min_ncv. */ + if (options->ncv < min_ncv) { + options->ncv = min_ncv; + } + /* ...but at most n */ + if (options->ncv > options->n) { + options->ncv = options->n; + } +} + +/** + * \function igraph_i_arpack_report_no_convergence + * \brief Prints a warning that informs the user that the ARPACK solver + * did not converge. + */ +static void igraph_i_arpack_report_no_convergence(const igraph_arpack_options_t* options) { + char buf[1024]; + snprintf(buf, sizeof(buf), "ARPACK solver failed to converge (%d iterations, " + "%d/%d eigenvectors converged)", options->iparam[2], + options->iparam[4], options->nev); + IGRAPH_WARNING(buf); +} + +/** + * \function igraph_arpack_rssolve + * \brief ARPACK solver for symmetric matrices + * + * This is the ARPACK solver for symmetric matrices. Please use + * \ref igraph_arpack_rnsolve() for non-symmetric matrices. + * \param fun Pointer to an \ref igraph_arpack_function_t object, + * the function that performs the matrix-vector multiplication. + * \param extra An extra argument to be passed to \c fun. + * \param options An \ref igraph_arpack_options_t object. + * \param storage An \ref igraph_arpack_storage_t object, or a null + * pointer. In the latter case memory allocation and deallocation + * is performed automatically. Either this or the \p vectors argument + * must be non-null if the ARPACK iteration is started from a + * given starting vector. If both are given \p vectors take + * precedence. + * \param values If not a null pointer, then it should be a pointer to an + * initialized vector. The eigenvalues will be stored here. The + * vector will be resized as needed. + * \param vectors If not a null pointer, then it must be a pointer to + * an initialized matrix. The eigenvectors will be stored in the + * columns of the matrix. The matrix will be resized as needed. + * Either this or the \p vectors argument must be non-null if the + * ARPACK iteration is started from a given starting vector. If + * both are given \p vectors take precedence. + * \return Error code. + * + * Time complexity: depends on the matrix-vector + * multiplication. Usually a small number of iterations is enough, so + * if the matrix is sparse and the matrix-vector multiplication can be + * done in O(n) time (the number of vertices), then the eigenvalues + * are found in O(n) time as well. + */ + +int igraph_arpack_rssolve(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, igraph_matrix_t *vectors) { + + igraph_real_t *v, *workl, *workd, *d, *resid, *ax; + igraph_bool_t free_them = 0; + int *select, i; + + int ido = 0; + int rvec = vectors || storage ? 1 : 0; /* calculate eigenvectors? */ + char *all = "All"; + + int origldv = options->ldv, origlworkl = options->lworkl, + orignev = options->nev, origncv = options->ncv; + char origwhich[2] = { options->which[0], options->which[1] }; + igraph_real_t origtol = options->tol; + + /* Special case for 1x1 and 2x2 matrices in mode 1 */ + if (options->mode == 1 && options->n == 1) { + return igraph_i_arpack_rssolve_1x1(fun, extra, options, values, vectors); + } else if (options->mode == 1 && options->n == 2) { + return igraph_i_arpack_rssolve_2x2(fun, extra, options, values, vectors); + } + + /* Brush up options if needed */ + if (options->ldv == 0) { + options->ldv = options->n; + } + if (options->ncv == 0) { + igraph_i_arpack_auto_ncv(options); + } + if (options->lworkl == 0) { + options->lworkl = options->ncv * (options->ncv + 8); + } + if (options->which[0] == 'X') { + options->which[0] = 'L'; + options->which[1] = 'M'; + } + + if (storage) { + /* Storage provided */ + if (storage->maxn < options->n) { + IGRAPH_ERROR("Not enough storage for ARPACK (`n')", IGRAPH_EINVAL); + } + if (storage->maxncv < options->ncv) { + IGRAPH_ERROR("Not enough storage for ARPACK (`ncv')", IGRAPH_EINVAL); + } + if (storage->maxldv < options->ldv) { + IGRAPH_ERROR("Not enough storage for ARPACK (`ldv')", IGRAPH_EINVAL); + } + + v = storage->v; + workl = storage->workl; + workd = storage->workd; + d = storage->d; + resid = storage->resid; + ax = storage->ax; + select = storage->select; + + } else { + /* Storage not provided */ + free_them = 1; + +#define CHECKMEM(x) \ + if (!x) { \ + IGRAPH_ERROR("Cannot allocate memory for ARPACK", IGRAPH_ENOMEM); \ + } \ + IGRAPH_FINALLY(igraph_free, x); + + v = igraph_Calloc(options->ldv * options->ncv, igraph_real_t); CHECKMEM(v); + workl = igraph_Calloc(options->lworkl, igraph_real_t); CHECKMEM(workl); + workd = igraph_Calloc(3 * options->n, igraph_real_t); CHECKMEM(workd); + d = igraph_Calloc(2 * options->ncv, igraph_real_t); CHECKMEM(d); + resid = igraph_Calloc(options->n, igraph_real_t); CHECKMEM(resid); + ax = igraph_Calloc(options->n, igraph_real_t); CHECKMEM(ax); + select = igraph_Calloc(options->ncv, int); CHECKMEM(select); + +#undef CHECKMEM + + } + + /* Set final bits */ + options->bmat[0] = 'I'; + options->iparam[0] = options->ishift; + options->iparam[1] = 0; // not referenced + options->iparam[2] = options->mxiter; + options->iparam[3] = 1; // currently dsaupd() works only for nb=1 + options->iparam[4] = 0; + options->iparam[5] = 0; // not referenced + options->iparam[6] = options->mode; + options->iparam[7] = 0; // return value + options->iparam[8] = 0; // return value + options->iparam[9] = 0; // return value + options->iparam[10] = 0; // return value + options->info = options->start; + if (options->start) { + if (!storage && !vectors) { + IGRAPH_ERROR("Starting vector not given", IGRAPH_EINVAL); + } + if (vectors && (igraph_matrix_nrow(vectors) != options->n || + igraph_matrix_ncol(vectors) != 1)) { + IGRAPH_ERROR("Invalid starting vector size", IGRAPH_EINVAL); + } + if (vectors) { + for (i = 0; i < options->n; i++) { + resid[i] = MATRIX(*vectors, i, 0); + } + } + } + + /* Ok, we have everything */ + while (1) { +#ifdef HAVE_GFORTRAN + igraphdsaupd_(&ido, options->bmat, &options->n, options->which, + &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, + options->iparam, options->ipntr, + workd, workl, &options->lworkl, &options->info, + /*bmat_len=*/ 1, /*which_len=*/ 2); +#else + igraphdsaupd_(&ido, options->bmat, &options->n, options->which, + &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, + options->iparam, options->ipntr, + workd, workl, &options->lworkl, &options->info); +#endif + + if (ido == -1 || ido == 1) { + igraph_real_t *from = workd + options->ipntr[0] - 1; + igraph_real_t *to = workd + options->ipntr[1] - 1; + if (fun(to, from, options->n, extra) != 0) { + IGRAPH_ERROR("ARPACK error while evaluating matrix-vector product", + IGRAPH_ARPACK_PROD); + } + + } else { + break; + } + } + + if (options->info == 1) { + igraph_i_arpack_report_no_convergence(options); + } + if (options->info != 0) { + IGRAPH_ERROR("ARPACK error", igraph_i_arpack_err_dsaupd(options->info)); + } + + options->ierr = 0; +#ifdef HAVE_GFORTRAN + igraphdseupd_(&rvec, all, select, d, v, &options->ldv, + &options->sigma, options->bmat, &options->n, + options->which, &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, options->iparam, + options->ipntr, workd, workl, &options->lworkl, + &options->ierr, /*howmny_len=*/ 1, /*bmat_len=*/ 1, + /*which_len=*/ 2); +#else + igraphdseupd_(&rvec, all, select, d, v, &options->ldv, + &options->sigma, options->bmat, &options->n, + options->which, &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, options->iparam, + options->ipntr, workd, workl, &options->lworkl, + &options->ierr); +#endif + + if (options->ierr != 0) { + IGRAPH_ERROR("ARPACK error", igraph_i_arpack_err_dseupd(options->ierr)); + } + + /* Save the result */ + + options->noiter = options->iparam[2]; + options->nconv = options->iparam[4]; + options->numop = options->iparam[8]; + options->numopb = options->iparam[9]; + options->numreo = options->iparam[10]; + + if (options->nconv < options->nev) { + IGRAPH_WARNING("Not enough eigenvalues/vectors in symmetric ARPACK " + "solver"); + } + + if (values || vectors) { + IGRAPH_CHECK(igraph_arpack_rssort(values, vectors, options, d, v)); + } + + options->ldv = origldv; + options->ncv = origncv; + options->lworkl = origlworkl; + options->which[0] = origwhich[0]; options->which[1] = origwhich[1]; + options->tol = origtol; + options->nev = orignev; + + /* Clean up if needed */ + if (free_them) { + igraph_Free(select); + igraph_Free(ax); + igraph_Free(resid); + igraph_Free(d); + igraph_Free(workd); + igraph_Free(workl); + igraph_Free(v); + IGRAPH_FINALLY_CLEAN(7); + } + return 0; +} + +/** + * \function igraph_arpack_rnsolve + * \brief ARPACK solver for non-symmetric matrices + * + * Please always consider calling \ref igraph_arpack_rssolve() if your + * matrix is symmetric, it is much faster. + * \ref igraph_arpack_rnsolve() for non-symmetric matrices. + * + * Note that ARPACK is not called for 2x2 matrices as an exact algebraic + * solution exists in these cases. + * + * \param fun Pointer to an \ref igraph_arpack_function_t object, + * the function that performs the matrix-vector multiplication. + * \param extra An extra argument to be passed to \c fun. + * \param options An \ref igraph_arpack_options_t object. + * \param storage An \ref igraph_arpack_storage_t object, or a null + * pointer. In the latter case memory allocation and deallocation + * is performed automatically. + * \param values If not a null pointer, then it should be a pointer to an + * initialized matrix. The (possibly complex) eigenvalues will be + * stored here. The matrix will have two columns, the first column + * contains the real, the second the imaginary parts of the + * eigenvalues. + * The matrix will be resized as needed. + * \param vectors If not a null pointer, then it must be a pointer to + * an initialized matrix. The eigenvectors will be stored in the + * columns of the matrix. The matrix will be resized as needed. + * Note that real eigenvalues will have real eigenvectors in a single + * column in this matrix; however, complex eigenvalues come in conjugate + * pairs and the result matrix will store the eigenvector corresponding to + * the eigenvalue with \em positive imaginary part only. Since in this case + * the eigenvector is also complex, it will occupy \em two columns in the + * eigenvector matrix (the real and the imaginary parts, in this order). + * Caveat: if the eigenvalue vector returns only the eigenvalue with the + * \em negative imaginary part for a complex conjugate eigenvalue pair, the + * result vector will \em still store the eigenvector corresponding to the + * eigenvalue with the positive imaginary part (since this is how ARPACK + * works). + * \return Error code. + * + * Time complexity: depends on the matrix-vector + * multiplication. Usually a small number of iterations is enough, so + * if the matrix is sparse and the matrix-vector multiplication can be + * done in O(n) time (the number of vertices), then the eigenvalues + * are found in O(n) time as well. + */ + +int igraph_arpack_rnsolve(igraph_arpack_function_t *fun, void *extra, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_matrix_t *values, igraph_matrix_t *vectors) { + + igraph_real_t *v, *workl, *workd, *dr, *di, *resid, *workev; + igraph_bool_t free_them = 0; + int *select, i; + + int ido = 0; + int rvec = vectors || storage ? 1 : 0; + char *all = "All"; + + int origldv = options->ldv, origlworkl = options->lworkl, + orignev = options->nev, origncv = options->ncv; + char origwhich[2] = { options->which[0], options->which[1] }; + igraph_real_t origtol = options->tol; + int d_size; + + /* Special case for 1x1 and 2x2 matrices in mode 1 */ + if (options->mode == 1 && options->n == 1) { + return igraph_i_arpack_rnsolve_1x1(fun, extra, options, values, vectors); + } else if (options->mode == 1 && options->n == 2) { + return igraph_i_arpack_rnsolve_2x2(fun, extra, options, values, vectors); + } + + /* Brush up options if needed */ + if (options->ldv == 0) { + options->ldv = options->n; + } + if (options->ncv == 0) { + igraph_i_arpack_auto_ncv(options); + } + if (options->lworkl == 0) { + options->lworkl = 3 * options->ncv * (options->ncv + 2); + } + if (options->which[0] == 'X') { + options->which[0] = 'L'; + options->which[1] = 'M'; + } + + if (storage) { + /* Storage provided */ + if (storage->maxn < options->n) { + IGRAPH_ERROR("Not enough storage for ARPACK (`n')", IGRAPH_EINVAL); + } + if (storage->maxncv < options->ncv) { + IGRAPH_ERROR("Not enough storage for ARPACK (`ncv')", IGRAPH_EINVAL); + } + if (storage->maxldv < options->ldv) { + IGRAPH_ERROR("Not enough storage for ARPACK (`ldv')", IGRAPH_EINVAL); + } + + v = storage->v; + workl = storage->workl; + workd = storage->workd; + workev = storage->workev; + dr = storage->d; + di = storage->di; + d_size = options->n; + resid = storage->resid; + select = storage->select; + + } else { + /* Storage not provided */ + free_them = 1; + +#define CHECKMEM(x) \ + if (!x) { \ + IGRAPH_ERROR("Cannot allocate memory for ARPACK", IGRAPH_ENOMEM); \ + } \ + IGRAPH_FINALLY(igraph_free, x); + + v = igraph_Calloc(options->n * options->ncv, igraph_real_t); CHECKMEM(v); + workl = igraph_Calloc(options->lworkl, igraph_real_t); CHECKMEM(workl); + workd = igraph_Calloc(3 * options->n, igraph_real_t); CHECKMEM(workd); + d_size = 2 * options->nev + 1 > options->ncv ? 2 * options->nev + 1 : options->ncv; + dr = igraph_Calloc(d_size, igraph_real_t); CHECKMEM(dr); + di = igraph_Calloc(d_size, igraph_real_t); CHECKMEM(di); + resid = igraph_Calloc(options->n, igraph_real_t); CHECKMEM(resid); + select = igraph_Calloc(options->ncv, int); CHECKMEM(select); + workev = igraph_Calloc(3 * options->ncv, igraph_real_t); CHECKMEM(workev); + +#undef CHECKMEM + + } + + /* Set final bits */ + options->bmat[0] = 'I'; + options->iparam[0] = options->ishift; + options->iparam[1] = 0; // not referenced + options->iparam[2] = options->mxiter; + options->iparam[3] = 1; // currently dnaupd() works only for nb=1 + options->iparam[4] = 0; + options->iparam[5] = 0; // not referenced + options->iparam[6] = options->mode; + options->iparam[7] = 0; // return value + options->iparam[8] = 0; // return value + options->iparam[9] = 0; // return value + options->iparam[10] = 0; // return value + options->info = options->start; + if (options->start) { + if (igraph_matrix_nrow(vectors) != options->n || igraph_matrix_ncol(vectors) != 1) { + IGRAPH_ERROR("Invalid starting vector size", IGRAPH_EINVAL); + } + for (i = 0; i < options->n; i++) { + resid[i] = MATRIX(*vectors, i, 0); + } + } + + /* Ok, we have everything */ + while (1) { +#ifdef HAVE_GFORTRAN + igraphdnaupd_(&ido, options->bmat, &options->n, options->which, + &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, + options->iparam, options->ipntr, + workd, workl, &options->lworkl, &options->info, + /*bmat_len=*/ 1, /*which_len=*/ 2); +#else + igraphdnaupd_(&ido, options->bmat, &options->n, options->which, + &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, + options->iparam, options->ipntr, + workd, workl, &options->lworkl, &options->info); +#endif + + if (ido == -1 || ido == 1) { + igraph_real_t *from = workd + options->ipntr[0] - 1; + igraph_real_t *to = workd + options->ipntr[1] - 1; + if (fun(to, from, options->n, extra) != 0) { + IGRAPH_ERROR("ARPACK error while evaluating matrix-vector product", + IGRAPH_ARPACK_PROD); + } + } else { + break; + } + } + + if (options->info == 1) { + igraph_i_arpack_report_no_convergence(options); + } + if (options->info != 0 && options->info != -9999) { + IGRAPH_ERROR("ARPACK error", igraph_i_arpack_err_dnaupd(options->info)); + } + + options->ierr = 0; +#ifdef HAVE_GFORTRAN + igraphdneupd_(&rvec, all, select, dr, di, v, &options->ldv, + &options->sigma, &options->sigmai, workev, options->bmat, + &options->n, options->which, &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, options->iparam, + options->ipntr, workd, workl, &options->lworkl, + &options->ierr, /*howmny_len=*/ 1, /*bmat_len=*/ 1, + /*which_len=*/ 2); +#else + igraphdneupd_(&rvec, all, select, dr, di, v, &options->ldv, + &options->sigma, &options->sigmai, workev, options->bmat, + &options->n, options->which, &options->nev, &options->tol, + resid, &options->ncv, v, &options->ldv, options->iparam, + options->ipntr, workd, workl, &options->lworkl, + &options->ierr); +#endif + + if (options->ierr != 0) { + IGRAPH_ERROR("ARPACK error", igraph_i_arpack_err_dneupd(options->info)); + } + + /* Save the result */ + + options->noiter = options->iparam[2]; + options->nconv = options->iparam[4]; + options->numop = options->iparam[8]; + options->numopb = options->iparam[9]; + options->numreo = options->iparam[10]; + + if (options->nconv < options->nev) { + IGRAPH_WARNING("Not enough eigenvalues/vectors in ARPACK " + "solver"); + } + + /* ARPACK might modify stuff in 'options' so reset everything that could + * potentially get modified */ + options->ldv = origldv; + options->ncv = origncv; + options->lworkl = origlworkl; + options->which[0] = origwhich[0]; options->which[1] = origwhich[1]; + options->tol = origtol; + options->nev = orignev; + + if (values || vectors) { + IGRAPH_CHECK(igraph_arpack_rnsort(values, vectors, options, + dr, di, v)); + } + + /* Clean up if needed */ + if (free_them) { + igraph_Free(workev); + igraph_Free(select); + igraph_Free(resid); + igraph_Free(di); + igraph_Free(dr); + igraph_Free(workd); + igraph_Free(workl); + igraph_Free(v); + IGRAPH_FINALLY_CLEAN(8); + } + return 0; +} + +/** + * \function igraph_arpack_unpack_complex + * \brief Make the result of the non-symmetric ARPACK solver more readable + * + * This function works on the output of \ref igraph_arpack_rnsolve and + * brushes it up a bit: it only keeps \p nev eigenvalues/vectors and + * every eigenvector is stored in two columns of the \p vectors + * matrix. + * + * + * The output of the non-symmetric ARPACK solver is somewhat hard to + * parse, as real eigenvectors occupy only one column in the matrix, + * and the complex conjugate eigenvectors are not stored at all + * (usually). The other problem is that the solver might return more + * eigenvalues than requested. The common use of this function is to + * call it directly after \ref igraph_arpack_rnsolve with its \p + * vectors and \p values argument and \c options->nev as \p nev. + * \param vectors The eigenvector matrix, as returned by \ref + * igraph_arpack_rnsolve. It will be resized, typically it will be + * larger. + * \param values The eigenvalue matrix, as returned by \ref + * igraph_arpack_rnsolve. It will be resized, typically extra, + * unneeded rows (=eigenvalues) will be removed. + * \param nev The number of eigenvalues/vectors to keep. Can be less + * or equal than the number originally requested from ARPACK. + * \return Error code. + * + * Time complexity: linear in the number of elements in the \p vectors + * matrix. + */ + +int igraph_arpack_unpack_complex(igraph_matrix_t *vectors, igraph_matrix_t *values, + long int nev) { + + long int nodes = igraph_matrix_nrow(vectors); + long int no_evs = igraph_matrix_nrow(values); + long int i, j, k, wh; + size_t colsize = (unsigned) nodes * sizeof(igraph_real_t); + + /* Error checks */ + if (nev < 0) { + IGRAPH_ERROR("`nev' cannot be negative", IGRAPH_EINVAL); + } + if (nev > no_evs) { + IGRAPH_ERROR("`nev' too large, we don't have that many in `values'", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(vectors, nodes, nev * 2)); + for (i = nev; i < igraph_matrix_nrow(values); i++) { + IGRAPH_CHECK(igraph_matrix_remove_row(values, i)); + } + + /* Calculate where to start copying */ + for (i = 0, j = 0, wh = 0; i < nev; i++) { + if (MATRIX(*values, i, 1) == 0) { /* TODO: == 0.0 ???? */ + /* real */ + j++; + } else { + /* complex */ + if (wh == 0) { + j += 2; + wh = 1 - wh; + } + } + } + j--; + + /* if (j>=origcol) { */ + /* IGRAPH_WARNING("Too few columns in `vectors', ARPACK results are likely wrong"); */ + /* } */ + + /* We copy the j-th eigenvector to the (k-1)-th and k-th column */ + k = nev * 2 - 1; + + for (i = nev - 1; i >= 0; i--) { + if (MATRIX(*values, i, 1) == 0) { + + /* real */ + memset( &MATRIX(*vectors, 0, k), 0, colsize); + if (k - 1 != j) { + memcpy( &MATRIX(*vectors, 0, k - 1), &MATRIX(*vectors, 0, j), colsize); + } + k -= 2; + j -= 1; + } else { + /* complex */ + if (k != j) { + /* Separate copy required, otherwise 'from' and 'to' might + overlap */ + memcpy( &MATRIX(*vectors, 0, k), &MATRIX(*vectors, 0, j), colsize); + memcpy( &MATRIX(*vectors, 0, k - 1), &MATRIX(*vectors, 0, j - 1), colsize); + } + if (i > 1 && MATRIX(*values, i, 1) != -MATRIX(*values, i - 1, 1)) { + /* The next one is not a conjugate of this one */ + j -= 2; + } else { + /* Conjugate */ + int l; + for (l = 0; l < nodes; l++) { + MATRIX(*vectors, l, k) = - MATRIX(*vectors, l, k); + } + } + k -= 2; + } + } + + return 0; +} diff --git a/src/array.c b/src/array.c new file mode 100644 index 0000000..4d9904a --- /dev/null +++ b/src/array.c @@ -0,0 +1,50 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_array.h" + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "array.pmt" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_LONG +#include "igraph_pmt.h" +#include "array.pmt" +#include "igraph_pmt_off.h" +#undef BASE_LONG + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "array.pmt" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "array.pmt" +#include "igraph_pmt_off.h" +#undef BASE_BOOL diff --git a/src/array.pmt b/src/array.pmt new file mode 100644 index 0000000..6d5c735 --- /dev/null +++ b/src/array.pmt @@ -0,0 +1,90 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" + +int FUNCTION(igraph_array3, init)(TYPE(igraph_array3) *a, long int n1, long int n2, + long int n3) { + int ret; + ret = FUNCTION(igraph_vector, init)(&a->data, n1 * n2 * n3); + a->n1 = n1; + a->n2 = n2; + a->n3 = n3; + a->n1n2 = n1 * n2; + + return ret; +} + +void FUNCTION(igraph_array3, destroy)(TYPE(igraph_array3) *a) { + FUNCTION(igraph_vector, destroy)(&a->data); +} + +long int FUNCTION(igraph_array3, size)(const TYPE(igraph_array3) *a) { + return (a->n1n2) * (a->n3); +} + +long int FUNCTION(igraph_array3, n)(const TYPE(igraph_array3) *a, long int idx) { + switch (idx) { + case 1: return a->n1; + break; + case 2: return a->n2; + break; + case 3: return a->n3; + break; + } + return 0; +} + +int FUNCTION(igraph_array3, resize)(TYPE(igraph_array3) *a, long int n1, long int n2, + long int n3) { + int ret = FUNCTION(igraph_vector, resize)(&a->data, n1 * n2 * n3); + a->n1 = n1; + a->n2 = n2; + a->n3 = n3; + a->n1n2 = n1 * n2; + + return ret; +} + +void FUNCTION(igraph_array3, null)(TYPE(igraph_array3) *a) { + FUNCTION(igraph_vector, null)(&a->data); +} + +BASE FUNCTION(igraph_array3, sum)(const TYPE(igraph_array3) *a) { + return FUNCTION(igraph_vector, sum)(&a->data); +} + +void FUNCTION(igraph_array3, scale)(TYPE(igraph_array3) *a, BASE by) { + FUNCTION(igraph_vector, scale)(&a->data, by); +} + +void FUNCTION(igraph_array3, fill)(TYPE(igraph_array3) *a, BASE e) { + FUNCTION(igraph_vector, fill)(&a->data, e); +} + +int FUNCTION(igraph_array3, update)(TYPE(igraph_array3) *to, + const TYPE(igraph_array3) *from) { + IGRAPH_CHECK(FUNCTION(igraph_array3, resize)(to, from->n1, from->n2, from->n3)); + FUNCTION(igraph_vector, update)(&to->data, &from->data); + return 0; +} diff --git a/src/atlas-edges.h b/src/atlas-edges.h new file mode 100644 index 0000000..229e8f1 --- /dev/null +++ b/src/atlas-edges.h @@ -0,0 +1,1296 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus + #define __BEGIN_DECLS extern "C" { + #define __END_DECLS } +#else + #define __BEGIN_DECLS /* empty */ + #define __END_DECLS /* empty */ +#endif + +__BEGIN_DECLS + +#include "igraph_types.h" + +const igraph_real_t igraph_i_atlas_edges[] = { + 0, 0, + 1, 0, + 2, 0, + 2, 1, 0, 1, + 3, 0, + 3, 1, 1, 2, + 3, 2, 0, 1, 0, 2, + 3, 3, 0, 1, 0, 2, 1, 2, + 4, 0, + 4, 1, 3, 2, + 4, 2, 3, 2, 3, 1, + 4, 2, 0, 1, 3, 2, + 4, 3, 3, 2, 1, 2, 3, 1, + 4, 3, 3, 0, 3, 1, 3, 2, + 4, 3, 0, 1, 1, 2, 0, 3, + 4, 4, 3, 2, 1, 2, 3, 1, 3, 0, + 4, 4, 0, 1, 1, 2, 2, 3, 0, 3, + 4, 5, 0, 1, 0, 2, 0, 3, 1, 2, 2, 3, + 4, 6, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, + 5, 0, + 5, 1, 4, 3, + 5, 2, 1, 2, 0, 1, + 5, 2, 0, 2, 4, 3, + 5, 3, 1, 2, 0, 1, 2, 0, + 5, 3, 4, 3, 3, 2, 3, 1, + 5, 3, 3, 2, 4, 3, 0, 4, + 5, 3, 1, 2, 0, 1, 4, 3, + 5, 4, 4, 3, 1, 2, 3, 1, 3, 2, + 5, 4, 0, 3, 1, 0, 2, 1, 3, 2, + 5, 4, 4, 3, 4, 0, 4, 1, 4, 2, + 5, 4, 4, 0, 3, 1, 4, 3, 3, 2, + 5, 4, 2, 3, 1, 2, 0, 1, 4, 0, + 5, 4, 1, 2, 0, 1, 2, 0, 4, 3, + 5, 5, 0, 3, 2, 0, 3, 2, 1, 0, 2, 1, + 5, 5, 4, 2, 4, 3, 2, 3, 4, 1, 4, 0, + 5, 5, 0, 1, 1, 2, 2, 3, 0, 4, 0, 2, + 5, 5, 4, 0, 1, 2, 4, 3, 3, 2, 3, 1, + 5, 5, 1, 0, 4, 1, 2, 4, 3, 2, 1, 3, + 5, 5, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, + 5, 6, 1, 0, 4, 1, 4, 0, 0, 3, 1, 3, 3, 4, + 5, 6, 1, 0, 4, 1, 2, 4, 3, 2, 1, 3, 2, 1, + 5, 6, 1, 0, 4, 1, 2, 4, 3, 2, 1, 3, 3, 4, + 5, 6, 0, 1, 4, 3, 2, 3, 4, 2, 4, 0, 4, 1, + 5, 6, 0, 4, 3, 0, 4, 3, 2, 3, 1, 2, 0, 1, + 5, 6, 2, 1, 0, 2, 3, 0, 1, 3, 4, 1, 0, 4, + 5, 7, 4, 0, 1, 2, 4, 3, 3, 2, 3, 1, 4, 1, 2, 4, + 5, 7, 4, 1, 2, 4, 3, 2, 1, 3, 3, 4, 0, 3, 4, 0, + 5, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, + 5, 7, 2, 1, 0, 2, 3, 0, 1, 3, 4, 1, 0, 4, 2, 4, + 5, 8, 1, 0, 4, 1, 2, 4, 3, 2, 1, 3, 4, 0, 3, 4, 0, 3, + 5, 8, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, + 5, 9, 0, 1, 3, 4, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, + 5, 10, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, + 6, 0, + 6, 1, 5, 4, + 6, 2, 0, 3, 5, 4, + 6, 2, 1, 3, 1, 2, + 6, 3, 1, 3, 2, 1, 3, 2, + 6, 3, 0, 3, 5, 0, 4, 0, + 6, 3, 4, 3, 5, 4, 0, 5, + 6, 3, 4, 3, 5, 1, 5, 2, + 6, 3, 1, 2, 3, 0, 5, 4, + 6, 4, 0, 3, 4, 0, 5, 4, 0, 5, + 6, 4, 3, 0, 5, 3, 4, 5, 0, 4, + 6, 4, 5, 1, 5, 3, 5, 2, 0, 5, + 6, 4, 4, 3, 3, 1, 4, 0, 3, 2, + 6, 4, 0, 2, 1, 3, 2, 1, 5, 3, + 6, 4, 1, 3, 2, 1, 3, 2, 0, 5, + 6, 4, 1, 2, 0, 3, 5, 0, 4, 0, + 6, 4, 4, 5, 1, 2, 0, 5, 3, 4, + 6, 4, 0, 2, 4, 0, 3, 1, 5, 3, + 6, 5, 3, 0, 5, 3, 4, 5, 0, 4, 5, 0, + 6, 5, 5, 3, 3, 1, 3, 2, 4, 3, 4, 5, + 6, 5, 5, 3, 5, 4, 2, 3, 3, 4, 0, 4, + 6, 5, 4, 3, 1, 2, 4, 0, 3, 2, 3, 1, + 6, 5, 1, 4, 3, 4, 4, 0, 2, 1, 3, 2, + 6, 5, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, + 6, 5, 5, 3, 5, 4, 5, 0, 5, 1, 5, 2, + 6, 5, 1, 4, 5, 1, 1, 0, 2, 1, 2, 3, + 6, 5, 0, 1, 3, 4, 0, 2, 3, 0, 5, 3, + 6, 5, 1, 0, 2, 1, 2, 4, 1, 3, 5, 3, + 6, 5, 4, 3, 0, 5, 4, 0, 3, 2, 3, 1, + 6, 5, 1, 2, 0, 1, 4, 5, 1, 3, 2, 3, + 6, 5, 0, 1, 0, 5, 2, 3, 3, 4, 4, 5, + 6, 5, 4, 3, 5, 1, 5, 2, 0, 3, 4, 0, + 6, 5, 1, 2, 3, 0, 5, 3, 4, 5, 0, 4, + 6, 6, 0, 3, 5, 0, 4, 5, 3, 4, 5, 3, 4, 0, + 6, 6, 1, 4, 2, 4, 4, 0, 2, 3, 3, 1, 3, 4, + 6, 6, 1, 4, 2, 4, 4, 0, 2, 1, 3, 1, 2, 3, + 6, 6, 2, 0, 5, 4, 4, 3, 5, 3, 4, 0, 2, 4, + 6, 6, 3, 2, 4, 3, 0, 4, 1, 0, 2, 1, 0, 3, + 6, 6, 4, 1, 3, 1, 4, 2, 3, 2, 2, 0, 1, 0, + 6, 6, 5, 2, 5, 3, 5, 4, 3, 4, 5, 1, 5, 0, + 6, 6, 4, 3, 4, 2, 4, 0, 1, 4, 3, 0, 5, 3, + 6, 6, 4, 3, 3, 5, 5, 4, 5, 1, 3, 2, 4, 0, + 6, 6, 4, 2, 1, 2, 4, 3, 4, 1, 4, 0, 0, 5, + 6, 6, 1, 2, 3, 1, 0, 3, 2, 0, 4, 0, 5, 0, + 6, 6, 2, 0, 4, 2, 1, 4, 2, 1, 3, 1, 5, 3, + 6, 6, 1, 2, 3, 1, 0, 3, 2, 0, 4, 0, 5, 3, + 6, 6, 5, 3, 2, 5, 2, 0, 4, 2, 4, 3, 3, 1, + 6, 6, 0, 2, 3, 4, 1, 0, 5, 3, 4, 5, 3, 0, + 6, 6, 1, 2, 3, 0, 5, 3, 4, 5, 0, 4, 5, 0, + 6, 6, 4, 3, 1, 2, 4, 0, 3, 2, 3, 1, 5, 0, + 6, 6, 1, 4, 2, 4, 4, 0, 0, 5, 3, 1, 2, 3, + 6, 6, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, + 6, 6, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, + 6, 6, 1, 3, 2, 1, 3, 2, 0, 4, 5, 0, 4, 5, + 6, 7, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 0, 5, + 6, 7, 1, 4, 2, 4, 2, 1, 3, 1, 2, 3, 2, 0, 0, 1, + 6, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, + 6, 7, 0, 1, 3, 2, 0, 2, 3, 0, 3, 1, 5, 1, 5, 2, + 6, 7, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 3, 4, + 6, 7, 1, 0, 4, 1, 2, 4, 3, 2, 5, 1, 2, 5, 1, 2, + 6, 7, 0, 4, 2, 0, 1, 2, 3, 1, 5, 3, 3, 0, 2, 3, + 6, 7, 1, 4, 2, 4, 2, 3, 2, 1, 3, 1, 4, 5, 0, 4, + 6, 7, 1, 0, 4, 1, 2, 4, 3, 2, 5, 1, 2, 5, 4, 5, + 6, 7, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, + 6, 7, 0, 5, 4, 0, 5, 4, 0, 2, 3, 0, 3, 2, 0, 1, + 6, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, 4, 1, + 6, 7, 0, 1, 4, 0, 1, 4, 0, 2, 3, 0, 3, 2, 3, 5, + 6, 7, 1, 4, 2, 4, 4, 0, 0, 5, 3, 1, 2, 3, 3, 4, + 6, 7, 2, 0, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, + 6, 7, 1, 5, 0, 1, 4, 0, 3, 4, 2, 3, 1, 2, 0, 3, + 6, 7, 1, 4, 2, 4, 4, 0, 0, 5, 3, 1, 2, 3, 2, 1, + 6, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 0, 2, 5, 1, + 6, 7, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, + 6, 7, 5, 0, 3, 5, 2, 3, 0, 2, 1, 3, 4, 1, 3, 4, + 6, 7, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 2, 3, + 6, 7, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, + 6, 7, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, + 6, 7, 1, 2, 0, 1, 2, 0, 3, 0, 4, 3, 5, 4, 3, 5, + 6, 8, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, + 6, 8, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 0, 0, 4, + 6, 8, 1, 2, 3, 1, 0, 3, 1, 0, 2, 0, 3, 2, 5, 3, 4, 0, + 6, 8, 0, 1, 2, 4, 0, 2, 5, 2, 3, 1, 3, 2, 2, 1, 4, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 1, 5, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, + 6, 8, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 2, 1, 5, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, + 6, 8, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 3, 0, 5, 1, + 6, 8, 2, 0, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, 5, 3, + 6, 8, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 0, 5, 5, 4, + 6, 8, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 5, 1, 5, 3, + 6, 8, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 3, 4, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 2, 0, 2, + 6, 8, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, + 6, 8, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 1, 4, 0, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, + 6, 8, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 2, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 5, 5, 3, 1, 5, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 1, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, 5, 2, 5, 0, + 6, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 1, 5, 2, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, + 6, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 2, + 6, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, + 6, 9, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 4, 5, + 6, 9, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 4, 3, + 6, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 5, + 6, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, + 6, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 0, 4, 1, 0, 4, 1, + 6, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, + 6, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, 4, 0, 5, 0, + 6, 9, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, + 6, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, + 6, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, + 6, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 2, 0, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, + 6, 9, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, + 6, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 5, 2, 4, 1, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 4, 5, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 5, + 6, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 1, 0, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, + 6, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 2, 4, + 6, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, + 6, 10, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, + 6, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, + 6, 10, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, + 6, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 0, 2, 1, 3, 5, 1, + 6, 10, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 3, 2, 0, 3, + 6, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, + 6, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, 1, 5, + 6, 11, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, + 6, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, + 6, 11, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 0, 2, + 6, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, 5, 1, + 6, 11, 1, 3, 4, 1, 3, 4, 2, 3, 0, 2, 4, 0, 5, 4, 2, 5, 4, 2, 0, 5, 1, 5, + 6, 11, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, + 6, 11, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, + 6, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 0, 3, + 6, 12, 0, 1, 1, 2, 0, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 4, 3, + 6, 12, 3, 2, 1, 3, 2, 1, 0, 2, 5, 0, 2, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 0, 4, + 6, 12, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 4, 5, + 6, 12, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 2, 3, + 6, 12, 0, 1, 1, 2, 0, 2, 3, 2, 3, 1, 4, 0, 2, 4, 5, 1, 0, 5, 4, 5, 3, 4, 5, 3, + 6, 13, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 2, 3, 0, 4, + 6, 13, 0, 1, 1, 2, 0, 2, 3, 2, 3, 1, 4, 0, 2, 4, 5, 1, 0, 5, 4, 5, 3, 4, 5, 3, 3, 0, + 6, 14, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 1, 3, 2, 0, 4, 0, 5, 3, + 6, 15, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, + 7, 0, + 7, 1, 6, 5, + 7, 2, 2, 3, 1, 2, + 7, 2, 5, 4, 6, 0, + 7, 3, 0, 4, 4, 2, 2, 0, + 7, 3, 0, 1, 0, 6, 0, 5, + 7, 3, 5, 4, 6, 0, 5, 6, + 7, 3, 3, 2, 1, 2, 5, 6, + 7, 3, 3, 1, 5, 6, 0, 4, + 7, 4, 2, 5, 6, 2, 5, 6, 1, 2, + 7, 4, 1, 2, 4, 1, 5, 4, 2, 5, + 7, 4, 1, 0, 5, 1, 1, 2, 4, 1, + 7, 4, 1, 0, 2, 1, 5, 2, 6, 2, + 7, 4, 3, 4, 2, 3, 1, 2, 0, 1, + 7, 4, 4, 2, 0, 4, 2, 0, 5, 6, + 7, 4, 0, 1, 6, 0, 0, 5, 4, 2, + 7, 4, 3, 1, 5, 4, 6, 5, 0, 6, + 7, 4, 0, 4, 3, 0, 2, 5, 6, 2, + 7, 4, 2, 3, 1, 2, 6, 0, 5, 4, + 7, 5, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, + 7, 5, 2, 5, 6, 2, 5, 6, 4, 2, 3, 2, + 7, 5, 4, 2, 4, 0, 2, 0, 5, 4, 6, 0, + 7, 5, 2, 5, 6, 2, 5, 6, 1, 2, 0, 1, + 7, 5, 4, 1, 0, 4, 3, 0, 1, 3, 2, 1, + 7, 5, 1, 2, 0, 1, 4, 0, 3, 4, 2, 3, + 7, 5, 5, 1, 5, 0, 2, 5, 3, 5, 4, 5, + 7, 5, 1, 5, 6, 1, 1, 0, 2, 1, 3, 2, + 7, 5, 1, 5, 4, 1, 2, 3, 6, 2, 2, 1, + 7, 5, 1, 5, 6, 1, 1, 2, 2, 3, 4, 3, + 7, 5, 2, 1, 3, 2, 4, 3, 5, 4, 3, 6, + 7, 5, 6, 5, 2, 6, 1, 2, 5, 2, 3, 4, + 7, 5, 4, 3, 5, 4, 6, 5, 0, 6, 1, 0, + 7, 5, 0, 4, 3, 0, 2, 5, 6, 2, 5, 6, + 7, 5, 4, 1, 5, 2, 6, 5, 3, 6, 2, 3, + 7, 5, 1, 4, 3, 1, 1, 0, 2, 1, 6, 5, + 7, 5, 0, 4, 3, 0, 1, 0, 2, 1, 6, 5, + 7, 5, 0, 4, 3, 0, 2, 1, 5, 2, 6, 2, + 7, 5, 6, 5, 3, 4, 2, 3, 1, 2, 0, 1, + 7, 5, 2, 3, 1, 2, 6, 0, 5, 6, 5, 4, + 7, 5, 0, 1, 4, 6, 5, 4, 3, 2, 6, 5, + 7, 6, 1, 5, 6, 1, 5, 6, 2, 5, 1, 2, 6, 2, + 7, 6, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, + 7, 6, 0, 4, 3, 0, 1, 3, 2, 1, 1, 4, 3, 4, + 7, 6, 5, 2, 4, 5, 2, 4, 3, 2, 6, 3, 2, 6, + 7, 6, 1, 2, 4, 1, 5, 4, 2, 5, 0, 1, 4, 0, + 7, 6, 1, 2, 5, 1, 4, 5, 2, 4, 0, 2, 5, 0, + 7, 6, 2, 5, 6, 2, 5, 6, 2, 4, 1, 2, 3, 2, + 7, 6, 1, 4, 3, 1, 2, 3, 1, 2, 2, 5, 6, 2, + 7, 6, 5, 4, 6, 5, 1, 6, 5, 1, 3, 6, 0, 1, + 7, 6, 6, 5, 1, 6, 5, 1, 3, 1, 0, 3, 1, 4, + 7, 6, 0, 4, 3, 0, 2, 3, 4, 2, 2, 5, 6, 2, + 7, 6, 1, 4, 3, 1, 2, 3, 1, 2, 2, 5, 6, 5, + 7, 6, 2, 3, 1, 2, 3, 6, 5, 4, 6, 5, 5, 2, + 7, 6, 2, 5, 6, 2, 5, 6, 1, 4, 3, 1, 2, 1, + 7, 6, 4, 5, 0, 4, 3, 0, 2, 3, 4, 2, 6, 3, + 7, 6, 0, 4, 3, 0, 1, 3, 6, 5, 1, 4, 1, 0, + 7, 6, 1, 4, 3, 1, 2, 3, 5, 2, 6, 5, 2, 6, + 7, 6, 6, 3, 5, 6, 4, 5, 1, 4, 2, 1, 5, 2, + 7, 6, 1, 0, 3, 1, 6, 3, 5, 6, 4, 5, 1, 4, + 7, 6, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, + 7, 6, 0, 4, 3, 0, 4, 3, 2, 5, 6, 2, 5, 6, + 7, 6, 6, 3, 0, 6, 6, 2, 5, 6, 6, 1, 4, 6, + 7, 6, 2, 4, 5, 2, 2, 3, 6, 2, 1, 2, 1, 0, + 7, 6, 1, 0, 2, 1, 5, 2, 1, 4, 3, 1, 6, 2, + 7, 6, 1, 0, 2, 1, 3, 6, 1, 3, 4, 1, 5, 4, + 7, 6, 1, 0, 2, 1, 5, 2, 6, 5, 1, 4, 3, 1, + 7, 6, 1, 0, 2, 4, 5, 2, 6, 5, 2, 6, 3, 2, + 7, 6, 4, 0, 1, 4, 3, 1, 2, 1, 5, 2, 6, 2, + 7, 6, 6, 5, 1, 2, 0, 1, 2, 0, 3, 2, 0, 4, + 7, 6, 0, 4, 3, 0, 1, 0, 2, 1, 5, 2, 6, 2, + 7, 6, 1, 0, 3, 1, 6, 3, 2, 6, 4, 1, 5, 4, + 7, 6, 2, 5, 6, 2, 4, 2, 1, 4, 3, 1, 0, 3, + 7, 6, 0, 4, 3, 0, 2, 3, 4, 2, 1, 2, 6, 5, + 7, 6, 0, 4, 3, 0, 2, 1, 5, 2, 6, 5, 2, 6, + 7, 6, 3, 4, 1, 0, 2, 1, 5, 2, 6, 5, 2, 6, + 7, 6, 4, 5, 0, 4, 3, 0, 6, 3, 1, 0, 2, 1, + 7, 6, 2, 5, 6, 2, 5, 6, 1, 4, 3, 1, 1, 0, + 7, 6, 4, 5, 3, 4, 2, 3, 1, 2, 0, 1, 6, 0, + 7, 6, 6, 4, 5, 6, 4, 5, 2, 3, 1, 2, 0, 1, + 7, 6, 0, 1, 4, 0, 2, 3, 5, 2, 6, 5, 3, 6, + 7, 6, 1, 2, 0, 1, 4, 0, 3, 4, 2, 3, 6, 5, + 7, 7, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 3, 4, + 7, 7, 1, 2, 5, 1, 4, 5, 2, 4, 0, 2, 5, 0, 5, 2, + 7, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, + 7, 7, 1, 2, 5, 1, 4, 5, 2, 4, 0, 2, 5, 0, 1, 0, + 7, 7, 0, 4, 3, 0, 2, 3, 4, 2, 2, 5, 6, 2, 2, 0, + 7, 7, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 2, 6, + 7, 7, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 3, 4, 6, 3, + 7, 7, 0, 4, 3, 0, 2, 3, 4, 2, 2, 5, 6, 2, 3, 4, + 7, 7, 0, 4, 3, 0, 1, 3, 3, 6, 1, 4, 1, 0, 5, 4, + 7, 7, 0, 4, 3, 0, 1, 3, 6, 5, 1, 4, 1, 0, 3, 4, + 7, 7, 5, 2, 4, 5, 2, 4, 3, 2, 6, 3, 2, 6, 2, 1, + 7, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 0, 2, 2, 5, + 7, 7, 5, 2, 4, 5, 2, 4, 3, 2, 6, 3, 2, 6, 3, 1, + 7, 7, 1, 4, 3, 1, 2, 3, 4, 2, 2, 0, 2, 1, 6, 0, + 7, 7, 1, 2, 5, 1, 4, 5, 2, 4, 0, 2, 5, 0, 3, 5, + 7, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 0, 2, 3, 5, + 7, 7, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 0, 2, 1, 5, + 7, 7, 3, 2, 4, 3, 3, 5, 2, 4, 5, 2, 6, 1, 6, 4, + 7, 7, 1, 2, 5, 1, 4, 5, 2, 4, 0, 2, 5, 0, 0, 3, + 7, 7, 3, 4, 1, 3, 2, 1, 6, 2, 5, 6, 1, 5, 4, 1, + 7, 7, 0, 1, 4, 0, 1, 4, 2, 1, 3, 2, 5, 3, 4, 5, + 7, 7, 6, 3, 5, 6, 1, 5, 2, 1, 3, 2, 4, 2, 5, 4, + 7, 7, 1, 2, 4, 1, 5, 4, 6, 5, 3, 6, 2, 3, 5, 2, + 7, 7, 4, 1, 3, 4, 1, 3, 2, 1, 6, 2, 5, 6, 2, 5, + 7, 7, 3, 0, 6, 3, 0, 6, 1, 0, 0, 2, 5, 0, 0, 4, + 7, 7, 1, 5, 6, 1, 1, 2, 3, 1, 4, 3, 1, 4, 4, 0, + 7, 7, 5, 0, 6, 5, 0, 6, 5, 2, 1, 5, 6, 3, 4, 6, + 7, 7, 4, 1, 0, 4, 1, 0, 2, 1, 0, 3, 6, 0, 4, 5, + 7, 7, 5, 2, 6, 5, 2, 6, 2, 4, 3, 2, 1, 0, 2, 1, + 7, 7, 4, 1, 0, 4, 3, 0, 1, 3, 2, 1, 1, 5, 6, 1, + 7, 7, 1, 0, 4, 1, 0, 4, 5, 4, 2, 1, 3, 2, 6, 1, + 7, 7, 0, 1, 4, 0, 1, 4, 2, 1, 3, 2, 5, 4, 6, 4, + 7, 7, 2, 3, 5, 2, 6, 5, 3, 6, 1, 2, 4, 5, 0, 5, + 7, 7, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 2, 1, 6, 5, + 7, 7, 2, 5, 6, 2, 5, 6, 4, 2, 1, 2, 0, 1, 3, 1, + 7, 7, 2, 5, 6, 2, 4, 2, 1, 4, 3, 1, 2, 3, 0, 1, + 7, 7, 6, 2, 5, 6, 2, 5, 1, 2, 0, 1, 4, 1, 3, 1, + 7, 7, 0, 4, 3, 0, 1, 3, 4, 1, 5, 4, 2, 1, 6, 3, + 7, 7, 2, 5, 6, 2, 5, 6, 4, 5, 3, 6, 1, 2, 0, 1, + 7, 7, 2, 5, 6, 2, 1, 4, 1, 2, 0, 1, 4, 0, 0, 3, + 7, 7, 6, 5, 1, 2, 4, 1, 0, 4, 3, 0, 1, 3, 3, 4, + 7, 7, 4, 1, 0, 4, 1, 0, 3, 6, 2, 3, 0, 2, 5, 0, + 7, 7, 4, 1, 0, 4, 3, 0, 1, 3, 2, 1, 5, 2, 6, 1, + 7, 7, 4, 1, 0, 4, 1, 0, 2, 3, 0, 2, 5, 0, 6, 5, + 7, 7, 0, 1, 5, 0, 6, 5, 3, 6, 2, 3, 0, 2, 4, 0, + 7, 7, 1, 0, 4, 1, 2, 4, 3, 2, 4, 3, 0, 4, 6, 5, + 7, 7, 3, 6, 2, 3, 1, 2, 0, 1, 4, 0, 1, 4, 5, 4, + 7, 7, 1, 0, 5, 1, 6, 5, 2, 6, 1, 2, 3, 2, 4, 3, + 7, 7, 2, 3, 1, 2, 0, 1, 4, 0, 5, 4, 6, 5, 4, 1, + 7, 7, 5, 2, 6, 5, 2, 6, 1, 2, 4, 1, 0, 4, 3, 1, + 7, 7, 2, 3, 1, 2, 0, 1, 4, 0, 5, 4, 6, 5, 5, 2, + 7, 7, 1, 4, 0, 1, 2, 0, 3, 2, 5, 3, 0, 5, 6, 3, + 7, 7, 2, 1, 3, 2, 6, 3, 5, 6, 0, 5, 2, 0, 5, 4, + 7, 7, 5, 2, 6, 5, 2, 6, 1, 2, 0, 1, 4, 0, 3, 0, + 7, 7, 4, 1, 0, 4, 3, 0, 1, 3, 2, 1, 5, 2, 6, 2, + 7, 7, 1, 0, 2, 1, 5, 2, 4, 5, 0, 4, 4, 1, 6, 3, + 7, 7, 2, 5, 6, 2, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, + 7, 7, 6, 5, 0, 4, 3, 0, 1, 3, 4, 1, 2, 4, 3, 2, + 7, 7, 2, 1, 5, 2, 4, 5, 0, 4, 3, 0, 6, 3, 2, 6, + 7, 7, 4, 0, 3, 4, 1, 3, 2, 1, 5, 2, 6, 5, 2, 6, + 7, 7, 6, 5, 2, 6, 1, 2, 4, 1, 0, 4, 3, 0, 1, 3, + 7, 7, 4, 1, 0, 4, 2, 0, 3, 2, 6, 3, 5, 6, 0, 5, + 7, 7, 0, 4, 3, 0, 4, 3, 2, 1, 5, 2, 6, 5, 2, 6, + 7, 7, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 0, 6, + 7, 7, 1, 0, 4, 1, 0, 4, 5, 2, 6, 5, 3, 6, 2, 3, + 7, 8, 0, 1, 4, 0, 5, 4, 2, 5, 1, 2, 5, 1, 4, 1, 2, 4, + 7, 8, 4, 1, 5, 4, 2, 5, 1, 2, 0, 1, 5, 0, 0, 4, 2, 0, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 3, 4, 5, 1, 6, 1, + 7, 8, 4, 1, 5, 4, 2, 5, 1, 2, 5, 1, 6, 5, 2, 4, 3, 2, + 7, 8, 1, 3, 0, 1, 4, 0, 2, 4, 1, 2, 4, 1, 5, 4, 1, 5, + 7, 8, 2, 0, 3, 2, 6, 3, 5, 6, 0, 5, 3, 0, 0, 6, 4, 0, + 7, 8, 1, 0, 2, 1, 5, 2, 4, 5, 0, 4, 2, 0, 5, 0, 6, 5, + 7, 8, 1, 0, 2, 1, 3, 2, 1, 3, 4, 3, 2, 4, 5, 2, 3, 5, + 7, 8, 2, 0, 3, 2, 6, 3, 5, 6, 0, 5, 3, 0, 6, 0, 4, 5, + 7, 8, 1, 0, 2, 1, 4, 3, 1, 5, 4, 1, 2, 4, 5, 2, 3, 5, + 7, 8, 3, 5, 2, 1, 4, 3, 1, 5, 4, 1, 2, 4, 5, 2, 4, 6, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 3, 4, 2, 1, 5, 2, + 7, 8, 3, 5, 2, 1, 4, 3, 1, 5, 4, 1, 2, 4, 5, 2, 0, 3, + 7, 8, 4, 0, 2, 4, 0, 2, 3, 0, 2, 3, 5, 2, 6, 5, 2, 6, + 7, 8, 3, 2, 6, 3, 5, 6, 2, 5, 0, 2, 5, 0, 4, 5, 2, 4, + 7, 8, 0, 5, 4, 0, 2, 4, 5, 2, 1, 5, 4, 1, 3, 4, 5, 3, + 7, 8, 2, 3, 1, 2, 4, 1, 5, 4, 1, 5, 5, 2, 6, 5, 3, 6, + 7, 8, 5, 2, 4, 5, 0, 4, 3, 0, 6, 3, 2, 6, 4, 2, 3, 2, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 2, 4, 3, 2, 5, 4, 2, 5, + 7, 8, 5, 6, 2, 5, 6, 2, 3, 2, 4, 3, 0, 4, 3, 0, 2, 4, + 7, 8, 1, 0, 5, 0, 3, 2, 1, 3, 5, 2, 6, 1, 6, 2, 6, 5, + 7, 8, 5, 4, 6, 5, 3, 6, 0, 3, 4, 0, 2, 4, 3, 2, 0, 2, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 2, 1, 5, + 7, 8, 5, 0, 6, 2, 0, 6, 1, 0, 2, 1, 5, 2, 4, 5, 4, 6, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 2, 1, 1, 5, 6, 1, + 7, 8, 0, 2, 4, 0, 1, 4, 0, 1, 3, 0, 1, 3, 5, 1, 6, 1, + 7, 8, 4, 2, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 1, 5, 6, 1, + 7, 8, 0, 4, 3, 0, 4, 3, 1, 4, 3, 1, 1, 5, 2, 1, 6, 1, + 7, 8, 2, 1, 0, 2, 3, 0, 5, 3, 2, 5, 3, 2, 4, 3, 6, 5, + 7, 8, 4, 2, 0, 4, 3, 0, 1, 3, 4, 1, 3, 4, 1, 5, 6, 1, + 7, 8, 2, 1, 0, 2, 3, 0, 5, 3, 2, 5, 6, 5, 4, 3, 5, 0, + 7, 8, 1, 0, 2, 1, 3, 2, 1, 3, 4, 2, 3, 4, 4, 5, 6, 4, + 7, 8, 6, 5, 1, 2, 4, 1, 0, 4, 3, 0, 1, 3, 0, 1, 3, 4, + 7, 8, 0, 1, 6, 5, 2, 3, 6, 4, 6, 3, 6, 2, 6, 0, 6, 1, + 7, 8, 6, 4, 1, 2, 2, 3, 6, 5, 4, 5, 6, 2, 6, 0, 6, 1, + 7, 8, 0, 1, 1, 2, 2, 3, 6, 5, 6, 4, 6, 3, 6, 0, 6, 2, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 6, 1, 5, 1, 2, 5, + 7, 8, 3, 0, 2, 3, 4, 2, 0, 4, 1, 0, 2, 1, 5, 2, 6, 2, + 7, 8, 2, 1, 3, 2, 6, 3, 5, 6, 0, 5, 2, 0, 5, 2, 4, 5, + 7, 8, 1, 0, 2, 1, 3, 2, 4, 3, 5, 2, 1, 5, 6, 1, 2, 6, + 7, 8, 2, 5, 4, 2, 1, 4, 3, 1, 0, 3, 1, 0, 2, 1, 6, 2, + 7, 8, 4, 5, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 6, 3, + 7, 8, 0, 1, 4, 0, 1, 4, 2, 1, 4, 2, 5, 4, 1, 5, 6, 3, + 7, 8, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 1, 6, 5, 0, + 7, 8, 4, 5, 0, 4, 1, 0, 4, 1, 3, 0, 1, 3, 6, 1, 2, 6, + 7, 8, 2, 5, 4, 2, 0, 4, 1, 0, 4, 1, 3, 0, 1, 3, 6, 1, + 7, 8, 1, 6, 2, 1, 0, 2, 1, 0, 4, 1, 3, 4, 2, 3, 4, 5, + 7, 8, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 1, 6, 5, 3, + 7, 8, 0, 4, 3, 0, 4, 3, 1, 4, 3, 1, 5, 1, 6, 2, 1, 6, + 7, 8, 2, 3, 1, 2, 0, 1, 5, 0, 4, 5, 0, 4, 2, 0, 6, 5, + 7, 8, 4, 5, 0, 4, 3, 0, 1, 3, 4, 1, 2, 4, 3, 2, 6, 2, + 7, 8, 2, 3, 1, 2, 0, 1, 4, 0, 5, 4, 4, 1, 2, 6, 5, 2, + 7, 8, 0, 1, 1, 2, 2, 3, 6, 3, 4, 5, 6, 2, 6, 0, 6, 1, + 7, 8, 4, 1, 0, 4, 3, 0, 1, 3, 0, 1, 2, 1, 5, 2, 6, 2, + 7, 8, 0, 1, 1, 2, 2, 3, 6, 5, 4, 5, 6, 2, 6, 4, 6, 1, + 7, 8, 0, 1, 4, 0, 0, 2, 5, 0, 6, 5, 3, 6, 2, 3, 5, 2, + 7, 8, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 2, 5, 6, 2, + 7, 8, 4, 5, 3, 4, 1, 3, 2, 1, 6, 2, 4, 6, 3, 2, 0, 1, + 7, 8, 1, 0, 2, 6, 3, 2, 4, 3, 5, 2, 1, 5, 6, 1, 6, 5, + 7, 8, 2, 3, 1, 2, 0, 1, 4, 0, 5, 4, 6, 5, 5, 2, 4, 1, + 7, 8, 4, 1, 0, 4, 3, 0, 1, 3, 3, 4, 2, 1, 2, 5, 6, 2, + 7, 8, 0, 6, 4, 0, 1, 4, 3, 1, 0, 3, 2, 4, 3, 2, 5, 2, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 2, 4, 3, 2, 1, 0, 6, 5, + 7, 8, 0, 1, 4, 0, 3, 2, 6, 3, 5, 6, 2, 5, 6, 2, 3, 5, + 7, 8, 5, 2, 6, 5, 2, 6, 4, 2, 0, 4, 3, 0, 2, 3, 1, 2, + 7, 8, 2, 0, 1, 2, 0, 1, 5, 0, 4, 5, 0, 4, 6, 0, 3, 6, + 7, 8, 0, 1, 2, 0, 3, 2, 2, 1, 1, 4, 5, 4, 5, 3, 1, 6, + 7, 8, 1, 6, 2, 1, 0, 2, 1, 0, 4, 1, 3, 4, 2, 3, 5, 6, + 7, 8, 6, 1, 0, 6, 1, 0, 5, 1, 0, 5, 2, 1, 3, 2, 4, 3, + 7, 8, 6, 5, 2, 6, 1, 2, 4, 1, 3, 4, 0, 3, 4, 0, 2, 4, + 7, 8, 1, 6, 0, 1, 5, 0, 1, 5, 3, 0, 4, 3, 2, 4, 0, 2, + 7, 8, 2, 6, 4, 2, 0, 4, 1, 0, 4, 1, 3, 4, 5, 3, 2, 5, + 7, 8, 1, 0, 2, 1, 6, 2, 5, 6, 1, 5, 4, 1, 3, 4, 2, 3, + 7, 8, 6, 1, 4, 3, 1, 0, 5, 1, 3, 2, 2, 1, 4, 6, 5, 4, + 7, 8, 4, 2, 0, 4, 1, 0, 4, 1, 3, 4, 6, 3, 5, 6, 3, 5, + 7, 8, 4, 1, 2, 4, 0, 2, 6, 0, 3, 6, 0, 3, 5, 0, 4, 5, + 7, 8, 5, 6, 4, 5, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, + 7, 8, 6, 3, 5, 6, 4, 5, 0, 4, 1, 0, 2, 1, 5, 2, 4, 1, + 7, 8, 0, 1, 2, 0, 3, 2, 2, 1, 1, 4, 5, 4, 5, 3, 4, 6, + 7, 8, 4, 0, 3, 4, 2, 3, 6, 2, 5, 6, 1, 5, 4, 1, 2, 1, + 7, 8, 6, 1, 0, 6, 4, 3, 5, 1, 0, 5, 2, 1, 3, 2, 5, 6, + 7, 8, 6, 2, 5, 6, 3, 5, 6, 3, 4, 3, 0, 4, 1, 0, 4, 1, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 6, 5, 1, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 6, 4, 2, + 7, 8, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 6, 0, 5, + 7, 8, 4, 0, 2, 4, 3, 2, 6, 3, 5, 6, 4, 5, 1, 2, 5, 1, + 7, 8, 5, 1, 2, 4, 3, 2, 0, 3, 5, 0, 4, 5, 1, 2, 0, 6, + 7, 8, 5, 6, 2, 5, 4, 2, 0, 4, 3, 0, 2, 3, 1, 4, 3, 1, + 7, 8, 0, 4, 1, 0, 4, 1, 3, 4, 5, 3, 6, 5, 2, 6, 4, 2, + 7, 8, 0, 1, 6, 5, 2, 3, 3, 4, 6, 4, 0, 5, 6, 2, 6, 1, + 7, 8, 1, 2, 0, 1, 4, 0, 5, 4, 2, 5, 3, 2, 6, 3, 5, 6, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 6, 1, 6, + 7, 8, 6, 2, 5, 6, 2, 5, 1, 2, 4, 1, 0, 4, 3, 0, 1, 3, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 5, 6, 1, + 7, 8, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 3, + 7, 8, 0, 4, 1, 0, 3, 2, 1, 4, 2, 5, 5, 3, 6, 4, 6, 3, + 7, 8, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 6, 2, 5, 6, 2, 5, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, + 7, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 2, + 7, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 4, 5, + 7, 9, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 4, 3, + 7, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 5, + 7, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 0, 4, 1, 0, 4, 1, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, 4, 0, 5, 0, + 7, 9, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, + 7, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 2, 0, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, + 7, 9, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 5, 2, 4, 1, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 0, 0, 4, 0, 6, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 0, 0, 4, 2, 6, + 7, 9, 1, 2, 3, 1, 0, 3, 1, 0, 2, 0, 3, 2, 5, 3, 4, 0, 1, 6, + 7, 9, 0, 1, 2, 4, 0, 2, 3, 1, 3, 2, 2, 1, 4, 1, 5, 2, 2, 6, + 7, 9, 0, 1, 2, 4, 0, 2, 5, 2, 3, 1, 3, 2, 2, 1, 4, 1, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 1, 5, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 1, 5, 4, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 2, 1, 5, 1, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, 1, 6, + 7, 9, 2, 0, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, 5, 3, 2, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 3, 0, 5, 1, 0, 6, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 0, 0, 4, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 2, 6, + 7, 9, 2, 0, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, 5, 3, 5, 6, + 7, 9, 1, 2, 3, 1, 0, 3, 1, 0, 2, 0, 3, 2, 4, 0, 6, 5, 6, 3, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, 0, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 6, 5, 4, 0, 3, 2, 4, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 2, 1, 5, 1, 5, 6, + 7, 9, 2, 0, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, 5, 3, 4, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, 2, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 3, 0, 5, 1, 5, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 5, 4, 3, 1, 3, 2, 3, 0, 5, 1, 2, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 4, 0, 3, 1, 3, 2, 5, 1, 5, 3, 0, 6, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 0, 5, 5, 4, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 3, 4, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 2, 0, 2, 0, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 3, 4, 3, 6, + 7, 9, 0, 1, 2, 4, 0, 2, 5, 2, 3, 1, 3, 2, 2, 1, 4, 1, 5, 6, + 7, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 2, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 6, 5, 6, 1, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 3, 4, 2, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 3, 4, 0, 6, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 3, 6, 1, 6, 0, 1, 1, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 0, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 2, 1, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 5, 6, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 3, 6, 1, 6, 0, 1, 0, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 2, 0, 2, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 2, 0, 2, 4, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 2, 1, 2, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 3, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 2, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 5, 1, 3, 1, 3, 2, 2, 1, 6, 0, 6, 4, + 7, 9, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 0, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 0, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 3, 6, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 3, 6, 1, 6, 0, 1, 2, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 5, 5, 3, 1, 5, 3, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 1, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 1, 4, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 5, 1, 3, 1, 3, 2, 3, 0, 6, 4, 6, 0, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, 5, 2, 5, 0, 1, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 2, 1, 6, 0, + 7, 9, 5, 3, 3, 2, 4, 3, 5, 4, 2, 5, 1, 2, 4, 1, 6, 0, 6, 2, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, 5, 2, 5, 0, 0, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 5, 5, 3, 1, 5, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 4, 6, + 7, 9, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 3, 6, 1, 6, 0, 1, 5, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 1, 5, 2, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 5, 5, 3, 1, 5, 1, 6, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 2, 1, 3, 6, + 7, 9, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 0, 5, 5, 4, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 5, 5, 3, 1, 5, 0, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 5, 5, 2, 5, 0, 4, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 1, 0, 6, + 7, 9, 0, 1, 2, 5, 0, 2, 5, 3, 3, 1, 3, 2, 5, 1, 6, 4, 6, 0, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 1, 5, 2, 0, 6, + 7, 9, 6, 3, 1, 2, 6, 5, 3, 4, 6, 4, 0, 5, 6, 0, 6, 1, 6, 2, + 7, 9, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 6, 2, 5, 6, 2, 5, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 3, 4, 6, 5, 6, 0, + 7, 9, 1, 4, 2, 4, 2, 3, 0, 4, 3, 1, 4, 5, 0, 5, 6, 4, 6, 3, + 7, 9, 4, 1, 5, 4, 6, 5, 3, 6, 2, 3, 1, 2, 5, 2, 0, 5, 2, 0, + 7, 9, 4, 1, 3, 1, 2, 3, 4, 0, 5, 0, 5, 2, 5, 4, 6, 4, 5, 6, + 7, 9, 1, 0, 2, 1, 6, 2, 3, 6, 5, 3, 4, 5, 3, 4, 2, 3, 0, 2, + 7, 9, 0, 2, 5, 0, 1, 5, 2, 1, 4, 2, 5, 4, 6, 5, 3, 6, 2, 3, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 0, 6, 1, 3, 4, 1, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 4, 5, 1, 6, 1, 0, 6, + 7, 9, 0, 4, 1, 0, 4, 1, 3, 4, 2, 3, 6, 2, 5, 6, 1, 5, 2, 1, + 7, 9, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 3, 6, 5, 3, 6, + 7, 9, 6, 5, 3, 6, 2, 3, 0, 4, 0, 5, 1, 0, 2, 0, 1, 2, 5, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 3, 5, 1, 6, 1, 0, 6, + 7, 9, 5, 2, 6, 5, 3, 6, 2, 3, 0, 2, 4, 0, 5, 4, 1, 5, 0, 1, + 7, 9, 2, 4, 1, 2, 4, 1, 5, 4, 0, 5, 1, 0, 6, 4, 3, 6, 2, 3, + 7, 9, 6, 2, 5, 6, 2, 5, 1, 2, 0, 1, 4, 0, 1, 4, 3, 1, 0, 3, + 7, 9, 0, 5, 6, 0, 1, 6, 4, 1, 2, 4, 3, 2, 1, 3, 5, 1, 6, 5, + 7, 9, 6, 5, 3, 6, 2, 3, 5, 2, 0, 5, 1, 0, 4, 1, 0, 4, 2, 0, + 7, 9, 0, 4, 3, 0, 1, 3, 4, 1, 2, 4, 6, 2, 5, 6, 2, 5, 3, 2, + 7, 9, 1, 0, 4, 1, 5, 4, 0, 5, 6, 0, 3, 6, 2, 3, 0, 2, 3, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 3, + 7, 9, 0, 1, 1, 2, 2, 3, 0, 3, 4, 1, 5, 4, 5, 3, 6, 0, 6, 4, + 7, 9, 0, 1, 4, 0, 1, 4, 2, 1, 5, 2, 4, 5, 6, 5, 3, 6, 2, 3, + 7, 9, 1, 0, 6, 3, 0, 4, 5, 0, 3, 5, 5, 6, 1, 2, 1, 4, 6, 2, + 7, 9, 6, 2, 5, 6, 2, 5, 1, 2, 0, 3, 4, 0, 1, 4, 3, 1, 3, 4, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 1, 5, 6, 6, 0, + 7, 9, 0, 4, 1, 0, 2, 1, 3, 2, 0, 3, 2, 4, 5, 4, 6, 5, 3, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 2, 6, 1, 5, 6, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 3, 5, 4, 6, 1, 6, 5, + 7, 9, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 4, 6, 2, + 7, 9, 0, 4, 3, 0, 4, 3, 6, 1, 5, 6, 1, 5, 2, 1, 5, 2, 6, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 4, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 1, 0, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 2, 4, + 7, 10, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, + 7, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, + 7, 10, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 0, 2, 1, 3, 5, 1, + 7, 10, 0, 1, 1, 2, 3, 4, 0, 2, 3, 0, 2, 4, 5, 2, 1, 5, 4, 1, 3, 5, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 2, 2, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 2, 1, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, 1, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, 0, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, 3, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 4, 5, 4, 6, + 7, 10, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 4, 3, 3, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 5, 2, 6, + 7, 10, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 4, 3, 2, 6, + 7, 10, 2, 3, 1, 2, 4, 1, 5, 4, 2, 5, 0, 2, 4, 0, 0, 1, 5, 0, 6, 5, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 0, 4, 5, 6, + 7, 10, 2, 0, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 4, 3, 4, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 4, 5, 6, 5, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 6, 5, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, 0, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, 2, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 1, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, 3, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 0, 4, 1, 0, 4, 1, 0, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, 1, 6, + 7, 10, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, 4, 0, 5, 0, 0, 6, + 7, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 3, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, 5, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 3, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, 0, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 2, 0, 5, 3, 2, 3, 6, 2, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 6, 4, 6, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 2, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, 5, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 3, 0, 4, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 3, 6, + 7, 10, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, 4, 0, 5, 0, 2, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 2, 0, 5, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 2, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 2, 0, 5, 3, 2, 3, 0, 6, + 7, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 1, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 4, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 6, 4, 6, 0, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 5, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 0, 4, 1, 0, 4, 1, 5, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 2, 0, 0, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 1, 0, 5, 1, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 5, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, 0, 6, + 7, 10, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 5, 4, 4, 0, 5, 0, 5, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 6, 5, 6, 4, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 1, 6, + 7, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 4, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 4, 6, + 7, 10, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 0, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, 5, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, 1, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 3, 6, + 7, 10, 4, 3, 4, 1, 1, 2, 5, 4, 2, 5, 3, 1, 5, 3, 3, 2, 6, 0, 6, 2, + 7, 10, 1, 0, 2, 1, 3, 2, 4, 3, 5, 4, 1, 5, 6, 1, 4, 6, 2, 6, 5, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, 0, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, 3, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, 2, 6, + 7, 10, 0, 1, 2, 5, 0, 2, 3, 0, 3, 1, 3, 2, 2, 1, 5, 1, 6, 5, 6, 4, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 1, 6, + 7, 10, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 6, + 7, 10, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 2, 0, 1, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 1, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 4, 2, 5, 2, 1, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 0, 5, 2, 5, 0, 5, 1, 4, 6, + 7, 10, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, 4, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 5, 2, 4, 1, 0, 6, + 7, 10, 4, 0, 1, 4, 3, 1, 2, 3, 1, 2, 6, 1, 0, 6, 5, 0, 1, 5, 0, 1, + 7, 10, 3, 2, 6, 3, 5, 6, 0, 5, 2, 0, 5, 2, 1, 5, 2, 1, 4, 2, 5, 4, + 7, 10, 2, 0, 1, 2, 3, 1, 0, 3, 6, 0, 1, 6, 5, 1, 0, 5, 4, 0, 1, 4, + 7, 10, 6, 4, 1, 2, 6, 5, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 2, 6, 3, + 7, 10, 0, 1, 6, 5, 2, 3, 3, 4, 6, 4, 0, 5, 6, 0, 6, 1, 6, 2, 6, 3, + 7, 10, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 0, 5, 5, 2, 6, 1, 2, 6, + 7, 10, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 0, 5, 2, 6, 2, 0, 6, + 7, 10, 6, 4, 1, 2, 6, 5, 3, 4, 4, 5, 0, 5, 6, 3, 6, 1, 6, 2, 0, 4, + 7, 10, 1, 0, 2, 1, 0, 2, 3, 2, 4, 3, 2, 4, 5, 2, 4, 5, 6, 4, 1, 6, + 7, 10, 0, 1, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 5, 2, 6, + 7, 10, 0, 2, 5, 0, 4, 5, 2, 4, 1, 2, 5, 1, 6, 5, 3, 6, 2, 3, 2, 6, + 7, 10, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 1, 0, 5, 6, 0, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 2, 6, 4, 0, 2, 4, 0, + 7, 10, 0, 4, 3, 0, 2, 3, 5, 2, 6, 5, 2, 6, 4, 2, 1, 4, 3, 1, 4, 3, + 7, 10, 1, 6, 2, 1, 0, 2, 1, 0, 4, 1, 3, 4, 2, 3, 5, 6, 4, 5, 3, 1, + 7, 10, 6, 5, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 2, 6, 4, + 7, 10, 0, 1, 6, 5, 2, 3, 3, 4, 6, 4, 0, 5, 6, 0, 6, 1, 6, 2, 5, 3, + 7, 10, 0, 1, 1, 2, 2, 3, 5, 4, 0, 4, 5, 0, 5, 3, 5, 2, 6, 5, 6, 1, + 7, 10, 0, 3, 2, 0, 1, 2, 3, 1, 4, 3, 2, 4, 0, 4, 6, 0, 5, 6, 0, 5, + 7, 10, 0, 3, 2, 0, 1, 2, 3, 1, 4, 3, 0, 5, 0, 4, 6, 0, 5, 6, 1, 4, + 7, 10, 0, 1, 6, 5, 2, 3, 3, 4, 6, 4, 0, 5, 6, 0, 6, 1, 6, 2, 4, 2, + 7, 10, 1, 2, 5, 1, 6, 5, 2, 6, 1, 6, 5, 2, 4, 1, 0, 4, 3, 0, 1, 3, + 7, 10, 4, 2, 6, 2, 5, 3, 4, 1, 2, 0, 6, 3, 5, 2, 0, 1, 0, 4, 6, 0, + 7, 10, 4, 2, 3, 6, 5, 3, 5, 1, 2, 0, 6, 0, 5, 2, 1, 4, 0, 4, 5, 4, + 7, 10, 4, 0, 5, 4, 4, 1, 2, 1, 3, 2, 0, 3, 3, 4, 5, 3, 6, 1, 6, 5, + 7, 10, 0, 4, 1, 0, 2, 1, 4, 2, 3, 4, 5, 3, 4, 5, 5, 2, 6, 3, 2, 6, + 7, 10, 1, 6, 2, 1, 0, 2, 1, 0, 4, 1, 3, 4, 2, 3, 5, 6, 4, 5, 4, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 3, 6, 1, 6, 5, 5, 1, + 7, 10, 1, 0, 4, 1, 0, 4, 2, 0, 3, 2, 6, 3, 5, 6, 0, 5, 5, 2, 6, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 3, 5, 1, 5, 4, 6, 1, 5, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 4, 1, 6, 1, 6, 5, + 7, 10, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 3, 2, 5, 6, 1, 4, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 2, 5, 4, 6, 5, 6, 0, 0, 2, + 7, 10, 2, 0, 5, 2, 1, 5, 0, 1, 3, 0, 5, 3, 6, 5, 4, 6, 0, 4, 4, 3, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 2, 1, 5, 6, 2, 6, 5, + 7, 10, 5, 0, 6, 5, 2, 6, 3, 2, 0, 3, 4, 0, 2, 4, 4, 3, 1, 4, 3, 1, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 6, 3, 5, 6, 3, 5, 4, 5, 4, 6, + 7, 10, 5, 2, 2, 1, 3, 2, 4, 3, 1, 4, 5, 0, 6, 1, 6, 0, 1, 5, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 2, 6, 4, 4, 2, 5, 1, + 7, 10, 4, 2, 2, 3, 4, 1, 0, 1, 3, 0, 6, 4, 0, 6, 5, 0, 4, 5, 1, 5, + 7, 10, 2, 1, 5, 2, 3, 5, 0, 3, 4, 0, 6, 4, 3, 6, 1, 3, 4, 1, 5, 4, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 6, 5, 6, 3, + 7, 10, 0, 1, 4, 0, 1, 4, 2, 1, 5, 2, 4, 5, 6, 5, 3, 6, 2, 3, 5, 3, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 5, 6, 1, 6, 2, 4, 2, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 3, 5, 2, 6, 5, 6, 4, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 3, 6, 2, 4, 0, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 4, 6, 5, 6, 3, + 7, 10, 0, 5, 6, 0, 1, 6, 0, 1, 1, 5, 2, 1, 3, 2, 4, 3, 6, 4, 4, 5, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 5, 6, 4, 2, 0, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 5, 6, 3, + 7, 10, 2, 1, 2, 0, 3, 2, 4, 3, 1, 4, 5, 1, 5, 0, 6, 0, 1, 6, 6, 5, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 3, 5, 1, 6, 1, 6, 4, 6, 5, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 2, 4, 1, 6, 0, 5, 6, + 7, 10, 3, 1, 0, 3, 5, 0, 1, 5, 2, 1, 6, 2, 0, 6, 0, 4, 4, 2, 4, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 4, 6, 1, 6, 2, 6, 5, + 7, 10, 0, 3, 2, 0, 1, 2, 3, 1, 4, 3, 2, 4, 5, 4, 5, 0, 6, 4, 6, 1, + 7, 10, 0, 1, 6, 5, 2, 3, 3, 4, 6, 4, 0, 5, 6, 2, 6, 1, 4, 2, 5, 1, + 7, 10, 5, 2, 6, 5, 2, 6, 4, 2, 0, 4, 3, 0, 2, 3, 1, 0, 1, 3, 4, 1, + 7, 10, 3, 4, 1, 3, 4, 1, 0, 4, 3, 0, 1, 0, 2, 1, 5, 2, 6, 5, 2, 6, + 7, 10, 5, 6, 2, 5, 6, 2, 3, 6, 0, 3, 4, 0, 5, 4, 1, 4, 3, 1, 2, 1, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 5, 4, 2, + 7, 10, 1, 0, 2, 1, 3, 2, 0, 3, 5, 0, 1, 5, 4, 5, 6, 4, 3, 6, 2, 6, + 7, 10, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 1, 2, 5, 6, 0, 3, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, 1, 5, + 7, 11, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, + 7, 11, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 0, 2, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 5, 1, + 7, 11, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 11, 3, 6, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 2, 6, 5, 1, 0, 3, 1, 6, 0, 1, + 7, 11, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 0, 3, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 4, 6, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 6, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 2, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 2, 5, 6, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 2, 0, 6, + 7, 11, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 6, 5, + 7, 11, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 1, 0, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, 6, 4, + 7, 11, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 4, 5, 1, 0, 1, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 2, 4, 2, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, 5, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, 2, 6, + 7, 11, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, 6, 1, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, 3, 6, + 7, 11, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, 6, 4, + 7, 11, 0, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 11, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, 0, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, 0, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, 1, 6, + 7, 11, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 5, 4, 3, 5, 1, 5, 0, 6, + 7, 11, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 2, 6, + 7, 11, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, 3, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, 2, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, 5, 6, + 7, 11, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, 5, 6, + 7, 11, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 4, 5, 6, + 7, 11, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, 5, 6, + 7, 11, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 4, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 4, 0, 2, 4, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 6, 5, 6, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 0, 2, 1, 3, 5, 1, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 1, 6, + 7, 11, 1, 0, 4, 1, 0, 4, 5, 0, 4, 5, 3, 4, 1, 3, 5, 1, 2, 3, 1, 2, 2, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 3, 2, 0, 3, 2, 4, 5, 2, 1, 6, + 7, 11, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 5, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 0, 2, 1, 3, 5, 1, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 0, 2, 1, 3, 5, 1, 6, 3, + 7, 11, 4, 3, 0, 4, 1, 0, 2, 1, 3, 2, 0, 5, 5, 3, 0, 3, 1, 5, 5, 2, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 5, 0, 5, 2, 0, 5, 1, 4, 1, 6, 3, + 7, 11, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 3, 2, 0, 3, 1, 6, + 7, 11, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, 6, 2, + 7, 11, 0, 1, 2, 4, 0, 2, 4, 5, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 6, + 7, 11, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, + 7, 11, 6, 5, 0, 6, 5, 0, 1, 5, 6, 1, 2, 6, 5, 2, 3, 5, 6, 3, 4, 6, 5, 4, + 7, 11, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 1, 2, 5, 6, 2, 1, 6, 3, 1, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 1, 3, 5, 6, 1, 4, 6, 1, 4, 3, 1, + 7, 11, 1, 4, 2, 3, 4, 2, 0, 6, 4, 5, 6, 5, 3, 1, 6, 4, 3, 0, 3, 6, 4, 3, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 2, 6, 4, 2, 6, 5, 2, 0, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 4, 0, 3, 0, 2, 0, 6, 0, 3, 6, + 7, 11, 0, 1, 2, 0, 5, 2, 6, 5, 2, 6, 1, 2, 4, 1, 2, 4, 3, 2, 4, 3, 3, 1, + 7, 11, 4, 5, 1, 4, 2, 1, 3, 2, 6, 3, 5, 6, 2, 5, 4, 2, 3, 5, 0, 5, 2, 0, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 2, 5, 2, 5, 0, 6, 2, 4, 6, 5, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 6, 1, 2, 6, 0, 2, 6, 0, 5, 2, 0, 5, + 7, 11, 0, 5, 6, 0, 1, 6, 5, 1, 2, 5, 6, 2, 4, 3, 3, 2, 4, 5, 6, 4, 6, 5, + 7, 11, 0, 5, 6, 0, 1, 6, 5, 1, 2, 5, 6, 2, 3, 6, 5, 3, 4, 5, 6, 4, 4, 3, + 7, 11, 0, 5, 0, 6, 1, 2, 1, 6, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 0, 2, 2, 4, 5, 2, 4, 5, 6, 5, 6, 0, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 0, 6, 4, 2, 0, 4, 2, 0, 6, 4, + 7, 11, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 5, 1, 5, 2, 6, 1, 2, 6, 4, 2, 5, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 2, 5, 4, 2, 6, 2, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 4, 6, 2, 0, 2, 4, 0, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 3, 1, 5, 3, 4, 5, 1, 4, 6, 5, 6, 1, + 7, 11, 0, 4, 3, 0, 4, 3, 2, 4, 3, 2, 1, 3, 2, 1, 4, 1, 5, 2, 6, 5, 2, 6, + 7, 11, 0, 5, 0, 6, 1, 4, 1, 6, 2, 3, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 11, 0, 1, 4, 0, 5, 4, 6, 5, 3, 6, 2, 3, 1, 2, 4, 1, 2, 4, 5, 2, 1, 5, + 7, 11, 0, 4, 3, 0, 4, 3, 2, 4, 6, 2, 1, 6, 5, 1, 2, 5, 3, 2, 1, 3, 4, 1, + 7, 11, 0, 1, 6, 5, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 2, 6, 3, 6, 4, + 7, 11, 4, 1, 0, 4, 1, 0, 3, 1, 0, 3, 5, 1, 6, 5, 1, 6, 2, 1, 5, 2, 6, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 4, 6, 5, 4, 6, + 7, 11, 1, 0, 2, 1, 3, 2, 4, 3, 0, 4, 2, 0, 5, 2, 6, 5, 3, 6, 6, 0, 0, 5, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 2, 4, 0, 6, 4, 0, 6, 3, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 2, 0, 5, 2, 6, 5, 4, 6, 0, 5, 6, 0, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 2, 6, 3, 6, 0, 3, 4, 0, + 7, 11, 4, 6, 5, 4, 6, 5, 3, 6, 5, 3, 2, 5, 3, 2, 5, 0, 6, 0, 1, 0, 2, 1, + 7, 11, 2, 0, 4, 2, 5, 4, 3, 5, 1, 3, 0, 1, 2, 1, 3, 2, 6, 3, 5, 6, 4, 3, + 7, 11, 4, 3, 4, 2, 1, 4, 3, 1, 0, 3, 1, 0, 3, 2, 3, 5, 2, 5, 6, 0, 4, 6, + 7, 11, 0, 1, 0, 2, 2, 3, 5, 1, 1, 3, 5, 2, 6, 3, 6, 0, 5, 3, 4, 5, 3, 4, + 7, 11, 4, 0, 1, 4, 6, 1, 0, 6, 3, 0, 1, 3, 5, 1, 0, 5, 6, 5, 2, 3, 0, 2, + 7, 11, 0, 1, 5, 0, 4, 5, 1, 4, 2, 1, 3, 2, 4, 3, 4, 2, 6, 4, 2, 6, 3, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 3, 5, 6, 3, 4, 6, 5, 6, + 7, 11, 6, 3, 5, 6, 2, 5, 3, 2, 5, 3, 4, 5, 2, 4, 1, 2, 5, 1, 0, 1, 4, 0, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 6, 5, 4, 6, + 7, 11, 0, 4, 3, 0, 2, 3, 5, 2, 6, 5, 2, 6, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, + 7, 11, 5, 0, 0, 1, 3, 0, 5, 3, 2, 5, 6, 2, 4, 6, 5, 4, 1, 5, 6, 1, 3, 6, + 7, 11, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 2, 1, 5, 1, 2, 5, 6, 4, 6, 2, 3, 6, + 7, 11, 3, 2, 6, 3, 5, 6, 0, 5, 2, 0, 1, 2, 0, 1, 5, 1, 2, 5, 4, 2, 6, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 1, 5, 3, 5, 1, 6, 4, 6, 5, 3, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 3, 6, 0, 6, 2, 6, 3, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 6, 5, 4, 6, 3, 6, 1, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 6, 5, 1, 6, 2, 6, 4, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 3, 4, 2, 6, 1, 6, 4, 5, 2, 3, 5, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 6, 1, 4, 6, 6, 5, 2, 6, + 7, 11, 0, 3, 0, 6, 1, 2, 1, 5, 2, 4, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 11, 5, 1, 6, 5, 4, 6, 3, 4, 2, 3, 0, 2, 1, 0, 5, 0, 6, 0, 2, 6, 4, 2, + 7, 11, 1, 0, 2, 1, 3, 2, 4, 3, 0, 4, 5, 2, 3, 5, 6, 0, 6, 5, 6, 2, 3, 6, + 7, 11, 0, 3, 4, 0, 2, 4, 3, 2, 1, 3, 4, 1, 5, 1, 6, 0, 6, 1, 5, 3, 4, 5, + 7, 11, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 5, 2, 6, 3, 4, 4, 5, 4, 6, 5, 6, + 7, 11, 0, 2, 1, 0, 2, 1, 0, 3, 3, 1, 5, 4, 5, 3, 6, 4, 6, 2, 6, 0, 1, 6, + 7, 11, 4, 1, 5, 4, 2, 5, 1, 2, 0, 1, 5, 0, 6, 5, 3, 6, 2, 3, 0, 2, 4, 0, + 7, 11, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 6, 1, 6, 2, 5, 1, 3, 5, 5, 2, 4, 5, + 7, 11, 0, 5, 0, 6, 1, 4, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 3, 6, 4, 6, 2, 2, 0, 4, 0, + 7, 11, 0, 2, 1, 0, 2, 1, 0, 3, 3, 1, 5, 4, 5, 3, 6, 4, 6, 2, 4, 0, 1, 4, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 6, 2, 0, 6, 1, 6, + 7, 11, 0, 3, 4, 0, 1, 4, 3, 1, 4, 3, 0, 1, 2, 4, 6, 2, 5, 6, 2, 5, 1, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 6, 1, 6, 5, + 7, 11, 4, 1, 5, 4, 2, 5, 1, 2, 0, 1, 4, 0, 5, 0, 6, 5, 3, 6, 2, 3, 5, 3, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 3, 4, 2, 5, 1, 5, 4, 6, 5, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 5, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 6, 3, 6, 4, + 7, 11, 0, 4, 3, 0, 1, 3, 4, 1, 3, 4, 5, 1, 6, 5, 1, 6, 2, 1, 5, 2, 6, 2, + 7, 11, 4, 1, 0, 4, 3, 0, 2, 5, 5, 4, 6, 5, 2, 6, 1, 2, 3, 1, 6, 3, 2, 3, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 2, 5, 4, 5, 3, 6, 0, 6, 5, 3, 6, + 7, 11, 5, 2, 2, 4, 5, 3, 4, 1, 5, 4, 0, 1, 3, 0, 0, 2, 6, 2, 6, 3, 0, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 4, 5, 2, 5, 3, 6, 1, 0, 6, + 7, 11, 0, 3, 0, 4, 1, 2, 1, 5, 1, 6, 2, 4, 2, 6, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 11, 4, 0, 3, 4, 5, 3, 0, 5, 1, 0, 2, 1, 3, 2, 4, 1, 5, 2, 6, 4, 5, 6, + 7, 11, 2, 3, 4, 2, 0, 4, 5, 0, 1, 5, 4, 1, 3, 4, 5, 3, 1, 0, 6, 5, 6, 2, + 7, 11, 4, 1, 0, 4, 3, 0, 4, 3, 5, 4, 6, 5, 2, 6, 1, 2, 3, 1, 6, 3, 2, 5, + 7, 11, 0, 3, 4, 0, 2, 4, 3, 2, 1, 3, 0, 1, 6, 0, 5, 6, 2, 5, 1, 5, 4, 1, + 7, 11, 0, 3, 0, 4, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 6, 4, 5, 5, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 3, 4, 2, 5, 1, 5, 4, 6, 1, 5, 6, + 7, 11, 4, 1, 5, 4, 6, 5, 3, 6, 2, 3, 1, 2, 0, 1, 5, 0, 4, 0, 5, 2, 6, 2, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 4, 2, 3, 5, 4, 5, 3, 0, 6, 5, 6, 1, + 7, 11, 0, 4, 1, 0, 4, 1, 3, 4, 2, 3, 1, 2, 6, 1, 5, 6, 3, 5, 5, 4, 2, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 5, 1, 4, 2, 6, 2, 3, 6, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 2, 1, 6, 5, 2, 4, 1, 0, 3, + 7, 11, 0, 3, 0, 4, 1, 2, 1, 5, 1, 6, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 11, 0, 1, 1, 2, 2, 3, 5, 4, 0, 4, 5, 3, 5, 1, 6, 3, 6, 4, 4, 2, 0, 3, + 7, 11, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 6, 3, 6, 4, 5, + 7, 11, 4, 3, 2, 4, 3, 2, 0, 3, 2, 1, 5, 4, 5, 0, 6, 4, 6, 1, 1, 5, 0, 6, + 7, 11, 6, 4, 3, 6, 1, 3, 4, 1, 0, 4, 2, 0, 3, 2, 0, 1, 5, 0, 6, 5, 5, 2, + 7, 11, 6, 1, 2, 6, 1, 2, 0, 1, 3, 0, 4, 3, 5, 4, 3, 5, 2, 0, 4, 0, 5, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 4, 3, 0, 2, + 7, 12, 3, 6, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 2, 6, 5, 1, 0, 3, 1, 6, 0, 1, 0, 6, + 7, 12, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 5, 1, 2, 4, + 7, 12, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 2, 3, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 2, 3, 1, 4, 0, 2, 4, 5, 1, 0, 5, 4, 5, 3, 4, 5, 3, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, 1, 5, 6, 1, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, 4, 6, 5, 3, + 7, 12, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 1, 6, + 7, 12, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 3, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, 6, 1, + 7, 12, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 0, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, 0, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, 2, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 0, 2, 1, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 0, 2, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 0, 2, 6, 1, 6, 5, + 7, 12, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 1, 5, 3, 2, 5, 1, 0, 4, 1, 1, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 3, 5, 0, 3, 5, 6, + 7, 12, 3, 6, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 2, 6, 5, 1, 0, 3, 1, 6, 0, 1, 1, 4, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 3, 5, 0, 3, 3, 6, + 7, 12, 0, 1, 2, 4, 0, 2, 2, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 4, 6, + 7, 12, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 1, 5, 3, 2, 5, 1, 0, 4, 1, 2, 6, + 7, 12, 3, 6, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 2, 6, 5, 1, 0, 3, 1, 6, 0, 1, 0, 4, + 7, 12, 1, 3, 4, 1, 3, 4, 2, 3, 0, 2, 4, 0, 5, 4, 2, 5, 4, 2, 0, 5, 1, 5, 3, 6, + 7, 12, 1, 3, 4, 1, 3, 4, 2, 3, 0, 2, 4, 0, 5, 4, 2, 5, 4, 2, 0, 5, 1, 5, 0, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 0, 4, 5, 5, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 0, 3, 5, 6, + 7, 12, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 4, 6, + 7, 12, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 0, 6, + 7, 12, 3, 6, 1, 3, 2, 1, 0, 2, 5, 0, 6, 5, 2, 6, 5, 1, 0, 3, 1, 6, 0, 1, 4, 5, + 7, 12, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 3, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 0, 3, 0, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 0, 2, 5, 6, + 7, 12, 0, 3, 6, 0, 5, 6, 3, 5, 1, 3, 6, 1, 4, 6, 3, 4, 2, 3, 6, 2, 3, 6, 5, 4, + 7, 12, 0, 1, 4, 0, 5, 4, 1, 5, 4, 1, 2, 4, 1, 2, 6, 1, 4, 6, 2, 6, 3, 2, 4, 3, + 7, 12, 4, 1, 3, 2, 3, 0, 4, 2, 5, 1, 5, 0, 3, 5, 4, 3, 5, 4, 6, 5, 3, 6, 4, 6, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 4, 2, 3, 4, 5, 3, 0, 5, 6, 3, 1, 6, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 6, 3, 0, 6, 5, 0, 1, 5, 4, 1, 2, 4, + 7, 12, 6, 2, 5, 6, 3, 5, 2, 3, 1, 2, 4, 1, 5, 4, 2, 5, 4, 2, 0, 4, 1, 0, 5, 1, + 7, 12, 5, 4, 6, 5, 3, 6, 4, 3, 0, 4, 3, 0, 1, 3, 0, 1, 4, 1, 3, 5, 2, 3, 4, 2, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 5, 0, 1, 5, 6, 1, 0, 6, + 7, 12, 1, 2, 0, 1, 2, 0, 3, 2, 0, 3, 1, 3, 4, 2, 0, 4, 5, 4, 2, 5, 6, 2, 1, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 2, 4, 5, 6, 4, 3, 6, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 5, 0, 1, 5, 6, 1, 2, 6, + 7, 12, 1, 2, 0, 1, 2, 0, 3, 2, 0, 3, 1, 3, 4, 2, 0, 4, 6, 4, 2, 6, 5, 2, 4, 5, + 7, 12, 1, 0, 2, 1, 0, 2, 3, 0, 4, 3, 0, 4, 5, 0, 3, 5, 4, 5, 6, 4, 3, 6, 6, 0, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 4, 1, 0, 4, 5, 0, 2, 5, 6, 5, 2, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 6, 4, 0, 6, 5, 0, 3, 5, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 1, 5, 6, 1, 0, 6, 5, 0, 4, 5, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 6, 1, 0, 6, 5, 0, 2, 5, + 7, 12, 5, 4, 3, 5, 4, 3, 6, 4, 3, 6, 2, 3, 4, 2, 0, 4, 3, 0, 1, 0, 2, 1, 6, 2, + 7, 12, 4, 1, 3, 2, 3, 0, 4, 2, 5, 1, 5, 0, 3, 5, 4, 3, 5, 4, 6, 5, 0, 6, 3, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 6, 1, 0, 6, 5, 0, 1, 5, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 3, 1, 5, 1, 6, 5, 4, 6, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 5, 4, 3, 5, 6, 3, 4, 6, + 7, 12, 1, 0, 2, 1, 3, 2, 0, 3, 4, 3, 1, 4, 4, 0, 5, 4, 0, 5, 3, 5, 6, 0, 1, 6, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 6, 2, 4, 6, 5, 0, 1, 5, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 5, 4, 2, 5, 1, 5, 6, 1, 5, 6, + 7, 12, 0, 2, 1, 0, 2, 1, 0, 3, 3, 1, 4, 2, 5, 3, 6, 1, 6, 4, 4, 0, 3, 4, 1, 5, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 0, 3, 2, 4, 3, 4, 1, 0, 4, 5, 2, 5, 4, 6, 0, 3, 6, + 7, 12, 5, 0, 2, 5, 6, 2, 3, 6, 2, 3, 1, 2, 0, 1, 4, 0, 1, 4, 5, 1, 6, 5, 0, 2, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 6, 4, 2, 6, 5, 2, 3, 5, + 7, 12, 0, 2, 1, 0, 5, 1, 3, 5, 6, 3, 2, 6, 4, 2, 3, 4, 5, 4, 2, 5, 1, 2, 4, 1, + 7, 12, 0, 2, 1, 0, 2, 1, 0, 3, 3, 1, 4, 0, 1, 4, 4, 2, 3, 4, 5, 4, 6, 5, 3, 6, + 7, 12, 0, 1, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 6, 1, 2, 6, 4, 6, 3, 4, 5, 3, 6, 5, + 7, 12, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 5, 0, 6, 5, 0, 6, 3, 4, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 2, 5, 6, 0, 3, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 3, 5, 1, 5, 4, 6, 3, 4, 6, 1, 6, 6, 5, + 7, 12, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 2, 6, 5, 0, 6, 6, 2, 0, 5, 1, 5, 6, 1, + 7, 12, 0, 1, 6, 5, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 2, 6, 3, 6, 4, 5, 1, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 6, 1, 5, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 4, 5, 6, 4, 5, 6, + 7, 12, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 1, 5, 3, 2, 5, 1, 0, 4, 1, 6, 3, 6, 1, + 7, 12, 0, 4, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 12, 0, 1, 2, 4, 0, 2, 2, 1, 4, 6, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 6, 1, 2, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 5, 4, 0, 4, 5, 3, 5, 1, 6, 3, 6, 4, 4, 2, 0, 3, 4, 3, + 7, 12, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 6, 1, 6, 5, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 6, 5, 0, 6, 1, 6, + 7, 12, 0, 3, 4, 0, 2, 4, 3, 2, 1, 3, 4, 1, 1, 0, 5, 1, 5, 2, 6, 1, 2, 6, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 5, 4, 4, 1, 1, 6, 5, 3, 1, 5, 6, 2, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 5, 4, 4, 1, 1, 5, 5, 3, 6, 1, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 5, 4, 4, 1, 1, 5, 5, 3, 6, 1, 0, 6, + 7, 12, 4, 3, 1, 2, 0, 1, 0, 3, 4, 0, 6, 4, 4, 2, 5, 4, 6, 2, 6, 3, 3, 2, 5, 1, + 7, 12, 2, 3, 4, 2, 0, 4, 6, 0, 6, 5, 4, 5, 1, 3, 5, 1, 0, 5, 6, 1, 3, 6, 5, 3, + 7, 12, 0, 3, 0, 5, 1, 2, 1, 5, 1, 6, 2, 4, 2, 6, 3, 4, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 3, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 4, 3, 5, 3, 6, 1, 3, 6, 6, 2, 0, 6, 4, 6, + 7, 12, 0, 1, 2, 4, 0, 2, 6, 1, 3, 1, 3, 2, 4, 1, 5, 1, 5, 2, 5, 3, 0, 3, 4, 6, + 7, 12, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 6, 3, 1, 6, + 7, 12, 4, 3, 1, 2, 5, 0, 0, 3, 4, 0, 4, 1, 4, 2, 5, 1, 6, 2, 6, 3, 3, 2, 6, 4, + 7, 12, 2, 3, 4, 2, 5, 4, 0, 5, 6, 0, 1, 6, 3, 1, 6, 3, 5, 3, 1, 5, 4, 0, 3, 0, + 7, 12, 0, 3, 0, 5, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 6, 5, 6, + 7, 12, 3, 2, 1, 2, 5, 0, 0, 3, 4, 0, 4, 1, 6, 4, 5, 1, 6, 2, 6, 3, 6, 1, 0, 6, + 7, 12, 0, 5, 0, 6, 1, 3, 1, 4, 1, 6, 2, 3, 2, 4, 2, 6, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 3, 0, 5, 1, 2, 1, 4, 1, 6, 2, 4, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 3, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 6, 5, 6, + 7, 12, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 5, 4, 4, 1, 3, 4, 5, 3, 2, 6, 6, 1, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 5, 4, 4, 1, 3, 4, 5, 3, 6, 1, 6, 5, + 7, 12, 0, 2, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 12, 0, 5, 0, 6, 1, 3, 1, 4, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 1, 2, 4, 5, 2, 0, 5, 4, 3, 6, 5, 6, 4, 3, 5, + 7, 12, 0, 2, 0, 6, 1, 2, 1, 4, 1, 5, 2, 3, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 2, 0, 6, 1, 3, 1, 4, 1, 5, 2, 4, 2, 5, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, + 7, 12, 0, 2, 0, 6, 1, 3, 1, 4, 1, 5, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 12, 0, 5, 0, 6, 1, 3, 1, 4, 1, 6, 2, 3, 2, 4, 2, 6, 3, 4, 3, 5, 4, 5, 5, 6, + 7, 12, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 12, 3, 0, 2, 3, 4, 2, 0, 4, 5, 1, 5, 2, 6, 1, 6, 0, 3, 6, 5, 3, 4, 5, 6, 4, + 7, 12, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 1, 0, 2, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 12, 3, 0, 2, 3, 4, 2, 0, 4, 5, 1, 5, 2, 6, 1, 6, 0, 3, 6, 5, 3, 6, 4, 1, 3, + 7, 12, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 3, 6, 4, 6, 5, 6, + 7, 12, 2, 3, 4, 2, 5, 2, 4, 1, 6, 0, 3, 0, 3, 1, 6, 3, 5, 6, 1, 5, 4, 0, 3, 4, + 7, 12, 2, 3, 4, 2, 4, 1, 2, 5, 6, 0, 6, 4, 3, 1, 6, 3, 0, 3, 1, 5, 4, 0, 5, 3, + 7, 12, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 3, 2, 6, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 12, 3, 0, 2, 3, 4, 2, 0, 4, 5, 1, 5, 2, 6, 1, 6, 0, 3, 6, 1, 3, 6, 4, 5, 4, + 7, 12, 6, 3, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 0, 6, 4, 1, 3, 4, 6, 5, 5, 1, 0, 4, + 7, 12, 0, 3, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 4, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, + 7, 12, 0, 3, 0, 5, 0, 6, 1, 2, 1, 4, 1, 6, 2, 3, 2, 5, 3, 4, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 3, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 4, 5, 4, 6, 5, 6, + 7, 12, 0, 4, 3, 0, 1, 3, 4, 1, 1, 0, 4, 5, 2, 4, 6, 2, 5, 6, 2, 5, 3, 2, 6, 3, + 7, 12, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 5, 2, 5, 4, 6, 4, 6, 3, 6, 2, 5, 3, + 7, 12, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 4, 5, 4, 6, + 7, 12, 3, 0, 4, 2, 3, 1, 4, 0, 5, 2, 5, 1, 4, 3, 5, 4, 3, 5, 6, 1, 0, 6, 2, 6, + 7, 12, 1, 0, 4, 1, 0, 4, 5, 0, 6, 5, 1, 6, 3, 4, 5, 3, 2, 3, 5, 2, 6, 3, 2, 6, + 7, 12, 0, 1, 2, 0, 2, 3, 3, 4, 0, 4, 0, 5, 6, 1, 4, 6, 6, 5, 2, 6, 3, 1, 5, 3, + 7, 12, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 6, 0, 6, 1, 6, 2, 6, 3, 6, 4, 6, 5, + 7, 12, 3, 6, 1, 2, 0, 6, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 2, 5, 4, 5, 6, 4, + 7, 13, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 6, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 4, 3, 0, 2, 6, 4, + 7, 13, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 0, 4, 1, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 6, 4, 5, 5, 6, + 7, 13, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 3, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 13, 0, 6, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 4, 5, 1, 6, + 7, 13, 0, 1, 1, 2, 2, 3, 4, 5, 0, 4, 1, 3, 4, 1, 2, 4, 0, 3, 5, 3, 4, 3, 0, 2, 5, 6, + 7, 13, 0, 5, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 6, 5, 6, + 7, 13, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 5, 6, 0, 5, 6, 0, 4, 6, 5, 4, 1, 5, 6, 1, 3, 6, 5, 3, 2, 5, 6, 2, 1, 0, 2, 1, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 6, + 7, 13, 3, 4, 0, 3, 4, 0, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 6, 0, 3, 6, 5, 3, 4, 5, + 7, 13, 3, 4, 0, 3, 4, 0, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 6, 0, 3, 6, 5, 3, 0, 5, + 7, 13, 3, 4, 0, 3, 4, 0, 1, 4, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 6, 4, 0, 6, 5, 0, 3, 5, + 7, 13, 0, 5, 0, 6, 1, 3, 1, 4, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 5, 4, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 5, 1, 6, 5, 1, 6, + 7, 13, 0, 4, 0, 6, 1, 3, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 5, 0, 6, 1, 4, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 13, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 5, 3, 6, 5, 4, 6, + 7, 13, 0, 5, 0, 6, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 6, 1, 6, 5, 5, 1, + 7, 13, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 4, 0, 5, 6, 0, 3, 6, 6, 4, + 7, 13, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 6, 1, 5, 1, 2, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 5, 6, + 7, 13, 1, 0, 2, 1, 4, 2, 1, 4, 3, 1, 0, 3, 2, 3, 5, 2, 1, 5, 0, 5, 6, 0, 1, 6, 2, 6, + 7, 13, 2, 5, 6, 2, 5, 6, 4, 5, 3, 4, 0, 3, 4, 0, 1, 4, 3, 1, 6, 3, 2, 1, 4, 2, 3, 2, + 7, 13, 0, 3, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 13, 2, 4, 3, 2, 1, 3, 4, 1, 0, 4, 3, 0, 6, 3, 1, 6, 5, 1, 4, 5, 6, 4, 3, 5, 1, 2, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 4, 2, 5, 3, 5, 3, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 5, 5, 6, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 3, 2, 5, 3, 6, 4, 5, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 4, 2, 5, 3, 4, 3, 5, + 7, 13, 0, 2, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 2, 5, 1, 2, 5, 1, 4, 5, 3, 4, 0, 3, 4, 0, 3, 2, 4, 2, 1, 3, 6, 3, 2, 6, 1, 6, + 7, 13, 0, 4, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 13, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 0, 4, 6, 1, 4, 6, + 7, 13, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, 5, 0, 6, 5, 1, 6, 4, 0, + 7, 13, 0, 1, 1, 2, 0, 2, 3, 0, 1, 3, 3, 2, 4, 0, 2, 5, 3, 4, 5, 3, 0, 5, 6, 4, 6, 1, + 7, 13, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, 6, 2, 5, 6, 0, 5, 1, 2, + 7, 13, 5, 4, 6, 2, 6, 4, 4, 3, 5, 0, 3, 1, 3, 2, 6, 3, 5, 6, 4, 0, 1, 4, 5, 1, 0, 3, + 7, 13, 0, 2, 0, 6, 1, 3, 1, 4, 1, 5, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 1, 5, 4, 1, 0, 4, 5, 0, 2, 5, 4, 2, 3, 4, 5, 3, 0, 1, 2, 0, 3, 2, 6, 5, 6, 4, + 7, 13, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 0, 4, 0, 6, 4, 6, + 7, 13, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 5, 0, 4, 5, 1, 5, 6, 1, 0, 6, 3, 6, + 7, 13, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 4, 1, 5, 3, 2, 5, 1, 0, 6, 3, 4, 6, 5, 1, + 7, 13, 5, 2, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 6, 2, 6, 3, 1, 2, 3, 1, 4, 0, + 7, 13, 1, 0, 2, 1, 0, 2, 0, 3, 3, 2, 6, 2, 1, 6, 5, 1, 6, 5, 4, 6, 5, 4, 0, 5, 4, 0, + 7, 13, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, + 7, 13, 0, 5, 0, 6, 1, 3, 1, 4, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 0, 1, 1, 2, 4, 1, 3, 1, 0, 4, 2, 3, 0, 3, 2, 0, 5, 0, 6, 5, 4, 6, 3, 5, 4, 2, + 7, 13, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, + 7, 13, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 0, 4, 2, 3, 6, 5, 0, 6, + 7, 13, 5, 2, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 6, 2, 6, 3, 2, 3, 5, 0, 4, 0, + 7, 13, 0, 1, 1, 2, 2, 3, 5, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 6, 3, 6, 4, 4, 2, 0, 3, + 7, 13, 0, 1, 0, 6, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, + 7, 13, 0, 1, 0, 6, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 6, 2, 0, 6, 5, 0, 2, 5, 5, 3, 4, 5, 6, 4, 3, 6, + 7, 13, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 6, 5, 0, 6, 0, 2, 2, 5, 5, 3, 4, 5, 6, 4, 3, 6, + 7, 13, 3, 4, 0, 3, 4, 0, 3, 6, 3, 1, 2, 3, 4, 2, 1, 0, 2, 1, 5, 2, 3, 5, 6, 2, 5, 6, + 7, 13, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 6, 5, 3, 6, 4, 6, + 7, 13, 1, 0, 2, 1, 6, 0, 1, 4, 3, 1, 0, 3, 2, 3, 1, 6, 1, 5, 2, 6, 4, 3, 5, 4, 6, 5, + 7, 13, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 5, 2, 6, 3, 5, 4, 6, + 7, 13, 0, 6, 1, 2, 2, 3, 0, 3, 4, 2, 5, 0, 6, 3, 4, 1, 3, 4, 6, 5, 1, 5, 3, 5, 1, 3, + 7, 13, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 6, 4, 4, 3, 5, 4, 5, 2, 6, 0, 3, 6, 3, 5, + 7, 13, 0, 1, 1, 2, 2, 3, 3, 6, 0, 4, 6, 5, 0, 6, 6, 4, 2, 5, 5, 3, 4, 5, 1, 5, 6, 1, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 4, 5, 4, 6, 5, 6, + 7, 13, 2, 3, 5, 2, 6, 5, 3, 6, 4, 3, 5, 4, 0, 5, 2, 0, 1, 2, 0, 1, 5, 1, 4, 2, 6, 4, + 7, 13, 2, 1, 0, 5, 6, 0, 4, 6, 5, 4, 1, 5, 6, 1, 3, 6, 5, 3, 2, 5, 6, 2, 1, 0, 3, 4, + 7, 13, 0, 1, 2, 0, 2, 3, 3, 4, 0, 4, 0, 5, 6, 1, 4, 6, 6, 5, 2, 6, 3, 1, 5, 3, 6, 0, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 3, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 2, 3, 0, 2, 3, 0, 4, 3, 4, 6, 5, 1, 4, 5, 3, 1, 5, 2, 6, 0, 6, 1, 2, 1, 4, 1, + 7, 13, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 3, 5, 4, 5, 2, 5, 1, 6, 5, 1, 6, 2, 6, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, + 7, 13, 3, 0, 2, 3, 4, 2, 0, 4, 5, 1, 5, 2, 6, 1, 6, 0, 3, 6, 1, 3, 6, 4, 5, 4, 5, 3, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 2, 1, 4, 1, 5, 2, 3, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 6, 4, 6, 5, 6, + 7, 13, 0, 2, 0, 5, 0, 6, 1, 2, 1, 4, 1, 6, 2, 3, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 3, 4, 5, 0, 3, 5, 0, 6, 6, 4, 2, 3, 4, 2, 1, 0, 2, 1, 1, 6, 5, 1, 2, 5, 6, 2, + 7, 13, 0, 2, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 13, 3, 0, 2, 3, 4, 2, 0, 4, 5, 3, 5, 2, 5, 4, 1, 3, 1, 0, 4, 1, 6, 0, 3, 6, 1, 6, + 7, 13, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 6, 0, 1, 6, 6, 3, 4, 6, 1, 2, 5, 0, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, + 7, 13, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, + 7, 13, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 5, 3, 5, 4, 6, 4, 6, 2, 6, 5, 5, 2, 3, 6, + 7, 13, 0, 1, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 13, 0, 1, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 13, 0, 1, 2, 0, 2, 3, 3, 4, 0, 4, 0, 5, 6, 1, 4, 6, 6, 5, 2, 6, 3, 1, 5, 3, 2, 1, + 7, 14, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 1, 3, 2, 0, 4, 0, 5, 3, + 7, 14, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 2, 3, 0, 4, 1, 6, + 7, 14, 0, 6, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, + 7, 14, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 3, 1, 2, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 3, 6, 5, 3, 4, 5, 6, 4, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 6, 2, 1, 6, 5, 1, 0, 5, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 5, 2, 3, 5, 6, 4, 0, 6, + 7, 14, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 2, 4, 5, 1, 0, 3, 1, 4, 0, 1, 0, 4, 6, 4, 0, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 3, 4, 3, 5, 4, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 6, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 3, 4, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 0, 1, 5, 1, 0, 4, 1, 4, 5, 3, 2, 5, 6, 4, 0, 6, + 7, 14, 1, 3, 2, 1, 0, 2, 5, 0, 4, 5, 3, 4, 5, 3, 2, 5, 1, 0, 4, 1, 6, 1, 5, 1, 2, 6, 0, 4, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 6, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 6, 0, 3, 4, 3, 1, 5, 4, 5, 0, 0, 3, 1, 5, 5, 3, 6, 4, 6, 1, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 5, 3, 6, 5, 4, 6, 5, 4, + 7, 14, 3, 1, 1, 4, 2, 3, 3, 4, 0, 4, 1, 5, 0, 1, 0, 2, 2, 5, 5, 3, 4, 5, 1, 2, 6, 2, 5, 6, + 7, 14, 0, 3, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 3, 0, 6, 1, 2, 1, 4, 1, 5, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 14, 0, 1, 0, 6, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 4, 6, 5, 2, 1, 5, 0, 5, 6, 3, + 7, 14, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 5, 0, 5, 1, 5, 2, 5, 3, 5, 4, 4, 2, 3, 0, 6, 1, 5, 6, + 7, 14, 0, 4, 0, 6, 1, 2, 1, 3, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 4, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 14, 0, 5, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 14, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 1, 2, 5, 2, 4, 0, 3, 1, 5, 0, 6, 5, 6, 4, 0, 1, + 7, 14, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 5, 2, 6, 0, 6, 1, 5, 0, 1, 2, 3, 1, 4, 0, + 7, 14, 5, 6, 0, 5, 6, 0, 4, 6, 5, 4, 1, 5, 6, 1, 3, 6, 5, 3, 2, 5, 6, 2, 1, 0, 2, 1, 3, 4, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 5, 1, 6, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 14, 0, 1, 2, 0, 2, 3, 3, 4, 0, 4, 0, 5, 6, 1, 4, 6, 6, 5, 2, 6, 3, 1, 5, 3, 6, 0, 3, 6, + 7, 14, 3, 1, 4, 2, 4, 5, 4, 0, 1, 4, 0, 3, 5, 0, 5, 2, 6, 1, 3, 6, 6, 0, 5, 6, 6, 2, 4, 6, + 7, 14, 0, 4, 3, 0, 2, 3, 4, 2, 1, 4, 3, 1, 1, 0, 2, 1, 5, 4, 1, 5, 6, 1, 3, 6, 0, 5, 6, 0, + 7, 14, 3, 4, 4, 2, 1, 5, 4, 0, 1, 4, 5, 3, 3, 0, 5, 2, 6, 4, 0, 6, 3, 6, 2, 6, 5, 6, 1, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 2, 3, 2, 6, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 4, 5, 3, 4, 3, 1, 5, 2, 1, 6, 5, 6, 6, 0, 5, 3, 6, 4, 0, 1, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 2, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 1, 4, 6, 0, 3, 1, 5, 2, 4, 5, 5, 6, 1, 5, 5, 3, 6, 4, 3, 0, + 7, 14, 3, 1, 4, 2, 0, 3, 4, 0, 1, 4, 5, 3, 5, 0, 5, 2, 6, 4, 1, 6, 6, 3, 0, 6, 6, 5, 2, 6, + 7, 14, 0, 1, 4, 2, 3, 0, 4, 0, 4, 5, 5, 3, 1, 3, 5, 2, 6, 4, 2, 6, 6, 5, 3, 6, 6, 1, 0, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 6, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 14, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 5, 1, 6, 2, 3, 2, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 1, 4, 6, 1, 3, 1, 5, 2, 5, 0, 5, 6, 4, 5, 5, 3, 6, 0, 3, 0, + 7, 14, 2, 3, 4, 2, 3, 0, 4, 0, 4, 5, 3, 4, 3, 1, 5, 2, 0, 1, 5, 6, 6, 0, 5, 3, 6, 4, 1, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 14, 0, 3, 0, 4, 0, 6, 1, 2, 1, 4, 1, 5, 2, 3, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 14, 0, 1, 0, 5, 0, 6, 1, 4, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 5, 6, + 7, 14, 0, 1, 0, 4, 0, 6, 1, 3, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 1, 4, 4, 5, 3, 1, 5, 2, 5, 0, 6, 1, 0, 6, 5, 3, 6, 4, 3, 0, + 7, 14, 0, 1, 0, 5, 0, 6, 1, 3, 1, 4, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 2, 3, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 5, 6, + 7, 14, 2, 3, 4, 2, 6, 3, 4, 0, 4, 5, 3, 5, 3, 1, 5, 2, 3, 0, 1, 4, 6, 0, 1, 6, 6, 4, 0, 1, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 4, 6, + 7, 14, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 6, 3, 4, 3, 5, 4, 6, 5, 6, + 7, 14, 0, 3, 0, 4, 0, 5, 1, 2, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 6, 5, 6, + 7, 14, 0, 3, 0, 4, 0, 5, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 6, 4, 6, 5, 6, + 7, 14, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 0, 6, 0, 2, 5, 0, 3, 5, 1, 3, 6, 1, 4, 6, 2, 4, + 7, 14, 0, 3, 0, 4, 0, 5, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 6, 4, 5, + 7, 15, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, + 7, 15, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 1, 3, 2, 0, 4, 0, 5, 3, 1, 6, + 7, 15, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 2, 4, 5, 2, 1, 5, 1, 4, 1, 3, 2, 0, 4, 0, 5, 3, 0, 6, + 7, 15, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 3, 4, 3, 5, 3, 6, 5, 6, + 7, 15, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 5, 1, 6, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 15, 3, 4, 4, 5, 0, 3, 0, 4, 0, 5, 0, 6, 3, 6, 4, 6, 5, 6, 1, 5, 3, 5, 2, 3, 2, 4, 1, 6, 0, 1, + 7, 15, 3, 4, 4, 5, 0, 3, 0, 4, 0, 5, 0, 6, 3, 6, 1, 3, 1, 4, 1, 5, 3, 5, 2, 3, 2, 4, 6, 1, 4, 6, + 7, 15, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 1, 4, 2, 4, 3, 5, 1, 0, 5, 5, 2, 3, 5, 4, 5, 6, 1, 5, 6, + 7, 15, 4, 3, 4, 5, 5, 3, 0, 1, 0, 5, 0, 3, 2, 4, 1, 5, 1, 3, 6, 5, 3, 6, 6, 0, 1, 6, 6, 2, 4, 6, + 7, 15, 3, 4, 4, 5, 5, 6, 0, 4, 0, 5, 0, 6, 3, 6, 1, 3, 4, 6, 1, 5, 3, 5, 2, 3, 2, 4, 1, 6, 0, 1, + 7, 15, 0, 2, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 15, 6, 1, 4, 5, 0, 3, 0, 4, 0, 5, 0, 6, 3, 6, 1, 3, 1, 4, 1, 5, 3, 5, 2, 3, 2, 4, 5, 6, 4, 6, + 7, 15, 3, 4, 4, 5, 0, 3, 0, 4, 0, 5, 4, 6, 3, 6, 1, 3, 1, 4, 1, 5, 3, 5, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 3, 4, 4, 5, 0, 3, 0, 4, 6, 0, 4, 6, 3, 6, 1, 3, 1, 4, 1, 5, 3, 5, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 0, 1, 1, 2, 0, 2, 3, 0, 1, 3, 2, 3, 5, 1, 3, 5, 4, 3, 2, 4, 6, 2, 3, 6, 6, 1, 0, 5, 4, 0, + 7, 15, 0, 1, 2, 0, 3, 2, 4, 3, 1, 4, 3, 1, 4, 2, 0, 4, 3, 0, 6, 3, 4, 6, 5, 4, 3, 5, 6, 2, 5, 1, + 7, 15, 0, 1, 0, 2, 0, 3, 0, 4, 1, 2, 1, 3, 1, 4, 2, 3, 2, 4, 3, 4, 5, 3, 4, 5, 6, 4, 5, 6, 6, 3, + 7, 15, 0, 1, 1, 2, 2, 3, 0, 3, 4, 0, 4, 3, 4, 2, 6, 1, 2, 6, 5, 2, 4, 5, 6, 4, 0, 6, 6, 5, 3, 6, + 7, 15, 0, 1, 5, 3, 1, 3, 0, 4, 3, 0, 4, 3, 2, 4, 5, 2, 4, 5, 6, 4, 2, 6, 6, 5, 3, 6, 6, 1, 0, 6, + 7, 15, 5, 2, 4, 5, 3, 1, 0, 4, 0, 5, 0, 3, 2, 4, 1, 5, 1, 4, 6, 3, 1, 6, 6, 0, 5, 6, 6, 2, 4, 6, + 7, 15, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 0, 5, 0, 3, 2, 0, 3, 1, 6, 4, 5, 6, 6, 3, 0, 6, 6, 2, 1, 6, + 7, 15, 5, 2, 3, 0, 5, 3, 0, 4, 0, 5, 4, 3, 2, 4, 1, 5, 1, 4, 6, 4, 2, 6, 6, 0, 5, 6, 6, 3, 1, 6, + 7, 15, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 15, 6, 1, 4, 5, 0, 3, 0, 4, 0, 5, 4, 6, 3, 6, 1, 3, 1, 4, 0, 6, 3, 5, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 3, 4, 0, 1, 0, 3, 0, 4, 0, 5, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 1, 6, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 6, + 7, 15, 5, 2, 4, 5, 5, 3, 0, 4, 0, 1, 1, 3, 2, 4, 3, 0, 1, 4, 6, 4, 1, 6, 6, 0, 3, 6, 6, 2, 5, 6, + 7, 15, 5, 0, 4, 3, 5, 3, 5, 2, 0, 1, 1, 3, 2, 4, 3, 0, 1, 4, 6, 2, 5, 6, 6, 4, 3, 6, 6, 0, 1, 6, + 7, 15, 3, 4, 4, 5, 0, 3, 4, 6, 0, 1, 1, 6, 3, 6, 1, 3, 1, 4, 6, 0, 0, 5, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 0, 2, 0, 3, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 15, 0, 4, 0, 5, 0, 6, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 15, 3, 4, 5, 0, 0, 3, 0, 4, 4, 6, 1, 6, 3, 6, 1, 3, 1, 4, 6, 0, 1, 5, 2, 3, 2, 4, 5, 6, 5, 2, + 7, 15, 6, 4, 5, 2, 0, 3, 0, 4, 2, 4, 1, 6, 3, 6, 1, 3, 1, 4, 6, 0, 3, 5, 2, 3, 0, 1, 5, 6, 4, 5, + 7, 15, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 5, 1, 6, 2, 3, 2, 4, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 15, 0, 1, 0, 2, 0, 3, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 15, 2, 3, 0, 2, 3, 0, 4, 3, 1, 4, 5, 1, 4, 5, 1, 0, 5, 2, 6, 2, 5, 6, 6, 1, 0, 6, 6, 4, 3, 6, + 7, 15, 3, 0, 3, 5, 3, 4, 2, 0, 2, 5, 2, 4, 1, 4, 1, 5, 1, 0, 6, 0, 1, 6, 6, 5, 3, 6, 6, 4, 2, 6, + 7, 15, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 15, 3, 4, 6, 2, 0, 3, 0, 4, 5, 0, 1, 6, 3, 6, 1, 3, 1, 4, 6, 0, 4, 5, 2, 3, 2, 4, 5, 1, 5, 2, + 7, 15, 3, 4, 6, 2, 0, 3, 0, 4, 5, 0, 5, 6, 3, 6, 1, 3, 1, 4, 0, 1, 4, 6, 2, 3, 2, 4, 5, 1, 5, 2, + 7, 15, 0, 1, 1, 2, 2, 3, 3, 4, 0, 4, 6, 2, 1, 6, 6, 0, 4, 6, 5, 4, 0, 5, 3, 5, 6, 3, 5, 2, 1, 5, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, 2, 6, + 7, 16, 3, 0, 4, 1, 4, 3, 1, 3, 4, 0, 2, 5, 6, 2, 5, 6, 1, 5, 4, 5, 3, 5, 0, 5, 0, 6, 3, 6, 4, 6, 6, 1, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 6, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 16, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 16, 3, 4, 5, 1, 0, 3, 0, 4, 5, 0, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 3, 5, 2, 3, 2, 4, 5, 6, 4, 5, 2, 5, + 7, 16, 2, 4, 3, 1, 3, 0, 4, 3, 4, 0, 5, 2, 4, 5, 5, 0, 3, 5, 5, 1, 6, 5, 1, 6, 3, 6, 6, 0, 4, 6, 6, 2, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 16, 2, 4, 4, 1, 3, 0, 3, 1, 4, 0, 5, 2, 4, 5, 5, 0, 3, 5, 6, 5, 1, 5, 6, 1, 3, 6, 4, 6, 6, 2, 6, 0, + 7, 16, 0, 1, 0, 3, 0, 5, 0, 6, 1, 3, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 16, 2, 5, 0, 1, 4, 5, 1, 3, 5, 0, 4, 3, 5, 3, 2, 4, 1, 4, 3, 0, 6, 3, 2, 6, 6, 4, 5, 6, 6, 1, 0, 6, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 3, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 4, 5, 4, 6, 5, 6, + 7, 16, 2, 5, 5, 1, 3, 1, 0, 4, 5, 0, 4, 3, 5, 3, 2, 4, 1, 4, 3, 0, 6, 2, 4, 6, 5, 6, 6, 1, 0, 6, 3, 6, + 7, 16, 1, 6, 0, 1, 0, 3, 0, 4, 5, 0, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 3, 5, 2, 3, 2, 4, 5, 6, 4, 5, 2, 5, + 7, 16, 3, 4, 5, 1, 0, 3, 0, 4, 5, 0, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 1, 6, 2, 3, 2, 4, 5, 6, 0, 1, 2, 5, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 3, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 5, 6, + 7, 16, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 5, 6, + 7, 16, 2, 5, 5, 1, 3, 5, 0, 4, 0, 1, 4, 3, 3, 2, 2, 4, 1, 4, 0, 5, 6, 4, 2, 6, 6, 3, 5, 6, 6, 1, 0, 6, + 7, 16, 5, 6, 5, 1, 0, 3, 0, 4, 0, 1, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 3, 5, 2, 3, 2, 4, 6, 2, 4, 5, 2, 5, + 7, 16, 3, 4, 5, 1, 0, 3, 0, 4, 0, 1, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 5, 0, 2, 3, 2, 4, 6, 2, 6, 5, 2, 5, + 7, 16, 5, 0, 5, 1, 0, 3, 0, 4, 6, 1, 4, 6, 3, 6, 1, 3, 1, 4, 6, 0, 3, 5, 2, 3, 2, 4, 6, 2, 4, 5, 2, 5, + 7, 17, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, 6, 2, 1, 6, + 7, 17, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 4, 5, 4, 6, + 7, 17, 4, 0, 4, 3, 0, 1, 3, 0, 2, 4, 3, 1, 5, 3, 4, 5, 5, 2, 6, 5, 5, 0, 1, 5, 6, 1, 0, 6, 6, 4, 2, 6, 3, 6, + 7, 17, 0, 1, 5, 1, 5, 3, 0, 4, 5, 0, 4, 3, 3, 1, 2, 5, 1, 4, 3, 0, 2, 4, 6, 2, 5, 6, 6, 3, 1, 6, 6, 0, 4, 6, + 7, 17, 3, 4, 5, 1, 0, 3, 0, 4, 4, 5, 4, 6, 3, 6, 1, 3, 1, 4, 0, 1, 3, 5, 2, 3, 2, 4, 2, 5, 5, 0, 5, 6, 6, 2, + 7, 17, 3, 2, 4, 1, 0, 1, 3, 0, 2, 4, 4, 3, 5, 1, 4, 5, 5, 2, 0, 5, 5, 3, 6, 5, 2, 6, 6, 3, 0, 6, 1, 6, 4, 6, + 7, 17, 3, 2, 4, 1, 4, 0, 3, 0, 2, 4, 3, 1, 5, 2, 4, 5, 5, 0, 3, 5, 5, 1, 6, 5, 2, 6, 6, 0, 3, 6, 6, 4, 1, 6, + 7, 17, 3, 2, 5, 1, 5, 0, 0, 4, 0, 1, 4, 3, 5, 3, 2, 5, 1, 4, 3, 0, 2, 4, 6, 4, 5, 6, 6, 2, 3, 6, 6, 0, 1, 6, + 7, 17, 3, 2, 5, 1, 5, 0, 0, 4, 4, 5, 0, 1, 3, 1, 2, 5, 1, 4, 3, 0, 2, 4, 6, 0, 3, 6, 6, 1, 5, 6, 6, 2, 4, 6, + 7, 17, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 5, 3, 6, 4, 5, 4, 6, + 7, 18, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 3, 4, 3, 5, 4, 5, 6, 1, 0, 6, 5, 6, + 7, 18, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, + 7, 18, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 4, 1, 5, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 18, 0, 1, 0, 2, 0, 3, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 18, 4, 0, 4, 5, 3, 0, 3, 5, 2, 0, 2, 5, 1, 3, 1, 4, 1, 5, 1, 0, 2, 3, 2, 4, 6, 0, 5, 6, 6, 1, 2, 6, 6, 4, 3, 6, + 7, 19, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 19, 0, 1, 0, 2, 0, 3, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 20, 0, 1, 0, 2, 0, 3, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, + 7, 21, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 2, 3, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 4, 5, 4, 6, 5, 6, +}; + +const long int igraph_i_atlas_edges_pos[] = {0, 2, 4, 6, 10, 12, 16, 22, 30, 32, 36, 42, 48, 56, 64, 72, 82, 92, 104, 118, 120, 124, 130, 136, 144, 152, 160, 168, 178, 188, 198, 208, 218, 228, 240, 252, 264, 276, 288, 300, 314, 328, 342, 356, 370, 384, 400, 416, 432, 448, 466, 484, 504, 526, 528, 532, 538, 544, 552, 560, 568, 576, 584, 594, 604, 614, 624, 634, 644, 654, 664, 674, 686, 698, 710, 722, 734, 746, 758, 770, 782, 794, 806, 818, 830, 842, 854, 868, 882, 896, 910, 924, 938, 952, 966, 980, 994, 1008, 1022, 1036, 1050, 1064, 1078, 1092, 1106, 1120, 1134, 1148, 1164, 1180, 1196, 1212, 1228, 1244, 1260, 1276, 1292, 1308, 1324, 1340, 1356, 1372, 1388, 1404, 1420, 1436, 1452, 1468, 1484, 1500, 1516, 1532, 1550, 1568, 1586, 1604, 1622, 1640, 1658, 1676, 1694, 1712, 1730, 1748, 1766, 1784, 1802, 1820, 1838, 1856, 1874, 1892, 1910, 1928, 1946, 1964, 1984, 2004, 2024, 2044, 2064, 2084, 2104, 2124, 2144, 2164, 2184, 2204, 2224, 2244, 2264, 2284, 2304, 2324, 2344, 2364, 2384, 2406, 2428, 2450, 2472, 2494, 2516, 2538, 2560, 2582, 2604, 2626, 2648, 2670, 2692, 2714, 2738, 2762, 2786, 2810, 2834, 2858, 2882, 2906, 2930, 2956, 2982, 3008, 3034, 3060, 3088, 3116, 3146, 3178, 3180, 3184, 3190, 3196, 3204, 3212, 3220, 3228, 3236, 3246, 3256, 3266, 3276, 3286, 3296, 3306, 3316, 3326, 3336, 3348, 3360, 3372, 3384, 3396, 3408, 3420, 3432, 3444, 3456, 3468, 3480, 3492, 3504, 3516, 3528, 3540, 3552, 3564, 3576, 3588, 3602, 3616, 3630, 3644, 3658, 3672, 3686, 3700, 3714, 3728, 3742, 3756, 3770, 3784, 3798, 3812, 3826, 3840, 3854, 3868, 3882, 3896, 3910, 3924, 3938, 3952, 3966, 3980, 3994, 4008, 4022, 4036, 4050, 4064, 4078, 4092, 4106, 4120, 4134, 4148, 4162, 4178, 4194, 4210, 4226, 4242, 4258, 4274, 4290, 4306, 4322, 4338, 4354, 4370, 4386, 4402, 4418, 4434, 4450, 4466, 4482, 4498, 4514, 4530, 4546, 4562, 4578, 4594, 4610, 4626, 4642, 4658, 4674, 4690, 4706, 4722, 4738, 4754, 4770, 4786, 4802, 4818, 4834, 4850, 4866, 4882, 4898, 4914, 4930, 4946, 4962, 4978, 4994, 5010, 5026, 5042, 5058, 5074, 5090, 5106, 5122, 5138, 5154, 5170, 5186, 5202, 5220, 5238, 5256, 5274, 5292, 5310, 5328, 5346, 5364, 5382, 5400, 5418, 5436, 5454, 5472, 5490, 5508, 5526, 5544, 5562, 5580, 5598, 5616, 5634, 5652, 5670, 5688, 5706, 5724, 5742, 5760, 5778, 5796, 5814, 5832, 5850, 5868, 5886, 5904, 5922, 5940, 5958, 5976, 5994, 6012, 6030, 6048, 6066, 6084, 6102, 6120, 6138, 6156, 6174, 6192, 6210, 6228, 6246, 6264, 6282, 6300, 6318, 6336, 6354, 6372, 6390, 6408, 6426, 6444, 6462, 6480, 6498, 6516, 6534, 6552, 6570, 6588, 6606, 6624, 6642, 6660, 6678, 6696, 6714, 6732, 6750, 6768, 6786, 6804, 6822, 6840, 6858, 6876, 6894, 6912, 6930, 6948, 6968, 6988, 7008, 7028, 7048, 7068, 7088, 7108, 7128, 7148, 7168, 7188, 7208, 7228, 7248, 7268, 7288, 7308, 7328, 7348, 7368, 7388, 7408, 7428, 7448, 7468, 7488, 7508, 7528, 7548, 7568, 7588, 7608, 7628, 7648, 7668, 7688, 7708, 7728, 7748, 7768, 7788, 7808, 7828, 7848, 7868, 7888, 7908, 7928, 7948, 7968, 7988, 8008, 8028, 8048, 8068, 8088, 8108, 8128, 8148, 8168, 8188, 8208, 8228, 8248, 8268, 8288, 8308, 8328, 8348, 8368, 8388, 8408, 8428, 8448, 8468, 8488, 8508, 8528, 8548, 8568, 8588, 8608, 8628, 8648, 8668, 8688, 8708, 8728, 8748, 8768, 8788, 8808, 8828, 8848, 8868, 8888, 8908, 8928, 8948, 8968, 8988, 9008, 9028, 9048, 9068, 9088, 9108, 9128, 9148, 9168, 9188, 9208, 9228, 9248, 9268, 9288, 9308, 9328, 9348, 9368, 9388, 9408, 9428, 9448, 9468, 9488, 9508, 9528, 9548, 9568, 9590, 9612, 9634, 9656, 9678, 9700, 9722, 9744, 9766, 9788, 9810, 9832, 9854, 9876, 9898, 9920, 9942, 9964, 9986, 10008, 10030, 10052, 10074, 10096, 10118, 10140, 10162, 10184, 10206, 10228, 10250, 10272, 10294, 10316, 10338, 10360, 10382, 10404, 10426, 10448, 10470, 10492, 10514, 10536, 10558, 10580, 10602, 10624, 10646, 10668, 10690, 10712, 10734, 10756, 10778, 10800, 10822, 10844, 10866, 10888, 10910, 10932, 10954, 10976, 10998, 11020, 11042, 11064, 11086, 11108, 11130, 11152, 11174, 11196, 11218, 11240, 11262, 11284, 11306, 11328, 11350, 11372, 11394, 11416, 11438, 11460, 11482, 11504, 11526, 11548, 11570, 11592, 11614, 11636, 11658, 11680, 11702, 11724, 11746, 11768, 11790, 11812, 11834, 11856, 11878, 11900, 11922, 11944, 11966, 11988, 12010, 12032, 12054, 12076, 12098, 12120, 12142, 12164, 12186, 12208, 12230, 12252, 12274, 12296, 12318, 12340, 12362, 12384, 12406, 12428, 12450, 12472, 12494, 12516, 12538, 12560, 12582, 12604, 12626, 12648, 12670, 12692, 12714, 12736, 12758, 12780, 12802, 12824, 12848, 12872, 12896, 12920, 12944, 12968, 12992, 13016, 13040, 13064, 13088, 13112, 13136, 13160, 13184, 13208, 13232, 13256, 13280, 13304, 13328, 13352, 13376, 13400, 13424, 13448, 13472, 13496, 13520, 13544, 13568, 13592, 13616, 13640, 13664, 13688, 13712, 13736, 13760, 13784, 13808, 13832, 13856, 13880, 13904, 13928, 13952, 13976, 14000, 14024, 14048, 14072, 14096, 14120, 14144, 14168, 14192, 14216, 14240, 14264, 14288, 14312, 14336, 14360, 14384, 14408, 14432, 14456, 14480, 14504, 14528, 14552, 14576, 14600, 14624, 14648, 14672, 14696, 14720, 14744, 14768, 14792, 14816, 14840, 14864, 14888, 14912, 14936, 14960, 14984, 15008, 15032, 15056, 15080, 15104, 15128, 15152, 15176, 15200, 15224, 15248, 15272, 15296, 15320, 15344, 15368, 15392, 15416, 15440, 15464, 15488, 15512, 15536, 15560, 15584, 15608, 15632, 15656, 15680, 15704, 15728, 15752, 15776, 15800, 15824, 15848, 15872, 15896, 15920, 15944, 15968, 15992, 16016, 16040, 16064, 16088, 16112, 16136, 16160, 16184, 16208, 16232, 16256, 16280, 16304, 16328, 16352, 16376, 16402, 16428, 16454, 16480, 16506, 16532, 16558, 16584, 16610, 16636, 16662, 16688, 16714, 16740, 16766, 16792, 16818, 16844, 16870, 16896, 16922, 16948, 16974, 17000, 17026, 17052, 17078, 17104, 17130, 17156, 17182, 17208, 17234, 17260, 17286, 17312, 17338, 17364, 17390, 17416, 17442, 17468, 17494, 17520, 17546, 17572, 17598, 17624, 17650, 17676, 17702, 17728, 17754, 17780, 17806, 17832, 17858, 17884, 17910, 17936, 17962, 17988, 18014, 18040, 18066, 18092, 18118, 18144, 18170, 18196, 18222, 18248, 18274, 18300, 18326, 18352, 18378, 18404, 18430, 18456, 18482, 18508, 18534, 18560, 18586, 18612, 18638, 18664, 18690, 18716, 18742, 18768, 18794, 18820, 18846, 18872, 18898, 18924, 18950, 18976, 19002, 19028, 19054, 19080, 19106, 19132, 19158, 19184, 19210, 19236, 19262, 19288, 19314, 19340, 19366, 19392, 19418, 19444, 19470, 19496, 19522, 19548, 19574, 19600, 19626, 19652, 19678, 19704, 19730, 19756, 19782, 19810, 19838, 19866, 19894, 19922, 19950, 19978, 20006, 20034, 20062, 20090, 20118, 20146, 20174, 20202, 20230, 20258, 20286, 20314, 20342, 20370, 20398, 20426, 20454, 20482, 20510, 20538, 20566, 20594, 20622, 20650, 20678, 20706, 20734, 20762, 20790, 20818, 20846, 20874, 20902, 20930, 20958, 20986, 21014, 21042, 21070, 21098, 21126, 21154, 21182, 21210, 21238, 21266, 21294, 21322, 21350, 21378, 21406, 21434, 21462, 21490, 21518, 21546, 21574, 21602, 21630, 21658, 21686, 21714, 21742, 21770, 21798, 21826, 21854, 21882, 21910, 21938, 21966, 21994, 22022, 22050, 22078, 22106, 22134, 22162, 22190, 22218, 22246, 22274, 22302, 22330, 22358, 22386, 22414, 22442, 22470, 22498, 22528, 22558, 22588, 22618, 22648, 22678, 22708, 22738, 22768, 22798, 22828, 22858, 22888, 22918, 22948, 22978, 23008, 23038, 23068, 23098, 23128, 23158, 23188, 23218, 23248, 23278, 23308, 23338, 23368, 23398, 23428, 23458, 23488, 23518, 23548, 23578, 23608, 23638, 23668, 23698, 23728, 23758, 23788, 23818, 23848, 23878, 23908, 23938, 23968, 23998, 24028, 24058, 24088, 24118, 24148, 24178, 24208, 24238, 24268, 24298, 24328, 24358, 24388, 24418, 24448, 24480, 24512, 24544, 24576, 24608, 24640, 24672, 24704, 24736, 24768, 24800, 24832, 24864, 24896, 24928, 24960, 24992, 25024, 25056, 25088, 25120, 25152, 25184, 25216, 25248, 25280, 25312, 25344, 25376, 25408, 25440, 25472, 25504, 25536, 25568, 25600, 25632, 25664, 25696, 25728, 25760, 25794, 25828, 25862, 25896, 25930, 25964, 25998, 26032, 26066, 26100, 26134, 26168, 26202, 26236, 26270, 26304, 26338, 26372, 26406, 26440, 26474, 26510, 26546, 26582, 26618, 26654, 26690, 26726, 26762, 26798, 26834, 26872, 26910, 26948, 26986, 27024, 27064, 27104, 27146}; + +__END_DECLS diff --git a/src/atlas.c b/src/atlas.c new file mode 100644 index 0000000..ddb6610 --- /dev/null +++ b/src/atlas.c @@ -0,0 +1,82 @@ +/* -*- mode: C -*- */ +/* + IGraph R package. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_constructors.h" +#include "atlas-edges.h" +#include "config.h" + +/** + * \function igraph_atlas + * \brief Create a small graph from the \quote Graph Atlas \endquote. + * + * + * The number of the graph is given as a parameter. + * The graphs are listed: \olist + * \oli in increasing order of number of nodes; + * \oli for a fixed number of nodes, in increasing order of the + * number of edges; + * \oli for fixed numbers of nodes and edges, in increasing + * order of the degree sequence, for example 111223 < 112222; + * \oli for fixed degree sequence, in increasing number of + * automorphisms. + * \endolist + * + * + * The data was converted from the NetworkX software package, + * see http://networkx.github.io . + * + * + * See \emb An Atlas of Graphs \eme by Ronald C. Read and Robin J. Wilson, + * Oxford University Press, 1998. + * + * \param graph Pointer to an uninitialized graph object. + * \param number The number of the graph to generate. + * + * Added in version 0.2. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number of + * edges. + * + * \example examples/simple/igraph_atlas.c + */ +int igraph_atlas(igraph_t *graph, int number) { + + igraph_integer_t pos, n, e; + igraph_vector_t v = IGRAPH_VECTOR_NULL; + + if (number < 0 || + number >= (int) (sizeof(igraph_i_atlas_edges_pos) / sizeof(long int))) { + IGRAPH_ERROR("No such graph in atlas", IGRAPH_EINVAL); + } + + pos = (igraph_integer_t) igraph_i_atlas_edges_pos[number]; + n = (igraph_integer_t) igraph_i_atlas_edges[pos]; + e = (igraph_integer_t) igraph_i_atlas_edges[pos + 1]; + + IGRAPH_CHECK(igraph_create(graph, + igraph_vector_view(&v, igraph_i_atlas_edges + pos + 2, + e * 2), + n, IGRAPH_UNDIRECTED)); + + return 0; +} diff --git a/src/attributes.c b/src/attributes.c new file mode 100644 index 0000000..0558870 --- /dev/null +++ b/src/attributes.c @@ -0,0 +1,442 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_attributes.h" +#include "igraph_memory.h" +#include "config.h" + +#include +#include + +/* Should you ever want to have a thread-local attribute handler table, prepend + * IGRAPH_THREAD_LOCAL to the following declaration */ +igraph_attribute_table_t *igraph_i_attribute_table = 0; + +int igraph_i_attribute_init(igraph_t *graph, void *attr) { + graph->attr = 0; + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->init(graph, attr); + } else { + return 0; + } +} + +void igraph_i_attribute_destroy(igraph_t *graph) { + if (igraph_i_attribute_table) { + igraph_i_attribute_table->destroy(graph); + } +} + +int igraph_i_attribute_copy(igraph_t *to, const igraph_t *from, igraph_bool_t ga, + igraph_bool_t va, igraph_bool_t ea) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->copy(to, from, ga, va, ea); + } else { + return 0; + } +} + +int igraph_i_attribute_add_vertices(igraph_t *graph, long int nv, void *attr) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->add_vertices(graph, nv, attr); + } else { + return 0; + } +} + +int igraph_i_attribute_permute_vertices(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_t *idx) { + + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->permute_vertices(graph, newgraph, idx); + } else { + return 0; + } +} + +int igraph_i_attribute_combine_vertices(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_ptr_t *merges, + const igraph_attribute_combination_t *comb) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->combine_vertices(graph, newgraph, + merges, + comb); + } else { + return 0; + } +} + +int igraph_i_attribute_add_edges(igraph_t *graph, + const igraph_vector_t *edges, void *attr) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->add_edges(graph, edges, attr); + } else { + return 0; + } +} + +int igraph_i_attribute_permute_edges(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_t *idx) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->permute_edges(graph, newgraph, idx); + } else { + return 0; + } +} + +int igraph_i_attribute_combine_edges(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_ptr_t *merges, + const igraph_attribute_combination_t *comb) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->combine_edges(graph, newgraph, + merges, + comb); + } else { + return 0; + } +} + +int igraph_i_attribute_get_info(const igraph_t *graph, + igraph_strvector_t *gnames, + igraph_vector_t *gtypes, + igraph_strvector_t *vnames, + igraph_vector_t *vtypes, + igraph_strvector_t *enames, + igraph_vector_t *etypes) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_info(graph, gnames, gtypes, + vnames, vtypes, + enames, etypes); + } else { + return 0; + } +} + +igraph_bool_t igraph_i_attribute_has_attr(const igraph_t *graph, + igraph_attribute_elemtype_t type, + const char *name) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->has_attr(graph, type, name); + } else { + return 0; + } +} + +int igraph_i_attribute_gettype(const igraph_t *graph, + igraph_attribute_type_t *type, + igraph_attribute_elemtype_t elemtype, + const char *name) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->gettype(graph, type, elemtype, name); + } else { + return 0; + } + +} + +int igraph_i_attribute_get_numeric_graph_attr(const igraph_t *graph, + const char *name, + igraph_vector_t *value) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_numeric_graph_attr(graph, name, value); + } else { + return 0; + } +} + +int igraph_i_attribute_get_numeric_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_vector_t *value) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_numeric_vertex_attr(graph, name, vs, value); + } else { + return 0; + } +} + +int igraph_i_attribute_get_numeric_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_vector_t *value) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_numeric_edge_attr(graph, name, es, value); + } else { + return 0; + } +} + +int igraph_i_attribute_get_string_graph_attr(const igraph_t *graph, + const char *name, + igraph_strvector_t *value) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_string_graph_attr(graph, name, value); + } else { + return 0; + } +} + +int igraph_i_attribute_get_string_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_strvector_t *value) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_string_vertex_attr(graph, name, vs, value); + } else { + return 0; + } +} + +int igraph_i_attribute_get_string_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_strvector_t *value) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_string_edge_attr(graph, name, es, value); + } else { + return 0; + } +} + +int igraph_i_attribute_get_bool_graph_attr(const igraph_t *graph, + const char *name, + igraph_vector_bool_t *value) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_bool_graph_attr(graph, name, value); + } else { + return 0; + } +} + +int igraph_i_attribute_get_bool_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_vector_bool_t *value) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_bool_vertex_attr(graph, name, vs, value); + } else { + return 0; + } +} + +int igraph_i_attribute_get_bool_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_vector_bool_t *value) { + if (igraph_i_attribute_table) { + return igraph_i_attribute_table->get_bool_edge_attr(graph, name, es, value); + } else { + return 0; + } +} + +/** + * \function igraph_i_set_attribute_table + * \brief Attach an attribute table. + * + * This function attaches attribute handling code to the igraph library. + * Note that the attribute handler table is \em not thread-local even if + * igraph is compiled in thread-local mode. In the vast majority of cases, + * this is not a significant restriction. + * + * \param table Pointer to an \ref igraph_attribute_table_t object + * containing the functions for attribute manipulation. Supply \c + * NULL here if you don't want attributes. + * \return Pointer to the old attribute handling table. + * + * Time complexity: O(1). + */ + +igraph_attribute_table_t * +igraph_i_set_attribute_table(const igraph_attribute_table_t * table) { + igraph_attribute_table_t *old = igraph_i_attribute_table; + igraph_i_attribute_table = (igraph_attribute_table_t*) table; + return old; +} + +igraph_bool_t igraph_has_attribute_table() { + return igraph_i_attribute_table != 0; +} + +int igraph_attribute_combination_init(igraph_attribute_combination_t *comb) { + IGRAPH_CHECK(igraph_vector_ptr_init(&comb->list, 0)); + return 0; +} + +void igraph_attribute_combination_destroy(igraph_attribute_combination_t *comb) { + long int i, n = igraph_vector_ptr_size(&comb->list); + for (i = 0; i < n; i++) { + igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[i]; + if (rec->name) { + igraph_Free(rec->name); + } + igraph_Free(rec); + } + igraph_vector_ptr_destroy(&comb->list); +} + +int igraph_attribute_combination_add(igraph_attribute_combination_t *comb, + const char *name, + igraph_attribute_combination_type_t type, + igraph_function_pointer_t func) { + long int i, n = igraph_vector_ptr_size(&comb->list); + + /* Search, in case it is already there */ + for (i = 0; i < n; i++) { + igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i]; + const char *n = r->name; + if ( (!name && !n) || + (name && n && !strcmp(n, name)) ) { + r->type = type; + r->func = func; + break; + } + } + + if (i == n) { + /* This is a new attribute name */ + igraph_attribute_combination_record_t *rec = + igraph_Calloc(1, igraph_attribute_combination_record_t); + + if (!rec) { + IGRAPH_ERROR("Cannot create attribute combination data", + IGRAPH_ENOMEM); + } + if (!name) { + rec->name = 0; + } else { + rec->name = strdup(name); + } + rec->type = type; + rec->func = func; + + IGRAPH_CHECK(igraph_vector_ptr_push_back(&comb->list, rec)); + + } + + return 0; +} + +int igraph_attribute_combination_remove(igraph_attribute_combination_t *comb, + const char *name) { + long int i, n = igraph_vector_ptr_size(&comb->list); + + /* Search, in case it is already there */ + for (i = 0; i < n; i++) { + igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i]; + const char *n = r->name; + if ( (!name && !n) || + (name && n && !strcmp(n, name)) ) { + break; + } + } + + if (i != n) { + igraph_attribute_combination_record_t *r = VECTOR(comb->list)[i]; + if (r->name) { + igraph_Free(r->name); + } + igraph_Free(r); + igraph_vector_ptr_remove(&comb->list, i); + } else { + /* It is not there, we don't do anything */ + } + + return 0; +} + +int igraph_attribute_combination_query(const igraph_attribute_combination_t *comb, + const char *name, + igraph_attribute_combination_type_t *type, + igraph_function_pointer_t *func) { + long int i, def = -1, len = igraph_vector_ptr_size(&comb->list); + + for (i = 0; i < len; i++) { + igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[i]; + const char *n = rec->name; + if ( (!name && !n) || + (name && n && !strcmp(n, name)) ) { + *type = rec->type; + *func = rec->func; + return 0; + } + if (!n) { + def = i; + } + } + + if (def == -1) { + /* Did not find anything */ + *type = IGRAPH_ATTRIBUTE_COMBINE_DEFAULT; + *func = 0; + } else { + igraph_attribute_combination_record_t *rec = VECTOR(comb->list)[def]; + *type = rec->type; + *func = rec->func; + } + + return 0; +} + +int igraph_attribute_combination(igraph_attribute_combination_t *comb, ...) { + + va_list ap; + + IGRAPH_CHECK(igraph_attribute_combination_init(comb)); + + va_start(ap, comb); + while (1) { + igraph_function_pointer_t func = 0; + igraph_attribute_combination_type_t type; + const char *name; + + name = va_arg(ap, const char *); + + if (name == IGRAPH_NO_MORE_ATTRIBUTES) { + break; + } + + type = (igraph_attribute_combination_type_t)va_arg(ap, int); + if (type == IGRAPH_ATTRIBUTE_COMBINE_FUNCTION) { +#if defined(__GNUC__) + func = va_arg(ap, void (*)(void)); +#else + func = va_arg(ap, void*); +#endif + } + + if (strlen(name) == 0) { + name = 0; + } + + IGRAPH_CHECK(igraph_attribute_combination_add(comb, name, type, func)); + } + + va_end(ap); + + return 0; +} diff --git a/src/basic_query.c b/src/basic_query.c new file mode 100644 index 0000000..d2a0055 --- /dev/null +++ b/src/basic_query.c @@ -0,0 +1,64 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_datatype.h" +#include "igraph_types.h" +#include "igraph_interface.h" +#include "igraph_structural.h" +#include "config.h" + +/** + * \ingroup structural + * \function igraph_are_connected + * \brief Decides whether two vertices are connected + * + * \param graph The graph object. + * \param v1 The first vertex. + * \param v2 The second vertex. + * \param res Boolean, \c TRUE if there is an edge from + * \p v1 to \p v2, \c FALSE otherwise. + * \return The error code \c IGRAPH_EINVVID is returned if an invalid + * vertex ID is given. + * + * The function is of course symmetric for undirected graphs. + * + * + * Time complexity: O( min(log(d1), log(d2)) ), + * d1 is the (out-)degree of \p v1 and d2 is the (in-)degree of \p v2. + */ +int igraph_are_connected(const igraph_t *graph, + igraph_integer_t v1, igraph_integer_t v2, + igraph_bool_t *res) { + + long int nov = igraph_vcount(graph); + igraph_integer_t eid = -1; + + if (v1 < 0 || v2 < 0 || v1 > nov - 1 || v2 > nov - 1) { + IGRAPH_ERROR("are connected", IGRAPH_EINVVID); + } + + igraph_get_eid(graph, &eid, v1, v2, /*directed=*/1, /*error=*/ 0); + *res = (eid >= 0); + + return IGRAPH_SUCCESS; +} diff --git a/src/bfgs.c b/src/bfgs.c new file mode 100644 index 0000000..620a09e --- /dev/null +++ b/src/bfgs.c @@ -0,0 +1,222 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_nongraph.h" +#include "igraph_interrupt_internal.h" +#include "igraph_statusbar.h" +#include "config.h" + +#include + +/* This is from GNU R's optim.c, slightly adapted to igraph */ + +#define stepredn 0.2 +#define acctol 0.0001 +#define reltest 10.0 +#define FALSE 0 +#define TRUE 1 + +/* BFGS variable-metric method, based on Pascal code +in J.C. Nash, `Compact Numerical Methods for Computers', 2nd edition, +converted by p2c then re-crafted by B.D. Ripley */ + +int +igraph_bfgs(igraph_vector_t *b, igraph_real_t *Fmin, + igraph_scalar_function_t fminfn, igraph_vector_function_t fmingr, + int maxit, int trace, + igraph_real_t abstol, igraph_real_t reltol, int nREPORT, void *ex, + igraph_integer_t *fncount, igraph_integer_t *grcount) { + int n = (int) igraph_vector_size(b); + igraph_bool_t accpoint, enough; + igraph_vector_t g, t, X, c; + igraph_matrix_t B; /* Lmatrix really */ + int count, funcount, gradcount; + igraph_real_t f, gradproj; + int i, j, ilast, iter = 0; + igraph_real_t s, steplength; + igraph_real_t D1, D2; + + if (maxit <= 0) { + *Fmin = fminfn(b, 0, ex); + *fncount = 1; + *grcount = 0; + return 0; + } + + if (nREPORT <= 0) { + IGRAPH_ERROR("REPORT must be > 0 (method = \"BFGS\")", IGRAPH_EINVAL); + } + IGRAPH_VECTOR_INIT_FINALLY(&g, n); + IGRAPH_VECTOR_INIT_FINALLY(&t, n); + IGRAPH_VECTOR_INIT_FINALLY(&X, n); + IGRAPH_VECTOR_INIT_FINALLY(&c, n); + IGRAPH_MATRIX_INIT_FINALLY(&B, n, n); + f = fminfn(b, 0, ex); + if (!IGRAPH_FINITE(f)) { + IGRAPH_ERROR("initial value in 'BFGS' is not finite", IGRAPH_DIVERGED); + } + if (trace) { + igraph_statusf("initial value %f ", 0, f); + } + *Fmin = f; + funcount = gradcount = 1; + fmingr(b, 0, &g, ex); + iter++; + ilast = gradcount; + + do { + + IGRAPH_ALLOW_INTERRUPTION(); + + if (ilast == gradcount) { + for (i = 0; i < n; i++) { + for (j = 0; j < i; j++) { + MATRIX(B, i, j) = 0.0; + } + MATRIX(B, i, i) = 1.0; + } + } + for (i = 0; i < n; i++) { + VECTOR(X)[i] = VECTOR(*b)[i]; + VECTOR(c)[i] = VECTOR(g)[i]; + } + gradproj = 0.0; + for (i = 0; i < n; i++) { + s = 0.0; + for (j = 0; j <= i; j++) { + s -= MATRIX(B, i, j) * VECTOR(g)[j]; + } + for (j = i + 1; j < n; j++) { + s -= MATRIX(B, j, i) * VECTOR(g)[j]; + } + VECTOR(t)[i] = s; + gradproj += s * VECTOR(g)[i]; + } + + if (gradproj < 0.0) { /* search direction is downhill */ + steplength = 1.0; + accpoint = FALSE; + do { + count = 0; + for (i = 0; i < n; i++) { + VECTOR(*b)[i] = VECTOR(X)[i] + steplength * VECTOR(t)[i]; + if (reltest + VECTOR(X)[i] == reltest + VECTOR(*b)[i]) { /* no change */ + count++; + } + } + if (count < n) { + f = fminfn(b, 0, ex); + funcount++; + accpoint = IGRAPH_FINITE(f) && + (f <= *Fmin + gradproj * steplength * acctol); + if (!accpoint) { + steplength *= stepredn; + } + } + } while (!(count == n || accpoint)); + enough = (f > abstol) && + fabs(f - *Fmin) > reltol * (fabs(*Fmin) + reltol); + /* stop if value if small or if relative change is low */ + if (!enough) { + count = n; + *Fmin = f; + } + if (count < n) {/* making progress */ + *Fmin = f; + fmingr(b, 0, &g, ex); + gradcount++; + iter++; + D1 = 0.0; + for (i = 0; i < n; i++) { + VECTOR(t)[i] = steplength * VECTOR(t)[i]; + VECTOR(c)[i] = VECTOR(g)[i] - VECTOR(c)[i]; + D1 += VECTOR(t)[i] * VECTOR(c)[i]; + } + if (D1 > 0) { + D2 = 0.0; + for (i = 0; i < n; i++) { + s = 0.0; + for (j = 0; j <= i; j++) { + s += MATRIX(B, i, j) * VECTOR(c)[j]; + } + for (j = i + 1; j < n; j++) { + s += MATRIX(B, j, i) * VECTOR(c)[j]; + } + VECTOR(X)[i] = s; + D2 += s * VECTOR(c)[i]; + } + D2 = 1.0 + D2 / D1; + for (i = 0; i < n; i++) { + for (j = 0; j <= i; j++) + MATRIX(B, i, j) += (D2 * VECTOR(t)[i] * VECTOR(t)[j] + - VECTOR(X)[i] * VECTOR(t)[j] + - VECTOR(t)[i] * VECTOR(X)[j]) / D1; + } + } else { /* D1 < 0 */ + ilast = gradcount; + } + } else { /* no progress */ + if (ilast < gradcount) { + count = 0; + ilast = gradcount; + } + } + } else { /* uphill search */ + count = 0; + if (ilast == gradcount) { + count = n; + } else { + ilast = gradcount; + } + /* Resets unless has just been reset */ + } + if (trace && (iter % nREPORT == 0)) { + igraph_statusf("iter%4d value %f", 0, iter, f); + } + if (iter >= maxit) { + break; + } + if (gradcount - ilast > 2 * n) { + ilast = gradcount; /* periodic restart */ + } + } while (count != n || ilast != gradcount); + if (trace) { + igraph_statusf("final value %f ", 0, *Fmin); + if (iter < maxit) { + igraph_status("converged", 0); + } else { + igraph_statusf("stopped after %i iterations", 0, iter); + } + } + *fncount = funcount; + *grcount = gradcount; + + igraph_matrix_destroy(&B); + igraph_vector_destroy(&c); + igraph_vector_destroy(&X); + igraph_vector_destroy(&t); + igraph_vector_destroy(&g); + IGRAPH_FINALLY_CLEAN(5); + + return (iter < maxit) ? 0 : IGRAPH_DIVERGED; +} diff --git a/src/bigint.c b/src/bigint.c new file mode 100644 index 0000000..e663853 --- /dev/null +++ b/src/bigint.c @@ -0,0 +1,329 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "bigint.h" +#include "igraph_error.h" +#include "igraph_memory.h" + +int igraph_biguint_init(igraph_biguint_t *b) { + IGRAPH_CHECK(igraph_vector_limb_init(&b->v, IGRAPH_BIGUINT_DEFAULT_SIZE)); + igraph_vector_limb_clear(&b->v); + return 0; +} + +void igraph_biguint_destroy(igraph_biguint_t *b) { + igraph_vector_limb_destroy(&b->v); +} + +int igraph_biguint_copy(igraph_biguint_t *to, igraph_biguint_t *from) { + return igraph_vector_limb_copy(&to->v, &from->v); +} + +int igraph_biguint_extend(igraph_biguint_t *b, limb_t l) { + return igraph_vector_limb_push_back(&b->v, l); +} + +int igraph_biguint_size(igraph_biguint_t *b) { + return (int) igraph_vector_limb_size(&b->v); +} + +int igraph_biguint_resize(igraph_biguint_t *b, int newlength) { + int origlen = igraph_biguint_size(b); + IGRAPH_CHECK(igraph_vector_limb_resize(&b->v, newlength)); + if (newlength > origlen) { + memset(VECTOR(b->v) + origlen, 0, + (size_t) (newlength - origlen) * sizeof(limb_t)); + } + return 0; +} + +int igraph_biguint_reserve(igraph_biguint_t *b, int length) { + return igraph_vector_limb_reserve(&b->v, length); +} + +int igraph_biguint_zero(igraph_biguint_t *b) { + igraph_vector_limb_clear(&b->v); + return 0; +} + +int igraph_biguint_set_limb(igraph_biguint_t *b, int value) { + IGRAPH_CHECK(igraph_vector_limb_resize(&b->v, 1)); + VECTOR(b->v)[0] = (limb_t) value; + return 0; +} + +igraph_real_t igraph_biguint_get(igraph_biguint_t *b) { + int size = igraph_biguint_size(b); + int i; + double val = VECTOR(b->v)[size - 1]; + if (size == 0) { + return 0.0; + } + for (i = size - 2; i >= 0; i--) { + val = val * LIMBMASK + VECTOR(b->v)[i]; + if (!IGRAPH_FINITE(val)) { + break; + } + } + return val; +} + +int igraph_biguint_compare_limb(igraph_biguint_t *b, limb_t l) { + int n = igraph_biguint_size(b); + return bn_cmp_limb(VECTOR(b->v), l, (count_t) n); +} + +int igraph_biguint_compare(igraph_biguint_t *left, igraph_biguint_t *right) { + /* bn_cmp requires the two numbers to have the same number of limbs, + so we do this partially by hand here */ + int size_left = igraph_biguint_size(left); + int size_right = igraph_biguint_size(right); + while (size_left > size_right) { + if (VECTOR(left->v)[--size_left] > 0) { + return +1; + } + } + while (size_right > size_left) { + if (VECTOR(right->v)[--size_right] > 0) { + return -1; + } + } + return bn_cmp( VECTOR(left->v), VECTOR(right->v), (count_t) size_right ); +} + + +igraph_bool_t igraph_biguint_equal(igraph_biguint_t *left, igraph_biguint_t *right) { + return 0 == igraph_biguint_compare(left, right); +} + + +igraph_bool_t igraph_biguint_bigger(igraph_biguint_t *left, + igraph_biguint_t *right) { + return 0 < igraph_biguint_compare(left, right); +} + + +igraph_bool_t igraph_biguint_biggerorequal(igraph_biguint_t *left, + igraph_biguint_t *right) { + return 0 <= igraph_biguint_compare(left, right); +} + +int igraph_biguint_inc(igraph_biguint_t *res, igraph_biguint_t *b) { + return igraph_biguint_add_limb(res, b, 1); +} + +int igraph_biguint_dec(igraph_biguint_t *res, igraph_biguint_t *b) { + return igraph_biguint_sub_limb(res, b, 1); +} + + +int igraph_biguint_add_limb(igraph_biguint_t *res, igraph_biguint_t *b, + limb_t l) { + int nlimb = igraph_biguint_size(b); + limb_t carry; + + if (res != b) { + IGRAPH_CHECK(igraph_biguint_resize(res, nlimb)); + } + + carry = bn_add_limb( VECTOR(res->v), VECTOR(b->v), l, (count_t) nlimb); + if (carry) { + IGRAPH_CHECK(igraph_biguint_extend(res, carry)); + } + return 0; +} + +int igraph_biguint_sub_limb(igraph_biguint_t *res, igraph_biguint_t *b, + limb_t l) { + int nlimb = igraph_biguint_size(b); + + if (res != b) { + IGRAPH_CHECK(igraph_biguint_resize(res, nlimb)); + } + + /* We don't check the return value here */ + bn_sub_limb( VECTOR(res->v), VECTOR(b->v), l, (count_t) nlimb); + + return 0; +} + +int igraph_biguint_mul_limb(igraph_biguint_t *res, igraph_biguint_t *b, + limb_t l) { + int nlimb = igraph_biguint_size(b); + limb_t carry; + + if (res != b) { + IGRAPH_CHECK(igraph_biguint_resize(res, nlimb)); + } + + carry = bn_mul_limb( VECTOR(res->v), VECTOR(b->v), l, (count_t) nlimb); + if (carry) { + IGRAPH_CHECK(igraph_biguint_extend(res, carry)); + } + return 0; +} + +int igraph_biguint_add(igraph_biguint_t *res, igraph_biguint_t *left, + igraph_biguint_t *right) { + + int size_left = igraph_biguint_size(left); + int size_right = igraph_biguint_size(right); + limb_t carry; + + if (size_left > size_right) { + IGRAPH_CHECK(igraph_biguint_resize(right, size_left)); + size_right = size_left; + } else if (size_left < size_right) { + IGRAPH_CHECK(igraph_biguint_resize(left, size_right)); + size_left = size_right; + } + IGRAPH_CHECK(igraph_biguint_resize(res, size_left)); + + carry = bn_add( VECTOR(res->v), VECTOR(left->v), VECTOR(right->v), + (count_t) size_left); + if (carry) { + IGRAPH_CHECK(igraph_biguint_extend(res, carry)); + } + return 0; +} + +int igraph_biguint_sub(igraph_biguint_t *res, igraph_biguint_t *left, + igraph_biguint_t *right) { + + int size_left = igraph_biguint_size(left); + int size_right = igraph_biguint_size(right); + + if (size_left > size_right) { + IGRAPH_CHECK(igraph_biguint_resize(right, size_left)); + size_right = size_left; + } else if (size_left < size_right) { + IGRAPH_CHECK(igraph_biguint_resize(left, size_right)); + size_left = size_right; + } + IGRAPH_CHECK(igraph_biguint_resize(res, size_left)); + + /* We don't check return value, left should not be smaller than right! */ + bn_sub( VECTOR(res->v), VECTOR(left->v), VECTOR(right->v), + (count_t) size_left); + + return 0; +} + +int igraph_biguint_mul(igraph_biguint_t *res, igraph_biguint_t *left, + igraph_biguint_t *right) { + + int size_left = igraph_biguint_size(left); + int size_right = igraph_biguint_size(right); + + if (size_left > size_right) { + IGRAPH_CHECK(igraph_biguint_resize(right, size_left)); + size_right = size_left; + } else if (size_left < size_right) { + IGRAPH_CHECK(igraph_biguint_resize(left, size_right)); + size_left = size_right; + } + IGRAPH_CHECK(igraph_biguint_resize(res, 2 * size_left)); + + bn_mul( VECTOR(res->v), VECTOR(left->v), VECTOR(right->v), + (count_t) size_left ); + return 0; +} + +int igraph_biguint_div(igraph_biguint_t *q, igraph_biguint_t *r, + igraph_biguint_t *u, igraph_biguint_t *v) { + + int ret; + int size_q = igraph_biguint_size(q); + int size_r = igraph_biguint_size(r); + int size_u = igraph_biguint_size(u); + int size_v = igraph_biguint_size(v); + int size_qru = size_q > size_r ? size_q : size_r; + size_qru = size_u > size_qru ? size_u : size_qru; + + if (size_q < size_qru) { + IGRAPH_CHECK(igraph_biguint_resize(q, size_qru)); + } + if (size_r < size_qru) { + IGRAPH_CHECK(igraph_biguint_resize(r, size_qru)); + } + if (size_u < size_qru) { + IGRAPH_CHECK(igraph_biguint_resize(u, size_qru)); + } + + ret = bn_div( VECTOR(q->v), VECTOR(r->v), VECTOR(u->v), VECTOR(v->v), + (count_t) size_qru, (count_t) size_v ); + + if (ret) { + IGRAPH_ERROR("Bigint division by zero", IGRAPH_EDIVZERO); + } + + return 0; +} + +#ifndef USING_R +int igraph_biguint_print(igraph_biguint_t *b) { + return igraph_biguint_fprint(b, stdout); +} +#endif + +int igraph_biguint_fprint(igraph_biguint_t *b, FILE *file) { + + /* It is hard to control memory allocation for the bn2d function, + so we do our own version */ + + int n = igraph_biguint_size(b); + long int size = 12 * n + 1; + igraph_biguint_t tmp; + char *dst; + limb_t r; + + /* Zero? */ + if (!bn_cmp_limb(VECTOR(b->v), 0, (count_t) n)) { + fputs("0", file); + return 0; + } + + IGRAPH_CHECK(igraph_biguint_copy(&tmp, b)); + IGRAPH_FINALLY(igraph_biguint_destroy, &tmp); + dst = igraph_Calloc(size, char); + if (!dst) { + IGRAPH_ERROR("Cannot print big number", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, dst); + + size--; + dst[size] = '\0'; + while (0 != bn_cmp_limb(VECTOR(tmp.v), 0, (count_t) n)) { + r = bn_div_limb(VECTOR(tmp.v), VECTOR(tmp.v), 10, (count_t) n); + dst[--size] = '0' + (char) r; + } + + fputs(&dst[size], file); + + igraph_Free(dst); + igraph_biguint_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + diff --git a/src/bigint.h b/src/bigint.h new file mode 100644 index 0000000..14463db --- /dev/null +++ b/src/bigint.h @@ -0,0 +1,107 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_BIGINT_H +#define IGRAPH_BIGINT_H + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus + #define __BEGIN_DECLS extern "C" { + #define __END_DECLS } +#else + #define __BEGIN_DECLS /* empty */ + #define __END_DECLS /* empty */ +#endif + +#include "igraph_types.h" +#include "igraph_vector.h" +#include "bignum.h" + +#include + +/* Arbitrary precision integer */ + +#define BASE_LIMB +#include "igraph_pmt.h" +#include "igraph_vector_type.h" +#include "igraph_vector_pmt.h" +#include "igraph_pmt_off.h" +#undef BASE_LIMB + +__BEGIN_DECLS + +typedef struct igraph_biguint_t { + igraph_vector_limb_t v; +} igraph_biguint_t; + +#define IGRAPH_BIGUINT_DEFAULT_SIZE 5 + +int igraph_biguint_init(igraph_biguint_t *b); +void igraph_biguint_destroy(igraph_biguint_t *b); +int igraph_biguint_copy(igraph_biguint_t *to, igraph_biguint_t *from); + +int igraph_biguint_extend(igraph_biguint_t *b, limb_t l); + +int igraph_biguint_size(igraph_biguint_t *b); +int igraph_biguint_resize(igraph_biguint_t *b, int newlength); +int igraph_biguint_reserve(igraph_biguint_t *b, int length); + +int igraph_biguint_zero(igraph_biguint_t *b); +int igraph_biguint_set_limb(igraph_biguint_t *b, int value); + +igraph_real_t igraph_biguint_get(igraph_biguint_t *b); + +int igraph_biguint_compare_limb(igraph_biguint_t *b, limb_t l); +int igraph_biguint_compare(igraph_biguint_t *left, igraph_biguint_t *right); +igraph_bool_t igraph_biguint_equal(igraph_biguint_t *left, igraph_biguint_t *right); +igraph_bool_t igraph_biguint_bigger(igraph_biguint_t *left, + igraph_biguint_t *right); +igraph_bool_t igraph_biguint_biggerorequal(igraph_biguint_t *left, + igraph_biguint_t *right); + +int igraph_biguint_inc(igraph_biguint_t *res, igraph_biguint_t *b); +int igraph_biguint_dec(igraph_biguint_t *res, igraph_biguint_t *b); + +int igraph_biguint_add_limb(igraph_biguint_t *res, igraph_biguint_t *b, + limb_t l); +int igraph_biguint_sub_limb(igraph_biguint_t *res, igraph_biguint_t *b, + limb_t l); +int igraph_biguint_mul_limb(igraph_biguint_t *res, igraph_biguint_t *b, + limb_t l); + +int igraph_biguint_add(igraph_biguint_t *res, igraph_biguint_t *left, + igraph_biguint_t *right); +int igraph_biguint_sub(igraph_biguint_t *res, igraph_biguint_t *left, + igraph_biguint_t *right); +int igraph_biguint_mul(igraph_biguint_t *res, igraph_biguint_t *left, + igraph_biguint_t *right); +int igraph_biguint_div(igraph_biguint_t *q, igraph_biguint_t *r, + igraph_biguint_t *u, igraph_biguint_t *v); + +int igraph_biguint_print(igraph_biguint_t *b); +int igraph_biguint_fprint(igraph_biguint_t *b, FILE *file); + +__END_DECLS + +#endif diff --git a/src/bignum.c b/src/bignum.c new file mode 100644 index 0000000..0b68a62 --- /dev/null +++ b/src/bignum.c @@ -0,0 +1,1983 @@ +/****************************************************************************** + * bn.c - big number math implementation + * + * Copyright (c) 2004 by Juergen Buchmueller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: bignum.c,v 1.17 2005/07/23 02:55:53 pullmoll Exp $ + ******************************************************************************/ + +#include "bignum.h" +#include "igraph_error.h" +#include "config.h" + +#ifndef ASM_X86 + #ifdef X86 + #define ASM_X86 1 + #endif +#endif + +/** + * @brief Return hex representation of a big number + * + * Returns the hex representation of a[], + * where a is a big number integer with nlimb limbs. + * + * @param a pointer to an array of limbs + * @param nlimb number of limbs in the array + * + * @result string containing the hex representation of a + */ +const char *bn2x(limb_t *a, count_t nlimb) { + static IGRAPH_THREAD_LOCAL count_t which = 0; + static IGRAPH_THREAD_LOCAL char *xbuff[8] = { + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL + }; + char *dst; + count_t size; + count_t n = nlimb; + + if (0 == n) { + return "0"; + } + + which = (which + 1) % 8; + size = 8 * n + 1; + if (NULL != xbuff[which]) { + free(xbuff[which]); + } + dst = xbuff[which] = calloc(size, sizeof(char)); + if (NULL == dst) { + return "memory error"; + } + while (n-- > 0) { + dst += snprintf(dst, size, "%08x", a[n]); + size -= 8; + } + return xbuff[which]; +} + +/** + * @brief Return decimal representation of a big number + * + * Returns the decimal representation of a[], + * where a is a big number integer with nlimb limbs. + * + * @param a pointer to an array of limbs + * @param nlimb number of limbs in the array + * + * @result string containing the decimal representation of a + */ +const char *bn2d(limb_t *a, count_t nlimb) { + static IGRAPH_THREAD_LOCAL count_t which = 0; + static IGRAPH_THREAD_LOCAL char *dbuff[8] = { + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL + }; + static IGRAPH_THREAD_LOCAL limb_t v[BN_MAXSIZE]; + limb_t r; + char *dst; + count_t size; + count_t n = bn_sizeof(a, nlimb); + + if (0 == n) { + return "0"; + } + + bn_copy(v, a, n); + which = (which + 1) % 8; + size = 12 * n + 1; + if (NULL != dbuff[which]) { + free(dbuff[which]); + } + dst = dbuff[which] = calloc(size, sizeof(char)); + if (NULL == dst) { + return "memory error"; + } + size--; + while (0 != bn_cmp_limb(v, 0, n)) { + r = bn_div_limb(v, v, 10, n); + dst[--size] = '0' + (char) r; + } + return &dst[size]; +} + +/** + * @brief Return decimal representation of a big number pair + * + * Returns the decimal representation of a[].b[], + * where a is a big number integer with alimb limbs, + * and b is a multiprecision fixed fraction with blimb limbs. + * + * @param a pointer to an array of limbs + * @param alimb number of limbs in the a array + * @param b pointer to an array of limbs + * @param blimb number of limbs in the b array + * + * @result string containing the decimal representation of a.b + */ +const char *bn2f(limb_t *a, count_t alimb, limb_t *b, count_t blimb) { + static IGRAPH_THREAD_LOCAL count_t which = 0; + static IGRAPH_THREAD_LOCAL char *dbuff[8] = { + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL + }; + static IGRAPH_THREAD_LOCAL limb_t v[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t w[BN_MAXSIZE]; + limb_t r; + char *dst; + count_t size; + + bn_copy(v, a, alimb); + bn_copy(w, b, blimb); + + which = (which + 1) % 8; + size = 12 * (alimb + blimb) + 1 + 1; + if (NULL != dbuff[which]) { + free(dbuff[which]); + } + dst = dbuff[which] = calloc(size, sizeof(char)); + if (NULL == dst) { + return "memory error"; + } + size = 12 * alimb; + while (0 != bn_cmp_limb(w, 0, blimb) && size < 12 * (alimb + blimb)) { + r = bn_mul_limb(w, w, 10, blimb); + dst[size++] = '0' + (char) r; + } + + size = 12 * alimb; + dst[size] = '.'; + while (0 != bn_cmp_limb(v, 0, alimb) && size > 0) { + r = bn_div_limb(v, v, 10, alimb); + dst[--size] = '0' + (char) r; + } + + return &dst[size]; +} + +/** + * @brief Return binary representation of a big number + * + * Returns the binary representation of a[], + * where a is a big number integer with nlimb limbs. + * + * @param a pointer to an array of limbs + * @param nlimb number of limbs in the array + * + * @result string containing the binary representation of a + */ +const char *bn2b(limb_t *a, count_t nlimb) { + static IGRAPH_THREAD_LOCAL count_t which = 0; + static IGRAPH_THREAD_LOCAL char *bbuff[8] = { + NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL + }; + limb_t r; + char *dst; + count_t size; + count_t n = bn_sizeof(a, nlimb); + + if (0 == n) { + return "0"; + } + + which = (which + 1) % 8; + size = LIMBBITS * n + 1; + if (NULL != bbuff[which]) { + free(bbuff[which]); + } + dst = bbuff[which] = calloc(size, sizeof(char)); + if (NULL == dst) { + return "memory error"; + } + n = 0; + size--; + while (size-- > 0) { + r = (a[n / LIMBBITS] >> (n % LIMBBITS)) & 1; + n++; + dst[size] = '0' + (char) r; + } + return &dst[size]; +} + +/** + * @brief Zero an array of limbs + * + * Sets a[] = 0 + * where a is a big number integer of nlimb limbs. + * + * @param a pointer to an array of limbs + * @param nlimb number of limbs in the array + * + */ +void bn_zero(limb_t a[], count_t nlimb) { + memset(a, 0, nlimb * sizeof(limb_t)); +} + +/** + * @brief Set an array of limbs to a single limb value + * + * Sets a[] = d + * where a is a big number integer of nlimb limbs, + * and d is a single limb + * + * @param a pointer to an array of limbs to set + * @param d limb value to set a to + * @param nlimb number of limbs in the array + * + */ +void bn_limb(limb_t a[], limb_t d, count_t nlimb) { + memset(a, 0, nlimb * sizeof(limb_t)); + a[0] = d; +} + +/** + * @brief Copy an array of limbs + * + * Sets a[] = b[] + * where a and b are a big number integers of nlimb limbs + * + * @param a pointer to an array of limbs (destination) + * @param b pointer to an array of limbs (source) + * @param nlimb number of limbs in the arrays + */ +void bn_copy(limb_t a[], limb_t b[], count_t nlimb) { + memcpy(a, b, nlimb * sizeof(limb_t)); +} + +/** + * @brief Return significant size of a big number + * + * Returns size of significant limbs in a[] + * i.e. searches for the first non-zero limb from + * nlimb-1 downto 0. + * + * @param a pointer to an array of limbs (candidate) + * @param nlimb number of limbs in the arrays + * + * @result number of significant limbs in a + */ +count_t bn_sizeof(limb_t a[], count_t nlimb) { + while (nlimb-- > 0) + if (0 != a[nlimb]) { + return ++nlimb; + } + return 0; +} + + +/** + * @brief Return sign of a bignum minus a limb + * + * Returns the sign of (a[] - b) + * where a is a big number integer of nlimb limbs, + * and b is a single limb + + + * @param a pointer to an array of limbs (minuend) + * @param b a single limb (subtrahend) + * @param nlimb number of limbs in the array a + * + * @result sign of the comparison: -1 ab + */ +int bn_cmp_limb(limb_t a[], limb_t b, count_t nlimb) { + if (0 == nlimb) { + return 0; + } + + while (nlimb-- > 1) + if (0 != a[nlimb]) { + return +1; + } + if (a[0] < b) { + return -1; + } + if (a[0] > b) { + return +1; + } + return 0; +} + +/** + * @brief Return sign of bignum a minus bignum b + * + * Returns the sign of (a[] - b[]) + * where a and b are a big number integers of nlimb limbs + * + * @param a pointer to an array of limbs (minuend) + * @param b pointer to an array of limbs (subtrahend) + * @param nlimb number of limbs in the arrays + * + * @result sign of the comparison: -1 ab + */ +int bn_cmp(limb_t a[], limb_t b[], count_t nlimb) { + if (0 == nlimb) { + return 0; + } + + while (nlimb-- > 0) { + if (a[nlimb] > b[nlimb]) { + return +1; /* GT */ + } + if (a[nlimb] < b[nlimb]) { + return -1; /* LT */ + } + } + + return 0; /* EQ */ +} + +/** + * @brief Single limb is even test + * + * Returns 1 if a is even, else 0 + * where a is a single limb + * + * @param a a single limb + * + * @result zero if a is odd, 1 if a is even + */ +int sl_iseven(limb_t a) { + return (a & 1) ? 0 : 1; +} + +/** + * @brief bignum is even test + * + * Returns 1 if a[] is even, else 0 + * where a is a big number integer of nlimb limbs + * Note: a zero limb big number integer is even! + * + * @param a pointer to an array of limbs + * @param nlimb number of limbs in the arrays + * + * @result zero if a is odd, 1 if a is even + */ +int bn_iseven(limb_t *a, count_t nlimb) { + if (0 == nlimb) { + return 1; + } + return (a[0] & 1) ? 0 : 1; +} + +/** + * @brief Add a single limb to a bignum + * + * Computes w[] = u[] + v + * where w, u are big number integers of nlimb lims each, + * and v is a single limb. + * Returns carry if the addition overflows. + * + * Ref: Derived from Knuth Algorithm A. + * + * @param w pointer to an array of limbs receiving result + * @param u pointer to an array of limbs (addend 1) + * @param v a single limb + * @param nlimb number of limbs in the arrays w and u + * + * @result The carry status of the addition + */ +limb_t bn_add_limb(limb_t w[], limb_t u[], limb_t v, count_t nlimb) { + limb_t carry; + count_t j; + + /* Copy u to w, so we can bail out if no borrow is left */ + if (w != u) { + bn_copy(w, u, nlimb); + } + + /* Add v to first limb of u */ + w[0] += v; + carry = (w[0] < v ? 1 : 0); + + /* Add carry to subsequent limbs */ + for (j = 1; 0 != carry && j < nlimb; j++) { + w[j] += carry; + carry = (w[j] < carry ? 1 : 0); + } + return carry; +} + + +/** + * @brief Subtract a single limb from a bignum + * + * Computes w[] = u[] - v + * where w, u are big number integers of nlimb limbs each, + * and v is a single limb. + * Returns borrow (0 if u >= v, or 1 if v > u). + * + * Ref: Derived from Knuth Algorithm S. + * + * @param w pointer to an array of limbs receiving the result + * @param u pointer to an array of limbs (minuend) + * @param v single limb (subtrahend) + * @param nlimb number of limbs in the arrays + * + * @result borrow of the subtraction (0 if u >= v, 1 if u < v) + */ +limb_t bn_sub_limb(limb_t w[], limb_t u[], limb_t v, count_t nlimb) { + limb_t borrow; + count_t j; + + /* Copy u to w, so we can bail out if no borrow is left */ + if (w != u) { + bn_copy(w, u, nlimb); + } + + /* Subtract v from first limb of u */ + w[0] -= v; + borrow = (w[0] > ~v ? 1 : 0); + + /* Subtract borrow from subsequent limbs */ + for (j = 1; 0 != borrow && j < nlimb; j++) { + w[j] -= borrow; + borrow = (w[j] > ~borrow ? 1 : 0); + } + + return borrow; +} + +/** + * @brief Divide a bignum by a single limb + * + * Computes quotient q[] = u[] / v + * and returns remainder r = u[] % v + * where q, u are big number integers of nlimb limbs each, + * and v is a single limb. + * + * Makes no assumptions about normalisation. + * + * Ref: Knuth Vol 2 Ch 4.3.1 Exercise 16 p625 + * + * @param q pointer to an array of limbs receiving the quotient + * @param u pointer to an array of limbs (dividend) + * @param v single limb (divisor) + * @param nlimb number of limbs in the arrays + * + * @result single limb remainder of the division (modulo) + */ +limb_t bn_div_limb(limb_t q[], limb_t u[], limb_t v, count_t nlimb) { + count_t j; + limb_t t[2], r; + count_t shift; + + if (0 == nlimb) { + return 0; + } + if (0 == v) { + return LIMBMASK; /* Divide by zero error */ + } + + /* + * Normalize first: + * qequires high bit of V to be set, + * so find most significant by shifting + * until DIGMSB is set. + */ + for (shift = 0; 0 == (v & DIGMSB); shift++) { + v <<= 1; + } + r = bn_shl(q, u, shift, nlimb); + + j = nlimb; + while (j-- > 0) { + t[0] = q[j]; + t[1] = r; + sl_div(&q[j], &r, t, v); + } + + /* Unnormalize */ + r >>= shift; + return r; +} + +/** + * @brief Modulo a bignum by a single limb + * + * Computes remainder (modulo) r = u[] mod v + * Computes r = u[] mod v + * where u is a big number integer of nlimb + * and r, v are single precision limbs + * + * Use remainder from divide function. + * + * @param u pointer to an array of limbs (dividend) + * @param v single limb (divisor) + * @param nlimb number of limbs in the arrays + * + * @result single limb remainder of the division (modulo) + */ +limb_t bn_mod_limb(limb_t u[], limb_t v, count_t nlimb) { + static IGRAPH_THREAD_LOCAL limb_t q[2 * BN_MAXSIZE]; + limb_t r; + + r = bn_div_limb(q, u, v, nlimb); + + bn_zero(q, nlimb); + return r; +} + +/** + * @brief Multiply a bignum by a single limb + * + * Computes product w[] = u[] * v + * Returns overflow k + * where w, u are big number integers of nlimb each + * and v is a single limb + * + * @param w pointer to an array of limbs to receive the result + * @param u pointer to an array of limbs (factor) + * @param v single limb (other factor) + * @param nlimb number of limbs in the arrays + * + * @result zero if no overflow, else overflow (value of w[nlimb]) + */ +limb_t bn_mul_limb(limb_t w[], limb_t u[], limb_t v, count_t nlimb) { + limb_t t[2]; + limb_t carry; + count_t j; + + if (0 == v) { + bn_zero(w, nlimb); + return 0; + } + + for (j = 0, carry = 0; j < nlimb; j++) { + sl_mul(t, u[j], v); + w[j] = t[0] + carry; + carry = t[1] + (w[j] < carry ? 1 : 0); + } + + return carry; +} + +#if HAVE_U64 +/** + * @brief Computes quotient and remainder of 64 bit / 32 bit + * + * Computes quotient q = u[] / v, remainder r = u[] mod v + * where u[] is a double limb. + * + * With native support for double limb division + * + * @param q pointer to the limb to receive the quotient + * @param r pointer to the limb to receive the remainder + * @param u pointer to an array of two limbs + * @param v single limb divisor + * + * @result zero on success + */ +limb_t sl_div(limb_t *q, limb_t *r, limb_t u[2], limb_t v) { +#if ASM_X86 + limb_t qq; + limb_t rr; + + if (0 == v) + /* division by zero */ + { + return LIMBMASK; + } + asm volatile( + "divl %4" + : "=a"(qq), "=d"(rr) + : "a"(u[0]), "d"(u[1]), "g"(v)); + *q = qq; + *r = rr; +#else + dlimb_t dd; + + if (0 == v) + /* division by zero */ + { + return LIMBMASK; + } + dd = ((dlimb_t)u[1] << LIMBBITS) | u[0]; + *q = (limb_t) (dd / v); + *r = dd % v; +#endif + return 0; +} + +#else + +#define B (HALFMASK + 1) + +/** + * @brief Computes quotient and remainder of 64 bit / 32 bit + * + * Computes quotient q = u / v, remainder r = u mod v + * where u is a double limb + * and q, v, r are single precision limbs. + * Returns high limb of quotient (max value is 1) + * Assumes normalized such that v1 >= b/2 + * where b is size of HALF_DIGIT + * i.e. the most significant bit of v should be one + * + * In terms of half-limbs in Knuth notation: + * (q2q1q0) = (u4u3u2u1u0) / (v1v0) + * (r1r0) = (u4u3u2u1u0) % (v1v0) + * for m = 2, n = 2 where u4 = 0 + * + * We set q = (q1q0) and return q2 as "overflow' + * Returned q2 is either 0 or 1. + * + * @param q pointer to the limb to receive the quotient + * @param r pointer to the limb to receive the remainder + * @param u pointer to an array of two limbs + * @param v single limb divisor + * + * @result zero on success + */ +limb_t sl_div(limb_t *q, limb_t *r, limb_t u[2], limb_t v) { + limb_t quot; + limb_t rem; + limb_t ul; + limb_t uh; + limb_t p0; + limb_t p1; + limb_t v0; + limb_t v1; + limb_t u0; + limb_t u1; + limb_t u2; + limb_t u3; + limb_t borrow; + limb_t q1; + limb_t q2; + limb_t s; + limb_t t; + + /* Check for normalisation */ + if (0 == (v & DIGMSB)) { + *q = *r = 0; + return LIMBMASK; + } + + /* Split up into half-limbs */ + v0 = LSH(v); + v1 = MSH(v); + u0 = LSH(u[0]); + u1 = MSH(u[0]); + u2 = LSH(u[1]); + u3 = MSH(u[1]); + + /* Do three rounds of Knuth Algorithm D Vol 2 p272 */ + + /* + * ROUND 1 calculate q2: + * estimate quot = (u4u3)/v1 = 0 or 1, + * then set (u4u3u2) -= quot*(v1v0) where u4 = 0. + */ + quot = u3 / v1; + if (quot > 0) { + rem = u3 - quot * v1; + t = SHL(rem) | u2; + if (quot * v0 > t) { + quot--; + } + } + uh = 0; /* (u4) */ + ul = u[1]; /* (u3u2) */ + if (quot > 0) { + /* (u4u3u2) -= quot*(v1v0) where u4 = 0 */ + p0 = quot * v0; + p1 = quot * v1; + s = p0 + SHL(p1); + ul -= s; + borrow = (ul > ~s ? 1 : 0); + uh -= MSH(p1) - borrow; + + if (0 != MSH(uh)) { + /* add back */ + quot--; + ul += v; + uh = 0; + } + } + q2 = quot; + + /* + * ROUND 2 calculate q1: + * estimate quot = (u3u2) / v1, + * then set (u3u2u1) -= quot*(v1v0) + */ + t = ul; + quot = t / v1; + rem = t - quot * v1; + /* Test on v0 */ + t = SHL(rem) | u1; + if (B == quot || (quot * v0) > t) { + quot--; + rem += v1; + t = SHL(rem) | u1; + if (rem < B && (quot * v0) > t) { + quot--; + } + } + + /* + * multiply and subtract: + * (u3u2u1)' = (u3u2u1) - quot*(v1v0) + */ + uh = MSH(ul); /* (0u3) */ + ul = SHL(ul) | u1; /* (u2u1) */ + p0 = quot * v0; + p1 = quot * v1; + s = p0 + SHL(p1); + ul -= s; + borrow = (ul > ~s ? 1 : 0); + uh -= MSH(p1) - borrow; + + if (0 != MSH(uh)) { + /* add back v */ + quot--; + ul += v; + uh = 0; + } + + /* quotient q1 */ + q1 = quot; + + /* + * ROUND 3: + * calculate q0; estimate quot = (u2u1) / v1, + * then set (u2u1u0) -= quot(v1v0) + */ + t = ul; + quot = t / v1; + rem = t - quot * v1; + /* Test on v0 */ + t = SHL(rem) | u0; + if (B == quot || (quot * v0) > t) { + quot--; + rem += v1; + t = SHL(rem) | u0; + if (rem < B && (quot * v0) > t) { + quot--; + } + } + + /* + * multiply and subtract: + * (u2u1u0)" = (u2u1u0)' - quot(v1v0) + */ + uh = MSH(ul); /* (0u2) */ + ul = SHL(ul) | u0; /* (u1u0) */ + + p0 = quot * v0; + p1 = quot * v1; + s = p0 + SHL(p1); + ul -= s; + borrow = (ul > ~s ? 1 : 0); + uh -= MSH(p1) - borrow; + if (0 != MSH(uh)) { + /* add back v */ + quot--; + ul += v; + uh = 0; + } + + /* quotient q1q0 */ + *q = SHL(q1) | LSH(quot); + + /* Remainder is in (u1u0) i.e. ul */ + *r = ul; + + /* quotient q2 (overflow) is returned */ + return q2; +} + +#endif /* HAVE_U64 */ + +/** + * @brief Return greatest common divisor of two single limbs + * + * Returns gcd(x, y) + * + * Ref: Schneier 2nd ed, p245 + * + * @param x single limb candidate #1 + * @param y single limb candidate #2 + * + * @result return zero if x and y are zero, else gcd(x,y) + */ +limb_t sl_gcd(limb_t x, limb_t y) { + limb_t g; + + if (x + y == 0) { + return 0; /* Error */ + } + + g = y; + while (x > 0) { + g = x; + x = y % x; + y = g; + } + return g; +} + +/** + * @brief Compute single limb exp = x^e mod m + * + * Computes exp = x^e mod m + * Binary left-to-right method + * + * @param exp pointer to limb to receive result + * @param x single limb x (base) + * @param e single limb e (exponent) + * @param m single limb m (modulus) + * + * @result zero on success (always!?) + */ +int sl_modexp(limb_t *exp, limb_t x, limb_t e, limb_t m) { + limb_t mask; + limb_t y; /* Temp variable */ + + /* Find most significant bit in e */ + for (mask = DIGMSB; mask > 0; mask >>= 1) { + if (e & mask) { + break; + } + } + + y = x; + + for (mask >>= 1; mask > 0; mask >>= 1) { + sl_modmul(&y, y, y, m); /* y = (y^2) % m */ + if (e & mask) { + sl_modmul(&y, y, x, m); /* y = (y*x) % m*/ + } + } + + *exp = y; + return 0; +} + +/** + * @brief Compute single limb inverse inv = u^(-1) % v + * + * Computes inv = u^(-1) % v + * Ref: Knuth Algorithm X Vol 2 p 342 + * ignoring u2, v2, t2 and avoiding negative numbers + * + * @param inv pointer to limb to receive result + * @param u single limb to inverse + * @param v single limb modulus + * + * @result zero on success (always!?) + */ +int sl_modinv(limb_t *inv, limb_t u, limb_t v) { + limb_t u1, u3, v1, v3, t1, t3, q, w; + int iter = 1; + + /* Step X1. Initialize */ + u1 = 1; + u3 = u; + v1 = 0; + v3 = v; + + /* Step X2. */ + while (v3 != 0) { + /* Step X3. */ + q = u3 / v3; /* Divide and */ + t3 = u3 % v3; + w = q * v1; /* "Subtract" */ + t1 = u1 + w; + /* Swap */ + u1 = v1; + v1 = t1; + u3 = v3; + v3 = t3; + iter = -iter; + } + + if (iter < 0) { + *inv = v - u1; + } else { + *inv = u1; + } + + return 0; +} + +/** + * @brief Compute single limb a = (x * y) % mod + * + * Computes a = (x * y) % m + * + * @param a pointer to single limb to receive result + * @param x single limb factor 1 + * @param y single limb factor 2 + * @param m single limb modulus + * + * @result zero on success (always!?) + */ +int sl_modmul(limb_t *a, limb_t x, limb_t y, limb_t m) { + static IGRAPH_THREAD_LOCAL limb_t pp[2]; + + /* pp[] = x * y */ + sl_mul(pp, x, y); + + /* *a = pp[] % m */ + *a = bn_mod_limb(pp, m, 2); + + /* Clean temp */ + pp[0] = pp[1] = 0; + return 0; +} + +#if HAVE_U64 +/** + * @brief Compute double limb product of two single limbs + * + * Computes p[] = x * y + * where p is two limbs (double precision) and x, y are single + * limbs. Use double precision natively supported on this machine. + * + * @param p pointer to an array of two limbs receiving the result + * @param x single limb factor #1 + * @param y single limb factor #2 + * + * @result zero on success (always) + */ +int sl_mul(limb_t p[2], limb_t x, limb_t y) { + dlimb_t dd; + + dd = (dlimb_t)x * y; + p[0] = (limb_t)dd; + p[1] = (limb_t)(dd >> 32); + return 0; +} + +#else + +/** + * @brief Compute double limb product of two single limbs + * + * Computes p[] = x * y + * Source: Arbitrary Precision Computation + * http://numbers.computation.free.fr/Constants/constants.html + * + * The limbs x and y are split in halves and the four products + * x1*y1, x0*y1, x1*y0 and x0*y0 are added shifting them to + * their respective least significant bit position: + * p[1] = x1*y1 + high(x0*y1 + x1*y0) + ch << 16 + cl + * p[0] = x0*y0 + low(x0*y1 + x1*y0) << 16 + * ch = carry from adding x0*y1 + x1*y0 + * cl = carry from adding low(x0*y1 + x1*y0) << 16 to p[0] + * + * @param p pointer to an array of two limbs receiving the result + * @param x single limb factor #1 + * @param y single limb factor #2 + * + * @result zero on success (always) + */ +int sl_mul(limb_t p[2], limb_t x, limb_t y) { + limb_t x0, y0, x1, y1; + limb_t t, u, carry; + + /* + * Split each x,y into two halves + * x = x0 + B*x1 + * y = y0 + B*y1 + * where B = 2^16, half the limb size + * Product is + * xy = x0y0 + B(x0y1 + x1y0) + B^2(x1y1) + */ + x0 = LSH(x); + x1 = MSH(x); + y0 = LSH(y); + y1 = MSH(y); + + /* Compute low part (w/o carry) */ + p[0] = x0 * y0; + + /* middle part */ + t = x0 * y1; + u = x1 * y0; + t += u; + carry = (t < u ? 1 : 0); + + /* + * The carry will go to high half of p[1], + * and the high half of t will go into the + * into low half of p[1] + */ + carry = SHL(carry) + MSH(t); + + /* add low half of t to high half of p[0] */ + t = SHL(t); + p[0] += t; + if (p[0] < t) { + carry++; + } + + p[1] = x1 * y1 + carry; + + return 0; +} + +#endif /* HAVE_U64 */ + +/** + * @brief Compute division of big number by a "half digit" + * + * Computes q[] = u[] / v, also returns r = u[] % v + * where q, a are big number integers of nlimb limbs each, + * and d, r are single limbs + * + * Using bit-by-bit method from MSB to LSB, + * so v must be <= HALFMASK + * + * According to "Principles in PGP by Phil Zimmermann" + * + * @param q pointer to an array of limbs to receive the result + * @param u pointer to an array of limbs (dividend) + * @param v single limb (actually half limb) divisor + * @param nlimb number of limbs in the arrays + * + * @result returns remainder of the division + */ +limb_t bn_div_hdig(limb_t q[], limb_t u[], limb_t v, count_t nlimb) { + limb_t mask = DIGMSB; + limb_t r = 0; + if (v > HALFMASK) { + igraph_errorf("bn_div_hdig called with v:%x", __FILE__, + __LINE__, (int) v); + } + + if (0 == nlimb) { + return 0; + } + if (0 == v) { + return 0; /* Divide by zero error */ + } + + /* Initialize quotient */ + bn_zero(q, nlimb); + + /* Work from MSB to LSB */ + while (nlimb > 0) { + /* Multiply remainder by 2 */ + r <<= 1; + + /* Look at current bit */ + if (u[nlimb - 1] & mask) { + r++; + } + if (r >= v) { + /* Remainder became greater than divisor */ + r -= v; + q[nlimb - 1] |= mask; + } + + /* next bit */ + mask >>= 1; + if (0 != mask) { + continue; + } + + /* next limb */ + --nlimb; + mask = DIGMSB; + } + return r; +} + +/** + * @brief Compute single limb remainder of bignum % single limb + * + * Computes r = u[] % v + * where a is a big number integer of nlimb + * and r, v are single limbs, using bit-by-bit + * method from MSB to LSB. + * + * Ref: + * Derived from principles in PGP by Phil Zimmermann + * Note: + * This method will only work until r <<= 1 overflows. + * i.e. for d < DIGMSB, but we keep HALF_DIGIT + * limit for safety, and also because we don't + * have a 32nd bit. + * + * @param u pointer to big number to divide + * @param v single limb (actually half limb) modulus + * @param nlimb number of limbs in the array + * + * @result returns remainder of the division + */ +limb_t bn_mod_hdig(limb_t u[], limb_t v, count_t nlimb) { + limb_t mask; + limb_t r; + + if (0 == nlimb) { + return 0; + } + if (0 == v) { + return 0; /* Divide by zero error */ + } + + if (v > HALFMASK) { + igraph_errorf("bn_mod_hdig called with v:%x", __FILE__, + __LINE__, (int) v); + } + + /* Work from left to right */ + mask = DIGMSB; + r = 0; + while (nlimb > 0) { + /* Multiply remainder by 2 */ + r <<= 1; + + /* Look at current bit */ + if (u[nlimb - 1] & mask) { + r++; + } + + if (r >= v) + /* Remainder became greater than divisor */ + { + r -= v; + } + + /* next bit */ + mask >>= 1; + if (0 != mask) { + continue; + } + + /* next limb */ + --nlimb; + mask = DIGMSB; + } + return r; +} + +/** + * @brief Addition of two bignum arrays + * + * Computes w[] = u[] + v[] + * where w, u, v are big number integers of nlimb limbs each. + * Returns carry, i.e. w[nlimb], as 0 or 1. + * + * Ref: Knuth Vol 2 Ch 4.3.1 p 266 Algorithm A. + * + * @param w pointer to array of limbs to receive the result + * @param u pointer to array of limbs (addend #1) + * @param v pointer to array of limbs (addend #2) + * @param nlimb number of limbs in the arrays + * + * @result returns the carry, i.e. w[nlimb], as 0 or 1 + */ +limb_t bn_add(limb_t w[], limb_t u[], limb_t v[], count_t nlimb) { + limb_t carry; + count_t j; + + for (j = 0, carry = 0; j < nlimb; j++) { + /* + * add limbs w[j] = u[j] + v[j] + carry; + * set carry = 1 if carry (overflow) occurs + */ + w[j] = u[j] + carry; + carry = (w[j] < carry ? 1 : 0); + + w[j] = w[j] + v[j]; + if (w[j] < v[j]) { + carry++; + } + } + + /* w[n] = carry */ + return carry; +} + +/** + * @brief Subtraction of two bignum arrays + * + * Calculates w[] = u[] - v[] where u[] >= v[] + * w, u, v are big number integers of nlimb limbs each + * Returns 0 if ok, or 1 if v was greater than u. + * + * Ref: Knuth Vol 2 Ch 4.3.1 p 267 Algorithm S. + * + * @param w pointer to array of limbs to receive the result + * @param u pointer to array of limbs (minuend) + * @param v pointer to array of limbs (subtrahend) + * @param nlimb number of limbs in the arrays + * + * @result zero on success, 1 if v was greater than u + */ +limb_t bn_sub(limb_t w[], limb_t u[], limb_t v[], count_t nlimb) { + limb_t borrow; + count_t j; + + for (j = 0, borrow = 0; j < nlimb; j++) { + /* + * Subtract limbs w[j] = u[j] - v[j] - borrow; + * set borrow = 1 if borrow occurs + */ + w[j] = u[j] - borrow; + borrow = (w[j] > ~borrow ? 1 : 0); + + w[j] = w[j] - v[j]; + if (w[j] > ~v[j]) { + borrow++; + } + } + + /* borrow should be 0, if u >= v */ + return borrow; +} + +/** + * @brief Product of two bignum arrays + * + * Computes product w[] = u[] * v[] + * where u, v are big number integers of nlimb each + * and w is a big number integer of 2*nlimb limbs. + * + * Ref: Knuth Vol 2 Ch 4.3.1 p 268 Algorithm M. + * + * @param w pointer to array of limbs to receive the result + * @param u pointer to array of limbs (factor #1) + * @param v pointer to array of limbs (factor #2) + * @param nlimb number of limbs in the arrays + * + * @result zero on success (always!?) + */ +int bn_mul(limb_t w[], limb_t u[], limb_t v[], count_t nlimb) { + limb_t t[2]; + limb_t carry; + count_t i, j, m, n; + + m = n = nlimb; + + /* zero result */ + bn_zero(w, 2 * nlimb); + + for (j = 0; j < n; j++) { + /* zero multiplier? */ + if (0 == v[j]) { + w[j + m] = 0; + continue; + } + /* Initialize i */ + carry = 0; + for (i = 0; i < m; i++) { + /* + * Multiply and add: + * t = u[i] * v[j] + w[i+j] + carry + */ + sl_mul(t, u[i], v[j]); + + t[0] += carry; + if (t[0] < carry) { + t[1]++; + } + t[0] += w[i + j]; + if (t[0] < w[i + j]) { + t[1]++; + } + + w[i + j] = t[0]; + carry = t[1]; + } + w[j + m] = carry; + } + + return 0; +} + +/** + * @brief Shift left a bignum by a number of bits (less than LIMBBITS) + * + * Computes a[] = b[] << x + * Where a and b are big number integers of nlimb each. + * The shift count must be less than LIMBBITS + * + * @param a pointer to array of limbs to receive the result + * @param b pointer to array of limbs to shift left + * @param x number of bits to shift (must be less than LIMBBITS) + * @param nlimb number of limbs in the arrays + * + * @result returns a single limb "carry", i.e. bits that came out left + */ +limb_t bn_shl(limb_t a[], limb_t b[], count_t x, count_t nlimb) { + count_t i, y; + limb_t carry, temp; + + if (0 == nlimb) { + return 0; + } + + if (0 == x) { + /* no shift at all */ + if (a != b) { + bn_copy(a, b, nlimb); + } + return 0; + } + + /* check shift amount */ + if (x >= LIMBBITS) { + igraph_errorf("bn_shl() called with x >= %d", __FILE__, + __LINE__, LIMBBITS); + return 0; + } + + y = LIMBBITS - x; + carry = 0; + for (i = 0; i < nlimb; i++) { + temp = b[i] >> y; + a[i] = (b[i] << x) | carry; + carry = temp; + } + + return carry; +} + +/** + * @brief Shift right a bignum by a number of bits (less than LIMBBITS) + * + * Computes a[] = b[] >> x + * Where a and b are big number integers of nlimb each. + * The shift count must be less than LIMBBITS + * + * @param a pointer to array of limbs to receive the result + * @param b pointer to array of limbs to shift right + * @param x number of bits to shift (must be less than LIMBBITS) + * @param nlimb number of limbs in the arrays + * + * @result returns a single limb "carry", i.e. bits that came out right + */ +limb_t bn_shr(limb_t a[], limb_t b[], count_t x, count_t nlimb) { + count_t i, y; + limb_t carry, temp; + + if (0 == nlimb) { + return 0; + } + + if (0 == x) { + /* no shift at all */ + if (a != b) { + bn_copy(a, b, nlimb); + } + return 0; + } + + /* check shift amount */ + if (x >= LIMBBITS) { + igraph_errorf("bn_shr() called with x >= %d", __FILE__, + __LINE__, LIMBBITS); + } + + y = LIMBBITS - x; + carry = 0; + i = nlimb; + while (i-- > 0) { + temp = b[i] << y; + a[i] = (b[i] >> x) | carry; + carry = temp; + } + + return carry; +} + +/** + * @brief Check a quotient for overflow + * + * Returns 1 if quot is too big, + * i.e. if (quot * Vn-2) > (b.rem + Uj+n-2) + * Returns 0 if ok + * + * @param quot quotient under test + * @param rem remainder + * @param + * + * @result zero on success + */ +static int quot_overflow(limb_t quot, limb_t rem, limb_t v, limb_t u) { + limb_t t[2]; + + sl_mul(t, quot, v); + if (t[1] < rem) { + return 0; + } + if (t[1] > rem) { + return 1; + } + if (t[0] > u) { + return 1; + } + + return 0; +} + +/** + * @brief Compute quotient and remainder of bignum division + * + * Computes quotient q[] = u[] / v[] + * and remainder r[] = u[] % v[] + * where q, r, u are big number integers of ulimb limbs, + * and the divisor v of vlimb limbs. + * + * Ref: Knuth Vol 2 Ch 4.3.1 p 272 Algorithm D. + * + * @param q pointer to array of limbs to receive quotient + * @param r pointer to array of limbs to receive remainder + * @param u pointer to array of limbs (dividend) + * @param ulimb number of limbs in the q, r, u arrays + * @param v pointer to array of limbs (divisor) + * @param vlimb number of limbs in the v array + * + * @result zero on success, LIMBASK on division by zero + */ +int bn_div(limb_t q[], limb_t r[], limb_t u[], limb_t v[], + count_t ulimb, count_t vlimb) { + static IGRAPH_THREAD_LOCAL limb_t qq[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t uu[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t vv[BN_MAXSIZE]; + limb_t mask; + limb_t overflow; + limb_t quot; + limb_t rem; + limb_t t[2]; + limb_t *ww; + count_t n, m, i, j, shift; + int ok, cmp; + + /* find size of v */ + n = bn_sizeof(v, vlimb); + + /* Catch special cases */ + if (0 == n) { + return (int) LIMBMASK; /* Error: divide by zero */ + } + + if (1 == n) { + /* Use short division instead */ + r[0] = bn_div_limb(q, u, v[0], ulimb); + return 0; + } + + /* find size of u */ + m = bn_sizeof(u, ulimb); + + if (m < n) { + /* v > u: just set q = 0 and r = u */ + bn_zero(q, ulimb); + bn_copy(r, u, ulimb); + return 0; + } + + if (m == n) { + /* u and v are the same length: compare them */ + cmp = bn_cmp(u, v, (unsigned int)n); + if (0 == cmp) { + /* v == u: set q = 1 and r = 0 */ + bn_limb(q, 1, ulimb); + bn_zero(r, ulimb); + return 0; + } + if (cmp < 0) { + /* v > u: set q = 0 and r = u */ + bn_zero(q, ulimb); + bn_copy(r, u, ulimb); + return 0; + } + } + + /* m greater than or equal to n */ + m -= n; + + /* clear quotient qq */ + bn_zero(qq, ulimb); + + /* + * Normalize v: requires high bit of v[n-1] to be set, + * so find most significant bit, then shift left + */ + mask = DIGMSB; + for (shift = 0; shift < LIMBBITS; shift++) { + if (v[n - 1] & mask) { + break; + } + mask >>= 1; + } + + /* normalize vv from v */ + overflow = bn_shl(vv, v, shift, n); + + /* copy normalized dividend u into remainder uu */ + overflow = bn_shl(uu, u, shift, n + m); + + /* new limb u[m+n] */ + t[0] = overflow; + + j = m + 1; + while (j-- > 0) { + /* quot = (b * u[j+n] + u[j+n-1]) / v[n-1] */ + ok = 0; + + /* This is Uj+n */ + t[1] = t[0]; + t[0] = uu[j + n - 1]; + + overflow = sl_div(", &rem, t, vv[n - 1]); + + if (overflow) { + /* quot = b */ + quot = LIMBMASK; + rem = uu[j + n - 1] + vv[n - 1]; + if (rem < vv[n - 1]) { + ok = 1; + } + } + if (0 == ok && quot_overflow(quot, rem, vv[n - 2], uu[j + n - 2])) { + /* quot * v[n-2] > b * rem + u[j+n-2] */ + quot--; + rem += vv[n - 1]; + if (rem >= vv[n - 1]) + if (quot_overflow(quot, rem, vv[n - 2], uu[j + n - 2])) { + quot--; + } + } + + /* multiply and subtract vv[] * quot */ + ww = &uu[j]; + + if (0 == quot) { + overflow = 0; + } else { + /* quot is non zero */ + limb_t tt[2]; + limb_t borrow; + + for (i = 0, borrow = 0; i < n; i++) { + sl_mul(tt, quot, vv[i]); + ww[i] -= borrow; + borrow = (ww[i] > ~borrow ? 1 : 0); + + ww[i] -= tt[0]; + if (ww[i] > ~tt[0]) { + borrow++; + } + borrow += tt[1]; + } + + /* + * w[n] is not in array w[0..n-1]: + * subtract final borrow + */ + overflow = t[1] - borrow; + } + + /* test for remainder */ + if (overflow) { + quot--; + /* add back if mul/sub was negative */ + overflow = bn_add(ww, ww, vv, n); + } + + qq[j] = quot; + + /* u[j+n] for next round */ + t[0] = uu[j + n - 1]; + } + + /* clear uu[] limbs from n to n+m */ + for (j = n; j < m + n; j++) { + uu[j] = 0; + } + + /* denormalize remainder */ + bn_shr(r, uu, shift, n); + + /* copy quotient */ + bn_copy(q, qq, n + m); + + /* clear temps */ + bn_zero(qq, n); + bn_zero(uu, n); + bn_zero(vv, n); + return 0; +} + +/** + * @brief Compute remainder of bignum division (modulo) + * + * Calculates r[] = u[] % v[] + * where r, v are big number integers of length vlimb + * and u is a big number integer of length ulimb. + * r may overlap v. + * + * Note that r here is only vlimb long, + * whereas in bn_div it is ulimb long. + * + * Use remainder from bn_div function. + * + * @param r pointer to array of limbs to receive remainder + * @param u pointer to array of limbs (dividend) + * @param ulimb number of limbs in the u array + * @param v pointer to array of limbs (divisor) + * @param vlimb number of limbs in the r and v array + * + * @result zero on success, LIMBASK on division by zero + */ +limb_t bn_mod(limb_t r[], limb_t u[], count_t ulimb, limb_t v[], count_t vlimb) { + static IGRAPH_THREAD_LOCAL limb_t qq[2 * BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t rr[2 * BN_MAXSIZE]; + limb_t d0; + + /* rr[] = u[] % v[n] */ + d0 = (limb_t) bn_div(qq, rr, u, v, ulimb, vlimb); + + /* copy vlimb limbs of remainder */ + bn_copy(r, rr, vlimb); + + /* zero temps */ + bn_zero(rr, ulimb); + bn_zero(qq, ulimb); + + return d0; +} + +/** + * @brief Compute greatest common divisor + * + * Computes g = gcd(x, y) + * Reference: Schneier + * + * @param g pointer to array of limbs to receive the gcd + * @param x pointer to array of limbs (candidate #1) + * @param y pointer to array of limbs (candidate #2) + * @param nlimb number of limbs in the arrays + * + * @result zero on succes (always) + */ +int bn_gcd(limb_t g[], limb_t x[], limb_t y[], count_t nlimb) { + static IGRAPH_THREAD_LOCAL limb_t yy[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t xx[BN_MAXSIZE]; + + bn_copy(xx, x, nlimb); + bn_copy(yy, y, nlimb); + + /* g = y */ + bn_copy(g, yy, nlimb); + + /* while (x > 0) { */ + while (0 != bn_cmp_limb(xx, 0, nlimb)) { + /* g = x */ + bn_copy(g, xx, nlimb); + /* x = y % x */ + bn_mod(xx, yy, nlimb, xx, nlimb); + /* y = g */ + bn_copy(yy, g, nlimb); + } + + bn_zero(xx, nlimb); + bn_zero(yy, nlimb); + + /* gcd is left in g */ + return 0; +} + +/** + * @brief Compute modular exponentiation of bignums + * + * Computes y[] = (x[]^e[]) % m[] + * Binary MSB to LSB method + * + * @param y pointer to array of limbs to receive the result + * @param x pointer to array of limbs (base) + * @param e pointer to array of limbs (exponent) + * @param m pointer to array of limbs (modulus) + * @param nlimb number of limbs in the arrays + * + * @result zero on success, -1 on error (nlimb is zero) + */ +int bn_modexp(limb_t y[], limb_t x[], limb_t e[], limb_t m[], count_t nlimb) { + limb_t mask; + count_t n; + + if (nlimb == 0) { + return -1; + } + + /* Find second-most significant bit in e */ + n = bn_sizeof(e, nlimb); + for (mask = DIGMSB; 0 != mask; mask >>= 1) { + if (e[n - 1] & mask) { + break; + } + } + /* next bit, because we start off with y[] == x[] */ + mask >>= 1; + if (0 == mask) { + mask = DIGMSB; + n--; + } + + /* y[] = x[] */ + bn_copy(y, x, nlimb); + + while (n > 0) { + /* y[] = (y[] ^ 2) % m[] */ + bn_modmul(y, y, y, m, nlimb); + + if (e[n - 1] & mask) + /* y[] = (y[] * x[]) % m[] */ + { + bn_modmul(y, y, x, m, nlimb); + } + + /* next bit */ + mask >>= 1; + if (0 == mask) { + mask = DIGMSB; + n--; + } + } + + return 0; +} + +/** + * @brief Compute modular product of two bignums + * + * Computes a[] = (x[] * y[]) % m[] + * where a, x, y and m are big numbers of nlimb length + * + * @param a pointer to array of limbs to receive the result + * @param x pointer to array of limbs (factor #1) + * @param y pointer to array of limbs (factor #2) + * @param m pointer to array of limbs (modulus) + * @param nlimb number of limbs in the arrays + * + * @result zero on success, LIMBMASK if m was zero (division by zero) + */ +limb_t bn_modmul(limb_t a[], limb_t x[], limb_t y[], limb_t m[], count_t nlimb) { + static IGRAPH_THREAD_LOCAL limb_t pp[2 * BN_MAXSIZE]; + limb_t d0; + + /* pp[] = x[] * y[] (NB: double size pp[]) */ + bn_mul(pp, x, y, nlimb); + + /* a[] = pp[] % m[] */ + d0 = bn_mod(a, pp, 2 * nlimb, m, nlimb); + + /* zero temp */ + bn_zero(pp, 2 * nlimb); + + return d0; +} + +/** + * @brief Compute modular inverse + * + * Computes inv[] = u[]^(-1) % v[] + * Ref: Knuth Algorithm X Vol 2 p 342 + * ignoring u2, v2, t2 and avoiding negative numbers. + * + * @param inv pointer to array of limbs receiving the result + * @param u pointer to array of limbs (candidate) + * @param v pointer to array of limbs (modulus) + * @param nlimb number of limbs in the arrays + * + * @result zero on success + */ +int bn_modinv(limb_t inv[], limb_t u[], limb_t v[], count_t nlimb) { + /* Allocate temp variables */ + static IGRAPH_THREAD_LOCAL limb_t u1[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t u3[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t v1[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t v3[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t t1[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t t3[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t q[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t w[2 * BN_MAXSIZE]; + int iter; + + /* Step X1. Initialize */ + bn_limb(u1, 1, nlimb); /* u1 = 1 */ + bn_limb(v1, 0, nlimb); /* v1 = 0 */ + bn_copy(u3, u, nlimb); /* u3 = u */ + bn_copy(v3, v, nlimb); /* v3 = v */ + + /* remember odd/even iterations */ + iter = 1; + + /* Step X2. Loop while v3 != 0 */ + while (0 != bn_cmp_limb(v3, 0, nlimb)) { + /* Step X3. Divide and "Subtract" */ + /* q = u3 / v3, t3 = u3 % v3 */ + bn_div(q, t3, u3, v3, nlimb, nlimb); + /* w = q * v1 */ + bn_mul(w, q, v1, nlimb); + /* t1 = u1 + w */ + bn_add(t1, u1, w, nlimb); + + /* Swap u1 <= v1 <= t1 */ + bn_copy(u1, v1, nlimb); + bn_copy(v1, t1, nlimb); + + /* Swap u3 <= v3 <= t3 */ + bn_copy(u3, v3, nlimb); + bn_copy(v3, t3, nlimb); + + iter ^= 1; + } + + if (iter) { + bn_copy(inv, u1, nlimb); /* inv = u1 */ + } else { + bn_sub(inv, v, u1, nlimb); /* inv = v - u1 */ + } + + /* clear temp vars */ + bn_zero(u1, nlimb); + bn_zero(v1, nlimb); + bn_zero(t1, nlimb); + bn_zero(u3, nlimb); + bn_zero(v3, nlimb); + bn_zero(t3, nlimb); + bn_zero(q, nlimb); + bn_zero(w, 2 * nlimb); + + return 0; +} + +/** + * @brief Compute square root (and fraction) of a bignum + * + * Compute q[] = sqrt(u[]), + * where q and u are big number integers of nlimb limbs + * + * Method according to sqrt.html of 2001-08-15: + * Act on bytes from MSB to LSB, counting the number of times + * that we can subtract consecutive odd numbers starting with + * 1, 3, 5. Just uses add, subtract, shift and comparisons. + * + * The pointer r can be NULL if caller is not interested in + * the (partial) fraction. + * + * @param q pointer to array of limbs to receive the result (integer) + * @param r pointer to array of limbs to receive the result (fraction) + * @param u pointer to array of limbs (square) + * @param rlimb number of limbs in the q and r arrays + * @param ulimb number of limbs in the u array + * + * @result zero on success + */ +int bn_sqrt(limb_t q[], limb_t r[], limb_t u[], count_t rlimb, count_t ulimb) { + static IGRAPH_THREAD_LOCAL limb_t step[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t accu[BN_MAXSIZE]; + static IGRAPH_THREAD_LOCAL limb_t w[2 * BN_MAXSIZE]; + limb_t d; + count_t m, n; + count_t shift; + + bn_zero(q, ulimb); + bn_limb(step, 1, BN_MAXSIZE); + bn_limb(accu, 0, BN_MAXSIZE); + n = bn_sizeof(u, ulimb); + + /* determine first non-zero byte from MSB to LSB */ + if (0 != (u[n - 1] >> 24)) { + shift = 32; + } else if (0 != (u[n - 1] >> 16)) { + shift = 24; + } else if (0 != (u[n - 1] >> 8)) { + shift = 16; + } else { + shift = 8; + } + + m = 1; + while (n-- > 0) { + while (shift > 0) { + /* shift accu one byte left */ + bn_shl(accu, accu, 8, m + 1); + + /* shift for next byte from u[] */ + shift -= 8; + accu[0] |= (u[n] >> shift) & 0xff; + + /* digit = 0 */ + d = 0; + /* subtract consecutive odd numbers step[] until overflow */ + for (d = 0; bn_cmp(step, accu, m + 1) <= 0; d++) { + bn_sub(accu, accu, step, m + 1); + bn_add_limb(step, step, 2, m + 1); + } + + /* put digit into result */ + bn_shl(q, q, 4, m); + q[0] |= d; + + /* step[] = 2 * q[] * 16 + 1 */ + bn_shl(step, q, 5, m + 1); + bn_add_limb(step, step, 1, m + 1); + } + shift = 32; + if (0 == (n & 1)) { + m++; + } + } + + /* Caller does not want to know the fraction? */ + if (NULL == r) { + return 0; + } + + /* nothing left to do if remainder is zero */ + if (0 == bn_cmp_limb(accu, 0, ulimb)) { + bn_zero(r, rlimb); + return 0; + } + + /* Start off with the integer part */ + bn_zero(w, 2 * BN_MAXSIZE); + bn_copy(w, q, ulimb); + + n = rlimb * (LIMBBITS / 4); + while (n-- > 0) { + /* shift accu one byte left */ + bn_shl(accu, accu, 8, rlimb); + + /* subtract consecutive odd numbers step[] until overflow */ + for (d = 0; bn_cmp(step, accu, rlimb) <= 0; d++) { + bn_sub(accu, accu, step, rlimb); + bn_add_limb(step, step, 2, rlimb); + } + + /* put digit into result */ + bn_shl(w, w, 4, rlimb); + w[0] |= d; + + /* step[] = 2 * w[] * 16 + 1 */ + bn_shl(step, w, 5, rlimb); + bn_add_limb(step, step, 1, rlimb); + } + + /* copy remainder */ + bn_copy(r, w, rlimb); + return 0; +} diff --git a/src/bignum.h b/src/bignum.h new file mode 100644 index 0000000..00102b0 --- /dev/null +++ b/src/bignum.h @@ -0,0 +1,125 @@ +/***************************************************************************** + * Entropy - Emerging Network To Reduce Orwellian Potency Yield + * + * Copyright (C) 2005 Juergen Buchmueller + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + * $Id: bignum.h,v 1.6 2005/08/11 17:57:39 pullmoll Exp $ + *****************************************************************************/ +#ifndef _bignum_h_ +#define _bignum_h_ + +#include "config.h" +#ifdef HAVE_STDINT_H + #include +#else + #ifdef HAVE_SYS_INT_TYPES_H + #include + #else + #include "pstdint.h" + #endif +#endif +#include +#include +#include + +#ifndef NULL + #define NULL 0 +#endif + +#ifndef O_BINARY + #define O_BINARY 0 +#endif + +#ifndef HAVE_U64 + #define HAVE_U64 1 +#endif + +/* up to 512 limbs (512 * 32 = 16384 bits) numbers */ +/* BN_MAXSIZE used to be 512 here, allowing us to go up to 512*32 = 16384 bits. + * However, this has caused compilation problems with clang 7.3 (unless + * compiling with -O2 -g). Since it is unlikely that we'll need that many bits, + * I have changed this to 128, which still yields 4096 bits of precision but + * does not cause problems with clang -- TN, 2016-04-18 */ +#define BN_MAXSIZE 128 +#define LIMBBITS 32 +#define LIMBMASK 0xfffffffful +#define HALFMASK 0x0000fffful +#define DIGMSB 0x80000000ul +#define DIGLSB 0x00000001ul + +typedef uint32_t count_t; +typedef uint16_t half_t; +typedef uint32_t limb_t; +#if HAVE_U64 + typedef uint64_t dlimb_t; +#endif + +/* less significant half limb */ +#define LSH(d) ((half_t)(d)) +/* more significant half limb */ +#define MSH(d) ((limb_t)(d)>>16) +/* shift left half limb */ +#define SHL(d) ((limb_t)(d)<<16) + +/* single limb functions */ +limb_t sl_div(limb_t *q, limb_t *r, limb_t u[2], limb_t v); +limb_t sl_gcd(limb_t x, limb_t y); +int sl_modexp(limb_t *exp, limb_t x, limb_t n, limb_t d); +int sl_modinv(limb_t *inv, limb_t u, limb_t v); +int sl_modmul(limb_t *a, limb_t x, limb_t y, limb_t m); +int sl_mul(limb_t p[2], limb_t x, limb_t y); + +/* big number functions (max. MAXSIZE limbs) */ +void bn_zero(limb_t a[], count_t nlimb); +void bn_limb(limb_t a[], limb_t d, count_t nlimb); +void bn_copy(limb_t a[], limb_t b[], count_t nlimb); +count_t bn_sizeof(limb_t a[], count_t nlimb); +int bn_cmp_limb(limb_t a[], limb_t b, count_t nlimb); +int bn_cmp(limb_t a[], limb_t b[], count_t nlimb); + +/* big number to hex, decimal, binary */ +const char *bn2x(limb_t a[], count_t nlimb); +const char *bn2d(limb_t a[], count_t nlimb); +const char *bn2f(limb_t a[], count_t alimb, limb_t b[], count_t blimb); +const char *bn2b(limb_t a[], count_t nlimb); + +/* big number with single limb operations */ +limb_t bn_add_limb(limb_t w[], limb_t u[], limb_t v, count_t nlimb); +limb_t bn_sub_limb(limb_t w[], limb_t u[], limb_t v, count_t nlimb); +limb_t bn_div_limb(limb_t q[], limb_t u[], limb_t v, count_t nlimb); +limb_t bn_mod_limb(limb_t u[], limb_t d, count_t nlimb); +limb_t bn_mul_limb(limb_t w[], limb_t u[], limb_t v, count_t nlimb); + +/* big number with single limb <= HALFMASK operations */ +limb_t bn_div_half(limb_t q[], limb_t u[], limb_t v, count_t nlimb); +limb_t bn_mod_half(limb_t a[], limb_t d, count_t nlimb); + +/* big number operations */ +limb_t bn_add(limb_t w[], limb_t u[], limb_t v[], count_t nlimb); +limb_t bn_sub(limb_t w[], limb_t u[], limb_t v[], count_t nlimb); +limb_t bn_shl(limb_t a[], limb_t b[], count_t x, count_t nlimb); +limb_t bn_shr(limb_t a[], limb_t b[], count_t x, count_t nlimb); +int bn_mul(limb_t w[], limb_t u[], limb_t v[], count_t nlimb); +int bn_div(limb_t q[], limb_t r[], limb_t u[], limb_t v[], count_t ulimb, count_t vlimb); +limb_t bn_mod(limb_t r[], limb_t u[], count_t ulimb, limb_t v[], count_t vlimb); +int bn_gcd(limb_t g[], limb_t x[], limb_t y[], count_t nlimb); +int bn_sqrt(limb_t g[], limb_t x[], limb_t y[], count_t rlimb, count_t nlimb); +int bn_modexp(limb_t y[], limb_t x[], limb_t e[], limb_t m[], count_t nlimb); +int bn_modinv(limb_t inv[], limb_t u[], limb_t v[], count_t nlimb); +limb_t bn_modmul(limb_t a[], limb_t x[], limb_t y[], limb_t m[], count_t nlimb); + +#endif /* !defined(_bignum_h_) */ diff --git a/src/bipartite.c b/src/bipartite.c new file mode 100644 index 0000000..9f2f683 --- /dev/null +++ b/src/bipartite.c @@ -0,0 +1,1147 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2008-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_bipartite.h" +#include "igraph_attributes.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_constructors.h" +#include "igraph_dqueue.h" +#include "igraph_random.h" +#include "igraph_nongraph.h" + +/** + * \section about_bipartite Bipartite networks in igraph + * + * + * A bipartite network contains two kinds of vertices and connections + * are only possible between two vertices of different kind. There are + * many natural examples, e.g. movies and actors as vertices and a + * movie is connected to all participating actors, etc. + * + * + * igraph does not have direct support for bipartite networks, at + * least not at the C language level. In other words the igraph_t + * structure does not contain information about the vertex types. + * The C functions for bipartite networks usually have an additional + * input argument to graph, called \c types, a boolean vector giving + * the vertex types. + * + * + * Most functions creating bipartite networks are able to create this + * extra vector, you just need to supply an initialized boolean vector + * to them. + */ + +/** + * \function igraph_bipartite_projection_size + * Calculate the number of vertices and edges in the bipartite projections + * + * This function calculates the number of vertices and edges in the + * two projections of a bipartite network. This is useful if you have + * a big bipartite network and you want to estimate the amount of + * memory you would need to calculate the projections themselves. + * + * \param graph The input graph. + * \param types Boolean vector giving the vertex types of the graph. + * \param vcount1 Pointer to an \c igraph_integer_t, the number of + * vertices in the first projection is stored here. + * \param ecount1 Pointer to an \c igraph_integer_t, the number of + * edges in the first projection is stored here. + * \param vcount2 Pointer to an \c igraph_integer_t, the number of + * vertices in the second projection is stored here. + * \param ecount2 Pointer to an \c igraph_integer_t, the number of + * edges in the second projection is stored here. + * \return Error code. + * + * \sa \ref igraph_bipartite_projection() to calculate the actual + * projection. + * + * Time complexity: O(|V|*d^2+|E|), |V| is the number of vertices, |E| + * is the number of edges, d is the average (total) degree of the + * graphs. + * + * \example examples/simple/igraph_bipartite_projection.c + */ + +int igraph_bipartite_projection_size(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_integer_t *vcount1, + igraph_integer_t *ecount1, + igraph_integer_t *vcount2, + igraph_integer_t *ecount2) { + + long int no_of_nodes = igraph_vcount(graph); + long int vc1 = 0, ec1 = 0, vc2 = 0, ec2 = 0; + igraph_adjlist_t adjlist; + igraph_vector_long_t added; + long int i; + + IGRAPH_CHECK(igraph_vector_long_init(&added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &added); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + for (i = 0; i < no_of_nodes; i++) { + igraph_vector_int_t *neis1; + long int neilen1, j; + long int *ecptr; + if (VECTOR(*types)[i]) { + vc2++; + ecptr = &ec2; + } else { + vc1++; + ecptr = &ec1; + } + neis1 = igraph_adjlist_get(&adjlist, i); + neilen1 = igraph_vector_int_size(neis1); + for (j = 0; j < neilen1; j++) { + long int k, neilen2, nei = (long int) VECTOR(*neis1)[j]; + igraph_vector_int_t *neis2 = igraph_adjlist_get(&adjlist, nei); + if (IGRAPH_UNLIKELY(VECTOR(*types)[i] == VECTOR(*types)[nei])) { + IGRAPH_ERROR("Non-bipartite edge found in bipartite projection", + IGRAPH_EINVAL); + } + neilen2 = igraph_vector_int_size(neis2); + for (k = 0; k < neilen2; k++) { + long int nei2 = (long int) VECTOR(*neis2)[k]; + if (nei2 <= i) { + continue; + } + if (VECTOR(added)[nei2] == i + 1) { + continue; + } + VECTOR(added)[nei2] = i + 1; + (*ecptr)++; + } + } + } + + *vcount1 = (igraph_integer_t) vc1; + *ecount1 = (igraph_integer_t) ec1; + *vcount2 = (igraph_integer_t) vc2; + *ecount2 = (igraph_integer_t) ec2; + + igraph_adjlist_destroy(&adjlist); + igraph_vector_long_destroy(&added); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +static int igraph_i_bipartite_projection(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_t *proj, + int which, + igraph_vector_t *multiplicity) { + + long int no_of_nodes = igraph_vcount(graph); + long int i, j, k; + igraph_integer_t remaining_nodes = 0; + igraph_vector_t vertex_perm, vertex_index; + igraph_vector_t edges; + igraph_adjlist_t adjlist; + igraph_vector_int_t *neis1, *neis2; + long int neilen1, neilen2; + igraph_vector_long_t added; + igraph_vector_t mult; + + if (which < 0) { + return 0; + } + + IGRAPH_VECTOR_INIT_FINALLY(&vertex_perm, 0); + IGRAPH_CHECK(igraph_vector_reserve(&vertex_perm, no_of_nodes)); + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&vertex_index, no_of_nodes); + IGRAPH_CHECK(igraph_vector_long_init(&added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &added); + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + if (multiplicity) { + IGRAPH_VECTOR_INIT_FINALLY(&mult, no_of_nodes); + igraph_vector_clear(multiplicity); + } + + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i] == which) { + VECTOR(vertex_index)[i] = ++remaining_nodes; + igraph_vector_push_back(&vertex_perm, i); + } + } + + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i] == which) { + long int new_i = (long int) VECTOR(vertex_index)[i] - 1; + long int iedges = 0; + neis1 = igraph_adjlist_get(&adjlist, i); + neilen1 = igraph_vector_int_size(neis1); + for (j = 0; j < neilen1; j++) { + long int nei = (long int) VECTOR(*neis1)[j]; + if (IGRAPH_UNLIKELY(VECTOR(*types)[i] == VECTOR(*types)[nei])) { + IGRAPH_ERROR("Non-bipartite edge found in bipartite projection", + IGRAPH_EINVAL); + } + neis2 = igraph_adjlist_get(&adjlist, nei); + neilen2 = igraph_vector_int_size(neis2); + for (k = 0; k < neilen2; k++) { + long int nei2 = (long int) VECTOR(*neis2)[k], new_nei2; + if (nei2 <= i) { + continue; + } + if (VECTOR(added)[nei2] == i + 1) { + if (multiplicity) { + VECTOR(mult)[nei2] += 1; + } + continue; + } + VECTOR(added)[nei2] = i + 1; + if (multiplicity) { + VECTOR(mult)[nei2] = 1; + } + iedges++; + + IGRAPH_CHECK(igraph_vector_push_back(&edges, new_i)); + if (multiplicity) { + /* If we need the multiplicity as well, then we put in the + old vertex ids here and rewrite it later */ + IGRAPH_CHECK(igraph_vector_push_back(&edges, nei2)); + } else { + new_nei2 = (long int) VECTOR(vertex_index)[nei2] - 1; + IGRAPH_CHECK(igraph_vector_push_back(&edges, new_nei2)); + } + } + } + if (multiplicity) { + /* OK, we need to go through all the edges added for vertex new_i + and check their multiplicity */ + long int now = igraph_vector_size(&edges); + long int from = now - iedges * 2; + for (j = from; j < now; j += 2) { + long int nei2 = (long int) VECTOR(edges)[j + 1]; + long int new_nei2 = (long int) VECTOR(vertex_index)[nei2] - 1; + long int m = (long int) VECTOR(mult)[nei2]; + VECTOR(edges)[j + 1] = new_nei2; + IGRAPH_CHECK(igraph_vector_push_back(multiplicity, m)); + } + } + } /* if VECTOR(*type)[i] == which */ + } + + if (multiplicity) { + igraph_vector_destroy(&mult); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_adjlist_destroy(&adjlist); + igraph_vector_long_destroy(&added); + igraph_vector_destroy(&vertex_index); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_create(proj, &edges, remaining_nodes, + /*directed=*/ 0)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_destroy, proj); + + IGRAPH_I_ATTRIBUTE_DESTROY(proj); + IGRAPH_I_ATTRIBUTE_COPY(proj, graph, 1, 0, 0); + IGRAPH_CHECK(igraph_i_attribute_permute_vertices(graph, proj, &vertex_perm)); + igraph_vector_destroy(&vertex_perm); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_bipartite_projection + * Create one or both projections of a bipartite (two-mode) network + * + * Creates one or both projections of a bipartite graph. + * \param graph The bipartite input graph. Directedness of the edges + * is ignored. + * \param types Boolean vector giving the vertex types of the graph. + * \param proj1 Pointer to an uninitialized graph object, the first + * projection will be created here. It a null pointer, then it is + * ignored, see also the \p probe1 argument. + * \param proj2 Pointer to an uninitialized graph object, the second + * projection is created here, if it is not a null pointer. See also + * the \p probe1 argument. + * \param multiplicity1 Pointer to a vector, or a null pointer. If not + * the latter, then the multiplicity of the edges is stored + * here. E.g. if there is an A-C-B and also an A-D-B triple in the + * bipartite graph (but no more X, such that A-X-B is also in the + * graph), then the multiplicity of the A-B edge in the projection + * will be 2. + * \param multiplicity2 The same as \c multiplicity1, but for the + * other projection. + * \param probe1 This argument can be used to specify the order of the + * projections in the resulting list. When it is non-negative, then + * it is considered as a vertex ID and the projection containing + * this vertex will be the first one in the result. Setting this + * argument to a non-negative value implies that \c proj1 must be + * a non-null pointer. If you don't care about the ordering of the + * projections, pass -1 here. + * \return Error code. + * + * \sa \ref igraph_bipartite_projection_size() to calculate the number + * of vertices and edges in the projections, without creating the + * projection graphs themselves. + * + * Time complexity: O(|V|*d^2+|E|), |V| is the number of vertices, |E| + * is the number of edges, d is the average (total) degree of the + * graphs. + * + * \example examples/simple/igraph_bipartite_projection.c + */ + +int igraph_bipartite_projection(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_t *proj1, + igraph_t *proj2, + igraph_vector_t *multiplicity1, + igraph_vector_t *multiplicity2, + igraph_integer_t probe1) { + + long int no_of_nodes = igraph_vcount(graph); + + /* t1 is -1 if proj1 is omitted, it is 0 if it belongs to type zero, + it is 1 if it belongs to type one. The same for t2 */ + int t1, t2; + + if (igraph_vector_bool_size(types) != no_of_nodes) { + IGRAPH_ERROR("Invalid bipartite type vector size", IGRAPH_EINVAL); + } + + if (probe1 >= no_of_nodes) { + IGRAPH_ERROR("No such vertex to probe", IGRAPH_EINVAL); + } + + if (probe1 >= 0 && !proj1) { + IGRAPH_ERROR("`probe1' given, but `proj1' is a null pointer", IGRAPH_EINVAL); + } + + if (probe1 >= 0) { + t1 = VECTOR(*types)[(long int)probe1]; + if (proj2) { + t2 = 1 - t1; + } else { + t2 = -1; + } + } else { + t1 = proj1 ? 0 : -1; + t2 = proj2 ? 1 : -1; + } + + IGRAPH_CHECK(igraph_i_bipartite_projection(graph, types, proj1, t1, multiplicity1)); + IGRAPH_FINALLY(igraph_destroy, proj1); + IGRAPH_CHECK(igraph_i_bipartite_projection(graph, types, proj2, t2, multiplicity2)); + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + + +/** + * \function igraph_full_bipartite + * Create a full bipartite network + * + * A bipartite network contains two kinds of vertices and connections + * are only possible between two vertices of different kind. There are + * many natural examples, e.g. movies and actors as vertices and a + * movie is connected to all participating actors, etc. + * + * + * igraph does not have direct support for bipartite networks, at + * least not at the C language level. In other words the igraph_t + * structure does not contain information about the vertex types. + * The C functions for bipartite networks usually have an additional + * input argument to graph, called \c types, a boolean vector giving + * the vertex types. + * + * + * Most functions creating bipartite networks are able to create this + * extra vector, you just need to supply an initialized boolean vector + * to them. + * + * \param graph Pointer to an igraph_t object, the graph will be + * created here. + * \param types Pointer to a boolean vector. If not a null pointer, + * then the vertex types will be stored here. + * \param n1 Integer, the number of vertices of the first kind. + * \param n2 Integer, the number of vertices of the second kind. + * \param directed Boolean, whether to create a directed graph. + * \param mode A constant that gives the type of connections for + * directed graphs. If \c IGRAPH_OUT, then edges point from vertices + * of the first kind to vertices of the second kind; if \c + * IGRAPH_IN, then the opposite direction is realized; if \c + * IGRAPH_ALL, then mutual edges will be created. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \sa \ref igraph_full() for non-bipartite full graphs. + */ + +int igraph_full_bipartite(igraph_t *graph, + igraph_vector_bool_t *types, + igraph_integer_t n1, igraph_integer_t n2, + igraph_bool_t directed, + igraph_neimode_t mode) { + + igraph_integer_t nn1 = n1, nn2 = n2; + igraph_integer_t no_of_nodes = nn1 + nn2; + igraph_vector_t edges; + long int no_of_edges; + long int ptr = 0; + long int i, j; + + if (!directed) { + no_of_edges = nn1 * nn2; + } else if (mode == IGRAPH_OUT || mode == IGRAPH_IN) { + no_of_edges = nn1 * nn2; + } else { /* mode==IGRAPH_ALL */ + no_of_edges = nn1 * nn2 * 2; + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + + if (!directed || mode == IGRAPH_OUT) { + + for (i = 0; i < nn1; i++) { + for (j = 0; j < nn2; j++) { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = nn1 + j; + } + } + + } else if (mode == IGRAPH_IN) { + + for (i = 0; i < nn1; i++) { + for (j = 0; j < nn2; j++) { + VECTOR(edges)[ptr++] = nn1 + j; + VECTOR(edges)[ptr++] = i; + } + } + + } else { + + for (i = 0; i < nn1; i++) { + for (j = 0; j < nn2; j++) { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = nn1 + j; + VECTOR(edges)[ptr++] = nn1 + j; + VECTOR(edges)[ptr++] = i; + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_destroy, graph); + + if (types) { + IGRAPH_CHECK(igraph_vector_bool_resize(types, no_of_nodes)); + igraph_vector_bool_null(types); + for (i = nn1; i < no_of_nodes; i++) { + VECTOR(*types)[i] = 1; + } + } + + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_create_bipartite + * Create a bipartite graph + * + * This is a simple wrapper function to create a bipartite graph. It + * does a little more than \ref igraph_create(), e.g. it checks that + * the graph is indeed bipartite with respect to the given \p types + * vector. If there is an edge connecting two vertices of the same + * kind, then an error is reported. + * \param graph Pointer to an uninitialized graph object, the result is + * created here. + * \param types Boolean vector giving the vertex types. The length of + * the vector defines the number of vertices in the graph. + * \param edges Vector giving the edges of the graph. The highest + * vertex id in this vector must be smaller than the length of the + * \p types vector. + * \param directed Boolean scalar, whether to create a directed + * graph. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \example examples/simple/igraph_bipartite_create.c + */ + +int igraph_create_bipartite(igraph_t *graph, const igraph_vector_bool_t *types, + const igraph_vector_t *edges, + igraph_bool_t directed) { + + igraph_integer_t no_of_nodes = + (igraph_integer_t) igraph_vector_bool_size(types); + long int no_of_edges = igraph_vector_size(edges); + igraph_real_t min_edge = 0, max_edge = 0; + igraph_bool_t min_type = 0, max_type = 0; + long int i; + + if (no_of_edges % 2 != 0) { + IGRAPH_ERROR("Invalid (odd) edges vector", IGRAPH_EINVEVECTOR); + } + no_of_edges /= 2; + + if (no_of_edges != 0) { + igraph_vector_minmax(edges, &min_edge, &max_edge); + } + if (min_edge < 0 || max_edge >= no_of_nodes) { + IGRAPH_ERROR("Invalid (negative) vertex id", IGRAPH_EINVVID); + } + + /* Check types vector */ + if (no_of_nodes != 0) { + igraph_vector_bool_minmax(types, &min_type, &max_type); + if (min_type < 0 || max_type > 1) { + IGRAPH_WARNING("Non-binary type vector when creating a bipartite graph"); + } + } + + /* Check bipartiteness */ + for (i = 0; i < no_of_edges * 2; i += 2) { + long int from = (long int) VECTOR(*edges)[i]; + long int to = (long int) VECTOR(*edges)[i + 1]; + long int t1 = VECTOR(*types)[from]; + long int t2 = VECTOR(*types)[to]; + if ( (t1 && t2) || (!t1 && !t2) ) { + IGRAPH_ERROR("Invalid edges, not a bipartite graph", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_empty(graph, no_of_nodes, directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_add_edges(graph, edges, 0)); + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_incidence + * Create a bipartite graph from an incidence matrix + * + * A bipartite (or two-mode) graph contains two types of vertices and + * edges always connect vertices of different types. An incidence + * matrix is an nxm matrix, n and m are the number of vertices of the + * two types, respectively. Nonzero elements in the matrix denote + * edges between the two corresponding vertices. + * + * + * Note that this function can operate in two modes, depending on the + * \p multiple argument. If it is FALSE (i.e. 0), then a single edge is + * created for every non-zero element in the incidence matrix. If \p + * multiple is TRUE (i.e. 1), then the matrix elements are rounded up + * to the closest non-negative integer to get the number of edges to + * create between a pair of vertices. + * + * + * This function does not create multiple edges if \p multiple is + * FALSE, but might create some if it is TRUE. + * + * \param graph Pointer to an uninitialized graph object. + * \param types Pointer to an initialized boolean vector, or a null + * pointer. If not a null pointer, then the vertex types are stored + * here. It is resized as needed. + * \param incidence The incidence matrix. + * \param directed Gives whether to create an undirected or a directed + * graph. + * \param mode Specifies the direction of the edges in a directed + * graph. If \c IGRAPH_OUT, then edges point from vertices + * of the first kind (corresponding to rows) to vertices of the + * second kind (corresponding to columns); if \c + * IGRAPH_IN, then the opposite direction is realized; if \c + * IGRAPH_ALL, then mutual edges will be created. + * \param multiple How to interpret the incidence matrix elements. See + * details below. + * \return Error code. + * + * Time complexity: O(n*m), the size of the incidence matrix. + */ + +int igraph_incidence(igraph_t *graph, igraph_vector_bool_t *types, + const igraph_matrix_t *incidence, + igraph_bool_t directed, + igraph_neimode_t mode, igraph_bool_t multiple) { + + igraph_integer_t n1 = (igraph_integer_t) igraph_matrix_nrow(incidence); + igraph_integer_t n2 = (igraph_integer_t) igraph_matrix_ncol(incidence); + igraph_integer_t no_of_nodes = n1 + n2; + igraph_vector_t edges; + long int i, j, k; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + if (multiple) { + + for (i = 0; i < n1; i++) { + for (j = 0; j < n2; j++) { + long int elem = (long int) MATRIX(*incidence, i, j); + long int from, to; + + if (!elem) { + continue; + } + + if (mode == IGRAPH_IN) { + from = n1 + j; + to = i; + } else { + from = i; + to = n1 + j; + } + + if (mode != IGRAPH_ALL || !directed) { + for (k = 0; k < elem; k++) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to)); + } + } else { + for (k = 0; k < elem; k++) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, from)); + } + } + } + } + + } else { + + for (i = 0; i < n1; i++) { + for (j = 0; j < n2; j++) { + long int from, to; + + if (MATRIX(*incidence, i, j) != 0) { + if (mode == IGRAPH_IN) { + from = n1 + j; + to = i; + } else { + from = i; + to = n1 + j; + } + if (mode != IGRAPH_ALL || !directed) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to)); + } else { + IGRAPH_CHECK(igraph_vector_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, from)); + } + } + } + } + + } + + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_destroy, graph); + + if (types) { + IGRAPH_CHECK(igraph_vector_bool_resize(types, no_of_nodes)); + igraph_vector_bool_null(types); + for (i = n1; i < no_of_nodes; i++) { + VECTOR(*types)[i] = 1; + } + } + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_get_incidence + * Convert a bipartite graph into an incidence matrix + * + * \param graph The input graph, edge directions are ignored. + * \param types Boolean vector containing the vertex types. + * \param res Pointer to an initialized matrix, the result is stored + * here. An element of the matrix gives the number of edges + * (irrespectively of their direction) between the two corresponding + * vertices. + * \param row_ids Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the vertex ids (in the + * graph) corresponding to the rows of the result matrix are stored + * here. + * \param col_ids Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the vertex ids corresponding + * to the columns of the result matrix are stored here. + * \return Error code. + * + * Time complexity: O(n*m), n and m are number of vertices of the two + * different kind. + * + * \sa \ref igraph_incidence() for the opposite operation. + */ + +int igraph_get_incidence(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_matrix_t *res, + igraph_vector_t *row_ids, + igraph_vector_t *col_ids) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + long int n1 = 0, n2 = 0, i; + igraph_vector_t perm; + long int p1, p2; + + if (igraph_vector_bool_size(types) != no_of_nodes) { + IGRAPH_ERROR("Invalid vertex type vector for bipartite graph", + IGRAPH_EINVAL); + } + + for (i = 0; i < no_of_nodes; i++) { + n1 += VECTOR(*types)[i] == 0 ? 1 : 0; + } + n2 = no_of_nodes - n1; + + IGRAPH_VECTOR_INIT_FINALLY(&perm, no_of_nodes); + + for (i = 0, p1 = 0, p2 = n1; i < no_of_nodes; i++) { + VECTOR(perm)[i] = VECTOR(*types)[i] ? p2++ : p1++; + } + + IGRAPH_CHECK(igraph_matrix_resize(res, n1, n2)); + igraph_matrix_null(res); + for (i = 0; i < no_of_edges; i++) { + long int from = IGRAPH_FROM(graph, i); + long int to = IGRAPH_TO(graph, i); + long int from2 = (long int) VECTOR(perm)[from]; + long int to2 = (long int) VECTOR(perm)[to]; + if (! VECTOR(*types)[from]) { + MATRIX(*res, from2, to2 - n1) += 1; + } else { + MATRIX(*res, to2, from2 - n1) += 1; + } + } + + if (row_ids) { + IGRAPH_CHECK(igraph_vector_resize(row_ids, n1)); + } + if (col_ids) { + IGRAPH_CHECK(igraph_vector_resize(col_ids, n2)); + } + if (row_ids || col_ids) { + for (i = 0; i < no_of_nodes; i++) { + if (! VECTOR(*types)[i]) { + if (row_ids) { + long int i2 = (long int) VECTOR(perm)[i]; + VECTOR(*row_ids)[i2] = i; + } + } else { + if (col_ids) { + long int i2 = (long int) VECTOR(perm)[i]; + VECTOR(*col_ids)[i2 - n1] = i; + } + } + } + } + + igraph_vector_destroy(&perm); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_is_bipartite + * Check whether a graph is bipartite + * + * + * This function simply checks whether a graph \emph{could} be + * bipartite. It tries to find a mapping that gives a possible division + * of the vertices into two classes, such that no two vertices of the + * same class are connected by an edge. + * + * + * The existence of such a mapping is equivalent of having no circuits of + * odd length in the graph. A graph with loop edges cannot bipartite. + * + * + * Note that the mapping is not necessarily unique, e.g. if the graph has + * at least two components, then the vertices in the separate components + * can be mapped independently. + * + * \param graph The input graph. + * \param res Pointer to a boolean, the result is stored here. + * \param type Pointer to an initialized boolean vector, or a null + * pointer. If not a null pointer and a mapping was found, then it + * is stored here. If not a null pointer, but no mapping was found, + * the contents of this vector is invalid. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ + +int igraph_is_bipartite(const igraph_t *graph, + igraph_bool_t *res, + igraph_vector_bool_t *type) { + + /* We basically do a breadth first search and label the + vertices along the way. We stop as soon as we can find a + contradiction. + + In the 'seen' vector 0 means 'not seen yet', 1 means type 1, + 2 means type 2. + */ + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_char_t seen; + igraph_dqueue_t Q; + igraph_vector_t neis; + igraph_bool_t bi = 1; + long int i; + + IGRAPH_CHECK(igraph_vector_char_init(&seen, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_char_destroy, &seen); + IGRAPH_DQUEUE_INIT_FINALLY(&Q, 100); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + for (i = 0; bi && i < no_of_nodes; i++) { + + if (VECTOR(seen)[i]) { + continue; + } + + IGRAPH_CHECK(igraph_dqueue_push(&Q, i)); + VECTOR(seen)[i] = 1; + + while (bi && !igraph_dqueue_empty(&Q)) { + long int n, j; + igraph_integer_t actnode = (igraph_integer_t) igraph_dqueue_pop(&Q); + char acttype = VECTOR(seen)[actnode]; + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, actnode, IGRAPH_ALL)); + n = igraph_vector_size(&neis); + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neis)[j]; + if (VECTOR(seen)[nei]) { + long int neitype = VECTOR(seen)[nei]; + if (neitype == acttype) { + bi = 0; + break; + } + } else { + VECTOR(seen)[nei] = 3 - acttype; + IGRAPH_CHECK(igraph_dqueue_push(&Q, nei)); + } + } + } + } + + igraph_vector_destroy(&neis); + igraph_dqueue_destroy(&Q); + IGRAPH_FINALLY_CLEAN(2); + + if (res) { + *res = bi; + } + + if (type && bi) { + IGRAPH_CHECK(igraph_vector_bool_resize(type, no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*type)[i] = VECTOR(seen)[i] - 1; + } + } + + igraph_vector_char_destroy(&seen); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +int igraph_bipartite_game_gnp(igraph_t *graph, igraph_vector_bool_t *types, + igraph_integer_t n1, igraph_integer_t n2, + igraph_real_t p, igraph_bool_t directed, + igraph_neimode_t mode) { + + int retval = 0; + igraph_vector_t edges, s; + int i; + + if (p < 0.0 || p > 1.0) { + IGRAPH_ERROR("Invalid connection probability", IGRAPH_EINVAL); + } + + if (types) { + IGRAPH_CHECK(igraph_vector_bool_resize(types, n1 + n2)); + igraph_vector_bool_null(types); + for (i = n1; i < n1 + n2; i++) { + VECTOR(*types)[i] = 1; + } + } + + if (p == 0 || n1 * n2 < 1) { + IGRAPH_CHECK(retval = igraph_empty(graph, n1 + n2, directed)); + } else if (p == 1.0) { + IGRAPH_CHECK(retval = igraph_full_bipartite(graph, types, n1, n2, directed, + mode)); + } else { + + long int to, from, slen; + double maxedges, last; + if (!directed || mode != IGRAPH_ALL) { + maxedges = (double) n1 * (double) n2; + } else { + maxedges = 2.0 * (double) n1 * (double) n2; + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_CHECK(igraph_vector_reserve(&s, (long) (maxedges * p * 1.1))); + + RNG_BEGIN(); + + last = RNG_GEOM(p); + while (last < maxedges) { + IGRAPH_CHECK(igraph_vector_push_back(&s, last)); + last += RNG_GEOM(p); + last += 1; + } + + RNG_END(); + + slen = igraph_vector_size(&s); + IGRAPH_CHECK(igraph_vector_reserve(&edges, slen * 2)); + + for (i = 0; i < slen; i++) { + if (!directed || mode != IGRAPH_ALL) { + to = (long) floor(VECTOR(s)[i] / n1); + from = (long) (VECTOR(s)[i] - ((igraph_real_t) to) * n1); + to += n1; + } else { + long int n1n2 = n1 * n2; + if (VECTOR(s)[i] < n1n2) { + to = (long) floor(VECTOR(s)[i] / n1); + from = (long) (VECTOR(s)[i] - ((igraph_real_t) to) * n1); + to += n1; + } else { + to = (long) floor( (VECTOR(s)[i] - n1n2) / n2); + from = (long) (VECTOR(s)[i] - n1n2 - ((igraph_real_t) to) * n2); + from += n1; + } + } + + if (mode != IGRAPH_IN) { + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } else { + igraph_vector_push_back(&edges, to); + igraph_vector_push_back(&edges, from); + } + } + + igraph_vector_destroy(&s); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_CHECK(retval = igraph_create(graph, &edges, n1 + n2, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } + + return retval; +} + +int igraph_bipartite_game_gnm(igraph_t *graph, igraph_vector_bool_t *types, + igraph_integer_t n1, igraph_integer_t n2, + igraph_integer_t m, igraph_bool_t directed, + igraph_neimode_t mode) { + igraph_vector_t edges; + igraph_vector_t s; + int retval = 0; + + if (n1 < 0 || n2 < 0) { + IGRAPH_ERROR("Invalid number of vertices", IGRAPH_EINVAL); + } + if (m < 0) { + IGRAPH_ERROR("Invalid number of edges", IGRAPH_EINVAL); + } + + if (types) { + long int i; + IGRAPH_CHECK(igraph_vector_bool_resize(types, n1 + n2)); + igraph_vector_bool_null(types); + for (i = n1; i < n1 + n2; i++) { + VECTOR(*types)[i] = 1; + } + } + + if (m == 0 || n1 * n2 == 0) { + if (m > 0) { + IGRAPH_ERROR("Invalid number (too large) of edges", IGRAPH_EINVAL); + } + IGRAPH_CHECK(retval = igraph_empty(graph, n1 + n2, directed)); + } else { + + + long int i; + double maxedges; + if (!directed || mode != IGRAPH_ALL) { + maxedges = (double) n1 * (double) n2; + } else { + maxedges = 2.0 * (double) n1 * (double) n2; + } + + if (m > maxedges) { + IGRAPH_ERROR("Invalid number (too large) of edges", IGRAPH_EINVAL); + } + + if (maxedges == m) { + IGRAPH_CHECK(retval = igraph_full_bipartite(graph, types, n1, n2, + directed, mode)); + } else { + + long int to, from; + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_CHECK(igraph_random_sample(&s, 0, maxedges - 1, m)); + IGRAPH_CHECK(igraph_vector_reserve(&edges, igraph_vector_size(&s) * 2)); + + for (i = 0; i < m; i++) { + if (!directed || mode != IGRAPH_ALL) { + to = (long) floor(VECTOR(s)[i] / n1); + from = (long) (VECTOR(s)[i] - ((igraph_real_t) to) * n1); + to += n1; + } else { + long int n1n2 = n1 * n2; + if (VECTOR(s)[i] < n1n2) { + to = (long) floor(VECTOR(s)[i] / n1); + from = (long) (VECTOR(s)[i] - ((igraph_real_t) to) * n1); + to += n1; + } else { + to = (long) floor( (VECTOR(s)[i] - n1n2) / n2); + from = (long) (VECTOR(s)[i] - n1n2 - ((igraph_real_t) to) * n2); + from += n1; + } + } + + if (mode != IGRAPH_IN) { + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } else { + igraph_vector_push_back(&edges, to); + igraph_vector_push_back(&edges, from); + } + } + + igraph_vector_destroy(&s); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_CHECK(retval = igraph_create(graph, &edges, n1 + n2, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } + } + + return retval; +} + +/** + * \function igraph_bipartite_game + * Generate a bipartite random graph (similar to Erdos-Renyi) + * + * Similarly to unipartite (one-mode) networks, we can define the + * G(n,p), and G(n,m) graph classes for bipartite graphs, via their + * generating process. In G(n,p) every possible edge between top and + * bottom vertices is realized with probablity p, independently of the + * rest of the edges. In G(n,m), we uniformly choose m edges to + * realize. + * \param graph Pointer to an uninitialized igraph graph, the result + * is stored here. + * \param types Pointer to an initialized boolean vector, or a null + * pointer. If not a null pointer, then the vertex types are stored + * here. Bottom vertices come first, n1 of them, then n2 top + * vertices. + * \param type The type of the random graph, possible values: + * \clist + * \cli IGRAPH_ERDOS_RENYI_GNM + * G(n,m) graph, + * m edges are + * selected uniformly randomly in a graph with + * n vertices. + * \cli IGRAPH_ERDOS_RENYI_GNP + * G(n,p) graph, + * every possible edge is included in the graph with + * probability p. + * \endclist + * \param n1 The number of bottom vertices. + * \param n2 The number of top verices. + * \param p The connection probability for G(n,p) graphs. It is + * ignored for G(n,m) graphs. + * \param m The number of edges for G(n,m) graphs. It is ignored for + * G(n,p) graphs. + * \param directed Boolean, whether to generate a directed graph. See + * also the \p mode argument. + * \param mode Specifies how to direct the edges in directed + * graphs. If it is \c IGRAPH_OUT, then directed edges point from + * bottom vertices to top vertices. If it is \c IGRAPH_IN, edges + * point from top vertices to bottom vertices. \c IGRAPH_OUT and + * \c IGRAPH_IN do not generate mutual edges. If this argument is + * \c IGRAPH_ALL, then each edge direction is considered + * independently and mutual edges might be generated. This + * argument is ignored for undirected graphs. + * \return Error code. + * + * \sa \ref igraph_erdos_renyi_game. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ + +int igraph_bipartite_game(igraph_t *graph, igraph_vector_bool_t *types, + igraph_erdos_renyi_t type, + igraph_integer_t n1, igraph_integer_t n2, + igraph_real_t p, igraph_integer_t m, + igraph_bool_t directed, igraph_neimode_t mode) { + int retval = 0; + + if (n1 < 0 || n2 < 0) { + IGRAPH_ERROR("Invalid number of vertices", IGRAPH_EINVAL); + } + + if (type == IGRAPH_ERDOS_RENYI_GNP) { + retval = igraph_bipartite_game_gnp(graph, types, n1, n2, p, directed, mode); + } else if (type == IGRAPH_ERDOS_RENYI_GNM) { + retval = igraph_bipartite_game_gnm(graph, types, n1, n2, m, directed, mode); + } else { + IGRAPH_ERROR("Invalid type", IGRAPH_EINVAL); + } + return retval; +} diff --git a/src/blas.c b/src/blas.c new file mode 100644 index 0000000..5a39813 --- /dev/null +++ b/src/blas.c @@ -0,0 +1,110 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_blas.h" +#include "igraph_blas_internal.h" + +#include + +/** + * \function igraph_blas_dgemv + * \brief Matrix-vector multiplication using BLAS, vector version. + * + * This function is a somewhat more user-friendly interface to + * the \c dgemv function in BLAS. \c dgemv performs the operation + * y = alpha*A*x + beta*y, where x and y are vectors and A is an + * appropriately sized matrix (symmetric or unsymmetric). + * + * \param transpose whether to transpose the matrix \p A + * \param alpha the constant \p alpha + * \param a the matrix \p A + * \param x the vector \p x + * \param beta the constant \p beta + * \param y the vector \p y (which will be modified in-place) + * + * Time complexity: O(nk) if the matrix is of size n x k + * + * \sa \ref igraph_blas_dgemv_array if you have arrays instead of + * vectors. + * + * \example examples/simple/blas.c + */ +void igraph_blas_dgemv(igraph_bool_t transpose, igraph_real_t alpha, + const igraph_matrix_t* a, const igraph_vector_t* x, + igraph_real_t beta, igraph_vector_t* y) { + char trans = transpose ? 'T' : 'N'; + int m, n; + int inc = 1; + + m = (int) igraph_matrix_nrow(a); + n = (int) igraph_matrix_ncol(a); + + assert(igraph_vector_size(x) == transpose ? m : n); + assert(igraph_vector_size(y) == transpose ? n : m); + + igraphdgemv_(&trans, &m, &n, &alpha, VECTOR(a->data), &m, + VECTOR(*x), &inc, &beta, VECTOR(*y), &inc); +} + +/** + * \function igraph_blas_dgemv_array + * \brief Matrix-vector multiplication using BLAS, array version. + * + * This function is a somewhat more user-friendly interface to + * the \c dgemv function in BLAS. \c dgemv performs the operation + * y = alpha*A*x + beta*y, where x and y are vectors and A is an + * appropriately sized matrix (symmetric or unsymmetric). + * + * \param transpose whether to transpose the matrix \p A + * \param alpha the constant \p alpha + * \param a the matrix \p A + * \param x the vector \p x as a regular C array + * \param beta the constant \p beta + * \param y the vector \p y as a regular C array + * (which will be modified in-place) + * + * Time complexity: O(nk) if the matrix is of size n x k + * + * \sa \ref igraph_blas_dgemv if you have vectors instead of + * arrays. + */ +void igraph_blas_dgemv_array(igraph_bool_t transpose, igraph_real_t alpha, + const igraph_matrix_t* a, const igraph_real_t* x, + igraph_real_t beta, igraph_real_t* y) { + char trans = transpose ? 'T' : 'N'; + int m, n; + int inc = 1; + + m = (int) igraph_matrix_nrow(a); + n = (int) igraph_matrix_ncol(a); + + igraphdgemv_(&trans, &m, &n, &alpha, VECTOR(a->data), &m, + (igraph_real_t*)x, &inc, &beta, y, &inc); +} + +igraph_real_t igraph_blas_dnrm2(const igraph_vector_t *v) { + int n = igraph_vector_size(v); + int one = 1; + return igraphdnrm2_(&n, VECTOR(*v), &one); +} diff --git a/src/bliss.cc b/src/bliss.cc new file mode 100644 index 0000000..72c1128 --- /dev/null +++ b/src/bliss.cc @@ -0,0 +1,262 @@ +/* + Copyright (C) 2003-2006 Tommi Junttila + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/* FSF address fixed in the above notice on 1 Oct 2009 by Tamas Nepusz */ + +#include "bliss/graph.hh" + +#include "igraph_types.h" +#include "igraph_topology.h" + +#include "igraph_datatype.h" +#include "igraph_interface.h" + + +using namespace bliss; +using namespace std; + + +namespace { // unnamed namespace + +inline AbstractGraph *bliss_from_igraph(const igraph_t *graph) { + unsigned int nof_vertices = (unsigned int)igraph_vcount(graph); + unsigned int nof_edges = (unsigned int)igraph_ecount(graph); + + AbstractGraph *g; + + if (igraph_is_directed(graph)) { + g = new Digraph(nof_vertices); + } else { + g = new Graph(nof_vertices); + } + + g->set_verbose_level(0); + + for (unsigned int i = 0; i < nof_edges; i++) { + g->add_edge((unsigned int)IGRAPH_FROM(graph, i), (unsigned int)IGRAPH_TO(graph, i)); + } + return g; +} + + +void bliss_free_graph(AbstractGraph *g) { + delete g; +} + + +inline int bliss_set_sh(AbstractGraph *g, igraph_bliss_sh_t sh, bool directed) { + if (directed) { + Digraph::SplittingHeuristic gsh = Digraph::shs_fsm; + switch (sh) { + case IGRAPH_BLISS_F: gsh = Digraph::shs_f; break; + case IGRAPH_BLISS_FL: gsh = Digraph::shs_fl; break; + case IGRAPH_BLISS_FS: gsh = Digraph::shs_fs; break; + case IGRAPH_BLISS_FM: gsh = Digraph::shs_fm; break; + case IGRAPH_BLISS_FLM: gsh = Digraph::shs_flm; break; + case IGRAPH_BLISS_FSM: gsh = Digraph::shs_fsm; break; + default: IGRAPH_ERROR("Invalid splitting heuristic", IGRAPH_EINVAL); + } + static_cast(g)->set_splitting_heuristic(gsh); + } else { + Graph::SplittingHeuristic gsh = Graph::shs_fsm; + switch (sh) { + case IGRAPH_BLISS_F: gsh = Graph::shs_f; break; + case IGRAPH_BLISS_FL: gsh = Graph::shs_fl; break; + case IGRAPH_BLISS_FS: gsh = Graph::shs_fs; break; + case IGRAPH_BLISS_FM: gsh = Graph::shs_fm; break; + case IGRAPH_BLISS_FLM: gsh = Graph::shs_flm; break; + case IGRAPH_BLISS_FSM: gsh = Graph::shs_fsm; break; + default: IGRAPH_ERROR("Invalid splitting heuristic", IGRAPH_EINVAL); + } + static_cast(g)->set_splitting_heuristic(gsh); + } + return IGRAPH_SUCCESS; +} + + +inline int bliss_set_colors(AbstractGraph *g, const igraph_vector_int_t *colors) { + if (colors == NULL) { + return IGRAPH_SUCCESS; + } + const int n = g->get_nof_vertices(); + if (n != igraph_vector_int_size(colors)) { + IGRAPH_ERROR("Invalid vertex color vector length", IGRAPH_EINVAL); + } + for (int i = 0; i < n; ++i) { + g->change_color(i, VECTOR(*colors)[i]); + } + return IGRAPH_SUCCESS; +} + + +inline void bliss_info_to_igraph(igraph_bliss_info_t *info, const Stats &stats) { + if (info) { + info->max_level = stats.get_max_level(); + info->nof_nodes = stats.get_nof_nodes(); + info->nof_leaf_nodes = stats.get_nof_leaf_nodes(); + info->nof_bad_nodes = stats.get_nof_bad_nodes(); + info->nof_canupdates = stats.get_nof_canupdates(); + info->nof_generators = stats.get_nof_generators(); + stats.group_size.tostring(&info->group_size); + } +} + + +// this is the callback function used with AbstractGraph::find_automorphisms() +// it collects the group generators into a pointer vector +void collect_generators(void *generators, unsigned int n, const unsigned int *aut) { + igraph_vector_ptr_t *gen = static_cast(generators); + igraph_vector_t *newvector = igraph_Calloc(1, igraph_vector_t); + igraph_vector_init(newvector, n); + copy(aut, aut + n, newvector->stor_begin); // takes care of unsigned int -> double conversion + igraph_vector_ptr_push_back(gen, newvector); +} + +} // end unnamed namespace + +/** + * \function igraph_canonical_permutation + * Canonical permutation using BLISS + * + * This function computes the canonical permutation which transforms + * the graph into a canonical form by using the BLISS algorithm. + * + * \param graph The input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param colors An optional vertex color vector for the graph. Supply a + * null pointer is the graph is not colored. + * \param labeling Pointer to a vector, the result is stored here. The + * permutation takes vertex 0 to the first element of the vector, + * vertex 1 to the second, etc. The vector will be resized as + * needed. + * \param sh The splitting heuristics to be used in BLISS. See \ref + * igraph_bliss_sh_t. + * \param info If not \c NULL then information on BLISS internals is + * stored here. See \ref igraph_bliss_info_t. + * \return Error code. + * + * Time complexity: exponential, in practice it is fast for many graphs. + */ +int igraph_canonical_permutation(const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_vector_t *labeling, igraph_bliss_sh_t sh, igraph_bliss_info_t *info) { + AbstractGraph *g = bliss_from_igraph(graph); + IGRAPH_FINALLY(bliss_free_graph, g); + const unsigned int N = g->get_nof_vertices(); + + IGRAPH_CHECK(bliss_set_sh(g, sh, igraph_is_directed(graph))); + IGRAPH_CHECK(bliss_set_colors(g, colors)); + + Stats stats; + const unsigned int *cl = g->canonical_form(stats, NULL, NULL); + IGRAPH_CHECK(igraph_vector_resize(labeling, N)); + for (unsigned int i = 0; i < N; i++) { + VECTOR(*labeling)[i] = cl[i]; + } + + bliss_info_to_igraph(info, stats); + + delete g; + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_automorphisms + * Number of automorphisms using BLISS + * + * The number of automorphisms of a graph is computed using BLISS. The + * result is returned as part of the \p info structure, in tag \c + * group_size. It is returned as a string, as it can be very high even + * for relatively small graphs. If the GNU MP library is used then + * this number is exact, otherwise a long double is used + * and it is only approximate. See also \ref igraph_bliss_info_t. + * + * \param graph The input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param colors An optional vertex color vector for the graph. Supply a + * null pointer is the graph is not colored. + * \param sh The splitting heuristics to be used in BLISS. See \ref + * igraph_bliss_sh_t. + * \param info The result is stored here, in particular in the \c + * group_size tag of \p info. + * \return Error code. + * + * Time complexity: exponential, in practice it is fast for many graphs. + */ +int igraph_automorphisms(const igraph_t *graph, const igraph_vector_int_t *colors, + igraph_bliss_sh_t sh, igraph_bliss_info_t *info) { + AbstractGraph *g = bliss_from_igraph(graph); + IGRAPH_FINALLY(bliss_free_graph, g); + + IGRAPH_CHECK(bliss_set_sh(g, sh, igraph_is_directed(graph))); + IGRAPH_CHECK(bliss_set_colors(g, colors)); + + Stats stats; + g->find_automorphisms(stats, NULL, NULL); + + bliss_info_to_igraph(info, stats); + + delete g; + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_automorphism_group + * Automorphism group generators using BLISS + * + * The generators of the automorphism group of a graph are computed + * using BLISS. The generator set may not be minimal and may depend on + * the splitting heuristics. + * + * \param graph The input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param colors An optional vertex color vector for the graph. Supply a + * null pointer is the graph is not colored. + * \param generators Must be an initialized pointer vector. It will + * contain pointers to \ref igraph_vector_t objects + * representing generators of the automorphism group. + * \param sh The splitting heuristics to be used in BLISS. See \ref + * igraph_bliss_sh_t. + * \param info If not \c NULL then information on BLISS internals is + * stored here. See \ref igraph_bliss_info_t. + * \return Error code. + * + * Time complexity: exponential, in practice it is fast for many graphs. + */ +int igraph_automorphism_group( + const igraph_t *graph, const igraph_vector_int_t *colors, igraph_vector_ptr_t *generators, + igraph_bliss_sh_t sh, igraph_bliss_info_t *info) { + AbstractGraph *g = bliss_from_igraph(graph); + IGRAPH_FINALLY(bliss_free_graph, g); + + IGRAPH_CHECK(bliss_set_sh(g, sh, igraph_is_directed(graph))); + IGRAPH_CHECK(bliss_set_colors(g, colors)); + + Stats stats; + igraph_vector_ptr_resize(generators, 0); + g->find_automorphisms(stats, collect_generators, generators); + + bliss_info_to_igraph(info, stats); + + delete g; + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + + + diff --git a/src/bliss/bignum.hh b/src/bliss/bignum.hh new file mode 100644 index 0000000..ff8250c --- /dev/null +++ b/src/bliss/bignum.hh @@ -0,0 +1,133 @@ +#ifndef BLISS_BIGNUM_HH +#define BLISS_BIGNUM_HH + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +#include +#include +#include +#include +#include +#include "defs.hh" + +#include "igraph_memory.h" +#include "igraph_error.h" + +#if defined(BLISS_USE_GMP) +#include +#endif + +namespace bliss { + +/** + * \brief A very simple class for big integers (or approximation of them). + * + * If the compile time flag BLISS_USE_GMP is set, + * then the GNU Multiple Precision Arithmetic library (GMP) is used to + * obtain arbitrary precision, otherwise "long double" is used to + * approximate big integers. + */ + +#if defined(BLISS_USE_GMP) + +class BigNum +{ + mpz_t v; +public: + /** + * Create a new big number and set it to zero. + */ + BigNum() {mpz_init(v); } + + /** + * Destroy the number. + */ + ~BigNum() {mpz_clear(v); } + + /** + * Set the number to \a n. + */ + void assign(const int n) {mpz_set_si(v, n); } + + /** + * Multiply the number with \a n. + */ + void multiply(const int n) {mpz_mul_si(v, v, n); } + + /** + * Print the number in the file stream \a fp. + */ + size_t print(FILE* const fp) const {return mpz_out_str(fp, 10, v); } + + int tostring(char **str) const { + *str=igraph_Calloc(mpz_sizeinbase(v, 10)+2, char); + if (! *str) { + IGRAPH_ERROR("Cannot convert big number to string", IGRAPH_ENOMEM); + } + mpz_get_str(*str, 10, v); + return 0; + } + +}; + +#else + +class BigNum +{ + long double v; +public: + /** + * Create a new big number and set it to zero. + */ + BigNum(): v(0.0) {} + + /** + * Set the number to \a n. + */ + void assign(const int n) {v = (long double)n; } + + /** + * Multiply the number with \a n. + */ + void multiply(const int n) {v *= (long double)n; } + + /** + * Print the number in the file stream \a fp. + */ + size_t print(FILE* const fp) const {return fprintf(fp, "%Lg", v); } + + int tostring(char **str) const { + int size=static_cast( (std::log(std::abs(v))/std::log(10.0))+4 ); + *str=igraph_Calloc(size, char ); + if (! *str) { + IGRAPH_ERROR("Cannot convert big number to string", IGRAPH_ENOMEM); + } + std::stringstream ss; + ss << v; + strncpy(*str, ss.str().c_str(), size); + return 0; + } +}; + +#endif + +} //namespace bliss + +#endif diff --git a/src/bliss/bliss_heap.cc b/src/bliss/bliss_heap.cc new file mode 100644 index 0000000..e5ce6d4 --- /dev/null +++ b/src/bliss/bliss_heap.cc @@ -0,0 +1,99 @@ +#include +#include +#include +#include "defs.hh" +#include "heap.hh" + +/* use 'and' instead of '&&' */ +#if _MSC_VER +#include +#endif + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +Heap::~Heap() +{ + if(array) + { + free(array); + array = 0; + n = 0; + N = 0; + } +} + +void Heap::upheap(unsigned int index) +{ + const unsigned int v = array[index]; + array[0] = 0; + while(array[index/2] > v) + { + array[index] = array[index/2]; + index = index/2; + } + array[index] = v; +} + +void Heap::downheap(unsigned int index) +{ + const unsigned int v = array[index]; + const unsigned int lim = n/2; + while(index <= lim) + { + unsigned int new_index = index + index; + if((new_index < n) and (array[new_index] > array[new_index+1])) + new_index++; + if(v <= array[new_index]) + break; + array[index] = array[new_index]; + index = new_index; + } + array[index] = v; +} + +void Heap::init(const unsigned int size) +{ + if(size > N) + { + if(array) + free(array); + array = (unsigned int*)malloc((size + 1) * sizeof(unsigned int)); + N = size; + } + n = 0; +} + +void Heap::insert(const unsigned int v) +{ + array[++n] = v; + upheap(n); +} + +unsigned int Heap::remove() +{ + const unsigned int v = array[1]; + array[1] = array[n--]; + downheap(1); + return v; +} + +} // namespace bliss diff --git a/src/bliss/defs.cc b/src/bliss/defs.cc new file mode 100644 index 0000000..e154944 --- /dev/null +++ b/src/bliss/defs.cc @@ -0,0 +1,42 @@ +#include +#include +#include "defs.hh" + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +#ifndef USING_R + +void +fatal_error(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr,"Bliss fatal error: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\nAborting!\n"); + va_end(ap); + exit(1); +} + +#endif + +} diff --git a/src/bliss/defs.hh b/src/bliss/defs.hh new file mode 100644 index 0000000..37f1404 --- /dev/null +++ b/src/bliss/defs.hh @@ -0,0 +1,128 @@ +#ifndef BLISS_DEFS_HH +#define BLISS_DEFS_HH + +#include +#include + +#include "config.h" + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +#if HAVE_GMP == 1 +# define BLISS_USE_GMP +#endif + +#ifdef USING_R +#include +#define fatal_error(...) (error(__VA_ARGS__)) +#endif + +namespace bliss { + +/** + * The version number of bliss. + */ +static const char * const version = "0.73"; + +/* + * If a fatal error (out of memory, internal error) is encountered, + * this function is called. + * There should not be a return from this function but exit or + * a jump to code that deallocates the AbstractGraph instance that called this. + */ +#ifndef USING_R +void fatal_error(const char* fmt, ...); +#endif + + +#if defined(BLISS_DEBUG) +#define BLISS_CONSISTENCY_CHECKS +#define BLISS_EXPENSIVE_CONSISTENCY_CHECKS +#endif + + +#if defined(BLISS_CONSISTENCY_CHECKS) +/* Force a check that the found automorphisms are valid */ +#define BLISS_VERIFY_AUTOMORPHISMS +#endif + + +#if defined(BLISS_CONSISTENCY_CHECKS) +/* Force a check that the generated partitions are equitable */ +#define BLISS_VERIFY_EQUITABLEDNESS +#endif + +} // namespace bliss + + + +/*! \mainpage Bliss + * + * \section intro_sec Introduction + * + * This is the source code documentation of bliss, + * produced by running doxygen in + * the source directory. + * The algorithms and data structures used in bliss are documented in + * the papers found at the + * bliss web site. + * + * + * \section compile_sec Compiling + * + * Compiling bliss in Linux should be easy, just execute + * \code + * make + * \endcode + * in the bliss source directory. + * This will produce the executable program \c bliss as well as + * the library file \c libbliss.a that can be linked in other programs. + * If you have the GNU Multiple Precision + * Arithmetic Library (GMP) installed in your machine, you can also use + * \code + * make gmp + * \endcode + * to enable exact computation of automorphism group sizes. + * + * When linking the bliss library \c libbliss.a in other programs, + * remember to include the standard c++ library + * (and the GMP library if you compiled bliss to include it). + * For instance, + * \code gcc -o test test.c -lstdc++ -lgmp -lbliss\endcode + * + * \section cppapi_sec The C++ language API + * + * The C++ language API is the main API to bliss; + * all other APIs are just more or less complete variants of it. + * The C++ API consists basically of the public methods in + * the classes bliss::AbstractGraph, bliss::Graph, and bliss::Digraph. + * For an example of its use, + * see the \ref executable "source of the bliss executable". + * + * + * \section capi_sec The C language API + * + * The C language API is given in the file bliss_C.h. + * It is currently more restricted than the C++ API so + * consider using the C++ API whenever possible. + */ + + +#endif diff --git a/src/bliss/graph.cc b/src/bliss/graph.cc new file mode 100644 index 0000000..8025cb0 --- /dev/null +++ b/src/bliss/graph.cc @@ -0,0 +1,5609 @@ +#include +#include +#include +#include +#include +#include + +#include "defs.hh" +#include "graph.hh" +#include "partition.hh" +#include "utils.hh" + +/* use 'and' instead of '&&' */ +#if _MSC_VER +#include +#endif + +#ifdef USING_R +#undef stdout +#define stdout NULL +#endif + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + + +namespace bliss { + +#define _INTERNAL_ERROR() fatal_error("%s:%d: internal error",__FILE__,__LINE__) +#define _OUT_OF_MEMORY() fatal_error("%s:%d: out of memory",__FILE__,__LINE__) + +/*------------------------------------------------------------------------- + * + * Constructor and destructor routines for the abstract graph class + * + *-------------------------------------------------------------------------*/ + + +AbstractGraph::AbstractGraph() +{ + /* Initialize stuff */ + first_path_labeling = 0; + first_path_labeling_inv = 0; + best_path_labeling = 0; + best_path_labeling_inv = 0; + first_path_automorphism = 0; + best_path_automorphism = 0; + in_search = false; + + /* Default value for using "long prune" */ + opt_use_long_prune = true; + /* Default value for using failure recording */ + opt_use_failure_recording = true; + /* Default value for using component recursion */ + opt_use_comprec = true; + + + verbose_level = 0; + verbstr = stdout; + + report_hook = 0; + report_user_param = 0; +} + + +AbstractGraph::~AbstractGraph() +{ + if(first_path_labeling) { + free(first_path_labeling); first_path_labeling = 0; } + if(first_path_labeling_inv) { + free(first_path_labeling_inv); first_path_labeling_inv = 0; } + if(best_path_labeling) { + free(best_path_labeling); best_path_labeling = 0; } + if(best_path_labeling_inv) { + free(best_path_labeling_inv); best_path_labeling_inv = 0; } + if(first_path_automorphism) { + free(first_path_automorphism); first_path_automorphism = 0; } + if(best_path_automorphism) { + free(best_path_automorphism); best_path_automorphism = 0; } + + report_hook = 0; + report_user_param = 0; +} + + + +/*------------------------------------------------------------------------- + * + * Verbose output management routines + * + *-------------------------------------------------------------------------*/ + +void +AbstractGraph::set_verbose_level(const unsigned int level) +{ + verbose_level = level; +} + +void +AbstractGraph::set_verbose_file(FILE* const fp) +{ + verbstr = fp; +} + + + +/*------------------------------------------------------------------------- + * + * Routines for refinement to equitable partition + * + *-------------------------------------------------------------------------*/ + +void +AbstractGraph::refine_to_equitable() +{ + + /* Start refinement from all cells -> push 'em all in the splitting queue */ + for(Partition::Cell* cell = p.first_cell; cell; cell = cell->next) + p.splitting_queue_add(cell); + + do_refine_to_equitable(); + +} + +void +AbstractGraph::refine_to_equitable(Partition::Cell* const unit_cell) +{ + + p.splitting_queue_add(unit_cell); + + do_refine_to_equitable(); +} + + + +void +AbstractGraph::refine_to_equitable(Partition::Cell* const unit_cell1, + Partition::Cell* const unit_cell2) +{ + + p.splitting_queue_add(unit_cell1); + p.splitting_queue_add(unit_cell2); + + do_refine_to_equitable(); +} + + + +bool +AbstractGraph::do_refine_to_equitable() +{ + + eqref_hash.reset(); + + while(!p.splitting_queue_is_empty()) + { + Partition::Cell* const cell = p.splitting_queue_pop(); + + if(cell->is_unit()) + { + if(in_search) { + const unsigned int index = cell->first; + if(first_path_automorphism) + { + /* Build the (potential) automorphism on-the-fly */ + first_path_automorphism[first_path_labeling_inv[index]] = + p.elements[index]; + } + if(best_path_automorphism) + { + /* Build the (potential) automorphism on-the-fly */ + best_path_automorphism[best_path_labeling_inv[index]] = + p.elements[index]; + } + } + const bool worse = split_neighbourhood_of_unit_cell(cell); + if(in_search and worse) + goto worse_exit; + } + else + { + const bool worse = split_neighbourhood_of_cell(cell); + if(in_search and worse) + goto worse_exit; + } + } + + return true; + + worse_exit: + /* Clear splitting_queue */ + p.splitting_queue_clear(); + return false; +} + + + + + + + + + + + + + + + + +/*------------------------------------------------------------------------- + * + * Routines for handling the canonical labeling + * + *-------------------------------------------------------------------------*/ + +/** \internal + * Assign the labeling induced by the current partition 'this.p' to + * \a labeling. + * That is, if the partition is [[2,0],[1]], + * then \a labeling will map 0 to 1, 1 to 2, and 2 to 0. + */ +void +AbstractGraph::update_labeling(unsigned int* const labeling) +{ + const unsigned int N = get_nof_vertices(); + unsigned int* ep = p.elements; + for(unsigned int i = 0; i < N; i++, ep++) + labeling[*ep] = i; +} + +/** \internal + * The same as update_labeling() except that the inverse of the labeling + * is also produced and assigned to \a labeling_inv. + */ +void +AbstractGraph::update_labeling_and_its_inverse(unsigned int* const labeling, + unsigned int* const labeling_inv) +{ + const unsigned int N = get_nof_vertices(); + unsigned int* ep = p.elements; + unsigned int* clip = labeling_inv; + + for(unsigned int i = 0; i < N; ) { + labeling[*ep] = i; + i++; + *clip = *ep; + ep++; + clip++; + } +} + + + + + +/*------------------------------------------------------------------------- + * + * Routines for handling automorphisms + * + *-------------------------------------------------------------------------*/ + + +/** \internal + * Reset the permutation \a perm to the identity permutation. + */ +void +AbstractGraph::reset_permutation(unsigned int* perm) +{ + const unsigned int N = get_nof_vertices(); + for(unsigned int i = 0; i < N; i++, perm++) + *perm = i; +} + +bool +AbstractGraph::is_automorphism(unsigned int* const perm) +{ + _INTERNAL_ERROR(); + return false; +} + +bool +AbstractGraph::is_automorphism(const std::vector& perm) const +{ + _INTERNAL_ERROR(); + return false; +} + + + + +/*------------------------------------------------------------------------- + * + * Certificate building + * + *-------------------------------------------------------------------------*/ + +void +AbstractGraph::cert_add(const unsigned int v1, + const unsigned int v2, + const unsigned int v3) +{ + if(refine_compare_certificate) + { + if(refine_equal_to_first) + { + /* So far equivalent to the first path... */ + unsigned int index = certificate_current_path.size(); + if(index >= refine_first_path_subcertificate_end) + { + refine_equal_to_first = false; + } + else if(certificate_first_path[index] != v1) + { + refine_equal_to_first = false; + } + else if(certificate_first_path[++index] != v2) + { + refine_equal_to_first = false; + } + else if(certificate_first_path[++index] != v3) + { + refine_equal_to_first = false; + } + if(opt_use_failure_recording and !refine_equal_to_first) + { + /* We just became different from the first path, + * remember the deviation point tree-specific invariant + * for the use of failure recording */ + UintSeqHash h; + h.update(v1); + h.update(v2); + h.update(v3); + h.update(index); + h.update(eqref_hash.get_value()); + failure_recording_fp_deviation = h.get_value(); + } + } + if(refine_cmp_to_best == 0) + { + /* So far equivalent to the current best path... */ + unsigned int index = certificate_current_path.size(); + if(index >= refine_best_path_subcertificate_end) + { + refine_cmp_to_best = 1; + } + else if(v1 > certificate_best_path[index]) + { + refine_cmp_to_best = 1; + } + else if(v1 < certificate_best_path[index]) + { + refine_cmp_to_best = -1; + } + else if(v2 > certificate_best_path[++index]) + { + refine_cmp_to_best = 1; + } + else if(v2 < certificate_best_path[index]) + { + refine_cmp_to_best = -1; + } + else if(v3 > certificate_best_path[++index]) + { + refine_cmp_to_best = 1; + } + else if(v3 < certificate_best_path[index]) + { + refine_cmp_to_best = -1; + } + } + if((refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + return; + } + /* Update the current path certificate */ + certificate_current_path.push_back(v1); + certificate_current_path.push_back(v2); + certificate_current_path.push_back(v3); +} + + +void +AbstractGraph::cert_add_redundant(const unsigned int v1, + const unsigned int v2, + const unsigned int v3) +{ + return cert_add(v1, v2, v3); +} + + + + + + + + + + + +/*------------------------------------------------------------------------- + * + * Long prune code + * + *-------------------------------------------------------------------------*/ + +void +AbstractGraph::long_prune_init() +{ + const unsigned int N = get_nof_vertices(); + long_prune_temp.clear(); + long_prune_temp.resize(N); + /* Of how many automorphisms we can store information in + the predefined, fixed amount of memory? */ + const unsigned int nof_fitting_in_max_mem = + (long_prune_options_max_mem * 1024 * 1024) / (((N * 2) / 8)+1); + long_prune_max_stored_autss = long_prune_options_max_stored_auts; + /* Had some problems with g++ in using (a* tmp = long_prune_fixed[real_i]; + long_prune_fixed[real_i] = long_prune_fixed[real_j]; + long_prune_fixed[real_j] = tmp; + tmp = long_prune_mcrs[real_i]; + long_prune_mcrs[real_i] = long_prune_mcrs[real_j]; + long_prune_mcrs[real_j] = tmp; +} + +std::vector& +AbstractGraph::long_prune_allocget_fixed(const unsigned int index) +{ + const unsigned int i = index % long_prune_max_stored_autss; + if(!long_prune_fixed[i]) + long_prune_fixed[i] = new std::vector(get_nof_vertices()); + return *long_prune_fixed[i]; +} + +std::vector& +AbstractGraph::long_prune_get_fixed(const unsigned int index) +{ + return *long_prune_fixed[index % long_prune_max_stored_autss]; +} + +std::vector& +AbstractGraph::long_prune_allocget_mcrs(const unsigned int index) +{ + const unsigned int i = index % long_prune_max_stored_autss; + if(!long_prune_mcrs[i]) + long_prune_mcrs[i] = new std::vector(get_nof_vertices()); + return *long_prune_mcrs[i]; +} + +std::vector& +AbstractGraph::long_prune_get_mcrs(const unsigned int index) +{ + return *long_prune_mcrs[index % long_prune_max_stored_autss]; +} + +void +AbstractGraph::long_prune_add_automorphism(const unsigned int* aut) +{ + if(long_prune_max_stored_autss == 0) + return; + + const unsigned int N = get_nof_vertices(); + + + /* If the buffer of stored auts is full, remove the oldest aut */ + if(long_prune_end - long_prune_begin == long_prune_max_stored_autss) + { + long_prune_begin++; + } + long_prune_end++; + std::vector& fixed = long_prune_allocget_fixed(long_prune_end-1); + std::vector& mcrs = long_prune_allocget_mcrs(long_prune_end-1); + /* Mark nodes that are (i) fixed or (ii) minimal orbit representatives + * under the automorphism 'aut' */ + for(unsigned int i = 0; i < N; i++) + { + fixed[i] = (aut[i] == i); + if(long_prune_temp[i] == false) + { + mcrs[i] = true; + unsigned int j = aut[i]; + while(j != i) + { + long_prune_temp[j] = true; + j = aut[j]; + } + } + else + { + mcrs[i] = false; + } + /* Clear the temp array on-the-fly... */ + long_prune_temp[i] = false; + } + + +} + + + +/*------------------------------------------------------------------------- + * + * Routines for handling orbit information + * + *-------------------------------------------------------------------------*/ + +void +AbstractGraph::update_orbit_information(Orbit& o, const unsigned int* perm) +{ + const unsigned int N = get_nof_vertices(); + for(unsigned int i = 0; i < N; i++) + if(perm[i] != i) + o.merge_orbits(i, perm[i]); +} + + + + + + + + +/*------------------------------------------------------------------------- + * + * The actual backtracking search + * + *-------------------------------------------------------------------------*/ + +class TreeNode +{ + //friend class AbstractGraph; +public: + unsigned int split_cell_first; + + int split_element; + static const int SPLIT_START = -1; + static const int SPLIT_END = -2; + + Partition::BacktrackPoint partition_bt_point; + + unsigned int certificate_index; + + static const char NO = -1; + static const char MAYBE = 0; + static const char YES = 1; + + /* First path stuff */ + bool fp_on; + bool fp_cert_equal; + char fp_extendable; + + /* Best path stuff */ + bool in_best_path; + int cmp_to_best_path; + + unsigned int failure_recording_ival; + + /* Component recursion related data */ + unsigned int cr_cep_stack_size; + unsigned int cr_cep_index; + unsigned int cr_level; + + bool needs_long_prune; + unsigned int long_prune_begin; + std::set > long_prune_redundant; + + UintSeqHash eqref_hash; + unsigned int subcertificate_length; +}; + + + + +typedef struct { + unsigned int splitting_element; + unsigned int certificate_index; + unsigned int subcertificate_length; + UintSeqHash eqref_hash; +} PathInfo; + + +void +AbstractGraph::search(const bool canonical, Stats& stats) +{ + const unsigned int N = get_nof_vertices(); + + unsigned int all_same_level = UINT_MAX; + + p.graph = this; + + /* + * Must be done! + */ + remove_duplicate_edges(); + + /* + * Reset search statistics + */ + stats.reset(); + stats.nof_nodes = 1; + stats.nof_leaf_nodes = 1; + + /* Free old first path data structures */ + if(first_path_labeling) { + free(first_path_labeling); first_path_labeling = 0; } + if(first_path_labeling_inv) { + free(first_path_labeling_inv); first_path_labeling_inv = 0; } + if(first_path_automorphism) { + free(first_path_automorphism); first_path_automorphism = 0; } + + /* Free old best path data structures */ + if(best_path_labeling) { + free(best_path_labeling); best_path_labeling = 0; } + if(best_path_labeling_inv) { + free(best_path_labeling_inv); best_path_labeling_inv = 0; } + if(best_path_automorphism) { + free(best_path_automorphism); best_path_automorphism = 0; } + + if(N == 0) + { + /* Nothing to do, return... */ + return; + } + + /* Initialize the partition ... */ + p.init(N); + /* ... and the component recursion data structures in the partition */ + if(opt_use_comprec) + p.cr_init(); + + neighbour_heap.init(N); + + in_search = false; + /* Do not compute certificate when building the initial partition */ + refine_compare_certificate = false; + /* The 'eqref_hash' hash value is not computed when building + * the initial partition as it is not used for anything at the moment. + * This saves some cycles. */ + compute_eqref_hash = false; + + make_initial_equitable_partition(); + + /* + * Allocate space for the "first path" and "best path" labelings + */ + if(first_path_labeling) free(first_path_labeling); + first_path_labeling = (unsigned int*)calloc(N, sizeof(unsigned int)); + if(!first_path_labeling) _OUT_OF_MEMORY(); + if(best_path_labeling) free(best_path_labeling); + best_path_labeling = (unsigned int*)calloc(N, sizeof(unsigned int)); + if(!best_path_labeling) _OUT_OF_MEMORY(); + + /* + * Is the initial partition discrete? + */ + if(p.is_discrete()) + { + /* Make the best path labeling i.e. the canonical labeling */ + update_labeling(best_path_labeling); + /* Update statistics */ + stats.nof_leaf_nodes = 1; + /* Free component recursion data */ + if(opt_use_comprec) + p.cr_free(); + return; + } + + /* + * Allocate the inverses of the "first path" and "best path" labelings + */ + if(first_path_labeling_inv) free(first_path_labeling_inv); + first_path_labeling_inv = (unsigned int*)calloc(N, sizeof(unsigned int)); + if(!first_path_labeling_inv) _OUT_OF_MEMORY(); + if(best_path_labeling_inv) free(best_path_labeling_inv); + best_path_labeling_inv = (unsigned int*)calloc(N, sizeof(unsigned int)); + if(!best_path_labeling_inv) _OUT_OF_MEMORY(); + + /* + * Allocate space for the automorphisms + */ + if(first_path_automorphism) free(first_path_automorphism); + first_path_automorphism = (unsigned int*)malloc(N * sizeof(unsigned int)); + if(!first_path_automorphism) _OUT_OF_MEMORY(); + if(best_path_automorphism) free(best_path_automorphism); + best_path_automorphism = (unsigned int*)malloc(N * sizeof(unsigned int)); + if(!best_path_automorphism) _OUT_OF_MEMORY(); + + /* + * Initialize orbit information so that all vertices are in their own orbits + */ + first_path_orbits.init(N); + best_path_orbits.init(N); + + /* + * Initialize certificate memory + */ + initialize_certificate(); + + std::vector search_stack; + std::vector first_path_info; + std::vector best_path_info; + + search_stack.clear(); + + /* Initialize "long prune" data structures */ + if(opt_use_long_prune) + long_prune_init(); + + /* + * Initialize failure recording data structures + */ + typedef std::set > FailureRecordingSet; + std::vector failure_recording_hashes; + + /* + * Initialize component recursion data structures + */ + cr_cep_stack.clear(); + unsigned int cr_cep_index = 0; + { + /* Inset a sentinel "component end point" */ + CR_CEP sentinel; + sentinel.creation_level = 0; + sentinel.discrete_cell_limit = get_nof_vertices(); + sentinel.next_cr_level = 0; + sentinel.next_cep_index = 0; + sentinel.first_checked = false; + sentinel.best_checked = false; + cr_cep_index = 0; + cr_cep_stack.push_back(sentinel); + } + cr_level = 0; + if(opt_use_comprec and + nucr_find_first_component(cr_level) == true and + p.nof_discrete_cells() + cr_component_elements < + cr_cep_stack[cr_cep_index].discrete_cell_limit) + { + cr_level = p.cr_split_level(0, cr_component); + CR_CEP cep; + cep.creation_level = 0; + cep.discrete_cell_limit = p.nof_discrete_cells() + cr_component_elements; + cep.next_cr_level = 0; + cep.next_cep_index = cr_cep_index; + cep.first_checked = false; + cep.best_checked = false; + cr_cep_index = cr_cep_stack.size(); + cr_cep_stack.push_back(cep); + } + + /* + * Build the root node of the search tree + */ + { + TreeNode root; + Partition::Cell* split_cell = find_next_cell_to_be_splitted(p.first_cell); + root.split_cell_first = split_cell->first; + root.split_element = TreeNode::SPLIT_START; + root.partition_bt_point = p.set_backtrack_point(); + root.certificate_index = 0; + root.fp_on = true; + root.fp_cert_equal = true; + root.fp_extendable = TreeNode::MAYBE; + root.in_best_path = false; + root.cmp_to_best_path = 0; + root.long_prune_begin = 0; + + root.failure_recording_ival = 0; + + /* Save component recursion info for backtracking */ + root.cr_level = cr_level; + root.cr_cep_stack_size = cr_cep_stack.size(); + root.cr_cep_index = cr_cep_index; + search_stack.push_back(root); + } + + /* + * Set status and global flags for search related procedures + */ + in_search = true; + /* Do not compare certificates during refinement until the first path has been traversed to the leaf */ + refine_compare_certificate = false; + + + + + /* + * The actual backtracking search + */ + while(!search_stack.empty()) + { + TreeNode& current_node = search_stack.back(); + const unsigned int current_level = (unsigned int)search_stack.size()-1; + + + if(opt_use_comprec) + { + CR_CEP& cep = cr_cep_stack[current_node.cr_cep_index]; + if(cep.first_checked == true and + current_node.fp_extendable == TreeNode::MAYBE and + !search_stack[cep.creation_level].fp_on) + { + current_node.fp_extendable = TreeNode::NO; + } + } + + if(current_node.fp_on) + { + if(current_node.split_element == TreeNode::SPLIT_END) + { + search_stack.pop_back(); + continue; + } + } + else + { + if(current_node.fp_extendable == TreeNode::YES) + { + search_stack.pop_back(); + continue; + } + if(current_node.split_element == TreeNode::SPLIT_END) + { + if(opt_use_failure_recording) + { + TreeNode& parent_node = search_stack[current_level-1]; + if(parent_node.fp_on) + failure_recording_hashes[current_level-1].insert(current_node.failure_recording_ival); + } + search_stack.pop_back(); + continue; + } + if(current_node.fp_extendable == TreeNode::NO and + (!canonical or current_node.cmp_to_best_path < 0)) + { + if(opt_use_failure_recording) + { + TreeNode& parent_node = search_stack[current_level-1]; + if(parent_node.fp_on) + failure_recording_hashes[current_level-1].insert(current_node.failure_recording_ival); + } + search_stack.pop_back(); + continue; + } + } + + /* Restore partition ... */ + p.goto_backtrack_point(current_node.partition_bt_point); + /* ... and re-remember backtracking point */ + current_node.partition_bt_point = p.set_backtrack_point(); + + /* Restore current path certificate */ + certificate_index = current_node.certificate_index; + refine_current_path_certificate_index = current_node.certificate_index; + certificate_current_path.resize(certificate_index); + + /* Fetch split cell information */ + Partition::Cell * const cell = + p.get_cell(p.elements[current_node.split_cell_first]); + + /* Restore component recursion information */ + cr_level = current_node.cr_level; + cr_cep_stack.resize(current_node.cr_cep_stack_size); + cr_cep_index = current_node.cr_cep_index; + + + /* + * Update long prune redundancy sets + */ + if(opt_use_long_prune and current_level >= 1 and !current_node.fp_on) + { + unsigned int begin = (current_node.long_prune_begin>long_prune_begin)?current_node.long_prune_begin:long_prune_begin; + for(unsigned int i = begin; i < long_prune_end; i++) + { + const std::vector& fixed = long_prune_get_fixed(i); +#if defined(BLISS_CONSISTENCY_CHECKS) + for(unsigned int l = 0; l < search_stack.size()-2; l++) + assert(fixed[search_stack[l].split_element]); +#endif + if(fixed[search_stack[search_stack.size()-1-1].split_element] == + false) + { + long_prune_swap(begin, i); + begin++; + current_node.long_prune_begin = begin; + continue; + } + } + + if(current_node.split_element == TreeNode::SPLIT_START) + { + current_node.needs_long_prune = true; + } + else if(current_node.needs_long_prune) + { + current_node.needs_long_prune = false; + unsigned int begin = (current_node.long_prune_begin>long_prune_begin)?current_node.long_prune_begin:long_prune_begin; + for(unsigned int i = begin; i < long_prune_end; i++) + { + const std::vector& fixed = long_prune_get_fixed(i); +#if defined(BLISS_CONSISTENCY_CHECKS) + for(unsigned int l = 0; l < search_stack.size()-2; l++) + assert(fixed[search_stack[l].split_element]); +#endif + assert(fixed[search_stack[current_level-1].split_element] == true); + if(fixed[search_stack[current_level-1].split_element] == false) + { + long_prune_swap(begin, i); + begin++; + current_node.long_prune_begin = begin; + continue; + } + const std::vector& mcrs = long_prune_get_mcrs(i); + unsigned int* ep = p.elements + cell->first; + for(unsigned int j = cell->length; j > 0; j--, ep++) { + if(mcrs[*ep] == false) + current_node.long_prune_redundant.insert(*ep); + } + } + } + } + + + /* + * Find the next smallest, non-isomorphic element in the cell and + * store it in current_node.split_element + */ + { + unsigned int next_split_element = UINT_MAX; + //unsigned int* next_split_element_pos = 0; + unsigned int* ep = p.elements + cell->first; + if(current_node.fp_on) + { + /* Find the next larger splitting element that is + * a minimal orbit representative w.r.t. first_path_orbits */ + for(unsigned int i = cell->length; i > 0; i--, ep++) { + if((int)(*ep) > current_node.split_element and + *ep < next_split_element and + first_path_orbits.is_minimal_representative(*ep)) { + next_split_element = *ep; + //next_split_element_pos = ep; + } + } + } + else if(current_node.in_best_path) + { + /* Find the next larger splitting element that is + * a minimal orbit representative w.r.t. best_path_orbits */ + for(unsigned int i = cell->length; i > 0; i--, ep++) { + if((int)(*ep) > current_node.split_element and + *ep < next_split_element and + best_path_orbits.is_minimal_representative(*ep) and + (!opt_use_long_prune or + current_node.long_prune_redundant.find(*ep) == + current_node.long_prune_redundant.end())) { + next_split_element = *ep; + //next_split_element_pos = ep; + } + } + } + else + { + /* Find the next larger splitting element */ + for(unsigned int i = cell->length; i > 0; i--, ep++) { + if((int)(*ep) > current_node.split_element and + *ep < next_split_element and + (!opt_use_long_prune or + current_node.long_prune_redundant.find(*ep) == + current_node.long_prune_redundant.end())) { + next_split_element = *ep; + //next_split_element_pos = ep; + } + } + } + if(next_split_element == UINT_MAX) + { + /* No more (unexplored children) in the cell */ + current_node.split_element = TreeNode::SPLIT_END; + if(current_node.fp_on) + { + /* Update group size */ + const unsigned int index = first_path_orbits.orbit_size(first_path_info[search_stack.size()-1].splitting_element); + stats.group_size.multiply(index); + stats.group_size_approx *= (long double)index; + /* + * Update all_same_level + */ + if(index == cell->length and all_same_level == current_level+1) + all_same_level = current_level; + if(verbstr and verbose_level >= 2) { + fprintf(verbstr, + "Level %u: orbits=%u, index=%u/%u, all_same_level=%u\n", + current_level, + first_path_orbits.nof_orbits(), + index, cell->length, + all_same_level); + fflush(verbstr); + } + } + continue; + } + + /* Split on smallest */ + current_node.split_element = next_split_element; + } + + const unsigned int child_level = current_level+1; + /* Update some statistics */ + stats.nof_nodes++; + if(search_stack.size() > stats.max_level) + stats.max_level = search_stack.size(); + + + + /* Set flags and indices for the refiner certificate builder */ + refine_equal_to_first = current_node.fp_cert_equal; + refine_cmp_to_best = current_node.cmp_to_best_path; + if(!first_path_info.empty()) + { + if(refine_equal_to_first) + refine_first_path_subcertificate_end = + first_path_info[search_stack.size()-1].certificate_index + + first_path_info[search_stack.size()-1].subcertificate_length; + if(canonical) + { + if(refine_cmp_to_best == 0) + refine_best_path_subcertificate_end = + best_path_info[search_stack.size()-1].certificate_index + + best_path_info[search_stack.size()-1].subcertificate_length; + } + else + refine_cmp_to_best = -1; + } + + const bool was_fp_cert_equal = current_node.fp_cert_equal; + + /* Individualize, i.e. split the cell in two, the latter new cell + * will be a unit one containing info.split_element */ + Partition::Cell* const new_cell = + p.individualize(cell, current_node.split_element); + + /* + * Refine the new partition to equitable + */ + if(cell->is_unit()) + refine_to_equitable(cell, new_cell); + else + refine_to_equitable(new_cell); + + + + + /* Update statistics */ + if(p.is_discrete()) + stats.nof_leaf_nodes++; + + + if(!first_path_info.empty()) + { + /* We are no longer on the first path */ + const unsigned int subcertificate_length = + certificate_current_path.size() - certificate_index; + if(refine_equal_to_first) + { + /* Was equal to the first path so far */ + PathInfo& first_pinfo = first_path_info[current_level]; + assert(first_pinfo.certificate_index == certificate_index); + if(subcertificate_length != first_pinfo.subcertificate_length) + { + refine_equal_to_first = false; + if(opt_use_failure_recording) + failure_recording_fp_deviation = subcertificate_length; + } + else if(first_pinfo.eqref_hash.cmp(eqref_hash) != 0) + { + refine_equal_to_first = false; + if(opt_use_failure_recording) + failure_recording_fp_deviation = eqref_hash.get_value(); + } + } + if(canonical and (refine_cmp_to_best == 0)) + { + /* Was equal to the best path so far */ + PathInfo& bestp_info = best_path_info[current_level]; + assert(bestp_info.certificate_index == certificate_index); + if(subcertificate_length < bestp_info.subcertificate_length) + { + refine_cmp_to_best = -1; + } + else if(subcertificate_length > bestp_info.subcertificate_length) + { + refine_cmp_to_best = 1; + } + else if(bestp_info.eqref_hash.cmp(eqref_hash) > 0) + { + refine_cmp_to_best = -1; + } + else if(bestp_info.eqref_hash.cmp(eqref_hash) < 0) + { + refine_cmp_to_best = 1; + } + } + + if(opt_use_failure_recording and + was_fp_cert_equal and + !refine_equal_to_first) + { + UintSeqHash k; + k.update(failure_recording_fp_deviation); + k.update(eqref_hash.get_value()); + failure_recording_fp_deviation = k.get_value(); + + if(current_node.fp_on) + failure_recording_hashes[current_level].insert(failure_recording_fp_deviation); + else + { + for(unsigned int i = current_level; i > 0; i--) + { + if(search_stack[i].fp_on) + break; + const FailureRecordingSet& s = failure_recording_hashes[i]; + if(i == current_level and + s.find(failure_recording_fp_deviation) != s.end()) + break; + if(s.find(0) != s.end()) + break; + search_stack[i].fp_extendable = TreeNode::NO; + } + } + } + + + /* Check if no longer equal to the first path and, + * if canonical labeling is desired, also worse than the + * current best path */ + if(refine_equal_to_first == false and + (!canonical or (refine_cmp_to_best < 0))) + { + /* Yes, backtrack */ + stats.nof_bad_nodes++; + if(current_node.fp_cert_equal == true and + current_level+1 > all_same_level) + { + assert(all_same_level >= 1); + for(unsigned int i = all_same_level; + i < search_stack.size(); + i++) + { + search_stack[i].fp_extendable = TreeNode::NO; + } + } + + continue; + } + } + +#if defined(BLISS_VERIFY_EQUITABLEDNESS) + /* The new partition should be equitable */ + if(!is_equitable()) + fatal_error("consistency check failed - partition after refinement is not equitable"); +#endif + + /* + * Next level search tree node info + */ + TreeNode child_node; + + /* No more in the first path */ + child_node.fp_on = false; + /* No more in the best path */ + child_node.in_best_path = false; + + child_node.fp_cert_equal = refine_equal_to_first; + if(current_node.fp_extendable == TreeNode::NO or + (current_node.fp_extendable == TreeNode::MAYBE and + child_node.fp_cert_equal == false)) + child_node.fp_extendable = TreeNode::NO; + else + child_node.fp_extendable = TreeNode::MAYBE; + child_node.cmp_to_best_path = refine_cmp_to_best; + + child_node.failure_recording_ival = 0; + child_node.cr_cep_stack_size = current_node.cr_cep_stack_size; + child_node.cr_cep_index = current_node.cr_cep_index; + child_node.cr_level = current_node.cr_level; + + certificate_index = certificate_current_path.size(); + + current_node.eqref_hash = eqref_hash; + current_node.subcertificate_length = + certificate_index - current_node.certificate_index; + + + /* + * The first encountered leaf node at the end of the "first path"? + */ + if(p.is_discrete() and first_path_info.empty()) + { + //fprintf(stdout, "Level %u: FIRST\n", child_level); fflush(stdout); + stats.nof_canupdates++; + /* + * Update labelings and their inverses + */ + update_labeling_and_its_inverse(first_path_labeling, + first_path_labeling_inv); + update_labeling_and_its_inverse(best_path_labeling, + best_path_labeling_inv); + /* + * Reset automorphism array + */ + reset_permutation(first_path_automorphism); + reset_permutation(best_path_automorphism); + /* + * Reset orbit information + */ + first_path_orbits.reset(); + best_path_orbits.reset(); + /* + * Reset group size + */ + stats.group_size.assign(1); + stats.group_size_approx = 1.0; + /* + * Reset all_same_level + */ + all_same_level = child_level; + /* + * Mark the current path to be the first and best one and save it + */ + const unsigned int base_size = search_stack.size(); + best_path_info.clear(); + //fprintf(stdout, " New base is: "); + for(unsigned int i = 0; i < base_size; i++) { + search_stack[i].fp_on = true; + search_stack[i].fp_cert_equal = true; + search_stack[i].fp_extendable = TreeNode::YES; + search_stack[i].in_best_path = true; + search_stack[i].cmp_to_best_path = 0; + PathInfo path_info; + path_info.splitting_element = search_stack[i].split_element; + path_info.certificate_index = search_stack[i].certificate_index; + path_info.eqref_hash = search_stack[i].eqref_hash; + path_info.subcertificate_length = search_stack[i].subcertificate_length; + first_path_info.push_back(path_info); + best_path_info.push_back(path_info); + //fprintf(stdout, "%u ", search_stack[i].split_element); + } + //fprintf(stdout, "\n"); fflush(stdout); + /* Copy certificates */ + certificate_first_path = certificate_current_path; + certificate_best_path = certificate_current_path; + + /* From now on, compare certificates when refining */ + refine_compare_certificate = true; + + if(opt_use_failure_recording) + failure_recording_hashes.resize(base_size); + + /* + for(unsigned int j = 0; j < search_stack.size(); j++) + fprintf(stderr, "%u ", search_stack[j].split_element); + fprintf(stderr, "\n"); + p.print(stderr); fprintf(stderr, "\n"); + */ + + /* + * Backtrack to the previous level + */ + continue; + } + + + if(p.is_discrete() and child_node.fp_cert_equal) + { + /* + * A leaf node that is equal to the first one. + * An automorphism found: aut[i] = elements[first_path_labeling[i]] + */ + goto handle_first_path_automorphism; + } + + + if(!p.is_discrete()) + { + Partition::Cell* next_split_cell = 0; + /* + * An internal, non-leaf node + */ + if(opt_use_comprec) + { + assert(p.nof_discrete_cells() <= + cr_cep_stack[cr_cep_index].discrete_cell_limit); + assert(cr_level == child_node.cr_level); + + + if(p.nof_discrete_cells() == + cr_cep_stack[cr_cep_index].discrete_cell_limit) + { + /* We have reached the end of a component */ + assert(cr_cep_index != 0); + CR_CEP& cep = cr_cep_stack[cr_cep_index]; + + /* First, compare with respect to the first path */ + if(first_path_info.empty() or child_node.fp_cert_equal) { + if(cep.first_checked == false) + { + /* First time, go to the next component */ + cep.first_checked = true; + } + else + { + assert(!first_path_info.empty()); + assert(cep.creation_level < search_stack.size()); + TreeNode& old_info = search_stack[cep.creation_level]; + /* If the component was found when on the first path, + * handle the found automorphism as the other + * first path automorphisms */ + if(old_info.fp_on) + goto handle_first_path_automorphism; + } + } + + if(canonical and + !first_path_info.empty() and + child_node.cmp_to_best_path >= 0) { + if(cep.best_checked == false) + { + /* First time, go to the next component */ + cep.best_checked = true; + } + else + { + assert(cep.creation_level < search_stack.size()); + TreeNode& old_info = search_stack[cep.creation_level]; + if(child_node.cmp_to_best_path == 0) { + /* If the component was found when on the best path, + * handle the found automorphism as the other + * best path automorphisms */ + if(old_info.in_best_path) + goto handle_best_path_automorphism; + /* Otherwise, we do not remember the automorhism as + * we didn't memorize the path that was invariant + * equal to the best one and passed through the + * component. + * Thus we can only backtrack to the previous level */ + child_node.cmp_to_best_path = -1; + if(!child_node.fp_cert_equal) + { + continue; + } + } + else { + assert(child_node.cmp_to_best_path > 0); + if(old_info.in_best_path) + { + stats.nof_canupdates++; + /* + * Update canonical labeling and its inverse + */ + for(unsigned int i = 0; i < N; i++) { + if(p.get_cell(p.elements[i])->is_unit()) { + best_path_labeling[p.elements[i]] = i; + best_path_labeling_inv[i] = p.elements[i]; + } + } + //update_labeling_and_its_inverse(best_path_labeling, best_path_labeling_inv); + /* Reset best path automorphism */ + reset_permutation(best_path_automorphism); + /* Reset best path orbit structure */ + best_path_orbits.reset(); + /* Mark to be the best one and save prefix */ + unsigned int postfix_start = cep.creation_level; + assert(postfix_start < best_path_info.size()); + while(p.get_cell(best_path_info[postfix_start].splitting_element)->is_unit()) { + postfix_start++; + assert(postfix_start < best_path_info.size()); + } + unsigned int postfix_start_cert = best_path_info[postfix_start].certificate_index; + std::vector best_path_temp = best_path_info; + best_path_info.clear(); + for(unsigned int i = 0; i < search_stack.size(); i++) { + TreeNode& ss_info = search_stack[i]; + PathInfo bp_info; + ss_info.cmp_to_best_path = 0; + ss_info.in_best_path = true; + bp_info.splitting_element = ss_info.split_element; + bp_info.certificate_index = ss_info.certificate_index; + bp_info.subcertificate_length = ss_info.subcertificate_length; + bp_info.eqref_hash = ss_info.eqref_hash; + best_path_info.push_back(bp_info); + } + /* Copy the postfix of the previous best path */ + for(unsigned int i = postfix_start; + i < best_path_temp.size(); + i++) + { + best_path_info.push_back(best_path_temp[i]); + best_path_info[best_path_info.size()-1].certificate_index = + best_path_info[best_path_info.size()-2].certificate_index + + best_path_info[best_path_info.size()-2].subcertificate_length; + } + std::vector certificate_best_path_old = certificate_best_path; + certificate_best_path = certificate_current_path; + for(unsigned int i = postfix_start_cert; i < certificate_best_path_old.size(); i++) + certificate_best_path.push_back(certificate_best_path_old[i]); + assert(certificate_best_path.size() == best_path_info.back().certificate_index + best_path_info.back().subcertificate_length); + /* Backtrack to the previous level */ + continue; + } + } + } + } + + /* No backtracking performed, go to next componenet */ + cr_level = cep.next_cr_level; + cr_cep_index = cep.next_cep_index; + } + + /* Check if the current component has been split into + * new non-uniformity subcomponents */ + //if(nucr_find_first_component(cr_level) == true and + // p.nof_discrete_cells() + cr_component_elements < + // cr_cep_stack[cr_cep_index].discrete_cell_limit) + if(nucr_find_first_component(cr_level, cr_component, + cr_component_elements, + next_split_cell) == true and + p.nof_discrete_cells() + cr_component_elements < + cr_cep_stack[cr_cep_index].discrete_cell_limit) + { + const unsigned int next_cr_level = + p.cr_split_level(cr_level, cr_component); + CR_CEP cep; + cep.creation_level = search_stack.size(); + cep.discrete_cell_limit = + p.nof_discrete_cells() + cr_component_elements; + cep.next_cr_level = cr_level; + cep.next_cep_index = cr_cep_index; + cep.first_checked = false; + cep.best_checked = false; + cr_cep_index = cr_cep_stack.size(); + cr_cep_stack.push_back(cep); + cr_level = next_cr_level; + } + } + + + /* + * Build the next node info + */ + /* Find the next cell to be splitted */ + if(!next_split_cell) + next_split_cell = find_next_cell_to_be_splitted(p.get_cell(p.elements[current_node.split_cell_first])); + //Partition::Cell * const next_split_cell = find_next_cell_to_be_splitted(p.get_cell(p.elements[current_node.split_cell_first])); + child_node.split_cell_first = next_split_cell->first; + child_node.split_element = TreeNode::SPLIT_START; + child_node.certificate_index = certificate_index; + child_node.partition_bt_point = p.set_backtrack_point(); + child_node.long_prune_redundant.clear(); + child_node.long_prune_begin = current_node.long_prune_begin; + + /* Save component recursion info for backtracking */ + child_node.cr_level = cr_level; + child_node.cr_cep_stack_size = cr_cep_stack.size(); + child_node.cr_cep_index = cr_cep_index; + + search_stack.push_back(child_node); + continue; + } + + /* + * A leaf node not in the first path or equivalent to the first path + */ + + + + if(child_node.cmp_to_best_path > 0) + { + /* + * A new, better representative found + */ + //fprintf(stdout, "Level %u: NEW BEST\n", child_level); fflush(stdout); + stats.nof_canupdates++; + /* + * Update canonical labeling and its inverse + */ + update_labeling_and_its_inverse(best_path_labeling, + best_path_labeling_inv); + /* Reset best path automorphism */ + reset_permutation(best_path_automorphism); + /* Reset best path orbit structure */ + best_path_orbits.reset(); + /* + * Mark the current path to be the best one and save it + */ + const unsigned int base_size = search_stack.size(); + assert(current_level+1 == base_size); + best_path_info.clear(); + for(unsigned int i = 0; i < base_size; i++) { + search_stack[i].cmp_to_best_path = 0; + search_stack[i].in_best_path = true; + PathInfo path_info; + path_info.splitting_element = search_stack[i].split_element; + path_info.certificate_index = search_stack[i].certificate_index; + path_info.subcertificate_length = search_stack[i].subcertificate_length; + path_info.eqref_hash = search_stack[i].eqref_hash; + best_path_info.push_back(path_info); + } + certificate_best_path = certificate_current_path; + /* + * Backtrack to the previous level + */ + continue; + } + + + handle_best_path_automorphism: + /* + * + * Best path automorphism handling + * + */ + { + + /* + * Equal to the previous best path + */ + if(p.is_discrete()) + { +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Verify that the automorphism is correctly built */ + for(unsigned int i = 0; i < N; i++) + assert(best_path_automorphism[i] == + p.elements[best_path_labeling[i]]); +#endif + } + else + { + /* An automorphism that was found before the partition was discrete. + * Set the image of all elements in non-disrete cells accordingly */ + for(Partition::Cell* c = p.first_nonsingleton_cell; c; + c = c->next_nonsingleton) { + for(unsigned int i = c->first; i < c->first+c->length; i++) + if(p.get_cell(p.elements[best_path_labeling[p.elements[i]]])->is_unit()) + best_path_automorphism[p.elements[best_path_labeling[p.elements[i]]]] = p.elements[i]; + else + best_path_automorphism[p.elements[i]] = p.elements[i]; + } + } + +#if defined(BLISS_VERIFY_AUTOMORPHISMS) + /* Verify that it really is an automorphism */ + if(!is_automorphism(best_path_automorphism)) + fatal_error("Best path automorhism validation check failed"); +#endif + + unsigned int gca_level_with_first = 0; + for(unsigned int i = search_stack.size(); i > 0; i--) { + if((int)first_path_info[gca_level_with_first].splitting_element != + search_stack[gca_level_with_first].split_element) + break; + gca_level_with_first++; + } + + unsigned int gca_level_with_best = 0; + for(unsigned int i = search_stack.size(); i > 0; i--) { + if((int)best_path_info[gca_level_with_best].splitting_element != + search_stack[gca_level_with_best].split_element) + break; + gca_level_with_best++; + } + + if(opt_use_long_prune) + { + /* Record automorphism */ + long_prune_add_automorphism(best_path_automorphism); + } + + /* + * Update orbit information + */ + update_orbit_information(best_path_orbits, best_path_automorphism); + + /* + * Update orbit information + */ + const unsigned int nof_old_orbits = first_path_orbits.nof_orbits(); + update_orbit_information(first_path_orbits, best_path_automorphism); + if(nof_old_orbits != first_path_orbits.nof_orbits()) + { + /* Some orbits were merged */ + /* Report automorphism */ + if(report_hook) + (*report_hook)(report_user_param, + get_nof_vertices(), + best_path_automorphism); + /* Update statistics */ + stats.nof_generators++; + } + + /* + * Compute backjumping level + */ + unsigned int backjumping_level = current_level+1-1; + if(!first_path_orbits.is_minimal_representative(search_stack[gca_level_with_first].split_element)) + { + backjumping_level = gca_level_with_first; + } + else + { + assert(!best_path_orbits.is_minimal_representative(search_stack[gca_level_with_best].split_element)); + backjumping_level = gca_level_with_best; + } + /* Backtrack */ + search_stack.resize(backjumping_level + 1); + continue; + } + + + _INTERNAL_ERROR(); + + + handle_first_path_automorphism: + /* + * + * A first-path automorphism: aut[i] = elements[first_path_labeling[i]] + * + */ + + + if(p.is_discrete()) + { +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Verify that the complete automorphism is correctly built */ + for(unsigned int i = 0; i < N; i++) + assert(first_path_automorphism[i] == + p.elements[first_path_labeling[i]]); +#endif + } + else + { + /* An automorphism that was found before the partition was discrete. + * Set the image of all elements in non-disrete cells accordingly */ + for(Partition::Cell* c = p.first_nonsingleton_cell; c; + c = c->next_nonsingleton) { + for(unsigned int i = c->first; i < c->first+c->length; i++) + if(p.get_cell(p.elements[first_path_labeling[p.elements[i]]])->is_unit()) + first_path_automorphism[p.elements[first_path_labeling[p.elements[i]]]] = p.elements[i]; + else + first_path_automorphism[p.elements[i]] = p.elements[i]; + } + } + +#if defined(BLISS_VERIFY_AUTOMORPHISMS) + /* Verify that it really is an automorphism */ + if(!is_automorphism(first_path_automorphism)) + fatal_error("First path automorphism validation check failed"); +#endif + + if(opt_use_long_prune) + { + long_prune_add_automorphism(first_path_automorphism); + } + + /* + * Update orbit information + */ + update_orbit_information(first_path_orbits, first_path_automorphism); + + /* + * Compute backjumping level + */ + for(unsigned int i = 0; i < search_stack.size(); i++) { + TreeNode& n = search_stack[i]; + if(n.fp_on) { + ; + } else { + n.fp_extendable = TreeNode::YES; + } + } + + /* Report automorphism by calling the user defined hook function */ + if(report_hook) + (*report_hook)(report_user_param, + get_nof_vertices(), + first_path_automorphism); + + /* Update statistics */ + stats.nof_generators++; + continue; + + } /* while(!search_stack.empty()) */ + + + + + /* Free "long prune" technique memory */ + if(opt_use_long_prune) + long_prune_deallocate(); + + /* Release component recursion data in partition */ + if(opt_use_comprec) + p.cr_free(); +} + + + + +void +AbstractGraph::find_automorphisms(Stats& stats, + void (*hook)(void *user_param, + unsigned int n, + const unsigned int *aut), + void *user_param) +{ + report_hook = hook; + report_user_param = user_param; + + search(false, stats); + + if(first_path_labeling) + { + free(first_path_labeling); + first_path_labeling = 0; + } + if(best_path_labeling) + { + free(best_path_labeling); + best_path_labeling = 0; + } +} + + +const unsigned int * +AbstractGraph::canonical_form(Stats& stats, + void (*hook)(void *user_param, + unsigned int n, + const unsigned int *aut), + void *user_param) +{ + + report_hook = hook; + report_user_param = user_param; + + search(true, stats); + + return best_path_labeling; +} + + + + +/*------------------------------------------------------------------------- + * + * Routines for directed graphs + * + *-------------------------------------------------------------------------*/ + +Digraph::Vertex::Vertex() +{ + color = 0; +} + + +Digraph::Vertex::~Vertex() +{ + ; +} + + +void +Digraph::Vertex::add_edge_to(const unsigned int other_vertex) +{ + edges_out.push_back(other_vertex); +} + + +void +Digraph::Vertex::add_edge_from(const unsigned int other_vertex) +{ + edges_in.push_back(other_vertex); +} + + +void +Digraph::Vertex::remove_duplicate_edges(std::vector& tmp) +{ +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Pre-conditions */ + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif + for(std::vector::iterator iter = edges_out.begin(); + iter != edges_out.end(); ) + { + const unsigned int dest_vertex = *iter; + if(tmp[dest_vertex] == true) + { + /* A duplicate edge found! */ + iter = edges_out.erase(iter); + } + else + { + /* Not seen earlier, mark as seen */ + tmp[dest_vertex] = true; + iter++; + } + } + + /* Clear tmp */ + for(std::vector::iterator iter = edges_out.begin(); + iter != edges_out.end(); + iter++) + { + tmp[*iter] = false; + } + + for(std::vector::iterator iter = edges_in.begin(); + iter != edges_in.end(); ) + { + const unsigned int dest_vertex = *iter; + if(tmp[dest_vertex] == true) + { + /* A duplicate edge found! */ + iter = edges_in.erase(iter); + } + else + { + /* Not seen earlier, mark as seen */ + tmp[dest_vertex] = true; + iter++; + } + } + + /* Clear tmp */ + for(std::vector::iterator iter = edges_in.begin(); + iter != edges_in.end(); + iter++) + { + tmp[*iter] = false; + } +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Post-conditions */ + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif +} + + +/** + * Sort the edges entering and leaving the vertex according to + * the vertex number of the other edge end. + * Time complexity: O(e log(e)), where e is the number of edges + * entering/leaving the vertex. + */ +void +Digraph::Vertex::sort_edges() +{ + std::sort(edges_in.begin(), edges_in.end()); + std::sort(edges_out.begin(), edges_out.end()); +} + + + + + +/*------------------------------------------------------------------------- + * + * Constructor and destructor for directed graphs + * + *-------------------------------------------------------------------------*/ + + +Digraph::Digraph(const unsigned int nof_vertices) +{ + vertices.resize(nof_vertices); + sh = shs_flm; +} + + +Digraph::~Digraph() +{ + ; +} + + +unsigned int +Digraph::add_vertex(const unsigned int color) +{ + const unsigned int new_vertex_num = vertices.size(); + vertices.resize(new_vertex_num + 1); + vertices.back().color = color; + return new_vertex_num; +} + + +void +Digraph::add_edge(const unsigned int vertex1, const unsigned int vertex2) +{ + assert(vertex1 < get_nof_vertices()); + assert(vertex2 < get_nof_vertices()); + vertices[vertex1].add_edge_to(vertex2); + vertices[vertex2].add_edge_from(vertex1); +} + + +void +Digraph::change_color(const unsigned int vertex, const unsigned int new_color) +{ + assert(vertex < get_nof_vertices()); + vertices[vertex].color = new_color; +} + + +void +Digraph::sort_edges() +{ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + vertices[i].sort_edges(); +} + + +int +Digraph::cmp(Digraph& other) +{ + /* Compare the numbers of vertices */ + if(get_nof_vertices() < other.get_nof_vertices()) + return -1; + if(get_nof_vertices() > other.get_nof_vertices()) + return 1; + /* Compare vertex colors */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + if(vertices[i].color < other.vertices[i].color) + return -1; + if(vertices[i].color > other.vertices[i].color) + return 1; + } + /* Compare vertex degrees */ + remove_duplicate_edges(); + other.remove_duplicate_edges(); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + if(vertices[i].nof_edges_in() < other.vertices[i].nof_edges_in()) + return -1; + if(vertices[i].nof_edges_in() > other.vertices[i].nof_edges_in()) + return 1; + if(vertices[i].nof_edges_out() < other.vertices[i].nof_edges_out()) + return -1; + if(vertices[i].nof_edges_out() > other.vertices[i].nof_edges_out()) + return 1; + } + /* Compare edges */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex& v1 = vertices[i]; + Vertex& v2 = other.vertices[i]; + v1.sort_edges(); + v2.sort_edges(); + std::vector::const_iterator ei1 = v1.edges_in.begin(); + std::vector::const_iterator ei2 = v2.edges_in.begin(); + while(ei1 != v1.edges_in.end()) + { + if(*ei1 < *ei2) + return -1; + if(*ei1 > *ei2) + return 1; + ei1++; + ei2++; + } + ei1 = v1.edges_out.begin(); + ei2 = v2.edges_out.begin(); + while(ei1 != v1.edges_out.end()) + { + if(*ei1 < *ei2) + return -1; + if(*ei1 > *ei2) + return 1; + ei1++; + ei2++; + } + } + return 0; +} + + + + +Digraph* +Digraph::permute(const std::vector& perm) const +{ + Digraph* const g = new Digraph(get_nof_vertices()); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v = vertices[i]; + g->change_color(perm[i], v.color); + for(std::vector::const_iterator ei = v.edges_out.begin(); + ei != v.edges_out.end(); + ei++) + { + g->add_edge(perm[i], perm[*ei]); + } + } + g->sort_edges(); + return g; +} + + +Digraph* +Digraph::permute(const unsigned int* const perm) const +{ + Digraph* const g = new Digraph(get_nof_vertices()); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex &v = vertices[i]; + g->change_color(perm[i], v.color); + for(std::vector::const_iterator ei = v.edges_out.begin(); + ei != v.edges_out.end(); + ei++) + { + g->add_edge(perm[i], perm[*ei]); + } + } + g->sort_edges(); + return g; +} + + + + + +/*------------------------------------------------------------------------- + * + * Print graph in graphviz format + * + *-------------------------------------------------------------------------*/ + + +void +Digraph::write_dot(const char* const filename) +{ + FILE* const fp = fopen(filename, "w"); + if(fp) + { + write_dot(fp); + fclose(fp); + } +} + + +void +Digraph::write_dot(FILE* const fp) +{ + remove_duplicate_edges(); + + fprintf(fp, "digraph g {\n"); + + unsigned int vnum = 0; + for(std::vector::const_iterator vi = vertices.begin(); + vi != vertices.end(); + vi++, vnum++) + { + const Vertex& v = *vi; + fprintf(fp, "v%u [label=\"%u:%u\"];\n", vnum, vnum, v.color); + for(std::vector::const_iterator ei = v.edges_out.begin(); + ei != v.edges_out.end(); + ei++) + { + fprintf(fp, "v%u -> v%u\n", vnum, *ei); + } + } + + fprintf(fp, "}\n"); +} + + +void +Digraph::remove_duplicate_edges() +{ + std::vector tmp(get_nof_vertices(), false); + + for(std::vector::iterator vi = vertices.begin(); + vi != vertices.end(); + vi++) + { +#if defined(BLISS_EXPENSIVE_CONSISTENCY_CHECKS) + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif + (*vi).remove_duplicate_edges(tmp); + } +} + + + + + +/*------------------------------------------------------------------------- + * + * Get a hash value for the graph. + * + *-------------------------------------------------------------------------*/ + +unsigned int +Digraph::get_hash() +{ + remove_duplicate_edges(); + sort_edges(); + + UintSeqHash h; + + h.update(get_nof_vertices()); + + /* Hash the color of each vertex */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + h.update(vertices[i].color); + } + + /* Hash the edges */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex &v = vertices[i]; + for(std::vector::const_iterator ei = v.edges_out.begin(); + ei != v.edges_out.end(); + ei++) + { + h.update(i); + h.update(*ei); + } + } + + return h.get_value(); +} + + + +/*------------------------------------------------------------------------- + * + * Read directed graph in the DIMACS format. + * Returns 0 if an error occurred. + * + *-------------------------------------------------------------------------*/ + +Digraph* +Digraph::read_dimacs(FILE* const fp, FILE* const errstr) +{ + Digraph* g = 0; + unsigned int nof_vertices; + unsigned int nof_edges; + unsigned int line_num = 1; + + const bool verbose = false; + FILE* const verbstr = stdout; + + /* Read comments and the problem definition line */ + while(1) + { + int c = getc(fp); + if(c == 'c') + { + /* A comment, ignore the rest of the line */ + while((c = getc(fp)) != '\n') + { + if(c == EOF) { + if(errstr) + fprintf(errstr, "error in line %u: not in DIMACS format\n", + line_num); + goto error_exit; + } + } + line_num++; + continue; + } + if(c == 'p') + { + /* The problem definition line */ + if(fscanf(fp, " edge %u %u\n", &nof_vertices, &nof_edges) != 2) + { + if(errstr) + fprintf(errstr, "error in line %u: not in DIMACS format\n", + line_num); + goto error_exit; + } + line_num++; + break; + } + if(errstr) + fprintf(errstr, "error in line %u: not in DIMACS format\n", line_num); + goto error_exit; + } + + if(nof_vertices <= 0) + { + if(errstr) + fprintf(errstr, "error: no vertices\n"); + goto error_exit; + } + if(verbose) + { + fprintf(verbstr, "Instance has %d vertices and %d edges\n", + nof_vertices, nof_edges); + fflush(verbstr); + } + + g = new Digraph(nof_vertices); + + // + // Read vertex colors + // + if(verbose) + { + fprintf(verbstr, "Reading vertex colors...\n"); + fflush(verbstr); + } + while(1) + { + int c = getc(fp); + if(c != 'n') + { + ungetc(c, fp); + break; + } + ungetc(c, fp); + unsigned int vertex; + unsigned int color; + if(fscanf(fp, "n %u %u\n", &vertex, &color) != 2) + { + if(errstr) + fprintf(errstr, "error in line %u: not in DIMACS format\n", + line_num); + goto error_exit; + } + if(!((vertex >= 1) && (vertex <= nof_vertices))) + { + if(errstr) + fprintf(errstr, + "error in line %u: vertex %u not in range [1,...%u]\n", + line_num, vertex, nof_vertices); + goto error_exit; + } + line_num++; + g->change_color(vertex - 1, color); + } + if(verbose) + { + fprintf(verbstr, "Done\n"); + fflush(verbstr); + } + + // + // Read edges + // + if(verbose) + { + fprintf(verbstr, "Reading edges...\n"); + fflush(verbstr); + } + for(unsigned i = 0; i < nof_edges; i++) + { + unsigned int from, to; + if(fscanf(fp, "e %u %u\n", &from, &to) != 2) + { + if(errstr) + fprintf(errstr, "error in line %u: not in DIMACS format\n", + line_num); + goto error_exit; + } + if(not((1 <= from) and (from <= nof_vertices))) + { + if(errstr) + fprintf(errstr, + "error in line %u: vertex %u not in range [1,...%u]\n", + line_num, from, nof_vertices); + goto error_exit; + } + if(not((1 <= to) and (to <= nof_vertices))) + { + if(errstr) + fprintf(errstr, + "error in line %u: vertex %u not in range [1,...%u]\n", + line_num, to, nof_vertices); + goto error_exit; + } + line_num++; + g->add_edge(from-1, to-1); + } + if(verbose) + { + fprintf(verbstr, "Done\n"); + fflush(verbstr); + } + + return g; + + error_exit: + if(g) + delete g; + return 0; +} + + + + + +void +Digraph::write_dimacs(FILE* const fp) +{ + remove_duplicate_edges(); + sort_edges(); + + /* First count the total number of edges */ + unsigned int nof_edges = 0; + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + nof_edges += vertices[i].edges_out.size(); + } + + /* Output the "header" line */ + fprintf(fp, "p edge %u %u\n", get_nof_vertices(), nof_edges); + + /* Print the color of each vertex */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex& v = vertices[i]; + fprintf(fp, "n %u %u\n", i+1, v.color); + /* + if(v.color != 0) + { + fprintf(fp, "n %u %u\n", i+1, v.color); + } + */ + } + + /* Print the edges */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex& v = vertices[i]; + for(std::vector::const_iterator ei = v.edges_out.begin(); + ei != v.edges_out.end(); + ei++) + { + fprintf(fp, "e %u %u\n", i+1, (*ei)+1); + } + } +} + + + + + + + + +/*------------------------------------------------------------------------- + * + * Partition independent invariants + * + *-------------------------------------------------------------------------*/ + +unsigned int +Digraph::vertex_color_invariant(const Digraph* const g, const unsigned int vnum) +{ + return g->vertices[vnum].color; +} + +unsigned int +Digraph::indegree_invariant(const Digraph* const g, const unsigned int vnum) +{ + return g->vertices[vnum].nof_edges_in(); +} + +unsigned int +Digraph::outdegree_invariant(const Digraph* const g, const unsigned int vnum) +{ + return g->vertices[vnum].nof_edges_out(); +} + +unsigned int +Digraph::selfloop_invariant(const Digraph* const g, const unsigned int vnum) +{ + /* Quite inefficient but luckily not in the critical path */ + const Vertex& v = g->vertices[vnum]; + for(std::vector::const_iterator ei = v.edges_out.begin(); + ei != v.edges_out.end(); + ei++) + { + if(*ei == vnum) + return 1; + } + return 0; +} + + + + + +/*------------------------------------------------------------------------- + * + * Refine the partition p according to a partition independent invariant + * + *-------------------------------------------------------------------------*/ + +bool +Digraph::refine_according_to_invariant(unsigned int (*inv)(const Digraph* const g, + const unsigned int v)) +{ + bool refined = false; + + for(Partition::Cell* cell = p.first_nonsingleton_cell; cell; ) + { + + Partition::Cell* const next_cell = cell->next_nonsingleton; + const unsigned int* ep = p.elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--, ep++) + { + unsigned int ival = inv(this, *ep); + p.invariant_values[*ep] = ival; + if(ival > cell->max_ival) { + cell->max_ival = ival; + cell->max_ival_count = 1; + } + else if(ival == cell->max_ival) { + cell->max_ival_count++; + } + } + Partition::Cell* const last_new_cell = p.zplit_cell(cell, true); + refined |= (last_new_cell != cell); + cell = next_cell; + } + + return refined; +} + + + + + +/*------------------------------------------------------------------------- + * + * Split the neighbourhood of a cell according to the equitable invariant + * + *-------------------------------------------------------------------------*/ + +bool +Digraph::split_neighbourhood_of_cell(Partition::Cell* const cell) +{ + + + const bool was_equal_to_first = refine_equal_to_first; + + if(compute_eqref_hash) + { + eqref_hash.update(cell->first); + eqref_hash.update(cell->length); + } + + const unsigned int* ep = p.elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--) + { + const Vertex& v = vertices[*ep++]; + + std::vector::const_iterator ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j != 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(dest_vertex); + if(neighbour_cell->is_unit()) + continue; + const unsigned int ival = ++p.invariant_values[dest_vertex]; + if(ival > neighbour_cell->max_ival) { + neighbour_cell->max_ival = ival; + neighbour_cell->max_ival_count = 1; + if(ival == 1) + neighbour_heap.insert(neighbour_cell->first); + } + else if(ival == neighbour_cell->max_ival) { + neighbour_cell->max_ival_count++; + } + } + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = p.get_cell(p.elements[start]); + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + + Partition::Cell* const last_new_cell = p.zplit_cell(neighbour_cell, true); + + /* Update certificate and hash if needed */ + const Partition::Cell* c = neighbour_cell; + while(1) + { + if(in_search) + { + /* Build certificate */ + cert_add_redundant(CERT_SPLIT, c->first, c->length); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + if(compute_eqref_hash) + { + eqref_hash.update(c->first); + eqref_hash.update(c->length); + } + if(c == last_new_cell) + break; + c = c->next; + } + } + + if(cell->is_in_splitting_queue()) + { + return false; + } + + + ep = p.elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--) + { + const Vertex& v = vertices[*ep++]; + + std::vector::const_iterator ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(dest_vertex); + if(neighbour_cell->is_unit()) + continue; + const unsigned int ival = ++p.invariant_values[dest_vertex]; + if(ival > neighbour_cell->max_ival) + { + neighbour_cell->max_ival = ival; + neighbour_cell->max_ival_count = 1; + if(ival == 1) + neighbour_heap.insert(neighbour_cell->first); + } + else if(ival == neighbour_cell->max_ival) { + neighbour_cell->max_ival_count++; + } + } + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = p.get_cell(p.elements[start]); + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + Partition::Cell* const last_new_cell = p.zplit_cell(neighbour_cell, true); + + /* Update certificate and hash if needed */ + const Partition::Cell* c = neighbour_cell; + while(1) + { + if(in_search) + { + /* Build certificate */ + cert_add_redundant(CERT_SPLIT, c->first, c->length); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + if(compute_eqref_hash) + { + eqref_hash.update(c->first); + eqref_hash.update(c->length); + } + if(c == last_new_cell) + break; + c = c->next; + } + } + + + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + return true; + + return false; + + worse_exit: + /* Clear neighbour heap */ + UintSeqHash rest; + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = p.get_cell(p.elements[start]); + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(neighbour_cell->first); + rest.update(neighbour_cell->length); + rest.update(neighbour_cell->max_ival); + rest.update(neighbour_cell->max_ival_count); + } + neighbour_cell->max_ival = 0; + neighbour_cell->max_ival_count = 0; + p.clear_ivs(neighbour_cell); + } + if(opt_use_failure_recording and was_equal_to_first) + { + for(unsigned int i = p.splitting_queue.size(); i > 0; i--) + { + Partition::Cell* const cell = p.splitting_queue.pop_front(); + rest.update(cell->first); + rest.update(cell->length); + p.splitting_queue.push_back(cell); + } + rest.update(failure_recording_fp_deviation); + failure_recording_fp_deviation = rest.get_value(); + } + + return true; +} + + +bool +Digraph::split_neighbourhood_of_unit_cell(Partition::Cell* const unit_cell) +{ + + + const bool was_equal_to_first = refine_equal_to_first; + + if(compute_eqref_hash) + { + eqref_hash.update(0x87654321); + eqref_hash.update(unit_cell->first); + eqref_hash.update(1); + } + + const Vertex& v = vertices[p.elements[unit_cell->first]]; + + /* + * Phase 1 + * Refine neighbours according to the edges that leave the vertex v + */ + std::vector::const_iterator ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(dest_vertex); + + if(neighbour_cell->is_unit()) { + if(in_search) { + /* Remember neighbour in order to generate certificate */ + neighbour_heap.insert(neighbour_cell->first); + } + continue; + } + if(neighbour_cell->max_ival_count == 0) + { + neighbour_heap.insert(neighbour_cell->first); + } + neighbour_cell->max_ival_count++; + + unsigned int* const swap_position = + p.elements + neighbour_cell->first + neighbour_cell->length - + neighbour_cell->max_ival_count; + *p.in_pos[dest_vertex] = *swap_position; + p.in_pos[*swap_position] = p.in_pos[dest_vertex]; + *swap_position = dest_vertex; + p.in_pos[dest_vertex] = swap_position; + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* neighbour_cell = p.get_cell(p.elements[start]); + +#if defined(BLISS_CONSISTENCY_CHECKS) + assert(neighbour_cell->first == start); + if(neighbour_cell->is_unit()) { + assert(neighbour_cell->max_ival_count == 0); + } else { + assert(neighbour_cell->max_ival_count > 0); + assert(neighbour_cell->max_ival_count <= neighbour_cell->length); + } +#endif + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + if(neighbour_cell->length > 1 and + neighbour_cell->max_ival_count != neighbour_cell->length) + { + + Partition::Cell* const new_cell = + p.aux_split_in_two(neighbour_cell, + neighbour_cell->length - + neighbour_cell->max_ival_count); + unsigned int* ep = p.elements + new_cell->first; + unsigned int* const lp = p.elements+new_cell->first+new_cell->length; + while(ep < lp) + { + p.element_to_cell_map[*ep] = new_cell; + ep++; + } + neighbour_cell->max_ival_count = 0; + + + if(compute_eqref_hash) + { + /* Update hash */ + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(0); + eqref_hash.update(new_cell->first); + eqref_hash.update(new_cell->length); + eqref_hash.update(1); + } + + /* Add cells in splitting_queue */ + if(neighbour_cell->is_in_splitting_queue()) { + /* Both cells must be included in splitting_queue in order + to have refinement to equitable partition */ + p.splitting_queue_add(new_cell); + } else { + Partition::Cell *min_cell, *max_cell; + if(neighbour_cell->length <= new_cell->length) { + min_cell = neighbour_cell; + max_cell = new_cell; + } else { + min_cell = new_cell; + max_cell = neighbour_cell; + } + /* Put the smaller cell in splitting_queue */ + p.splitting_queue_add(min_cell); + if(max_cell->is_unit()) { + /* Put the "larger" cell also in splitting_queue */ + p.splitting_queue_add(max_cell); + } + } + /* Update pointer for certificate generation */ + neighbour_cell = new_cell; + } + else + { + neighbour_cell->max_ival_count = 0; + } + + /* + * Build certificate if required + */ + if(in_search) + { + for(unsigned int i = neighbour_cell->first, + j = neighbour_cell->length; + j > 0; + j--, i++) + { + /* Build certificate */ + cert_add(CERT_EDGE, unit_cell->first, i); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + } /* if(in_search) */ + } /* while(!neighbour_heap.is_empty()) */ + + /* + * Phase 2 + * Refine neighbours according to the edges that enter the vertex v + */ + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(dest_vertex); + + if(neighbour_cell->is_unit()) { + if(in_search) { + neighbour_heap.insert(neighbour_cell->first); + } + continue; + } + if(neighbour_cell->max_ival_count == 0) + { + neighbour_heap.insert(neighbour_cell->first); + } + neighbour_cell->max_ival_count++; + + unsigned int* const swap_position = + p.elements + neighbour_cell->first + neighbour_cell->length - + neighbour_cell->max_ival_count; + *p.in_pos[dest_vertex] = *swap_position; + p.in_pos[*swap_position] = p.in_pos[dest_vertex]; + *swap_position = dest_vertex; + p.in_pos[dest_vertex] = swap_position; + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* neighbour_cell = p.get_cell(p.elements[start]); + +#if defined(BLISS_CONSISTENCY_CHECKS) + assert(neighbour_cell->first == start); + if(neighbour_cell->is_unit()) { + assert(neighbour_cell->max_ival_count == 0); + } else { + assert(neighbour_cell->max_ival_count > 0); + assert(neighbour_cell->max_ival_count <= neighbour_cell->length); + } +#endif + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + if(neighbour_cell->length > 1 and + neighbour_cell->max_ival_count != neighbour_cell->length) + { + Partition::Cell* const new_cell = + p.aux_split_in_two(neighbour_cell, + neighbour_cell->length - + neighbour_cell->max_ival_count); + unsigned int* ep = p.elements + new_cell->first; + unsigned int* const lp = p.elements+new_cell->first+new_cell->length; + while(ep < lp) { + p.element_to_cell_map[*ep] = new_cell; + ep++; + } + neighbour_cell->max_ival_count = 0; + + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(0); + eqref_hash.update(new_cell->first); + eqref_hash.update(new_cell->length); + eqref_hash.update(1); + } + + /* Add cells in splitting_queue */ + if(neighbour_cell->is_in_splitting_queue()) { + /* Both cells must be included in splitting_queue in order + to have refinement to equitable partition */ + p.splitting_queue_add(new_cell); + } else { + Partition::Cell *min_cell, *max_cell; + if(neighbour_cell->length <= new_cell->length) { + min_cell = neighbour_cell; + max_cell = new_cell; + } else { + min_cell = new_cell; + max_cell = neighbour_cell; + } + /* Put the smaller cell in splitting_queue */ + p.splitting_queue_add(min_cell); + if(max_cell->is_unit()) { + /* Put the "larger" cell also in splitting_queue */ + p.splitting_queue_add(max_cell); + } + } + /* Update pointer for certificate generation */ + neighbour_cell = new_cell; + } + else + { + neighbour_cell->max_ival_count = 0; + } + + /* + * Build certificate if required + */ + if(in_search) + { + for(unsigned int i = neighbour_cell->first, + j = neighbour_cell->length; + j > 0; + j--, i++) + { + /* Build certificate */ + cert_add(CERT_EDGE, i, unit_cell->first); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + } /* if(in_search) */ + } /* while(!neighbour_heap.is_empty()) */ + + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + return true; + + return false; + + worse_exit: + /* Clear neighbour heap */ + UintSeqHash rest; + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = p.get_cell(p.elements[start]); + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(neighbour_cell->first); + rest.update(neighbour_cell->length); + rest.update(neighbour_cell->max_ival_count); + } + neighbour_cell->max_ival_count = 0; + } + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(failure_recording_fp_deviation); + failure_recording_fp_deviation = rest.get_value(); + } + return true; +} + + + + + +/*------------------------------------------------------------------------- + * + * Check whether the current partition p is equitable. + * Performance: very slow, use only for debugging purposes. + * + *-------------------------------------------------------------------------*/ + +bool +Digraph::is_equitable() const +{ + const unsigned int N = get_nof_vertices(); + if(N == 0) + return true; + + std::vector first_count = std::vector(N, 0); + std::vector other_count = std::vector(N, 0); + + /* + * Check equitabledness w.r.t. outgoing edges + */ + for(Partition::Cell* cell = p.first_cell; cell; cell = cell->next) + { + if(cell->is_unit()) + continue; + + unsigned int* ep = p.elements + cell->first; + const Vertex& first_vertex = vertices[*ep++]; + + /* Count outgoing edges of the first vertex for cells */ + for(std::vector::const_iterator ei = + first_vertex.edges_out.begin(); + ei != first_vertex.edges_out.end(); + ei++) + { + first_count[p.get_cell(*ei)->first]++; + } + + /* Count and compare outgoing edges of the other vertices */ + for(unsigned int i = cell->length; i > 1; i--) + { + const Vertex &vertex = vertices[*ep++]; + for(std::vector::const_iterator ei = + vertex.edges_out.begin(); + ei != vertex.edges_out.end(); + ei++) + { + other_count[p.get_cell(*ei)->first]++; + } + for(Partition::Cell *cell2 = p.first_cell; + cell2; + cell2 = cell2->next) + { + if(first_count[cell2->first] != other_count[cell2->first]) + { + /* Not equitable */ + return false; + } + other_count[cell2->first] = 0; + } + } + /* Reset first_count */ + for(unsigned int i = 0; i < N; i++) + first_count[i] = 0; + } + + + /* + * Check equitabledness w.r.t. incoming edges + */ + for(Partition::Cell* cell = p.first_cell; cell; cell = cell->next) + { + if(cell->is_unit()) + continue; + + unsigned int* ep = p.elements + cell->first; + const Vertex& first_vertex = vertices[*ep++]; + + /* Count incoming edges of the first vertex for cells */ + for(std::vector::const_iterator ei = + first_vertex.edges_in.begin(); + ei != first_vertex.edges_in.end(); + ei++) + { + first_count[p.get_cell(*ei)->first]++; + } + + /* Count and compare incoming edges of the other vertices */ + for(unsigned int i = cell->length; i > 1; i--) + { + const Vertex &vertex = vertices[*ep++]; + for(std::vector::const_iterator ei = + vertex.edges_in.begin(); + ei != vertex.edges_in.end(); + ei++) + { + other_count[p.get_cell(*ei)->first]++; + } + for(Partition::Cell *cell2 = p.first_cell; + cell2; + cell2 = cell2->next) + { + if(first_count[cell2->first] != other_count[cell2->first]) + { + /* Not equitable */ + return false; + } + other_count[cell2->first] = 0; + } + } + /* Reset first_count */ + for(unsigned int i = 0; i < N; i++) + first_count[i] = 0; + } + return true; +} + + + + + +/*------------------------------------------------------------------------- + * + * Build the initial equitable partition + * + *-------------------------------------------------------------------------*/ + +void +Digraph::make_initial_equitable_partition() +{ + refine_according_to_invariant(&vertex_color_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_according_to_invariant(&selfloop_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_according_to_invariant(&outdegree_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_according_to_invariant(&indegree_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_to_equitable(); + //p.print_signature(stderr); fprintf(stderr, "\n"); +} + + + + + +/*------------------------------------------------------------------------- + * + * Find the next cell to be splitted + * + *-------------------------------------------------------------------------*/ + +Partition::Cell* +Digraph::find_next_cell_to_be_splitted(Partition::Cell* cell) +{ + switch(sh) { + case shs_f: return sh_first(); + case shs_fs: return sh_first_smallest(); + case shs_fl: return sh_first_largest(); + case shs_fm: return sh_first_max_neighbours(); + case shs_fsm: return sh_first_smallest_max_neighbours(); + case shs_flm: return sh_first_largest_max_neighbours(); + default: + fatal_error("Internal error - unknown splitting heuristics"); + return 0; + } +} + +/** \internal + * A splitting heuristic. + * Returns the first nonsingleton cell in the current partition. + * The argument \a cell is ignored. + */ +Partition::Cell* +Digraph::sh_first() +{ + Partition::Cell* best_cell = 0; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + best_cell = cell; + break; + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first smallest nonsingleton cell in the current partition. + * The argument \a cell is ignored. + */ +Partition::Cell* +Digraph::sh_first_smallest() +{ + Partition::Cell* best_cell = 0; + unsigned int best_size = UINT_MAX; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + if(cell->length < best_size) + { + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first largest nonsingleton cell in the current partition. + * The argument \a cell is ignored. + */ +Partition::Cell* +Digraph::sh_first_largest() +{ + Partition::Cell* best_cell = 0; + unsigned int best_size = 0; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + if(cell->length > best_size) + { + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Digraph::sh_first_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + int value = 0; + const Vertex &v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei; + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + Partition::Cell * const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + Partition::Cell * const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + if(value > best_value) + { + best_value = value; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first smallest nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Digraph::sh_first_smallest_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + unsigned int best_size = UINT_MAX; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + + int value = 0; + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei; + + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + Partition::Cell * const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell * const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + Partition::Cell * const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell * const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + if((value > best_value) or + (value == best_value and cell->length < best_size)) + { + best_value = value; + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first largest nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Digraph::sh_first_largest_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + unsigned int best_size = 0; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + + int value = 0; + const Vertex &v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei; + + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + Partition::Cell* const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + Partition::Cell* const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + + if((value > best_value) || + (value == best_value && cell->length > best_size)) + { + best_value = value; + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + + + + + + +/*------------------------------------------------------------------------ + * + * Initialize the certificate size and memory + * + *-------------------------------------------------------------------------*/ + +void +Digraph::initialize_certificate() +{ + certificate_index = 0; + certificate_current_path.clear(); + certificate_first_path.clear(); + certificate_best_path.clear(); +} + + + +/* + * Check whether perm is an automorphism. + * Slow, mainly for debugging and validation purposes. + */ +bool +Digraph::is_automorphism(unsigned int* const perm) +{ + std::set > edges1; + std::set > edges2; + +#if defined(BLISS_CONSISTENCY_CHECKS) + if(!is_permutation(get_nof_vertices(), perm)) + _INTERNAL_ERROR(); +#endif + + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex& v1 = vertices[i]; + Vertex& v2 = vertices[perm[i]]; + + edges1.clear(); + for(std::vector::iterator ei = v1.edges_in.begin(); + ei != v1.edges_in.end(); + ei++) + edges1.insert(perm[*ei]); + edges2.clear(); + for(std::vector::iterator ei = v2.edges_in.begin(); + ei != v2.edges_in.end(); + ei++) + edges2.insert(*ei); + if(!(edges1 == edges2)) + return false; + + edges1.clear(); + for(std::vector::iterator ei = v1.edges_out.begin(); + ei != v1.edges_out.end(); + ei++) + edges1.insert(perm[*ei]); + edges2.clear(); + for(std::vector::iterator ei = v2.edges_out.begin(); + ei != v2.edges_out.end(); + ei++) + edges2.insert(*ei); + if(!(edges1 == edges2)) + return false; + } + + return true; +} + +bool +Digraph::is_automorphism(const std::vector& perm) const +{ + + if(!(perm.size() == get_nof_vertices() and is_permutation(perm))) + return false; + + std::set > edges1; + std::set > edges2; + + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v1 = vertices[i]; + const Vertex& v2 = vertices[perm[i]]; + + edges1.clear(); + for(std::vector::const_iterator ei = v1.edges_in.begin(); + ei != v1.edges_in.end(); + ei++) + edges1.insert(perm[*ei]); + edges2.clear(); + for(std::vector::const_iterator ei = v2.edges_in.begin(); + ei != v2.edges_in.end(); + ei++) + edges2.insert(*ei); + if(!(edges1 == edges2)) + return false; + + edges1.clear(); + for(std::vector::const_iterator ei = v1.edges_out.begin(); + ei != v1.edges_out.end(); + ei++) + edges1.insert(perm[*ei]); + edges2.clear(); + for(std::vector::const_iterator ei = v2.edges_out.begin(); + ei != v2.edges_out.end(); + ei++) + edges2.insert(*ei); + if(!(edges1 == edges2)) + return false; + } + + return true; +} + + + + +bool +Digraph::nucr_find_first_component(const unsigned int level) +{ + + cr_component.clear(); + cr_component_elements = 0; + + /* Find first non-discrete cell in the component level */ + Partition::Cell* first_cell = p.first_nonsingleton_cell; + while(first_cell) + { + if(p.cr_get_level(first_cell->first) == level) + break; + first_cell = first_cell->next_nonsingleton; + } + + /* The component is discrete, return false */ + if(!first_cell) + return false; + + std::vector component; + first_cell->max_ival = 1; + component.push_back(first_cell); + + for(unsigned int i = 0; i < component.size(); i++) + { + Partition::Cell* const cell = component[i]; + + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei; + + ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + + /* Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Already marked to be in the same component? */ + if(neighbour_cell->max_ival == 1) + continue; + /* Is the neighbour at the same component recursion level? */ + if(p.cr_get_level(neighbour_cell->first) != level) + continue; + + if(neighbour_cell->max_ival_count == 0) + neighbour_heap.insert(neighbour_cell->first); + neighbour_cell->max_ival_count++; + } + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = + p.get_cell(p.elements[start]); + + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + neighbour_cell->max_ival_count = 0; + neighbour_cell->max_ival = 1; + component.push_back(neighbour_cell); + } + + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + + /* Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Already marked to be in the same component? */ + if(neighbour_cell->max_ival == 1) + continue; + /* Is the neighbour at the same component recursion level? */ + if(p.cr_get_level(neighbour_cell->first) != level) + continue; + + if(neighbour_cell->max_ival_count == 0) + neighbour_heap.insert(neighbour_cell->first); + neighbour_cell->max_ival_count++; + } + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = + p.get_cell(p.elements[start]); + + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + neighbour_cell->max_ival_count = 0; + neighbour_cell->max_ival = 1; + component.push_back(neighbour_cell); + } + } + + for(unsigned int i = 0; i < component.size(); i++) + { + Partition::Cell* const cell = component[i]; + cell->max_ival = 0; + cr_component.push_back(cell->first); + cr_component_elements += cell->length; + } + + if(verbstr and verbose_level > 2) { + fprintf(verbstr, "NU-component with %lu cells and %u vertices\n", + (long unsigned)cr_component.size(), cr_component_elements); + fflush(verbstr); + } + + return true; +} + + + + + +bool +Digraph::nucr_find_first_component(const unsigned int level, + std::vector& component, + unsigned int& component_elements, + Partition::Cell*& sh_return) +{ + + component.clear(); + component_elements = 0; + sh_return = 0; + unsigned int sh_first = 0; + unsigned int sh_size = 0; + unsigned int sh_nuconn = 0; + + /* Find first non-discrete cell in the component level */ + Partition::Cell* first_cell = p.first_nonsingleton_cell; + while(first_cell) + { + if(p.cr_get_level(first_cell->first) == level) + break; + first_cell = first_cell->next_nonsingleton; + } + + if(!first_cell) + { + /* The component is discrete, return false */ + return false; + } + + std::vector comp; + KStack neighbours; + neighbours.init(get_nof_vertices()); + + first_cell->max_ival = 1; + comp.push_back(first_cell); + + for(unsigned int i = 0; i < comp.size(); i++) + { + Partition::Cell* const cell = comp[i]; + + unsigned int nuconn = 1; + + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei; + + /*| Phase 1: outgoing edges */ + ei = v.edges_out.begin(); + for(unsigned int j = v.nof_edges_out(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + + /* Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Is the neighbour at the same component recursion level? */ + //if(p.cr_get_level(neighbour_cell->first) != level) + // continue; + if(neighbour_cell->max_ival_count == 0) + neighbours.push(neighbour_cell); + neighbour_cell->max_ival_count++; + } + while(!neighbours.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbours.pop(); + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + nuconn++; + neighbour_cell->max_ival_count = 0; + if(neighbour_cell->max_ival == 0) { + comp.push_back(neighbour_cell); + neighbour_cell->max_ival = 1; + } + } + + /*| Phase 2: incoming edges */ + ei = v.edges_in.begin(); + for(unsigned int j = v.nof_edges_in(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + /*| Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Is the neighbour at the same component recursion level? */ + //if(p.cr_get_level(neighbour_cell->first) != level) + // continue; + if(neighbour_cell->max_ival_count == 0) + neighbours.push(neighbour_cell); + neighbour_cell->max_ival_count++; + } + while(!neighbours.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbours.pop(); + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + nuconn++; + neighbour_cell->max_ival_count = 0; + if(neighbour_cell->max_ival == 0) { + comp.push_back(neighbour_cell); + neighbour_cell->max_ival = 1; + } + } + + /*| Phase 3: splitting heuristics */ + switch(sh) { + case shs_f: + if(sh_return == 0 or + cell->first <= sh_first) { + sh_return = cell; + sh_first = cell->first; + } + break; + case shs_fs: + if(sh_return == 0 or + cell->length < sh_size or + (cell->length == sh_size and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + } + break; + case shs_fl: + if(sh_return == 0 or + cell->length > sh_size or + (cell->length == sh_size and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + } + break; + case shs_fm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_nuconn = nuconn; + } + break; + case shs_fsm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and + (cell->length < sh_size or + (cell->length == sh_size and cell->first <= sh_first)))) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + sh_nuconn = nuconn; + } + break; + case shs_flm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and + (cell->length > sh_size or + (cell->length == sh_size and cell->first <= sh_first)))) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + sh_nuconn = nuconn; + } + break; + default: + fatal_error("Internal error - unknown splitting heuristics"); + return 0; + } + } + assert(sh_return); + + for(unsigned int i = 0; i < comp.size(); i++) + { + Partition::Cell* const cell = comp[i]; + cell->max_ival = 0; + component.push_back(cell->first); + component_elements += cell->length; + } + + if(verbstr and verbose_level > 2) { + fprintf(verbstr, "NU-component with %lu cells and %u vertices\n", + (long unsigned)component.size(), component_elements); + fflush(verbstr); + } + + return true; +} + + + + +/*------------------------------------------------------------------------- + * + * Routines for undirected graphs + * + *-------------------------------------------------------------------------*/ + +Graph::Vertex::Vertex() +{ + color = 0; +} + + +Graph::Vertex::~Vertex() +{ + ; +} + + +void +Graph::Vertex::add_edge(const unsigned int other_vertex) +{ + edges.push_back(other_vertex); +} + + +void +Graph::Vertex::remove_duplicate_edges(std::vector& tmp) +{ +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Pre-conditions */ + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif + for(std::vector::iterator iter = edges.begin(); + iter != edges.end(); ) + { + const unsigned int dest_vertex = *iter; + if(tmp[dest_vertex] == true) + { + /* A duplicate edge found! */ + iter = edges.erase(iter); + } + else + { + /* Not seen earlier, mark as seen */ + tmp[dest_vertex] = true; + iter++; + } + } + + /* Clear tmp */ + for(std::vector::iterator iter = edges.begin(); + iter != edges.end(); + iter++) + { + tmp[*iter] = false; + } +#if defined(BLISS_CONSISTENCY_CHECKS) + /* Post-conditions */ + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif +} + + +/** + * Sort the edges leaving the vertex according to + * the vertex number of the other edge end. + * Time complexity: O(e log(e)), where e is the number of edges + * leaving the vertex. + */ +void +Graph::Vertex::sort_edges() +{ + std::sort(edges.begin(), edges.end()); +} + + + +/*------------------------------------------------------------------------- + * + * Constructor and destructor for undirected graphs + * + *-------------------------------------------------------------------------*/ + + +Graph::Graph(const unsigned int nof_vertices) +{ + vertices.resize(nof_vertices); + sh = shs_flm; +} + + +Graph::~Graph() +{ + ; +} + + +unsigned int +Graph::add_vertex(const unsigned int color) +{ + const unsigned int vertex_num = vertices.size(); + vertices.resize(vertex_num + 1); + vertices.back().color = color; + return vertex_num; +} + + +void +Graph::add_edge(const unsigned int vertex1, const unsigned int vertex2) +{ + //fprintf(stderr, "(%u,%u) ", vertex1, vertex2); + vertices[vertex1].add_edge(vertex2); + vertices[vertex2].add_edge(vertex1); +} + + +void +Graph::change_color(const unsigned int vertex, const unsigned int color) +{ + vertices[vertex].color = color; +} + + + + + +/*------------------------------------------------------------------------- + * + * Read graph in the DIMACS format. + * Returns 0 if an error occurred. + * + *-------------------------------------------------------------------------*/ + +Graph* +Graph::read_dimacs(FILE* const fp, FILE* const errstr) +{ + Graph *g = 0; + unsigned int nof_vertices; + unsigned int nof_edges; + unsigned int line_num = 1; + int c; + + const bool verbose = false; + FILE* const verbstr = stdout; + + /* Read comments and the problem definition line */ + while(1) + { + c = getc(fp); + if(c == 'c') + { + /* A comment, ignore the rest of the line */ + while((c = getc(fp)) != '\n') + { + if(c == EOF) + { + if(errstr) + fprintf(errstr, + "error in line %u: not in DIMACS format\n", + line_num); + goto error_exit; + } + } + line_num++; + continue; + } + if(c == 'p') + { + /* The problem definition line */ + if(fscanf(fp, " edge %u %u\n", &nof_vertices, &nof_edges) != 2) + { + if(errstr) + fprintf(errstr, "error in line %u: not in DIMACS format\n", + line_num); + goto error_exit; + } + line_num++; + break; + } + if(errstr) + fprintf(errstr, "error in line %u: not in DIMACS format\n", line_num); + goto error_exit; + } + + if(nof_vertices <= 0) + { + if(errstr) + fprintf(errstr, "error: no vertices\n"); + goto error_exit; + } + if(verbose) + { + fprintf(verbstr, "Instance has %d vertices and %d edges\n", + nof_vertices, nof_edges); + fflush(verbstr); + } + + g = new Graph(nof_vertices); + + // + // Read vertex colors + // + if(verbose) + { + fprintf(verbstr, "Reading vertex colors...\n"); + fflush(verbstr); + } + while(1) + { + c = getc(fp); + if(c != 'n') + { + ungetc(c, fp); + break; + } + ungetc(c, fp); + unsigned int vertex; + unsigned int color; + if(fscanf(fp, "n %u %u\n", &vertex, &color) != 2) + { + if(errstr) + fprintf(errstr, "error in line %u: not in DIMACS format\n", + line_num); + goto error_exit; + } + if(!((vertex >= 1) && (vertex <= nof_vertices))) + { + if(errstr) + fprintf(errstr, + "error in line %u: vertex %u not in range [1,...,%u]\n", + line_num, vertex, nof_vertices); + goto error_exit; + } + line_num++; + g->change_color(vertex - 1, color); + } + if(verbose) + { + fprintf(verbstr, "Done\n"); + fflush(verbstr); + } + + // + // Read edges + // + if(verbose) + { + fprintf(verbstr, "Reading edges...\n"); + fflush(verbstr); + } + for(unsigned i = 0; i < nof_edges; i++) + { + unsigned int from, to; + if(fscanf(fp, "e %u %u\n", &from, &to) != 2) + { + if(errstr) + fprintf(errstr, "error in line %u: not in DIMACS format\n", + line_num); + goto error_exit; + } + if(!((from >= 1) && (from <= nof_vertices))) + { + if(errstr) + fprintf(errstr, + "error in line %u: vertex %u not in range [1,...,%u]\n", + line_num, from, nof_vertices); + goto error_exit; + } + if(!((to >= 1) && (to <= nof_vertices))) + { + if(errstr) + fprintf(errstr, + "error in line %u: vertex %u not in range [1,...,%u]\n", + line_num, to, nof_vertices); + goto error_exit; + } + line_num++; + g->add_edge(from-1, to-1); + } + if(verbose) + { + fprintf(verbstr, "Done\n"); + fflush(verbstr); + } + + return g; + + error_exit: + if(g) + delete g; + return 0; + +} + + +void +Graph::write_dimacs(FILE* const fp) +{ + remove_duplicate_edges(); + sort_edges(); + + /* First count the total number of edges */ + unsigned int nof_edges = 0; + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex &v = vertices[i]; + for(std::vector::const_iterator ei = v.edges.begin(); + ei != v.edges.end(); + ei++) + { + const unsigned int dest_i = *ei; + if(dest_i < i) + continue; + nof_edges++; + } + } + + /* Output the "header" line */ + fprintf(fp, "p edge %u %u\n", get_nof_vertices(), nof_edges); + + /* Print the color of each vertex */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex &v = vertices[i]; + fprintf(fp, "n %u %u\n", i+1, v.color); + /* + if(v.color != 0) + { + fprintf(fp, "n %u %u\n", i+1, v.color); + } + */ + } + + /* Print the edges */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex &v = vertices[i]; + for(std::vector::const_iterator ei = v.edges.begin(); + ei != v.edges.end(); + ei++) + { + const unsigned int dest_i = *ei; + if(dest_i < i) + continue; + fprintf(fp, "e %u %u\n", i+1, dest_i+1); + } + } +} + + + +void +Graph::sort_edges() +{ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + vertices[i].sort_edges(); +} + + +int +Graph::cmp(Graph& other) +{ + /* Compare the numbers of vertices */ + if(get_nof_vertices() < other.get_nof_vertices()) + return -1; + if(get_nof_vertices() > other.get_nof_vertices()) + return 1; + /* Compare vertex colors */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + if(vertices[i].color < other.vertices[i].color) + return -1; + if(vertices[i].color > other.vertices[i].color) + return 1; + } + /* Compare vertex degrees */ + remove_duplicate_edges(); + other.remove_duplicate_edges(); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + if(vertices[i].nof_edges() < other.vertices[i].nof_edges()) + return -1; + if(vertices[i].nof_edges() > other.vertices[i].nof_edges()) + return 1; + } + /* Compare edges */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex &v1 = vertices[i]; + Vertex &v2 = other.vertices[i]; + v1.sort_edges(); + v2.sort_edges(); + std::vector::const_iterator ei1 = v1.edges.begin(); + std::vector::const_iterator ei2 = v2.edges.begin(); + while(ei1 != v1.edges.end()) + { + if(*ei1 < *ei2) + return -1; + if(*ei1 > *ei2) + return 1; + ei1++; + ei2++; + } + } + return 0; +} + + +Graph* +Graph::permute(const std::vector& perm) const +{ +#if defined(BLISS_CONSISTENCY_CHECKS) +#endif + + Graph* const g = new Graph(get_nof_vertices()); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v = vertices[i]; + Vertex& permuted_v = g->vertices[perm[i]]; + permuted_v.color = v.color; + for(std::vector::const_iterator ei = v.edges.begin(); + ei != v.edges.end(); + ei++) + { + const unsigned int dest_v = *ei; + permuted_v.add_edge(perm[dest_v]); + } + permuted_v.sort_edges(); + } + return g; +} + +Graph* +Graph::permute(const unsigned int* perm) const +{ +#if defined(BLISS_CONSISTENCY_CHECKS) + if(!is_permutation(get_nof_vertices(), perm)) + _INTERNAL_ERROR(); +#endif + + Graph* const g = new Graph(get_nof_vertices()); + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v = vertices[i]; + Vertex& permuted_v = g->vertices[perm[i]]; + permuted_v.color = v.color; + for(std::vector::const_iterator ei = v.edges.begin(); + ei != v.edges.end(); + ei++) + { + const unsigned int dest_v = *ei; + permuted_v.add_edge(perm[dest_v]); + } + permuted_v.sort_edges(); + } + return g; +} + + + + + +/*------------------------------------------------------------------------- + * + * Print graph in graphviz format + * + *-------------------------------------------------------------------------*/ + + +void +Graph::write_dot(const char* const filename) +{ + FILE *fp = fopen(filename, "w"); + if(fp) + { + write_dot(fp); + fclose(fp); + } +} + +void +Graph::write_dot(FILE* const fp) +{ + remove_duplicate_edges(); + + fprintf(fp, "graph g {\n"); + + unsigned int vnum = 0; + for(std::vector::iterator vi = vertices.begin(); + vi != vertices.end(); + vi++, vnum++) + { + Vertex& v = *vi; + fprintf(fp, "v%u [label=\"%u:%u\"];\n", vnum, vnum, v.color); + for(std::vector::const_iterator ei = v.edges.begin(); + ei != v.edges.end(); + ei++) + { + const unsigned int vnum2 = *ei; + if(vnum2 > vnum) + fprintf(fp, "v%u -- v%u\n", vnum, vnum2); + } + } + + fprintf(fp, "}\n"); +} + + + + + + + + +/*------------------------------------------------------------------------- + * + * Get a hash value for the graph. + * + *-------------------------------------------------------------------------*/ + +unsigned int +Graph::get_hash() +{ + remove_duplicate_edges(); + sort_edges(); + + UintSeqHash h; + + h.update(get_nof_vertices()); + + /* Hash the color of each vertex */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + h.update(vertices[i].color); + } + + /* Hash the edges */ + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex &v = vertices[i]; + for(std::vector::const_iterator ei = v.edges.begin(); + ei != v.edges.end(); + ei++) + { + const unsigned int dest_i = *ei; + if(dest_i < i) + continue; + h.update(i); + h.update(dest_i); + } + } + + return h.get_value(); +} + + + + + +void +Graph::remove_duplicate_edges() +{ + std::vector tmp(vertices.size(), false); + + for(std::vector::iterator vi = vertices.begin(); + vi != vertices.end(); + vi++) + { +#if defined(BLISS_EXPENSIVE_CONSISTENCY_CHECKS) + for(unsigned int i = 0; i < tmp.size(); i++) assert(tmp[i] == false); +#endif + (*vi).remove_duplicate_edges(tmp); + } +} + + + + + +/*------------------------------------------------------------------------- + * + * Partition independent invariants + * + *-------------------------------------------------------------------------*/ + +/* + * Return the color of the vertex. + * Time complexity: O(1) + */ +unsigned int +Graph::vertex_color_invariant(const Graph* const g, const unsigned int v) +{ + return g->vertices[v].color; +} + +/* + * Return the degree of the vertex. + * Time complexity: O(1) + */ +unsigned int +Graph::degree_invariant(const Graph* const g, const unsigned int v) +{ + return g->vertices[v].nof_edges(); +} + +/* + * Return 1 if the vertex v has a self-loop, 0 otherwise + * Time complexity: O(E_v), where E_v is the number of edges leaving v + */ +unsigned int +Graph::selfloop_invariant(const Graph* const g, const unsigned int v) +{ + const Vertex& vertex = g->vertices[v]; + for(std::vector::const_iterator ei = vertex.edges.begin(); + ei != vertex.edges.end(); + ei++) + { + if(*ei == v) + return 1; + } + return 0; +} + + + + + + +/*------------------------------------------------------------------------- + * + * Refine the partition p according to a partition independent invariant + * + *-------------------------------------------------------------------------*/ + +bool +Graph::refine_according_to_invariant(unsigned int (*inv)(const Graph* const g, + const unsigned int v)) +{ + bool refined = false; + + for(Partition::Cell* cell = p.first_nonsingleton_cell; cell; ) + { + + Partition::Cell* const next_cell = cell->next_nonsingleton; + + const unsigned int* ep = p.elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--, ep++) + { + const unsigned int ival = inv(this, *ep); + p.invariant_values[*ep] = ival; + if(ival > cell->max_ival) + { + cell->max_ival = ival; + cell->max_ival_count = 1; + } + else if(ival == cell->max_ival) + { + cell->max_ival_count++; + } + } + Partition::Cell* const last_new_cell = p.zplit_cell(cell, true); + refined |= (last_new_cell != cell); + cell = next_cell; + } + + return refined; +} + + + + + + + + + + + + +/*------------------------------------------------------------------------- + * + * Split the neighbourhood of a cell according to the equitable invariant + * + *-------------------------------------------------------------------------*/ + +bool +Graph::split_neighbourhood_of_cell(Partition::Cell* const cell) +{ + + + const bool was_equal_to_first = refine_equal_to_first; + + if(compute_eqref_hash) + { + eqref_hash.update(cell->first); + eqref_hash.update(cell->length); + } + + const unsigned int* ep = p.elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--) + { + const Vertex& v = vertices[*ep++]; + + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j != 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell * const neighbour_cell = p.get_cell(dest_vertex); + if(neighbour_cell->is_unit()) + continue; + const unsigned int ival = ++p.invariant_values[dest_vertex]; + if(ival > neighbour_cell->max_ival) + { + neighbour_cell->max_ival = ival; + neighbour_cell->max_ival_count = 1; + if(ival == 1) { + neighbour_heap.insert(neighbour_cell->first); + } + } + else if(ival == neighbour_cell->max_ival) { + neighbour_cell->max_ival_count++; + } + } + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell * const neighbour_cell = p.get_cell(p.elements[start]); + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + + Partition::Cell* const last_new_cell = p.zplit_cell(neighbour_cell, true); + + /* Update certificate and hash if needed */ + const Partition::Cell* c = neighbour_cell; + while(1) + { + if(in_search) + { + /* Build certificate */ + cert_add_redundant(CERT_SPLIT, c->first, c->length); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + if(compute_eqref_hash) + { + eqref_hash.update(c->first); + eqref_hash.update(c->length); + } + if(c == last_new_cell) + break; + c = c->next; + } + } + + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + return true; + + return false; + + worse_exit: + /* Clear neighbour heap */ + UintSeqHash rest; + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell * const neighbour_cell = p.get_cell(p.elements[start]); + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(neighbour_cell->first); + rest.update(neighbour_cell->length); + rest.update(neighbour_cell->max_ival); + rest.update(neighbour_cell->max_ival_count); + } + neighbour_cell->max_ival = 0; + neighbour_cell->max_ival_count = 0; + p.clear_ivs(neighbour_cell); + } + if(opt_use_failure_recording and was_equal_to_first) + { + for(unsigned int i = p.splitting_queue.size(); i > 0; i--) + { + Partition::Cell* const cell = p.splitting_queue.pop_front(); + rest.update(cell->first); + rest.update(cell->length); + p.splitting_queue.push_back(cell); + } + rest.update(failure_recording_fp_deviation); + failure_recording_fp_deviation = rest.get_value(); + } + + return true; +} + + + +bool +Graph::split_neighbourhood_of_unit_cell(Partition::Cell* const unit_cell) +{ + + + const bool was_equal_to_first = refine_equal_to_first; + + if(compute_eqref_hash) + { + eqref_hash.update(0x87654321); + eqref_hash.update(unit_cell->first); + eqref_hash.update(1); + } + + const Vertex& v = vertices[p.elements[unit_cell->first]]; + + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + const unsigned int dest_vertex = *ei++; + Partition::Cell * const neighbour_cell = p.get_cell(dest_vertex); + + if(neighbour_cell->is_unit()) { + if(in_search) { + /* Remember neighbour in order to generate certificate */ + neighbour_heap.insert(neighbour_cell->first); + } + continue; + } + if(neighbour_cell->max_ival_count == 0) + { + neighbour_heap.insert(neighbour_cell->first); + } + neighbour_cell->max_ival_count++; + + unsigned int * const swap_position = + p.elements + neighbour_cell->first + neighbour_cell->length - + neighbour_cell->max_ival_count; + *p.in_pos[dest_vertex] = *swap_position; + p.in_pos[*swap_position] = p.in_pos[dest_vertex]; + *swap_position = dest_vertex; + p.in_pos[dest_vertex] = swap_position; + } + + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* neighbour_cell = p.get_cell(p.elements[start]); + +#if defined(BLISS_CONSISTENCY_CHECKS) + if(neighbour_cell->is_unit()) { + } else { + } +#endif + + if(compute_eqref_hash) + { + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(neighbour_cell->max_ival_count); + } + + if(neighbour_cell->length > 1 and + neighbour_cell->max_ival_count != neighbour_cell->length) + { + Partition::Cell * const new_cell = + p.aux_split_in_two(neighbour_cell, + neighbour_cell->length - + neighbour_cell->max_ival_count); + unsigned int *ep = p.elements + new_cell->first; + unsigned int * const lp = p.elements+new_cell->first+new_cell->length; + while(ep < lp) + { + p.element_to_cell_map[*ep] = new_cell; + ep++; + } + neighbour_cell->max_ival_count = 0; + + + if(compute_eqref_hash) + { + /* Update hash */ + eqref_hash.update(neighbour_cell->first); + eqref_hash.update(neighbour_cell->length); + eqref_hash.update(0); + eqref_hash.update(new_cell->first); + eqref_hash.update(new_cell->length); + eqref_hash.update(1); + } + + /* Add cells in splitting_queue */ + if(neighbour_cell->is_in_splitting_queue()) { + /* Both cells must be included in splitting_queue in order + to ensure refinement into equitable partition */ + p.splitting_queue_add(new_cell); + } else { + Partition::Cell *min_cell, *max_cell; + if(neighbour_cell->length <= new_cell->length) { + min_cell = neighbour_cell; + max_cell = new_cell; + } else { + min_cell = new_cell; + max_cell = neighbour_cell; + } + /* Put the smaller cell in splitting_queue */ + p.splitting_queue_add(min_cell); + if(max_cell->is_unit()) { + /* Put the "larger" cell also in splitting_queue */ + p.splitting_queue_add(max_cell); + } + } + /* Update pointer for certificate generation */ + neighbour_cell = new_cell; + } + else + { + /* neighbour_cell->length == 1 || + neighbour_cell->max_ival_count == neighbour_cell->length */ + neighbour_cell->max_ival_count = 0; + } + + /* + * Build certificate if required + */ + if(in_search) + { + for(unsigned int i = neighbour_cell->first, + j = neighbour_cell->length; + j > 0; + j--, i++) + { + /* Build certificate */ + cert_add(CERT_EDGE, unit_cell->first, i); + /* No need to continue? */ + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + goto worse_exit; + } + } /* if(in_search) */ + } /* while(!neighbour_heap.is_empty()) */ + + if(refine_compare_certificate and + (refine_equal_to_first == false) and + (refine_cmp_to_best < 0)) + return true; + + return false; + + worse_exit: + /* Clear neighbour heap */ + UintSeqHash rest; + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell * const neighbour_cell = p.get_cell(p.elements[start]); + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(neighbour_cell->first); + rest.update(neighbour_cell->length); + rest.update(neighbour_cell->max_ival_count); + } + neighbour_cell->max_ival_count = 0; + } + if(opt_use_failure_recording and was_equal_to_first) + { + rest.update(failure_recording_fp_deviation); + failure_recording_fp_deviation = rest.get_value(); + } + return true; +} + + + + + + + + + +/*------------------------------------------------------------------------- + * + * Check whether the current partition p is equitable. + * Performance: very slow, use only for debugging purposes. + * + *-------------------------------------------------------------------------*/ + +bool Graph::is_equitable() const +{ + const unsigned int N = get_nof_vertices(); + if(N == 0) + return true; + + std::vector first_count = std::vector(N, 0); + std::vector other_count = std::vector(N, 0); + + for(Partition::Cell *cell = p.first_cell; cell; cell = cell->next) + { + if(cell->is_unit()) + continue; + + unsigned int *ep = p.elements + cell->first; + const Vertex &first_vertex = vertices[*ep++]; + + /* Count how many edges lead from the first vertex to + * the neighbouring cells */ + for(std::vector::const_iterator ei = + first_vertex.edges.begin(); + ei != first_vertex.edges.end(); + ei++) + { + first_count[p.get_cell(*ei)->first]++; + } + + /* Count and compare to the edges of the other vertices */ + for(unsigned int i = cell->length; i > 1; i--) + { + const Vertex &vertex = vertices[*ep++]; + for(std::vector::const_iterator ei = + vertex.edges.begin(); + ei != vertex.edges.end(); + ei++) + { + other_count[p.get_cell(*ei)->first]++; + } + for(Partition::Cell *cell2 = p.first_cell; + cell2; + cell2 = cell2->next) + { + if(first_count[cell2->first] != other_count[cell2->first]) + { + /* Not equitable */ + return false; + } + other_count[cell2->first] = 0; + } + } + /* Reset first_count */ + for(unsigned int i = 0; i < N; i++) + first_count[i] = 0; + } + return true; +} + + + + + +/*------------------------------------------------------------------------- + * + * Build the initial equitable partition + * + *-------------------------------------------------------------------------*/ + +void Graph::make_initial_equitable_partition() +{ + refine_according_to_invariant(&vertex_color_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_according_to_invariant(&selfloop_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_according_to_invariant(°ree_invariant); + p.splitting_queue_clear(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + + refine_to_equitable(); + //p.print_signature(stderr); fprintf(stderr, "\n"); + +} + + + + + + + +/*------------------------------------------------------------------------- + * + * Find the next cell to be splitted + * + *-------------------------------------------------------------------------*/ + + +Partition::Cell* +Graph::find_next_cell_to_be_splitted(Partition::Cell* cell) +{ + switch(sh) { + case shs_f: return sh_first(); + case shs_fs: return sh_first_smallest(); + case shs_fl: return sh_first_largest(); + case shs_fm: return sh_first_max_neighbours(); + case shs_fsm: return sh_first_smallest_max_neighbours(); + case shs_flm: return sh_first_largest_max_neighbours(); + default: + fatal_error("Internal error - unknown splitting heuristics"); + return 0; + } +} + +/** \internal + * A splitting heuristic. + * Returns the first nonsingleton cell in the current partition. + */ +Partition::Cell* +Graph::sh_first() +{ + Partition::Cell* best_cell = 0; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + best_cell = cell; + break; + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first smallest nonsingleton cell in the current partition. + */ +Partition::Cell* +Graph::sh_first_smallest() +{ + Partition::Cell* best_cell = 0; + unsigned int best_size = UINT_MAX; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + if(cell->length < best_size) + { + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first largest nonsingleton cell in the current partition. + */ +Partition::Cell* +Graph::sh_first_largest() +{ + Partition::Cell* best_cell = 0; + unsigned int best_size = 0; + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + if(cell->length > best_size) + { + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Graph::sh_first_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + Partition::Cell * const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + int value = 0; + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + if(value > best_value) + { + best_value = value; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first smallest nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Graph::sh_first_smallest_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + unsigned int best_size = UINT_MAX; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + Partition::Cell* const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + int value = 0; + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + if((value > best_value) or + (value == best_value and cell->length < best_size)) + { + best_value = value; + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + +/** \internal + * A splitting heuristic. + * Returns the first largest nonsingleton cell with max number of neighbouring + * nonsingleton cells. + * Assumes that the partition p is equitable. + * Assumes that the max_ival fields of the cells are all 0. + */ +Partition::Cell* +Graph::sh_first_largest_max_neighbours() +{ + Partition::Cell* best_cell = 0; + int best_value = -1; + unsigned int best_size = 0; + KStack neighbour_cells_visited; + neighbour_cells_visited.init(get_nof_vertices()); + for(Partition::Cell* cell = p.first_nonsingleton_cell; + cell; + cell = cell->next_nonsingleton) + { + + if(opt_use_comprec and p.cr_get_level(cell->first) != cr_level) + continue; + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + Partition::Cell* const neighbour_cell = p.get_cell(*ei++); + if(neighbour_cell->is_unit()) + continue; + neighbour_cell->max_ival++; + if(neighbour_cell->max_ival == 1) + neighbour_cells_visited.push(neighbour_cell); + } + int value = 0; + while(!neighbour_cells_visited.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbour_cells_visited.pop(); + if(neighbour_cell->max_ival != neighbour_cell->length) + value++; + neighbour_cell->max_ival = 0; + } + if((value > best_value) or + (value == best_value and cell->length > best_size)) + { + best_value = value; + best_size = cell->length; + best_cell = cell; + } + } + return best_cell; +} + + + + + + + + + + + + + + + + + + + + +/*------------------------------------------------------------------------- + * + * Initialize the certificate size and memory + * + *-------------------------------------------------------------------------*/ + +void +Graph::initialize_certificate() +{ + certificate_index = 0; + certificate_current_path.clear(); + certificate_first_path.clear(); + certificate_best_path.clear(); +} + + + + + +/*------------------------------------------------------------------------- + * + * Check whether perm is an automorphism. + * Slow, mainly for debugging and validation purposes. + * + *-------------------------------------------------------------------------*/ + +bool +Graph::is_automorphism(unsigned int* const perm) +{ + std::set > edges1; + std::set > edges2; + +#if defined(BLISS_CONSISTENCY_CHECKS) + if(!is_permutation(get_nof_vertices(), perm)) + _INTERNAL_ERROR(); +#endif + + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + Vertex& v1 = vertices[i]; + edges1.clear(); + for(std::vector::iterator ei = v1.edges.begin(); + ei != v1.edges.end(); + ei++) + edges1.insert(perm[*ei]); + + Vertex& v2 = vertices[perm[i]]; + edges2.clear(); + for(std::vector::iterator ei = v2.edges.begin(); + ei != v2.edges.end(); + ei++) + edges2.insert(*ei); + + if(!(edges1 == edges2)) + return false; + } + + return true; +} + + + + +bool +Graph::is_automorphism(const std::vector& perm) const +{ + + if(!(perm.size() == get_nof_vertices() and is_permutation(perm))) + return false; + + std::set > edges1; + std::set > edges2; + + for(unsigned int i = 0; i < get_nof_vertices(); i++) + { + const Vertex& v1 = vertices[i]; + edges1.clear(); + for(std::vector::const_iterator ei = v1.edges.begin(); + ei != v1.edges.end(); + ei++) + edges1.insert(perm[*ei]); + + const Vertex& v2 = vertices[perm[i]]; + edges2.clear(); + for(std::vector::const_iterator ei = v2.edges.begin(); + ei != v2.edges.end(); + ei++) + edges2.insert(*ei); + + if(!(edges1 == edges2)) + return false; + } + + return true; +} + + + + + + + +bool +Graph::nucr_find_first_component(const unsigned int level) +{ + + cr_component.clear(); + cr_component_elements = 0; + + /* Find first non-discrete cell in the component level */ + Partition::Cell* first_cell = p.first_nonsingleton_cell; + while(first_cell) + { + if(p.cr_get_level(first_cell->first) == level) + break; + first_cell = first_cell->next_nonsingleton; + } + + /* The component is discrete, return false */ + if(!first_cell) + return false; + + std::vector component; + first_cell->max_ival = 1; + component.push_back(first_cell); + + for(unsigned int i = 0; i < component.size(); i++) + { + Partition::Cell* const cell = component[i]; + + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + + /* Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Already marked to be in the same component? */ + if(neighbour_cell->max_ival == 1) + continue; + /* Is the neighbour at the same component recursion level? */ + if(p.cr_get_level(neighbour_cell->first) != level) + continue; + + if(neighbour_cell->max_ival_count == 0) + neighbour_heap.insert(neighbour_cell->first); + neighbour_cell->max_ival_count++; + } + while(!neighbour_heap.is_empty()) + { + const unsigned int start = neighbour_heap.remove(); + Partition::Cell* const neighbour_cell = + p.get_cell(p.elements[start]); + + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + neighbour_cell->max_ival_count = 0; + neighbour_cell->max_ival = 1; + component.push_back(neighbour_cell); + } + } + + for(unsigned int i = 0; i < component.size(); i++) + { + Partition::Cell* const cell = component[i]; + cell->max_ival = 0; + cr_component.push_back(cell->first); + cr_component_elements += cell->length; + } + + if(verbstr and verbose_level > 2) { + fprintf(verbstr, "NU-component with %lu cells and %u vertices\n", + (long unsigned)cr_component.size(), cr_component_elements); + fflush(verbstr); + } + + return true; +} + + + + +bool +Graph::nucr_find_first_component(const unsigned int level, + std::vector& component, + unsigned int& component_elements, + Partition::Cell*& sh_return) +{ + + component.clear(); + component_elements = 0; + sh_return = 0; + unsigned int sh_first = 0; + unsigned int sh_size = 0; + unsigned int sh_nuconn = 0; + + /* Find first non-discrete cell in the component level */ + Partition::Cell* first_cell = p.first_nonsingleton_cell; + while(first_cell) + { + if(p.cr_get_level(first_cell->first) == level) + break; + first_cell = first_cell->next_nonsingleton; + } + + if(!first_cell) + { + /* The component is discrete, return false */ + return false; + } + + std::vector comp; + KStack neighbours; + neighbours.init(get_nof_vertices()); + + first_cell->max_ival = 1; + comp.push_back(first_cell); + + for(unsigned int i = 0; i < comp.size(); i++) + { + Partition::Cell* const cell = comp[i]; + + const Vertex& v = vertices[p.elements[cell->first]]; + std::vector::const_iterator ei = v.edges.begin(); + for(unsigned int j = v.nof_edges(); j > 0; j--) + { + const unsigned int neighbour = *ei++; + + Partition::Cell* const neighbour_cell = p.get_cell(neighbour); + + /* Skip unit neighbours */ + if(neighbour_cell->is_unit()) + continue; + /* Is the neighbour at the same component recursion level? */ + //if(p.cr_get_level(neighbour_cell->first) != level) + // continue; + if(neighbour_cell->max_ival_count == 0) + neighbours.push(neighbour_cell); + neighbour_cell->max_ival_count++; + } + unsigned int nuconn = 1; + while(!neighbours.is_empty()) + { + Partition::Cell* const neighbour_cell = neighbours.pop(); + //neighbours.pop_back(); + + /* Skip saturated neighbour cells */ + if(neighbour_cell->max_ival_count == neighbour_cell->length) + { + neighbour_cell->max_ival_count = 0; + continue; + } + nuconn++; + neighbour_cell->max_ival_count = 0; + if(neighbour_cell->max_ival == 0) { + comp.push_back(neighbour_cell); + neighbour_cell->max_ival = 1; + } + } + + switch(sh) { + case shs_f: + if(sh_return == 0 or + cell->first <= sh_first) { + sh_return = cell; + sh_first = cell->first; + } + break; + case shs_fs: + if(sh_return == 0 or + cell->length < sh_size or + (cell->length == sh_size and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + } + break; + case shs_fl: + if(sh_return == 0 or + cell->length > sh_size or + (cell->length == sh_size and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + } + break; + case shs_fm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and cell->first <= sh_first)) { + sh_return = cell; + sh_first = cell->first; + sh_nuconn = nuconn; + } + break; + case shs_fsm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and + (cell->length < sh_size or + (cell->length == sh_size and cell->first <= sh_first)))) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + sh_nuconn = nuconn; + } + break; + case shs_flm: + if(sh_return == 0 or + nuconn > sh_nuconn or + (nuconn == sh_nuconn and + (cell->length > sh_size or + (cell->length == sh_size and cell->first <= sh_first)))) { + sh_return = cell; + sh_first = cell->first; + sh_size = cell->length; + sh_nuconn = nuconn; + } + break; + default: + fatal_error("Internal error - unknown splitting heuristics"); + return 0; + } + } + assert(sh_return); + + for(unsigned int i = 0; i < comp.size(); i++) + { + Partition::Cell* const cell = comp[i]; + cell->max_ival = 0; + component.push_back(cell->first); + component_elements += cell->length; + } + + if(verbstr and verbose_level > 2) { + fprintf(verbstr, "NU-component with %lu cells and %u vertices\n", + (long unsigned)component.size(), component_elements); + fflush(verbstr); + } + + return true; +} + + + + +} diff --git a/src/bliss/graph.hh b/src/bliss/graph.hh new file mode 100644 index 0000000..8288379 --- /dev/null +++ b/src/bliss/graph.hh @@ -0,0 +1,997 @@ +#ifndef BLISS_GRAPH_HH +#define BLISS_GRAPH_HH + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +/** + * \namespace bliss + * The namespace bliss contains all the classes and functions of the bliss + * tool except for the C programming language API. + */ +namespace bliss { + class AbstractGraph; +} + +#include +#include +#include "kstack.hh" +#include "kqueue.hh" +#include "heap.hh" +#include "orbit.hh" +#include "partition.hh" +#include "bignum.hh" +#include "uintseqhash.hh" + +namespace bliss { + +/** + * \brief Statistics returned by the bliss search algorithm. + */ +class Stats +{ + friend class AbstractGraph; +public: + /** \internal The size of the automorphism group. */ + BigNum group_size; +private: + /** \internal An approximation (due to possible overflows) of + * the size of the automorphism group. */ + long double group_size_approx; + /** \internal The number of nodes in the search tree. */ + long unsigned int nof_nodes; + /** \internal The number of leaf nodes in the search tree. */ + long unsigned int nof_leaf_nodes; + /** \internal The number of bad nodes in the search tree. */ + long unsigned int nof_bad_nodes; + /** \internal The number of canonical representative updates. */ + long unsigned int nof_canupdates; + /** \internal The number of generator permutations. */ + long unsigned int nof_generators; + /** \internal The maximal depth of the search tree. */ + unsigned long int max_level; + /** */ + void reset() + { + group_size.assign(1); + group_size_approx = 1.0; + nof_nodes = 0; + nof_leaf_nodes = 0; + nof_bad_nodes = 0; + nof_canupdates = 0; + nof_generators = 0; + max_level = 0; + } +public: + Stats() { reset(); } + /** Print the statistics. */ + size_t print(FILE* const fp) const + { + size_t r = 0; + r += fprintf(fp, "Nodes: %lu\n", nof_nodes); + r += fprintf(fp, "Leaf nodes: %lu\n", nof_leaf_nodes); + r += fprintf(fp, "Bad nodes: %lu\n", nof_bad_nodes); + r += fprintf(fp, "Canrep updates: %lu\n", nof_canupdates); + r += fprintf(fp, "Generators: %lu\n", nof_generators); + r += fprintf(fp, "Max level: %lu\n", max_level); + r += fprintf(fp, "|Aut|: ")+group_size.print(fp)+fprintf(fp, "\n"); + fflush(fp); + return r; + } + /** An approximation (due to possible overflows/rounding errors) of + * the size of the automorphism group. */ + long double get_group_size_approx() const {return group_size_approx;} + /** The number of nodes in the search tree. */ + long unsigned int get_nof_nodes() const {return nof_nodes;} + /** The number of leaf nodes in the search tree. */ + long unsigned int get_nof_leaf_nodes() const {return nof_leaf_nodes;} + /** The number of bad nodes in the search tree. */ + long unsigned int get_nof_bad_nodes() const {return nof_bad_nodes;} + /** The number of canonical representative updates. */ + long unsigned int get_nof_canupdates() const {return nof_canupdates;} + /** The number of generator permutations. */ + long unsigned int get_nof_generators() const {return nof_generators;} + /** The maximal depth of the search tree. */ + unsigned long int get_max_level() const {return max_level;} +}; + + + + + + +/** + * \brief An abstract base class for different types of graphs. + */ +class AbstractGraph +{ + friend class Partition; + +public: + AbstractGraph(); + virtual ~AbstractGraph(); + + /** + * Set the verbose output level for the algorithms. + * \param level the level of verbose output, 0 means no verbose output + */ + void set_verbose_level(const unsigned int level); + + /** + * Set the file stream for the verbose output. + * \param fp the file stream; if null, no verbose output is written + */ + void set_verbose_file(FILE * const fp); + + /** + * Add a new vertex with color \a color in the graph and return its index. + */ + virtual unsigned int add_vertex(const unsigned int color = 0) = 0; + + /** + * Add an edge between vertices \a source and \a target. + * Duplicate edges between vertices are ignored but try to avoid introducing + * them in the first place as they are not ignored immediately but will + * consume memory and computation resources for a while. + */ + virtual void add_edge(const unsigned int source, const unsigned int target) = 0; + + /** + * Change the color of the vertex \a vertex to \a color. + */ + virtual void change_color(const unsigned int vertex, const unsigned int color) = 0; + + /** + * Check whether \a perm is an automorphism of this graph. + * Unoptimized, mainly for debugging purposes. + */ + virtual bool is_automorphism(const std::vector& perm) const; + + + /** Activate/deactivate failure recording. + * May not be called during the search, i.e. from an automorphism reporting + * hook function. + * \param active if true, activate failure recording, deactivate otherwise + */ + void set_failure_recording(const bool active) {assert(!in_search); opt_use_failure_recording = active;} + + /** Activate/deactivate component recursion. + * The choice affects the computed canonical labelings; + * therefore, if you want to compare whether two graphs are isomorphic by + * computing and comparing (for equality) their canonical versions, + * be sure to use the same choice for both graphs. + * May not be called during the search, i.e. from an automorphism reporting + * hook function. + * \param active if true, activate component recursion, deactivate otherwise + */ + void set_component_recursion(const bool active) {assert(!in_search); opt_use_comprec = active;} + + + + /** + * Return the number of vertices in the graph. + */ + virtual unsigned int get_nof_vertices() const = 0; + + /** + * Return a new graph that is the result of applying the permutation \a perm + * to this graph. This graph is not modified. + * \a perm must contain N=this.get_nof_vertices() elements and be a bijection + * on {0,1,...,N-1}, otherwise the result is undefined or a segfault. + */ + virtual AbstractGraph* permute(const unsigned int* const perm) const = 0; + virtual AbstractGraph* permute(const std::vector& perm) const = 0; + + /** + * Find a set of generators for the automorphism group of the graph. + * The function \a hook (if non-null) is called each time a new generator + * for the automorphism group is found. + * The first argument \a user_param for the hook is the + * \a hook_user_param given below, + * the second argument \a n is the length of the automorphism (equal to + * get_nof_vertices()) and + * the third argument \a aut is the automorphism + * (a bijection on {0,...,get_nof_vertices()-1}). + * The memory for the automorphism \a aut will be invalidated immediately + * after the return from the hook function; + * if you want to use the automorphism later, you have to take a copy of it. + * Do not call any member functions in the hook. + * The search statistics are copied in \a stats. + */ + void find_automorphisms(Stats& stats, + void (*hook)(void* user_param, + unsigned int n, + const unsigned int* aut), + void* hook_user_param); + + /** + * Otherwise the same as find_automorphisms() except that + * a canonical labeling of the graph (a bijection on + * {0,...,get_nof_vertices()-1}) is returned. + * The memory allocated for the returned canonical labeling will remain + * valid only until the next call to a member function with the exception + * that constant member functions (for example, bliss::Graph::permute()) can + * be called without invalidating the labeling. + * To compute the canonical version of an undirected graph, call this + * function and then bliss::Graph::permute() with the returned canonical + * labeling. + * Note that the computed canonical version may depend on the applied version + * of bliss as well as on some other options (for instance, the splitting + * heuristic selected with bliss::Graph::set_splitting_heuristic()). + */ + const unsigned int* canonical_form(Stats& stats, + void (*hook)(void* user_param, + unsigned int n, + const unsigned int* aut), + void* hook_user_param); + + /** + * Write the graph to a file in a variant of the DIMACS format. + * See the bliss website + * for the definition of the file format. + * Note that in the DIMACS file the vertices are numbered from 1 to N while + * in this C++ API they are from 0 to N-1. + * Thus the vertex n in the file corresponds to the vertex n-1 in the API. + * \param fp the file stream where the graph is written + */ + virtual void write_dimacs(FILE * const fp) = 0; + + /** + * Write the graph to a file in the graphviz dotty format. + * \param fp the file stream where the graph is written + */ + virtual void write_dot(FILE * const fp) = 0; + + /** + * Write the graph in a file in the graphviz dotty format. + * Do nothing if the file cannot be written. + * \param file_name the name of the file to which the graph is written + */ + virtual void write_dot(const char * const file_name) = 0; + + /** + * Get a hash value for the graph. + * \return the hash value + */ + virtual unsigned int get_hash() = 0; + + /** + * Disable/enable the "long prune" method. + * The choice affects the computed canonical labelings; + * therefore, if you want to compare whether two graphs are isomorphic by + * computing and comparing (for equality) their canonical versions, + * be sure to use the same choice for both graphs. + * May not be called during the search, i.e. from an automorphism reporting + * hook function. + * \param active if true, activate "long prune", deactivate otherwise + */ + void set_long_prune_activity(const bool active) { + assert(!in_search); + opt_use_long_prune = active; + } + + + +protected: + /** \internal + * How much verbose output is produced (0 means none) */ + unsigned int verbose_level; + /** \internal + * The output stream for verbose output. */ + FILE *verbstr; +protected: + + /** \internal + * The ordered partition used in the search algorithm. */ + Partition p; + + /** \internal + * Whether the search for automorphisms and a canonical labeling is + * in progress. + */ + bool in_search; + + /** \internal + * Is failure recording in use? + */ + bool opt_use_failure_recording; + /* The "tree-specific" invariant value for the point when current path + * got different from the first path */ + unsigned int failure_recording_fp_deviation; + + /** \internal + * Is component recursion in use? + */ + bool opt_use_comprec; + + + unsigned int refine_current_path_certificate_index; + bool refine_compare_certificate; + bool refine_equal_to_first; + unsigned int refine_first_path_subcertificate_end; + int refine_cmp_to_best; + unsigned int refine_best_path_subcertificate_end; + + static const unsigned int CERT_SPLIT = 0; //UINT_MAX; + static const unsigned int CERT_EDGE = 1; //UINT_MAX-1; + /** \internal + * Add a triple (v1,v2,v3) in the certificate. + * May modify refine_equal_to_first and refine_cmp_to_best. + * May also update eqref_hash and failure_recording_fp_deviation. */ + void cert_add(const unsigned int v1, + const unsigned int v2, + const unsigned int v3); + + /** \internal + * Add a redundant triple (v1,v2,v3) in the certificate. + * Can also just dicard the triple. + * May modify refine_equal_to_first and refine_cmp_to_best. + * May also update eqref_hash and failure_recording_fp_deviation. */ + void cert_add_redundant(const unsigned int x, + const unsigned int y, + const unsigned int z); + + /**\internal + * Is the long prune method in use? + */ + bool opt_use_long_prune; + /**\internal + * Maximum amount of memory (in megabytes) available for + * the long prune method + */ + static const unsigned int long_prune_options_max_mem = 50; + /**\internal + * Maximum amount of automorphisms stored for the long prune method; + * less than this is stored if the memory limit above is reached first + */ + static const unsigned int long_prune_options_max_stored_auts = 100; + + unsigned int long_prune_max_stored_autss; + std::vector *> long_prune_fixed; + std::vector *> long_prune_mcrs; + std::vector long_prune_temp; + unsigned int long_prune_begin; + unsigned int long_prune_end; + /** \internal + * Initialize the "long prune" data structures. + */ + void long_prune_init(); + /** \internal + * Release the memory allocated for "long prune" data structures. + */ + void long_prune_deallocate(); + void long_prune_add_automorphism(const unsigned int *aut); + std::vector& long_prune_get_fixed(const unsigned int index); + std::vector& long_prune_allocget_fixed(const unsigned int index); + std::vector& long_prune_get_mcrs(const unsigned int index); + std::vector& long_prune_allocget_mcrs(const unsigned int index); + /** \internal + * Swap the i:th and j:th stored automorphism information; + * i and j must be "in window, i.e. in [long_prune_begin,long_prune_end[ + */ + void long_prune_swap(const unsigned int i, const unsigned int j); + + /* + * Data structures and routines for refining the partition p into equitable + */ + Heap neighbour_heap; + virtual bool split_neighbourhood_of_unit_cell(Partition::Cell *) = 0; + virtual bool split_neighbourhood_of_cell(Partition::Cell * const) = 0; + void refine_to_equitable(); + void refine_to_equitable(Partition::Cell * const unit_cell); + void refine_to_equitable(Partition::Cell * const unit_cell1, + Partition::Cell * const unit_cell2); + + + /** \internal + * \return false if it was detected that the current certificate + * is different from the first and/or best (whether this is checked + * depends on in_search and refine_compare_certificate flags. + */ + bool do_refine_to_equitable(); + + unsigned int eqref_max_certificate_index; + /** \internal + * Whether eqref_hash is updated during equitable refinement process. + */ + bool compute_eqref_hash; + UintSeqHash eqref_hash; + + + /** \internal + * Check whether the current partition p is equitable. + * Performance: very slow, use only for debugging purposes. + */ + virtual bool is_equitable() const = 0; + + unsigned int *first_path_labeling; + unsigned int *first_path_labeling_inv; + Orbit first_path_orbits; + unsigned int *first_path_automorphism; + + unsigned int *best_path_labeling; + unsigned int *best_path_labeling_inv; + Orbit best_path_orbits; + unsigned int *best_path_automorphism; + + void update_labeling(unsigned int * const lab); + void update_labeling_and_its_inverse(unsigned int * const lab, + unsigned int * const lab_inv); + void update_orbit_information(Orbit &o, const unsigned int *perm); + + void reset_permutation(unsigned int *perm); + + /* Mainly for debugging purposes */ + virtual bool is_automorphism(unsigned int* const perm); + + std::vector certificate_current_path; + std::vector certificate_first_path; + std::vector certificate_best_path; + + unsigned int certificate_index; + virtual void initialize_certificate() = 0; + + virtual void remove_duplicate_edges() = 0; + virtual void make_initial_equitable_partition() = 0; + virtual Partition::Cell* find_next_cell_to_be_splitted(Partition::Cell *cell) = 0; + + + void search(const bool canonical, Stats &stats); + + + void (*report_hook)(void *user_param, + unsigned int n, + const unsigned int *aut); + void *report_user_param; + + + /* + * + * Nonuniform component recursion (NUCR) + * + */ + + /** The currently traversed component */ + unsigned int cr_level; + + /** \internal + * The "Component End Point" data structure + */ + class CR_CEP { + public: + /** At which level in the search was this CEP created */ + unsigned int creation_level; + /** The current component has been fully traversed when the partition has + * this many discrete cells left */ + unsigned int discrete_cell_limit; + /** The component to be traversed after the current one */ + unsigned int next_cr_level; + /** The next component end point */ + unsigned int next_cep_index; + bool first_checked; + bool best_checked; + }; + /** \internal + * A stack for storing Component End Points + */ + std::vector cr_cep_stack; + + /** \internal + * Find the first non-uniformity component at the component recursion + * level \a level. + * The component is stored in \a cr_component. + * If no component is found, \a cr_component is empty. + * Returns false if all the cells in the component recursion level \a level + * were discrete. + * Modifies the max_ival and max_ival_count fields of Partition:Cell + * (assumes that they are 0 when called and + * quarantees that they are 0 when returned). + */ + virtual bool nucr_find_first_component(const unsigned int level) = 0; + virtual bool nucr_find_first_component(const unsigned int level, + std::vector& component, + unsigned int& component_elements, + Partition::Cell*& sh_return) = 0; + /** \internal + * The non-uniformity component found by nucr_find_first_component() + * is stored here. + */ + std::vector cr_component; + /** \internal + * The number of vertices in the component \a cr_component + */ + unsigned int cr_component_elements; + + + + +}; + + + +/** + * \brief The class for undirected, vertex colored graphs. + * + * Multiple edges between vertices are not allowed (i.e., are ignored). + */ +class Graph : public AbstractGraph +{ +public: + /** + * The possible splitting heuristics. + * The selected splitting heuristics affects the computed canonical + * labelings; therefore, if you want to compare whether two graphs + * are isomorphic by computing and comparing (for equality) their + * canonical versions, be sure to use the same splitting heuristics + * for both graphs. + */ + typedef enum { + /** First non-unit cell. + * Very fast but may result in large search spaces on difficult graphs. + * Use for large but easy graphs. */ + shs_f = 0, + /** First smallest non-unit cell. + * Fast, should usually produce smaller search spaces than shs_f. */ + shs_fs, + /** First largest non-unit cell. + * Fast, should usually produce smaller search spaces than shs_f. */ + shs_fl, + /** First maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_fm, + /** First smallest maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_fsm, + /** First largest maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_flm + } SplittingHeuristic; + +protected: + class Vertex { + public: + Vertex(); + ~Vertex(); + void add_edge(const unsigned int other_vertex); + void remove_duplicate_edges(std::vector& tmp); + void sort_edges(); + + unsigned int color; + std::vector edges; + unsigned int nof_edges() const {return edges.size(); } + }; + std::vector vertices; + void sort_edges(); + void remove_duplicate_edges(); + + /** \internal + * Partition independent invariant. + * Returns the color of the vertex. + * Time complexity: O(1). + */ + static unsigned int vertex_color_invariant(const Graph* const g, + const unsigned int v); + /** \internal + * Partition independent invariant. + * Returns the degree of the vertex. + * DUPLICATE EDGES MUST HAVE BEEN REMOVED BEFORE. + * Time complexity: O(1). + */ + static unsigned int degree_invariant(const Graph* const g, + const unsigned int v); + /** \internal + * Partition independent invariant. + * Returns 1 if there is an edge from the vertex to itself, 0 if not. + * Time complexity: O(k), where k is the number of edges leaving the vertex. + */ + static unsigned int selfloop_invariant(const Graph* const g, + const unsigned int v); + + + bool refine_according_to_invariant(unsigned int (*inv)(const Graph* const g, + const unsigned int v)); + + /* + * Routines needed when refining the partition p into equitable + */ + bool split_neighbourhood_of_unit_cell(Partition::Cell *); + bool split_neighbourhood_of_cell(Partition::Cell * const); + + /** \internal + * \copydoc AbstractGraph::is_equitable() const + */ + bool is_equitable() const; + + /* Splitting heuristics, documented in more detail in graph.cc */ + SplittingHeuristic sh; + Partition::Cell* find_next_cell_to_be_splitted(Partition::Cell *cell); + Partition::Cell* sh_first(); + Partition::Cell* sh_first_smallest(); + Partition::Cell* sh_first_largest(); + Partition::Cell* sh_first_max_neighbours(); + Partition::Cell* sh_first_smallest_max_neighbours(); + Partition::Cell* sh_first_largest_max_neighbours(); + + + void make_initial_equitable_partition(); + + void initialize_certificate(); + + bool is_automorphism(unsigned int* const perm); + + + bool nucr_find_first_component(const unsigned int level); + bool nucr_find_first_component(const unsigned int level, + std::vector& component, + unsigned int& component_elements, + Partition::Cell*& sh_return); + + + +public: + /** + * Create a new graph with \a N vertices and no edges. + */ + Graph(const unsigned int N = 0); + + /** + * Destroy the graph. + */ + ~Graph(); + + /** + * Read the graph from the file \a fp in a variant of the DIMACS format. + * See the bliss website + * for the definition of the file format. + * Note that in the DIMACS file the vertices are numbered from 1 to N while + * in this C++ API they are from 0 to N-1. + * Thus the vertex n in the file corresponds to the vertex n-1 in the API. + * + * \param fp the file stream for the graph file + * \param errstr if non-null, the possible error messages are printed + * in this file stream + * \return a new Graph object or 0 if reading failed for some + * reason + */ + static Graph* read_dimacs(FILE* const fp, FILE* const errstr = stderr); + + /** + * Write the graph to a file in a variant of the DIMACS format. + * See the bliss website + * for the definition of the file format. + */ + void write_dimacs(FILE* const fp); + + /** + * \copydoc AbstractGraph::write_dot(FILE * const fp) + */ + void write_dot(FILE* const fp); + + /** + * \copydoc AbstractGraph::write_dot(const char * const file_name) + */ + void write_dot(const char* const file_name); + + /** + * \copydoc AbstractGraph::is_automorphism(const std::vector& perm) const + */ + bool is_automorphism(const std::vector& perm) const; + + + /** + * \copydoc AbstractGraph::get_hash() + */ + virtual unsigned int get_hash(); + + /** + * Return the number of vertices in the graph. + */ + unsigned int get_nof_vertices() const {return vertices.size(); } + + /** + * \copydoc AbstractGraph::permute(const unsigned int* const perm) const + */ + Graph* permute(const unsigned int* const perm) const; + Graph* permute(const std::vector& perm) const; + + /** + * Add a new vertex with color \a color in the graph and return its index. + */ + unsigned int add_vertex(const unsigned int color = 0); + + /** + * Add an edge between vertices \a v1 and \a v2. + * Duplicate edges between vertices are ignored but try to avoid introducing + * them in the first place as they are not ignored immediately but will + * consume memory and computation resources for a while. + */ + void add_edge(const unsigned int v1, const unsigned int v2); + + /** + * Change the color of the vertex \a vertex to \a color. + */ + void change_color(const unsigned int vertex, const unsigned int color); + + /** + * Compare this graph with the graph \a other. + * Returns 0 if the graphs are equal, and a negative (positive) integer + * if this graph is "smaller than" ("greater than", resp.) than \a other. + */ + int cmp(Graph& other); + + /** + * Set the splitting heuristic used by the automorphism and canonical + * labeling algorithm. + * The selected splitting heuristics affects the computed canonical + * labelings; therefore, if you want to compare whether two graphs + * are isomorphic by computing and comparing (for equality) their + * canonical versions, be sure to use the same splitting heuristics + * for both graphs. + */ + void set_splitting_heuristic(const SplittingHeuristic shs) {sh = shs; } + + +}; + + + +/** + * \brief The class for directed, vertex colored graphs. + * + * Multiple edges between vertices are not allowed (i.e., are ignored). + */ +class Digraph : public AbstractGraph +{ +public: + /** + * The possible splitting heuristics. + * The selected splitting heuristics affects the computed canonical + * labelings; therefore, if you want to compare whether two graphs + * are isomorphic by computing and comparing (for equality) their + * canonical versions, be sure to use the same splitting heuristics + * for both graphs. + */ + typedef enum { + /** First non-unit cell. + * Very fast but may result in large search spaces on difficult graphs. + * Use for large but easy graphs. */ + shs_f = 0, + /** First smallest non-unit cell. + * Fast, should usually produce smaller search spaces than shs_f. */ + shs_fs, + /** First largest non-unit cell. + * Fast, should usually produce smaller search spaces than shs_f. */ + shs_fl, + /** First maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_fm, + /** First smallest maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_fsm, + /** First largest maximally non-trivially connected non-unit cell. + * Not so fast, should usually produce smaller search spaces than shs_f, + * shs_fs, and shs_fl. */ + shs_flm + } SplittingHeuristic; + +protected: + class Vertex { + public: + Vertex(); + ~Vertex(); + void add_edge_to(const unsigned int dest_vertex); + void add_edge_from(const unsigned int source_vertex); + void remove_duplicate_edges(std::vector& tmp); + void sort_edges(); + unsigned int color; + std::vector edges_out; + std::vector edges_in; + unsigned int nof_edges_in() const {return edges_in.size(); } + unsigned int nof_edges_out() const {return edges_out.size(); } + }; + std::vector vertices; + void remove_duplicate_edges(); + + /** \internal + * Partition independent invariant. + * Returns the color of the vertex. + * Time complexity: O(1). + */ + static unsigned int vertex_color_invariant(const Digraph* const g, + const unsigned int v); + /** \internal + * Partition independent invariant. + * Returns the indegree of the vertex. + * DUPLICATE EDGES MUST HAVE BEEN REMOVED BEFORE. + * Time complexity: O(1). + */ + static unsigned int indegree_invariant(const Digraph* const g, + const unsigned int v); + /** \internal + * Partition independent invariant. + * Returns the outdegree of the vertex. + * DUPLICATE EDGES MUST HAVE BEEN REMOVED BEFORE. + * Time complexity: O(1). + */ + static unsigned int outdegree_invariant(const Digraph* const g, + const unsigned int v); + /** \internal + * Partition independent invariant. + * Returns 1 if there is an edge from the vertex to itself, 0 if not. + * Time complexity: O(k), where k is the number of edges leaving the vertex. + */ + static unsigned int selfloop_invariant(const Digraph* const g, + const unsigned int v); + + /** \internal + * Refine the partition \a p according to + * the partition independent invariant \a inv. + */ + bool refine_according_to_invariant(unsigned int (*inv)(const Digraph* const g, + const unsigned int v)); + + /* + * Routines needed when refining the partition p into equitable + */ + bool split_neighbourhood_of_unit_cell(Partition::Cell* const); + bool split_neighbourhood_of_cell(Partition::Cell* const); + + + /** \internal + * \copydoc AbstractGraph::is_equitable() const + */ + bool is_equitable() const; + + /* Splitting heuristics, documented in more detail in the cc-file. */ + SplittingHeuristic sh; + Partition::Cell* find_next_cell_to_be_splitted(Partition::Cell *cell); + Partition::Cell* sh_first(); + Partition::Cell* sh_first_smallest(); + Partition::Cell* sh_first_largest(); + Partition::Cell* sh_first_max_neighbours(); + Partition::Cell* sh_first_smallest_max_neighbours(); + Partition::Cell* sh_first_largest_max_neighbours(); + + void make_initial_equitable_partition(); + + void initialize_certificate(); + + bool is_automorphism(unsigned int* const perm); + + void sort_edges(); + + bool nucr_find_first_component(const unsigned int level); + bool nucr_find_first_component(const unsigned int level, + std::vector& component, + unsigned int& component_elements, + Partition::Cell*& sh_return); + +public: + /** + * Create a new directed graph with \a N vertices and no edges. + */ + Digraph(const unsigned int N = 0); + + /** + * Destroy the graph. + */ + ~Digraph(); + + /** + * Read the graph from the file \a fp in a variant of the DIMACS format. + * See the bliss website + * for the definition of the file format. + * Note that in the DIMACS file the vertices are numbered from 1 to N while + * in this C++ API they are from 0 to N-1. + * Thus the vertex n in the file corresponds to the vertex n-1 in the API. + * \param fp the file stream for the graph file + * \param errstr if non-null, the possible error messages are printed + * in this file stream + * \return a new Digraph object or 0 if reading failed for some + * reason + */ + static Digraph* read_dimacs(FILE* const fp, FILE* const errstr = stderr); + + /** + * \copydoc AbstractGraph::write_dimacs(FILE * const fp) + */ + void write_dimacs(FILE* const fp); + + + /** + * \copydoc AbstractGraph::write_dot(FILE *fp) + */ + void write_dot(FILE * const fp); + + /** + * \copydoc AbstractGraph::write_dot(const char * const file_name) + */ + void write_dot(const char * const file_name); + + /** + * \copydoc AbstractGraph::is_automorphism(const std::vector& perm) const + */ + bool is_automorphism(const std::vector& perm) const; + + + + /** + * \copydoc AbstractGraph::get_hash() + */ + virtual unsigned int get_hash(); + + /** + * Return the number of vertices in the graph. + */ + unsigned int get_nof_vertices() const {return vertices.size(); } + + /** + * Add a new vertex with color 'color' in the graph and return its index. + */ + unsigned int add_vertex(const unsigned int color = 0); + + /** + * Add an edge from the vertex \a source to the vertex \a target. + * Duplicate edges are ignored but try to avoid introducing + * them in the first place as they are not ignored immediately but will + * consume memory and computation resources for a while. + */ + void add_edge(const unsigned int source, const unsigned int target); + + /** + * Change the color of the vertex 'vertex' to 'color'. + */ + void change_color(const unsigned int vertex, const unsigned int color); + + /** + * Compare this graph with the graph \a other. + * Returns 0 if the graphs are equal, and a negative (positive) integer + * if this graph is "smaller than" ("greater than", resp.) than \a other. + */ + int cmp(Digraph& other); + + /** + * Set the splitting heuristic used by the automorphism and canonical + * labeling algorithm. + * The selected splitting heuristics affects the computed canonical + * labelings; therefore, if you want to compare whether two graphs + * are isomorphic by computing and comparing (for equality) their + * canonical versions, be sure to use the same splitting heuristics + * for both graphs. + */ + void set_splitting_heuristic(SplittingHeuristic shs) {sh = shs; } + + /** + * \copydoc AbstractGraph::permute(const unsigned int* const perm) const + */ + Digraph* permute(const unsigned int* const perm) const; + Digraph* permute(const std::vector& perm) const; +}; + + + + +} + +#endif diff --git a/src/bliss/heap.hh b/src/bliss/heap.hh new file mode 100644 index 0000000..cf65913 --- /dev/null +++ b/src/bliss/heap.hh @@ -0,0 +1,83 @@ +#ifndef BLISS_HEAP_HH +#define BLISS_HEAP_HH + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +/** \internal + * \brief A capacity bounded heap data structure. + */ + +class Heap +{ + unsigned int N; + unsigned int n; + unsigned int *array; + void upheap(unsigned int k); + void downheap(unsigned int k); +public: + /** + * Create a new heap. + * init() must be called after this. + */ + Heap() {array = 0; n = 0; N = 0; } + ~Heap(); + + /** + * Initialize the heap to have the capacity to hold \e size elements. + */ + void init(const unsigned int size); + + /** + * Is the heap empty? + * Time complexity is O(1). + */ + bool is_empty() const {return(n==0); } + + /** + * Remove all the elements in the heap. + * Time complexity is O(1). + */ + void clear() {n = 0;} + + /** + * Insert the element \a e in the heap. + * Time complexity is O(log(N)), where N is the number of elements + * currently in the heap. + */ + void insert(const unsigned int e); + + /** + * Remove and return the smallest element in the heap. + * Time complexity is O(log(N)), where N is the number of elements + * currently in the heap. + */ + unsigned int remove(); + + /** + * Get the number of elements in the heap. + */ + unsigned int size() const {return n; } +}; + +} // namespace bliss + +#endif diff --git a/src/bliss/igraph-changes.md b/src/bliss/igraph-changes.md new file mode 100644 index 0000000..32df7c9 --- /dev/null +++ b/src/bliss/igraph-changes.md @@ -0,0 +1,36 @@ +This file lists changes that were made to the original Bliss package (version 0.73) to integrate it into igraph. + +Remove `Makefile`, `Doxyfile` + +Removed `bliss.cc`, `bliss_C.cc`, `bliss_C.h` + +Remove references to `Timer` class in `graph.cc` + +Remove `timer.cc` and `timer.hh` + +Add to `defs.hh`: + + #include "config.h" + + #if HAVE_GMP == 1 + # define BLISS_USE_GMP + #endif + +In `bignum.hh`: + +Move `#if defined(BLISS_USE_GMP) ...` below `#include "defs.h"` + +Add: + + #include "igraph_memory.h" + #include "igraph_error.h" + +Also add, for the `tostring` method without GMP: + + #include + #include + #include + +Add `tostring` member function to `BigNum` class for both cases (with or without GMP). + +In `graph.cc`, add IGRAPH_THREAD_LOCAL to the `PathInfo` global variable on line 612. diff --git a/src/bliss/kqueue.hh b/src/bliss/kqueue.hh new file mode 100644 index 0000000..41a4b70 --- /dev/null +++ b/src/bliss/kqueue.hh @@ -0,0 +1,162 @@ +#ifndef BLISS_KQUEUE_HH +#define BLISS_KQUEUE_HH + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +#include "defs.hh" + +namespace bliss { + +/** \internal + * \brief A very simple implementation of queues with fixed capacity. + */ + +template +class KQueue +{ +public: + /** + * Create a new queue with capacity zero. + * The function init() should be called next. + */ + KQueue(); + + ~KQueue(); + + /** + * Initialize the queue to have the capacity to hold at most \a N elements. + */ + void init(const unsigned int N); + + /** Is the queue empty? */ + bool is_empty() const; + + /** Return the number of elements in the queue. */ + unsigned int size() const; + + /** Remove all the elements in the queue. */ + void clear(); + + /** Return (but don't remove) the first element in the queue. */ + Type front() const; + + /** Remove and return the first element of the queue. */ + Type pop_front(); + + /** Push the element \a e in the front of the queue. */ + void push_front(Type e); + + /** Remove and return the last element of the queue. */ + Type pop_back(); + + /** Push the element \a e in the back of the queue. */ + void push_back(Type e); +private: + Type *entries, *end; + Type *head, *tail; +}; + +template +KQueue::KQueue() +{ + entries = 0; + end = 0; + head = 0; + tail = 0; +} + +template +KQueue::~KQueue() +{ + if(entries) + free(entries); +} + +template +void KQueue::init(const unsigned int k) +{ + assert(k > 0); + if(entries) + free(entries); + entries = (Type*)malloc((k + 1) * sizeof(Type)); + end = entries + k + 1; + head = entries; + tail = head; +} + +template +void KQueue::clear() +{ + head = entries; + tail = head; +} + +template +bool KQueue::is_empty() const +{ + return(head == tail); +} + +template +unsigned int KQueue::size() const +{ + if(tail >= head) + return(tail - head); + return((end - head) + (tail - entries)); +} + +template +Type KQueue::front() const +{ + return *head; +} + +template +Type KQueue::pop_front() +{ + Type *old_head = head; + head++; + if(head == end) + head = entries; + return *old_head; +} + +template +void KQueue::push_front(Type e) +{ + if(head == entries) + head = end - 1; + else + head--; + *head = e; +} + +template +void KQueue::push_back(Type e) +{ + *tail = e; + tail++; + if(tail == end) + tail = entries; +} + +} // namespace bliss + +#endif diff --git a/src/bliss/kstack.hh b/src/bliss/kstack.hh new file mode 100644 index 0000000..08493a0 --- /dev/null +++ b/src/bliss/kstack.hh @@ -0,0 +1,141 @@ +#ifndef BLISS_KSTACK_H +#define BLISS_KSTACK_H + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +#include +#include "defs.hh" + +namespace bliss { + +/** \internal + * \brief A very simple implementation of a stack with fixed capacity. + */ +template +class KStack { +public: + /** + * Create a new stack with zero capacity. + * The function init() should be called next. + */ + KStack(); + + /** + * Create a new stack with the capacity to hold at most \a N elements. + */ + KStack(int N); + + ~KStack(); + + /** + * Initialize the stack to have the capacity to hold at most \a N elements. + */ + void init(int N); + + /** + * Is the stack empty? + */ + bool is_empty() const {return(cursor == entries); } + + /** + * Return (but don't remove) the top element of the stack. + */ + Type top() const {BLISS_ASSERT(cursor > entries); return *cursor; } + + /** + * Pop (remove) the top element of the stack. + */ + Type pop() + { + return *cursor--; + } + + /** + * Push the element \a e in the stack. + */ + void push(Type e) + { + *(++cursor) = e; + } + + /** Remove all the elements in the stack. */ + void clean() {cursor = entries; } + + /** + * Get the number of elements in the stack. + */ + unsigned int size() const {return(cursor - entries); } + + /** + * Return the i:th element in the stack, where \a i is in the range + * 0,...,this.size()-1; the 0:th element is the bottom element + * in the stack. + */ + Type element_at(unsigned int i) + { + assert(i < size()); + return entries[i+1]; + } + + /** Return the capacity (NOT the number of elements) of the stack. */ + int capacity() {return kapacity; } +private: + int kapacity; + Type *entries; + Type *cursor; +}; + +template +KStack::KStack() +{ + kapacity = 0; + entries = 0; + cursor = 0; +} + +template +KStack::KStack(int k) +{ + assert(k > 0); + kapacity = k; + entries = (Type*)malloc((k+1) * sizeof(Type)); + cursor = entries; +} + +template +void KStack::init(int k) +{ + assert(k > 0); + if(entries) + free(entries); + kapacity = k; + entries = (Type*)malloc((k+1) * sizeof(Type)); + cursor = entries; +} + +template +KStack::~KStack() +{ + free(entries); +} + +} // namespace bliss + +#endif diff --git a/src/bliss/orbit.cc b/src/bliss/orbit.cc new file mode 100644 index 0000000..9d6d706 --- /dev/null +++ b/src/bliss/orbit.cc @@ -0,0 +1,144 @@ +#include +#include +#include "defs.hh" +#include "orbit.hh" + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +Orbit::Orbit() +{ + orbits = 0; + in_orbit = 0; + nof_elements = 0; +} + + +Orbit::~Orbit() +{ + if(orbits) + { + free(orbits); + orbits = 0; + } + if(in_orbit) + { + free(in_orbit); + in_orbit = 0; + } + nof_elements = 0; +} + + +void Orbit::init(const unsigned int n) +{ + assert(n > 0); + if(orbits) free(orbits); + orbits = (OrbitEntry*)malloc(n * sizeof(OrbitEntry)); + if(in_orbit) free(in_orbit); + in_orbit = (OrbitEntry**)malloc(n * sizeof(OrbitEntry*)); + nof_elements = n; + + reset(); +} + + +void Orbit::reset() +{ + assert(orbits); + assert(in_orbit); + + for(unsigned int i = 0; i < nof_elements; i++) + { + orbits[i].element = i; + orbits[i].next = 0; + orbits[i].size = 1; + in_orbit[i] = &orbits[i]; + } + _nof_orbits = nof_elements; +} + + +void Orbit::merge_orbits(OrbitEntry *orbit1, OrbitEntry *orbit2) +{ + + if(orbit1 != orbit2) + { + _nof_orbits--; + /* Only update the elements in the smaller orbit */ + if(orbit1->size > orbit2->size) + { + OrbitEntry * const temp = orbit2; + orbit2 = orbit1; + orbit1 = temp; + } + /* Link the elements of orbit1 to the almost beginning of orbit2 */ + OrbitEntry *e = orbit1; + while(e->next) + { + in_orbit[e->element] = orbit2; + e = e->next; + } + in_orbit[e->element] = orbit2; + e->next = orbit2->next; + orbit2->next = orbit1; + /* Keep the minimal orbit representative in the beginning */ + if(orbit1->element < orbit2->element) + { + const unsigned int temp = orbit1->element; + orbit1->element = orbit2->element; + orbit2->element = temp; + } + orbit2->size += orbit1->size; + } +} + + +void Orbit::merge_orbits(unsigned int e1, unsigned int e2) +{ + + merge_orbits(in_orbit[e1], in_orbit[e2]); +} + + +bool Orbit::is_minimal_representative(unsigned int element) const +{ + return(get_minimal_representative(element) == element); +} + + +unsigned int Orbit::get_minimal_representative(unsigned int element) const +{ + + OrbitEntry * const orbit = in_orbit[element]; + + return(orbit->element); +} + + +unsigned int Orbit::orbit_size(unsigned int element) const +{ + + return(in_orbit[element]->size); +} + + +} // namespace bliss diff --git a/src/bliss/orbit.hh b/src/bliss/orbit.hh new file mode 100644 index 0000000..fa1be12 --- /dev/null +++ b/src/bliss/orbit.hh @@ -0,0 +1,111 @@ +#ifndef BLISS_ORBIT_HH +#define BLISS_ORBIT_HH + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +/** \internal + * \brief A class for representing orbit information. + * + * Given a set {0,...,N-1} of N elements, represent equivalence + * classes (that is, unordered partitions) of the elements. + * Supports only equivalence class merging, not splitting. + * Merging two classes requires time O(k), where k is the number of + * the elements in the smaller of the merged classes. + * Getting the smallest representative in a class (and thus testing + * whether two elements belong to the same class) is a constant time operation. + */ +class Orbit +{ + class OrbitEntry + { + public: + unsigned int element; + OrbitEntry *next; + unsigned int size; + }; + + OrbitEntry *orbits; + OrbitEntry **in_orbit; + unsigned int nof_elements; + unsigned int _nof_orbits; + void merge_orbits(OrbitEntry *o1, OrbitEntry *o2); + +public: + /** + * Create a new orbit information object. + * The init() function must be called next to actually initialize + * the object. + */ + Orbit(); + ~Orbit(); + + /** + * Initialize the orbit information to consider sets of \a N elements. + * It is required that \a N > 0. + * The orbit information is reset so that each element forms + * an orbit of its own. + * Time complexity is O(N). + * \sa reset() + */ + void init(const unsigned int N); + + /** + * Reset the orbits so that each element forms an orbit of its own. + * Time complexity is O(N). + */ + void reset(); + + /** + * Merge the orbits of the elements \a e1 and \a e2. + * Time complexity is O(k), where k is the number of elements in + * the smaller of the merged orbits. + */ + void merge_orbits(unsigned int e1, unsigned int e2); + + /** + * Is the element \a e the smallest element in its orbit? + * Time complexity is O(1). + */ + bool is_minimal_representative(unsigned int e) const; + + /** + * Get the smallest element in the orbit of the element \a e. + * Time complexity is O(1). + */ + unsigned int get_minimal_representative(unsigned int e) const; + + /** + * Get the number of elements in the orbit of the element \a e. + * Time complexity is O(1). + */ + unsigned int orbit_size(unsigned int e) const; + + /** + * Get the number of orbits. + * Time complexity is O(1). + */ + unsigned int nof_orbits() const {return _nof_orbits; } +}; + +} // namespace bliss + +#endif diff --git a/src/bliss/partition.cc b/src/bliss/partition.cc new file mode 100644 index 0000000..5dcbe16 --- /dev/null +++ b/src/bliss/partition.cc @@ -0,0 +1,1143 @@ +#include +#include +#include +#include "graph.hh" +#include "partition.hh" + +/* use 'and' instead of '&&' */ +#if _MSC_VER +#include +#endif + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +Partition::Partition() +{ + N = 0; + elements = 0; + in_pos = 0; + invariant_values = 0; + cells = 0; + free_cells = 0; + element_to_cell_map = 0; + graph = 0; + discrete_cell_count = 0; + /* Initialize a distribution count sorting array. */ + for(unsigned int i = 0; i < 256; i++) + dcs_count[i] = 0; + + cr_enabled = false; + cr_cells = 0; + cr_levels = 0; +} + + + +Partition::~Partition() +{ + if(elements) {free(elements); elements = 0; } + if(cells) {free(cells); cells = 0; } + if(element_to_cell_map) {free(element_to_cell_map); element_to_cell_map = 0; } + if(in_pos) {free(in_pos); in_pos = 0; } + if(invariant_values) {free(invariant_values); invariant_values = 0; } + N = 0; +} + + + +void Partition::init(const unsigned int M) +{ + assert(M > 0); + N = M; + + if(elements) + free(elements); + elements = (unsigned int*)malloc(N * sizeof(unsigned int)); + for(unsigned int i = 0; i < N; i++) + elements[i] = i; + + if(in_pos) + free(in_pos); + in_pos = (unsigned int**)malloc(N * sizeof(unsigned int*)); + for(unsigned int i = 0; i < N; i++) + in_pos[i] = elements + i; + + if(invariant_values) + free(invariant_values); + invariant_values = (unsigned int*)malloc(N * sizeof(unsigned int)); + for(unsigned int i = 0; i < N; i++) + invariant_values[i] = 0; + + if(cells) + free(cells); + cells = (Cell*)malloc(N * sizeof(Cell)); + + cells[0].first = 0; + cells[0].length = N; + cells[0].max_ival = 0; + cells[0].max_ival_count = 0; + cells[0].in_splitting_queue = false; + cells[0].in_neighbour_heap = false; + cells[0].prev = 0; + cells[0].next = 0; + cells[0].next_nonsingleton = 0; + cells[0].prev_nonsingleton = 0; + cells[0].split_level = 0; + first_cell = &cells[0]; + if(N == 1) + { + first_nonsingleton_cell = 0; + discrete_cell_count = 1; + } + else + { + first_nonsingleton_cell = &cells[0]; + discrete_cell_count = 0; + } + + for(unsigned int i = 1; i < N; i++) + { + cells[i].first = 0; + cells[i].length = 0; + cells[i].max_ival = 0; + cells[i].max_ival_count = 0; + cells[i].in_splitting_queue = false; + cells[i].in_neighbour_heap = false; + cells[i].prev = 0; + cells[i].next = (i < N-1)?&cells[i+1]:0; + cells[i].next_nonsingleton = 0; + cells[i].prev_nonsingleton = 0; + } + if(N > 1) + free_cells = &cells[1]; + else + free_cells = 0; + + if(element_to_cell_map) + free(element_to_cell_map); + element_to_cell_map = (Cell **)malloc(N * sizeof(Cell *)); + for(unsigned int i = 0; i < N; i++) + element_to_cell_map[i] = first_cell; + + splitting_queue.init(N); + refinement_stack.init(N); + + /* Reset the main backtracking stack */ + bt_stack.clear(); +} + + + + + + +Partition::BacktrackPoint +Partition::set_backtrack_point() +{ + BacktrackInfo info; + info.refinement_stack_size = refinement_stack.size(); + if(cr_enabled) + info.cr_backtrack_point = cr_get_backtrack_point(); + BacktrackPoint p = bt_stack.size(); + bt_stack.push_back(info); + return p; +} + + + +void +Partition::goto_backtrack_point(BacktrackPoint p) +{ + BacktrackInfo info = bt_stack[p]; + bt_stack.resize(p); + + if(cr_enabled) + cr_goto_backtrack_point(info.cr_backtrack_point); + + const unsigned int dest_refinement_stack_size = info.refinement_stack_size; + + assert(refinement_stack.size() >= dest_refinement_stack_size); + while(refinement_stack.size() > dest_refinement_stack_size) + { + RefInfo i = refinement_stack.pop(); + const unsigned int first = i.split_cell_first; + Cell* cell = get_cell(elements[first]); + + if(cell->first != first) + { + assert(cell->first < first); + assert(cell->split_level <= dest_refinement_stack_size); + goto done; + } + assert(cell->split_level > dest_refinement_stack_size); + + while(cell->split_level > dest_refinement_stack_size) + { + assert(cell->prev); + cell = cell->prev; + } + while(cell->next and + cell->next->split_level > dest_refinement_stack_size) + { + /* Merge next cell */ + Cell* const next_cell = cell->next; + if(cell->length == 1) + discrete_cell_count--; + if(next_cell->length == 1) + discrete_cell_count--; + /* Update element_to_cell_map values of elements added in cell */ + unsigned int* ep = elements + next_cell->first; + unsigned int* const lp = ep + next_cell->length; + for( ; ep < lp; ep++) + element_to_cell_map[*ep] = cell; + /* Update cell parameters */ + cell->length += next_cell->length; + if(next_cell->next) + next_cell->next->prev = cell; + cell->next = next_cell->next; + /* (Pseudo)free next_cell */ + next_cell->first = 0; + next_cell->length = 0; + next_cell->prev = 0; + next_cell->next = free_cells; + free_cells = next_cell; + } + + done: + if(i.prev_nonsingleton_first >= 0) + { + Cell* const prev_cell = get_cell(elements[i.prev_nonsingleton_first]); + cell->prev_nonsingleton = prev_cell; + prev_cell->next_nonsingleton = cell; + } + else + { + //assert(cell->prev_nonsingleton == 0); + cell->prev_nonsingleton = 0; + first_nonsingleton_cell = cell; + } + + if(i.next_nonsingleton_first >= 0) + { + Cell* const next_cell = get_cell(elements[i.next_nonsingleton_first]); + cell->next_nonsingleton = next_cell; + next_cell->prev_nonsingleton = cell; + } + else + { + //assert(cell->next_nonsingleton == 0); + cell->next_nonsingleton = 0; + } + } + +} + + + +Partition::Cell* +Partition::individualize(Partition::Cell * const cell, + const unsigned int element) +{ + + unsigned int * const pos = in_pos[element]; + + const unsigned int last = cell->first + cell->length - 1; + *pos = elements[last]; + in_pos[*pos] = pos; + elements[last] = element; + in_pos[element] = elements + last; + + Partition::Cell * const new_cell = aux_split_in_two(cell, cell->length-1); + element_to_cell_map[element] = new_cell; + + return new_cell; +} + + + +Partition::Cell* +Partition::aux_split_in_two(Partition::Cell* const cell, + const unsigned int first_half_size) +{ + RefInfo i; + + + /* (Pseudo)allocate new cell */ + Cell * const new_cell = free_cells; + free_cells = new_cell->next; + /* Update new cell parameters */ + new_cell->first = cell->first + first_half_size; + new_cell->length = cell->length - first_half_size; + new_cell->next = cell->next; + if(new_cell->next) + new_cell->next->prev = new_cell; + new_cell->prev = cell; + new_cell->split_level = refinement_stack.size()+1; + /* Update old, splitted cell parameters */ + cell->length = first_half_size; + cell->next = new_cell; + /* CR */ + if(cr_enabled) + cr_create_at_level_trailed(new_cell->first, cr_get_level(cell->first)); + + /* Add cell in refinement_stack for backtracking */ + i.split_cell_first = new_cell->first; + if(cell->prev_nonsingleton) + i.prev_nonsingleton_first = cell->prev_nonsingleton->first; + else + i.prev_nonsingleton_first = -1; + if(cell->next_nonsingleton) + i.next_nonsingleton_first = cell->next_nonsingleton->first; + else + i.next_nonsingleton_first = -1; + refinement_stack.push(i); + + /* Modify nonsingleton cell list */ + if(new_cell->length > 1) + { + new_cell->prev_nonsingleton = cell; + new_cell->next_nonsingleton = cell->next_nonsingleton; + if(new_cell->next_nonsingleton) + new_cell->next_nonsingleton->prev_nonsingleton = new_cell; + cell->next_nonsingleton = new_cell; + } + else + { + new_cell->next_nonsingleton = 0; + new_cell->prev_nonsingleton = 0; + discrete_cell_count++; + } + + if(cell->is_unit()) + { + if(cell->prev_nonsingleton) + cell->prev_nonsingleton->next_nonsingleton = cell->next_nonsingleton; + else + first_nonsingleton_cell = cell->next_nonsingleton; + if(cell->next_nonsingleton) + cell->next_nonsingleton->prev_nonsingleton = cell->prev_nonsingleton; + cell->next_nonsingleton = 0; + cell->prev_nonsingleton = 0; + discrete_cell_count++; + } + + return new_cell; +} + + + +size_t +Partition::print(FILE* const fp, const bool add_newline) const +{ + size_t r = 0; + const char* cell_sep = ""; + r += fprintf(fp, "["); + for(Cell* cell = first_cell; cell; cell = cell->next) + { + /* Print cell */ + r += fprintf(fp, "%s{", cell_sep); + cell_sep = ","; + const char* elem_sep = ""; + for(unsigned int i = 0; i < cell->length; i++) + { + r += fprintf(fp, "%s%u", elem_sep, elements[cell->first + i]); + elem_sep = ","; + } + r += fprintf(fp, "}"); + } + r += fprintf(fp, "]"); + if(add_newline) r += fprintf(fp, "\n"); + return r; +} + + + +size_t +Partition::print_signature(FILE* const fp, const bool add_newline) const +{ + size_t r = 0; + const char* cell_sep = ""; + r += fprintf(fp, "["); + for(Cell* cell = first_cell; cell; cell = cell->next) + { + if(cell->is_unit()) continue; + //fprintf(fp, "%s%u", cell_sep, cr_cells[cell->first].level); + r += fprintf(fp, "%s%u", cell_sep, cell->length); + cell_sep = ","; + } + r += fprintf(fp, "]"); + if(add_newline) r += fprintf(fp, "\n"); + return r; +} + + + +void +Partition::splitting_queue_add(Cell* const cell) +{ + static const unsigned int smallish_cell_threshold = 1; + cell->in_splitting_queue = true; + if(cell->length <= smallish_cell_threshold) + splitting_queue.push_front(cell); + else + splitting_queue.push_back(cell); +} + + + +void +Partition::splitting_queue_clear() +{ + while(!splitting_queue_is_empty()) + splitting_queue_pop(); +} + + + + + +/* + * Assumes that the invariant values are NOT the same + * and that the cell contains more than one element + */ +Partition::Cell* +Partition::sort_and_split_cell1(Partition::Cell* const cell) +{ +#if defined(BLISS_EXPENSIVE_CONSISTENCY_CHECKS) + assert(cell->length > 1); + assert(cell->first + cell->length <= N); + unsigned int nof_0_found = 0; + unsigned int nof_1_found = 0; + for(unsigned int i = cell->first; i < cell->first + cell->length; i++) + { + const unsigned int ival = invariant_values[elements[i]]; + assert(ival == 0 or ival == 1); + if(ival == 0) nof_0_found++; + else nof_1_found++; + } + assert(nof_0_found > 0); + assert(nof_1_found > 0); + assert(nof_1_found == cell->max_ival_count); + assert(nof_0_found + nof_1_found == cell->length); + assert(cell->max_ival == 1); +#endif + + + /* (Pseudo)allocate new cell */ + Cell* const new_cell = free_cells; + free_cells = new_cell->next; + +#define NEW_SORT1 +#ifdef NEW_SORT1 + unsigned int *ep0 = elements + cell->first; + unsigned int *ep1 = ep0 + cell->length - cell->max_ival_count; + if(cell->max_ival_count > cell->length / 2) + { + /* There are more ones than zeros, only move zeros */ + unsigned int * const end = ep0 + cell->length; + while(ep1 < end) + { + while(invariant_values[*ep1] == 0) + { + const unsigned int tmp = *ep1; + *ep1 = *ep0; + *ep0 = tmp; + in_pos[tmp] = ep0; + in_pos[*ep1] = ep1; + ep0++; + } + element_to_cell_map[*ep1] = new_cell; + invariant_values[*ep1] = 0; + ep1++; + } + } + else + { + /* There are more zeros than ones, only move ones */ + unsigned int * const end = ep1; + while(ep0 < end) + { + while(invariant_values[*ep0] != 0) + { + const unsigned int tmp = *ep0; + *ep0 = *ep1; + *ep1 = tmp; + in_pos[tmp] = ep1; + in_pos[*ep0] = ep0; + ep1++; + } + ep0++; + } + ep1 = end; + while(ep1 < elements + cell->first + cell->length) + { + element_to_cell_map[*ep1] = new_cell; + invariant_values[*ep1] = 0; + ep1++; + } + } + /* Update new cell parameters */ + new_cell->first = cell->first + cell->length - cell->max_ival_count; + new_cell->length = cell->length - (new_cell->first - cell->first); + new_cell->next = cell->next; + if(new_cell->next) + new_cell->next->prev = new_cell; + new_cell->prev = cell; + new_cell->split_level = refinement_stack.size()+1; + /* Update old, splitted cell parameters */ + cell->length = new_cell->first - cell->first; + cell->next = new_cell; + /* CR */ + if(cr_enabled) + cr_create_at_level_trailed(new_cell->first, cr_get_level(cell->first)); + +#else + /* Sort vertices in the cell according to the invariant values */ + unsigned int *ep0 = elements + cell->first; + unsigned int *ep1 = ep0 + cell->length; + while(ep1 > ep0) + { + const unsigned int element = *ep0; + const unsigned int ival = invariant_values[element]; + invariant_values[element] = 0; + if(ival == 0) + { + ep0++; + } + else + { + ep1--; + *ep0 = *ep1; + *ep1 = element; + element_to_cell_map[element] = new_cell; + in_pos[element] = ep1; + in_pos[*ep0] = ep0; + } + } + + + /* Update new cell parameters */ + new_cell->first = ep1 - elements; + new_cell->length = cell->length - (new_cell->first - cell->first); + new_cell->next = cell->next; + if(new_cell->next) + new_cell->next->prev = new_cell; + new_cell->prev = cell; + new_cell->split_level = cell->split_level; + /* Update old, splitted cell parameters */ + cell->length = new_cell->first - cell->first; + cell->next = new_cell; + cell->split_level = refinement_stack.size()+1; + /* CR */ + if(cr_enabled) + cr_create_at_level_trailed(new_cell->first, cr_get_level(cell->first)); + +#endif /* ifdef NEW_SORT1*/ + + /* Add cell in refinement stack for backtracking */ + { + RefInfo i; + i.split_cell_first = new_cell->first; + if(cell->prev_nonsingleton) + i.prev_nonsingleton_first = cell->prev_nonsingleton->first; + else + i.prev_nonsingleton_first = -1; + if(cell->next_nonsingleton) + i.next_nonsingleton_first = cell->next_nonsingleton->first; + else + i.next_nonsingleton_first = -1; + /* Modify nonsingleton cell list */ + if(new_cell->length > 1) + { + new_cell->prev_nonsingleton = cell; + new_cell->next_nonsingleton = cell->next_nonsingleton; + if(new_cell->next_nonsingleton) + new_cell->next_nonsingleton->prev_nonsingleton = new_cell; + cell->next_nonsingleton = new_cell; + } + else + { + new_cell->next_nonsingleton = 0; + new_cell->prev_nonsingleton = 0; + discrete_cell_count++; + } + if(cell->is_unit()) + { + if(cell->prev_nonsingleton) + cell->prev_nonsingleton->next_nonsingleton = cell->next_nonsingleton; + else + first_nonsingleton_cell = cell->next_nonsingleton; + if(cell->next_nonsingleton) + cell->next_nonsingleton->prev_nonsingleton = cell->prev_nonsingleton; + cell->next_nonsingleton = 0; + cell->prev_nonsingleton = 0; + discrete_cell_count++; + } + refinement_stack.push(i); + } + + + /* Add cells in splitting queue */ + if(cell->in_splitting_queue) { + /* Both cells must be included in splitting_queue in order to have + refinement to equitable partition */ + splitting_queue_add(new_cell); + } else { + Cell *min_cell, *max_cell; + if(cell->length <= new_cell->length) { + min_cell = cell; + max_cell = new_cell; + } else { + min_cell = new_cell; + max_cell = cell; + } + /* Put the smaller cell in splitting_queue */ + splitting_queue_add(min_cell); + if(max_cell->is_unit()) { + /* Put the "larger" cell also in splitting_queue */ + splitting_queue_add(max_cell); + } + } + + + return new_cell; +} + + + + + +/** + * An auxiliary function for distribution count sorting. + * Build start array so that + * dcs_start[0] = 0 and dcs_start[i+1] = dcs_start[i] + dcs_count[i]. + */ +void +Partition::dcs_cumulate_count(const unsigned int max) +{ + unsigned int* count_p = dcs_count; + unsigned int* start_p = dcs_start; + unsigned int sum = 0; + for(unsigned int i = max+1; i > 0; i--) + { + *start_p = sum; + start_p++; + sum += *count_p; + count_p++; + } +} + + +/** + * Distribution count sorting of cells with invariant values less than 256. + */ +Partition::Cell* +Partition::sort_and_split_cell255(Partition::Cell* const cell, + const unsigned int max_ival) +{ + + if(cell->is_unit()) + { + /* Reset invariant value */ + invariant_values[elements[cell->first]] = 0; + return cell; + } + +#ifdef BLISS_CONSISTENCY_CHECKS + for(unsigned int i = 0; i < 256; i++) + assert(dcs_count[i] == 0); +#endif + + /* + * Compute the distribution of invariant values to the count array + */ + { + const unsigned int *ep = elements + cell->first; + const unsigned int ival = invariant_values[*ep]; + dcs_count[ival]++; + ep++; +#if defined(BLISS_CONSISTENCY_CHECKS) + bool equal_invariant_values = true; +#endif + for(unsigned int i = cell->length - 1; i != 0; i--) + { + const unsigned int ival2 = invariant_values[*ep]; + dcs_count[ival2]++; +#if defined(BLISS_CONSISTENCY_CHECKS) + if(ival2 != ival) { + equal_invariant_values = false; + } +#endif + ep++; + } +#if defined(BLISS_CONSISTENCY_CHECKS) + assert(!equal_invariant_values); + if(equal_invariant_values) { + assert(dcs_count[ival] == cell->length); + dcs_count[ival] = 0; + clear_ivs(cell); + return cell; + } +#endif + } + + /* Build start array */ + dcs_cumulate_count(max_ival); + + + /* Do the sorting */ + for(unsigned int i = 0; i <= max_ival; i++) + { + unsigned int *ep = elements + cell->first + dcs_start[i]; + for(unsigned int j = dcs_count[i]; j > 0; j--) + { + while(true) + { + const unsigned int element = *ep; + const unsigned int ival = invariant_values[element]; + if(ival == i) + break; + *ep = elements[cell->first + dcs_start[ival]]; + elements[cell->first + dcs_start[ival]] = element; + dcs_start[ival]++; + dcs_count[ival]--; + } + ep++; + } + dcs_count[i] = 0; + } + +#if defined(BLISS_CONSISTENCY_CHECKS) + for(unsigned int i = 0; i < 256; i++) + assert(dcs_count[i] == 0); +#endif + + /* split cell */ + Cell* const new_cell = split_cell(cell); + return new_cell; +} + + + +/* + * Sort the elements in a cell according to their invariant values. + * The invariant values are not cleared. + * Warning: the in_pos array is left in incorrect state. + */ +bool +Partition::shellsort_cell(Partition::Cell* const cell) +{ + unsigned int h; + unsigned int* ep; + + + if(cell->is_unit()) + return false; + + /* Check whether all the elements have the same invariant value */ + bool equal_invariant_values = true; + { + ep = elements + cell->first; + const unsigned int ival = invariant_values[*ep]; + ep++; + for(unsigned int i = cell->length - 1; i > 0; i--) + { + if(invariant_values[*ep] != ival) { + equal_invariant_values = false; + break; + } + ep++; + } + } + if(equal_invariant_values) + return false; + + ep = elements + cell->first; + + for(h = 1; h <= cell->length/9; h = 3*h + 1) + ; + for( ; h > 0; h = h/3) { + for(unsigned int i = h; i < cell->length; i++) { + const unsigned int element = ep[i]; + const unsigned int ival = invariant_values[element]; + unsigned int j = i; + while(j >= h and invariant_values[ep[j-h]] > ival) { + ep[j] = ep[j-h]; + j -= h; + } + ep[j] = element; + } + } + return true; +} + + + +void +Partition::clear_ivs(Cell* const cell) +{ + unsigned int* ep = elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--, ep++) + invariant_values[*ep] = 0; +} + + +/* + * Assumes that the elements in the cell are sorted according to their + * invariant values. + */ +Partition::Cell* +Partition::split_cell(Partition::Cell* const original_cell) +{ + Cell* cell = original_cell; + const bool original_cell_was_in_splitting_queue = + original_cell->in_splitting_queue; + Cell* largest_new_cell = 0; + + while(true) + { + unsigned int* ep = elements + cell->first; + const unsigned int* const lp = ep + cell->length; + const unsigned int ival = invariant_values[*ep]; + invariant_values[*ep] = 0; + element_to_cell_map[*ep] = cell; + in_pos[*ep] = ep; + ep++; + while(ep < lp) + { + const unsigned int e = *ep; + if(invariant_values[e] != ival) + break; + invariant_values[e] = 0; + in_pos[e] = ep; + ep++; + element_to_cell_map[e] = cell; + } + if(ep == lp) + break; + + Cell* const new_cell = aux_split_in_two(cell, + (ep - elements) - cell->first); + + if(graph and graph->compute_eqref_hash) + { + graph->eqref_hash.update(new_cell->first); + graph->eqref_hash.update(new_cell->length); + graph->eqref_hash.update(ival); + } + + /* Add cells in splitting_queue */ + assert(!new_cell->is_in_splitting_queue()); + if(original_cell_was_in_splitting_queue) + { + /* In this case, all new cells are inserted in splitting_queue */ + assert(cell->is_in_splitting_queue()); + splitting_queue_add(new_cell); + } + else + { + /* Otherwise, we can omit one new cell from splitting_queue */ + assert(!cell->is_in_splitting_queue()); + if(largest_new_cell == 0) { + largest_new_cell = cell; + } else { + assert(!largest_new_cell->is_in_splitting_queue()); + if(cell->length > largest_new_cell->length) { + splitting_queue_add(largest_new_cell); + largest_new_cell = cell; + } else { + splitting_queue_add(cell); + } + } + } + /* Process the rest of the cell */ + cell = new_cell; + } + + + if(original_cell == cell) { + /* All the elements in cell had the same invariant value */ + return cell; + } + + /* Add cells in splitting_queue */ + if(!original_cell_was_in_splitting_queue) + { + /* Also consider the last new cell */ + assert(largest_new_cell); + if(cell->length > largest_new_cell->length) + { + splitting_queue_add(largest_new_cell); + largest_new_cell = cell; + } + else + { + splitting_queue_add(cell); + } + if(largest_new_cell->is_unit()) + { + /* Needed in certificate computation */ + splitting_queue_add(largest_new_cell); + } + } + + return cell; +} + + +Partition::Cell* +Partition::zplit_cell(Partition::Cell* const cell, + const bool max_ival_info_ok) +{ + + Cell* last_new_cell = cell; + + if(!max_ival_info_ok) + { + /* Compute max_ival info */ + assert(cell->max_ival == 0); + assert(cell->max_ival_count == 0); + unsigned int *ep = elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--, ep++) + { + const unsigned int ival = invariant_values[*ep]; + if(ival > cell->max_ival) + { + cell->max_ival = ival; + cell->max_ival_count = 1; + } + else if(ival == cell->max_ival) + { + cell->max_ival_count++; + } + } + } + +#ifdef BLISS_CONSISTENCY_CHECKS + /* Verify max_ival info */ + { + unsigned int nof_zeros = 0; + unsigned int max_ival = 0; + unsigned int max_ival_count = 0; + unsigned int *ep = elements + cell->first; + for(unsigned int i = cell->length; i > 0; i--, ep++) + { + const unsigned int ival = invariant_values[*ep]; + if(ival == 0) + nof_zeros++; + if(ival > max_ival) + { + max_ival = ival; + max_ival_count = 1; + } + else if(ival == max_ival) + max_ival_count++; + } + assert(max_ival == cell->max_ival); + assert(max_ival_count == cell->max_ival_count); + } +#endif + + /* max_ival info has been computed */ + + if(cell->max_ival_count == cell->length) + { + /* All invariant values are the same, clear 'em */ + if(cell->max_ival > 0) + clear_ivs(cell); + } + else + { + /* All invariant values are not the same */ + if(cell->max_ival == 1) + { + /* Specialized splitting for cells with binary invariant values */ + last_new_cell = sort_and_split_cell1(cell); + } + else if(cell->max_ival < 256) + { + /* Specialized splitting for cells with invariant values < 256 */ + last_new_cell = sort_and_split_cell255(cell, cell->max_ival); + } + else + { + /* Generic sorting and splitting */ + const bool sorted = shellsort_cell(cell); + assert(sorted); + last_new_cell = split_cell(cell); + } + } + cell->max_ival = 0; + cell->max_ival_count = 0; + return last_new_cell; +} + + + +/* + * + * Component recursion specific code + * + */ +void +Partition::cr_init() +{ + assert(bt_stack.empty()); + + cr_enabled = true; + + if(cr_cells) free(cr_cells); + cr_cells = (CRCell*)malloc(N * sizeof(CRCell)); + if(!cr_cells) {assert(false && "Mem out"); } + + if(cr_levels) free(cr_levels); + cr_levels = (CRCell**)malloc(N * sizeof(CRCell*)); + if(!cr_levels) {assert(false && "Mem out"); } + + for(unsigned int i = 0; i < N; i++) { + cr_levels[i] = 0; + cr_cells[i].level = UINT_MAX; + cr_cells[i].next = 0; + cr_cells[i].prev_next_ptr = 0; + } + + for(const Cell *cell = first_cell; cell; cell = cell->next) + cr_create_at_level_trailed(cell->first, 0); + + cr_max_level = 0; +} + + +void +Partition::cr_free() +{ + if(cr_cells) {free(cr_cells); cr_cells = 0; } + if(cr_levels) {free(cr_levels); cr_levels = 0; } + + cr_created_trail.clear(); + cr_splitted_level_trail.clear(); + cr_bt_info.clear(); + cr_max_level = 0; + + cr_enabled = false; +} + + +unsigned int +Partition::cr_split_level(const unsigned int level, + const std::vector& splitted_cells) +{ + assert(cr_enabled); + assert(level <= cr_max_level); + cr_levels[++cr_max_level] = 0; + cr_splitted_level_trail.push_back(level); + + for(unsigned int i = 0; i < splitted_cells.size(); i++) + { + const unsigned int cell_index = splitted_cells[i]; + assert(cell_index < N); + CRCell& cr_cell = cr_cells[cell_index]; + assert(cr_cell.level == level); + cr_cell.detach(); + cr_create_at_level(cell_index, cr_max_level); + } + + return cr_max_level; +} + + +unsigned int +Partition::cr_get_backtrack_point() +{ + assert(cr_enabled); + CR_BTInfo info; + info.created_trail_index = cr_created_trail.size(); + info.splitted_level_trail_index = cr_splitted_level_trail.size(); + cr_bt_info.push_back(info); + return cr_bt_info.size()-1; +} + + +void +Partition::cr_goto_backtrack_point(const unsigned int btpoint) +{ + assert(cr_enabled); + assert(btpoint < cr_bt_info.size()); + while(cr_created_trail.size() > cr_bt_info[btpoint].created_trail_index) + { + const unsigned int cell_index = cr_created_trail.back(); + cr_created_trail.pop_back(); + CRCell& cr_cell = cr_cells[cell_index]; + assert(cr_cell.level != UINT_MAX); + assert(cr_cell.prev_next_ptr); + cr_cell.detach(); + } + + while(cr_splitted_level_trail.size() > + cr_bt_info[btpoint].splitted_level_trail_index) + { + const unsigned int dest_level = cr_splitted_level_trail.back(); + cr_splitted_level_trail.pop_back(); + assert(cr_max_level > 0); + assert(dest_level < cr_max_level); + while(cr_levels[cr_max_level]) { + CRCell *cr_cell = cr_levels[cr_max_level]; + cr_cell->detach(); + cr_create_at_level(cr_cell - cr_cells, dest_level); + } + cr_max_level--; + } + cr_bt_info.resize(btpoint); +} + + +void +Partition::cr_create_at_level(const unsigned int cell_index, + const unsigned int level) +{ + assert(cr_enabled); + assert(cell_index < N); + assert(level < N); + CRCell& cr_cell = cr_cells[cell_index]; + assert(cr_cell.level == UINT_MAX); + assert(cr_cell.next == 0); + assert(cr_cell.prev_next_ptr == 0); + if(cr_levels[level]) + cr_levels[level]->prev_next_ptr = &(cr_cell.next); + cr_cell.next = cr_levels[level]; + cr_levels[level] = &cr_cell; + cr_cell.prev_next_ptr = &cr_levels[level]; + cr_cell.level = level; +} + + +void +Partition::cr_create_at_level_trailed(const unsigned int cell_index, + const unsigned int level) +{ + assert(cr_enabled); + cr_create_at_level(cell_index, level); + cr_created_trail.push_back(cell_index); +} + + +} // namespace bliss diff --git a/src/bliss/partition.hh b/src/bliss/partition.hh new file mode 100644 index 0000000..6316f0c --- /dev/null +++ b/src/bliss/partition.hh @@ -0,0 +1,308 @@ +#ifndef BLISS_PARTITION_HH +#define BLISS_PARTITION_HH + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + class Partition; +} + +#include +#include +#include +#include "kstack.hh" +#include "kqueue.hh" +#include "heap.hh" +#include "orbit.hh" +#include "graph.hh" + + +namespace bliss { + +/** \internal + * \brief A class for refinable, backtrackable ordered partitions. + * + * This is rather a data structure with some helper functions than + * a proper self-contained class. + * That is, for efficiency reasons the fields of this class are directly + * manipulated from bliss::AbstractGraph and its subclasses. + * Conversely, some methods of this class modify the fields of + * bliss::AbstractGraph, too. + */ +class Partition +{ +public: + /** + * \brief Data structure for holding information about a cell in a Partition. + */ + class Cell + { + friend class Partition; + public: + unsigned int length; + /* Index of the first element of the cell in + the Partition::elements array */ + unsigned int first; + unsigned int max_ival; + unsigned int max_ival_count; + private: + bool in_splitting_queue; + public: + bool in_neighbour_heap; + /* Pointer to the next cell, null if this is the last one. */ + Cell* next; + Cell* prev; + Cell* next_nonsingleton; + Cell* prev_nonsingleton; + unsigned int split_level; + /** Is this a unit cell? */ + bool is_unit() const {return(length == 1); } + /** Is this cell in splitting queue? */ + bool is_in_splitting_queue() const {return(in_splitting_queue); } + }; + + +private: + + /** \internal + * Data structure for remembering information about splits in order to + * perform efficient backtracking over the splits. + */ + class RefInfo { + public: + unsigned int split_cell_first; + int prev_nonsingleton_first; + int next_nonsingleton_first; + }; + /** \internal + * A stack for remembering the splits, used for backtracking. + */ + KStack refinement_stack; + + class BacktrackInfo { + public: + unsigned int refinement_stack_size; + unsigned int cr_backtrack_point; + }; + + /** \internal + * The main stack for enabling backtracking. + */ + std::vector bt_stack; + +public: + AbstractGraph* graph; + + /* Used during equitable partition refinement */ + KQueue splitting_queue; + void splitting_queue_add(Cell* const cell); + Cell* splitting_queue_pop(); + bool splitting_queue_is_empty() const; + void splitting_queue_clear(); + + + /** Type for backtracking points. */ + typedef unsigned int BacktrackPoint; + + /** + * Get a new backtrack point for the current partition + */ + BacktrackPoint set_backtrack_point(); + + /** + * Backtrack to the point \a p and remove it. + */ + void goto_backtrack_point(BacktrackPoint p); + + /** + * Split the non-unit Cell \a cell = {\a element,e1,e2,...,en} containing + * the element \a element in two: + * \a cell = {e1,...,en} and \a newcell = {\a element}. + * @param cell a non-unit Cell + * @param element an element in \a cell + * @return the new unit Cell \a newcell + */ + Cell* individualize(Cell* const cell, + const unsigned int element); + + Cell* aux_split_in_two(Cell* const cell, + const unsigned int first_half_size); + + +private: + unsigned int N; + Cell* cells; + Cell* free_cells; + unsigned int discrete_cell_count; +public: + Cell* first_cell; + Cell* first_nonsingleton_cell; + unsigned int *elements; + /* invariant_values[e] gives the invariant value of the element e */ + unsigned int *invariant_values; + /* element_to_cell_map[e] gives the cell of the element e */ + Cell **element_to_cell_map; + /** Get the cell of the element \a e */ + Cell* get_cell(const unsigned int e) const { + return element_to_cell_map[e]; + } + /* in_pos[e] points to the elements array s.t. *in_pos[e] = e */ + unsigned int **in_pos; + + Partition(); + ~Partition(); + + /** + * Initialize the partition to the unit partition (all elements in one cell) + * over the \a N > 0 elements {0,...,\a N-1}. + */ + void init(const unsigned int N); + + /** + * Returns true iff the partition is discrete, meaning that all + * the elements are in their own cells. + */ + bool is_discrete() const {return(free_cells == 0); } + + unsigned int nof_discrete_cells() const {return(discrete_cell_count); } + + /** + * Print the partition into the file stream \a fp. + */ + size_t print(FILE* const fp, const bool add_newline = true) const; + + /** + * Print the partition cell sizes into the file stream \a fp. + */ + size_t print_signature(FILE* const fp, const bool add_newline = true) const; + + /* + * Splits the Cell \a cell into [cell_1,...,cell_n] + * according to the invariant_values of the elements in \a cell. + * After splitting, cell_1 == \a cell. + * Returns the pointer to the Cell cell_n; + * cell_n != cell iff the Cell \a cell was actually splitted. + * The flag \a max_ival_info_ok indicates whether the max_ival and + * max_ival_count fields of the Cell \a cell have consistent values + * when the method is called. + * Clears the invariant values of elements in the Cell \a cell as well as + * the max_ival and max_ival_count fields of the Cell \a cell. + */ + Cell *zplit_cell(Cell * const cell, const bool max_ival_info_ok); + + /* + * Routines for component recursion + */ + void cr_init(); + void cr_free(); + unsigned int cr_get_level(const unsigned int cell_index) const; + unsigned int cr_split_level(const unsigned int level, + const std::vector& cells); + + /** Clear the invariant_values of the elements in the Cell \a cell. */ + void clear_ivs(Cell* const cell); + +private: + /* + * Component recursion data structures + */ + + /* Is component recursion support in use? */ + bool cr_enabled; + + class CRCell { + public: + unsigned int level; + CRCell* next; + CRCell** prev_next_ptr; + void detach() { + if(next) + next->prev_next_ptr = prev_next_ptr; + *(prev_next_ptr) = next; + level = UINT_MAX; + next = 0; + prev_next_ptr = 0; + } + }; + CRCell* cr_cells; + CRCell** cr_levels; + class CR_BTInfo { + public: + unsigned int created_trail_index; + unsigned int splitted_level_trail_index; + }; + std::vector cr_created_trail; + std::vector cr_splitted_level_trail; + std::vector cr_bt_info; + unsigned int cr_max_level; + void cr_create_at_level(const unsigned int cell_index, unsigned int level); + void cr_create_at_level_trailed(const unsigned int cell_index, unsigned int level); + unsigned int cr_get_backtrack_point(); + void cr_goto_backtrack_point(const unsigned int btpoint); + + + /* + * + * Auxiliary routines for sorting and splitting cells + * + */ + Cell* sort_and_split_cell1(Cell* cell); + Cell* sort_and_split_cell255(Cell* const cell, const unsigned int max_ival); + bool shellsort_cell(Cell* cell); + Cell* split_cell(Cell* const cell); + + /* + * Some auxiliary stuff needed for distribution count sorting. + * To make the code thread-safe (modulo the requirement that each graph is + * only accessed in one thread at a time), the arrays are owned by + * the partition instance, not statically defined. + */ + unsigned int dcs_count[256]; + unsigned int dcs_start[256]; + void dcs_cumulate_count(const unsigned int max); +}; + + +inline Partition::Cell* +Partition::splitting_queue_pop() +{ + Cell* const cell = splitting_queue.pop_front(); + cell->in_splitting_queue = false; + return cell; +} + +inline bool +Partition::splitting_queue_is_empty() const +{ + return splitting_queue.is_empty(); +} + + +inline unsigned int +Partition::cr_get_level(const unsigned int cell_index) const +{ + return(cr_cells[cell_index].level); +} + + + +} // namespace bliss + +#endif diff --git a/src/bliss/uintseqhash.cc b/src/bliss/uintseqhash.cc new file mode 100644 index 0000000..21b1a17 --- /dev/null +++ b/src/bliss/uintseqhash.cc @@ -0,0 +1,117 @@ +#include "uintseqhash.hh" + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +/* + * Random bits generated by + * http://www.fourmilab.ch/hotbits/ + */ +static unsigned int rtab[256] = { + 0xAEAA35B8, 0x65632E16, 0x155EDBA9, 0x01349B39, + 0x8EB8BD97, 0x8E4C5367, 0x8EA78B35, 0x2B1B4072, + 0xC1163893, 0x269A8642, 0xC79D7F6D, 0x6A32DEA0, + 0xD4D2DA56, 0xD96D4F47, 0x47B5F48A, 0x2587C6BF, + 0x642B71D8, 0x5DBBAF58, 0x5C178169, 0xA16D9279, + 0x75CDA063, 0x291BC48B, 0x01AC2F47, 0x5416DF7C, + 0x45307514, 0xB3E1317B, 0xE1C7A8DE, 0x3ACDAC96, + 0x11B96831, 0x32DE22DD, 0x6A1DA93B, 0x58B62381, + 0x283810E2, 0xBC30E6A6, 0x8EE51705, 0xB06E8DFB, + 0x729AB12A, 0xA9634922, 0x1A6E8525, 0x49DD4E19, + 0xE5DB3D44, 0x8C5B3A02, 0xEBDE2864, 0xA9146D9F, + 0x736D2CB4, 0xF5229F42, 0x712BA846, 0x20631593, + 0x89C02603, 0xD5A5BF6A, 0x823F4E18, 0x5BE5DEFF, + 0x1C4EBBFA, 0x5FAB8490, 0x6E559B0C, 0x1FE528D6, + 0xB3198066, 0x4A965EB5, 0xFE8BB3D5, 0x4D2F6234, + 0x5F125AA4, 0xBCC640FA, 0x4F8BC191, 0xA447E537, + 0xAC474D3C, 0x703BFA2C, 0x617DC0E7, 0xF26299D7, + 0xC90FD835, 0x33B71C7B, 0x6D83E138, 0xCBB1BB14, + 0x029CF5FF, 0x7CBD093D, 0x4C9825EF, 0x845C4D6D, + 0x124349A5, 0x53942D21, 0x800E60DA, 0x2BA6EB7F, + 0xCEBF30D3, 0xEB18D449, 0xE281F724, 0x58B1CB09, + 0xD469A13D, 0x9C7495C3, 0xE53A7810, 0xA866C08E, + 0x832A038B, 0xDDDCA484, 0xD5FE0DDE, 0x0756002B, + 0x2FF51342, 0x60FEC9C8, 0x061A53E3, 0x47B1884E, + 0xDC17E461, 0xA17A6A37, 0x3158E7E2, 0xA40D873B, + 0x45AE2140, 0xC8F36149, 0x63A4EE2D, 0xD7107447, + 0x6F90994F, 0x5006770F, 0xC1F3CA9A, 0x91B317B2, + 0xF61B4406, 0xA8C9EE8F, 0xC6939B75, 0xB28BBC3B, + 0x36BF4AEF, 0x3B12118D, 0x4D536ECF, 0x9CF4B46B, + 0xE8AB1E03, 0x8225A360, 0x7AE4A130, 0xC4EE8B50, + 0x50651797, 0x5BB4C59F, 0xD120EE47, 0x24F3A386, + 0xBE579B45, 0x3A378EFC, 0xC5AB007B, 0x3668942B, + 0x2DBDCC3A, 0x6F37F64C, 0xC24F862A, 0xB6F97FCF, + 0x9E4FA23D, 0x551AE769, 0x46A8A5A6, 0xDC1BCFDD, + 0x8F684CF9, 0x501D811B, 0x84279F80, 0x2614E0AC, + 0x86445276, 0xAEA0CE71, 0x0812250F, 0xB586D18A, + 0xC68D721B, 0x44514E1D, 0x37CDB99A, 0x24731F89, + 0xFA72E589, 0x81E6EBA2, 0x15452965, 0x55523D9D, + 0x2DC47E14, 0x2E7FA107, 0xA7790F23, 0x40EBFDBB, + 0x77E7906B, 0x6C1DB960, 0x1A8B9898, 0x65FA0D90, + 0xED28B4D8, 0x34C3ED75, 0x768FD2EC, 0xFAB60BCB, + 0x962C75F4, 0x304F0498, 0x0A41A36B, 0xF7DE2A4A, + 0xF4770FE2, 0x73C93BBB, 0xD21C82C5, 0x6C387447, + 0x8CDB4CB9, 0x2CC243E8, 0x41859E3D, 0xB667B9CB, + 0x89681E8A, 0x61A0526C, 0x883EDDDC, 0x539DE9A4, + 0xC29E1DEC, 0x97C71EC5, 0x4A560A66, 0xBD7ECACF, + 0x576AE998, 0x31CE5616, 0x97172A6C, 0x83D047C4, + 0x274EA9A8, 0xEB31A9DA, 0x327209B5, 0x14D1F2CB, + 0x00FE1D96, 0x817DBE08, 0xD3E55AED, 0xF2D30AFC, + 0xFB072660, 0x866687D6, 0x92552EB9, 0xEA8219CD, + 0xF7927269, 0xF1948483, 0x694C1DF5, 0xB7D8B7BF, + 0xFFBC5D2F, 0x2E88B849, 0x883FD32B, 0xA0331192, + 0x8CB244DF, 0x41FAF895, 0x16902220, 0x97FB512A, + 0x2BEA3CC4, 0xAF9CAE61, 0x41ACD0D5, 0xFD2F28FF, + 0xE780ADFA, 0xB3A3A76E, 0x7112AD87, 0x7C3D6058, + 0x69E64FFF, 0xE5F8617C, 0x8580727C, 0x41F54F04, + 0xD72BE498, 0x653D1795, 0x1275A327, 0x14B499D4, + 0x4E34D553, 0x4687AA39, 0x68B64292, 0x5C18ABC3, + 0x41EABFCC, 0x92A85616, 0x82684CF8, 0x5B9F8A4E, + 0x35382FFE, 0xFB936318, 0x52C08E15, 0x80918B2E, + 0x199EDEE0, 0xA9470163, 0xEC44ACDD, 0x612D6735, + 0x8F88EA7D, 0x759F5EA4, 0xE5CC7240, 0x68CFEB8B, + 0x04725601, 0x0C22C23E, 0x5BC97174, 0x89965841, + 0x5D939479, 0x690F338A, 0x3C2D4380, 0xDAE97F2B +}; + + +void UintSeqHash::update(unsigned int i) +{ + i++; + while(i > 0) + { + h ^= rtab[i & 0xff]; +#if 1 + const unsigned int b = (h & 0x80000000) >> 31; + i = i >> 8; + h = (h << 1) | b; +#else + const unsigned int b = h & 0x80000000; + h = h << 1; + if(b != 0) + h++; + i = i >> 8; +#endif + } +} + + +} // namespace bliss diff --git a/src/bliss/uintseqhash.hh b/src/bliss/uintseqhash.hh new file mode 100644 index 0000000..6b9bfca --- /dev/null +++ b/src/bliss/uintseqhash.hh @@ -0,0 +1,65 @@ +#ifndef BLISS_UINTSEQHASH_HH +#define BLISS_UINTSEQHASH_HH + +#include + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +/** \internal + * \brief A hash for sequences of unsigned ints. + */ +class UintSeqHash +{ +protected: + unsigned int h; +public: + UintSeqHash() {h = 0; } + UintSeqHash(const UintSeqHash &other) {h = other.h; } + UintSeqHash& operator=(const UintSeqHash &other) {h = other.h; return *this; } + + /** Reset the hash value. */ + void reset() {h = 0; } + + /** Add the unsigned int \a n to the sequence. */ + void update(unsigned int n); + + /** Get the hash value of the sequence seen so far. */ + unsigned int get_value() const {return h; } + + /** Compare the hash values of this and \a other. + * Return -1/0/1 if the value of this is smaller/equal/greater than + * that of \a other. */ + int cmp(const UintSeqHash &other) const { + return (h < other.h)?-1:((h == other.h)?0:1); + } + /** An abbreviation for cmp(other) < 0 */ + bool is_lt(const UintSeqHash &other) const {return(cmp(other) < 0); } + /** An abbreviation for cmp(other) <= 0 */ + bool is_le(const UintSeqHash &other) const {return(cmp(other) <= 0); } + /** An abbreviation for cmp(other) == 0 */ + bool is_equal(const UintSeqHash &other) const {return(cmp(other) == 0); } +}; + + +} // namespace bliss + +#endif diff --git a/src/bliss/utils.cc b/src/bliss/utils.cc new file mode 100644 index 0000000..548f500 --- /dev/null +++ b/src/bliss/utils.cc @@ -0,0 +1,122 @@ +#include +#include +#include "utils.hh" + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +namespace bliss { + +void +print_permutation(FILE* const fp, + const unsigned int N, + const unsigned int* perm, + const unsigned int offset) +{ + assert(N > 0); + assert(perm); + for(unsigned int i = 0; i < N; i++) { + unsigned int j = perm[i]; + if(j == i) + continue; + bool is_first = true; + while(j != i) { + if(j < i) { + is_first = false; + break; + } + j = perm[j]; + } + if(!is_first) + continue; + fprintf(fp, "(%u,", i+offset); + j = perm[i]; + while(j != i) { + fprintf(fp, "%u", j+offset); + j = perm[j]; + if(j != i) + fprintf(fp, ","); + } + fprintf(fp, ")"); + } +} + +void +print_permutation(FILE* const fp, + const std::vector& perm, + const unsigned int offset) +{ + const unsigned int N = perm.size(); + for(unsigned int i = 0; i < N; i++) { + unsigned int j = perm[i]; + if(j == i) + continue; + bool is_first = true; + while(j != i) { + if(j < i) { + is_first = false; + break; + } + j = perm[j]; + } + if(!is_first) + continue; + fprintf(fp, "(%u,", i+offset); + j = perm[i]; + while(j != i) { + fprintf(fp, "%u", j+offset); + j = perm[j]; + if(j != i) + fprintf(fp, ","); + } + fprintf(fp, ")"); + } +} + +bool +is_permutation(const unsigned int N, const unsigned int* perm) +{ + if(N == 0) + return true; + std::vector m(N, false); + for(unsigned int i = 0; i < N; i++) { + if(perm[i] >= N) return false; + if(m[perm[i]]) return false; + m[perm[i]] = true; + } + return true; +} + +bool +is_permutation(const std::vector& perm) +{ + const unsigned int N = perm.size(); + if(N == 0) + return true; + std::vector m(N, false); + for(unsigned int i = 0; i < N; i++) { + if(perm[i] >= N) return false; + if(m[perm[i]]) return false; + m[perm[i]] = true; + } + return true; +} + + +} // namespace bliss diff --git a/src/bliss/utils.hh b/src/bliss/utils.hh new file mode 100644 index 0000000..99f2fde --- /dev/null +++ b/src/bliss/utils.hh @@ -0,0 +1,69 @@ +#ifndef BLISS_UTILS_HH +#define BLISS_UTILS_HH + +/* + Copyright (c) 2003-2015 Tommi Junttila + Released under the GNU Lesser General Public License version 3. + + This file is part of bliss. + + bliss is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, version 3 of the License. + + bliss is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with bliss. If not, see . +*/ + +/** + * \file + * \brief Some small utilities. + * + */ + +#include +using namespace std; + +namespace bliss { + +/** + * Print the permutation \a perm of {0,...,N-1} in the cycle format + * in the file stream \a fp. + * The amount \a offset is added to each element before printing, + * e.g. the permutation (2 4) is printed as (3 5) when \a offset is 1. + */ +void print_permutation(FILE* fp, + const unsigned int N, + const unsigned int* perm, + const unsigned int offset = 0); + +/** + * Print the permutation \a perm of {0,...,N-1} in the cycle format + * in the file stream \a fp. + * The amount \a offset is added to each element before printing, + * e.g. the permutation (2 4) is printed as (3 5) when \a offset is 1. + */ +void print_permutation(FILE* fp, + const std::vector& perm, + const unsigned int offset = 0); + +/** + * Check whether \a perm is a valid permutation on {0,...,N-1}. + * Slow, mainly for debugging and validation purposes. + */ +bool is_permutation(const unsigned int N, const unsigned int* perm); + +/** + * Check whether \a perm is a valid permutation on {0,...,N-1}. + * Slow, mainly for debugging and validation purposes. + */ +bool is_permutation(const std::vector& perm); + +} // namespace bliss + +#endif diff --git a/src/cattributes.c b/src/cattributes.c new file mode 100644 index 0000000..d398962 --- /dev/null +++ b/src/cattributes.c @@ -0,0 +1,4211 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_attributes.h" +#include "igraph_memory.h" +#include "igraph_math.h" +#include "igraph_interface.h" +#include "igraph_random.h" +#include "config.h" + +#include + +/* An attribute is either a numeric vector (vector_t) or a string + vector (strvector_t). The attribute itself is stored in a + struct igraph_attribute_record_t, there is one such object for each + attribute. The igraph_t has a pointer to an array of three + vector_ptr_t's which contains pointers to + igraph_i_cattribute_t's. Graph attributes are first, then vertex + and edge attributes. */ + +igraph_bool_t igraph_i_cattribute_find(const igraph_vector_ptr_t *ptrvec, + const char *name, long int *idx) { + long int i, n = igraph_vector_ptr_size(ptrvec); + igraph_bool_t l = 0; + for (i = 0; !l && i < n; i++) { + igraph_attribute_record_t *rec = VECTOR(*ptrvec)[i]; + l = !strcmp(rec->name, name); + } + if (idx) { + *idx = i - 1; + } + return l; +} + +typedef struct igraph_i_cattributes_t { + igraph_vector_ptr_t gal; + igraph_vector_ptr_t val; + igraph_vector_ptr_t eal; +} igraph_i_cattributes_t; + +int igraph_i_cattributes_copy_attribute_record(igraph_attribute_record_t **newrec, + const igraph_attribute_record_t *rec) { + igraph_vector_t *num, *newnum; + igraph_strvector_t *str, *newstr; + + *newrec = igraph_Calloc(1, igraph_attribute_record_t); + if (!(*newrec)) { + IGRAPH_ERROR("Cannot copy attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, *newrec); + (*newrec)->type = rec->type; + (*newrec)->name = strdup(rec->name); + if (!(*newrec)->name) { + IGRAPH_ERROR("Cannot copy attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (void*)(*newrec)->name); + if (rec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + num = (igraph_vector_t *)rec->value; + newnum = igraph_Calloc(1, igraph_vector_t); + if (!newnum) { + IGRAPH_ERROR("Cannot copy attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newnum); + IGRAPH_CHECK(igraph_vector_copy(newnum, num)); + IGRAPH_FINALLY(igraph_vector_destroy, newnum); + (*newrec)->value = newnum; + } else if (rec->type == IGRAPH_ATTRIBUTE_STRING) { + str = (igraph_strvector_t*)rec->value; + newstr = igraph_Calloc(1, igraph_strvector_t); + if (!newstr) { + IGRAPH_ERROR("Cannot copy attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newstr); + IGRAPH_CHECK(igraph_strvector_copy(newstr, str)); + IGRAPH_FINALLY(igraph_strvector_destroy, newstr); + (*newrec)->value = newstr; + } else if (rec->type == IGRAPH_ATTRIBUTE_BOOLEAN) { + igraph_vector_bool_t *log = (igraph_vector_bool_t*) rec->value; + igraph_vector_bool_t *newlog = igraph_Calloc(1, igraph_vector_bool_t); + if (!newlog) { + IGRAPH_ERROR("Cannot copy attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newlog); + IGRAPH_CHECK(igraph_vector_bool_copy(newlog, log)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newlog); + (*newrec)->value = newlog; + } + + IGRAPH_FINALLY_CLEAN(4); + return 0; +} + + +int igraph_i_cattribute_init(igraph_t *graph, igraph_vector_ptr_t *attr) { + igraph_attribute_record_t *attr_rec; + long int i, n; + igraph_i_cattributes_t *nattr; + + n = attr ? igraph_vector_ptr_size(attr) : 0; + + nattr = igraph_Calloc(1, igraph_i_cattributes_t); + if (!nattr) { + IGRAPH_ERROR("Can't init attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, nattr); + + IGRAPH_CHECK(igraph_vector_ptr_init(&nattr->gal, n)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &nattr->gal); + IGRAPH_CHECK(igraph_vector_ptr_init(&nattr->val, 0)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &nattr->val); + IGRAPH_CHECK(igraph_vector_ptr_init(&nattr->eal, 0)); + IGRAPH_FINALLY_CLEAN(3); + + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_i_cattributes_copy_attribute_record( + &attr_rec, VECTOR(*attr)[i])); + VECTOR(nattr->gal)[i] = attr_rec; + } + + graph->attr = nattr; + + return 0; +} + +void igraph_i_cattribute_destroy(igraph_t *graph) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *als[3] = { &attr->gal, &attr->val, &attr->eal }; + long int i, n, a; + igraph_vector_t *num; + igraph_strvector_t *str; + igraph_vector_bool_t *boolvec; + igraph_attribute_record_t *rec; + for (a = 0; a < 3; a++) { + n = igraph_vector_ptr_size(als[a]); + for (i = 0; i < n; i++) { + rec = VECTOR(*als[a])[i]; + if (rec) { + if (rec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + num = (igraph_vector_t*)rec->value; + igraph_vector_destroy(num); + igraph_free(num); + } else if (rec->type == IGRAPH_ATTRIBUTE_STRING) { + str = (igraph_strvector_t*)rec->value; + igraph_strvector_destroy(str); + igraph_free(str); + } else if (rec->type == IGRAPH_ATTRIBUTE_BOOLEAN) { + boolvec = (igraph_vector_bool_t*)rec->value; + igraph_vector_bool_destroy(boolvec); + igraph_free(boolvec); + } + igraph_free((char*)rec->name); + igraph_free(rec); + } + } + } + igraph_vector_ptr_destroy(&attr->gal); + igraph_vector_ptr_destroy(&attr->val); + igraph_vector_ptr_destroy(&attr->eal); + igraph_free(graph->attr); + graph->attr = 0; +} + +/* Almost the same as destroy, but we might have null pointers */ + +void igraph_i_cattribute_copy_free(igraph_i_cattributes_t *attr) { + igraph_vector_ptr_t *als[3] = { &attr->gal, &attr->val, &attr->eal }; + long int i, n, a; + igraph_vector_t *num; + igraph_strvector_t *str; + igraph_vector_bool_t *boolvec; + igraph_attribute_record_t *rec; + for (a = 0; a < 3; a++) { + n = igraph_vector_ptr_size(als[a]); + for (i = 0; i < n; i++) { + rec = VECTOR(*als[a])[i]; + if (!rec) { + continue; + } + if (rec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + num = (igraph_vector_t*)rec->value; + igraph_vector_destroy(num); + igraph_free(num); + } else if (rec->type == IGRAPH_ATTRIBUTE_BOOLEAN) { + boolvec = (igraph_vector_bool_t*)rec->value; + igraph_vector_bool_destroy(boolvec); + igraph_free(boolvec); + } else if (rec->type == IGRAPH_ATTRIBUTE_STRING) { + str = (igraph_strvector_t*)rec->value; + igraph_strvector_destroy(str); + igraph_free(str); + } + igraph_free((char*)rec->name); + igraph_free(rec); + } + } +} + +/* No reference counting here. If you use attributes in C you should + know what you're doing. */ + +int igraph_i_cattribute_copy(igraph_t *to, const igraph_t *from, + igraph_bool_t ga, igraph_bool_t va, igraph_bool_t ea) { + igraph_i_cattributes_t *attrfrom = from->attr, *attrto; + igraph_vector_ptr_t *alto[3], *alfrom[3] = { &attrfrom->gal, &attrfrom->val, + &attrfrom->eal + }; + long int i, n, a; + igraph_bool_t copy[3] = { ga, va, ea }; + to->attr = attrto = igraph_Calloc(1, igraph_i_cattributes_t); + if (!attrto) { + IGRAPH_ERROR("Cannot copy attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, attrto); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&attrto->gal, 0); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&attrto->val, 0); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&attrto->eal, 0); + IGRAPH_FINALLY_CLEAN(3); + IGRAPH_FINALLY(igraph_i_cattribute_copy_free, attrto); + + alto[0] = &attrto->gal; alto[1] = &attrto->val; alto[2] = &attrto->eal; + for (a = 0; a < 3; a++) { + if (copy[a]) { + n = igraph_vector_ptr_size(alfrom[a]); + IGRAPH_CHECK(igraph_vector_ptr_resize(alto[a], n)); + igraph_vector_ptr_null(alto[a]); + for (i = 0; i < n; i++) { + igraph_attribute_record_t *newrec; + IGRAPH_CHECK(igraph_i_cattributes_copy_attribute_record(&newrec, + VECTOR(*alfrom[a])[i])); + VECTOR(*alto[a])[i] = newrec; + } + } + } + + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +int igraph_i_cattribute_add_vertices(igraph_t *graph, long int nv, + igraph_vector_ptr_t *nattr) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int length = igraph_vector_ptr_size(val); + long int nattrno = nattr == NULL ? 0 : igraph_vector_ptr_size(nattr); + long int origlen = igraph_vcount(graph) - nv; + long int newattrs = 0, i; + igraph_vector_t news; + + /* First add the new attributes if any */ + newattrs = 0; + IGRAPH_VECTOR_INIT_FINALLY(&news, 0); + for (i = 0; i < nattrno; i++) { + igraph_attribute_record_t *nattr_entry = VECTOR(*nattr)[i]; + const char *nname = nattr_entry->name; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(val, nname, &j); + if (!l) { + newattrs++; + IGRAPH_CHECK(igraph_vector_push_back(&news, i)); + } else { + /* check types */ + if (nattr_entry->type != + ((igraph_attribute_record_t*)VECTOR(*val)[j])->type) { + IGRAPH_ERROR("You cannot mix attribute types", IGRAPH_EINVAL); + } + } + } + + /* Add NA/empty string vectors for the existing vertices */ + if (newattrs != 0) { + for (i = 0; i < newattrs; i++) { + igraph_attribute_record_t *tmp = VECTOR(*nattr)[(long int)VECTOR(news)[i]]; + igraph_attribute_record_t *newrec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_attribute_type_t type = tmp->type; + if (!newrec) { + IGRAPH_ERROR("Cannot add attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newrec); + newrec->type = type; + newrec->name = strdup(tmp->name); + if (!newrec->name) { + IGRAPH_ERROR("Cannot add attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)newrec->name); + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *newnum = igraph_Calloc(1, igraph_vector_t); + if (!newnum) { + IGRAPH_ERROR("Cannot add attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newnum); + IGRAPH_VECTOR_INIT_FINALLY(newnum, origlen); + newrec->value = newnum; + igraph_vector_fill(newnum, IGRAPH_NAN); + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *newstr = igraph_Calloc(1, igraph_strvector_t); + if (!newstr) { + IGRAPH_ERROR("Cannot add attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newstr); + IGRAPH_STRVECTOR_INIT_FINALLY(newstr, origlen); + newrec->value = newstr; + } else if (type == IGRAPH_ATTRIBUTE_BOOLEAN) { + igraph_vector_bool_t *newbool = igraph_Calloc(1, igraph_vector_bool_t); + if (!newbool) { + IGRAPH_ERROR("Cannot add attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newbool); + IGRAPH_CHECK(igraph_vector_bool_init(newbool, origlen)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newbool); + newrec->value = newbool; + igraph_vector_bool_fill(newbool, 0); + } + IGRAPH_CHECK(igraph_vector_ptr_push_back(val, newrec)); + IGRAPH_FINALLY_CLEAN(4); + } + length = igraph_vector_ptr_size(val); + } + + /* Now append the new values */ + for (i = 0; i < length; i++) { + igraph_attribute_record_t *oldrec = VECTOR(*val)[i]; + igraph_attribute_record_t *newrec = 0; + const char *name = oldrec->name; + long int j; + igraph_bool_t l = 0; + if (nattr) { + l = igraph_i_cattribute_find(nattr, name, &j); + } + if (l) { + /* This attribute is present in nattr */ + igraph_vector_t *oldnum, *newnum; + igraph_strvector_t *oldstr, *newstr; + igraph_vector_bool_t *oldbool, *newbool; + newrec = VECTOR(*nattr)[j]; + oldnum = (igraph_vector_t*)oldrec->value; + newnum = (igraph_vector_t*)newrec->value; + oldstr = (igraph_strvector_t*)oldrec->value; + newstr = (igraph_strvector_t*)newrec->value; + oldbool = (igraph_vector_bool_t*)oldrec->value; + newbool = (igraph_vector_bool_t*)newrec->value; + if (oldrec->type != newrec->type) { + IGRAPH_ERROR("Attribute types do not match", IGRAPH_EINVAL); + } + switch (oldrec->type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + if (nv != igraph_vector_size(newnum)) { + IGRAPH_ERROR("Invalid numeric attribute length", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_vector_append(oldnum, newnum)); + break; + case IGRAPH_ATTRIBUTE_STRING: + if (nv != igraph_strvector_size(newstr)) { + IGRAPH_ERROR("Invalid string attribute length", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_strvector_append(oldstr, newstr)); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + if (nv != igraph_vector_bool_size(newbool)) { + IGRAPH_ERROR("Invalid Boolean attribute length", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_vector_bool_append(oldbool, newbool)); + break; + default: + IGRAPH_WARNING("Invalid attribute type"); + break; + } + } else { + /* No such attribute, append NA's */ + igraph_vector_t *oldnum = (igraph_vector_t *)oldrec->value; + igraph_strvector_t *oldstr = (igraph_strvector_t*)oldrec->value; + igraph_vector_bool_t *oldbool = (igraph_vector_bool_t*)oldrec->value; + switch (oldrec->type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + IGRAPH_CHECK(igraph_vector_resize(oldnum, origlen + nv)); + for (j = origlen; j < origlen + nv; j++) { + VECTOR(*oldnum)[j] = IGRAPH_NAN; + } + break; + case IGRAPH_ATTRIBUTE_STRING: + IGRAPH_CHECK(igraph_strvector_resize(oldstr, origlen + nv)); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + IGRAPH_CHECK(igraph_vector_bool_resize(oldbool, origlen + nv)); + for (j = origlen; j < origlen + nv; j++) { + VECTOR(*oldbool)[j] = 0; + } + break; + default: + IGRAPH_WARNING("Invalid attribute type"); + break; + } + } + } + + igraph_vector_destroy(&news); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +void igraph_i_cattribute_permute_free(igraph_vector_ptr_t *v) { + long int i, n = igraph_vector_ptr_size(v); + for (i = 0; i < n; i++) { + igraph_attribute_record_t *rec = VECTOR(*v)[i]; + igraph_Free(rec->name); + if (rec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *numv = (igraph_vector_t*) rec->value; + igraph_vector_destroy(numv); + igraph_Free(numv); + } else if (rec->type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *strv = (igraph_strvector_t*) rec->value; + igraph_strvector_destroy(strv); + igraph_Free(strv); + } else if (rec->type == IGRAPH_ATTRIBUTE_BOOLEAN) { + igraph_vector_bool_t *boolv = (igraph_vector_bool_t*) rec->value; + igraph_vector_bool_destroy(boolv); + igraph_Free(boolv); + } + igraph_Free(rec); + } + igraph_vector_ptr_clear(v); +} + +int igraph_i_cattribute_permute_vertices(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_t *idx) { + + if (graph == newgraph) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int valno = igraph_vector_ptr_size(val); + long int i; + + for (i = 0; i < valno; i++) { + igraph_attribute_record_t *oldrec = VECTOR(*val)[i]; + igraph_attribute_type_t type = oldrec->type; + igraph_vector_t *num, *newnum; + igraph_strvector_t *str, *newstr; + igraph_vector_bool_t *oldbool, *newbool; + switch (type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + num = (igraph_vector_t*) oldrec->value; + newnum = igraph_Calloc(1, igraph_vector_t); + if (!newnum) { + IGRAPH_ERROR("Cannot permute vertex attributes", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(newnum, 0); + igraph_vector_index(num, newnum, idx); + oldrec->value = newnum; + igraph_vector_destroy(num); + igraph_Free(num); + IGRAPH_FINALLY_CLEAN(1); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + oldbool = (igraph_vector_bool_t*) oldrec->value; + newbool = igraph_Calloc(1, igraph_vector_bool_t); + if (!newbool) { + IGRAPH_ERROR("Cannot permute vertex attributes", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_bool_init(newbool, 0)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newbool); + igraph_vector_bool_index(oldbool, newbool, idx); + oldrec->value = newbool; + igraph_vector_bool_destroy(oldbool); + igraph_Free(oldbool); + IGRAPH_FINALLY_CLEAN(1); + break; + case IGRAPH_ATTRIBUTE_STRING: + str = (igraph_strvector_t*)oldrec->value; + newstr = igraph_Calloc(1, igraph_strvector_t); + if (!newstr) { + IGRAPH_ERROR("Cannot permute vertex attributes", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_strvector_init(newstr, 0)); + IGRAPH_FINALLY(igraph_strvector_destroy, newstr); + igraph_strvector_index(str, newstr, idx); + oldrec->value = newstr; + igraph_strvector_destroy(str); + igraph_Free(str); + IGRAPH_FINALLY_CLEAN(1); + break; + default: + IGRAPH_WARNING("Unknown edge attribute ignored"); + } + } + + } else { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int valno = igraph_vector_ptr_size(val); + long int i; + + /* New vertex attributes */ + igraph_i_cattributes_t *new_attr = newgraph->attr; + igraph_vector_ptr_t *new_val = &new_attr->val; + if (igraph_vector_ptr_size(new_val) != 0) { + IGRAPH_ERROR("Vertex attributes were already copied", + IGRAPH_EATTRIBUTES); + } + IGRAPH_CHECK(igraph_vector_ptr_resize(new_val, valno)); + + IGRAPH_FINALLY(igraph_i_cattribute_permute_free, new_val); + + for (i = 0; i < valno; i++) { + igraph_attribute_record_t *oldrec = VECTOR(*val)[i]; + igraph_attribute_type_t type = oldrec->type; + igraph_vector_t *num, *newnum; + igraph_strvector_t *str, *newstr; + igraph_vector_bool_t *oldbool, *newbool; + + /* The record itself */ + igraph_attribute_record_t *new_rec = + igraph_Calloc(1, igraph_attribute_record_t); + if (!new_rec) { + IGRAPH_ERROR("Cannot create vertex attributes", IGRAPH_ENOMEM); + } + new_rec->name = strdup(oldrec->name); + new_rec->type = oldrec->type; + VECTOR(*new_val)[i] = new_rec; + + /* The data */ + switch (type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + num = (igraph_vector_t*)oldrec->value; + newnum = igraph_Calloc(1, igraph_vector_t); + if (!newnum) { + IGRAPH_ERROR("Cannot permute vertex attributes", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(newnum, 0); + igraph_vector_index(num, newnum, idx); + new_rec->value = newnum; + IGRAPH_FINALLY_CLEAN(1); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + oldbool = (igraph_vector_bool_t*)oldrec->value; + newbool = igraph_Calloc(1, igraph_vector_bool_t); + if (!newbool) { + IGRAPH_ERROR("Cannot permute vertex attributes", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_bool_init(newbool, 0)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newbool); + igraph_vector_bool_index(oldbool, newbool, idx); + new_rec->value = newbool; + IGRAPH_FINALLY_CLEAN(1); + break; + case IGRAPH_ATTRIBUTE_STRING: + str = (igraph_strvector_t*)oldrec->value; + newstr = igraph_Calloc(1, igraph_strvector_t); + if (!newstr) { + IGRAPH_ERROR("Cannot permute vertex attributes", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_strvector_init(newstr, 0)); + IGRAPH_FINALLY(igraph_strvector_destroy, newstr); + igraph_strvector_index(str, newstr, idx); + new_rec->value = newstr; + IGRAPH_FINALLY_CLEAN(1); + break; + default: + IGRAPH_WARNING("Unknown vertex attribute ignored"); + } + } + } + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +typedef int igraph_cattributes_combine_num_t(const igraph_vector_t *input, + igraph_real_t *output); + +typedef int igraph_cattributes_combine_str_t(const igraph_strvector_t *input, + char **output); + +typedef int igraph_cattributes_combine_bool_t(const igraph_vector_bool_t *input, + igraph_bool_t *output); + +int igraph_i_cattributes_cn_sum(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + const igraph_vector_t *oldv = oldrec->value; + igraph_vector_t *newv = igraph_Calloc(1, igraph_vector_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_VECTOR_INIT_FINALLY(newv, newlen); + + for (i = 0; i < newlen; i++) { + igraph_real_t s = 0.0; + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int j, n = igraph_vector_size(idx); + for (j = 0; j < n; j++) { + long int x = (long int) VECTOR(*idx)[j]; + s += VECTOR(*oldv)[x]; + } + VECTOR(*newv)[i] = s; + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cn_prod(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + const igraph_vector_t *oldv = oldrec->value; + igraph_vector_t *newv = igraph_Calloc(1, igraph_vector_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_VECTOR_INIT_FINALLY(newv, newlen); + + for (i = 0; i < newlen; i++) { + igraph_real_t s = 1.0; + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int j, n = igraph_vector_size(idx); + for (j = 0; j < n; j++) { + long int x = (long int) VECTOR(*idx)[j]; + s *= VECTOR(*oldv)[x]; + } + VECTOR(*newv)[i] = s; + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cn_min(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + const igraph_vector_t *oldv = oldrec->value; + igraph_vector_t *newv = igraph_Calloc(1, igraph_vector_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i; + igraph_real_t nan = IGRAPH_NAN; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_VECTOR_INIT_FINALLY(newv, newlen); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int j, n = igraph_vector_size(idx); + igraph_real_t m = n > 0 ? VECTOR(*oldv)[ (long int) VECTOR(*idx)[0] ] : nan; + for (j = 1; j < n; j++) { + long int x = (long int) VECTOR(*idx)[j]; + igraph_real_t val = VECTOR(*oldv)[x]; + if (val < m) { + m = val; + } + } + VECTOR(*newv)[i] = m; + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cn_max(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + const igraph_vector_t *oldv = oldrec->value; + igraph_vector_t *newv = igraph_Calloc(1, igraph_vector_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i; + igraph_real_t nan = IGRAPH_NAN; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_VECTOR_INIT_FINALLY(newv, newlen); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int j, n = igraph_vector_size(idx); + igraph_real_t m = n > 0 ? VECTOR(*oldv)[ (long int) VECTOR(*idx)[0] ] : nan; + for (j = 1; j < n; j++) { + long int x = (long int) VECTOR(*idx)[j]; + igraph_real_t val = VECTOR(*oldv)[x]; + if (val > m) { + m = val; + } + } + VECTOR(*newv)[i] = m; + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cn_random(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_vector_t *oldv = oldrec->value; + igraph_vector_t *newv = igraph_Calloc(1, igraph_vector_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i; + igraph_real_t nan = IGRAPH_NAN; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_VECTOR_INIT_FINALLY(newv, newlen); + + RNG_BEGIN(); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int n = igraph_vector_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = nan; + } else if (n == 1) { + VECTOR(*newv)[i] = VECTOR(*oldv)[ (long int) VECTOR(*idx)[0] ]; + } else { + long int r = RNG_INTEGER(0, n - 1); + VECTOR(*newv)[i] = VECTOR(*oldv)[ (long int) VECTOR(*idx)[r] ]; + } + } + + RNG_END(); + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cn_first(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_vector_t *oldv = oldrec->value; + igraph_vector_t *newv = igraph_Calloc(1, igraph_vector_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i; + igraph_real_t nan = IGRAPH_NAN; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_VECTOR_INIT_FINALLY(newv, newlen); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int n = igraph_vector_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = nan; + } else { + VECTOR(*newv)[i] = VECTOR(*oldv)[ (long int) VECTOR(*idx)[0] ]; + } + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cn_last(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_vector_t *oldv = oldrec->value; + igraph_vector_t *newv = igraph_Calloc(1, igraph_vector_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i; + igraph_real_t nan = IGRAPH_NAN; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_VECTOR_INIT_FINALLY(newv, newlen); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int n = igraph_vector_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = nan; + } else { + VECTOR(*newv)[i] = VECTOR(*oldv)[ (long int) VECTOR(*idx)[n - 1] ]; + } + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cn_mean(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + const igraph_vector_t *oldv = oldrec->value; + igraph_vector_t *newv = igraph_Calloc(1, igraph_vector_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i; + igraph_real_t nan = IGRAPH_NAN; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_VECTOR_INIT_FINALLY(newv, newlen); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int j, n = igraph_vector_size(idx); + igraph_real_t s = n > 0 ? 0.0 : nan; + for (j = 0; j < n; j++) { + long int x = (long int) VECTOR(*idx)[j]; + s += VECTOR(*oldv)[x]; + } + if (n > 0) { + s = s / n; + } + VECTOR(*newv)[i] = s; + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cn_func(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_ptr_t *merges, + igraph_cattributes_combine_num_t *func) { + + const igraph_vector_t *oldv = oldrec->value; + long int newlen = igraph_vector_ptr_size(merges); + long int i; + igraph_vector_t *newv = igraph_Calloc(1, igraph_vector_t); + igraph_vector_t values; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_VECTOR_INIT_FINALLY(newv, newlen); + + IGRAPH_VECTOR_INIT_FINALLY(&values, 0); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int j, n = igraph_vector_size(idx); + igraph_real_t res; + IGRAPH_CHECK(igraph_vector_resize(&values, n)); + for (j = 0; j < n; j++) { + long int x = (long int) VECTOR(*idx)[j]; + VECTOR(values)[j] = VECTOR(*oldv)[x]; + } + IGRAPH_CHECK(func(&values, &res)); + VECTOR(*newv)[i] = res; + } + + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(3); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cb_random(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value; + igraph_vector_bool_t *newv = igraph_Calloc(1, igraph_vector_bool_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_vector_bool_init(newv, newlen)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newv); + + RNG_BEGIN(); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int n = igraph_vector_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = 0; + } else if (n == 1) { + VECTOR(*newv)[i] = VECTOR(*oldv)[ (long int) VECTOR(*idx)[0] ]; + } else { + long int r = RNG_INTEGER(0, n - 1); + VECTOR(*newv)[i] = VECTOR(*oldv)[ (long int) VECTOR(*idx)[r] ]; + } + } + + RNG_END(); + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cb_first(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value; + igraph_vector_bool_t *newv = igraph_Calloc(1, igraph_vector_bool_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_vector_bool_init(newv, newlen)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newv); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int n = igraph_vector_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = 0; + } else { + VECTOR(*newv)[i] = VECTOR(*oldv)[ (long int) VECTOR(*idx)[0] ]; + } + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cb_last(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value; + igraph_vector_bool_t *newv = igraph_Calloc(1, igraph_vector_bool_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_vector_bool_init(newv, newlen)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newv); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int n = igraph_vector_size(idx); + if (n == 0) { + VECTOR(*newv)[i] = 0; + } else { + VECTOR(*newv)[i] = VECTOR(*oldv)[ (long int) VECTOR(*idx)[n - 1] ]; + } + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cb_all_is_true(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value; + igraph_vector_bool_t *newv = igraph_Calloc(1, igraph_vector_bool_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i, j, n, x; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_vector_bool_init(newv, newlen)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newv); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + n = igraph_vector_size(idx); + VECTOR(*newv)[i] = 1; + for (j = 0; j < n; j++) { + x = (long int) VECTOR(*idx)[j]; + if (!VECTOR(*oldv)[x]) { + VECTOR(*newv)[i] = 0; + break; + } + } + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cb_any_is_true(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value; + igraph_vector_bool_t *newv = igraph_Calloc(1, igraph_vector_bool_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i, j, n, x; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_vector_bool_init(newv, newlen)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newv); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + n = igraph_vector_size(idx); + VECTOR(*newv)[i] = 0; + for (j = 0; j < n; j++) { + x = (long int) VECTOR(*idx)[j]; + if (VECTOR(*oldv)[x]) { + VECTOR(*newv)[i] = 1; + break; + } + } + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cb_majority(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t * newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_vector_bool_t *oldv = oldrec->value; + igraph_vector_bool_t *newv = igraph_Calloc(1, igraph_vector_bool_t); + long int newlen = igraph_vector_ptr_size(merges); + long int i, j, n, x, num_trues; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_vector_bool_init(newv, newlen)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newv); + + RNG_BEGIN(); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + + n = igraph_vector_size(idx); + + num_trues = 0; + for (j = 0; j < n; j++) { + x = (long int) VECTOR(*idx)[j]; + if (VECTOR(*oldv)[x]) { + num_trues++; + } + } + + if (n % 2 != 0) { + VECTOR(*newv)[i] = (num_trues > n / 2); + } else { + if (num_trues == n / 2) { + VECTOR(*newv)[i] = (RNG_UNIF01() < 0.5); + } else { + VECTOR(*newv)[i] = (num_trues > n / 2); + } + } + } + + RNG_END(); + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_cb_func(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_ptr_t *merges, + igraph_cattributes_combine_bool_t *func) { + + const igraph_vector_bool_t *oldv = oldrec->value; + long int newlen = igraph_vector_ptr_size(merges); + long int i; + igraph_vector_bool_t *newv = igraph_Calloc(1, igraph_vector_bool_t); + igraph_vector_bool_t values; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_vector_bool_init(newv, newlen)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newv); + + IGRAPH_CHECK(igraph_vector_bool_init(&values, 0)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newv); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int j, n = igraph_vector_size(idx); + igraph_bool_t res; + + IGRAPH_CHECK(igraph_vector_bool_resize(&values, n)); + for (j = 0; j < n; j++) { + long int x = (long int) VECTOR(*idx)[j]; + VECTOR(values)[j] = VECTOR(*oldv)[x]; + } + + IGRAPH_CHECK(func(&values, &res)); + VECTOR(*newv)[i] = res; + } + + igraph_vector_bool_destroy(&values); + IGRAPH_FINALLY_CLEAN(3); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_sn_random(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_strvector_t *oldv = oldrec->value; + long int newlen = igraph_vector_ptr_size(merges); + long int i; + igraph_strvector_t *newv = igraph_Calloc(1, igraph_strvector_t); + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_strvector_init(newv, newlen)); + IGRAPH_FINALLY(igraph_strvector_destroy, newv); + + RNG_BEGIN(); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int n = igraph_vector_size(idx); + char *tmp; + if (n == 0) { + IGRAPH_CHECK(igraph_strvector_set(newv, i, "")); + } else if (n == 1) { + igraph_strvector_get(oldv, 0, &tmp); + IGRAPH_CHECK(igraph_strvector_set(newv, i, tmp)); + } else { + long int r = RNG_INTEGER(0, n - 1); + igraph_strvector_get(oldv, r, &tmp); + IGRAPH_CHECK(igraph_strvector_set(newv, i, tmp)); + } + } + + RNG_END(); + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_sn_first(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_strvector_t *oldv = oldrec->value; + long int i, newlen = igraph_vector_ptr_size(merges); + igraph_strvector_t *newv = igraph_Calloc(1, igraph_strvector_t); + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_strvector_init(newv, newlen)); + IGRAPH_FINALLY(igraph_strvector_destroy, newv); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int n = igraph_vector_size(idx); + if (n == 0) { + IGRAPH_CHECK(igraph_strvector_set(newv, i, "")); + } else { + char *tmp; + igraph_strvector_get(oldv, (long int) VECTOR(*idx)[0], &tmp); + IGRAPH_CHECK(igraph_strvector_set(newv, i, tmp)); + } + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_sn_last(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_strvector_t *oldv = oldrec->value; + long int i, newlen = igraph_vector_ptr_size(merges); + igraph_strvector_t *newv = igraph_Calloc(1, igraph_strvector_t); + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_strvector_init(newv, newlen)); + IGRAPH_FINALLY(igraph_strvector_destroy, newv); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int n = igraph_vector_size(idx); + if (n == 0) { + IGRAPH_CHECK(igraph_strvector_set(newv, i, "")); + } else { + char *tmp; + igraph_strvector_get(oldv, (long int) VECTOR(*idx)[n - 1], &tmp); + IGRAPH_CHECK(igraph_strvector_set(newv, i, tmp)); + } + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_sn_concat(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_ptr_t *merges) { + + const igraph_strvector_t *oldv = oldrec->value; + long int i, newlen = igraph_vector_ptr_size(merges); + igraph_strvector_t *newv = igraph_Calloc(1, igraph_strvector_t); + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_strvector_init(newv, newlen)); + IGRAPH_FINALLY(igraph_strvector_destroy, newv); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int j, n = igraph_vector_size(idx); + size_t len = 0; + char *tmp, *tmp2; + for (j = 0; j < n; j++) { + igraph_strvector_get(oldv, j, &tmp); + len += strlen(tmp); + } + tmp2 = igraph_Calloc(len + 1, char); + if (!tmp2) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmp2); + len = 0; + for (j = 0; j < n; j++) { + igraph_strvector_get(oldv, j, &tmp); + strcpy(tmp2 + len, tmp); + len += strlen(tmp); + } + + IGRAPH_CHECK(igraph_strvector_set(newv, i, tmp2)); + igraph_Free(tmp2); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_FINALLY_CLEAN(2); + newrec->value = newv; + + return 0; +} + +int igraph_i_cattributes_sn_func(const igraph_attribute_record_t *oldrec, + igraph_attribute_record_t *newrec, + const igraph_vector_ptr_t *merges, + igraph_cattributes_combine_str_t *func) { + + const igraph_strvector_t *oldv = oldrec->value; + long int newlen = igraph_vector_ptr_size(merges); + long int i; + igraph_strvector_t *newv = igraph_Calloc(1, igraph_strvector_t); + igraph_strvector_t values; + + if (!newv) { + IGRAPH_ERROR("Cannot combine attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_strvector_init(newv, newlen)); + IGRAPH_FINALLY(igraph_strvector_destroy, newv); + + IGRAPH_CHECK(igraph_strvector_init(newv, 0)); + IGRAPH_FINALLY(igraph_strvector_destroy, &values); + + for (i = 0; i < newlen; i++) { + igraph_vector_t *idx = VECTOR(*merges)[i]; + long int j, n = igraph_vector_size(idx); + char *res; + IGRAPH_CHECK(igraph_strvector_resize(&values, n)); + for (j = 0; j < n; j++) { + long int x = (long int) VECTOR(*idx)[j]; + char *elem; + igraph_strvector_get(oldv, x, &elem); + IGRAPH_CHECK(igraph_strvector_set(newv, j, elem)); + } + IGRAPH_CHECK(func(&values, &res)); + IGRAPH_FINALLY(igraph_free, res); + IGRAPH_CHECK(igraph_strvector_set(newv, i, res)); + IGRAPH_FINALLY_CLEAN(1); + igraph_Free(res); + } + + igraph_strvector_destroy(&values); + IGRAPH_FINALLY_CLEAN(3); + newrec->value = newv; + + return 0; +} + + +int igraph_i_cattribute_combine_vertices(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_ptr_t *merges, + const igraph_attribute_combination_t *comb) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_i_cattributes_t *toattr = newgraph->attr; + igraph_vector_ptr_t *val = &attr->val; + igraph_vector_ptr_t *new_val = &toattr->val; + long int valno = igraph_vector_ptr_size(val); + long int i, j, keepno = 0; + int *TODO; + igraph_function_pointer_t *funcs; + + TODO = igraph_Calloc(valno, int); + if (!TODO) { + IGRAPH_ERROR("Cannot combine vertex attributes", + IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, TODO); + funcs = igraph_Calloc(valno, igraph_function_pointer_t); + if (!funcs) { + IGRAPH_ERROR("Cannot combine vertex attributes", + IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, funcs); + + for (i = 0; i < valno; i++) { + igraph_attribute_record_t *oldrec = VECTOR(*val)[i]; + const char *name = oldrec->name; + igraph_attribute_combination_type_t todo; + igraph_function_pointer_t voidfunc; + igraph_attribute_combination_query(comb, name, &todo, &voidfunc); + TODO[i] = todo; + funcs[i] = voidfunc; + if (todo != IGRAPH_ATTRIBUTE_COMBINE_IGNORE) { + keepno++; + } + } + + IGRAPH_CHECK(igraph_vector_ptr_resize(new_val, keepno)); + IGRAPH_FINALLY(igraph_i_cattribute_permute_free, new_val); + + for (i = 0, j = 0; i < valno; i++) { + igraph_attribute_record_t *newrec, *oldrec = VECTOR(*val)[i]; + const char *name = oldrec->name; + igraph_attribute_combination_type_t todo = + (igraph_attribute_combination_type_t) (TODO[i]); + igraph_attribute_type_t type = oldrec->type; + igraph_cattributes_combine_num_t *numfunc = + (igraph_cattributes_combine_num_t*) funcs[i]; + igraph_cattributes_combine_str_t *strfunc = + (igraph_cattributes_combine_str_t*) funcs[i]; + igraph_cattributes_combine_bool_t *boolfunc = + (igraph_cattributes_combine_bool_t*) funcs[i]; + + if (todo == IGRAPH_ATTRIBUTE_COMBINE_DEFAULT || + todo == IGRAPH_ATTRIBUTE_COMBINE_IGNORE) { + continue; + } + + newrec = igraph_Calloc(1, igraph_attribute_record_t); + if (!newrec) { + IGRAPH_ERROR("Cannot combine vertex attributes", + IGRAPH_ENOMEM); + } + newrec->name = strdup(name); + newrec->type = type; + VECTOR(*new_val)[j] = newrec; + + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + switch (todo) { + case IGRAPH_ATTRIBUTE_COMBINE_FUNCTION: + IGRAPH_CHECK(igraph_i_cattributes_cn_func(oldrec, newrec, merges, + numfunc)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_SUM: + IGRAPH_CHECK(igraph_i_cattributes_cn_sum(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_PROD: + IGRAPH_CHECK(igraph_i_cattributes_cn_prod(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MIN: + IGRAPH_CHECK(igraph_i_cattributes_cn_min(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MAX: + IGRAPH_CHECK(igraph_i_cattributes_cn_max(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_RANDOM: + IGRAPH_CHECK(igraph_i_cattributes_cn_random(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_FIRST: + IGRAPH_CHECK(igraph_i_cattributes_cn_first(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_LAST: + IGRAPH_CHECK(igraph_i_cattributes_cn_last(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEAN: + IGRAPH_CHECK(igraph_i_cattributes_cn_mean(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEDIAN: + IGRAPH_ERROR("Median calculation not implemented", + IGRAPH_UNIMPLEMENTED); + break; + case IGRAPH_ATTRIBUTE_COMBINE_CONCAT: + IGRAPH_ERROR("Cannot concatenate numeric attributes", + IGRAPH_EATTRCOMBINE); + break; + default: + IGRAPH_ERROR("Unknown attribute_combination", + IGRAPH_UNIMPLEMENTED); + break; + } + } else if (type == IGRAPH_ATTRIBUTE_BOOLEAN) { + switch (todo) { + case IGRAPH_ATTRIBUTE_COMBINE_FUNCTION: + IGRAPH_CHECK(igraph_i_cattributes_cb_func(oldrec, newrec, merges, + boolfunc)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_SUM: + case IGRAPH_ATTRIBUTE_COMBINE_MAX: + IGRAPH_CHECK(igraph_i_cattributes_cb_any_is_true(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_PROD: + case IGRAPH_ATTRIBUTE_COMBINE_MIN: + IGRAPH_CHECK(igraph_i_cattributes_cb_all_is_true(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEAN: + case IGRAPH_ATTRIBUTE_COMBINE_MEDIAN: + IGRAPH_CHECK(igraph_i_cattributes_cb_majority(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_RANDOM: + IGRAPH_CHECK(igraph_i_cattributes_cb_random(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_FIRST: + IGRAPH_CHECK(igraph_i_cattributes_cb_first(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_LAST: + IGRAPH_CHECK(igraph_i_cattributes_cb_last(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_CONCAT: + IGRAPH_ERROR("Cannot calculate concatenation of Booleans", + IGRAPH_EATTRCOMBINE); + break; + default: + IGRAPH_ERROR("Unknown attribute_combination", + IGRAPH_UNIMPLEMENTED); + break; + } + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + switch (todo) { + case IGRAPH_ATTRIBUTE_COMBINE_FUNCTION: + IGRAPH_CHECK(igraph_i_cattributes_sn_func(oldrec, newrec, merges, + strfunc)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_SUM: + IGRAPH_ERROR("Cannot sum strings", IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_PROD: + IGRAPH_ERROR("Cannot multiply strings", IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MIN: + IGRAPH_ERROR("Cannot find minimum of strings", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MAX: + IGRAPH_ERROR("Cannot find maximum of strings", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEAN: + IGRAPH_ERROR("Cannot calculate mean of strings", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEDIAN: + IGRAPH_ERROR("Cannot calculate median of strings", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_RANDOM: + IGRAPH_CHECK(igraph_i_cattributes_sn_random(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_FIRST: + IGRAPH_CHECK(igraph_i_cattributes_sn_first(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_LAST: + IGRAPH_CHECK(igraph_i_cattributes_sn_last(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_CONCAT: + IGRAPH_CHECK(igraph_i_cattributes_sn_concat(oldrec, newrec, merges)); + break; + default: + IGRAPH_ERROR("Unknown attribute_combination", + IGRAPH_UNIMPLEMENTED); + break; + } + } else { + IGRAPH_ERROR("Unknown attribute type, this should not happen", + IGRAPH_UNIMPLEMENTED); + } + + j++; + } + + igraph_free(funcs); + igraph_free(TODO); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/* void igraph_i_cattribute_delete_vertices(igraph_t *graph, */ +/* const igraph_vector_t *eidx, */ +/* const igraph_vector_t *vidx) { */ + +/* igraph_i_cattributes_t *attr=graph->attr; */ +/* igraph_vector_ptr_t *val=&attr->val; */ +/* igraph_vector_ptr_t *eal=&attr->eal; */ +/* long int valno=igraph_vector_ptr_size(val); */ +/* long int ealno=igraph_vector_ptr_size(eal); */ +/* long int i; */ +/* long int origlen, newlen; */ + +/* /\* Vertices *\/ */ +/* origlen=igraph_vector_size(vidx); */ +/* newlen=0; */ +/* for (i=0; i0) { */ +/* newlen++; */ +/* } */ +/* } */ +/* for (i=0; itype; */ +/* igraph_vector_t *num=(igraph_vector_t*)oldrec->value; */ +/* igraph_strvector_t *str=(igraph_strvector_t*)oldrec->value; */ +/* switch (type) { */ +/* case IGRAPH_ATTRIBUTE_NUMERIC: */ +/* igraph_vector_permdelete(num, vidx, origlen-newlen); */ +/* break; */ +/* case IGRAPH_ATTRIBUTE_STRING: */ +/* igraph_strvector_permdelete(str, vidx, origlen-newlen); */ +/* break; */ +/* default: */ +/* IGRAPH_WARNING("Unknown vertex attribute ignored"); */ +/* } */ +/* } */ + +/* /\* Edges *\/ */ +/* origlen=igraph_vector_size(eidx); */ +/* newlen=0; */ +/* for (i=0; i0) { */ +/* newlen++; */ +/* } */ +/* } */ +/* for (i=0; itype; */ +/* igraph_vector_t *num=(igraph_vector_t*)oldrec->value; */ +/* igraph_strvector_t *str=(igraph_strvector_t*)oldrec->value; */ +/* switch (type) { */ +/* case IGRAPH_ATTRIBUTE_NUMERIC: */ +/* igraph_vector_permdelete(num, eidx, origlen-newlen); */ +/* break; */ +/* case IGRAPH_ATTRIBUTE_STRING: */ +/* igraph_strvector_permdelete(str, eidx, origlen-newlen); */ +/* break; */ +/* default: */ +/* IGRAPH_WARNING("Unknown edge attribute ignored"); */ +/* } */ +/* } */ +/* } */ + +int igraph_i_cattribute_add_edges(igraph_t *graph, const igraph_vector_t *edges, + igraph_vector_ptr_t *nattr) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int ealno = igraph_vector_ptr_size(eal); + long int ne = igraph_vector_size(edges) / 2; + long int origlen = igraph_ecount(graph) - ne; + long int nattrno = nattr == 0 ? 0 : igraph_vector_ptr_size(nattr); + igraph_vector_t news; + long int newattrs, i; + + /* First add the new attributes if any */ + newattrs = 0; + IGRAPH_VECTOR_INIT_FINALLY(&news, 0); + for (i = 0; i < nattrno; i++) { + igraph_attribute_record_t *nattr_entry = VECTOR(*nattr)[i]; + const char *nname = nattr_entry->name; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(eal, nname, &j); + if (!l) { + newattrs++; + IGRAPH_CHECK(igraph_vector_push_back(&news, i)); + } else { + /* check types */ + if (nattr_entry->type != + ((igraph_attribute_record_t*)VECTOR(*eal)[j])->type) { + IGRAPH_ERROR("You cannot mix attribute types", IGRAPH_EINVAL); + } + } + } + + /* Add NA/empty string vectors for the existing vertices */ + if (newattrs != 0) { + for (i = 0; i < newattrs; i++) { + igraph_attribute_record_t *tmp = VECTOR(*nattr)[(long int)VECTOR(news)[i]]; + igraph_attribute_record_t *newrec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_attribute_type_t type = tmp->type; + if (!newrec) { + IGRAPH_ERROR("Cannot add attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newrec); + newrec->type = type; + newrec->name = strdup(tmp->name); + if (!newrec->name) { + IGRAPH_ERROR("Cannot add attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)newrec->name); + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *newnum = igraph_Calloc(1, igraph_vector_t); + if (!newnum) { + IGRAPH_ERROR("Cannot add attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newnum); + IGRAPH_VECTOR_INIT_FINALLY(newnum, origlen); + newrec->value = newnum; + igraph_vector_fill(newnum, IGRAPH_NAN); + } else if (type == IGRAPH_ATTRIBUTE_BOOLEAN) { + igraph_vector_bool_t *newbool = igraph_Calloc(1, igraph_vector_bool_t); + if (!newbool) { + IGRAPH_ERROR("Cannot add attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newbool); + IGRAPH_CHECK(igraph_vector_bool_init(newbool, origlen)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newbool); + newrec->value = newbool; + igraph_vector_bool_fill(newbool, 0); + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *newstr = igraph_Calloc(1, igraph_strvector_t); + if (!newstr) { + IGRAPH_ERROR("Cannot add attributes", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newstr); + IGRAPH_STRVECTOR_INIT_FINALLY(newstr, origlen); + newrec->value = newstr; + } + IGRAPH_CHECK(igraph_vector_ptr_push_back(eal, newrec)); + IGRAPH_FINALLY_CLEAN(4); + } + ealno = igraph_vector_ptr_size(eal); + } + + /* Now append the new values */ + for (i = 0; i < ealno; i++) { + igraph_attribute_record_t *oldrec = VECTOR(*eal)[i]; + igraph_attribute_record_t *newrec = 0; + const char *name = oldrec->name; + long int j; + igraph_bool_t l = 0; + if (nattr) { + l = igraph_i_cattribute_find(nattr, name, &j); + } + if (l) { + /* This attribute is present in nattr */ + igraph_vector_t *oldnum, *newnum; + igraph_strvector_t *oldstr, *newstr; + igraph_vector_bool_t *oldbool, *newbool; + newrec = VECTOR(*nattr)[j]; + oldnum = (igraph_vector_t*)oldrec->value; + newnum = (igraph_vector_t*)newrec->value; + oldstr = (igraph_strvector_t*)oldrec->value; + newstr = (igraph_strvector_t*)newrec->value; + oldbool = (igraph_vector_bool_t*)oldrec->value; + newbool = (igraph_vector_bool_t*)newrec->value; + if (oldrec->type != newrec->type) { + IGRAPH_ERROR("Attribute types do not match", IGRAPH_EINVAL); + } + switch (oldrec->type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + if (ne != igraph_vector_size(newnum)) { + IGRAPH_ERROR("Invalid numeric attribute length", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_vector_append(oldnum, newnum)); + break; + case IGRAPH_ATTRIBUTE_STRING: + if (ne != igraph_strvector_size(newstr)) { + IGRAPH_ERROR("Invalid string attribute length", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_strvector_append(oldstr, newstr)); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + if (ne != igraph_vector_bool_size(newbool)) { + IGRAPH_ERROR("Invalid Boolean attribute length", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_vector_bool_append(oldbool, newbool)); + break; + default: + IGRAPH_WARNING("Invalid attribute type"); + break; + } + } else { + /* No such attribute, append NA's */ + igraph_vector_t *oldnum = (igraph_vector_t *)oldrec->value; + igraph_strvector_t *oldstr = (igraph_strvector_t*)oldrec->value; + igraph_vector_bool_t *oldbool = (igraph_vector_bool_t *)oldrec->value; + switch (oldrec->type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + IGRAPH_CHECK(igraph_vector_resize(oldnum, origlen + ne)); + for (j = origlen; j < origlen + ne; j++) { + VECTOR(*oldnum)[j] = IGRAPH_NAN; + } + break; + case IGRAPH_ATTRIBUTE_STRING: + IGRAPH_CHECK(igraph_strvector_resize(oldstr, origlen + ne)); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + IGRAPH_CHECK(igraph_vector_bool_resize(oldbool, origlen + ne)); + for (j = origlen; j < origlen + ne; j++) { + VECTOR(*oldbool)[j] = 0; + } + break; + default: + IGRAPH_WARNING("Invalid attribute type"); + break; + } + } + } + + igraph_vector_destroy(&news); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/* void igraph_i_cattribute_delete_edges(igraph_t *graph, const igraph_vector_t *idx) { */ + +/* igraph_i_cattributes_t *attr=graph->attr; */ +/* igraph_vector_ptr_t *eal=&attr->eal; */ +/* long int ealno=igraph_vector_ptr_size(eal); */ +/* long int i; */ +/* long int origlen=igraph_vector_size(idx), newlen; */ + +/* newlen=0; */ +/* for (i=0; i0) { */ +/* newlen++; */ +/* } */ +/* } */ +/* for (i=0; itype; */ +/* igraph_vector_t *num=(igraph_vector_t*)oldrec->value; */ +/* igraph_strvector_t *str=(igraph_strvector_t*)oldrec->value; */ +/* switch (type) { */ +/* case IGRAPH_ATTRIBUTE_NUMERIC: */ +/* igraph_vector_permdelete(num, idx, origlen-newlen); */ +/* break; */ +/* case IGRAPH_ATTRIBUTE_STRING: */ +/* igraph_strvector_permdelete(str, idx, origlen-newlen); */ +/* break; */ +/* default: */ +/* IGRAPH_WARNING("Unknown edge attribute ignored"); */ +/* } */ +/* } */ + +/* } */ + +int igraph_i_cattribute_permute_edges(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_t *idx) { + + if (graph == newgraph) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int ealno = igraph_vector_ptr_size(eal); + long int i; + + for (i = 0; i < ealno; i++) { + igraph_attribute_record_t *oldrec = VECTOR(*eal)[i]; + igraph_attribute_type_t type = oldrec->type; + igraph_vector_t *num, *newnum; + igraph_strvector_t *str, *newstr; + igraph_vector_bool_t *oldbool, *newbool; + switch (type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + num = (igraph_vector_t*) oldrec->value; + newnum = igraph_Calloc(1, igraph_vector_t); + if (!newnum) { + IGRAPH_ERROR("Cannot permute edge attributes", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(newnum, 0); + igraph_vector_index(num, newnum, idx); + oldrec->value = newnum; + igraph_vector_destroy(num); + igraph_Free(num); + IGRAPH_FINALLY_CLEAN(1); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + oldbool = (igraph_vector_bool_t*) oldrec->value; + newbool = igraph_Calloc(1, igraph_vector_bool_t); + if (!newbool) { + IGRAPH_ERROR("Cannot permute edge attributes", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_bool_init(newbool, 0)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newbool); + igraph_vector_bool_index(oldbool, newbool, idx); + oldrec->value = newbool; + igraph_vector_bool_destroy(oldbool); + igraph_Free(oldbool); + IGRAPH_FINALLY_CLEAN(1); + break; + case IGRAPH_ATTRIBUTE_STRING: + str = (igraph_strvector_t*)oldrec->value; + newstr = igraph_Calloc(1, igraph_strvector_t); + if (!newstr) { + IGRAPH_ERROR("Cannot permute edge attributes", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_strvector_init(newstr, 0)); + IGRAPH_FINALLY(igraph_strvector_destroy, newstr); + igraph_strvector_index(str, newstr, idx); + oldrec->value = newstr; + igraph_strvector_destroy(str); + igraph_Free(str); + IGRAPH_FINALLY_CLEAN(1); + break; + default: + IGRAPH_WARNING("Unknown edge attribute ignored"); + } + } + + } else { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int ealno = igraph_vector_ptr_size(eal); + long int i; + + /* New edge attributes */ + igraph_i_cattributes_t *new_attr = newgraph->attr; + igraph_vector_ptr_t *new_eal = &new_attr->eal; + IGRAPH_CHECK(igraph_vector_ptr_resize(new_eal, ealno)); + + IGRAPH_FINALLY(igraph_i_cattribute_permute_free, new_eal); + + for (i = 0; i < ealno; i++) { + igraph_attribute_record_t *oldrec = VECTOR(*eal)[i]; + igraph_attribute_type_t type = oldrec->type; + igraph_vector_t *num, *newnum; + igraph_strvector_t *str, *newstr; + igraph_vector_bool_t *oldbool, *newbool; + + /* The record itself */ + igraph_attribute_record_t *new_rec = + igraph_Calloc(1, igraph_attribute_record_t); + if (!new_rec) { + IGRAPH_ERROR("Cannot create edge attributes", IGRAPH_ENOMEM); + } + new_rec->name = strdup(oldrec->name); + new_rec->type = oldrec->type; + VECTOR(*new_eal)[i] = new_rec; + + switch (type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + num = (igraph_vector_t*) oldrec->value; + newnum = igraph_Calloc(1, igraph_vector_t); + if (!newnum) { + IGRAPH_ERROR("Cannot permute edge attributes", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(newnum, 0); + igraph_vector_index(num, newnum, idx); + new_rec->value = newnum; + IGRAPH_FINALLY_CLEAN(1); + break; + case IGRAPH_ATTRIBUTE_STRING: + str = (igraph_strvector_t*)oldrec->value; + newstr = igraph_Calloc(1, igraph_strvector_t); + if (!newstr) { + IGRAPH_ERROR("Cannot permute edge attributes", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_strvector_init(newstr, 0)); + IGRAPH_FINALLY(igraph_strvector_destroy, newstr); + igraph_strvector_index(str, newstr, idx); + new_rec->value = newstr; + IGRAPH_FINALLY_CLEAN(1); + break; + case IGRAPH_ATTRIBUTE_BOOLEAN: + oldbool = (igraph_vector_bool_t*) oldrec->value; + newbool = igraph_Calloc(1, igraph_vector_bool_t); + if (!newbool) { + IGRAPH_ERROR("Cannot permute edge attributes", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_bool_init(newbool, 0)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, newbool); + igraph_vector_bool_index(oldbool, newbool, idx); + new_rec->value = newbool; + IGRAPH_FINALLY_CLEAN(1); + break; + default: + IGRAPH_WARNING("Unknown edge attribute ignored"); + } + } + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +int igraph_i_cattribute_combine_edges(const igraph_t *graph, + igraph_t *newgraph, + const igraph_vector_ptr_t *merges, + const igraph_attribute_combination_t *comb) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_i_cattributes_t *toattr = newgraph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + igraph_vector_ptr_t *new_eal = &toattr->eal; + long int ealno = igraph_vector_ptr_size(eal); + long int i, j, keepno = 0; + int *TODO; + igraph_function_pointer_t *funcs; + + TODO = igraph_Calloc(ealno, int); + if (!TODO) { + IGRAPH_ERROR("Cannot combine edge attributes", + IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, TODO); + funcs = igraph_Calloc(ealno, igraph_function_pointer_t); + if (!funcs) { + IGRAPH_ERROR("Cannot combine edge attributes", + IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, funcs); + + for (i = 0; i < ealno; i++) { + igraph_attribute_record_t *oldrec = VECTOR(*eal)[i]; + const char *name = oldrec->name; + igraph_attribute_combination_type_t todo; + igraph_function_pointer_t voidfunc; + igraph_attribute_combination_query(comb, name, &todo, &voidfunc); + TODO[i] = todo; + funcs[i] = voidfunc; + if (todo != IGRAPH_ATTRIBUTE_COMBINE_IGNORE) { + keepno++; + } + } + + IGRAPH_CHECK(igraph_vector_ptr_resize(new_eal, keepno)); + IGRAPH_FINALLY(igraph_i_cattribute_permute_free, new_eal); + + for (i = 0, j = 0; i < ealno; i++) { + igraph_attribute_record_t *newrec, *oldrec = VECTOR(*eal)[i]; + const char *name = oldrec->name; + igraph_attribute_combination_type_t todo = + (igraph_attribute_combination_type_t) (TODO[i]); + igraph_attribute_type_t type = oldrec->type; + igraph_cattributes_combine_num_t *numfunc = + (igraph_cattributes_combine_num_t*) funcs[i]; + igraph_cattributes_combine_str_t *strfunc = + (igraph_cattributes_combine_str_t*) funcs[i]; + igraph_cattributes_combine_bool_t *boolfunc = + (igraph_cattributes_combine_bool_t*) funcs[i]; + + if (todo == IGRAPH_ATTRIBUTE_COMBINE_DEFAULT || + todo == IGRAPH_ATTRIBUTE_COMBINE_IGNORE) { + continue; + } + + newrec = igraph_Calloc(1, igraph_attribute_record_t); + if (!newrec) { + IGRAPH_ERROR("Cannot combine edge attributes", + IGRAPH_ENOMEM); + } + newrec->name = strdup(name); + newrec->type = type; + VECTOR(*new_eal)[j] = newrec; + + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + switch (todo) { + case IGRAPH_ATTRIBUTE_COMBINE_FUNCTION: + IGRAPH_CHECK(igraph_i_cattributes_cn_func(oldrec, newrec, merges, + numfunc)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_SUM: + IGRAPH_CHECK(igraph_i_cattributes_cn_sum(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_PROD: + IGRAPH_CHECK(igraph_i_cattributes_cn_prod(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MIN: + IGRAPH_CHECK(igraph_i_cattributes_cn_min(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MAX: + IGRAPH_CHECK(igraph_i_cattributes_cn_max(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_RANDOM: + IGRAPH_CHECK(igraph_i_cattributes_cn_random(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_FIRST: + IGRAPH_CHECK(igraph_i_cattributes_cn_first(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_LAST: + IGRAPH_CHECK(igraph_i_cattributes_cn_last(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEAN: + IGRAPH_CHECK(igraph_i_cattributes_cn_mean(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEDIAN: + IGRAPH_ERROR("Median calculation not implemented", + IGRAPH_UNIMPLEMENTED); + break; + case IGRAPH_ATTRIBUTE_COMBINE_CONCAT: + IGRAPH_ERROR("Cannot concatenate numeric attributes", + IGRAPH_EATTRCOMBINE); + break; + default: + IGRAPH_ERROR("Unknown attribute_combination", + IGRAPH_UNIMPLEMENTED); + break; + } + } else if (type == IGRAPH_ATTRIBUTE_BOOLEAN) { + switch (todo) { + case IGRAPH_ATTRIBUTE_COMBINE_FUNCTION: + IGRAPH_CHECK(igraph_i_cattributes_cb_func(oldrec, newrec, merges, + boolfunc)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_SUM: + case IGRAPH_ATTRIBUTE_COMBINE_MAX: + IGRAPH_CHECK(igraph_i_cattributes_cb_any_is_true(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_PROD: + case IGRAPH_ATTRIBUTE_COMBINE_MIN: + IGRAPH_CHECK(igraph_i_cattributes_cb_all_is_true(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEAN: + case IGRAPH_ATTRIBUTE_COMBINE_MEDIAN: + IGRAPH_CHECK(igraph_i_cattributes_cb_majority(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_RANDOM: + IGRAPH_CHECK(igraph_i_cattributes_cb_random(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_FIRST: + IGRAPH_CHECK(igraph_i_cattributes_cb_first(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_LAST: + IGRAPH_CHECK(igraph_i_cattributes_cb_last(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_CONCAT: + IGRAPH_ERROR("Cannot calculate concatenation of Booleans", + IGRAPH_EATTRCOMBINE); + break; + default: + IGRAPH_ERROR("Unknown attribute_combination", + IGRAPH_UNIMPLEMENTED); + break; + } + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + switch (todo) { + case IGRAPH_ATTRIBUTE_COMBINE_FUNCTION: + IGRAPH_CHECK(igraph_i_cattributes_sn_func(oldrec, newrec, merges, + strfunc)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_SUM: + IGRAPH_ERROR("Cannot sum strings", IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_PROD: + IGRAPH_ERROR("Cannot multiply strings", IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MIN: + IGRAPH_ERROR("Cannot find minimum of strings", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MAX: + IGRAPH_ERROR("Cannot find maximum of strings", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEAN: + IGRAPH_ERROR("Cannot calculate mean of strings", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_MEDIAN: + IGRAPH_ERROR("Cannot calculate median of strings", + IGRAPH_EATTRCOMBINE); + break; + case IGRAPH_ATTRIBUTE_COMBINE_RANDOM: + IGRAPH_CHECK(igraph_i_cattributes_sn_random(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_FIRST: + IGRAPH_CHECK(igraph_i_cattributes_sn_first(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_LAST: + IGRAPH_CHECK(igraph_i_cattributes_sn_last(oldrec, newrec, merges)); + break; + case IGRAPH_ATTRIBUTE_COMBINE_CONCAT: + IGRAPH_CHECK(igraph_i_cattributes_sn_concat(oldrec, newrec, merges)); + break; + default: + IGRAPH_ERROR("Unknown attribute_combination", + IGRAPH_UNIMPLEMENTED); + break; + } + } else { + IGRAPH_ERROR("Unknown attribute type, this should not happen", + IGRAPH_UNIMPLEMENTED); + } + + j++; + } + + igraph_free(funcs); + igraph_free(TODO); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +int igraph_i_cattribute_get_info(const igraph_t *graph, + igraph_strvector_t *gnames, + igraph_vector_t *gtypes, + igraph_strvector_t *vnames, + igraph_vector_t *vtypes, + igraph_strvector_t *enames, + igraph_vector_t *etypes) { + + igraph_strvector_t *names[3] = { gnames, vnames, enames }; + igraph_vector_t *types[3] = { gtypes, vtypes, etypes }; + igraph_i_cattributes_t *at = graph->attr; + igraph_vector_ptr_t *attr[3] = { &at->gal, &at->val, &at->eal }; + long int i, j; + + for (i = 0; i < 3; i++) { + igraph_strvector_t *n = names[i]; + igraph_vector_t *t = types[i]; + igraph_vector_ptr_t *al = attr[i]; + long int len = igraph_vector_ptr_size(al); + + if (n) { + IGRAPH_CHECK(igraph_strvector_resize(n, len)); + } + if (t) { + IGRAPH_CHECK(igraph_vector_resize(t, len)); + } + + for (j = 0; j < len; j++) { + igraph_attribute_record_t *rec = VECTOR(*al)[j]; + const char *name = rec->name; + igraph_attribute_type_t type = rec->type; + if (n) { + IGRAPH_CHECK(igraph_strvector_set(n, j, name)); + } + if (t) { + VECTOR(*t)[j] = type; + } + } + } + + return 0; +} + +igraph_bool_t igraph_i_cattribute_has_attr(const igraph_t *graph, + igraph_attribute_elemtype_t type, + const char *name) { + igraph_i_cattributes_t *at = graph->attr; + igraph_vector_ptr_t *attr[3] = { &at->gal, &at->val, &at->eal }; + long int attrnum; + + switch (type) { + case IGRAPH_ATTRIBUTE_GRAPH: + attrnum = 0; + break; + case IGRAPH_ATTRIBUTE_VERTEX: + attrnum = 1; + break; + case IGRAPH_ATTRIBUTE_EDGE: + attrnum = 2; + break; + default: + IGRAPH_ERROR("Unknown attribute element type", IGRAPH_EINVAL); + break; + } + + return igraph_i_cattribute_find(attr[attrnum], name, 0); +} + +int igraph_i_cattribute_gettype(const igraph_t *graph, + igraph_attribute_type_t *type, + igraph_attribute_elemtype_t elemtype, + const char *name) { + long int attrnum; + igraph_attribute_record_t *rec; + igraph_i_cattributes_t *at = graph->attr; + igraph_vector_ptr_t *attr[3] = { &at->gal, &at->val, &at->eal }; + igraph_vector_ptr_t *al; + long int j; + igraph_bool_t l = 0; + + switch (elemtype) { + case IGRAPH_ATTRIBUTE_GRAPH: + attrnum = 0; + break; + case IGRAPH_ATTRIBUTE_VERTEX: + attrnum = 1; + break; + case IGRAPH_ATTRIBUTE_EDGE: + attrnum = 2; + break; + default: + IGRAPH_ERROR("Unknown attribute element type", IGRAPH_EINVAL); + break; + } + + al = attr[attrnum]; + l = igraph_i_cattribute_find(al, name, &j); + if (!l) { + IGRAPH_ERROR("Unknown attribute", IGRAPH_EINVAL); + } + rec = VECTOR(*al)[j]; + *type = rec->type; + + return 0; +} + +int igraph_i_cattribute_get_numeric_graph_attr(const igraph_t *graph, + const char *name, + igraph_vector_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *gal = &attr->gal; + long int j; + igraph_attribute_record_t *rec; + igraph_vector_t *num; + igraph_bool_t l = igraph_i_cattribute_find(gal, name, &j); + + if (!l) { + IGRAPH_ERROR("Unknown attribute", IGRAPH_EINVAL); + } + + rec = VECTOR(*gal)[j]; + num = (igraph_vector_t*)rec->value; + IGRAPH_CHECK(igraph_vector_resize(value, 1)); + VECTOR(*value)[0] = VECTOR(*num)[0]; + + return 0; +} + +int igraph_i_cattribute_get_bool_graph_attr(const igraph_t *graph, + const char *name, + igraph_vector_bool_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *gal = &attr->gal; + long int j; + igraph_attribute_record_t *rec; + igraph_vector_bool_t *log; + igraph_bool_t l = igraph_i_cattribute_find(gal, name, &j); + + if (!l) { + IGRAPH_ERROR("Unknown attribute", IGRAPH_EINVAL); + } + + rec = VECTOR(*gal)[j]; + log = (igraph_vector_bool_t*)rec->value; + IGRAPH_CHECK(igraph_vector_bool_resize(value, 1)); + VECTOR(*value)[0] = VECTOR(*log)[0]; + + return 0; +} + +int igraph_i_cattribute_get_string_graph_attr(const igraph_t *graph, + const char *name, + igraph_strvector_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *gal = &attr->gal; + long int j; + igraph_attribute_record_t *rec; + igraph_strvector_t *str; + igraph_bool_t l = igraph_i_cattribute_find(gal, name, &j); + + if (!l) { + IGRAPH_ERROR("Unknown attribute", IGRAPH_EINVAL); + } + + rec = VECTOR(*gal)[j]; + str = (igraph_strvector_t*)rec->value; + IGRAPH_CHECK(igraph_strvector_resize(value, 1)); + IGRAPH_CHECK(igraph_strvector_set(value, 0, STR(*str, 0))); + + return 0; +} + +int igraph_i_cattribute_get_numeric_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_vector_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_attribute_record_t *rec; + igraph_vector_t *num; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + if (!l) { + IGRAPH_ERROR("Unknown attribute", IGRAPH_EINVAL); + } + + rec = VECTOR(*val)[j]; + num = (igraph_vector_t*)rec->value; + if (igraph_vs_is_all(&vs)) { + igraph_vector_clear(value); + IGRAPH_CHECK(igraph_vector_append(value, num)); + } else { + igraph_vit_t it; + long int i = 0; + IGRAPH_CHECK(igraph_vit_create(graph, vs, &it)); + IGRAPH_FINALLY(igraph_vit_destroy, &it); + IGRAPH_CHECK(igraph_vector_resize(value, IGRAPH_VIT_SIZE(it))); + for (; !IGRAPH_VIT_END(it); IGRAPH_VIT_NEXT(it), i++) { + long int v = IGRAPH_VIT_GET(it); + VECTOR(*value)[i] = VECTOR(*num)[v]; + } + igraph_vit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +int igraph_i_cattribute_get_bool_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_vector_bool_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + igraph_vit_t it; + long int i, j, v; + igraph_attribute_record_t *rec; + igraph_vector_bool_t *log; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + if (!l) { + IGRAPH_ERROR("Unknown attribute", IGRAPH_EINVAL); + } + + rec = VECTOR(*val)[j]; + log = (igraph_vector_bool_t*)rec->value; + if (igraph_vs_is_all(&vs)) { + igraph_vector_bool_clear(value); + IGRAPH_CHECK(igraph_vector_bool_append(value, log)); + } else { + IGRAPH_CHECK(igraph_vit_create(graph, vs, &it)); + IGRAPH_FINALLY(igraph_vit_destroy, &it); + IGRAPH_CHECK(igraph_vector_bool_resize(value, IGRAPH_VIT_SIZE(it))); + for (i = 0; !IGRAPH_VIT_END(it); IGRAPH_VIT_NEXT(it), i++) { + v = IGRAPH_VIT_GET(it); + VECTOR(*value)[i] = VECTOR(*log)[v]; + } + igraph_vit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +int igraph_i_cattribute_get_string_vertex_attr(const igraph_t *graph, + const char *name, + igraph_vs_t vs, + igraph_strvector_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_attribute_record_t *rec; + igraph_strvector_t *str; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + if (!l) { + IGRAPH_ERROR("Unknown attribute", IGRAPH_EINVAL); + } + + rec = VECTOR(*val)[j]; + str = (igraph_strvector_t*)rec->value; + if (igraph_vs_is_all(&vs)) { + igraph_strvector_resize(value, 0); + IGRAPH_CHECK(igraph_strvector_append(value, str)); + } else { + igraph_vit_t it; + long int i = 0; + IGRAPH_CHECK(igraph_vit_create(graph, vs, &it)); + IGRAPH_FINALLY(igraph_vit_destroy, &it); + IGRAPH_CHECK(igraph_strvector_resize(value, IGRAPH_VIT_SIZE(it))); + for (; !IGRAPH_VIT_END(it); IGRAPH_VIT_NEXT(it), i++) { + long int v = IGRAPH_VIT_GET(it); + char *s; + igraph_strvector_get(str, v, &s); + IGRAPH_CHECK(igraph_strvector_set(value, i, s)); + } + igraph_vit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +int igraph_i_cattribute_get_numeric_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_vector_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_attribute_record_t *rec; + igraph_vector_t *num; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + if (!l) { + IGRAPH_ERROR("Unknown attribute", IGRAPH_EINVAL); + } + + rec = VECTOR(*eal)[j]; + num = (igraph_vector_t*)rec->value; + if (igraph_es_is_all(&es)) { + igraph_vector_clear(value); + IGRAPH_CHECK(igraph_vector_append(value, num)); + } else { + igraph_eit_t it; + long int i = 0; + IGRAPH_CHECK(igraph_eit_create(graph, es, &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + IGRAPH_CHECK(igraph_vector_resize(value, IGRAPH_EIT_SIZE(it))); + for (; !IGRAPH_EIT_END(it); IGRAPH_EIT_NEXT(it), i++) { + long int e = IGRAPH_EIT_GET(it); + VECTOR(*value)[i] = VECTOR(*num)[e]; + } + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +int igraph_i_cattribute_get_string_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_strvector_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_attribute_record_t *rec; + igraph_strvector_t *str; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + if (!l) { + IGRAPH_ERROR("Unknown attribute", IGRAPH_EINVAL); + } + + rec = VECTOR(*eal)[j]; + str = (igraph_strvector_t*)rec->value; + if (igraph_es_is_all(&es)) { + igraph_strvector_resize(value, 0); + IGRAPH_CHECK(igraph_strvector_append(value, str)); + } else { + igraph_eit_t it; + long int i = 0; + IGRAPH_CHECK(igraph_eit_create(graph, es, &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + IGRAPH_CHECK(igraph_strvector_resize(value, IGRAPH_EIT_SIZE(it))); + for (; !IGRAPH_EIT_END(it); IGRAPH_EIT_NEXT(it), i++) { + long int e = IGRAPH_EIT_GET(it); + char *s; + igraph_strvector_get(str, e, &s); + IGRAPH_CHECK(igraph_strvector_set(value, i, s)); + } + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +int igraph_i_cattribute_get_bool_edge_attr(const igraph_t *graph, + const char *name, + igraph_es_t es, + igraph_vector_bool_t *value) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_attribute_record_t *rec; + igraph_vector_bool_t *log; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + if (!l) { + IGRAPH_ERROR("Unknown attribute", IGRAPH_EINVAL); + } + + rec = VECTOR(*eal)[j]; + log = (igraph_vector_bool_t*)rec->value; + if (igraph_es_is_all(&es)) { + igraph_vector_bool_clear(value); + IGRAPH_CHECK(igraph_vector_bool_append(value, log)); + } else { + igraph_eit_t it; + long int i = 0; + IGRAPH_CHECK(igraph_eit_create(graph, es, &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + IGRAPH_CHECK(igraph_vector_bool_resize(value, IGRAPH_EIT_SIZE(it))); + for (; !IGRAPH_EIT_END(it); IGRAPH_EIT_NEXT(it), i++) { + long int e = IGRAPH_EIT_GET(it); + VECTOR(*value)[i] = VECTOR(*log)[e]; + } + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/* -------------------------------------- */ + +const igraph_attribute_table_t igraph_cattribute_table = { + &igraph_i_cattribute_init, &igraph_i_cattribute_destroy, + &igraph_i_cattribute_copy, &igraph_i_cattribute_add_vertices, + &igraph_i_cattribute_permute_vertices, + &igraph_i_cattribute_combine_vertices, &igraph_i_cattribute_add_edges, + &igraph_i_cattribute_permute_edges, + &igraph_i_cattribute_combine_edges, + &igraph_i_cattribute_get_info, + &igraph_i_cattribute_has_attr, &igraph_i_cattribute_gettype, + &igraph_i_cattribute_get_numeric_graph_attr, + &igraph_i_cattribute_get_string_graph_attr, + &igraph_i_cattribute_get_bool_graph_attr, + &igraph_i_cattribute_get_numeric_vertex_attr, + &igraph_i_cattribute_get_string_vertex_attr, + &igraph_i_cattribute_get_bool_vertex_attr, + &igraph_i_cattribute_get_numeric_edge_attr, + &igraph_i_cattribute_get_string_edge_attr, + &igraph_i_cattribute_get_bool_edge_attr +}; + +/* -------------------------------------- */ + +/** + * \section cattributes + * There is an experimental attribute handler that can be used + * from C code. In this section we show how this works. This attribute + * handler is by default not attached (the default is no attribute + * handler), so we first need to attach it: + * + * igraph_i_set_attribute_table(&igraph_cattribute_table); + * + * + * Now the attribute functions are available. Please note that + * the attribute handler must be attached before you call any other + * igraph functions, otherwise you might end up with graphs without + * attributes and an active attribute handler, which might cause + * unexpected program behaviour. The rule is that you attach the + * attribute handler in the beginning of your + * main() and never touch it again. (Detaching + * the attribute handler might lead to memory leaks.) + * + * It is not currently possible to have attribute handlers on a + * per-graph basis. All graphs in an application must be managed with + * the same attribute handler. (Including the default case when there + * is no attribute handler at all. + * + * The C attribute handler supports attaching real numbers and + * character strings as attributes. No vectors are allowed, ie. every + * vertex might have an attribute called name, but it is + * not possible to have a coords graph (or other) + * attribute which is a vector of numbers. + * + * \example examples/simple/cattributes.c + * \example examples/simple/cattributes2.c + * \example examples/simple/cattributes3.c + * \example examples/simple/cattributes4.c + */ + +/** + * \function igraph_cattribute_GAN + * Query a numeric graph attribute. + * + * Returns the value of the given numeric graph attribute. + * The attribute must exist, otherwise an error is triggered. + * \param graph The input graph. + * \param name The name of the attribute to query. + * \return The value of the attribute. + * + * \sa \ref GAN for a simpler interface. + * + * Time complexity: O(Ag), the number of graph attributes. + */ +igraph_real_t igraph_cattribute_GAN(const igraph_t *graph, const char *name) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *gal = &attr->gal; + long int j; + igraph_attribute_record_t *rec; + igraph_vector_t *num; + igraph_bool_t l = igraph_i_cattribute_find(gal, name, &j); + + if (!l) { + igraph_error("Unknown attribute", __FILE__, __LINE__, IGRAPH_EINVAL); + return 0; + } + + rec = VECTOR(*gal)[j]; + num = (igraph_vector_t*)rec->value; + return VECTOR(*num)[0]; +} + +/** + * \function igraph_cattribute_GAB + * Query a boolean graph attribute. + * + * Returns the value of the given numeric graph attribute. + * The attribute must exist, otherwise an error is triggered. + * \param graph The input graph. + * \param name The name of the attribute to query. + * \return The value of the attribute. + * + * \sa \ref GAB for a simpler interface. + * + * Time complexity: O(Ag), the number of graph attributes. + */ +igraph_bool_t igraph_cattribute_GAB(const igraph_t *graph, const char *name) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *gal = &attr->gal; + long int j; + igraph_attribute_record_t *rec; + igraph_vector_bool_t *log; + igraph_bool_t l = igraph_i_cattribute_find(gal, name, &j); + + if (!l) { + igraph_error("Unknown attribute", __FILE__, __LINE__, IGRAPH_EINVAL); + return 0; + } + + rec = VECTOR(*gal)[j]; + log = (igraph_vector_bool_t*)rec->value; + return VECTOR(*log)[0]; +} + +/** + * \function igraph_cattribute_GAS + * Query a string graph attribute. + * + * Returns a const pointer to the string graph attribute + * specified in \p name. + * The attribute must exist, otherwise an error is triggered. + * \param graph The input graph. + * \param name The name of the attribute to query. + * \return The value of the attribute. + * + * \sa \ref GAS for a simpler interface. + * + * Time complexity: O(Ag), the number of graph attributes. + */ +const char* igraph_cattribute_GAS(const igraph_t *graph, const char *name) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *gal = &attr->gal; + long int j; + igraph_attribute_record_t *rec; + igraph_strvector_t *str; + igraph_bool_t l = igraph_i_cattribute_find(gal, name, &j); + + if (!l) { + igraph_error("Unknown attribute", __FILE__, __LINE__, IGRAPH_EINVAL); + return 0; + } + + rec = VECTOR(*gal)[j]; + str = (igraph_strvector_t*)rec->value; + return STR(*str, 0); +} + +/** + * \function igraph_cattribute_VAN + * Query a numeric vertex attribute. + * + * The attribute must exist, otherwise an error is triggered. + * \param graph The input graph. + * \param name The name of the attribute. + * \param vid The id of the queried vertex. + * \return The value of the attribute. + * + * \sa \ref VAN macro for a simpler interface. + * + * Time complexity: O(Av), the number of vertex attributes. + */ +igraph_real_t igraph_cattribute_VAN(const igraph_t *graph, const char *name, + igraph_integer_t vid) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_attribute_record_t *rec; + igraph_vector_t *num; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + if (!l) { + igraph_error("Unknown attribute", __FILE__, __LINE__, IGRAPH_EINVAL); + return 0; + } + + rec = VECTOR(*val)[j]; + num = (igraph_vector_t*)rec->value; + return VECTOR(*num)[(long int)vid]; +} + +/** + * \function igraph_cattribute_VAB + * Query a boolean vertex attribute. + * + * The attribute must exist, otherwise an error is triggered. + * \param graph The input graph. + * \param name The name of the attribute. + * \param vid The id of the queried vertex. + * \return The value of the attribute. + * + * \sa \ref VAB macro for a simpler interface. + * + * Time complexity: O(Av), the number of vertex attributes. + */ +igraph_bool_t igraph_cattribute_VAB(const igraph_t *graph, const char *name, + igraph_integer_t vid) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_attribute_record_t *rec; + igraph_vector_bool_t *log; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + if (!l) { + igraph_error("Unknown attribute", __FILE__, __LINE__, IGRAPH_EINVAL); + return 0; + } + + rec = VECTOR(*val)[j]; + log = (igraph_vector_bool_t*)rec->value; + return VECTOR(*log)[(long int)vid]; +} + +/** + * \function igraph_cattribute_VAS + * Query a string vertex attribute. + * + * The attribute must exist, otherwise an error is triggered. + * \param graph The input graph. + * \param name The name of the attribute. + * \param vid The id of the queried vertex. + * \return The value of the attribute. + * + * \sa The macro \ref VAS for a simpler interface. + * + * Time complexity: O(Av), the number of vertex attributes. + */ +const char* igraph_cattribute_VAS(const igraph_t *graph, const char *name, + igraph_integer_t vid) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_attribute_record_t *rec; + igraph_strvector_t *str; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + if (!l) { + igraph_error("Unknown attribute", __FILE__, __LINE__, IGRAPH_EINVAL); + return 0; + } + + rec = VECTOR(*val)[j]; + str = (igraph_strvector_t*)rec->value; + return STR(*str, (long int)vid); +} + +/** + * \function igraph_cattribute_EAN + * Query a numeric edge attribute. + * + * The attribute must exist, otherwise an error is triggered. + * \param graph The input graph. + * \param name The name of the attribute. + * \param eid The id of the queried edge. + * \return The value of the attribute. + * + * \sa \ref EAN for an easier interface. + * + * Time complexity: O(Ae), the number of edge attributes. + */ +igraph_real_t igraph_cattribute_EAN(const igraph_t *graph, const char *name, + igraph_integer_t eid) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_attribute_record_t *rec; + igraph_vector_t *num; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + if (!l) { + igraph_error("Unknown attribute", __FILE__, __LINE__, IGRAPH_EINVAL); + return 0; + } + + rec = VECTOR(*eal)[j]; + num = (igraph_vector_t*)rec->value; + return VECTOR(*num)[(long int)eid]; +} + +/** + * \function igraph_cattribute_EAB + * Query a boolean edge attribute. + * + * The attribute must exist, otherwise an error is triggered. + * \param graph The input graph. + * \param name The name of the attribute. + * \param eid The id of the queried edge. + * \return The value of the attribute. + * + * \sa \ref EAB for an easier interface. + * + * Time complexity: O(Ae), the number of edge attributes. + */ +igraph_bool_t igraph_cattribute_EAB(const igraph_t *graph, const char *name, + igraph_integer_t eid) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_attribute_record_t *rec; + igraph_vector_bool_t *log; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + if (!l) { + igraph_error("Unknown attribute", __FILE__, __LINE__, IGRAPH_EINVAL); + return 0; + } + + rec = VECTOR(*eal)[j]; + log = (igraph_vector_bool_t*)rec->value; + return VECTOR(*log)[(long int)eid]; +} + +/** + * \function igraph_cattribute_EAS + * Query a string edge attribute. + * + * The attribute must exist, otherwise an error is triggered. + * \param graph The input graph. + * \param name The name of the attribute. + * \param eid The id of the queried edge. + * \return The value of the attribute. + * + * \se \ref EAS if you want to type less. + * + * Time complexity: O(Ae), the number of edge attributes. + */ +const char* igraph_cattribute_EAS(const igraph_t *graph, const char *name, + igraph_integer_t eid) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_attribute_record_t *rec; + igraph_strvector_t *str; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + if (!l) { + igraph_error("Unknown attribute", __FILE__, __LINE__, IGRAPH_EINVAL); + return 0; + } + + rec = VECTOR(*eal)[j]; + str = (igraph_strvector_t*)rec->value; + return STR(*str, (long int)eid); +} + +/** + * \function igraph_cattribute_VANV + * Query a numeric vertex attribute for many vertices + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param vids The vertices to query. + * \param result Pointer to an initialized vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(v), where v is the number of vertices in 'vids'. + */ + +int igraph_cattribute_VANV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_vector_t *result) { + + return igraph_i_cattribute_get_numeric_vertex_attr(graph, name, vids, + result); +} + +/** + * \function igraph_cattribute_VABV + * Query a boolean vertex attribute for many vertices + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param vids The vertices to query. + * \param result Pointer to an initialized boolean vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(v), where v is the number of vertices in 'vids'. + */ + +int igraph_cattribute_VABV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_vector_bool_t *result) { + + return igraph_i_cattribute_get_bool_vertex_attr(graph, name, vids, + result); +} + +/** + * \function igraph_cattribute_EANV + * Query a numeric edge attribute for many edges + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param eids The edges to query. + * \param result Pointer to an initialized vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(e), where e is the number of edges in 'eids'. + */ + +int igraph_cattribute_EANV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_vector_t *result) { + + return igraph_i_cattribute_get_numeric_edge_attr(graph, name, eids, + result); +} + +/** + * \function igraph_cattribute_EABV + * Query a boolean edge attribute for many edges + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param eids The edges to query. + * \param result Pointer to an initialized boolean vector, the result is + * stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(e), where e is the number of edges in 'eids'. + */ + +int igraph_cattribute_EABV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_vector_bool_t *result) { + + return igraph_i_cattribute_get_bool_edge_attr(graph, name, eids, + result); +} + +/** + * \function igraph_cattribute_VASV + * Query a string vertex attribute for many vertices + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param vids The vertices to query. + * \param result Pointer to an initialized string vector, the result + * is stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(v), where v is the number of vertices in 'vids'. + * (We assume that the string attributes have a bounded length.) + */ + +int igraph_cattribute_VASV(const igraph_t *graph, const char *name, + igraph_vs_t vids, igraph_strvector_t *result) { + + return igraph_i_cattribute_get_string_vertex_attr(graph, name, vids, + result); +} + +/** + * \function igraph_cattribute_EASV + * Query a string edge attribute for many edges + * + * \param graph The input graph. + * \param name The name of the attribute. + * \param vids The edges to query. + * \param result Pointer to an initialized string vector, the result + * is stored here. It will be resized, if needed. + * \return Error code. + * + * Time complexity: O(e), where e is the number of edges in + * 'eids'. (We assume that the string attributes have a bounded length.) + */ + +int igraph_cattribute_EASV(const igraph_t *graph, const char *name, + igraph_es_t eids, igraph_strvector_t *result) { + + return igraph_i_cattribute_get_string_edge_attr(graph, name, eids, + result); +} + +/** + * \function igraph_cattribute_list + * List all attributes + * + * See \ref igraph_attribute_type_t for the various attribute types. + * \param graph The input graph. + * \param gnames String vector, the names of the graph attributes. + * \param gtypes Numeric vector, the types of the graph attributes. + * \param vnames String vector, the names of the vertex attributes. + * \param vtypes Numeric vector, the types of the vertex attributes. + * \param enames String vector, the names of the edge attributes. + * \param etypes Numeric vector, the types of the edge attributes. + * \return Error code. + * + * Naturally, the string vector with the attribute names and the + * numeric vector with the attribute types are in the right order, + * i.e. the first name corresponds to the first type, etc. + * + * Time complexity: O(Ag+Av+Ae), the number of all attributes. + */ +int igraph_cattribute_list(const igraph_t *graph, + igraph_strvector_t *gnames, igraph_vector_t *gtypes, + igraph_strvector_t *vnames, igraph_vector_t *vtypes, + igraph_strvector_t *enames, igraph_vector_t *etypes) { + return igraph_i_cattribute_get_info(graph, gnames, gtypes, vnames, vtypes, + enames, etypes); +} + +/** + * \function igraph_cattribute_has_attr + * Checks whether a (graph, vertex or edge) attribute exists + * + * \param graph The graph. + * \param type The type of the attribute, \c IGRAPH_ATTRIBUTE_GRAPH, + * \c IGRAPH_ATTRIBUTE_VERTEX or \c IGRAPH_ATTRIBUTE_EDGE. + * \param name Character constant, the name of the attribute. + * \return Logical value, TRUE if the attribute exists, FALSE otherwise. + * + * Time complexity: O(A), the number of (graph, vertex or edge) + * attributes, assuming attribute names are not too long. + */ +igraph_bool_t igraph_cattribute_has_attr(const igraph_t *graph, + igraph_attribute_elemtype_t type, + const char *name) { + return igraph_i_cattribute_has_attr(graph, type, name); +} + +/** + * \function igraph_cattribute_GAN_set + * Set a numeric graph attribute + * + * \param graph The graph. + * \param name Name of the graph attribute. If there is no such + * attribute yet, then it will be added. + * \param value The (new) value of the graph attribute. + * \return Error code. + * + * \se \ref SETGAN if you want to type less. + * + * Time complexity: O(1). + */ +int igraph_cattribute_GAN_set(igraph_t *graph, const char *name, + igraph_real_t value) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *gal = &attr->gal; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(gal, name, &j); + + if (l) { + igraph_attribute_record_t *rec = VECTOR(*gal)[j]; + if (rec->type != IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_ERROR("Invalid attribute type", IGRAPH_EINVAL); + } else { + igraph_vector_t *num = (igraph_vector_t *)rec->value; + VECTOR(*num)[0] = value; + } + } else { + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_vector_t *num; + if (!rec) { + IGRAPH_ERROR("Cannot add graph attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add graph attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + rec->type = IGRAPH_ATTRIBUTE_NUMERIC; + num = igraph_Calloc(1, igraph_vector_t); + if (!num) { + IGRAPH_ERROR("Cannot add graph attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, num); + IGRAPH_VECTOR_INIT_FINALLY(num, 1); + VECTOR(*num)[0] = value; + rec->value = num; + IGRAPH_CHECK(igraph_vector_ptr_push_back(gal, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_GAB_set + * Set a boolean graph attribute + * + * \param graph The graph. + * \param name Name of the graph attribute. If there is no such + * attribute yet, then it will be added. + * \param value The (new) value of the graph attribute. + * \return Error code. + * + * \se \ref SETGAN if you want to type less. + * + * Time complexity: O(1). + */ +int igraph_cattribute_GAB_set(igraph_t *graph, const char *name, + igraph_bool_t value) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *gal = &attr->gal; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(gal, name, &j); + + if (l) { + igraph_attribute_record_t *rec = VECTOR(*gal)[j]; + if (rec->type != IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_ERROR("Invalid attribute type", IGRAPH_EINVAL); + } else { + igraph_vector_bool_t *log = (igraph_vector_bool_t *)rec->value; + VECTOR(*log)[0] = value; + } + } else { + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_vector_bool_t *log; + if (!rec) { + IGRAPH_ERROR("Cannot add graph attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add graph attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + rec->type = IGRAPH_ATTRIBUTE_BOOLEAN; + log = igraph_Calloc(1, igraph_vector_bool_t); + if (!log) { + IGRAPH_ERROR("Cannot add graph attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, log); + IGRAPH_CHECK(igraph_vector_bool_init(log, 1)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, log); + VECTOR(*log)[0] = value; + rec->value = log; + IGRAPH_CHECK(igraph_vector_ptr_push_back(gal, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_GAS_set + * Set a string graph attribute. + * + * \param graph The graph. + * \param name Name of the graph attribute. If there is no such + * attribute yet, then it will be added. + * \param value The (new) value of the graph attribute. It will be + * copied. + * \return Error code. + * + * \se \ref SETGAS if you want to type less. + * + * Time complexity: O(1). + */ +int igraph_cattribute_GAS_set(igraph_t *graph, const char *name, + const char *value) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *gal = &attr->gal; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(gal, name, &j); + + if (l) { + igraph_attribute_record_t *rec = VECTOR(*gal)[j]; + if (rec->type != IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_ERROR("Invalid attribute type", IGRAPH_EINVAL); + } else { + igraph_strvector_t *str = (igraph_strvector_t*)rec->value; + IGRAPH_CHECK(igraph_strvector_set(str, 0, value)); + } + } else { + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_strvector_t *str; + if (!rec) { + IGRAPH_ERROR("Cannot add graph attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add graph attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + rec->type = IGRAPH_ATTRIBUTE_STRING; + str = igraph_Calloc(1, igraph_strvector_t); + if (!str) { + IGRAPH_ERROR("Cannot add graph attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, str); + IGRAPH_STRVECTOR_INIT_FINALLY(str, 1); + IGRAPH_CHECK(igraph_strvector_set(str, 0, value)); + rec->value = str; + IGRAPH_CHECK(igraph_vector_ptr_push_back(gal, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_VAN_set + * Set a numeric vertex attribute + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all vertices + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param vid Vertices for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETVAN for a simpler way. + * + * Time complexity: O(n), the number of vertices if the attribute is + * new, O(|vid|) otherwise. + */ +int igraph_cattribute_VAN_set(igraph_t *graph, const char *name, + igraph_integer_t vid, igraph_real_t value) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + if (l) { + igraph_attribute_record_t *rec = VECTOR(*val)[j]; + if (rec->type != IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_ERROR("Invalid attribute type", IGRAPH_EINVAL); + } else { + igraph_vector_t *num = (igraph_vector_t*)rec->value; + VECTOR(*num)[(long int)vid] = value; + } + } else { + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_vector_t *num; + if (!rec) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + rec->type = IGRAPH_ATTRIBUTE_NUMERIC; + num = igraph_Calloc(1, igraph_vector_t); + if (!num) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, num); + IGRAPH_VECTOR_INIT_FINALLY(num, igraph_vcount(graph)); + igraph_vector_fill(num, IGRAPH_NAN); + VECTOR(*num)[(long int)vid] = value; + rec->value = num; + IGRAPH_CHECK(igraph_vector_ptr_push_back(val, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_VAB_set + * Set a boolean vertex attribute + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all vertices + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param vid Vertices for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETVAB for a simpler way. + * + * Time complexity: O(n), the number of vertices if the attribute is + * new, O(|vid|) otherwise. + */ +int igraph_cattribute_VAB_set(igraph_t *graph, const char *name, + igraph_integer_t vid, igraph_bool_t value) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + if (l) { + igraph_attribute_record_t *rec = VECTOR(*val)[j]; + if (rec->type != IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_ERROR("Invalid attribute type", IGRAPH_EINVAL); + } else { + igraph_vector_bool_t *log = (igraph_vector_bool_t*)rec->value; + VECTOR(*log)[(long int)vid] = value; + } + } else { + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_vector_bool_t *log; + if (!rec) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + rec->type = IGRAPH_ATTRIBUTE_BOOLEAN; + log = igraph_Calloc(1, igraph_vector_bool_t); + if (!log) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, log); + IGRAPH_CHECK(igraph_vector_bool_init(log, igraph_vcount(graph))); + IGRAPH_FINALLY(igraph_vector_bool_destroy, log); + igraph_vector_bool_fill(log, 0); + VECTOR(*log)[(long int)vid] = value; + rec->value = log; + IGRAPH_CHECK(igraph_vector_ptr_push_back(val, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_VAS_set + * Set a string vertex attribute + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all vertices + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param vid Vertices for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETVAS for a simpler way. + * + * Time complexity: O(n*l), n is the number of vertices, l is the + * length of the string to set. If the attribute if not new then only + * O(|vid|*l). + */ +int igraph_cattribute_VAS_set(igraph_t *graph, const char *name, + igraph_integer_t vid, const char *value) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + if (l) { + igraph_attribute_record_t *rec = VECTOR(*val)[j]; + if (rec->type != IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_ERROR("Invalid attribute type", IGRAPH_EINVAL); + } else { + igraph_strvector_t *str = (igraph_strvector_t*)rec->value; + IGRAPH_CHECK(igraph_strvector_set(str, vid, value)); + } + } else { + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_strvector_t *str; + if (!rec) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + rec->type = IGRAPH_ATTRIBUTE_STRING; + str = igraph_Calloc(1, igraph_strvector_t); + if (!str) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, str); + IGRAPH_STRVECTOR_INIT_FINALLY(str, igraph_vcount(graph)); + IGRAPH_CHECK(igraph_strvector_set(str, vid, value)); + rec->value = str; + IGRAPH_CHECK(igraph_vector_ptr_push_back(val, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_EAN_set + * Set a numeric edge attribute + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all edges + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param eid Edges for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETEAN for a simpler way. + * + * Time complexity: O(e), the number of edges if the attribute is + * new, O(|eid|) otherwise. + */ +int igraph_cattribute_EAN_set(igraph_t *graph, const char *name, + igraph_integer_t eid, igraph_real_t value) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + if (l) { + igraph_attribute_record_t *rec = VECTOR(*eal)[j]; + if (rec->type != IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_ERROR("Invalid attribute type", IGRAPH_EINVAL); + } else { + igraph_vector_t *num = (igraph_vector_t*)rec->value; + VECTOR(*num)[(long int)eid] = value; + } + } else { + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_vector_t *num; + if (!rec) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + rec->type = IGRAPH_ATTRIBUTE_NUMERIC; + num = igraph_Calloc(1, igraph_vector_t); + if (!num) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, num); + IGRAPH_VECTOR_INIT_FINALLY(num, igraph_ecount(graph)); + igraph_vector_fill(num, IGRAPH_NAN); + VECTOR(*num)[(long int)eid] = value; + rec->value = num; + IGRAPH_CHECK(igraph_vector_ptr_push_back(eal, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_EAB_set + * Set a boolean edge attribute + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all edges + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param eid Edges for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETEAB for a simpler way. + * + * Time complexity: O(e), the number of edges if the attribute is + * new, O(|eid|) otherwise. + */ +int igraph_cattribute_EAB_set(igraph_t *graph, const char *name, + igraph_integer_t eid, igraph_bool_t value) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + if (l) { + igraph_attribute_record_t *rec = VECTOR(*eal)[j]; + if (rec->type != IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_ERROR("Invalid attribute type", IGRAPH_EINVAL); + } else { + igraph_vector_bool_t *log = (igraph_vector_bool_t*)rec->value; + VECTOR(*log)[(long int)eid] = value; + } + } else { + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_vector_bool_t *log; + if (!rec) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + rec->type = IGRAPH_ATTRIBUTE_BOOLEAN; + log = igraph_Calloc(1, igraph_vector_bool_t); + if (!log) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, log); + IGRAPH_CHECK(igraph_vector_bool_init(log, igraph_ecount(graph))); + IGRAPH_FINALLY(igraph_vector_bool_destroy, log); + igraph_vector_bool_fill(log, 0); + VECTOR(*log)[(long int)eid] = value; + rec->value = log; + IGRAPH_CHECK(igraph_vector_ptr_push_back(eal, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_EAS_set + * Set a string edge attribute + * + * The attribute will be added if not present already. If present it + * will be overwritten. The same \p value is set for all edges + * included in \p vid. + * \param graph The graph. + * \param name Name of the attribute. + * \param eid Edges for which to set the attribute. + * \param value The (new) value of the attribute. + * \return Error code. + * + * \sa \ref SETEAS for a simpler way. + * + * Time complexity: O(e*l), n is the number of edges, l is the + * length of the string to set. If the attribute if not new then only + * O(|eid|*l). + */ +int igraph_cattribute_EAS_set(igraph_t *graph, const char *name, + igraph_integer_t eid, const char *value) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + if (l) { + igraph_attribute_record_t *rec = VECTOR(*eal)[j]; + if (rec->type != IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_ERROR("Invalid attribute type", IGRAPH_EINVAL); + } else { + igraph_strvector_t *str = (igraph_strvector_t*)rec->value; + IGRAPH_CHECK(igraph_strvector_set(str, eid, value)); + } + } else { + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_strvector_t *str; + if (!rec) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + rec->type = IGRAPH_ATTRIBUTE_STRING; + str = igraph_Calloc(1, igraph_strvector_t); + if (!str) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, str); + IGRAPH_STRVECTOR_INIT_FINALLY(str, igraph_ecount(graph)); + IGRAPH_CHECK(igraph_strvector_set(str, eid, value)); + rec->value = str; + IGRAPH_CHECK(igraph_vector_ptr_push_back(eal, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_VAN_setv + * Set a numeric vertex attribute for all vertices. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param v The new attribute values. The length of this vector must + * match the number of vertices. + * \return Error code. + * + * \sa \ref SETVANV for a simpler way. + * + * Time complexity: O(n), the number of vertices. + */ + +int igraph_cattribute_VAN_setv(igraph_t *graph, const char *name, + const igraph_vector_t *v) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + /* Check length first */ + if (igraph_vector_size(v) != igraph_vcount(graph)) { + IGRAPH_ERROR("Invalid vertex attribute vector length", IGRAPH_EINVAL); + } + + if (l) { + /* Already present, check type */ + igraph_attribute_record_t *rec = VECTOR(*val)[j]; + igraph_vector_t *num = (igraph_vector_t *)rec->value; + if (rec->type != IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_ERROR("Attribute type mismatch", IGRAPH_EINVAL); + } + igraph_vector_clear(num); + IGRAPH_CHECK(igraph_vector_append(num, v)); + } else { + /* Add it */ + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_vector_t *num; + if (!rec) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->type = IGRAPH_ATTRIBUTE_NUMERIC; + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + num = igraph_Calloc(1, igraph_vector_t); + if (!num) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, num); + rec->value = num; + IGRAPH_CHECK(igraph_vector_copy(num, v)); + IGRAPH_FINALLY(igraph_vector_destroy, num); + IGRAPH_CHECK(igraph_vector_ptr_push_back(val, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} +/** + * \function igraph_cattribute_VAB_setv + * Set a boolean vertex attribute for all vertices. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param v The new attribute values. The length of this boolean vector must + * match the number of vertices. + * \return Error code. + * + * \sa \ref SETVANV for a simpler way. + * + * Time complexity: O(n), the number of vertices. + */ + +int igraph_cattribute_VAB_setv(igraph_t *graph, const char *name, + const igraph_vector_bool_t *v) { + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + /* Check length first */ + if (igraph_vector_bool_size(v) != igraph_vcount(graph)) { + IGRAPH_ERROR("Invalid vertex attribute vector length", IGRAPH_EINVAL); + } + + if (l) { + /* Already present, check type */ + igraph_attribute_record_t *rec = VECTOR(*val)[j]; + igraph_vector_bool_t *log = (igraph_vector_bool_t *)rec->value; + if (rec->type != IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_ERROR("Attribute type mismatch", IGRAPH_EINVAL); + } + igraph_vector_bool_clear(log); + IGRAPH_CHECK(igraph_vector_bool_append(log, v)); + } else { + /* Add it */ + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_vector_bool_t *log; + if (!rec) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->type = IGRAPH_ATTRIBUTE_BOOLEAN; + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + log = igraph_Calloc(1, igraph_vector_bool_t); + if (!log) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, log); + rec->value = log; + IGRAPH_CHECK(igraph_vector_bool_copy(log, v)); + IGRAPH_FINALLY(igraph_vector_destroy, log); + IGRAPH_CHECK(igraph_vector_ptr_push_back(val, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_VAS_setv + * Set a string vertex attribute for all vertices. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param sv String vector, the new attribute values. The length of this vector must + * match the number of vertices. + * \return Error code. + * + * \sa \ref SETVASV for a simpler way. + * + * Time complexity: O(n+l), n is the number of vertices, l is the + * total length of the strings. + */ +int igraph_cattribute_VAS_setv(igraph_t *graph, const char *name, + const igraph_strvector_t *sv) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + /* Check length first */ + if (igraph_strvector_size(sv) != igraph_vcount(graph)) { + IGRAPH_ERROR("Invalid vertex attribute vector length", IGRAPH_EINVAL); + } + + if (l) { + /* Already present, check type */ + igraph_attribute_record_t *rec = VECTOR(*val)[j]; + igraph_strvector_t *str = (igraph_strvector_t *)rec->value; + if (rec->type != IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_ERROR("Attribute type mismatch", IGRAPH_EINVAL); + } + igraph_strvector_clear(str); + IGRAPH_CHECK(igraph_strvector_append(str, sv)); + } else { + /* Add it */ + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_strvector_t *str; + if (!rec) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->type = IGRAPH_ATTRIBUTE_STRING; + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + str = igraph_Calloc(1, igraph_strvector_t); + if (!str) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, str); + rec->value = str; + IGRAPH_CHECK(igraph_strvector_copy(str, sv)); + IGRAPH_FINALLY(igraph_strvector_destroy, str); + IGRAPH_CHECK(igraph_vector_ptr_push_back(val, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_EAN_setv + * Set a numeric edge attribute for all vertices. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param v The new attribute values. The length of this vector must + * match the number of edges. + * \return Error code. + * + * \sa \ref SETEANV for a simpler way. + * + * Time complexity: O(e), the number of edges. + */ +int igraph_cattribute_EAN_setv(igraph_t *graph, const char *name, + const igraph_vector_t *v) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + /* Check length first */ + if (igraph_vector_size(v) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid edge attribute vector length", IGRAPH_EINVAL); + } + + if (l) { + /* Already present, check type */ + igraph_attribute_record_t *rec = VECTOR(*eal)[j]; + igraph_vector_t *num = (igraph_vector_t *)rec->value; + if (rec->type != IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_ERROR("Attribute type mismatch", IGRAPH_EINVAL); + } + igraph_vector_clear(num); + IGRAPH_CHECK(igraph_vector_append(num, v)); + } else { + /* Add it */ + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_vector_t *num; + if (!rec) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->type = IGRAPH_ATTRIBUTE_NUMERIC; + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + num = igraph_Calloc(1, igraph_vector_t); + if (!num) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, num); + rec->value = num; + IGRAPH_CHECK(igraph_vector_copy(num, v)); + IGRAPH_FINALLY(igraph_vector_destroy, num); + IGRAPH_CHECK(igraph_vector_ptr_push_back(eal, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_EAB_setv + * Set a boolean edge attribute for all vertices. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param v The new attribute values. The length of this vector must + * match the number of edges. + * \return Error code. + * + * \sa \ref SETEABV for a simpler way. + * + * Time complexity: O(e), the number of edges. + */ +int igraph_cattribute_EAB_setv(igraph_t *graph, const char *name, + const igraph_vector_bool_t *v) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + /* Check length first */ + if (igraph_vector_bool_size(v) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid edge attribute vector length", IGRAPH_EINVAL); + } + + if (l) { + /* Already present, check type */ + igraph_attribute_record_t *rec = VECTOR(*eal)[j]; + igraph_vector_bool_t *log = (igraph_vector_bool_t *)rec->value; + if (rec->type != IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_ERROR("Attribute type mismatch", IGRAPH_EINVAL); + } + igraph_vector_bool_clear(log); + IGRAPH_CHECK(igraph_vector_bool_append(log, v)); + } else { + /* Add it */ + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_vector_bool_t *log; + if (!rec) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->type = IGRAPH_ATTRIBUTE_BOOLEAN; + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + log = igraph_Calloc(1, igraph_vector_bool_t); + if (!log) { + IGRAPH_ERROR("Cannot add edge attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, log); + rec->value = log; + IGRAPH_CHECK(igraph_vector_bool_copy(log, v)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, log); + IGRAPH_CHECK(igraph_vector_ptr_push_back(eal, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +/** + * \function igraph_cattribute_EAS_setv + * Set a string edge attribute for all vertices. + * + * The attribute will be added if not present yet. + * \param graph The graph. + * \param name Name of the attribute. + * \param sv String vector, the new attribute values. The length of this vector must + * match the number of edges. + * \return Error code. + * + * \sa \ref SETEASV for a simpler way. + * + * Time complexity: O(e+l), e is the number of edges, l is the + * total length of the strings. + */ +int igraph_cattribute_EAS_setv(igraph_t *graph, const char *name, + const igraph_strvector_t *sv) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + /* Check length first */ + if (igraph_strvector_size(sv) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid edge attribute vector length", IGRAPH_EINVAL); + } + + if (l) { + /* Already present, check type */ + igraph_attribute_record_t *rec = VECTOR(*eal)[j]; + igraph_strvector_t *str = (igraph_strvector_t *)rec->value; + if (rec->type != IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_ERROR("Attribute type mismatch", IGRAPH_EINVAL); + } + igraph_strvector_clear(str); + IGRAPH_CHECK(igraph_strvector_append(str, sv)); + } else { + /* Add it */ + igraph_attribute_record_t *rec = igraph_Calloc(1, igraph_attribute_record_t); + igraph_strvector_t *str; + if (!rec) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, rec); + rec->type = IGRAPH_ATTRIBUTE_STRING; + rec->name = strdup(name); + if (!rec->name) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (char*)rec->name); + str = igraph_Calloc(1, igraph_strvector_t); + if (!str) { + IGRAPH_ERROR("Cannot add vertex attribute", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, str); + rec->value = str; + IGRAPH_CHECK(igraph_strvector_copy(str, sv)); + IGRAPH_FINALLY(igraph_strvector_destroy, str); + IGRAPH_CHECK(igraph_vector_ptr_push_back(eal, rec)); + IGRAPH_FINALLY_CLEAN(4); + } + + return 0; +} + +void igraph_i_cattribute_free_rec(igraph_attribute_record_t *rec) { + + if (rec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *num = (igraph_vector_t*)rec->value; + igraph_vector_destroy(num); + } else if (rec->type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *str = (igraph_strvector_t*)rec->value; + igraph_strvector_destroy(str); + } else if (rec->type == IGRAPH_ATTRIBUTE_BOOLEAN) { + igraph_vector_bool_t *boolvec = (igraph_vector_bool_t*)rec->value; + igraph_vector_bool_destroy(boolvec); + } + igraph_Free(rec->name); + igraph_Free(rec->value); + igraph_Free(rec); +} + +/** + * \function igraph_cattribute_remove_g + * Remove a graph attribute + * + * \param graph The graph object. + * \param name Name of the graph attribute to remove. + * + * \sa \ref DELGA for a simpler way. + * + */ +void igraph_cattribute_remove_g(igraph_t *graph, const char *name) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *gal = &attr->gal; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(gal, name, &j); + + if (l) { + igraph_i_cattribute_free_rec(VECTOR(*gal)[j]); + igraph_vector_ptr_remove(gal, j); + } else { + IGRAPH_WARNING("Cannot remove non-existent graph attribute"); + } +} + +/** + * \function igraph_cattribute_remove_v + * Remove a vertex attribute + * + * \param graph The graph object. + * \param name Name of the vertex attribute to remove. + * + * \sa \ref DELVA for a simpler way. + * + */ +void igraph_cattribute_remove_v(igraph_t *graph, const char *name) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *val = &attr->val; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(val, name, &j); + + if (l) { + igraph_i_cattribute_free_rec(VECTOR(*val)[j]); + igraph_vector_ptr_remove(val, j); + } else { + IGRAPH_WARNING("Cannot remove non-existent graph attribute"); + } +} + +/** + * \function igraph_cattribute_remove_e + * Remove an edge attribute + * + * \param graph The graph object. + * \param name Name of the edge attribute to remove. + * + * \sa \ref DELEA for a simpler way. + * + */ +void igraph_cattribute_remove_e(igraph_t *graph, const char *name) { + + igraph_i_cattributes_t *attr = graph->attr; + igraph_vector_ptr_t *eal = &attr->eal; + long int j; + igraph_bool_t l = igraph_i_cattribute_find(eal, name, &j); + + if (l) { + igraph_i_cattribute_free_rec(VECTOR(*eal)[j]); + igraph_vector_ptr_remove(eal, j); + } else { + IGRAPH_WARNING("Cannot remove non-existent graph attribute"); + } +} + +/** + * \function igraph_cattribute_remove_all + * Remove all graph/vertex/edge attributes + * + * \param graph The graph object. + * \param g Boolean, whether to remove graph attributes. + * \param v Boolean, whether to remove vertex attributes. + * \param e Boolean, whether to remove edge attributes. + * + * \sa \ref DELGAS, \ref DELVAS, \ref DELEAS, \ref DELALL for simpler + * ways. + */ +void igraph_cattribute_remove_all(igraph_t *graph, igraph_bool_t g, + igraph_bool_t v, igraph_bool_t e) { + + igraph_i_cattributes_t *attr = graph->attr; + + if (g) { + igraph_vector_ptr_t *gal = &attr->gal; + long int i, n = igraph_vector_ptr_size(gal); + for (i = 0; i < n; i++) { + igraph_i_cattribute_free_rec(VECTOR(*gal)[i]); + } + igraph_vector_ptr_clear(gal); + } + if (v) { + igraph_vector_ptr_t *val = &attr->val; + long int i, n = igraph_vector_ptr_size(val); + for (i = 0; i < n; i++) { + igraph_i_cattribute_free_rec(VECTOR(*val)[i]); + } + igraph_vector_ptr_clear(val); + } + if (e) { + igraph_vector_ptr_t *eal = &attr->eal; + long int i, n = igraph_vector_ptr_size(eal); + for (i = 0; i < n; i++) { + igraph_i_cattribute_free_rec(VECTOR(*eal)[i]); + } + igraph_vector_ptr_clear(eal); + } +} diff --git a/src/centrality.c b/src/centrality.c new file mode 100644 index 0000000..ecae6d9 --- /dev/null +++ b/src/centrality.c @@ -0,0 +1,3516 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_centrality.h" +#include "igraph_math.h" +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_interrupt_internal.h" +#include "igraph_topology.h" +#include "igraph_types_internal.h" +#include "igraph_stack.h" +#include "igraph_dqueue.h" +#include "config.h" + +#include "bigint.h" +#include "prpack.h" + +#include +#include /* memset */ + +int igraph_personalized_pagerank_arpack(const igraph_t *graph, + igraph_vector_t *vector, + igraph_real_t *value, const igraph_vs_t vids, + igraph_bool_t directed, igraph_real_t damping, + igraph_vector_t *reset, + const igraph_vector_t *weights, + igraph_arpack_options_t *options); + +static igraph_bool_t igraph_i_vector_mostly_negative(const igraph_vector_t *vector) { + /* Many of the centrality measures correspond to the eigenvector of some + * matrix. When v is an eigenvector, c*v is also an eigenvector, therefore + * it may happen that all the scores in the eigenvector are negative, in which + * case we want to negate them since the centrality scores should be positive. + * However, since ARPACK is not always stable, sometimes it happens that + * *some* of the centrality scores are small negative numbers. This function + * helps distinguish between the two cases; it should return true if most of + * the values are relatively large negative numbers, in which case we should + * negate the eigenvector. + */ + long int i, n = igraph_vector_size(vector); + igraph_real_t mi, ma; + + if (n == 0) { + return 0; + } + + mi = ma = VECTOR(*vector)[0]; + for (i = 1; i < n; i++) { + if (VECTOR(*vector)[i] < mi) { + mi = VECTOR(*vector)[i]; + } + if (VECTOR(*vector)[i] > ma) { + ma = VECTOR(*vector)[i]; + } + } + + if (mi >= 0) { + return 0; + } + if (ma <= 0) { + return 1; + } + + mi /= ma; + return (mi < 1e-5) ? 1 : 0; +} + +static int igraph_i_eigenvector_centrality(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_adjlist_t *adjlist = extra; + igraph_vector_int_t *neis; + long int i, j, nlen; + + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(adjlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + to[i] += from[nei]; + } + } + + + return 0; +} + +typedef struct igraph_i_eigenvector_centrality_t { + const igraph_t *graph; + const igraph_inclist_t *inclist; + const igraph_vector_t *weights; +} igraph_i_eigenvector_centrality_t; + +static int igraph_i_eigenvector_centrality2(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_eigenvector_centrality_t *data = extra; + const igraph_t *graph = data->graph; + const igraph_inclist_t *inclist = data->inclist; + const igraph_vector_t *weights = data->weights; + igraph_vector_int_t *edges; + long int i, j, nlen; + + for (i = 0; i < n; i++) { + edges = igraph_inclist_get(inclist, i); + nlen = igraph_vector_int_size(edges); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int edge = VECTOR(*edges)[j]; + long int nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + to[i] += w * from[nei]; + } + } + + return 0; +} + +static int igraph_i_eigenvector_centrality_loop(igraph_adjlist_t *adjlist) { + + long int i, j, k, nlen, n = igraph_adjlist_size(adjlist); + igraph_vector_int_t *neis; + + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(adjlist, i); + nlen = igraph_vector_int_size(neis); + for (j = 0; j < nlen && VECTOR(*neis)[j] < i; j++) ; + for (k = j; k < nlen && VECTOR(*neis)[k] == i; k++) ; + if (k != j) { + /* First loop edge is 'j', first non-loop edge is 'k' */ + igraph_vector_int_remove_section(neis, j + (k - j) / 2, k); + } + } + + return 0; +} + +int igraph_eigenvector_centrality_undirected(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, igraph_bool_t scale, + const igraph_vector_t *weights, + igraph_arpack_options_t *options) { + + igraph_vector_t values; + igraph_matrix_t vectors; + igraph_vector_t degree; + long int i; + + options->n = igraph_vcount(graph); + options->start = 1; /* no random start vector */ + + if (igraph_ecount(graph) == 0) { + /* special case: empty graph */ + if (value) { + *value = 0; + } + if (vector) { + igraph_vector_resize(vector, igraph_vcount(graph)); + igraph_vector_fill(vector, 1); + } + return IGRAPH_SUCCESS; + } + + if (weights) { + igraph_real_t min, max; + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid length of weights vector when calculating " + "eigenvector centrality", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_vector_minmax(weights, &min, &max)); + if (min == 0 && max == 0) { + /* special case: all weights are zeros */ + if (value) { + *value = 0; + } + if (vector) { + igraph_vector_resize(vector, igraph_vcount(graph)); + igraph_vector_fill(vector, 1); + } + return IGRAPH_SUCCESS; + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&values, 0); + IGRAPH_MATRIX_INIT_FINALLY(&vectors, options->n, 1); + + IGRAPH_VECTOR_INIT_FINALLY(°ree, options->n); + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), + IGRAPH_ALL, /*loops=*/ 0)); + RNG_BEGIN(); + for (i = 0; i < options->n; i++) { + if (VECTOR(degree)[i]) { + MATRIX(vectors, i, 0) = VECTOR(degree)[i] + RNG_UNIF(-1e-4, 1e-4); + } else { + MATRIX(vectors, i, 0) = 1.0; + } + } + RNG_END(); + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + + options->n = igraph_vcount(graph); + options->nev = 1; + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rssolve */ + options->which[0] = 'L'; options->which[1] = 'A'; + options->start = 1; /* no random start vector */ + + if (!weights) { + + igraph_adjlist_t adjlist; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_i_eigenvector_centrality_loop(&adjlist)); + + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_eigenvector_centrality, + &adjlist, options, 0, &values, &vectors)); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + } else { + + igraph_inclist_t inclist; + igraph_i_eigenvector_centrality_t data = { graph, &inclist, weights }; + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_inclist_remove_duplicate(graph, &inclist)); + + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_eigenvector_centrality2, + &data, options, 0, &values, &vectors)); + + igraph_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(1); + } + + if (value) { + *value = VECTOR(values)[0]; + } + + if (vector) { + igraph_real_t amax = 0; + long int which = 0; + long int i; + IGRAPH_CHECK(igraph_vector_resize(vector, options->n)); + + if (VECTOR(values)[0] <= 0) { + /* Pathological case: largest eigenvalue is zero, therefore all the + * scores can also be zeros, this will be a valid eigenvector. + * This usually happens with graphs that have lots of sinks and + * sources only. */ + igraph_vector_fill(vector, 0); + } else { + for (i = 0; i < options->n; i++) { + igraph_real_t tmp; + VECTOR(*vector)[i] = MATRIX(vectors, i, 0); + tmp = fabs(VECTOR(*vector)[i]); + if (tmp > amax) { + amax = tmp; + which = i; + } + } + if (scale && amax != 0) { + igraph_vector_scale(vector, 1 / VECTOR(*vector)[which]); + } else if (igraph_i_vector_mostly_negative(vector)) { + igraph_vector_scale(vector, -1.0); + } + + /* Correction for numeric inaccuracies (eliminating -0.0) */ + for (i = 0; i < options->n; i++) { + if (VECTOR(*vector)[i] < 0) { + VECTOR(*vector)[i] = 0; + } + } + } + } + + if (options->info) { + IGRAPH_WARNING("Non-zero return code from ARPACK routine!"); + } + + igraph_matrix_destroy(&vectors); + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/* int igraph_i_evcent_dir(igraph_real_t *to, const igraph_real_t *from, */ +/* long int n, void *extra) { */ +/* /\* TODO *\/ */ +/* return 0; */ +/* } */ + +/* int igraph_i_evcent_dir2(igraph_real_t *to, const igraph_real_t *from, */ +/* long int n, void *extra) { */ +/* /\* TODO *\/ */ +/* return 0; */ +/* } */ + +int igraph_eigenvector_centrality_directed(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, igraph_bool_t scale, + const igraph_vector_t *weights, + igraph_arpack_options_t *options) { + + igraph_matrix_t values; + igraph_matrix_t vectors; + igraph_vector_t indegree; + igraph_bool_t dag; + long int i; + + if (igraph_ecount(graph) == 0) { + /* special case: empty graph */ + if (value) { + *value = 0; + } + if (vector) { + igraph_vector_resize(vector, igraph_vcount(graph)); + igraph_vector_fill(vector, 1); + } + return IGRAPH_SUCCESS; + } + + /* Quick check: if the graph is a DAG, all the eigenvector centralities are + * zeros, and so is the eigenvalue */ + IGRAPH_CHECK(igraph_is_dag(graph, &dag)); + if (dag) { + /* special case: graph is a DAG */ + IGRAPH_WARNING("graph is directed and acyclic; eigenvector centralities " + "will be zeros"); + if (value) { + *value = 0; + } + if (vector) { + igraph_vector_resize(vector, igraph_vcount(graph)); + igraph_vector_fill(vector, 0); + } + return IGRAPH_SUCCESS; + } + + if (weights) { + igraph_real_t min, max; + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid length of weights vector when calculating " + "eigenvector centrality", IGRAPH_EINVAL); + } + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Weighted directed graph in eigenvector centrality"); + } + + IGRAPH_CHECK(igraph_vector_minmax(weights, &min, &max)); + + if (min < 0.0) { + IGRAPH_WARNING("Negative weights, eigenpair might be complex"); + } + if (min == 0.0 && max == 0.0) { + /* special case: all weights are zeros */ + if (value) { + *value = 0; + } + if (vector) { + igraph_vector_resize(vector, igraph_vcount(graph)); + igraph_vector_fill(vector, 1); + } + return IGRAPH_SUCCESS; + } + } + + options->n = igraph_vcount(graph); + options->start = 1; + options->nev = 1; + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rnsolve */ + /* LM mode is not OK here because +1 and -1 can be eigenvalues at the + * same time, e.g.: a -> b -> a, c -> a */ + options->which[0] = 'L' ; options->which[1] = 'R'; + + IGRAPH_MATRIX_INIT_FINALLY(&values, 0, 0); + IGRAPH_MATRIX_INIT_FINALLY(&vectors, options->n, 1); + + IGRAPH_VECTOR_INIT_FINALLY(&indegree, options->n); + IGRAPH_CHECK(igraph_strength(graph, &indegree, igraph_vss_all(), + IGRAPH_IN, /*loops=*/ 1, weights)); + RNG_BEGIN(); + for (i = 0; i < options->n; i++) { + if (VECTOR(indegree)[i]) { + MATRIX(vectors, i, 0) = VECTOR(indegree)[i] + RNG_UNIF(-1e-4, 1e-4); + } else { + MATRIX(vectors, i, 0) = 1.0; + } + } + RNG_END(); + igraph_vector_destroy(&indegree); + IGRAPH_FINALLY_CLEAN(1); + + if (!weights) { + igraph_adjlist_t adjlist; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_arpack_rnsolve(igraph_i_eigenvector_centrality, + &adjlist, options, 0, &values, + &vectors)); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_inclist_t inclist; + igraph_i_eigenvector_centrality_t data = { graph, &inclist, weights }; + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_arpack_rnsolve(igraph_i_eigenvector_centrality2, + &data, options, 0, &values, &vectors)); + + igraph_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(1); + } + + if (value) { + *value = MATRIX(values, 0, 0); + } + + if (vector) { + igraph_real_t amax = 0; + long int which = 0; + long int i; + IGRAPH_CHECK(igraph_vector_resize(vector, options->n)); + + if (MATRIX(values, 0, 0) <= 0) { + /* Pathological case: largest eigenvalue is zero, therefore all the + * scores can also be zeros, this will be a valid eigenvector. + * This usually happens with graphs that have lots of sinks and + * sources only. */ + igraph_vector_fill(vector, 0); + MATRIX(values, 0, 0) = 0; + } else { + for (i = 0; i < options->n; i++) { + igraph_real_t tmp; + VECTOR(*vector)[i] = MATRIX(vectors, i, 0); + tmp = fabs(VECTOR(*vector)[i]); + if (tmp > amax) { + amax = tmp; + which = i; + } + } + if (scale && amax != 0) { + igraph_vector_scale(vector, 1 / VECTOR(*vector)[which]); + } else if (igraph_i_vector_mostly_negative(vector)) { + igraph_vector_scale(vector, -1.0); + } + } + + /* Correction for numeric inaccuracies (eliminating -0.0) */ + for (i = 0; i < options->n; i++) { + if (VECTOR(*vector)[i] < 0) { + VECTOR(*vector)[i] = 0; + } + } + } + + if (options->info) { + IGRAPH_WARNING("Non-zero return code from ARPACK routine!"); + } + + igraph_matrix_destroy(&vectors); + igraph_matrix_destroy(&values); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_eigenvector_centrality + * Eigenvector centrality of the vertices + * + * Eigenvector centrality is a measure of the importance of a node in a + * network. It assigns relative scores to all nodes in the network based + * on the principle that connections to high-scoring nodes contribute + * more to the score of the node in question than equal connections to + * low-scoring nodes. In practice, this is determined by calculating the + * eigenvector corresponding to the largest positive eigenvalue of the + * adjacency matrix. The centrality scores returned by igraph are always + * normalized such that the largest eigenvector centrality score is one + * (with one exception, see below). + * + * + * Since the eigenvector centrality scores of nodes in different components + * do not affect each other, it may be beneficial for large graphs to + * decompose it first into weakly connected components and calculate the + * centrality scores individually for each component. + * + * + * Also note that the adjacency matrix of a directed acyclic graph or the + * adjacency matrix of an empty graph does not possess positive eigenvalues, + * therefore the eigenvector centrality is not defined for these graphs. + * igraph will return an eigenvalue of zero in such cases. The eigenvector + * centralities will all be equal for an empty graph and will all be zeros + * for a directed acyclic graph. Such pathological cases can be detected + * by asking igraph to calculate the eigenvalue as well (using the \p value + * parameter, see below) and checking whether the eigenvalue is very close + * to zero. + * + * \param graph The input graph. It might be directed. + * \param vector Pointer to an initialized vector, it will be resized + * as needed. The result of the computation is stored here. It can + * be a null pointer, then it is ignored. + * \param value If not a null pointer, then the eigenvalue + * corresponding to the found eigenvector is stored here. + * \param directed Boolean scalar, whether to consider edge directions + * in a directed graph. It is ignored for undirected graphs. + * \param scale If not zero then the result will be scaled such that + * the absolute value of the maximum centrality is one. + * \param weights A null pointer (=no edge weights), or a vector + * giving the weights of the edges. The algorithm might result + * complex numbers is some weights are negative. In this case only + * the real part is reported. + * \param options Options to ARPACK. See \ref igraph_arpack_options_t + * for details. Note that the function overwrites the + * n (number of vertices) parameter and + * it always starts the calculation from a non-random vector + * calculated based on the degree of the vertices. + * \return Error code. + * + * Time complexity: depends on the input graph, usually it is O(|V|+|E|). + * + * \sa \ref igraph_pagerank and \ref igraph_personalized_pagerank for + * modifications of eigenvector centrality. + * + * \example examples/simple/eigenvector_centrality.c + */ + +int igraph_eigenvector_centrality(const igraph_t *graph, + igraph_vector_t *vector, + igraph_real_t *value, + igraph_bool_t directed, igraph_bool_t scale, + const igraph_vector_t *weights, + igraph_arpack_options_t *options) { + + if (directed && igraph_is_directed(graph)) { + return igraph_eigenvector_centrality_directed(graph, vector, value, + scale, weights, options); + } else { + return igraph_eigenvector_centrality_undirected(graph, vector, value, + scale, weights, options); + } +} + +/* struct for the unweighted variant of the HITS algorithm */ +typedef struct igraph_i_kleinberg_data_t { + igraph_adjlist_t *in; + igraph_adjlist_t *out; + igraph_vector_t *tmp; +} igraph_i_kleinberg_data_t; + +/* struct for the weighted variant of the HITS algorithm */ +typedef struct igraph_i_kleinberg_data2_t { + const igraph_t *graph; + igraph_inclist_t *in; + igraph_inclist_t *out; + igraph_vector_t *tmp; + const igraph_vector_t *weights; +} igraph_i_kleinberg_data2_t; + +/* ARPACK auxiliary routine for the unweighted HITS algorithm */ +static int igraph_i_kleinberg_unweighted(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + igraph_i_kleinberg_data_t *data = (igraph_i_kleinberg_data_t*)extra; + igraph_adjlist_t *in = data->in; + igraph_adjlist_t *out = data->out; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + long int i, j, nlen; + + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(in, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + VECTOR(*tmp)[i] += from[nei]; + } + } + + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(out, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + to[i] += VECTOR(*tmp)[nei]; + } + } + + return 0; +} + +/* ARPACK auxiliary routine for the weighted HITS algorithm */ +static int igraph_i_kleinberg_weighted(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + + igraph_i_kleinberg_data2_t *data = (igraph_i_kleinberg_data2_t*)extra; + igraph_inclist_t *in = data->in; + igraph_inclist_t *out = data->out; + igraph_vector_t *tmp = data->tmp; + const igraph_vector_t *weights = data->weights; + const igraph_t *g = data->graph; + igraph_vector_int_t *neis; + long int i, j, nlen; + + for (i = 0; i < n; i++) { + neis = igraph_inclist_get(in, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei_edge = (long int) VECTOR(*neis)[j]; + long int nei = IGRAPH_OTHER(g, nei_edge, i); + VECTOR(*tmp)[i] += from[nei] * VECTOR(*weights)[nei_edge]; + } + } + + for (i = 0; i < n; i++) { + neis = igraph_inclist_get(out, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei_edge = (long int) VECTOR(*neis)[j]; + long int nei = IGRAPH_OTHER(g, nei_edge, i); + to[i] += VECTOR(*tmp)[nei] * VECTOR(*weights)[nei_edge]; + } + } + + return 0; +} + +static int igraph_i_kleinberg(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, igraph_bool_t scale, + const igraph_vector_t *weights, + igraph_arpack_options_t *options, int inout) { + + igraph_adjlist_t myinadjlist, myoutadjlist; + igraph_inclist_t myininclist, myoutinclist; + igraph_adjlist_t *inadjlist, *outadjlist; + igraph_inclist_t *ininclist, *outinclist; + igraph_vector_t tmp; + igraph_vector_t values; + igraph_matrix_t vectors; + igraph_i_kleinberg_data_t extra; + igraph_i_kleinberg_data2_t extra2; + long int i; + + if (igraph_ecount(graph) == 0 || igraph_vcount(graph) == 1) { + /* special case: empty graph or single vertex */ + if (value) { + *value = igraph_ecount(graph) ? 1.0 : IGRAPH_NAN; + } + if (vector) { + igraph_vector_resize(vector, igraph_vcount(graph)); + igraph_vector_fill(vector, 1); + } + return IGRAPH_SUCCESS; + } + + if (weights) { + igraph_real_t min, max; + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid length of weights vector when calculating " + "hub or authority scores", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_vector_minmax(weights, &min, &max)); + if (min == 0 && max == 0) { + /* special case: all weights are zeros */ + if (value) { + *value = IGRAPH_NAN; + } + if (vector) { + igraph_vector_resize(vector, igraph_vcount(graph)); + igraph_vector_fill(vector, 1); + } + return IGRAPH_SUCCESS; + } + } + + options->n = igraph_vcount(graph); + options->start = 1; /* no random start vector */ + + IGRAPH_VECTOR_INIT_FINALLY(&values, 0); + IGRAPH_MATRIX_INIT_FINALLY(&vectors, options->n, 1); + IGRAPH_VECTOR_INIT_FINALLY(&tmp, options->n); + + if (inout == 0) { + inadjlist = &myinadjlist; + outadjlist = &myoutadjlist; + ininclist = &myininclist; + outinclist = &myoutinclist; + } else if (inout == 1) { + inadjlist = &myoutadjlist; + outadjlist = &myinadjlist; + ininclist = &myoutinclist; + outinclist = &myininclist; + } else { + /* This should not happen */ + IGRAPH_ERROR("Invalid 'inout' argument, please do not call " + "this function directly", IGRAPH_FAILURE); + } + + if (weights == 0) { + IGRAPH_CHECK(igraph_adjlist_init(graph, &myinadjlist, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &myinadjlist); + IGRAPH_CHECK(igraph_adjlist_init(graph, &myoutadjlist, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &myoutadjlist); + } else { + IGRAPH_CHECK(igraph_inclist_init(graph, &myininclist, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_inclist_destroy, &myininclist); + IGRAPH_CHECK(igraph_inclist_init(graph, &myoutinclist, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_inclist_destroy, &myoutinclist); + } + + IGRAPH_CHECK(igraph_degree(graph, &tmp, igraph_vss_all(), IGRAPH_ALL, 0)); + for (i = 0; i < options->n; i++) { + if (VECTOR(tmp)[i] != 0) { + MATRIX(vectors, i, 0) = VECTOR(tmp)[i]; + } else { + MATRIX(vectors, i, 0) = 1.0; + } + } + + extra.in = inadjlist; extra.out = outadjlist; extra.tmp = &tmp; + extra2.in = ininclist; extra2.out = outinclist; extra2.tmp = &tmp; + extra2.graph = graph; extra2.weights = weights; + + options->nev = 1; + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rssolve */ + options->which[0] = 'L'; options->which[1] = 'M'; + + if (weights == 0) { + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_kleinberg_unweighted, &extra, + options, 0, &values, &vectors)); + igraph_adjlist_destroy(&myoutadjlist); + igraph_adjlist_destroy(&myinadjlist); + IGRAPH_FINALLY_CLEAN(2); + } else { + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_kleinberg_weighted, &extra2, + options, 0, &values, &vectors)); + igraph_inclist_destroy(&myoutinclist); + igraph_inclist_destroy(&myininclist); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + if (value) { + *value = VECTOR(values)[0]; + } + + if (vector) { + igraph_real_t amax = 0; + long int which = 0; + long int i; + IGRAPH_CHECK(igraph_vector_resize(vector, options->n)); + for (i = 0; i < options->n; i++) { + igraph_real_t tmp; + VECTOR(*vector)[i] = MATRIX(vectors, i, 0); + tmp = fabs(VECTOR(*vector)[i]); + if (tmp > amax) { + amax = tmp; + which = i; + } + } + if (scale && amax != 0) { + igraph_vector_scale(vector, 1 / VECTOR(*vector)[which]); + } else if (igraph_i_vector_mostly_negative(vector)) { + igraph_vector_scale(vector, -1.0); + } + + /* Correction for numeric inaccuracies (eliminating -0.0) */ + for (i = 0; i < options->n; i++) { + if (VECTOR(*vector)[i] < 0) { + VECTOR(*vector)[i] = 0; + } + } + } + + if (options->info) { + IGRAPH_WARNING("Non-zero return code from ARPACK routine!"); + } + igraph_matrix_destroy(&vectors); + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_hub_score + * Kleinberg's hub scores + * + * The hub scores of the vertices are defined as the principal + * eigenvector of A*A^T, where A is the adjacency + * matrix of the graph, A^T is its transposed. + * + * See the following reference on the meaning of this score: + * J. Kleinberg. Authoritative sources in a hyperlinked + * environment. \emb Proc. 9th ACM-SIAM Symposium on Discrete + * Algorithms, \eme 1998. Extended version in \emb Journal of the + * ACM \eme 46(1999). Also appears as IBM Research Report RJ 10076, May + * 1997. + * \param graph The input graph. Can be directed and undirected. + * \param vector Pointer to an initialized vector, the result is + * stored here. If a null pointer then it is ignored. + * \param value If not a null pointer then the eigenvalue + * corresponding to the calculated eigenvector is stored here. + * \param scale If not zero then the result will be scaled such that + * the absolute value of the maximum centrality is one. + * \param weights A null pointer (=no edge weights), or a vector + * giving the weights of the edges. + * \param options Options to ARPACK. See \ref igraph_arpack_options_t + * for details. Note that the function overwrites the + * n (number of vertices) parameter and + * it always starts the calculation from a non-random vector + * calculated based on the degree of the vertices. + * \return Error code. + * + * Time complexity: depends on the input graph, usually it is O(|V|), + * the number of vertices. + * + * \sa \ref igraph_authority_score() for the companion measure, + * \ref igraph_pagerank(), \ref igraph_personalized_pagerank(), + * \ref igraph_eigenvector_centrality() for similar measures. + */ + +int igraph_hub_score(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, igraph_bool_t scale, + const igraph_vector_t *weights, + igraph_arpack_options_t *options) { + + return igraph_i_kleinberg(graph, vector, value, scale, weights, options, 0); +} + +/** + * \function igraph_authority_score + * Kleinerg's authority scores + * + * The authority scores of the vertices are defined as the principal + * eigenvector of A^T*A, where A is the adjacency + * matrix of the graph, A^T is its transposed. + * + * See the following reference on the meaning of this score: + * J. Kleinberg. Authoritative sources in a hyperlinked + * environment. \emb Proc. 9th ACM-SIAM Symposium on Discrete + * Algorithms, \eme 1998. Extended version in \emb Journal of the + * ACM \eme 46(1999). Also appears as IBM Research Report RJ 10076, May + * 1997. + * \param graph The input graph. Can be directed and undirected. + * \param vector Pointer to an initialized vector, the result is + * stored here. If a null pointer then it is ignored. + * \param value If not a null pointer then the eigenvalue + * corresponding to the calculated eigenvector is stored here. + * \param scale If not zero then the result will be scaled such that + * the absolute value of the maximum centrality is one. + * \param weights A null pointer (=no edge weights), or a vector + * giving the weights of the edges. + * \param options Options to ARPACK. See \ref igraph_arpack_options_t + * for details. Note that the function overwrites the + * n (number of vertices) parameter and + * it always starts the calculation from a non-random vector + * calculated based on the degree of the vertices. + * \return Error code. + * + * Time complexity: depends on the input graph, usually it is O(|V|), + * the number of vertices. + * + * \sa \ref igraph_hub_score() for the companion measure, + * \ref igraph_pagerank(), \ref igraph_personalized_pagerank(), + * \ref igraph_eigenvector_centrality() for similar measures. + */ + +int igraph_authority_score(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, igraph_bool_t scale, + const igraph_vector_t *weights, + igraph_arpack_options_t *options) { + + return igraph_i_kleinberg(graph, vector, value, scale, weights, options, 1); +} + +typedef struct igraph_i_pagerank_data_t { + const igraph_t *graph; + igraph_adjlist_t *adjlist; + igraph_real_t damping; + igraph_vector_t *outdegree; + igraph_vector_t *tmp; + igraph_vector_t *reset; +} igraph_i_pagerank_data_t; + +typedef struct igraph_i_pagerank_data2_t { + const igraph_t *graph; + igraph_inclist_t *inclist; + const igraph_vector_t *weights; + igraph_real_t damping; + igraph_vector_t *outdegree; + igraph_vector_t *tmp; + igraph_vector_t *reset; +} igraph_i_pagerank_data2_t; + +static int igraph_i_pagerank(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_pagerank_data_t *data = extra; + igraph_adjlist_t *adjlist = data->adjlist; + igraph_vector_t *outdegree = data->outdegree; + igraph_vector_t *tmp = data->tmp; + igraph_vector_t *reset = data->reset; + igraph_vector_int_t *neis; + long int i, j, nlen; + igraph_real_t sumfrom = 0.0; + igraph_real_t fact = 1 - data->damping; + + /* Calculate p(x) / outdegree(x) in advance for all the vertices. + * Note that we may divide by zero here; this is intentional since + * we won't use those values and we save a comparison this way. + * At the same time, we calculate the global probability of a + * random jump in `sumfrom`. For vertices with no outgoing edges, + * we will surely jump from there if we are there, hence those + * vertices contribute p(x) to the teleportation probability. + * For vertices with some outgoing edges, we jump from there with + * probability `fact` if we are there, hence they contribute + * p(x)*fact */ + for (i = 0; i < n; i++) { + sumfrom += VECTOR(*outdegree)[i] != 0 ? from[i] * fact : from[i]; + VECTOR(*tmp)[i] = from[i] / VECTOR(*outdegree)[i]; + } + + /* Here we calculate the part of the `to` vector that results from + * moving along links (and not from teleportation) */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(adjlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + to[i] += VECTOR(*tmp)[nei]; + } + to[i] *= data->damping; + } + + /* Now we add the contribution from random jumps. `reset` is a vector + * that defines the probability of ending up in vertex i after a jump. + * `sumfrom` is the global probability of jumping as mentioned above. */ + /* printf("sumfrom = %.6f\n", (float)sumfrom); */ + + if (reset) { + /* Running personalized PageRank */ + for (i = 0; i < n; i++) { + to[i] += sumfrom * VECTOR(*reset)[i]; + } + } else { + /* Traditional PageRank with uniform reset vector */ + sumfrom /= n; + for (i = 0; i < n; i++) { + to[i] += sumfrom; + } + } + + return 0; +} + +static int igraph_i_pagerank2(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_pagerank_data2_t *data = extra; + const igraph_t *graph = data->graph; + igraph_inclist_t *inclist = data->inclist; + const igraph_vector_t *weights = data->weights; + igraph_vector_t *outdegree = data->outdegree; + igraph_vector_t *tmp = data->tmp; + igraph_vector_t *reset = data->reset; + long int i, j, nlen; + igraph_real_t sumfrom = 0.0; + igraph_vector_int_t *neis; + igraph_real_t fact = 1 - data->damping; + + /* + printf("PageRank weighted: multiplying vector: "); + for (i=0; idamping; + } + + /* printf("sumfrom = %.6f\n", (float)sumfrom); */ + + if (reset) { + /* Running personalized PageRank */ + for (i = 0; i < n; i++) { + to[i] += sumfrom * VECTOR(*reset)[i]; + } + } else { + /* Traditional PageRank with uniform reset vector */ + sumfrom /= n; + for (i = 0; i < n; i++) { + to[i] += sumfrom; + } + } + + /* + printf("PageRank weighted: multiplied vector: "); + for (i=0; i + * Please note that the PageRank of a given vertex depends on the PageRank + * of all other vertices, so even if you want to calculate the PageRank for + * only some of the vertices, all of them must be calculated. Requesting + * the PageRank for only some of the vertices does not result in any + * performance increase at all. + * + * + * + * For the explanation of the PageRank algorithm, see the following + * webpage: + * http://infolab.stanford.edu/~backrub/google.html , or the + * following reference: + * + * + * + * Sergey Brin and Larry Page: The Anatomy of a Large-Scale Hypertextual + * Web Search Engine. Proceedings of the 7th World-Wide Web Conference, + * Brisbane, Australia, April 1998. + * + * + * \param graph The graph object. + * \param algo The PageRank implementation to use. Possible values: + * \c IGRAPH_PAGERANK_ALGO_POWER, \c IGRAPH_PAGERANK_ALGO_ARPACK, + * \c IGRAPH_PAGERANK_ALGO_PRPACK. + * \param vector Pointer to an initialized vector, the result is + * stored here. It is resized as needed. + * \param value Pointer to a real variable, the eigenvalue + * corresponding to the PageRank vector is stored here. It should + * be always exactly one. + * \param vids The vertex ids for which the PageRank is returned. + * \param directed Boolean, whether to consider the directedness of + * the edges. This is ignored for undirected graphs. + * \param damping The damping factor ("d" in the original paper) + * \param weights Optional edge weights, it is either a null pointer, + * then the edges are not weighted, or a vector of the same length + * as the number of edges. + * \param options Options to the power method or ARPACK. For the power + * method, \c IGRAPH_PAGERANK_ALGO_POWER it must be a pointer to + * a \ref igraph_pagerank_power_options_t object. + * For \c IGRAPH_PAGERANK_ALGO_ARPACK it must be a pointer to an + * \ref igraph_arpack_options_t object. See \ref igraph_arpack_options_t + * for details. Note that the function overwrites the + * n (number of vertices), nev (1), + * ncv (3) and which (LM) parameters and + * it always starts the calculation from a non-random vector + * calculated based on the degree of the vertices. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex id in + * \p vids. + * + * Time complexity: depends on the input graph, usually it is O(|E|), + * the number of edges. + * + * \sa \ref igraph_pagerank_old() for the old implementation, + * \ref igraph_personalized_pagerank() and \ref igraph_personalized_pagerank_vs() + * for the personalized PageRank measure, \ref igraph_arpack_rssolve() and + * \ref igraph_arpack_rnsolve() for the underlying machinery. + * + * \example examples/simple/igraph_pagerank.c + */ + +int igraph_pagerank(const igraph_t *graph, igraph_pagerank_algo_t algo, + igraph_vector_t *vector, + igraph_real_t *value, const igraph_vs_t vids, + igraph_bool_t directed, igraph_real_t damping, + const igraph_vector_t *weights, void *options) { + return igraph_personalized_pagerank(graph, algo, vector, value, vids, + directed, damping, 0, weights, + options); +} + +/** + * \function igraph_personalized_pagerank_vs + * \brief Calculates the personalized Google PageRank for the specified vertices. + * + * The personalized PageRank is similar to the original PageRank measure, but the + * random walk is reset in every step with probability 1-damping to a non-uniform + * distribution (instead of the uniform distribution in the original PageRank measure. + * + * + * This simplified interface takes a vertex sequence and resets the random walk to + * one of the vertices in the specified vertex sequence, chosen uniformly. A typical + * application of personalized PageRank is when the random walk is reset to the same + * vertex every time - this can easily be achieved using \ref igraph_vss_1() which + * generates a vertex sequence containing only a single vertex. + * + * + * Please note that the personalized PageRank of a given vertex depends on the + * personalized PageRank of all other vertices, so even if you want to calculate + * the personalized PageRank for only some of the vertices, all of them must be + * calculated. Requesting the personalized PageRank for only some of the vertices + * does not result in any performance increase at all. + * + * + * + * \param graph The graph object. + * \param algo The PageRank implementation to use. Possible values: + * \c IGRAPH_PAGERANK_ALGO_POWER, \c IGRAPH_PAGERANK_ALGO_ARPACK, + * \c IGRAPH_PAGERANK_ALGO_PRPACK. + * \param vector Pointer to an initialized vector, the result is + * stored here. It is resized as needed. + * \param value Pointer to a real variable, the eigenvalue + * corresponding to the PageRank vector is stored here. It should + * be always exactly one. + * \param vids The vertex ids for which the PageRank is returned. + * \param directed Boolean, whether to consider the directedness of + * the edges. This is ignored for undirected graphs. + * \param damping The damping factor ("d" in the original paper) + * \param reset_vids IDs of the vertices used when resetting the random walk. + * \param weights Optional edge weights, it is either a null pointer, + * then the edges are not weighted, or a vector of the same length + * as the number of edges. + * \param options Options to the power method or ARPACK. For the power + * method, \c IGRAPH_PAGERANK_ALGO_POWER it must be a pointer to + * a \ref igraph_pagerank_power_options_t object. + * For \c IGRAPH_PAGERANK_ALGO_ARPACK it must be a pointer to an + * \ref igraph_arpack_options_t object. See \ref igraph_arpack_options_t + * for details. Note that the function overwrites the + * n (number of vertices), nev (1), + * ncv (3) and which (LM) parameters and + * it always starts the calculation from a non-random vector + * calculated based on the degree of the vertices. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex id in + * \p vids or an empty reset vertex sequence in + * \p vids_reset. + * + * Time complexity: depends on the input graph, usually it is O(|E|), + * the number of edges. + * + * \sa \ref igraph_pagerank() for the non-personalized implementation, + * \ref igraph_arpack_rssolve() and \ref igraph_arpack_rnsolve() for + * the underlying machinery. + */ + +int igraph_personalized_pagerank_vs(const igraph_t *graph, + igraph_pagerank_algo_t algo, igraph_vector_t *vector, + igraph_real_t *value, const igraph_vs_t vids, + igraph_bool_t directed, igraph_real_t damping, + igraph_vs_t reset_vids, + const igraph_vector_t *weights, + void *options) { + igraph_vector_t reset; + igraph_vit_t vit; + + IGRAPH_VECTOR_INIT_FINALLY(&reset, igraph_vcount(graph)); + IGRAPH_CHECK(igraph_vit_create(graph, reset_vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + while (!IGRAPH_VIT_END(vit)) { + VECTOR(reset)[(long int)IGRAPH_VIT_GET(vit)]++; + IGRAPH_VIT_NEXT(vit); + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_personalized_pagerank(graph, algo, vector, + value, vids, directed, + damping, &reset, weights, + options)); + + igraph_vector_destroy(&reset); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_personalized_pagerank + * \brief Calculates the personalized Google PageRank for the specified vertices. + * + * The personalized PageRank is similar to the original PageRank measure, but the + * random walk is reset in every step with probability 1-damping to a non-uniform + * distribution (instead of the uniform distribution in the original PageRank measure. + * + * + * Please note that the personalized PageRank of a given vertex depends on the + * personalized PageRank of all other vertices, so even if you want to calculate + * the personalized PageRank for only some of the vertices, all of them must be + * calculated. Requesting the personalized PageRank for only some of the vertices + * does not result in any performance increase at all. + * + * + * + * \param graph The graph object. + * \param algo The PageRank implementation to use. Possible values: + * \c IGRAPH_PAGERANK_ALGO_POWER, \c IGRAPH_PAGERANK_ALGO_ARPACK, + * \c IGRAPH_PAGERANK_ALGO_PRPACK. + * \param vector Pointer to an initialized vector, the result is + * stored here. It is resized as needed. + * \param value Pointer to a real variable, the eigenvalue + * corresponding to the PageRank vector is stored here. It should + * be always exactly one. + * \param vids The vertex ids for which the PageRank is returned. + * \param directed Boolean, whether to consider the directedness of + * the edges. This is ignored for undirected graphs. + * \param damping The damping factor ("d" in the original paper) + * \param reset The probability distribution over the vertices used when + * resetting the random walk. It is either a null pointer (denoting + * a uniform choice that results in the original PageRank measure) + * or a vector of the same length as the number of vertices. + * \param weights Optional edge weights, it is either a null pointer, + * then the edges are not weighted, or a vector of the same length + * as the number of edges. + * \param options Options to the power method or ARPACK. For the power + * method, \c IGRAPH_PAGERANK_ALGO_POWER it must be a pointer to + * a \ref igraph_pagerank_power_options_t object. + * For \c IGRAPH_PAGERANK_ALGO_ARPACK it must be a pointer to an + * \ref igraph_arpack_options_t object. See \ref igraph_arpack_options_t + * for details. Note that the function overwrites the + * n (number of vertices), nev (1), + * ncv (3) and which (LM) parameters and + * it always starts the calculation from a non-random vector + * calculated based on the degree of the vertices. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex id in + * \p vids or an invalid reset vector in \p reset. + * + * Time complexity: depends on the input graph, usually it is O(|E|), + * the number of edges. + * + * \sa \ref igraph_pagerank() for the non-personalized implementation, + * \ref igraph_arpack_rssolve() and \ref igraph_arpack_rnsolve() for + * the underlying machinery. + */ +int igraph_personalized_pagerank(const igraph_t *graph, + igraph_pagerank_algo_t algo, igraph_vector_t *vector, + igraph_real_t *value, const igraph_vs_t vids, + igraph_bool_t directed, igraph_real_t damping, + igraph_vector_t *reset, + const igraph_vector_t *weights, + void *options) { + + if (algo == IGRAPH_PAGERANK_ALGO_POWER) { + igraph_pagerank_power_options_t *o = + (igraph_pagerank_power_options_t *) options; + if (reset) { + IGRAPH_WARNING("Cannot use weights with power method, " + "weights will be ignored"); + } + return igraph_pagerank_old(graph, vector, vids, directed, + o->niter, o->eps, damping, + /*old=*/ 0); + } else if (algo == IGRAPH_PAGERANK_ALGO_ARPACK) { + igraph_arpack_options_t *o = (igraph_arpack_options_t*) options; + return igraph_personalized_pagerank_arpack(graph, vector, value, vids, + directed, damping, reset, + weights, o); + } else if (algo == IGRAPH_PAGERANK_ALGO_PRPACK) { + return igraph_personalized_pagerank_prpack(graph, vector, value, vids, + directed, damping, reset, + weights); + } else { + IGRAPH_ERROR("Unknown PageRank algorithm", IGRAPH_EINVAL); + } + + return 0; +} + +/* + * ARPACK-based implementation of \c igraph_personalized_pagerank. + * + * See \c igraph_personalized_pagerank for the documentation of the parameters. + */ +int igraph_personalized_pagerank_arpack(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, const igraph_vs_t vids, + igraph_bool_t directed, igraph_real_t damping, + igraph_vector_t *reset, + const igraph_vector_t *weights, + igraph_arpack_options_t *options) { + igraph_matrix_t values; + igraph_matrix_t vectors; + igraph_neimode_t dirmode; + igraph_vector_t outdegree; + igraph_vector_t indegree; + igraph_vector_t tmp; + + long int i; + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + + if (no_of_edges == 0) { + /* special case: empty graph */ + if (value) { + *value = 1.0; + } + if (vector) { + igraph_vector_resize(vector, no_of_nodes); + igraph_vector_fill(vector, 1.0 / no_of_nodes); + } + return IGRAPH_SUCCESS; + } + + options->n = (int) no_of_nodes; + options->nev = 1; + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rnsolve */ + options->which[0] = 'L'; options->which[1] = 'M'; + options->start = 1; /* no random start vector */ + + directed = directed && igraph_is_directed(graph); + + if (weights) { + igraph_real_t min, max; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid length of weights vector when calculating " + "PageRank scores", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_minmax(weights, &min, &max)); + if (min == 0 && max == 0) { + /* special case: all weights are zeros */ + if (value) { + *value = 1.0; + } + if (vector) { + igraph_vector_resize(vector, igraph_vcount(graph)); + igraph_vector_fill(vector, 1.0 / no_of_nodes); + } + return IGRAPH_SUCCESS; + } + } + + if (reset && igraph_vector_size(reset) != no_of_nodes) { + IGRAPH_ERROR("Invalid length of reset vector when calculating " + "personalized PageRank scores", IGRAPH_EINVAL); + } + + IGRAPH_MATRIX_INIT_FINALLY(&values, 0, 0); + IGRAPH_MATRIX_INIT_FINALLY(&vectors, options->n, 1); + + if (directed) { + dirmode = IGRAPH_IN; + } else { + dirmode = IGRAPH_ALL; + } + + IGRAPH_VECTOR_INIT_FINALLY(&indegree, options->n); + IGRAPH_VECTOR_INIT_FINALLY(&outdegree, options->n); + IGRAPH_VECTOR_INIT_FINALLY(&tmp, options->n); + + RNG_BEGIN(); + + if (reset) { + /* Normalize reset vector so the sum is 1 */ + double reset_sum; + if (igraph_vector_min(reset) < 0) { + IGRAPH_ERROR("the reset vector must not contain negative elements", IGRAPH_EINVAL); + } + reset_sum = igraph_vector_sum(reset); + if (reset_sum == 0) { + IGRAPH_ERROR("the sum of the elements in the reset vector must not be zero", IGRAPH_EINVAL); + } + igraph_vector_scale(reset, 1.0 / reset_sum); + } + + if (!weights) { + + igraph_adjlist_t adjlist; + igraph_i_pagerank_data_t data = { graph, &adjlist, damping, + &outdegree, &tmp, reset + }; + + IGRAPH_CHECK(igraph_degree(graph, &outdegree, igraph_vss_all(), + directed ? IGRAPH_OUT : IGRAPH_ALL, /*loops=*/ 0)); + IGRAPH_CHECK(igraph_degree(graph, &indegree, igraph_vss_all(), + directed ? IGRAPH_IN : IGRAPH_ALL, /*loops=*/ 0)); + /* Set up an appropriate starting vector. We start from the in-degrees + * plus some small random noise to avoid convergence problems */ + for (i = 0; i < options->n; i++) { + if (VECTOR(indegree)[i]) { + MATRIX(vectors, i, 0) = VECTOR(indegree)[i] + RNG_UNIF(-1e-4, 1e-4); + } else { + MATRIX(vectors, i, 0) = 1; + } + } + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, dirmode)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_arpack_rnsolve(igraph_i_pagerank, + &data, options, 0, &values, &vectors)); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + } else { + + igraph_inclist_t inclist; + igraph_bool_t negative_weight_warned = 0; + igraph_i_pagerank_data2_t data = { graph, &inclist, weights, + damping, &outdegree, &tmp, reset + }; + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, dirmode)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + /* Weighted degree */ + for (i = 0; i < no_of_edges; i++) { + long int from = IGRAPH_FROM(graph, i); + long int to = IGRAPH_TO(graph, i); + igraph_real_t weight = VECTOR(*weights)[i]; + if (weight < 0 && !negative_weight_warned) { + IGRAPH_WARNING("replacing negative weights with zeros"); + weight = 0; + negative_weight_warned = 1; + } + VECTOR(outdegree)[from] += weight; + VECTOR(indegree) [to] += weight; + if (!directed) { + VECTOR(outdegree)[to] += weight; + VECTOR(indegree) [from] += weight; + } + } + /* Set up an appropriate starting vector. We start from the in-degrees + * plus some small random noise to avoid convergence problems */ + for (i = 0; i < options->n; i++) { + if (VECTOR(indegree)[i]) { + MATRIX(vectors, i, 0) = VECTOR(indegree)[i] + RNG_UNIF(-1e-4, 1e-4); + } else { + MATRIX(vectors, i, 0) = 1; + } + } + + IGRAPH_CHECK(igraph_arpack_rnsolve(igraph_i_pagerank2, + &data, options, 0, &values, &vectors)); + + igraph_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(1); + } + + RNG_END(); + + igraph_vector_destroy(&tmp); + igraph_vector_destroy(&outdegree); + igraph_vector_destroy(&indegree); + IGRAPH_FINALLY_CLEAN(3); + + if (value) { + *value = MATRIX(values, 0, 0); + } + + if (vector) { + long int i; + igraph_vit_t vit; + long int nodes_to_calc; + igraph_real_t sum = 0; + + for (i = 0; i < no_of_nodes; i++) { + sum += MATRIX(vectors, i, 0); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_vector_resize(vector, nodes_to_calc)); + for (IGRAPH_VIT_RESET(vit), i = 0; !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + VECTOR(*vector)[i] = MATRIX(vectors, (long int)IGRAPH_VIT_GET(vit), 0); + VECTOR(*vector)[i] /= sum; + } + + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + } + + if (options->info) { + IGRAPH_WARNING("Non-zero return code from ARPACK routine!"); + } + + igraph_matrix_destroy(&vectors); + igraph_matrix_destroy(&values); + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/** + * \ingroup structural + * \function igraph_betweenness + * \brief Betweenness centrality of some vertices. + * + * + * The betweenness centrality of a vertex is the number of geodesics + * going through it. If there are more than one geodesic between two + * vertices, the value of these geodesics are weighted by one over the + * number of geodesics. + * \param graph The graph object. + * \param res The result of the computation, a vector containing the + * betweenness scores for the specified vertices. + * \param vids The vertices of which the betweenness centrality scores + * will be calculated. + * \param directed Logical, if true directed paths will be considered + * for directed graphs. It is ignored for undirected graphs. + * \param weights An optional vector containing edge weights for + * calculating weighted betweenness. Supply a null pointer here + * for unweighted betweenness. + * \param nobigint Logical, if true, then we don't use big integers + * for the calculation, setting this to 1 (=true) should + * work for most graphs. It is currently ignored for weighted + * graphs. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex id passed in + * \p vids. + * + * Time complexity: O(|V||E|), + * |V| and + * |E| are the number of vertices and + * edges in the graph. + * Note that the time complexity is independent of the number of + * vertices for which the score is calculated. + * + * \sa Other centrality types: \ref igraph_degree(), \ref igraph_closeness(). + * See \ref igraph_edge_betweenness() for calculating the betweenness score + * of the edges in a graph. See \ref igraph_betweenness_estimate() to + * estimate the betweenness score of the vertices in a graph. + * + * \example examples/simple/igraph_betweenness.c + */ +int igraph_betweenness(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_bool_t directed, + const igraph_vector_t* weights, igraph_bool_t nobigint) { + return igraph_betweenness_estimate(graph, res, vids, directed, -1, weights, + nobigint); +} + +static int igraph_i_betweenness_estimate_weighted( + const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_bool_t directed, + igraph_real_t cutoff, + const igraph_vector_t *weights, + igraph_bool_t nobigint) { + + igraph_real_t minweight; + igraph_integer_t no_of_nodes = (igraph_integer_t) igraph_vcount(graph); + igraph_integer_t no_of_edges = (igraph_integer_t) igraph_ecount(graph); + igraph_2wheap_t Q; + igraph_inclist_t inclist; + igraph_adjlist_t fathers; + long int source, j; + igraph_stack_t S; + igraph_neimode_t mode = directed ? IGRAPH_OUT : IGRAPH_ALL; + igraph_vector_t dist, nrgeo, tmpscore; + igraph_vector_t v_tmpres, *tmpres = &v_tmpres; + igraph_vit_t vit; + int cmp_result; + const double eps = IGRAPH_SHORTEST_PATH_EPSILON; + + IGRAPH_UNUSED(nobigint); + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Weight vector length does not match", IGRAPH_EINVAL); + } + minweight = igraph_vector_min(weights); + if (minweight <= 0) { + IGRAPH_ERROR("Weight vector must be positive", IGRAPH_EINVAL); + } else if (minweight <= eps) { + IGRAPH_WARNING("Some weights are smaller than epsilon, calculations may suffer from numerical precision."); + } + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, mode)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + IGRAPH_CHECK(igraph_adjlist_init_empty(&fathers, no_of_nodes)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &fathers); + + IGRAPH_CHECK(igraph_stack_init(&S, no_of_nodes)); + IGRAPH_FINALLY(igraph_stack_destroy, &S); + IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&tmpscore, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&nrgeo, no_of_nodes); + + if (igraph_vs_is_all(&vids)) { + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + tmpres = res; + } else { + IGRAPH_VECTOR_INIT_FINALLY(tmpres, no_of_nodes); + } + + for (source = 0; source < no_of_nodes; source++) { + IGRAPH_PROGRESS("Betweenness centrality: ", 100.0 * source / no_of_nodes, 0); + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_2wheap_push_with_index(&Q, source, -1.0); + VECTOR(dist)[source] = 1.0; + VECTOR(nrgeo)[source] = 1; + + while (!igraph_2wheap_empty(&Q)) { + long int minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_delete_max(&Q); + igraph_vector_int_t *neis; + long int nlen; + + igraph_stack_push(&S, minnei); + if (cutoff > 0 && VECTOR(dist)[minnei] >= cutoff + 1.0) { + continue; + } + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_inclist_get(&inclist, minnei); + nlen = igraph_vector_int_size(neis); + for (j = 0; j < nlen; j++) { + long int edge = (long int) VECTOR(*neis)[j]; + long int to = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = VECTOR(dist)[to]; + + if (curdist == 0) { + /* this means curdist is infinity */ + cmp_result = -1; + } else { + cmp_result = igraph_cmp_epsilon(altdist, curdist, eps); + } + + if (curdist == 0) { + /* This is the first non-infinite distance */ + igraph_vector_int_t *v = igraph_adjlist_get(&fathers, to); + igraph_vector_int_resize(v, 1); + VECTOR(*v)[0] = minnei; + VECTOR(nrgeo)[to] = VECTOR(nrgeo)[minnei]; + + VECTOR(dist)[to] = altdist; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, to, -altdist)); + } else if (cmp_result < 0) { + /* This is a shorter path */ + igraph_vector_int_t *v = igraph_adjlist_get(&fathers, to); + igraph_vector_int_resize(v, 1); + VECTOR(*v)[0] = minnei; + VECTOR(nrgeo)[to] = VECTOR(nrgeo)[minnei]; + + VECTOR(dist)[to] = altdist; + IGRAPH_CHECK(igraph_2wheap_modify(&Q, to, -altdist)); + } else if (cmp_result == 0) { + igraph_vector_int_t *v = igraph_adjlist_get(&fathers, to); + igraph_vector_int_push_back(v, minnei); + VECTOR(nrgeo)[to] += VECTOR(nrgeo)[minnei]; + } + } + + } /* !igraph_2wheap_empty(&Q) */ + + while (!igraph_stack_empty(&S)) { + long int w = (long int) igraph_stack_pop(&S); + igraph_vector_int_t *fatv = igraph_adjlist_get(&fathers, w); + long int fatv_len = igraph_vector_int_size(fatv); + for (j = 0; j < fatv_len; j++) { + long int f = (long int) VECTOR(*fatv)[j]; + VECTOR(tmpscore)[f] += VECTOR(nrgeo)[f] / VECTOR(nrgeo)[w] * (1 + VECTOR(tmpscore)[w]); + } + if (w != source) { + VECTOR(*tmpres)[w] += VECTOR(tmpscore)[w]; + } + + VECTOR(tmpscore)[w] = 0; + VECTOR(dist)[w] = 0; + VECTOR(nrgeo)[w] = 0; + igraph_vector_int_clear(igraph_adjlist_get(&fathers, w)); + } + + } /* source < no_of_nodes */ + + if (!igraph_vs_is_all(&vids)) { + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_VIT_SIZE(vit))); + + for (j = 0, IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), j++) { + long int node = IGRAPH_VIT_GET(vit); + VECTOR(*res)[j] = VECTOR(*tmpres)[node]; + } + + no_of_nodes = (igraph_integer_t) j; + + igraph_vit_destroy(&vit); + igraph_vector_destroy(tmpres); + IGRAPH_FINALLY_CLEAN(2); + } + + if (!directed || !igraph_is_directed(graph)) { + for (j = 0; j < no_of_nodes; j++) { + VECTOR(*res)[j] /= 2.0; + } + } + + IGRAPH_PROGRESS("Betweenness centrality: ", 100.0, 0); + + igraph_vector_destroy(&nrgeo); + igraph_vector_destroy(&tmpscore); + igraph_vector_destroy(&dist); + igraph_stack_destroy(&S); + igraph_adjlist_destroy(&fathers); + igraph_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + IGRAPH_FINALLY_CLEAN(7); + + return 0; +} + +static void igraph_i_destroy_biguints(igraph_biguint_t *p) { + igraph_biguint_t *p2 = p; + while ( *((long int*)(p)) ) { + igraph_biguint_destroy(p); + p++; + } + igraph_Free(p2); +} + +/** + * \ingroup structural + * \function igraph_betweenness_estimate + * \brief Estimated betweenness centrality of some vertices. + * + * + * The betweenness centrality of a vertex is the number of geodesics + * going through it. If there are more than one geodesic between two + * vertices, the value of these geodesics are weighted by one over the + * number of geodesics. When estimating betweenness centrality, igraph + * takes into consideration only those paths that are shorter than or + * equal to a prescribed length. Note that the estimated centrality + * will always be less than the real one. + * + * \param graph The graph object. + * \param res The result of the computation, a vector containing the + * estimated betweenness scores for the specified vertices. + * \param vids The vertices of which the betweenness centrality scores + * will be estimated. + * \param directed Logical, if true directed paths will be considered + * for directed graphs. It is ignored for undirected graphs. + * \param cutoff The maximal length of paths that will be considered. + * If zero or negative, the exact betweenness will be calculated + * (no upper limit on path lengths). + * \param weights An optional vector containing edge weights for + * calculating weighted betweenness. Supply a null pointer here + * for unweighted betweenness. + * \param nobigint Logical, if true, then we don't use big integers + * for the calculation, setting this to 1 (=true) should + * work for most graphs. It is currently ignored for weighted + * graphs. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex id passed in + * \p vids. + * + * Time complexity: O(|V||E|), + * |V| and + * |E| are the number of vertices and + * edges in the graph. + * Note that the time complexity is independent of the number of + * vertices for which the score is calculated. + * + * \sa Other centrality types: \ref igraph_degree(), \ref igraph_closeness(). + * See \ref igraph_edge_betweenness() for calculating the betweenness score + * of the edges in a graph. + */ +int igraph_betweenness_estimate(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_bool_t directed, + igraph_real_t cutoff, + const igraph_vector_t *weights, + igraph_bool_t nobigint) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + long int *distance; + unsigned long long int *nrgeo = 0; /* must be long long; consider grid + graphs for example */ + igraph_biguint_t *big_nrgeo = 0; + double *tmpscore; + igraph_stack_t stack = IGRAPH_STACK_NULL; + long int source; + long int j, k, nneis; + igraph_vector_int_t *neis; + igraph_vector_t v_tmpres, *tmpres = &v_tmpres; + igraph_vit_t vit; + + igraph_adjlist_t adjlist_out, adjlist_in; + igraph_adjlist_t *adjlist_out_p, *adjlist_in_p; + + igraph_biguint_t D, R, T; + + if (weights) { + return igraph_i_betweenness_estimate_weighted(graph, res, vids, directed, + cutoff, weights, nobigint); + } + + if (!igraph_vs_is_all(&vids)) { + /* subset */ + IGRAPH_VECTOR_INIT_FINALLY(tmpres, no_of_nodes); + } else { + /* only */ + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + tmpres = res; + } + + directed = directed && igraph_is_directed(graph); + if (directed) { + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist_out, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist_out); + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist_in, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist_in); + adjlist_out_p = &adjlist_out; + adjlist_in_p = &adjlist_in; + } else { + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist_out, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist_out); + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist_in, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist_in); + adjlist_out_p = &adjlist_out; + adjlist_in_p = &adjlist_in; + } + for (j = 0; j < no_of_nodes; j++) { + igraph_vector_int_clear(igraph_adjlist_get(adjlist_in_p, j)); + } + + distance = igraph_Calloc(no_of_nodes, long int); + if (distance == 0) { + IGRAPH_ERROR("betweenness failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, distance); + if (nobigint) { + nrgeo = igraph_Calloc(no_of_nodes, unsigned long long int); + if (nrgeo == 0) { + IGRAPH_ERROR("betweenness failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, nrgeo); + } else { + /* +1 is to have one containing zeros, when we free it, we stop + at the zero */ + big_nrgeo = igraph_Calloc(no_of_nodes + 1, igraph_biguint_t); + if (!big_nrgeo) { + IGRAPH_ERROR("betweenness failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_i_destroy_biguints, big_nrgeo); + for (j = 0; j < no_of_nodes; j++) { + IGRAPH_CHECK(igraph_biguint_init(&big_nrgeo[j])); + } + IGRAPH_CHECK(igraph_biguint_init(&D)); + IGRAPH_FINALLY(igraph_biguint_destroy, &D); + IGRAPH_CHECK(igraph_biguint_init(&R)); + IGRAPH_FINALLY(igraph_biguint_destroy, &R); + IGRAPH_CHECK(igraph_biguint_init(&T)); + IGRAPH_FINALLY(igraph_biguint_destroy, &T); + } + tmpscore = igraph_Calloc(no_of_nodes, double); + if (tmpscore == 0) { + IGRAPH_ERROR("betweenness failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmpscore); + + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + igraph_stack_init(&stack, no_of_nodes); + IGRAPH_FINALLY(igraph_stack_destroy, &stack); + + /* here we go */ + + for (source = 0; source < no_of_nodes; source++) { + IGRAPH_PROGRESS("Betweenness centrality: ", 100.0 * source / no_of_nodes, 0); + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_dqueue_push(&q, source)); + if (nobigint) { + nrgeo[source] = 1; + } else { + igraph_biguint_set_limb(&big_nrgeo[source], 1); + } + distance[source] = 1; + + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + IGRAPH_CHECK(igraph_stack_push(&stack, actnode)); + + if (cutoff > 0 && distance[actnode] >= cutoff + 1) { + continue; + } + + neis = igraph_adjlist_get(adjlist_out_p, actnode); + nneis = igraph_vector_int_size(neis); + for (j = 0; j < nneis; j++) { + long int neighbor = (long int) VECTOR(*neis)[j]; + if (distance[neighbor] == 0) { + distance[neighbor] = distance[actnode] + 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + } + if (distance[neighbor] == distance[actnode] + 1) { + igraph_vector_int_t *v = igraph_adjlist_get(adjlist_in_p, + neighbor); + igraph_vector_int_push_back(v, actnode); + if (nobigint) { + nrgeo[neighbor] += nrgeo[actnode]; + } else { + IGRAPH_CHECK(igraph_biguint_add(&big_nrgeo[neighbor], + &big_nrgeo[neighbor], + &big_nrgeo[actnode])); + } + } + } + } /* while !igraph_dqueue_empty */ + + /* Ok, we've the distance of each node and also the number of + shortest paths to them. Now we do an inverse search, starting + with the farthest nodes. */ + while (!igraph_stack_empty(&stack)) { + long int actnode = (long int) igraph_stack_pop(&stack); + neis = igraph_adjlist_get(adjlist_in_p, actnode); + nneis = igraph_vector_int_size(neis); + for (j = 0; j < nneis; j++) { + long int neighbor = (long int) VECTOR(*neis)[j]; + if (nobigint) { + tmpscore[neighbor] += (tmpscore[actnode] + 1) * + ((double)(nrgeo[neighbor])) / nrgeo[actnode]; + } else { + if (!igraph_biguint_compare_limb(&big_nrgeo[actnode], 0)) { + tmpscore[neighbor] = IGRAPH_INFINITY; + } else { + double div; + limb_t shift = 1000000000L; + IGRAPH_CHECK(igraph_biguint_mul_limb(&T, &big_nrgeo[neighbor], + shift)); + igraph_biguint_div(&D, &R, &T, &big_nrgeo[actnode]); + div = igraph_biguint_get(&D) / shift; + tmpscore[neighbor] += (tmpscore[actnode] + 1) * div; + } + } + } + + if (actnode != source) { + VECTOR(*tmpres)[actnode] += tmpscore[actnode]; + } + + distance[actnode] = 0; + if (nobigint) { + nrgeo[actnode] = 0; + } else { + igraph_biguint_set_limb(&big_nrgeo[actnode], 0); + } + tmpscore[actnode] = 0; + igraph_vector_int_clear(igraph_adjlist_get(adjlist_in_p, actnode)); + } + + } /* for source < no_of_nodes */ + + IGRAPH_PROGRESS("Betweenness centrality: ", 100.0, 0); + + /* clean */ + igraph_Free(distance); + if (nobigint) { + igraph_Free(nrgeo); + } else { + igraph_biguint_destroy(&T); + igraph_biguint_destroy(&R); + igraph_biguint_destroy(&D); + IGRAPH_FINALLY_CLEAN(3); + igraph_i_destroy_biguints(big_nrgeo); + } + igraph_Free(tmpscore); + + igraph_dqueue_destroy(&q); + igraph_stack_destroy(&stack); + IGRAPH_FINALLY_CLEAN(5); + + /* Keep only the requested vertices */ + if (!igraph_vs_is_all(&vids)) { + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_VIT_SIZE(vit))); + + for (k = 0, IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), k++) { + long int node = IGRAPH_VIT_GET(vit); + VECTOR(*res)[k] = VECTOR(*tmpres)[node]; + } + + igraph_vit_destroy(&vit); + igraph_vector_destroy(tmpres); + IGRAPH_FINALLY_CLEAN(2); + } + + /* divide by 2 for undirected graph */ + if (!directed) { + nneis = igraph_vector_size(res); + for (j = 0; j < nneis; j++) { + VECTOR(*res)[j] /= 2.0; + } + } + + igraph_adjlist_destroy(&adjlist_out); + igraph_adjlist_destroy(&adjlist_in); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +static int igraph_i_edge_betweenness_estimate_weighted( + const igraph_t *graph, + igraph_vector_t *result, + igraph_bool_t directed, + igraph_real_t cutoff, + const igraph_vector_t *weights) { + + igraph_real_t minweight; + igraph_integer_t no_of_nodes = (igraph_integer_t) igraph_vcount(graph); + igraph_integer_t no_of_edges = (igraph_integer_t) igraph_ecount(graph); + igraph_2wheap_t Q; + igraph_inclist_t inclist; + igraph_inclist_t fathers; + igraph_neimode_t mode = directed ? IGRAPH_OUT : IGRAPH_ALL; + igraph_vector_t distance, tmpscore; + igraph_vector_long_t nrgeo; + long int source, j; + int cmp_result; + const double eps = IGRAPH_SHORTEST_PATH_EPSILON; + igraph_stack_t S; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Weight vector length does not match", IGRAPH_EINVAL); + } + minweight = igraph_vector_min(weights); + if (minweight <= 0) { + IGRAPH_ERROR("Weight vector must be positive", IGRAPH_EINVAL); + } else if (minweight <= eps) { + IGRAPH_WARNING("Some weights are smaller than epsilon, calculations may suffer from numerical precision."); + } + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, mode)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + IGRAPH_CHECK(igraph_inclist_init_empty(&fathers, no_of_nodes)); + IGRAPH_FINALLY(igraph_inclist_destroy, &fathers); + + IGRAPH_VECTOR_INIT_FINALLY(&distance, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&tmpscore, no_of_nodes); + IGRAPH_CHECK(igraph_vector_long_init(&nrgeo, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &nrgeo); + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_stack_init(&S, no_of_nodes)); + IGRAPH_FINALLY(igraph_stack_destroy, &S); + + IGRAPH_CHECK(igraph_vector_resize(result, no_of_edges)); + igraph_vector_null(result); + + for (source = 0; source < no_of_nodes; source++) { + IGRAPH_PROGRESS("Edge betweenness centrality: ", 100.0 * source / no_of_nodes, 0); + IGRAPH_ALLOW_INTERRUPTION(); + + /* printf("source: %li\n", source); */ + + igraph_vector_null(&distance); + igraph_vector_null(&tmpscore); + igraph_vector_long_null(&nrgeo); + + igraph_2wheap_push_with_index(&Q, source, -1.0); + VECTOR(distance)[source] = 1.0; + VECTOR(nrgeo)[source] = 1; + + while (!igraph_2wheap_empty(&Q)) { + long int minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_delete_max(&Q); + igraph_vector_int_t *neis; + long int nlen; + + /* printf("SP to %li is final, dist: %g, nrgeo: %li\n", minnei, */ + /* VECTOR(distance)[minnei]-1.0, VECTOR(nrgeo)[minnei]); */ + + igraph_stack_push(&S, minnei); + + if (cutoff > 0 && VECTOR(distance)[minnei] >= cutoff + 1.0) { + continue; + } + + neis = igraph_inclist_get(&inclist, minnei); + nlen = igraph_vector_int_size(neis); + for (j = 0; j < nlen; j++) { + long int edge = (long int) VECTOR(*neis)[j]; + long int to = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = VECTOR(distance)[to]; + + if (curdist == 0) { + /* this means curdist is infinity */ + cmp_result = -1; + } else { + cmp_result = igraph_cmp_epsilon(altdist, curdist, eps); + } + + /* printf("to=%ld, altdist = %lg, curdist = %lg, cmp = %d\n", + to, altdist, curdist-1, cmp_result); */ + if (curdist == 0) { + /* This is the first finite distance to 'to' */ + igraph_vector_int_t *v = igraph_inclist_get(&fathers, to); + /* printf("Found first path to %li (from %li)\n", to, minnei); */ + igraph_vector_int_resize(v, 1); + VECTOR(*v)[0] = edge; + VECTOR(nrgeo)[to] = VECTOR(nrgeo)[minnei]; + VECTOR(distance)[to] = altdist; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, to, -altdist)); + } else if (cmp_result < 0) { + /* This is a shorter path */ + igraph_vector_int_t *v = igraph_inclist_get(&fathers, to); + /* printf("Found a shorter path to %li (from %li)\n", to, minnei); */ + igraph_vector_int_resize(v, 1); + VECTOR(*v)[0] = edge; + VECTOR(nrgeo)[to] = VECTOR(nrgeo)[minnei]; + VECTOR(distance)[to] = altdist; + IGRAPH_CHECK(igraph_2wheap_modify(&Q, to, -altdist)); + } else if (cmp_result == 0) { + igraph_vector_int_t *v = igraph_inclist_get(&fathers, to); + /* printf("Found a second SP to %li (from %li)\n", to, minnei); */ + igraph_vector_int_push_back(v, edge); + VECTOR(nrgeo)[to] += VECTOR(nrgeo)[minnei]; + } + } + + } /* igraph_2wheap_empty(&Q) */ + + while (!igraph_stack_empty(&S)) { + long int w = (long int) igraph_stack_pop(&S); + igraph_vector_int_t *fatv = igraph_inclist_get(&fathers, w); + long int fatv_len = igraph_vector_int_size(fatv); + /* printf("Popping %li.\n", w); */ + for (j = 0; j < fatv_len; j++) { + long int fedge = (long int) VECTOR(*fatv)[j]; + long int neighbor = IGRAPH_OTHER(graph, fedge, w); + VECTOR(tmpscore)[neighbor] += ((double)VECTOR(nrgeo)[neighbor]) / + VECTOR(nrgeo)[w] * (1.0 + VECTOR(tmpscore)[w]); + /* printf("Scoring %li (edge %li)\n", neighbor, fedge); */ + VECTOR(*result)[fedge] += + ((VECTOR(tmpscore)[w] + 1) * VECTOR(nrgeo)[neighbor]) / + VECTOR(nrgeo)[w]; + } + + VECTOR(tmpscore)[w] = 0; + VECTOR(distance)[w] = 0; + VECTOR(nrgeo)[w] = 0; + igraph_vector_int_clear(fatv); + } + + } /* source < no_of_nodes */ + + if (!directed || !igraph_is_directed(graph)) { + for (j = 0; j < no_of_edges; j++) { + VECTOR(*result)[j] /= 2.0; + } + } + + IGRAPH_PROGRESS("Edge betweenness centrality: ", 100.0, 0); + + igraph_stack_destroy(&S); + igraph_2wheap_destroy(&Q); + IGRAPH_FINALLY_CLEAN(2); + + igraph_inclist_destroy(&inclist); + igraph_inclist_destroy(&fathers); + igraph_vector_destroy(&distance); + igraph_vector_destroy(&tmpscore); + igraph_vector_long_destroy(&nrgeo); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +/** + * \ingroup structural + * \function igraph_edge_betweenness + * \brief Betweenness centrality of the edges. + * + * + * The betweenness centrality of an edge is the number of geodesics + * going through it. If there are more than one geodesics between two + * vertices, the value of these geodesics are weighted by one over the + * number of geodesics. + * \param graph The graph object. + * \param result The result of the computation, vector containing the + * betweenness scores for the edges. + * \param directed Logical, if true directed paths will be considered + * for directed graphs. It is ignored for undirected graphs. + * \param weights An optional weight vector for weighted edge + * betweenness. Supply a null pointer here for the unweighted + * version. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * + * Time complexity: O(|V||E|), + * |V| and + * |E| are the number of vertices and + * edges in the graph. + * + * \sa Other centrality types: \ref igraph_degree(), \ref igraph_closeness(). + * See \ref igraph_edge_betweenness() for calculating the betweenness score + * of the edges in a graph. See \ref igraph_edge_betweenness_estimate() to + * estimate the betweenness score of the edges in a graph. + * + * \example examples/simple/igraph_edge_betweenness.c + */ +int igraph_edge_betweenness(const igraph_t *graph, igraph_vector_t *result, + igraph_bool_t directed, + const igraph_vector_t *weights) { + return igraph_edge_betweenness_estimate(graph, result, directed, -1, + weights); +} + +/** + * \ingroup structural + * \function igraph_edge_betweenness_estimate + * \brief Estimated betweenness centrality of the edges. + * + * + * The betweenness centrality of an edge is the number of geodesics + * going through it. If there are more than one geodesics between two + * vertices, the value of these geodesics are weighted by one over the + * number of geodesics. When estimating betweenness centrality, igraph + * takes into consideration only those paths that are shorter than or + * equal to a prescribed length. Note that the estimated centrality + * will always be less than the real one. + * \param graph The graph object. + * \param result The result of the computation, vector containing the + * betweenness scores for the edges. + * \param directed Logical, if true directed paths will be considered + * for directed graphs. It is ignored for undirected graphs. + * \param cutoff The maximal length of paths that will be considered. + * If zero or negative, the exact betweenness will be calculated + * (no upper limit on path lengths). + * \param weights An optional weight vector for weighted + * betweenness. Supply a null pointer here for unweighted + * betweenness. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * + * Time complexity: O(|V||E|), + * |V| and + * |E| are the number of vertices and + * edges in the graph. + * + * \sa Other centrality types: \ref igraph_degree(), \ref igraph_closeness(). + * See \ref igraph_betweenness() for calculating the betweenness score + * of the vertices in a graph. + */ +int igraph_edge_betweenness_estimate(const igraph_t *graph, igraph_vector_t *result, + igraph_bool_t directed, igraph_real_t cutoff, + const igraph_vector_t *weights) { + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + long int *distance; + unsigned long long int *nrgeo; + double *tmpscore; + igraph_stack_t stack = IGRAPH_STACK_NULL; + long int source; + long int j; + + igraph_inclist_t elist_out, elist_in; + igraph_inclist_t *elist_out_p, *elist_in_p; + igraph_vector_int_t *neip; + long int neino; + long int i; + + if (weights) { + return igraph_i_edge_betweenness_estimate_weighted(graph, result, + directed, cutoff, weights); + } + + directed = directed && igraph_is_directed(graph); + if (directed) { + IGRAPH_CHECK(igraph_inclist_init(graph, &elist_out, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_inclist_destroy, &elist_out); + IGRAPH_CHECK(igraph_inclist_init(graph, &elist_in, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_inclist_destroy, &elist_in); + elist_out_p = &elist_out; + elist_in_p = &elist_in; + } else { + IGRAPH_CHECK(igraph_inclist_init(graph, &elist_out, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &elist_out); + elist_out_p = elist_in_p = &elist_out; + } + + distance = igraph_Calloc(no_of_nodes, long int); + if (distance == 0) { + IGRAPH_ERROR("edge betweenness failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, distance); + nrgeo = igraph_Calloc(no_of_nodes, unsigned long long int); + if (nrgeo == 0) { + IGRAPH_ERROR("edge betweenness failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, nrgeo); + tmpscore = igraph_Calloc(no_of_nodes, double); + if (tmpscore == 0) { + IGRAPH_ERROR("edge betweenness failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmpscore); + + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_stack_init(&stack, no_of_nodes)); + IGRAPH_FINALLY(igraph_stack_destroy, &stack); + + IGRAPH_CHECK(igraph_vector_resize(result, no_of_edges)); + + igraph_vector_null(result); + + /* here we go */ + + for (source = 0; source < no_of_nodes; source++) { + IGRAPH_PROGRESS("Edge betweenness centrality: ", 100.0 * source / no_of_nodes, 0); + IGRAPH_ALLOW_INTERRUPTION(); + + memset(distance, 0, (size_t) no_of_nodes * sizeof(long int)); + memset(nrgeo, 0, (size_t) no_of_nodes * sizeof(unsigned long long int)); + memset(tmpscore, 0, (size_t) no_of_nodes * sizeof(double)); + igraph_stack_clear(&stack); /* it should be empty anyway... */ + + IGRAPH_CHECK(igraph_dqueue_push(&q, source)); + + nrgeo[source] = 1; + distance[source] = 0; + + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + + if (cutoff > 0 && distance[actnode] >= cutoff ) { + continue; + } + + /* check the neighbors and add to them to the queue if unseen before */ + neip = igraph_inclist_get(elist_out_p, actnode); + neino = igraph_vector_int_size(neip); + for (i = 0; i < neino; i++) { + igraph_integer_t edge = (igraph_integer_t) VECTOR(*neip)[i], from, to; + long int neighbor; + igraph_edge(graph, edge, &from, &to); + neighbor = actnode != from ? from : to; + if (nrgeo[neighbor] != 0) { + /* we've already seen this node, another shortest path? */ + if (distance[neighbor] == distance[actnode] + 1) { + nrgeo[neighbor] += nrgeo[actnode]; + } + } else { + /* we haven't seen this node yet */ + nrgeo[neighbor] += nrgeo[actnode]; + distance[neighbor] = distance[actnode] + 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + IGRAPH_CHECK(igraph_stack_push(&stack, neighbor)); + } + } + } /* while !igraph_dqueue_empty */ + + /* Ok, we've the distance of each node and also the number of + shortest paths to them. Now we do an inverse search, starting + with the farthest nodes. */ + while (!igraph_stack_empty(&stack)) { + long int actnode = (long int) igraph_stack_pop(&stack); + if (distance[actnode] < 1) { + continue; /* skip source node */ + } + + /* set the temporary score of the friends */ + neip = igraph_inclist_get(elist_in_p, actnode); + neino = igraph_vector_int_size(neip); + for (i = 0; i < neino; i++) { + igraph_integer_t from, to; + long int neighbor; + igraph_integer_t edgeno = (igraph_integer_t) VECTOR(*neip)[i]; + igraph_edge(graph, edgeno, &from, &to); + neighbor = actnode != from ? from : to; + if (distance[neighbor] == distance[actnode] - 1 && + nrgeo[neighbor] != 0) { + tmpscore[neighbor] += + (tmpscore[actnode] + 1) * nrgeo[neighbor] / nrgeo[actnode]; + VECTOR(*result)[edgeno] += + (tmpscore[actnode] + 1) * nrgeo[neighbor] / nrgeo[actnode]; + } + } + } + /* Ok, we've the scores for this source */ + } /* for source <= no_of_nodes */ + IGRAPH_PROGRESS("Edge betweenness centrality: ", 100.0, 0); + + /* clean and return */ + igraph_Free(distance); + igraph_Free(nrgeo); + igraph_Free(tmpscore); + igraph_dqueue_destroy(&q); + igraph_stack_destroy(&stack); + IGRAPH_FINALLY_CLEAN(5); + + if (directed) { + igraph_inclist_destroy(&elist_out); + igraph_inclist_destroy(&elist_in); + IGRAPH_FINALLY_CLEAN(2); + } else { + igraph_inclist_destroy(&elist_out); + IGRAPH_FINALLY_CLEAN(1); + } + + /* divide by 2 for undirected graph */ + if (!directed || !igraph_is_directed(graph)) { + for (j = 0; j < igraph_vector_size(result); j++) { + VECTOR(*result)[j] /= 2.0; + } + } + + return 0; +} + +/** + * \ingroup structural + * \function igraph_closeness + * \brief Closeness centrality calculations for some vertices. + * + * + * The closeness centrality of a vertex measures how easily other + * vertices can be reached from it (or the other way: how easily it + * can be reached from the other vertices). It is defined as + * the number of vertices minus one divided by the sum of the + * lengths of all geodesics from/to the given vertex. + * + * + * If the graph is not connected, and there is no path between two + * vertices, the number of vertices is used instead the length of the + * geodesic. This is longer than the longest possible geodesic in case + * of unweighted graphs, but may not be so in weighted graphs, so it is + * best not to use this function on weighted graphs. + * + * + * If the graph has a single vertex only, the closeness centrality of + * that single vertex will be NaN (because we are essentially dividing + * zero with zero). + * + * \param graph The graph object. + * \param res The result of the computation, a vector containing the + * closeness centrality scores for the given vertices. + * \param vids The vertices for which the closeness centrality will be computed. + * \param mode The type of shortest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the lengths of the outgoing paths are calculated. + * \cli IGRAPH_IN + * the lengths of the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param weights An optional vector containing edge weights for + * weighted closeness. Supply a null pointer here for + * traditional, unweighted closeness. + * \param normalized Boolean, whether to normalize results by multiplying + * by the number of vertices minus one. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex id passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(n|E|), + * n is the number + * of vertices for which the calculation is done and + * |E| is the number + * of edges in the graph. + * + * \sa Other centrality types: \ref igraph_degree(), \ref igraph_betweenness(). + * See \ref igraph_closeness_estimate() to estimate closeness values. + */ +int igraph_closeness(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_neimode_t mode, + const igraph_vector_t *weights, + igraph_bool_t normalized) { + return igraph_closeness_estimate(graph, res, vids, mode, -1, weights, + normalized); +} + +static int igraph_i_closeness_estimate_weighted(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_neimode_t mode, + igraph_real_t cutoff, + const igraph_vector_t *weights, + igraph_bool_t normalized) { + + /* See igraph_shortest_paths_dijkstra() for the implementation + details and the dirty tricks. */ + + igraph_real_t minweight; + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + + igraph_2wheap_t Q; + igraph_vit_t vit; + long int nodes_to_calc; + + igraph_lazy_inclist_t inclist; + long int i, j; + + igraph_vector_t dist; + igraph_vector_long_t which; + long int nodes_reached; + + int cmp_result; + const double eps = IGRAPH_SHORTEST_PATH_EPSILON; + igraph_real_t mindist; + + igraph_bool_t warning_shown = 0; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + minweight = igraph_vector_min(weights); + if (minweight <= 0) { + IGRAPH_ERROR("Weight vector must be positive", IGRAPH_EINVAL); + } else if (minweight <= eps) { + IGRAPH_WARNING("Some weights are smaller than epsilon, calculations may suffer from numerical precision."); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes); + IGRAPH_CHECK(igraph_vector_long_init(&which, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &which); + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + igraph_vector_null(res); + + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + + long int source = IGRAPH_VIT_GET(vit); + igraph_2wheap_clear(&Q); + igraph_2wheap_push_with_index(&Q, source, -1.0); + VECTOR(which)[source] = i + 1; + VECTOR(dist)[source] = 1.0; /* actual distance is zero but we need to store distance + 1 */ + nodes_reached = 0; + + while (!igraph_2wheap_empty(&Q)) { + igraph_integer_t minnei = (igraph_integer_t) igraph_2wheap_max_index(&Q); + /* Now check all neighbors of minnei for a shorter path */ + igraph_vector_t *neis = igraph_lazy_inclist_get(&inclist, minnei); + long int nlen = igraph_vector_size(neis); + + mindist = -igraph_2wheap_delete_max(&Q); + + VECTOR(*res)[i] += (mindist - 1.0); + nodes_reached++; + + if (cutoff > 0 && mindist >= cutoff + 1.0) { + continue; /* NOT break!!! */ + } + + for (j = 0; j < nlen; j++) { + long int edge = (long int) VECTOR(*neis)[j]; + long int to = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = VECTOR(dist)[to]; + if (curdist == 0) { + /* this means curdist is infinity */ + cmp_result = -1; + } else { + cmp_result = igraph_cmp_epsilon(altdist, curdist, eps); + } + + if (VECTOR(which)[to] != i + 1) { + /* First non-infinite distance */ + VECTOR(which)[to] = i + 1; + VECTOR(dist)[to] = altdist; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, to, -altdist)); + } else if (cmp_result < 0) { + /* This is a shorter path */ + VECTOR(dist)[to] = altdist; + IGRAPH_CHECK(igraph_2wheap_modify(&Q, to, -altdist)); + } + } + + } /* !igraph_2wheap_empty(&Q) */ + + /* using igraph_real_t here instead of igraph_integer_t to avoid overflow */ + VECTOR(*res)[i] += ((igraph_real_t)no_of_nodes * (no_of_nodes - nodes_reached)); + VECTOR(*res)[i] = (no_of_nodes - 1) / VECTOR(*res)[i]; + + if (((cutoff > 0 && mindist < cutoff + 1.0) || (cutoff <= 0)) && + nodes_reached < no_of_nodes && !warning_shown) { + IGRAPH_WARNING("closeness centrality is not well-defined for disconnected graphs"); + warning_shown = 1; + } + } /* !IGRAPH_VIT_END(vit) */ + + if (!normalized) { + for (i = 0; i < nodes_to_calc; i++) { + VECTOR(*res)[i] /= (no_of_nodes - 1); + } + } + + igraph_vector_long_destroy(&which); + igraph_vector_destroy(&dist); + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +/** + * \ingroup structural + * \function igraph_closeness_estimate + * \brief Closeness centrality estimations for some vertices. + * + * + * The closeness centrality of a vertex measures how easily other + * vertices can be reached from it (or the other way: how easily it + * can be reached from the other vertices). It is defined as + * the number of vertices minus one divided by the sum of the + * lengths of all geodesics from/to the given vertex. When estimating + * closeness centrality, igraph considers paths having a length less than + * or equal to a prescribed cutoff value. + * + * + * If the graph is not connected, and there is no such path between two + * vertices, the number of vertices is used instead the length of the + * geodesic. This is always longer than the longest possible geodesic. + * + * + * Since the estimation considers vertex pairs with a distance greater than + * the given value as disconnected, the resulting estimation will always be + * lower than the actual closeness centrality. + * + * \param graph The graph object. + * \param res The result of the computation, a vector containing the + * closeness centrality scores for the given vertices. + * \param vids The vertices for which the closeness centrality will be estimated. + * \param mode The type of shortest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the lengths of the outgoing paths are calculated. + * \cli IGRAPH_IN + * the lengths of the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param cutoff The maximal length of paths that will be considered. + * If zero or negative, the exact closeness will be calculated + * (no upper limit on path lengths). + * \param weights An optional vector containing edge weights for + * weighted closeness. Supply a null pointer here for + * traditional, unweighted closeness. + * \param normalized Boolean, whether to normalize results by multiplying + * by the number of vertices minus one. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex id passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(n|E|), + * n is the number + * of vertices for which the calculation is done and + * |E| is the number + * of edges in the graph. + * + * \sa Other centrality types: \ref igraph_degree(), \ref igraph_betweenness(). + */ +int igraph_closeness_estimate(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_neimode_t mode, + igraph_real_t cutoff, + const igraph_vector_t *weights, + igraph_bool_t normalized) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t already_counted; + igraph_vector_int_t *neis; + long int i, j; + long int nodes_reached; + long int actdist; + igraph_adjlist_t allneis; + + igraph_dqueue_t q; + + long int nodes_to_calc; + igraph_vit_t vit; + + igraph_bool_t warning_shown = 0; + + if (weights) { + return igraph_i_closeness_estimate_weighted(graph, res, vids, mode, cutoff, + weights, normalized); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("calculating closeness", IGRAPH_EINVMODE); + } + + IGRAPH_VECTOR_INIT_FINALLY(&already_counted, no_of_nodes); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, mode)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + igraph_vector_null(res); + + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + igraph_dqueue_clear(&q); + IGRAPH_CHECK(igraph_dqueue_push(&q, IGRAPH_VIT_GET(vit))); + IGRAPH_CHECK(igraph_dqueue_push(&q, 0)); + nodes_reached = 1; + VECTOR(already_counted)[(long int)IGRAPH_VIT_GET(vit)] = i + 1; + + IGRAPH_PROGRESS("Closeness: ", 100.0 * i / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_empty(&q)) { + long int act = (long int) igraph_dqueue_pop(&q); + actdist = (long int) igraph_dqueue_pop(&q); + + VECTOR(*res)[i] += actdist; + + if (cutoff > 0 && actdist >= cutoff) { + continue; /* NOT break!!! */ + } + + /* check the neighbors */ + neis = igraph_adjlist_get(&allneis, act); + for (j = 0; j < igraph_vector_int_size(neis); j++) { + long int neighbor = (long int) VECTOR(*neis)[j]; + if (VECTOR(already_counted)[neighbor] == i + 1) { + continue; + } + VECTOR(already_counted)[neighbor] = i + 1; + nodes_reached++; + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1)); + } + } + + /* using igraph_real_t here instead of igraph_integer_t to avoid overflow */ + VECTOR(*res)[i] += ((igraph_real_t)no_of_nodes * (no_of_nodes - nodes_reached)); + VECTOR(*res)[i] = (no_of_nodes - 1) / VECTOR(*res)[i]; + + if (((cutoff > 0 && actdist < cutoff) || cutoff <= 0) && + no_of_nodes > nodes_reached && !warning_shown) { + IGRAPH_WARNING("closeness centrality is not well-defined for disconnected graphs"); + warning_shown = 1; + } + } + + if (!normalized) { + for (i = 0; i < nodes_to_calc; i++) { + VECTOR(*res)[i] /= (no_of_nodes - 1); + } + } + + IGRAPH_PROGRESS("Closeness: ", 100.0, NULL); + + /* Clean */ + igraph_dqueue_destroy(&q); + igraph_vector_destroy(&already_counted); + igraph_vit_destroy(&vit); + igraph_adjlist_destroy(&allneis); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +/** + * \function igraph_centralization + * Calculate the centralization score from the node level scores + * + * For a centrality score defined on the vertices of a graph, it is + * possible to define a graph level centralization index, by + * calculating the sum of the deviation from the maximum centrality + * score. Consequently, the higher the centralization index of the + * graph, the more centralized the structure is. + * + * In order to make graphs of different sizes comparable, + * the centralization index is usually normalized to a number between + * zero and one, by dividing the (unnormalized) centralization score + * of the most centralized structure with the same number of vertices. + * + * For most centrality indices the most centralized + * structure is the star graph, a single center connected to all other + * nodes in the network. There are some variation depending on whether + * the graph is directed or not, whether loop edges are allowed, etc. + * + * + * This function simply calculates the graph level index, if the node + * level scores and the theoretical maximum are given. It is called by + * all the measure-specific centralization functions. + * + * \param scores A vector containing the node-level centrality + * scores. + * \param theoretical_max The graph level centrality score of the most + * centralized graph with the same number of vertices. Only used + * if \c normalized set to true. + * \param normalized Boolean, whether to normalize the centralization + * by dividing the supplied theoretical maximum. + * \return The graph level index. + * + * \sa \ref igraph_centralization_degree(), \ref + * igraph_centralization_betweenness(), \ref + * igraph_centralization_closeness(), and \ref + * igraph_centralization_eigenvector_centrality() for specific + * centralization functions. + * + * Time complexity: O(n), the length of the score vector. + * + * \example examples/simple/centralization.c + */ + +igraph_real_t igraph_centralization(const igraph_vector_t *scores, + igraph_real_t theoretical_max, + igraph_bool_t normalized) { + + long int no_of_nodes = igraph_vector_size(scores); + igraph_real_t maxscore = 0.0; + igraph_real_t cent = 0.0; + + if (no_of_nodes != 0) { + maxscore = igraph_vector_max(scores); + cent = no_of_nodes * maxscore - igraph_vector_sum(scores); + if (normalized) { + cent = cent / theoretical_max; + } + } else { + cent = IGRAPH_NAN; + } + + return cent; +} + +/** + * \function igraph_centralization_degree + * Calculate vertex degree and graph centralization + * + * This function calculates the degree of the vertices by passing its + * arguments to \ref igraph_degree(); and it calculates the graph + * level centralization index based on the results by calling \ref + * igraph_centralization(). + * \param graph The input graph. + * \param res A vector if you need the node-level degree scores, or a + * null pointer otherwise. + * \param mode Constant the specifies the type of degree for directed + * graphs. Possible values: \c IGRAPH_IN, \c IGRAPH_OUT and \c + * IGRAPH_ALL. This argument is ignored for undirected graphs. + * \param loops Boolean, whether to consider loop edges when + * calculating the degree (and the centralization). + * \param centralization Pointer to a real number, the centralization + * score is placed here. + * \param theoretical_max Pointer to real number or a null pointer. If + * not a null pointer, then the theoretical maximum graph + * centrality score for a graph with the same number vertices is + * stored here. + * \param normalized Boolean, whether to calculate a normalized + * centralization score. See \ref igraph_centralization() for how + * the normalization is done. + * \return Error code. + * + * \sa \ref igraph_centralization(), \ref igraph_degree(). + * + * Time complexity: the complexity of \ref igraph_degree() plus O(n), + * the number of vertices queried, for calculating the centralization + * score. + */ + +int igraph_centralization_degree(const igraph_t *graph, igraph_vector_t *res, + igraph_neimode_t mode, igraph_bool_t loops, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized) { + + igraph_vector_t myscores; + igraph_vector_t *scores = res; + igraph_real_t *tmax = theoretical_max, mytmax; + + if (!tmax) { + tmax = &mytmax; + } + + if (!res) { + scores = &myscores; + IGRAPH_VECTOR_INIT_FINALLY(scores, 0); + } + + IGRAPH_CHECK(igraph_degree(graph, scores, igraph_vss_all(), mode, loops)); + + IGRAPH_CHECK(igraph_centralization_degree_tmax(graph, 0, mode, loops, + tmax)); + + *centralization = igraph_centralization(scores, *tmax, normalized); + + if (!res) { + igraph_vector_destroy(scores); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_centralization_degree_tmax + * Theoretical maximum for graph centralization based on degree + * + * This function returns the theoretical maximum graph centrality + * based on vertex degree. + * + * + * There are two ways to call this function, the first is to supply a + * graph as the graph argument, and then the number of + * vertices is taken from this object, and its directedness is + * considered as well. The nodes argument is ignored in + * this case. The mode argument is also ignored if the + * supplied graph is undirected. + * + * + * The other way is to supply a null pointer as the graph + * argument. In this case the nodes and mode + * arguments are considered. + * + * + * The most centralized structure is the star. More specifically, for + * undirected graphs it is the star, for directed graphs it is the + * in-star or the out-star. + * \param graph A graph object or a null pointer, see the description + * above. + * \param nodes The number of nodes. This is ignored if the + * graph argument is not a null pointer. + * \param mode Constant, whether the calculation is based on in-degree + * (IGRAPH_IN), out-degree (IGRAPH_OUT) + * or total degree (IGRAPH_ALL). This is ignored if + * the graph argument is not a null pointer and the + * given graph is undirected. + * \param loops Boolean scalar, whether to consider loop edges in the + * calculation. + * \param res Pointer to a real variable, the result is stored here. + * \return Error code. + * + * Time complexity: O(1). + * + * \sa \ref igraph_centralization_degree() and \ref + * igraph_centralization(). + */ + +int igraph_centralization_degree_tmax(const igraph_t *graph, + igraph_integer_t nodes, + igraph_neimode_t mode, + igraph_bool_t loops, + igraph_real_t *res) { + + igraph_bool_t directed = mode != IGRAPH_ALL; + igraph_real_t real_nodes; + + if (graph) { + directed = igraph_is_directed(graph); + nodes = igraph_vcount(graph); + } + + real_nodes = nodes; /* implicit cast to igraph_real_t */ + + if (directed) { + switch (mode) { + case IGRAPH_IN: + case IGRAPH_OUT: + if (!loops) { + *res = (real_nodes - 1) * (real_nodes - 1); + } else { + *res = (real_nodes - 1) * real_nodes; + } + break; + case IGRAPH_ALL: + if (!loops) { + *res = 2 * (real_nodes - 1) * (real_nodes - 2); + } else { + *res = 2 * (real_nodes - 1) * (real_nodes - 1); + } + break; + } + } else { + if (!loops) { + *res = (real_nodes - 1) * (real_nodes - 2); + } else { + *res = (real_nodes - 1) * real_nodes; + } + } + + return 0; +} + +/** + * \function igraph_centralization_betweenness + * Calculate vertex betweenness and graph centralization + * + * This function calculates the betweenness centrality of the vertices + * by passing its arguments to \ref igraph_betweenness(); and it + * calculates the graph level centralization index based on the + * results by calling \ref igraph_centralization(). + * \param graph The input graph. + * \param res A vector if you need the node-level betweenness scores, or a + * null pointer otherwise. + * \param directed Boolean, whether to consider directed paths when + * calculating betweenness. + * \param nobigint Logical, if true, then we don't use big integers + * for the calculation, setting this to zero (=false) should + * work for most graphs. It is currently ignored for weighted + * graphs. + * \param centralization Pointer to a real number, the centralization + * score is placed here. + * \param theoretical_max Pointer to real number or a null pointer. If + * not a null pointer, then the theoretical maximum graph + * centrality score for a graph with the same number vertices is + * stored here. + * \param normalized Boolean, whether to calculate a normalized + * centralization score. See \ref igraph_centralization() for how + * the normalization is done. + * \return Error code. + * + * \sa \ref igraph_centralization(), \ref igraph_betweenness(). + * + * Time complexity: the complexity of \ref igraph_betweenness() plus + * O(n), the number of vertices queried, for calculating the + * centralization score. + */ + +int igraph_centralization_betweenness(const igraph_t *graph, + igraph_vector_t *res, + igraph_bool_t directed, + igraph_bool_t nobigint, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized) { + + igraph_vector_t myscores; + igraph_vector_t *scores = res; + igraph_real_t *tmax = theoretical_max, mytmax; + + if (!tmax) { + tmax = &mytmax; + } + + if (!res) { + scores = &myscores; + IGRAPH_VECTOR_INIT_FINALLY(scores, 0); + } + + IGRAPH_CHECK(igraph_betweenness(graph, scores, igraph_vss_all(), directed, + /*weights=*/ 0, nobigint)); + + IGRAPH_CHECK(igraph_centralization_betweenness_tmax(graph, 0, directed, + tmax)); + + *centralization = igraph_centralization(scores, *tmax, normalized); + + if (!res) { + igraph_vector_destroy(scores); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_centralization_betweenness_tmax + * Theoretical maximum for graph centralization based on betweenness + * + * This function returns the theoretical maximum graph centrality + * based on vertex betweenness. + * + * + * There are two ways to call this function, the first is to supply a + * graph as the graph argument, and then the number of + * vertices is taken from this object, and its directedness is + * considered as well. The nodes argument is ignored in + * this case. The directed argument is also ignored if the + * supplied graph is undirected. + * + * + * The other way is to supply a null pointer as the graph + * argument. In this case the nodes and directed + * arguments are considered. + * + * + * The most centralized structure is the star. + * \param graph A graph object or a null pointer, see the description + * above. + * \param nodes The number of nodes. This is ignored if the + * graph argument is not a null pointer. + * \param directed Boolean scalar, whether to use directed paths in + * the betweenness calculation. This argument is ignored if + * graph is not a null pointer and it is undirected. + * \param res Pointer to a real variable, the result is stored here. + * \return Error code. + * + * Time complexity: O(1). + * + * \sa \ref igraph_centralization_betweenness() and \ref + * igraph_centralization(). + */ + +int igraph_centralization_betweenness_tmax(const igraph_t *graph, + igraph_integer_t nodes, + igraph_bool_t directed, + igraph_real_t *res) { + igraph_real_t real_nodes; + + if (graph) { + directed = directed && igraph_is_directed(graph); + nodes = igraph_vcount(graph); + } + + real_nodes = nodes; /* implicit cast to igraph_real_t */ + + if (directed) { + *res = (real_nodes - 1) * (real_nodes - 1) * (real_nodes - 2); + } else { + *res = (real_nodes - 1) * (real_nodes - 1) * (real_nodes - 2) / 2.0; + } + + return 0; +} + +/** + * \function igraph_centralization_closeness + * Calculate vertex closeness and graph centralization + * + * This function calculates the closeness centrality of the vertices + * by passing its arguments to \ref igraph_closeness(); and it + * calculates the graph level centralization index based on the + * results by calling \ref igraph_centralization(). + * \param graph The input graph. + * \param res A vector if you need the node-level closeness scores, or a + * null pointer otherwise. + * \param mode Constant the specifies the type of closeness for directed + * graphs. Possible values: \c IGRAPH_IN, \c IGRAPH_OUT and \c + * IGRAPH_ALL. This argument is ignored for undirected graphs. See + * \ref igraph_closeness() argument with the same name for more. + * \param centralization Pointer to a real number, the centralization + * score is placed here. + * \param theoretical_max Pointer to real number or a null pointer. If + * not a null pointer, then the theoretical maximum graph + * centrality score for a graph with the same number vertices is + * stored here. + * \param normalized Boolean, whether to calculate a normalized + * centralization score. See \ref igraph_centralization() for how + * the normalization is done. + * \return Error code. + * + * \sa \ref igraph_centralization(), \ref igraph_closeness(). + * + * Time complexity: the complexity of \ref igraph_closeness() plus + * O(n), the number of vertices queried, for calculating the + * centralization score. + */ + +int igraph_centralization_closeness(const igraph_t *graph, + igraph_vector_t *res, + igraph_neimode_t mode, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized) { + + igraph_vector_t myscores; + igraph_vector_t *scores = res; + igraph_real_t *tmax = theoretical_max, mytmax; + + if (!tmax) { + tmax = &mytmax; + } + + if (!res) { + scores = &myscores; + IGRAPH_VECTOR_INIT_FINALLY(scores, 0); + } + + IGRAPH_CHECK(igraph_closeness(graph, scores, igraph_vss_all(), mode, + /*weights=*/ 0, /*normalize=*/ 1)); + + IGRAPH_CHECK(igraph_centralization_closeness_tmax(graph, 0, mode, + tmax)); + + *centralization = igraph_centralization(scores, *tmax, normalized); + + if (!res) { + igraph_vector_destroy(scores); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_centralization_closeness_tmax + * Theoretical maximum for graph centralization based on closeness + * + * This function returns the theoretical maximum graph centrality + * based on vertex closeness. + * + * + * There are two ways to call this function, the first is to supply a + * graph as the graph argument, and then the number of + * vertices is taken from this object, and its directedness is + * considered as well. The nodes argument is ignored in + * this case. The mode argument is also ignored if the + * supplied graph is undirected. + * + * + * The other way is to supply a null pointer as the graph + * argument. In this case the nodes and mode + * arguments are considered. + * + * + * The most centralized structure is the star. + * \param graph A graph object or a null pointer, see the description + * above. + * \param nodes The number of nodes. This is ignored if the + * graph argument is not a null pointer. + * \param mode Constant, specifies what kinf of distances to consider + * to calculate closeness. See the mode argument of + * \ref igraph_closeness() for details. This argument is ignored + * if graph is not a null pointer and it is + * undirected. + * \param res Pointer to a real variable, the result is stored here. + * \return Error code. + * + * Time complexity: O(1). + * + * \sa \ref igraph_centralization_closeness() and \ref + * igraph_centralization(). + */ + +int igraph_centralization_closeness_tmax(const igraph_t *graph, + igraph_integer_t nodes, + igraph_neimode_t mode, + igraph_real_t *res) { + igraph_real_t real_nodes; + + if (graph) { + nodes = igraph_vcount(graph); + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + } + + real_nodes = nodes; /* implicit cast to igraph_real_t */ + + if (mode != IGRAPH_ALL) { + *res = (real_nodes - 1) * (1.0 - 1.0 / real_nodes); + } else { + *res = (real_nodes - 1) * (real_nodes - 2) / (2.0 * real_nodes - 3); + } + + return 0; +} + +/** + * \function igraph_centralization_eigenvector_centrality + * Calculate eigenvector centrality scores and graph centralization + * + * This function calculates the eigenvector centrality of the vertices + * by passing its arguments to \ref igraph_eigenvector_centrality); + * and it calculates the graph level centralization index based on the + * results by calling \ref igraph_centralization(). + * \param graph The input graph. + * \param vector A vector if you need the node-level eigenvector + * centrality scores, or a null pointer otherwise. + * \param value If not a null pointer, then the leading eigenvalue is + * stored here. + * \param scale If not zero then the result will be scaled, such that + * the absolute value of the maximum centrality is one. + * \param options Options to ARPACK. See \ref igraph_arpack_options_t + * for details. Note that the function overwrites the + * n (number of vertices) parameter and + * it always starts the calculation from a non-random vector + * calculated based on the degree of the vertices. + * \param centralization Pointer to a real number, the centralization + * score is placed here. + * \param theoretical_max Pointer to real number or a null pointer. If + * not a null pointer, then the theoretical maximum graph + * centrality score for a graph with the same number vertices is + * stored here. + * \param normalized Boolean, whether to calculate a normalized + * centralization score. See \ref igraph_centralization() for how + * the normalization is done. + * \return Error code. + * + * \sa \ref igraph_centralization(), \ref igraph_eigenvector_centrality(). + * + * Time complexity: the complexity of \ref + * igraph_eigenvector_centrality() plus O(|V|), the number of vertices + * for the calculating the centralization. + */ + +int igraph_centralization_eigenvector_centrality( + const igraph_t *graph, + igraph_vector_t *vector, + igraph_real_t *value, + igraph_bool_t directed, + igraph_bool_t scale, + igraph_arpack_options_t *options, + igraph_real_t *centralization, + igraph_real_t *theoretical_max, + igraph_bool_t normalized) { + + igraph_vector_t myscores; + igraph_vector_t *scores = vector; + igraph_real_t realvalue, *myvalue = value; + igraph_real_t *tmax = theoretical_max, mytmax; + + if (!tmax) { + tmax = &mytmax; + } + + if (!vector) { + scores = &myscores; + IGRAPH_VECTOR_INIT_FINALLY(scores, 0); + } + if (!value) { + myvalue = &realvalue; + } + + IGRAPH_CHECK(igraph_eigenvector_centrality(graph, scores, myvalue, directed, + scale, /*weights=*/ 0, + options)); + + IGRAPH_CHECK(igraph_centralization_eigenvector_centrality_tmax( + graph, 0, directed, + scale, + tmax)); + + *centralization = igraph_centralization(scores, *tmax, normalized); + + if (!vector) { + igraph_vector_destroy(scores); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_centralization_eigenvector_centrality_tmax + * Theoretical maximum centralization for eigenvector centrality + * + * This function returns the theoretical maximum graph centrality + * based on vertex eigenvector centrality. + * + * + * There are two ways to call this function, the first is to supply a + * graph as the graph argument, and then the number of + * vertices is taken from this object, and its directedness is + * considered as well. The nodes argument is ignored in + * this case. The directed argument is also ignored if the + * supplied graph is undirected. + * + * + * The other way is to supply a null pointer as the graph + * argument. In this case the nodes and directed + * arguments are considered. + * + * + * The most centralized directed structure is the in-star. The most + * centralized undirected structure is the graph with a single edge. + * \param graph A graph object or a null pointer, see the description + * above. + * \param nodes The number of nodes. This is ignored if the + * graph argument is not a null pointer. + * \param directed Boolean scalar, whether to consider edge + * directions. This argument is ignored if + * graph is not a null pointer and it is undirected. + * \param scale Whether to rescale the node-level centrality scores to + * have a maximum of one. + * \param res Pointer to a real variable, the result is stored here. + * \return Error code. + * + * Time complexity: O(1). + * + * \sa \ref igraph_centralization_closeness() and \ref + * igraph_centralization(). + */ + +int igraph_centralization_eigenvector_centrality_tmax( + const igraph_t *graph, + igraph_integer_t nodes, + igraph_bool_t directed, + igraph_bool_t scale, + igraph_real_t *res) { + + if (graph) { + nodes = igraph_vcount(graph); + directed = directed && igraph_is_directed(graph); + } + + if (directed) { + *res = nodes - 1; + } else { + if (scale) { + *res = nodes - 2; + } else { + *res = (nodes - 2.0) / M_SQRT2; + } + } + + return 0; +} diff --git a/src/cliquer/README b/src/cliquer/README new file mode 100644 index 0000000..8eb21f1 --- /dev/null +++ b/src/cliquer/README @@ -0,0 +1,61 @@ + +Cliquer - routines for clique searching +--------------------------------------- + + +Cliquer is a set of C routines for finding cliques in an arbitrary +weighted graph. It uses an exact branch-and-bound algorithm recently +developed by Patric Ostergard. It is designed with the aim of being +efficient while still being flexible and easy to use. + +Cliquer was developed on Linux, and it should compile without +modification on most modern UNIX systems. Other operating systems may +require minor changes to the source code. + +Features: + + * support for both weighted and unweighted graphs (faster routines + for unweighted graphs) + * search for maximum clique / maximum-weight clique + * search for clique with size / weight within a given range + * restrict search to maximal cliques + * store found cliques in memory + * call a user-defined function for every clique found + * Cliquer is re-entrant, so you can use the clique-searching + functions from within the callback function + +The full documentation can be obtained via the www page of +Cliquer . + + +License + +Cliquer is Copyright (C) 2002 Sampo Niskanen, Patric Ostergard. + +Cliquer is licensed under the GNU General Public License as published +by the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. The full license is included in +the file LICENSE. + +Basically, you can use Cliquer for any purpose, provided that any +programs or modifications you make and distribute are also licensed +under the GNU GPL. + +ABSOLUTELY NO GUARANTEES OR WARRANTIES are made concerning the +suitability, correctness, or any other aspect of these routines. + + +Contact + +Cliquer was mainly written by Sampo Niskanen . + +For bug-fixes, feedback, and, in particular, for putting your +name on the mailing list for important information regarding Cliquer, +please contact: + +Patric Ostergard +Department of Communications and Networking +Aalto University +P.O. Box 13000, 00076 Aalto +FINLAND + diff --git a/src/cliquer/cliquer.c b/src/cliquer/cliquer.c new file mode 100644 index 0000000..bbd8804 --- /dev/null +++ b/src/cliquer/cliquer.c @@ -0,0 +1,1778 @@ + +/* + * This file contains the clique searching routines. + * + * Copyright (C) 2002 Sampo Niskanen, Patric ÖstergÃ¥rd. + * Licensed under the GNU GPL, read the file LICENSE for details. + */ + +#include +#include +#include +/* +#include +#include +#include +*/ + +#include "cliquer.h" + +#include "config.h" + +#ifdef USING_R +#include +#endif + +/* Default cliquer options */ +IGRAPH_THREAD_LOCAL clique_options clique_default_options = { + reorder_by_default, NULL, /*clique_print_time*/ NULL, NULL, NULL, NULL, NULL, 0 +}; + + +/* Calculate d/q, rounding result upwards/downwards. */ +#define DIV_UP(d,q) (((d)+(q)-1)/(q)) +#define DIV_DOWN(d,q) ((int)((d)/(q))) + + +/* Global variables used: */ +/* These must be saved and restored in re-entrance. */ +static IGRAPH_THREAD_LOCAL int *clique_size; /* c[i] == max. clique size in {0,1,...,i-1} */ +static IGRAPH_THREAD_LOCAL set_t current_clique; /* Current clique being searched. */ +static IGRAPH_THREAD_LOCAL set_t best_clique; /* Largest/heaviest clique found so far. */ +/*static struct tms cputimer;*/ /* Timer for opts->time_function() */ +/*static struct timeval realtimer;*/ /* Timer for opts->time_function() */ +static IGRAPH_THREAD_LOCAL int clique_list_count=0; /* No. of cliques in opts->clique_list[] */ +static IGRAPH_THREAD_LOCAL int weight_multiplier=1; /* Weights multiplied by this when passing + * to time_function(). */ + +/* List cache (contains memory blocks of size g->n * sizeof(int)) */ +static IGRAPH_THREAD_LOCAL int **temp_list=NULL; +static IGRAPH_THREAD_LOCAL int temp_count=0; + + +/* + * Macros for re-entrance. ENTRANCE_SAVE() must be called immediately + * after variable definitions, ENTRANCE_RESTORE() restores global + * variables to original values. entrance_level should be increased + * and decreased accordingly. + */ +static IGRAPH_THREAD_LOCAL int entrance_level=0; /* How many levels for entrance have occurred? */ + +#define ENTRANCE_SAVE() \ +int *old_clique_size = clique_size; \ +set_t old_current_clique = current_clique; \ +set_t old_best_clique = best_clique; \ +int old_clique_list_count = clique_list_count; \ +int old_weight_multiplier = weight_multiplier; \ +int **old_temp_list = temp_list; \ +int old_temp_count = temp_count; \ +/*struct tms old_cputimer; \ +struct timeval old_realtimer; \ +memcpy(&old_cputimer,&cputimer,sizeof(struct tms)); \ +memcpy(&old_realtimer,&realtimer,sizeof(struct timeval));*/ + +#define ENTRANCE_RESTORE() \ +clique_size = old_clique_size; \ +current_clique = old_current_clique; \ +best_clique = old_best_clique; \ +clique_list_count = old_clique_list_count; \ +weight_multiplier = old_weight_multiplier; \ +temp_list = old_temp_list; \ +temp_count = old_temp_count; \ +/*memcpy(&cputimer,&old_cputimer,sizeof(struct tms)); \ +memcpy(&realtimer,&old_realtimer,sizeof(struct timeval));*/ + + +/* Number of clock ticks per second (as returned by sysconf(_SC_CLK_TCK)) */ +/*static int clocks_per_sec=0;*/ + + + + +/* Recursion and helper functions */ +static boolean sub_unweighted_single(int *table, int size, int min_size, + graph_t *g); +static int sub_unweighted_all(int *table, int size, int min_size, int max_size, + boolean maximal, graph_t *g, + clique_options *opts); +static int sub_weighted_all(int *table, int size, int weight, + int current_weight, int prune_low, int prune_high, + int min_weight, int max_weight, boolean maximal, + graph_t *g, clique_options *opts); + + +static boolean store_clique(set_t clique, graph_t *g, clique_options *opts); +static boolean is_maximal(set_t clique, graph_t *g); +static boolean false_function(set_t clique,graph_t *g,clique_options *opts); + + + + + +/***** Unweighted searches *****/ +/* + * Unweighted searches are done separately from weighted searches because + * some effective pruning methods can be used when the vertex weights + * are all 1. Single and all clique finding routines are separated, + * because the single clique finding routine can store the found clique + * while it is returning from the recursion, thus requiring no implicit + * storing of the current clique. When searching for all cliques the + * current clique must be stored. + */ + + +/* + * unweighted_clique_search_single() + * + * Searches for a single clique of size min_size. Stores maximum clique + * sizes into clique_size[]. + * + * table - the order of the vertices in g to use + * min_size - minimum size of clique to search for. If min_size==0, + * searches for a maximum clique. + * g - the graph + * opts - time printing options + * + * opts->time_function is called after each base-level recursion, if + * non-NULL. + * + * Returns the size of the clique found, or 0 if min_size>0 and a clique + * of that size was not found (or if time_function aborted the search). + * The largest clique found is stored in current_clique. + * + * Note: Does NOT use opts->user_function of opts->clique_list. + */ +static int unweighted_clique_search_single(int *table, int min_size, + graph_t *g, clique_options *opts) { + /* + struct tms tms; + struct timeval timeval; + */ + int i,j; + int v,w; + int *newtable; + int newsize; + + v=table[0]; + clique_size[v]=1; + set_empty(current_clique); + SET_ADD_ELEMENT(current_clique,v); + if (min_size==1) + return 1; + + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + for (i=1; i < g->n; i++) { + w=v; + v=table[i]; + + newsize=0; + for (j=0; jtime_function) { + gettimeofday(&timeval,NULL); + times(&tms); + if (!opts->time_function(entrance_level, + i+1,g->n,clique_size[v] * + weight_multiplier, + (double)(tms.tms_utime- + cputimer.tms_utime)/ + clocks_per_sec, + timeval.tv_sec- + realtimer.tv_sec+ + (double)(timeval.tv_usec- + realtimer.tv_usec)/ + 1000000,opts)) { + temp_list[temp_count++]=newtable; + return 0; + } + } + */ + + if (min_size) { + if (clique_size[v]>=min_size) { + temp_list[temp_count++]=newtable; + return clique_size[v]; + } + if (clique_size[v]+g->n-i-1 < min_size) { + temp_list[temp_count++]=newtable; + return 0; + } + } + } + + temp_list[temp_count++]=newtable; + + if (min_size) + return 0; + return clique_size[v]; +} + +/* + * sub_unweighted_single() + * + * Recursion function for searching for a single clique of size min_size. + * + * table - subset of the vertices in graph + * size - size of table + * min_size - size of clique to look for within the subgraph + * (decreased with every recursion) + * g - the graph + * + * Returns TRUE if a clique of size min_size is found, FALSE otherwise. + * If a clique of size min_size is found, it is stored in current_clique. + * + * clique_size[] for all values in table must be defined and correct, + * otherwise inaccurate results may occur. + */ +static boolean sub_unweighted_single(int *table, int size, int min_size, + graph_t *g) { + int i; + int v; + int *newtable; + int *p1, *p2; + + /* Zero or one vertices needed anymore. */ + if (min_size <= 1) { + if (size>0 && min_size==1) { + set_empty(current_clique); + SET_ADD_ELEMENT(current_clique,table[0]); + return TRUE; + } + if (min_size==0) { + set_empty(current_clique); + return TRUE; + } + return FALSE; + } + if (size < min_size) + return FALSE; + + /* Dynamic memory allocation with cache */ + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + for (i = size-1; i >= 0; i--) { + v = table[i]; + + if (clique_size[v] < min_size) + break; + /* This is faster when compiling with gcc than placing + * this in the for-loop condition. */ + if (i+1 < min_size) + break; + + /* Very ugly code, but works faster than "for (i=...)" */ + p1 = newtable; + for (p2=table; p2 < table+i; p2++) { + int w = *p2; + if (GRAPH_IS_EDGE(g, v, w)) { + *p1 = w; + p1++; + } + } + + /* Avoid unneccessary loops (next size == p1-newtable) */ + if (p1-newtable < min_size-1) + continue; + /* Now p1-newtable >= min_size-1 >= 2-1 == 1, so we can use + * p1-newtable-1 safely. */ + if (clique_size[newtable[p1-newtable-1]] < min_size-1) + continue; + + if (sub_unweighted_single(newtable,p1-newtable, + min_size-1,g)) { + /* Clique found. */ + SET_ADD_ELEMENT(current_clique,v); + temp_list[temp_count++]=newtable; + return TRUE; + } + } + temp_list[temp_count++]=newtable; + return FALSE; +} + + +/* + * unweighted_clique_search_all() + * + * Searches for all cliques with size at least min_size and at most + * max_size. Stores the cliques as opts declares. + * + * table - the order of the vertices in g to search + * start - first index where the subgraph table[0], ..., table[start] + * might include a requested kind of clique + * min_size - minimum size of clique to search for. min_size > 0 ! + * max_size - maximum size of clique to search for. If no upper limit + * is desired, use eg. INT_MAX + * maximal - requires cliques to be maximal + * g - the graph + * opts - time printing and clique storage options + * + * Cliques found are stored as defined by opts->user_function and + * opts->clique_list. opts->time_function is called after each + * base-level recursion, if non-NULL. + * + * clique_size[] must be defined and correct for all values of + * table[0], ..., table[start-1]. + * + * Returns the number of cliques stored (not neccessarily number of cliques + * in graph, if user/time_function aborts). + */ +static int unweighted_clique_search_all(int *table, int start, + int min_size, int max_size, + boolean maximal, graph_t *g, + clique_options *opts) { + /* + struct timeval timeval; + struct tms tms; + */ + int i,j; + int v; + int *newtable; + int newsize; + int count=0; + + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + clique_list_count=0; + set_empty(current_clique); + for (i=start; i < g->n; i++) { + v=table[i]; + clique_size[v]=min_size; /* Do not prune here. */ + + newsize=0; + for (j=0; jtime_function) { + gettimeofday(&timeval,NULL); + times(&tms); + if (!opts->time_function(entrance_level, + i+1,g->n,min_size * + weight_multiplier, + (double)(tms.tms_utime- + cputimer.tms_utime)/ + clocks_per_sec, + timeval.tv_sec- + realtimer.tv_sec+ + (double)(timeval.tv_usec- + realtimer.tv_usec)/ + 1000000,opts)) { + /* Abort. */ + break; + } + } +#endif + } + temp_list[temp_count++]=newtable; + return count; +} + +/* + * sub_unweighted_all() + * + * Recursion function for searching for all cliques of given size. + * + * table - subset of vertices of graph g + * size - size of table + * min_size - minimum size of cliques to search for (decreased with + * every recursion) + * max_size - maximum size of cliques to search for (decreased with + * every recursion). If no upper limit is desired, use + * eg. INT_MAX + * maximal - require cliques to be maximal (passed through) + * g - the graph + * opts - storage options + * + * All cliques of suitable size found are stored according to opts. + * + * Returns the number of cliques found. If user_function returns FALSE, + * then the number of cliques is returned negative. + * + * Uses current_clique to store the currently-being-searched clique. + * clique_size[] for all values in table must be defined and correct, + * otherwise inaccurate results may occur. + */ +static int sub_unweighted_all(int *table, int size, int min_size, int max_size, + boolean maximal, graph_t *g, + clique_options *opts) { + int i; + int v; + int n; + int *newtable; + int *p1, *p2; + int count=0; /* Amount of cliques found */ + + if (min_size <= 0) { + if ((!maximal) || is_maximal(current_clique,g)) { + /* We've found one. Store it. */ + count++; + if (!store_clique(current_clique,g,opts)) { + return -count; + } + } + if (max_size <= 0) { + /* If we add another element, size will be too big. */ + return count; + } + } + + if (size < min_size) { + return count; + } + + /* Dynamic memory allocation with cache */ + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + for (i=size-1; i>=0; i--) { + v = table[i]; + if (clique_size[v] < min_size) { + break; + } + if (i+1 < min_size) { + break; + } + + /* Very ugly code, but works faster than "for (i=...)" */ + p1 = newtable; + for (p2=table; p2 < table+i; p2++) { + int w = *p2; + if (GRAPH_IS_EDGE(g, v, w)) { + *p1 = w; + p1++; + } + } + + /* Avoid unneccessary loops (next size == p1-newtable) */ + if (p1-newtable < min_size-1) { + continue; + } + + SET_ADD_ELEMENT(current_clique,v); + n=sub_unweighted_all(newtable,p1-newtable, + min_size-1,max_size-1,maximal,g,opts); + SET_DEL_ELEMENT(current_clique,v); + if (n < 0) { + /* Abort. */ + count -= n; + count = -count; + break; + } + count+=n; + } + temp_list[temp_count++]=newtable; + return count; +} + + + + +/***** Weighted clique searches *****/ +/* + * Weighted clique searches can use the same recursive routine, because + * in both cases (single/all) they have to search through all potential + * permutations searching for heavier cliques. + */ + + +/* + * weighted_clique_search_single() + * + * Searches for a single clique of weight at least min_weight, and at + * most max_weight. Stores maximum clique sizes into clique_size[] + * (or min_weight-1, whichever is smaller). + * + * table - the order of the vertices in g to use + * min_weight - minimum weight of clique to search for. If min_weight==0, + * then searches for a maximum weight clique + * max_weight - maximum weight of clique to search for. If no upper limit + * is desired, use eg. INT_MAX + * g - the graph + * opts - time printing options + * + * opts->time_function is called after each base-level recursion, if + * non-NULL. + * + * Returns 0 if a clique of requested weight was not found (also if + * time_function requested an abort), otherwise returns >= 1. + * If min_weight==0 (search for maximum-weight clique), then the return + * value is the weight of the clique found. The found clique is stored + * in best_clique. + * + * Note: Does NOT use opts->user_function of opts->clique_list. + */ +static int weighted_clique_search_single(int *table, int min_weight, + int max_weight, graph_t *g, + clique_options *opts) { + /* + struct timeval timeval; + struct tms tms; + */ + int i,j; + int v; + int *newtable; + int newsize; + int newweight; + int search_weight; + int min_w; + clique_options localopts; + + if (min_weight==0) + min_w=INT_MAX; + else + min_w=min_weight; + + + if (min_weight==1) { + /* min_weight==1 may cause trouble in the routine, and + * it's trivial to check as it's own case. + * We write nothing to clique_size[]. */ + for (i=0; i < g->n; i++) { + if (g->weights[table[i]] <= max_weight) { + set_empty(best_clique); + SET_ADD_ELEMENT(best_clique,table[i]); + return g->weights[table[i]]; + } + } + return 0; + } + + localopts.time_function=NULL; + localopts.reorder_function=NULL; + localopts.reorder_map=NULL; + localopts.user_function=false_function; + localopts.user_data=NULL; + localopts.clique_list=&best_clique; + localopts.clique_list_length=1; + clique_list_count=0; + + v=table[0]; + set_empty(best_clique); + SET_ADD_ELEMENT(best_clique,v); + search_weight=g->weights[v]; + if (min_weight && (search_weight >= min_weight)) { + if (search_weight <= max_weight) { + /* Found suitable clique. */ + return search_weight; + } + search_weight=min_weight-1; + } + clique_size[v]=search_weight; + set_empty(current_clique); + + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + for (i = 1; i < g->n; i++) { + v=table[i]; + + newsize=0; + newweight=0; + for (j=0; jweights[table[j]]; + newtable[newsize]=table[j]; + newsize++; + } + } + + + SET_ADD_ELEMENT(current_clique,v); + search_weight=sub_weighted_all(newtable,newsize,newweight, + g->weights[v],search_weight, + clique_size[table[i-1]] + + g->weights[v], + min_w,max_weight,FALSE, + g,&localopts); + SET_DEL_ELEMENT(current_clique,v); + if (search_weight < 0) { + break; + } + + clique_size[v]=search_weight; + + /* + if (opts->time_function) { + gettimeofday(&timeval,NULL); + times(&tms); + if (!opts->time_function(entrance_level, + i+1,g->n,clique_size[v] * + weight_multiplier, + (double)(tms.tms_utime- + cputimer.tms_utime)/ + clocks_per_sec, + timeval.tv_sec- + realtimer.tv_sec+ + (double)(timeval.tv_usec- + realtimer.tv_usec)/ + 1000000,opts)) { + set_free(current_clique); + current_clique=NULL; + break; + } + } + */ + } + temp_list[temp_count++]=newtable; + if (min_weight && (search_weight > 0)) { + /* Requested clique has not been found. */ + return 0; + } + return clique_size[table[i-1]]; +} + + +/* + * weighted_clique_search_all() + * + * Searches for all cliques with weight at least min_weight and at most + * max_weight. Stores the cliques as opts declares. + * + * table - the order of the vertices in g to search + * start - first index where the subgraph table[0], ..., table[start] + * might include a requested kind of clique + * min_weight - minimum weight of clique to search for. min_weight > 0 ! + * max_weight - maximum weight of clique to search for. If no upper limit + * is desired, use eg. INT_MAX + * maximal - search only for maximal cliques + * g - the graph + * opts - time printing and clique storage options + * + * Cliques found are stored as defined by opts->user_function and + * opts->clique_list. opts->time_function is called after each + * base-level recursion, if non-NULL. + * + * clique_size[] must be defined and correct for all values of + * table[0], ..., table[start-1]. + * + * Returns the number of cliques stored (not neccessarily number of cliques + * in graph, if user/time_function aborts). + */ +static int weighted_clique_search_all(int *table, int start, + int min_weight, int max_weight, + boolean maximal, graph_t *g, + clique_options *opts) { + /* + struct timeval timeval; + struct tms tms; + */ + int i,j; + int v; + int *newtable; + int newsize; + int newweight; + + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + clique_list_count=0; + set_empty(current_clique); + for (i=start; i < g->n; i++) { + v=table[i]; + clique_size[v]=min_weight; /* Do not prune here. */ + + newsize=0; + newweight=0; + for (j=0; jweights[table[j]]; + newsize++; + } + } + + SET_ADD_ELEMENT(current_clique,v); + j=sub_weighted_all(newtable,newsize,newweight, + g->weights[v],min_weight-1,INT_MAX, + min_weight,max_weight,maximal,g,opts); + SET_DEL_ELEMENT(current_clique,v); + + if (j<0) { + /* Abort. */ + break; + } + + /* + if (opts->time_function) { + gettimeofday(&timeval,NULL); + times(&tms); + if (!opts->time_function(entrance_level, + i+1,g->n,clique_size[v] * + weight_multiplier, + (double)(tms.tms_utime- + cputimer.tms_utime)/ + clocks_per_sec, + timeval.tv_sec- + realtimer.tv_sec+ + (double)(timeval.tv_usec- + realtimer.tv_usec)/ + 1000000,opts)) { + set_free(current_clique); + current_clique=NULL; + break; + } + } + */ + } + temp_list[temp_count++]=newtable; + + return clique_list_count; +} + +/* + * sub_weighted_all() + * + * Recursion function for searching for all cliques of given weight. + * + * table - subset of vertices of graph g + * size - size of table + * weight - total weight of vertices in table + * current_weight - weight of clique found so far + * prune_low - ignore all cliques with weight less or equal to this value + * (often heaviest clique found so far) (passed through) + * prune_high - maximum weight possible for clique in this subgraph + * (passed through) + * min_size - minimum weight of cliques to search for (passed through) + * Must be greater than 0. + * max_size - maximum weight of cliques to search for (passed through) + * If no upper limit is desired, use eg. INT_MAX + * maximal - search only for maximal cliques + * g - the graph + * opts - storage options + * + * All cliques of suitable weight found are stored according to opts. + * + * Returns weight of heaviest clique found (prune_low if a heavier clique + * hasn't been found); if a clique with weight at least min_size is found + * then min_size-1 is returned. If clique storage failed, -1 is returned. + * + * The largest clique found smaller than max_weight is stored in + * best_clique, if non-NULL. + * + * Uses current_clique to store the currently-being-searched clique. + * clique_size[] for all values in table must be defined and correct, + * otherwise inaccurate results may occur. + * + * To search for a single maximum clique, use min_weight==max_weight==INT_MAX, + * with best_clique non-NULL. To search for a single given-weight clique, + * use opts->clique_list and opts->user_function=false_function. When + * searching for all cliques, min_weight should be given the minimum weight + * desired. + */ +static int sub_weighted_all(int *table, int size, int weight, + int current_weight, int prune_low, int prune_high, + int min_weight, int max_weight, boolean maximal, + graph_t *g, clique_options *opts) { + int i; + int v,w; + int *newtable; + int *p1, *p2; + int newweight; + + if (current_weight >= min_weight) { + if ((current_weight <= max_weight) && + ((!maximal) || is_maximal(current_clique,g))) { + /* We've found one. Store it. */ + if (!store_clique(current_clique,g,opts)) { + return -1; + } + } + if (current_weight >= max_weight) { + /* Clique too heavy. */ + return min_weight-1; + } + } + if (size <= 0) { + /* current_weight < min_weight, prune_low < min_weight, + * so return value is always < min_weight. */ + if (current_weight>prune_low) { + if (best_clique) { + best_clique = set_copy(best_clique,current_clique); + } + if (current_weight < min_weight) + return current_weight; + else + return min_weight-1; + } else { + return prune_low; + } + } + + /* Dynamic memory allocation with cache */ + if (temp_count) { + temp_count--; + newtable=temp_list[temp_count]; + } else { + newtable=malloc(g->n * sizeof(int)); + } + + for (i = size-1; i >= 0; i--) { + v = table[i]; + if (current_weight+clique_size[v] <= prune_low) { + /* Dealing with subset without heavy enough clique. */ + break; + } + if (current_weight+weight <= prune_low) { + /* Even if all elements are added, won't do. */ + break; + } + + /* Very ugly code, but works faster than "for (i=...)" */ + p1 = newtable; + newweight = 0; + for (p2=table; p2 < table+i; p2++) { + w = *p2; + if (GRAPH_IS_EDGE(g, v, w)) { + *p1 = w; + newweight += g->weights[w]; + p1++; + } + } + + w=g->weights[v]; + weight-=w; + /* Avoid a few unneccessary loops */ + if (current_weight+w+newweight <= prune_low) { + continue; + } + + SET_ADD_ELEMENT(current_clique,v); + prune_low=sub_weighted_all(newtable,p1-newtable, + newweight, + current_weight+w, + prune_low,prune_high, + min_weight,max_weight,maximal, + g,opts); + SET_DEL_ELEMENT(current_clique,v); + if ((prune_low<0) || (prune_low>=prune_high)) { + /* Impossible to find larger clique. */ + break; + } + } + temp_list[temp_count++]=newtable; + return prune_low; +} + + + + +/***** Helper functions *****/ + + +/* + * store_clique() + * + * Stores a clique according to given user options. + * + * clique - the clique to store + * opts - storage options + * + * Returns FALSE if opts->user_function() returned FALSE; otherwise + * returns TRUE. + */ +static boolean store_clique(set_t clique, graph_t *g, clique_options *opts) { + + clique_list_count++; + + /* clique_list[] */ + if (opts->clique_list) { + /* + * This has been a major source of bugs: + * Has clique_list_count been set to 0 before calling + * the recursions? + */ + if (clique_list_count <= 0) { +#ifdef USING_R + error("CLIQUER INTERNAL ERROR: ", + "clique_list_count has negative value!"); +#else + fprintf(stderr,"CLIQUER INTERNAL ERROR: " + "clique_list_count has negative value!\n"); + fprintf(stderr,"Please report as a bug.\n"); + abort(); +#endif + } + if (clique_list_count <= opts->clique_list_length) + opts->clique_list[clique_list_count-1] = + set_copy(opts->clique_list[clique_list_count-1], clique); + } + + /* user_function() */ + if (opts->user_function) { + if (!opts->user_function(clique,g,opts)) { + /* User function requested abort. */ + return FALSE; + } + } + + return TRUE; +} + +/* + * maximalize_clique() + * + * Adds greedily all possible vertices in g to set s to make it a maximal + * clique. + * + * s - clique of vertices to make maximal + * g - graph + * + * Note: Not very optimized (uses a simple O(n^2) routine), but is called + * at maximum once per clique_xxx() call, so it shouldn't matter. + */ +static void maximalize_clique(set_t s,graph_t *g) { + int i,j; + boolean add; + + for (i=0; i < g->n; i++) { + add=TRUE; + for (j=0; j < g->n; j++) { + if (SET_CONTAINS_FAST(s,j) && !GRAPH_IS_EDGE(g,i,j)) { + add=FALSE; + break; + } + } + if (add) { + SET_ADD_ELEMENT(s,i); + } + } + return; +} + + +/* + * is_maximal() + * + * Check whether a clique is maximal or not. + * + * clique - set of vertices in clique + * g - graph + * + * Returns TRUE is clique is a maximal clique of g, otherwise FALSE. + */ +static boolean is_maximal(set_t clique, graph_t *g) { + int i,j; + int *table; + int len; + boolean addable; + + if (temp_count) { + temp_count--; + table=temp_list[temp_count]; + } else { + table=malloc(g->n * sizeof(int)); + } + + len=0; + for (i=0; i < g->n; i++) + if (SET_CONTAINS_FAST(clique,i)) + table[len++]=i; + + for (i=0; i < g->n; i++) { + addable=TRUE; + for (j=0; jtime_function() requests abort). + * + * The returned clique is newly allocated and can be freed by set_free(). + * + * Note: Does NOT use opts->user_function() or opts->clique_list[]. + */ +set_t clique_unweighted_find_single(graph_t *g,int min_size,int max_size, + boolean maximal, clique_options *opts) { + int i; + int *table; + set_t s; + + ENTRANCE_SAVE(); + entrance_level++; + + if (opts==NULL) + opts=&clique_default_options; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(g!=NULL); + ASSERT(min_size>=0); + ASSERT(max_size>=0); + ASSERT((max_size==0) || (min_size <= max_size)); + ASSERT(!((min_size==0) && (max_size>0))); + ASSERT((opts->reorder_function==NULL) || (opts->reorder_map==NULL)); + + if ((max_size>0) && (min_size>max_size)) { + /* state was not changed */ + entrance_level--; + return NULL; + } + + /* + if (clocks_per_sec==0) + clocks_per_sec=sysconf(_SC_CLK_TCK); + ASSERT(clocks_per_sec>0); + */ + + /* Dynamic allocation */ + current_clique=set_new(g->n); + clique_size=malloc(g->n * sizeof(int)); + /* table allocated later */ + temp_list=malloc((g->n+2)*sizeof(int *)); + temp_count=0; + + /* "start clock" */ + /* + gettimeofday(&realtimer,NULL); + times(&cputimer); + */ + + /* reorder */ + if (opts->reorder_function) { + table=opts->reorder_function(g,FALSE); + } else if (opts->reorder_map) { + table=reorder_duplicate(opts->reorder_map,g->n); + } else { + table=reorder_ident(g->n); + } + ASSERT(reorder_is_bijection(table,g->n)); + + + if (unweighted_clique_search_single(table,min_size,g,opts)==0) { + set_free(current_clique); + current_clique=NULL; + goto cleanreturn; + } + if (maximal && (min_size>0)) { + maximalize_clique(current_clique,g); + + if ((max_size > 0) && (set_size(current_clique) > max_size)) { + clique_options localopts; + + s = set_new(g->n); + localopts.time_function = opts->time_function; + localopts.output = opts->output; + localopts.user_function = false_function; + localopts.clique_list = &s; + localopts.clique_list_length = 1; + + for (i=0; i < g->n-1; i++) + if (clique_size[table[i]]>=min_size) + break; + if (unweighted_clique_search_all(table,i,min_size, + max_size,maximal, + g,&localopts)) { + set_free(current_clique); + current_clique=s; + } else { + set_free(current_clique); + current_clique=NULL; + } + } + } + + cleanreturn: + s=current_clique; + + /* Free resources */ + for (i=0; i < temp_count; i++) + free(temp_list[i]); + free(temp_list); + free(table); + free(clique_size); + + ENTRANCE_RESTORE(); + entrance_level--; + + return s; +} + + +/* + * clique_unweighted_find_all() + * + * Find all cliques with size at least min_size and at most max_size. + * + * g - the graph + * min_size - minimum size of cliques to search for. If min_size==0, + * searches for maximum cliques. + * max_size - maximum size of cliques to search for. If max_size==0, no + * upper limit is used. If min_size==0, this must also be 0. + * maximal - require cliques to be maximal cliques + * opts - time printing and clique storage options + * + * Returns the number of cliques found. This can be less than the number + * of cliques in the graph iff opts->time_function() or opts->user_function() + * returns FALSE (request abort). + * + * The cliques found are stored in opts->clique_list[] and + * opts->user_function() is called with them (if non-NULL). The cliques + * stored in opts->clique_list[] are newly allocated, and can be freed + * by set_free(). + */ +int clique_unweighted_find_all(graph_t *g, int min_size, int max_size, + boolean maximal, clique_options *opts) { + int i; + int *table; + int count; + + ENTRANCE_SAVE(); + entrance_level++; + + if (opts==NULL) + opts=&clique_default_options; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(g!=NULL); + ASSERT(min_size>=0); + ASSERT(max_size>=0); + ASSERT((max_size==0) || (min_size <= max_size)); + ASSERT(!((min_size==0) && (max_size>0))); + ASSERT((opts->reorder_function==NULL) || (opts->reorder_map==NULL)); + + if ((max_size>0) && (min_size>max_size)) { + /* state was not changed */ + entrance_level--; + return 0; + } + + /* + if (clocks_per_sec==0) + clocks_per_sec=sysconf(_SC_CLK_TCK); + ASSERT(clocks_per_sec>0); + */ + + /* Dynamic allocation */ + current_clique=set_new(g->n); + clique_size=malloc(g->n * sizeof(int)); + /* table allocated later */ + temp_list=malloc((g->n+2)*sizeof(int *)); + temp_count=0; + + clique_list_count=0; + memset(clique_size,0,g->n * sizeof(int)); + + /* "start clock" */ + /* + gettimeofday(&realtimer,NULL); + times(&cputimer); + */ + + /* reorder */ + if (opts->reorder_function) { + table=opts->reorder_function(g,FALSE); + } else if (opts->reorder_map) { + table=reorder_duplicate(opts->reorder_map,g->n); + } else { + table=reorder_ident(g->n); + } + ASSERT(reorder_is_bijection(table,g->n)); + + + /* Search as normal until there is a chance to find a suitable + * clique. */ + if (unweighted_clique_search_single(table,min_size,g,opts)==0) { + count=0; + goto cleanreturn; + } + + if (min_size==0 && max_size==0) { + min_size=max_size=clique_size[table[g->n-1]]; + maximal=FALSE; /* No need to test, since we're searching + * for maximum cliques. */ + } + if (max_size==0) { + max_size=INT_MAX; + } + + for (i=0; i < g->n-1; i++) + if (clique_size[table[i]] >= min_size) + break; + count=unweighted_clique_search_all(table,i,min_size,max_size, + maximal,g,opts); + + cleanreturn: + /* Free resources */ + for (i=0; itime_function() requests abort). + * + * The returned clique is newly allocated and can be freed by set_free(). + * + * Note: Does NOT use opts->user_function() or opts->clique_list[]. + * Note: Automatically uses clique_unweighted_find_single if all vertex + * weights are the same. + */ +set_t clique_find_single(graph_t *g,int min_weight,int max_weight, + boolean maximal, clique_options *opts) { + int i; + int *table; + set_t s; + + ENTRANCE_SAVE(); + entrance_level++; + + if (opts==NULL) + opts=&clique_default_options; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(g!=NULL); + ASSERT(min_weight>=0); + ASSERT(max_weight>=0); + ASSERT((max_weight==0) || (min_weight <= max_weight)); + ASSERT(!((min_weight==0) && (max_weight>0))); + ASSERT((opts->reorder_function==NULL) || (opts->reorder_map==NULL)); + + if ((max_weight>0) && (min_weight>max_weight)) { + /* state was not changed */ + entrance_level--; + return NULL; + } + + /* + if (clocks_per_sec==0) + clocks_per_sec=sysconf(_SC_CLK_TCK); + ASSERT(clocks_per_sec>0); + */ + + /* Check whether we can use unweighted routines. */ + if (!graph_weighted(g)) { + min_weight=DIV_UP(min_weight,g->weights[0]); + if (max_weight) { + max_weight=DIV_DOWN(max_weight,g->weights[0]); + if (max_weight < min_weight) { + /* state was not changed */ + entrance_level--; + return NULL; + } + } + + weight_multiplier = g->weights[0]; + entrance_level--; + s=clique_unweighted_find_single(g,min_weight,max_weight, + maximal,opts); + ENTRANCE_RESTORE(); + return s; + } + + /* Dynamic allocation */ + current_clique=set_new(g->n); + best_clique=set_new(g->n); + clique_size=malloc(g->n * sizeof(int)); + memset(clique_size, 0, g->n * sizeof(int)); + /* table allocated later */ + temp_list=malloc((g->n+2)*sizeof(int *)); + temp_count=0; + + clique_list_count=0; + + /* "start clock" */ + /* + gettimeofday(&realtimer,NULL); + times(&cputimer); + */ + + /* reorder */ + if (opts->reorder_function) { + table=opts->reorder_function(g,TRUE); + } else if (opts->reorder_map) { + table=reorder_duplicate(opts->reorder_map,g->n); + } else { + table=reorder_ident(g->n); + } + ASSERT(reorder_is_bijection(table,g->n)); + + if (max_weight==0) + max_weight=INT_MAX; + + if (weighted_clique_search_single(table,min_weight,max_weight, + g,opts)==0) { + /* Requested clique has not been found. */ + set_free(best_clique); + best_clique=NULL; + goto cleanreturn; + } + if (maximal && (min_weight>0)) { + maximalize_clique(best_clique,g); + if (graph_subgraph_weight(g,best_clique) > max_weight) { + clique_options localopts; + + localopts.time_function = opts->time_function; + localopts.output = opts->output; + localopts.user_function = false_function; + localopts.clique_list = &best_clique; + localopts.clique_list_length = 1; + + for (i=0; i < g->n-1; i++) + if ((clique_size[table[i]] >= min_weight) || + (clique_size[table[i]] == 0)) + break; + if (!weighted_clique_search_all(table,i,min_weight, + max_weight,maximal, + g,&localopts)) { + set_free(best_clique); + best_clique=NULL; + } + } + } + + cleanreturn: + s=best_clique; + + /* Free resources */ + for (i=0; i < temp_count; i++) + free(temp_list[i]); + free(temp_list); + temp_list=NULL; + temp_count=0; + free(table); + set_free(current_clique); + current_clique=NULL; + free(clique_size); + clique_size=NULL; + + ENTRANCE_RESTORE(); + entrance_level--; + + return s; +} + + + + + +/* + * clique_find_all() + * + * Find all cliques with weight at least min_weight and at most max_weight. + * + * g - the graph + * min_weight - minimum weight of cliques to search for. If min_weight==0, + * searches for maximum weight cliques. + * max_weight - maximum weight of cliques to search for. If max_weight==0, + * no upper limit is used. If min_weight==0, max_weight must + * also be 0. + * maximal - require cliques to be maximal cliques + * opts - time printing and clique storage options + * + * Returns the number of cliques found. This can be less than the number + * of cliques in the graph iff opts->time_function() or opts->user_function() + * returns FALSE (request abort). + * + * The cliques found are stored in opts->clique_list[] and + * opts->user_function() is called with them (if non-NULL). The cliques + * stored in opts->clique_list[] are newly allocated, and can be freed + * by set_free(). + * + * Note: Automatically uses clique_unweighted_find_all if all vertex + * weights are the same. + */ +int clique_find_all(graph_t *g, int min_weight, int max_weight, + boolean maximal, clique_options *opts) { + int i,n; + int *table; + + ENTRANCE_SAVE(); + entrance_level++; + + if (opts==NULL) + opts=&clique_default_options; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(g!=NULL); + ASSERT(min_weight>=0); + ASSERT(max_weight>=0); + ASSERT((max_weight==0) || (min_weight <= max_weight)); + ASSERT(!((min_weight==0) && (max_weight>0))); + ASSERT((opts->reorder_function==NULL) || (opts->reorder_map==NULL)); + + if ((max_weight>0) && (min_weight>max_weight)) { + /* state was not changed */ + entrance_level--; + return 0; + } + + /* + if (clocks_per_sec==0) + clocks_per_sec=sysconf(_SC_CLK_TCK); + ASSERT(clocks_per_sec>0); + */ + + if (!graph_weighted(g)) { + min_weight=DIV_UP(min_weight,g->weights[0]); + if (max_weight) { + max_weight=DIV_DOWN(max_weight,g->weights[0]); + if (max_weight < min_weight) { + /* state was not changed */ + entrance_level--; + return 0; + } + } + + weight_multiplier = g->weights[0]; + entrance_level--; + i=clique_unweighted_find_all(g,min_weight,max_weight,maximal, + opts); + ENTRANCE_RESTORE(); + return i; + } + + /* Dynamic allocation */ + current_clique=set_new(g->n); + best_clique=set_new(g->n); + clique_size=malloc(g->n * sizeof(int)); + memset(clique_size, 0, g->n * sizeof(int)); + /* table allocated later */ + temp_list=malloc((g->n+2)*sizeof(int *)); + temp_count=0; + + /* "start clock" */ + /* + gettimeofday(&realtimer,NULL); + times(&cputimer); + */ + + /* reorder */ + if (opts->reorder_function) { + table=opts->reorder_function(g,TRUE); + } else if (opts->reorder_map) { + table=reorder_duplicate(opts->reorder_map,g->n); + } else { + table=reorder_ident(g->n); + } + ASSERT(reorder_is_bijection(table,g->n)); + + /* First phase */ + n=weighted_clique_search_single(table,min_weight,INT_MAX,g,opts); + if (n==0) { + /* Requested clique has not been found. */ + goto cleanreturn; + } + + if (min_weight==0) { + min_weight=n; + max_weight=n; + maximal=FALSE; /* They're maximum cliques already. */ + } + if (max_weight==0) + max_weight=INT_MAX; + + for (i=0; i < g->n; i++) + if ((clique_size[table[i]] >= min_weight) || + (clique_size[table[i]] == 0)) + break; + + /* Second phase */ + n=weighted_clique_search_all(table,i,min_weight,max_weight,maximal, + g,opts); + + cleanreturn: + /* Free resources */ + for (i=0; i < temp_count; i++) + free(temp_list[i]); + free(temp_list); + free(table); + set_free(current_clique); + set_free(best_clique); + free(clique_size); + + ENTRANCE_RESTORE(); + entrance_level--; + + return n; +} + + + + + + + + + + + + + + + + +#if 0 +/* + * clique_print_time() + * + * Reports current running information every 0.1 seconds or when values + * change. + * + * level - re-entrance level + * i - current recursion level + * n - maximum recursion level + * max - weight of heaviest clique found + * cputime - CPU time used in algorithm so far + * realtime - real time used in algorithm so far + * opts - prints information to (FILE *)opts->output (or stdout if NULL) + * + * Returns always TRUE (ie. never requests abort). + */ +boolean clique_print_time(int level, int i, int n, int max, + double cputime, double realtime, + clique_options *opts) { + static float prev_time=100; + static int prev_i=100; + static int prev_max=100; + static int prev_level=0; + FILE *fp=opts->output; + int j; + + if (fp==NULL) + fp=stdout; + + if (ABS(prev_time-realtime)>0.1 || i==n || ioutput (or stdout if NULL) + * + * Returns always TRUE (ie. never requests abort). + */ +boolean clique_print_time_always(int level, int i, int n, int max, + double cputime, double realtime, + clique_options *opts) { + static float prev_time=100; + static int prev_i=100; + FILE *fp=opts->output; + int j; + + if (fp==NULL) + fp=stdout; + + for (j=1; j + +#include "set.h" +#include "graph.h" +#include "reorder.h" + +typedef struct _clique_options clique_options; +struct _clique_options { + int *(*reorder_function)(graph_t *, boolean); + int *reorder_map; + + /* arguments: level, n, max, user_time, system_time, opts */ + boolean (*time_function)(int,int,int,int,double,double, + clique_options *); + FILE *output; + + boolean (*user_function)(set_t,graph_t *,clique_options *); + void *user_data; + set_t *clique_list; + int clique_list_length; +}; + +/* Weighted clique functions */ +extern int clique_max_weight(graph_t *g,clique_options *opts); +extern set_t clique_find_single(graph_t *g,int min_weight,int max_weight, + boolean maximal, clique_options *opts); +extern int clique_find_all(graph_t *g, int req_weight, boolean exact, + boolean maximal, clique_options *opts); + +/* Unweighted clique functions */ +#define clique_unweighted_max_size clique_unweighted_max_weight +extern int clique_unweighted_max_weight(graph_t *g, clique_options *opts); +extern set_t clique_unweighted_find_single(graph_t *g,int min_size, + int max_size,boolean maximal, + clique_options *opts); +extern int clique_unweighted_find_all(graph_t *g, int min_size, int max_size, + boolean maximal, clique_options *opts); + +/* Time printing functions */ +/* +extern boolean clique_print_time(int level, int i, int n, int max, + double cputime, double realtime, + clique_options *opts); +extern boolean clique_print_time_always(int level, int i, int n, int max, + double cputime, double realtime, + clique_options *opts); +*/ + +/* Alternate spelling (let's be a little forgiving): */ +#define cliquer_options clique_options +#define cliquer_default_options clique_default_options + +#endif /* !CLIQUER_H */ diff --git a/src/cliquer/cliquer_graph.c b/src/cliquer/cliquer_graph.c new file mode 100644 index 0000000..a409295 --- /dev/null +++ b/src/cliquer/cliquer_graph.c @@ -0,0 +1,768 @@ + +/* + * This file contains the graph handling routines. + * + * Copyright (C) 2002 Sampo Niskanen, Patric ÖstergÃ¥rd. + * Licensed under the GNU GPL, read the file LICENSE for details. + */ + + +#include +#include +#include +#include "graph.h" + +#ifdef USING_R +#include +#endif + +/* +static graph_t *graph_read_dimacs_binary(FILE *fp,char *firstline); +static graph_t *graph_read_dimacs_ascii(FILE *fp,char *firstline); +*/ + + +/* + * graph_new() + * + * Returns a newly allocated graph with n vertices all with weight 1, + * and no edges. + */ +graph_t *graph_new(int n) { + graph_t *g; + int i; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(n>0); + + g=malloc(sizeof(graph_t)); + g->n=n; + g->edges=malloc(g->n * sizeof(set_t)); + g->weights=malloc(g->n * sizeof(int)); + for (i=0; i < g->n; i++) { + g->edges[i]=set_new(n); + g->weights[i]=1; + } + return g; +} + +/* + * graph_free() + * + * Frees the memory associated with the graph g. + */ +void graph_free(graph_t *g) { + int i; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(g!=NULL); + ASSERT(g->n > 0); + + for (i=0; i < g->n; i++) { + set_free(g->edges[i]); + } + free(g->weights); + free(g->edges); + free(g); + return; +} + + +/* + * graph_resize() + * + * Resizes graph g to given size. If size > g->n, the new vertices are + * not connected to any others and their weights are set to 1. + * If size < g->n, the last g->n - size vertices are removed. + */ +void graph_resize(graph_t *g, int size) { + int i; + + ASSERT(g!=NULL); + ASSERT(g->n > 0); + ASSERT(size > 0); + + if (g->n == size) + return; + + /* Free/alloc extra edge-sets */ + for (i=size; i < g->n; i++) + set_free(g->edges[i]); + g->edges=realloc(g->edges, size * sizeof(set_t)); + for (i=g->n; i < size; i++) + g->edges[i]=set_new(size); + + /* Resize original sets */ + for (i=0; i < MIN(g->n,size); i++) { + g->edges[i]=set_resize(g->edges[i],size); + } + + /* Weights */ + g->weights=realloc(g->weights,size * sizeof(int)); + for (i=g->n; iweights[i]=1; + + g->n=size; + return; +} + +/* + * graph_crop() + * + * Resizes the graph so as to remove all highest-valued isolated vertices. + */ +void graph_crop(graph_t *g) { + int i; + + for (i=g->n-1; i>=1; i--) + if (set_size(g->edges[i])>0) + break; + graph_resize(g,i+1); + return; +} + + +/* + * graph_weighted() + * + * Returns TRUE if all vertex weights of graph g are all the same. + * + * Note: Does NOT require weights to be 1. + */ +boolean graph_weighted(graph_t *g) { + int i,w; + + w=g->weights[0]; + for (i=1; i < g->n; i++) + if (g->weights[i] != w) + return TRUE; + return FALSE; +} + +/* + * graph_edge_count() + * + * Returns the number of edges in graph g. + */ +int graph_edge_count(graph_t *g) { + int i; + int count=0; + + for (i=0; i < g->n; i++) { + count += set_size(g->edges[i]); + } + return count/2; +} + + +#if 0 +/* + * graph_write_dimacs_ascii_file() + * + * Writes an ASCII dimacs-format file of graph g, with comment, to + * given file. + * + * Returns TRUE if successful, FALSE if an error occurred. + */ +boolean graph_write_dimacs_ascii_file(graph_t *g, char *comment, char *file) { + FILE *fp; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(file!=NULL); + + if ((fp=fopen(file,"wb"))==NULL) + return FALSE; + if (!graph_write_dimacs_ascii(g,comment,fp)) { + fclose(fp); + return FALSE; + } + fclose(fp); + return TRUE; +} + +/* + * graph_write_dimacs_ascii() + * + * Writes an ASCII dimacs-format file of graph g, with comment, to the + * file stream fp. + * + * Returns TRUE if successful, FALSE if an error occurred. + */ +boolean graph_write_dimacs_ascii(graph_t *g, char *comment, FILE *fp) { + int i,j; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(graph_test(g,NULL)); + ASSERT(fp!=NULL); + + if (comment) + fprintf(fp,"c %s\n",comment); + fprintf(fp,"p edge %d %d\n",g->n,graph_edge_count(g)); + for (i=0; i < g->n; i++) + if (g->weights[i]!=1) + fprintf(fp,"n %d %d\n",i+1,g->weights[i]); + for (i=0; i < g->n; i++) + for (j=0; j= headersize) { \ + headersize+=1024; \ + header=realloc(header,headersize); \ +} \ +strncat(header,s,1000); \ +headerlength+=strlen(s); + +boolean graph_write_dimacs_binary(graph_t *g, char *comment,FILE *fp) { + char *buf; + char *header=NULL; + int headersize=0; + int headerlength=0; + int i,j; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + ASSERT(graph_test(g,NULL)); + ASSERT(fp!=NULL); + + buf=malloc(MAX(1024,g->n/8+1)); + header=malloc(1024); + header[0]=0; + headersize=1024; + if (comment) { + strcpy(buf,"c "); + strncat(buf,comment,1000); + strcat(buf,"\n"); + STR_APPEND(buf); + } + sprintf(buf,"p edge %d %d\n",g->n,graph_edge_count(g)); + STR_APPEND(buf); + for (i=0; i < g->n; i++) { + if (g->weights[i]!=1) { + sprintf(buf,"n %d %d\n",i+1,g->weights[i]); + STR_APPEND(buf); + } + } + + fprintf(fp,"%d\n",(int)strlen(header)); + fprintf(fp,"%s",header); + free(header); + + for (i=0; i < g->n; i++) { + memset(buf,0,i/8+1); + for (j=0; j=strlen(str)) /* blank line */ + return TRUE; + if (str[i+1]!=0 && !isspace(str[i+1])) /* not 1-char field */ + return FALSE; + + switch (str[i]) { + case 'c': + return TRUE; + case 'p': + if (g->n != 0) + return FALSE; + if (sscanf(str," p %15s %d %d %2s",tmp,&(g->n),&i,tmp)!=3) + return FALSE; + if (g->n <= 0) + return FALSE; + g->edges=calloc(g->n,sizeof(set_t)); + for (i=0; in; i++) + g->edges[i]=set_new(g->n); + g->weights=calloc(g->n,sizeof(int)); + for (i=0; in; i++) + g->weights[i]=1; + return TRUE; + case 'n': + if ((g->n <= 0) || (g->weights == NULL)) + return FALSE; + if (sscanf(str," n %d %d %2s",&i,&w,tmp)!=2) + return FALSE; + if (i<1 || i>g->n) + return FALSE; + if (w<=0) + return FALSE; + g->weights[i-1]=w; + return TRUE; + case 'e': + if ((g->n <= 0) || (g->edges == NULL)) + return FALSE; + if (sscanf(str," e %d %d %2s",&i,&j,tmp)!=2) + return FALSE; + if (i<1 || j<1 || i>g->n || j>g->n) + return FALSE; + if (i==j) /* We want antireflexive graphs. */ + return TRUE; + GRAPH_ADD_EDGE(g,i-1,j-1); + return TRUE; + case 'd': + case 'v': + case 'x': + return TRUE; + default: + fprintf(stderr,"Warning: ignoring field '%c' in " + "input.\n",str[i]); + return TRUE; + } +} + + +/* + * graph_read_dimacs_binary() + * + * Reads a dimacs-format binary file from file stream fp with the first + * line being firstline. + * + * Returns the newly-allocated graph or NULL if an error occurred. + * + * TODO: This function leaks memory when reading erroneous files. + */ +static graph_t *graph_read_dimacs_binary(FILE *fp,char *firstline) { + int length=0; + graph_t *g; + int i,j; + char *buffer; + char *start; + char *end; + char **buf; + char tmp[10]; + + if (sscanf(firstline," %d %2s",&length,tmp)!=1) + return NULL; + if (length<=0) { + fprintf(stderr,"Malformed preamble: preamble size < 0.\n"); + return NULL; + } + buffer=malloc(length+2); + if (fread(buffer,1,length,fp)n <= 0) { + fprintf(stderr,"Malformed preamble: number of " + "vertices <= 0\n"); + free(g); + return NULL; + } + + /* Binary part. */ + buf=calloc(g->n,sizeof(char*)); + for (i=0; i < g->n; i++) { + buf[i]=calloc(g->n,1); + if (fread(buf[i],1,i/8+1,fp) < (i/8+1)) { + fprintf(stderr,"Unexpected end of file when " + "reading graph.\n"); + return NULL; + } + } + + for (i=0; i < g->n; i++) { + for (j=0; jn <= 0) { + free(g); + fprintf(stderr,"Unexpected end of file when reading graph.\n"); + return NULL; + } + + return g; +} +#endif + + +#ifndef USING_R +/* + * graph_print() + * + * Prints a representation of the graph g to stdout (along with any errors + * noticed). Mainly useful for debugging purposes and trivial output. + * + * The output consists of a first line describing the dimensions and then + * one line per vertex containing the vertex number (numbered 0,...,n-1), + * the vertex weight (if the graph is weighted), "->" and then a list + * of all vertices it is adjacent to. + */ +void graph_print(graph_t *g) { + int i,j; + int asymm=0; + int refl=0; + int nonpos=0; + int extra=0; + unsigned int weight=0; + boolean weighted; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + + if (g==NULL) { + printf(" WARNING: Graph pointer is NULL!\n"); + return; + } + if (g->n <= 0) { + printf(" WARNING: Graph has %d vertices " + "(should be positive)!\n",g->n); + return; + } + + weighted=graph_weighted(g); + + printf("%s graph has %d vertices, %d edges (density %.2f).\n", + weighted?"Weighted":((g->weights[0]==1)? + "Unweighted":"Semi-weighted"), + g->n,graph_edge_count(g), + (float)graph_edge_count(g)/((float)(g->n - 1)*(g->n)/2)); + + for (i=0; i < g->n; i++) { + printf("%2d",i); + if (weighted) { + printf(" w=%d",g->weights[i]); + if (g->weights[i] <= 0) { + printf("*NON-POSITIVE*"); + nonpos++; + } + } + if (weight < INT_MAX) + weight+=g->weights[i]; + printf(" ->"); + for (j=0; j < g->n; j++) { + if (SET_CONTAINS_FAST(g->edges[i],j)) { + printf(" %d",j); + if (i==j) { + printf("*REFLEXIVE*"); + refl++; + } + if (!SET_CONTAINS_FAST(g->edges[j],i)) { + printf("*ASYMMERTIC*"); + asymm++; + } + } + } + for (j=g->n; j < SET_ARRAY_LENGTH(g->edges[i])*ELEMENTSIZE; + j++) { + if (SET_CONTAINS_FAST(g->edges[i],j)) { + printf(" %d*NON-EXISTENT*",j); + extra++; + } + } + printf("\n"); + } + + if (asymm) + printf(" WARNING: Graph contained %d asymmetric edges!\n", + asymm); + if (refl) + printf(" WARNING: Graph contained %d reflexive edges!\n", + refl); + if (nonpos) + printf(" WARNING: Graph contained %d non-positive vertex " + "weights!\n",nonpos); + if (extra) + printf(" WARNING: Graph contained %d edges to " + "non-existent vertices!\n",extra); + if (weight>=INT_MAX) + printf(" WARNING: Total graph weight >= INT_MAX!\n"); + return; +} + +#endif + +/* + * graph_test() + * + * Tests graph g to be valid. Checks that g is non-NULL, the edges are + * symmetric and anti-reflexive, and that all vertex weights are positive. + * If output is non-NULL, prints a few lines telling the status of the graph + * to file descriptor output. + * + * Returns TRUE if the graph is valid, FALSE otherwise. + */ +boolean graph_test(graph_t *g,FILE *output) { + int i,j; + int edges=0; + int asymm=0; + int nonpos=0; + int refl=0; + int extra=0; + unsigned int weight=0; + boolean weighted; + + ASSERT((sizeof(setelement)*8)==ELEMENTSIZE); + + if (g==NULL) { + if (output) + fprintf(output," WARNING: Graph pointer is NULL!\n"); + return FALSE; + } + + weighted=graph_weighted(g); + + for (i=0; i < g->n; i++) { + if (g->edges[i]==NULL) { + if (output) + fprintf(output," WARNING: Graph edge set " + "NULL!\n" + " (further warning suppressed)\n"); + return FALSE; + } + if (SET_MAX_SIZE(g->edges[i]) < g->n) { + if (output) + fprintf(output," WARNING: Graph edge set " + "too small!\n" + " (further warnings suppressed)\n"); + return FALSE; + } + for (j=0; j < g->n; j++) { + if (SET_CONTAINS_FAST(g->edges[i],j)) { + edges++; + if (i==j) { + refl++; + } + if (!SET_CONTAINS_FAST(g->edges[j],i)) { + asymm++; + } + } + } + for (j=g->n; j < SET_ARRAY_LENGTH(g->edges[i])*ELEMENTSIZE; + j++) { + if (SET_CONTAINS_FAST(g->edges[i],j)) + extra++; + } + if (g->weights[i] <= 0) + nonpos++; + if (weightweights[i]; + } + + edges/=2; /* Each is counted twice. */ + + if (output) { + /* Semi-weighted means all weights are equal, but not 1. */ + fprintf(output,"%s graph has %d vertices, %d edges " + "(density %.2f).\n", + weighted?"Weighted": + ((g->weights[0]==1)?"Unweighted":"Semi-weighted"), + g->n,edges,(float)edges/((float)(g->n - 1)*(g->n)/2)); + + if (asymm) + fprintf(output," WARNING: Graph contained %d " + "asymmetric edges!\n",asymm); + if (refl) + fprintf(output," WARNING: Graph contained %d " + "reflexive edges!\n",refl); + if (nonpos) + fprintf(output," WARNING: Graph contained %d " + "non-positive vertex weights!\n",nonpos); + if (extra) + fprintf(output," WARNING: Graph contained %d edges " + "to non-existent vertices!\n",extra); + if (weight>=INT_MAX) + fprintf(output," WARNING: Total graph weight >= " + "INT_MAX!\n"); + if (asymm==0 && refl==0 && nonpos==0 && extra==0 && + weight=INT_MAX) + return FALSE; + + return TRUE; +} + + +/* + * graph_test_regular() + * + * Returns the vertex degree for regular graphs, or -1 if the graph is + * not regular. + */ +int graph_test_regular(graph_t *g) { + int i,n; + + n=set_size(g->edges[0]); + + for (i=1; i < g->n; i++) { + if (set_size(g->edges[i]) != n) + return -1; + } + return n; +} + diff --git a/src/cliquer/cliquerconf.h b/src/cliquer/cliquerconf.h new file mode 100644 index 0000000..47d923b --- /dev/null +++ b/src/cliquer/cliquerconf.h @@ -0,0 +1,68 @@ + +#ifndef CLIQUERCONF_H +#define CLIQUERCONF_H + +/* + * setelement is the basic memory type used in sets. It is often fastest + * to be as large as can fit into the CPU registers. + * + * ELEMENTSIZE is the size of one setelement, measured in bits. It must + * be either 16, 32 or 64 (otherwise additional changes must be made to + * the source). + * + * The default is to use "unsigned long int" and attempt to guess the + * size using , which should work pretty well. Check functioning + * with "make test". + */ + +/* typedef unsigned long int setelement; */ +/* #define ELEMENTSIZE 64 */ + + +/* + * INLINE is a command prepended to function declarations to instruct the + * compiler to inline the function. If inlining is not desired, define blank. + * + * The default is to use "inline", which is recognized by most compilers. + */ + +/* #define INLINE */ +/* #define INLINE __inline__ */ +#if __STDC_VERSION__ >= 199901L + #define INLINE inline +#else + #if defined(_MSC_VER) + #define INLINE __inline + #elif defined(__GNUC__) + #define INLINE __inline__ + #else + #define INLINE + #endif +#endif + + +/* + * Set handling functions are defined as static functions in set.h for + * performance reasons. This may cause unnecessary warnings from the + * compiler. Some compilers (such as GCC) have the possibility to turn + * off the warnings on a per-function basis using a flag prepended to + * the function declaration. + * + * The default is to use the correct attribute when compiling with GCC, + * or no flag otherwise. + */ + +/* #define UNUSED_FUNCTION __attribute__((unused)) */ +/* #define UNUSED_FUNCTION */ + + +/* + * Uncommenting the following will disable all assertions (checks that + * function arguments and other variables are correct). This is highly + * discouraged, as it allows bugs to go unnoticed easier. The assertions + * are set so that they do not slow down programs notably. + */ + +/* #define ASSERT(x) */ + +#endif /* !CLIQUERCONF_H */ diff --git a/src/cliquer/graph.h b/src/cliquer/graph.h new file mode 100644 index 0000000..956f2a1 --- /dev/null +++ b/src/cliquer/graph.h @@ -0,0 +1,75 @@ + +#ifndef CLIQUER_GRAPH_H +#define CLIQUER_GRAPH_H + +#include "set.h" + +typedef struct _graph_t graph_t; +struct _graph_t { + int n; /* Vertices numbered 0...n-1 */ + set_t *edges; /* A list of n sets (the edges). */ + int *weights; /* A list of n vertex weights. */ +}; + + +#define GRAPH_IS_EDGE_FAST(g,i,j) (SET_CONTAINS_FAST((g)->edges[(i)],(j))) +#define GRAPH_IS_EDGE(g,i,j) (((i)<((g)->n))?SET_CONTAINS((g)->edges[(i)], \ + (j)):FALSE) +#define GRAPH_ADD_EDGE(g,i,j) do { \ + SET_ADD_ELEMENT((g)->edges[(i)],(j)); \ + SET_ADD_ELEMENT((g)->edges[(j)],(i)); \ +} while (FALSE) +#define GRAPH_DEL_EDGE(g,i,j) do { \ + SET_DEL_ELEMENT((g)->edges[(i)],(j)); \ + SET_DEL_ELEMENT((g)->edges[(j)],(i)); \ +} while (FALSE) + + +extern graph_t *graph_new(int n); +extern void graph_free(graph_t *g); +extern void graph_resize(graph_t *g, int size); +extern void graph_crop(graph_t *g); + +extern boolean graph_weighted(graph_t *g); +extern int graph_edge_count(graph_t *g); + +/* +extern graph_t *graph_read_dimacs(FILE *fp); +extern graph_t *graph_read_dimacs_file(char *file); +extern boolean graph_write_dimacs_ascii(graph_t *g, char *comment,FILE *fp); +extern boolean graph_write_dimacs_ascii_file(graph_t *g,char *comment, + char *file); +extern boolean graph_write_dimacs_binary(graph_t *g, char *comment,FILE *fp); +extern boolean graph_write_dimacs_binary_file(graph_t *g, char *comment, + char *file); +*/ + +extern void graph_print(graph_t *g); +extern boolean graph_test(graph_t *g, FILE *output); +extern int graph_test_regular(graph_t *g); + +UNUSED_FUNCTION INLINE +static int graph_subgraph_weight(graph_t *g,set_t s) { + int i,j; + int count=0; + setelement e; + + for (i=0; iweights[i*ELEMENTSIZE+j]; + e = e>>1; + } + } + } + return count; +} + +UNUSED_FUNCTION INLINE +static int graph_vertex_degree(graph_t *g, int v) { + return set_size(g->edges[v]); +} + +#endif /* !CLIQUER_GRAPH_H */ diff --git a/src/cliquer/misc.h b/src/cliquer/misc.h new file mode 100644 index 0000000..8022103 --- /dev/null +++ b/src/cliquer/misc.h @@ -0,0 +1,73 @@ + +#ifndef CLIQUER_MISC_H +#define CLIQUER_MISC_H + +#include "cliquerconf.h" + +/* + * We #define boolean instead of using a typedef because nauty.h uses it + * also. AFAIK, there is no way to check for an existing typedef, and + * re-typedefing is illegal (even when using exactly the same datatype!). + */ +#ifndef boolean +#define boolean int +#endif + + +/* + * The original cliquer source has some functions incorrectly marked as unused, + * thus leave this undefined. + */ +#define UNUSED_FUNCTION + + +/* + * Default inlining directive: "inline" + */ +#ifndef INLINE +#define INLINE inline +#endif + + +#include +#include + +#ifndef ASSERT +#ifdef USING_R +#include +#define ASSERT(expr) \ + if (!(expr)) { \ + error("cliquer file %s: line %d: assertion failed: " \ + "(%s)\n",__FILE__,__LINE__,#expr); \ + } +#else +#define ASSERT(expr) \ + if (!(expr)) { \ + fprintf(stderr,"cliquer file %s: line %d: assertion failed: " \ + "(%s)\n",__FILE__,__LINE__,#expr); \ + abort(); \ + } +#endif +#endif /* !ASSERT */ + + +#ifndef FALSE +#define FALSE (0) +#endif +#ifndef TRUE +#define TRUE (!FALSE) +#endif + + +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif +#ifndef ABS +#define ABS(v) (((v)<0)?(-(v)):(v)) +#endif + +#endif /* !CLIQUER_MISC_H */ + diff --git a/src/cliquer/reorder.c b/src/cliquer/reorder.c new file mode 100644 index 0000000..c84df58 --- /dev/null +++ b/src/cliquer/reorder.c @@ -0,0 +1,425 @@ + +/* + * This file contains the vertex reordering routines. + * + * Copyright (C) 2002 Sampo Niskanen, Patric ÖstergÃ¥rd. + * Licensed under the GNU GPL, read the file LICENSE for details. + */ + +#include "reorder.h" + +#include + +#include + +#include + + +/* + * reorder_set() + * + * Reorders the set s with a function i -> order[i]. + * + * Note: Assumes that order is the same size as SET_MAX_SIZE(s). + */ +void reorder_set(set_t s,int *order) { + set_t tmp; + int i,j; + setelement e; + + ASSERT(reorder_is_bijection(order,SET_MAX_SIZE(s))); + + tmp=set_new(SET_MAX_SIZE(s)); + + for (i=0; i<(SET_MAX_SIZE(s)/ELEMENTSIZE); i++) { + e=s[i]; + if (e==0) + continue; + for (j=0; j>1; + } + } + if (SET_MAX_SIZE(s)%ELEMENTSIZE) { + e=s[i]; + for (j=0; j<(SET_MAX_SIZE(s)%ELEMENTSIZE); j++) { + if (e&1) { + SET_ADD_ELEMENT(tmp,order[i*ELEMENTSIZE+j]); + } + e = e>>1; + } + } + set_copy(s,tmp); + set_free(tmp); + return; +} + + +/* + * reorder_graph() + * + * Reorders the vertices in the graph with function i -> order[i]. + * + * Note: Assumes that order is of size g->n. + */ +void reorder_graph(graph_t *g, int *order) { + int i; + set_t *tmp_e; + int *tmp_w; + + ASSERT(reorder_is_bijection(order,g->n)); + + tmp_e=malloc(g->n * sizeof(set_t)); + tmp_w=malloc(g->n * sizeof(int)); + for (i=0; in; i++) { + reorder_set(g->edges[i],order); + tmp_e[order[i]]=g->edges[i]; + tmp_w[order[i]]=g->weights[i]; + } + for (i=0; in; i++) { + g->edges[i]=tmp_e[i]; + g->weights[i]=tmp_w[i]; + } + free(tmp_e); + free(tmp_w); + return; +} + + + +/* + * reorder_duplicate() + * + * Returns a newly allocated duplicate of the given ordering. + */ +int *reorder_duplicate(int *order,int n) { + int *new; + + new=malloc(n*sizeof(int)); + memcpy(new,order,n*sizeof(int)); + return new; +} + +/* + * reorder_invert() + * + * Inverts the given ordering so that new[old[i]]==i. + * + * Note: Asserts that order is a bijection. + */ +void reorder_invert(int *order,int n) { + int *new; + int i; + + ASSERT(reorder_is_bijection(order,n)); + + new=malloc(n*sizeof(int)); + for (i=0; i {0,...,n-1}. + * + * Returns TRUE if it is a bijection, FALSE otherwise. + */ +boolean reorder_is_bijection(int *order,int n) { + boolean *used; + int i; + + used=calloc(n,sizeof(boolean)); + for (i=0; i=n) { + free(used); + return FALSE; + } + if (used[order[i]]) { + free(used); + return FALSE; + } + used[order[i]]=TRUE; + } + for (i=0; in); +} + +/* + * reorder_by_reverse() + * + * Returns a reverse identity ordering. + */ +int *reorder_by_reverse(graph_t *g,boolean weighted) { + int i; + int *order; + + order=malloc(g->n * sizeof(int)); + for (i=0; i < g->n; i++) + order[i]=g->n-i-1; + return order; +} + +/* + * reorder_by_greedy_coloring() + * + * Equivalent to reorder_by_weighted_greedy_coloring or + * reorder_by_unweighted_greedy_coloring according to the value of weighted. + */ +int *reorder_by_greedy_coloring(graph_t *g,boolean weighted) { + if (weighted) + return reorder_by_weighted_greedy_coloring(g,weighted); + else + return reorder_by_unweighted_greedy_coloring(g,weighted); +} + + +/* + * reorder_by_unweighted_greedy_coloring() + * + * Returns an ordering for the graph g by coloring the clique one + * color at a time, always adding the vertex of largest degree within + * the uncolored graph, and numbering these vertices 0, 1, ... + * + * Experimentally efficient for use with unweighted graphs. + */ +int *reorder_by_unweighted_greedy_coloring(graph_t *g,boolean weighted) { + int i,j,v; + boolean *tmp_used; + int *degree; /* -1 for used vertices */ + int *order; + int maxdegree,maxvertex=0; + boolean samecolor; + + tmp_used=calloc(g->n,sizeof(boolean)); + degree=calloc(g->n,sizeof(int)); + order=calloc(g->n,sizeof(int)); + + for (i=0; i < g->n; i++) { + for (j=0; j < g->n; j++) { + ASSERT(!((i==j) && GRAPH_IS_EDGE(g,i,j))); + if (GRAPH_IS_EDGE(g,i,j)) + degree[i]++; + } + } + + v=0; + while (v < g->n) { + /* Reset tmp_used. */ + memset(tmp_used,0,g->n * sizeof(boolean)); + + do { + /* Find vertex to be colored. */ + maxdegree=0; + samecolor=FALSE; + for (i=0; i < g->n; i++) { + if (!tmp_used[i] && degree[i] >= maxdegree) { + maxvertex=i; + maxdegree=degree[i]; + samecolor=TRUE; + } + } + if (samecolor) { + order[v]=maxvertex; + degree[maxvertex]=-1; + v++; + + /* Mark neighbors not to color with same + * color and update neighbor degrees. */ + for (i=0; i < g->n; i++) { + if (GRAPH_IS_EDGE(g,maxvertex,i)) { + tmp_used[i]=TRUE; + degree[i]--; + } + } + } + } while (samecolor); + } + + free(tmp_used); + free(degree); + return order; +} + +/* + * reorder_by_weighted_greedy_coloring() + * + * Returns an ordering for the graph g by coloring the clique one + * color at a time, always adding the vertex that (in order of importance): + * 1. has the minimum weight in the remaining graph + * 2. has the largest sum of weights surrounding the vertex + * + * Experimentally efficient for use with weighted graphs. + */ +int *reorder_by_weighted_greedy_coloring(graph_t *g, boolean weighted) { + int i,j,p=0; + int cnt; + int *nwt; /* Sum of surrounding vertices' weights */ + int min_wt,max_nwt; + boolean *used; + int *order; + + nwt=malloc(g->n * sizeof(int)); + order=malloc(g->n * sizeof(int)); + used=calloc(g->n,sizeof(boolean)); + + for (i=0; i < g->n; i++) { + nwt[i]=0; + for (j=0; j < g->n; j++) + if (GRAPH_IS_EDGE(g, i, j)) + nwt[i] += g->weights[j]; + } + + for (cnt=0; cnt < g->n; cnt++) { + min_wt=INT_MAX; + max_nwt=-1; + for (i=g->n-1; i>=0; i--) + if ((!used[i]) && (g->weights[i] < min_wt)) + min_wt=g->weights[i]; + for (i=g->n-1; i>=0; i--) { + if (used[i] || (g->weights[i] > min_wt)) + continue; + if (nwt[i] > max_nwt) { + max_nwt=nwt[i]; + p=i; + } + } + order[cnt]=p; + used[p]=TRUE; + for (j=0; j < g->n; j++) + if ((!used[j]) && (GRAPH_IS_EDGE(g, p, j))) + nwt[j] -= g->weights[p]; + } + + free(nwt); + free(used); + + ASSERT(reorder_is_bijection(order,g->n)); + + return order; +} + +/* + * reorder_by_degree() + * + * Returns a reordering of the graph g so that the vertices with largest + * degrees (most neighbors) are first. + */ +int *reorder_by_degree(graph_t *g, boolean weighted) { + int i,j,v; + int *degree; + int *order; + int maxdegree,maxvertex=0; + + degree=calloc(g->n,sizeof(int)); + order=calloc(g->n,sizeof(int)); + + for (i=0; i < g->n; i++) { + for (j=0; j < g->n; j++) { + ASSERT(!((i==j) && GRAPH_IS_EDGE(g,i,j))); + if (GRAPH_IS_EDGE(g,i,j)) + degree[i]++; + } + } + + for (v=0; v < g->n; v++) { + maxdegree=0; + for (i=0; i < g->n; i++) { + if (degree[i] >= maxdegree) { + maxvertex=i; + maxdegree=degree[i]; + } + } + order[v]=maxvertex; + degree[maxvertex]=-1; /* used */ +/*** Max. degree withing unselected graph: + for (i=0; i < g->n; i++) { + if (GRAPH_IS_EDGE(g,maxvertex,i)) + degree[i]--; + } +***/ + } + + free(degree); + return order; +} + +/* + * reorder_by_random() + * + * Returns a random reordering for graph g. + * Note: Used the functions rand() and srand() to generate the random + * numbers. srand() is re-initialized every time reorder_by_random() + * is called using the system time. + */ +int *reorder_by_random(graph_t *g, boolean weighted) { + int i,r; + int *new; + boolean *used; + + new=calloc(g->n, sizeof(int)); + used=calloc(g->n, sizeof(boolean)); + for (i=0; i < g->n; i++) { + do { + r = igraph_rng_get_integer(igraph_rng_default(), 0, g->n - 1); + } while (used[r]); + new[i]=r; + used[r]=TRUE; + } + free(used); + return new; +} + diff --git a/src/cliquer/reorder.h b/src/cliquer/reorder.h new file mode 100644 index 0000000..5c06d31 --- /dev/null +++ b/src/cliquer/reorder.h @@ -0,0 +1,26 @@ + +#ifndef CLIQUER_REORDER_H +#define CLIQUER_REORDER_H + +#include "set.h" +#include "graph.h" + +extern void reorder_set(set_t s,int *order); +extern void reorder_graph(graph_t *g, int *order); +extern int *reorder_duplicate(int *order,int n); +extern void reorder_invert(int *order,int n); +extern void reorder_reverse(int *order,int n); +extern int *reorder_ident(int n); +extern boolean reorder_is_bijection(int *order,int n); + + +#define reorder_by_default reorder_by_greedy_coloring +extern int *reorder_by_greedy_coloring(graph_t *g, boolean weighted); +extern int *reorder_by_weighted_greedy_coloring(graph_t *g, boolean weighted); +extern int *reorder_by_unweighted_greedy_coloring(graph_t *g,boolean weighted); +extern int *reorder_by_degree(graph_t *g, boolean weighted); +extern int *reorder_by_random(graph_t *g, boolean weighted); +extern int *reorder_by_ident(graph_t *g, boolean weighted); +extern int *reorder_by_reverse(graph_t *g, boolean weighted); + +#endif /* !CLIQUER_REORDER_H */ diff --git a/src/cliquer/set.h b/src/cliquer/set.h new file mode 100644 index 0000000..721d77b --- /dev/null +++ b/src/cliquer/set.h @@ -0,0 +1,389 @@ + +/* + * This file contains the set handling routines. + * + * Copyright (C) 2002 Sampo Niskanen, Patric ÖstergÃ¥rd. + * Licensed under the GNU GPL, read the file LICENSE for details. + */ + +#ifndef CLIQUER_SET_H +#define CLIQUER_SET_H + +#include +#include +#include +#include +#include "misc.h" + +/* + * Sets are arrays of setelement's (typically unsigned long int's) with + * representative bits for each value they can contain. The values + * are numbered 0,...,n-1. + */ + + +/*** Variable types and constants. ***/ + + +/* + * If setelement hasn't been declared: + * - use "unsigned long int" as setelement + * - try to deduce size from ULONG_MAX + */ + +#ifndef ELEMENTSIZE +typedef unsigned long int setelement; +# if (ULONG_MAX == 65535) +# define ELEMENTSIZE 16 +# elif (ULONG_MAX == 4294967295) +# define ELEMENTSIZE 32 +# else +# define ELEMENTSIZE 64 +# endif +#endif /* !ELEMENTSIZE */ + +typedef setelement * set_t; + + +/*** Counting amount of 1 bits in a setelement ***/ + +/* Array for amount of 1 bits in a byte. */ +static int set_bit_count[256] = { + 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4, + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, + 4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8 }; + +/* The following macros assume that all higher bits are 0. + * They may in some cases be useful also on with other ELEMENTSIZE's, + * so we define them all. */ +#define SET_ELEMENT_BIT_COUNT_8(a) (set_bit_count[(a)]) +#define SET_ELEMENT_BIT_COUNT_16(a) (set_bit_count[(a)>>8] + \ + set_bit_count[(a)&0xFF]) +#define SET_ELEMENT_BIT_COUNT_32(a) (set_bit_count[(a)>>24] + \ + set_bit_count[((a)>>16)&0xFF] + \ + set_bit_count[((a)>>8)&0xFF] + \ + set_bit_count[(a)&0xFF]) +#define SET_ELEMENT_BIT_COUNT_64(a) (set_bit_count[(a)>>56] + \ + set_bit_count[((a)>>48)&0xFF] + \ + set_bit_count[((a)>>40)&0xFF] + \ + set_bit_count[((a)>>32)&0xFF] + \ + set_bit_count[((a)>>24)&0xFF] + \ + set_bit_count[((a)>>16)&0xFF] + \ + set_bit_count[((a)>>8)&0xFF] + \ + set_bit_count[(a)&0xFF]) +#if (ELEMENTSIZE==64) +# define SET_ELEMENT_BIT_COUNT(a) SET_ELEMENT_BIT_COUNT_64(a) +# define FULL_ELEMENT ((setelement)0xFFFFFFFFFFFFFFFF) +#elif (ELEMENTSIZE==32) +# define SET_ELEMENT_BIT_COUNT(a) SET_ELEMENT_BIT_COUNT_32(a) +# define FULL_ELEMENT ((setelement)0xFFFFFFFF) +#elif (ELEMENTSIZE==16) +# define SET_ELEMENT_BIT_COUNT(a) SET_ELEMENT_BIT_COUNT_16(a) +# define FULL_ELEMENT ((setelement)0xFFFF) +#else +# error "SET_ELEMENT_BIT_COUNT(a) not defined for current ELEMENTSIZE" +#endif + + + +/*** Macros and functions ***/ + +/* + * Gives a value with bit x (counting from lsb up) set. + * + * Making this as a table might speed up things on some machines + * (though on most modern machines it's faster to shift instead of + * using memory). Making it a macro makes it easy to change. + */ +#define SET_BIT_MASK(x) ((setelement)1<<(x)) + + + +/* Set element handling macros */ + +#define SET_ELEMENT_INTERSECT(a,b) ((a)&(b)) +#define SET_ELEMENT_UNION(a,b) ((a)|(b)) +#define SET_ELEMENT_DIFFERENCE(a,b) ((a)&(~(b))) +#define SET_ELEMENT_CONTAINS(e,v) ((e)&SET_BIT_MASK(v)) + + +/* Set handling macros */ + +#define SET_ADD_ELEMENT(s,a) \ + ((s)[(a)/ELEMENTSIZE] |= SET_BIT_MASK((a)%ELEMENTSIZE)) +#define SET_DEL_ELEMENT(s,a) \ + ((s)[(a)/ELEMENTSIZE] &= ~SET_BIT_MASK((a)%ELEMENTSIZE)) +#define SET_CONTAINS_FAST(s,a) (SET_ELEMENT_CONTAINS((s)[(a)/ELEMENTSIZE], \ + (a)%ELEMENTSIZE)) +#define SET_CONTAINS(s,a) (((a)0); + + n=(size/ELEMENTSIZE+1)+1; + s=calloc(n,sizeof(setelement)); + s[0]=size; + + return &(s[1]); +} + +/* + * set_free() + * + * Free the memory associated with set s. + */ +UNUSED_FUNCTION INLINE +static void set_free(set_t s) { + ASSERT(s!=NULL); + free(&(s[-1])); +} + +/* + * set_resize() + * + * Resizes set s to given size. If the size is less than SET_MAX_SIZE(s), + * the last elements are dropped. + * + * Returns a pointer to the new set. + */ +UNUSED_FUNCTION INLINE +static set_t set_resize(set_t s, int size) { + int n; + + ASSERT(size>0); + + n=(size/ELEMENTSIZE+1); + s=((setelement *)realloc(s-1,(n+1)*sizeof(setelement)))+1; + + if (n>SET_ARRAY_LENGTH(s)) + memset(s+SET_ARRAY_LENGTH(s),0, + (n-SET_ARRAY_LENGTH(s))*sizeof(setelement)); + if (size < SET_MAX_SIZE(s)) + s[(size-1)/ELEMENTSIZE] &= (FULL_ELEMENT >> + (ELEMENTSIZE-size%ELEMENTSIZE)); + s[-1]=size; + + return s; +} + +/* + * set_size() + * + * Returns the number of elements in set s. + */ +UNUSED_FUNCTION INLINE +static int set_size(set_t s) { + int count=0; + setelement *c; + + for (c=s; c < s+SET_ARRAY_LENGTH(s); c++) + count+=SET_ELEMENT_BIT_COUNT(*c); + return count; +} + +/* + * set_duplicate() + * + * Returns a newly allocated duplicate of set s. + */ +UNUSED_FUNCTION INLINE +static set_t set_duplicate(set_t s) { + set_t new; + + new=set_new(SET_MAX_SIZE(s)); + memcpy(new,s,SET_ARRAY_LENGTH(s)*sizeof(setelement)); + return new; +} + +/* + * set_copy() + * + * Copies set src to dest. If dest is NULL, is equal to set_duplicate. + * If dest smaller than src, it is freed and a new set of the same size as + * src is returned. + */ +UNUSED_FUNCTION INLINE +static set_t set_copy(set_t dest,set_t src) { + if (dest==NULL) + return set_duplicate(src); + if (SET_MAX_SIZE(dest)=0) { + * // i is in set s + * } + */ +UNUSED_FUNCTION INLINE +static int set_return_next(set_t s, int n) { + if (n<0) + n=0; + else + n++; + if (n >= SET_MAX_SIZE(s)) + return -1; + + while (n%ELEMENTSIZE) { + if (SET_CONTAINS(s,n)) + return n; + n++; + if (n >= SET_MAX_SIZE(s)) + return -1; + } + + while (s[n/ELEMENTSIZE]==0) { + n+=ELEMENTSIZE; + if (n >= SET_MAX_SIZE(s)) + return -1; + } + while (!SET_CONTAINS(s,n)) { + n++; + if (n >= SET_MAX_SIZE(s)) + return -1; + } + return n; +} + + +/* + * set_print() + * + * Prints the size and contents of set s to stdout. + * Mainly useful for debugging purposes and trivial output. + */ +/* +UNUSED_FUNCTION +static void set_print(set_t s) { + int i; + printf("size=%d(max %d)",set_size(s),(int)SET_MAX_SIZE(s)); + for (i=0; i + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_cliques.h" +#include "igraph_memory.h" +#include "igraph_constants.h" +#include "igraph_adjlist.h" +#include "igraph_interrupt_internal.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_stack.h" +#include "igraph_types_internal.h" +#include "igraph_cliquer.h" +#include "config.h" + +#include +#include /* memset */ + +static void igraph_i_cliques_free_res(igraph_vector_ptr_t *res) { + long i, n; + + n = igraph_vector_ptr_size(res); + for (i = 0; i < n; i++) { + if (VECTOR(*res)[i] != 0) { + igraph_vector_destroy(VECTOR(*res)[i]); + igraph_free(VECTOR(*res)[i]); + } + } + igraph_vector_ptr_clear(res); +} + +static int igraph_i_find_k_cliques( + const igraph_t *graph, + long int size, + const igraph_real_t *member_storage, + igraph_real_t **new_member_storage, + long int old_clique_count, + long int *clique_count, + igraph_vector_t *neis, + igraph_bool_t independent_vertices) { + + long int j, k, l, m, n, new_member_storage_size; + const igraph_real_t *c1, *c2; + igraph_real_t v1, v2; + igraph_bool_t ok; + + /* Allocate the storage */ + *new_member_storage = igraph_Realloc(*new_member_storage, + (size_t) (size * old_clique_count), + igraph_real_t); + if (*new_member_storage == 0) { + IGRAPH_ERROR("cliques failed", IGRAPH_ENOMEM); + } + new_member_storage_size = size * old_clique_count; + IGRAPH_FINALLY(igraph_free, *new_member_storage); + + m = n = 0; + + /* Now consider all pairs of i-1-cliques and see if they can be merged */ + for (j = 0; j < old_clique_count; j++) { + for (k = j + 1; k < old_clique_count; k++) { + IGRAPH_ALLOW_INTERRUPTION(); + + /* Since cliques are represented by their vertex indices in increasing + * order, two cliques can be merged iff they have exactly the same + * indices excluding one AND there is an edge between the two different + * vertices */ + c1 = member_storage + j * (size - 1); + c2 = member_storage + k * (size - 1); + /* Find the longest prefixes of c1 and c2 that are equal */ + for (l = 0; l < size - 1 && c1[l] == c2[l]; l++) { + (*new_member_storage)[m++] = c1[l]; + } + /* Now, if l == size-1, the two vectors are totally equal. + This is a bug */ + if (l == size - 1) { + IGRAPH_WARNING("possible bug in igraph_cliques"); + m = n; + } else { + /* Assuming that j (*new_member_storage)[m - 1]) { + (*new_member_storage)[m++] = v2; + n = m; + } else { + m = n; + } + } else { + m = n; + } + } + /* See if new_member_storage is full. If so, reallocate */ + if (m == new_member_storage_size) { + IGRAPH_FINALLY_CLEAN(1); + *new_member_storage = igraph_Realloc(*new_member_storage, + (size_t) new_member_storage_size * 2, + igraph_real_t); + if (*new_member_storage == 0) { + IGRAPH_ERROR("cliques failed", IGRAPH_ENOMEM); + } + new_member_storage_size *= 2; + IGRAPH_FINALLY(igraph_free, *new_member_storage); + } + } + } + } + + /* Calculate how many cliques have we found */ + *clique_count = n / size; + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/* Internal function for calculating cliques or independent vertex sets. + * They are practically the same except that the complementer of the graph + * should be used in the latter case. + */ +static int igraph_i_cliques(const igraph_t *graph, igraph_vector_ptr_t *res, + igraph_integer_t min_size, igraph_integer_t max_size, + igraph_bool_t independent_vertices) { + + igraph_integer_t no_of_nodes; + igraph_vector_t neis; + igraph_real_t *member_storage = 0, *new_member_storage, *c1; + long int i, j, k, clique_count, old_clique_count; + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("directionality of edges is ignored for directed graphs"); + } + + no_of_nodes = igraph_vcount(graph); + + if (min_size < 0) { + min_size = 0; + } + if (max_size > no_of_nodes || max_size <= 0) { + max_size = no_of_nodes; + } + + igraph_vector_ptr_clear(res); + + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_FINALLY(igraph_i_cliques_free_res, res); + + /* Will be resized later, if needed. */ + member_storage = igraph_Calloc(1, igraph_real_t); + if (member_storage == 0) { + IGRAPH_ERROR("cliques failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, member_storage); + + /* Find all 1-cliques: every vertex will be a clique */ + new_member_storage = igraph_Calloc(no_of_nodes, igraph_real_t); + if (new_member_storage == 0) { + IGRAPH_ERROR("cliques failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, new_member_storage); + + for (i = 0; i < no_of_nodes; i++) { + new_member_storage[i] = i; + } + clique_count = no_of_nodes; + old_clique_count = 0; + + /* Add size 1 cliques if requested */ + if (min_size <= 1) { + IGRAPH_CHECK(igraph_vector_ptr_resize(res, no_of_nodes)); + igraph_vector_ptr_null(res); + for (i = 0; i < no_of_nodes; i++) { + igraph_vector_t *p = igraph_Calloc(1, igraph_vector_t); + if (p == 0) { + IGRAPH_ERROR("cliques failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, p); + IGRAPH_CHECK(igraph_vector_init(p, 1)); + VECTOR(*p)[0] = i; + VECTOR(*res)[i] = p; + IGRAPH_FINALLY_CLEAN(1); + } + } + + for (i = 2; i <= max_size && clique_count > 1; i++) { + + /* Here new_member_storage contains the cliques found in the previous + iteration. Save this into member_storage, might be needed later */ + + c1 = member_storage; + member_storage = new_member_storage; + new_member_storage = c1; + old_clique_count = clique_count; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Calculate the cliques */ + + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_CHECK(igraph_i_find_k_cliques(graph, i, member_storage, + &new_member_storage, + old_clique_count, + &clique_count, + &neis, + independent_vertices)); + IGRAPH_FINALLY(igraph_free, member_storage); + IGRAPH_FINALLY(igraph_free, new_member_storage); + + /* Add the cliques just found to the result if requested */ + if (i >= min_size && i <= max_size) { + for (j = 0, k = 0; j < clique_count; j++, k += i) { + igraph_vector_t *p = igraph_Calloc(1, igraph_vector_t); + if (p == 0) { + IGRAPH_ERROR("cliques failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, p); + IGRAPH_CHECK(igraph_vector_init_copy(p, &new_member_storage[k], i)); + IGRAPH_FINALLY(igraph_vector_destroy, p); + IGRAPH_CHECK(igraph_vector_ptr_push_back(res, p)); + IGRAPH_FINALLY_CLEAN(2); + } + } + + } /* i <= max_size && clique_count != 0 */ + + igraph_free(member_storage); + igraph_free(new_member_storage); + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(4); /* 3 here, +1 is igraph_i_cliques_free_res */ + + return 0; +} + +/** + * \function igraph_cliques + * \brief Find all or some cliques in a graph + * + * + * Cliques are fully connected subgraphs of a graph. + * + * + * If you are only interested in the size of the largest clique in the graph, + * use \ref igraph_clique_number() instead. + * + * The current implementation of this function searches + * for maximal independent vertex sets (see \ref + * igraph_maximal_independent_vertex_sets()) in the complementer graph + * using the algorithm published in: + * S. Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm + * for generating all the maximal independent sets. SIAM J Computing, + * 6:505--517, 1977. + * + * \param graph The input graph. + * \param res Pointer to a pointer vector, the result will be stored + * here, ie. \c res will contain pointers to \c igraph_vector_t + * objects which contain the indices of vertices involved in a clique. + * The pointer vector will be resized if needed but note that the + * objects in the pointer vector will not be freed. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \return Error code. + * + * \sa \ref igraph_largest_cliques() and \ref igraph_clique_number(). + * + * Time complexity: TODO + * + * \example examples/simple/igraph_cliques.c + */ +int igraph_cliques(const igraph_t *graph, igraph_vector_ptr_t *res, + igraph_integer_t min_size, igraph_integer_t max_size) { + return igraph_i_cliquer_cliques(graph, res, min_size, max_size); +} + + +/** + * \function igraph_clique_size_hist + * \brief Count cliques of each size in the graph + * + * + * Cliques are fully connected subgraphs of a graph. + * + * The current implementation of this function + * uses version 1.21 of the Cliquer library by Sampo Niskanen and + * Patric R. J. ÖstergÃ¥rd, http://users.aalto.fi/~pat/cliquer.html + * + * \param graph The input graph. + * \param hist Pointer to an initialized vector. The result will be stored + * here. The first element will store the number of size-1 cliques, the second + * element the number of size-2 cliques, etc. For cliques smaller than \c min_size, + * zero counts will be returned. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \return Error code. + * + * \sa \ref igraph_cliques() and \ref igraph_cliques_callback() + * + * Time complexity: Exponential + * + */ +int igraph_clique_size_hist(const igraph_t *graph, igraph_vector_t *hist, + igraph_integer_t min_size, igraph_integer_t max_size) { + return igraph_i_cliquer_histogram(graph, hist, min_size, max_size); +} + + +/** + * \function igraph_cliques_callback + * \brief Calls a function for each clique in the graph. + * + * + * Cliques are fully connected subgraphs of a graph. This function + * enumerates all cliques within the given size range and calls + * \p cliquehandler_fn for each of them. The cliques are passed to the + * callback function as an igraph_vector_t *. Destroying and + * freeing this vector is left up to the user. Use \ref igraph_vector_destroy() + * to destroy it first, then free it using \ref igraph_free(). + * + * The current implementation of this function + * uses version 1.21 of the Cliquer library by Sampo Niskanen and + * Patric R. J. ÖstergÃ¥rd, http://users.aalto.fi/~pat/cliquer.html + * + * \param graph The input graph. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \param cliquehandler_fn Callback function to be called for each clique. + * See also igraph_clique_handler_t. + * \param arg Extra argument to supply to \p cliquehandler_fn. + * \return Error code. + * + * \sa \ref igraph_cliques() + * + * Time complexity: Exponential + * + */ +int igraph_cliques_callback(const igraph_t *graph, + igraph_integer_t min_size, igraph_integer_t max_size, + igraph_clique_handler_t *cliquehandler_fn, void *arg) { + return igraph_i_cliquer_callback(graph, min_size, max_size, cliquehandler_fn, arg); +} + + +/** + * \function igraph_weighted_cliques + * \brief Find all cliques in a given weight range in a vertex weighted graph + * + * + * Cliques are fully connected subgraphs of a graph. + * The weight of a clique is the sum of the weights + * of individual vertices within the clique. + * + * The current implementation of this function + * uses version 1.21 of the Cliquer library by Sampo Niskanen and + * Patric R. J. ÖstergÃ¥rd, http://users.aalto.fi/~pat/cliquer.html + * + * Only positive integer vertex weights are supported. + * + * \param graph The input graph. + * \param vertex_weights A vector of vertex weights. The current implementation + * will truncate all weights to their integer parts. + * \param res Pointer to a pointer vector, the result will be stored + * here, ie. \c res will contain pointers to \c igraph_vector_t + * objects which contain the indices of vertices involved in a clique. + * The pointer vector will be resized if needed but note that the + * objects in the pointer vector will not be freed. + * \param min_weight Integer giving the minimum weight of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_weight Integer giving the maximum weight of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \param maximal If true, only maximal cliques will be returned + * \return Error code. + * + * \sa \ref igraph_cliques(), \ref igraph_maximal_cliques() + * + * Time complexity: Exponential + * + */ +int igraph_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_ptr_t *res, + igraph_real_t min_weight, igraph_real_t max_weight, igraph_bool_t maximal) { + return igraph_i_weighted_cliques(graph, vertex_weights, res, min_weight, max_weight, maximal); +} + + +/** + * \function igraph_largest_weighted_cliques + * \brief Finds the largest weight clique(s) in a graph. + * + * + * Finds the clique(s) having the largest weight in the graph. + * + * The current implementation of this function + * uses version 1.21 of the Cliquer library by Sampo Niskanen and + * Patric R. J. ÖstergÃ¥rd, http://users.aalto.fi/~pat/cliquer.html + * + * Only positive integer vertex weights are supported. + * + * \param graph The input graph. + * \param vertex_weights A vector of vertex weights. The current implementation + * will truncate all weights to their integer parts. + * \param res Pointer to a pointer vector, the result will be stored + * here, ie. \c res will contain pointers to \c igraph_vector_t + * objects which contain the indices of vertices involved in a clique. + * The pointer vector will be resized if needed but note that the + * objects in the pointer vector will not be freed. + * \return Error code. + * + * \sa \ref igraph_weighted_cliques(), \ref igraph_weighted_clique_number(), \ref igraph_largest_cliques() + * + * Time complexity: TODO + */ +int igraph_largest_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_ptr_t *res) { + return igraph_i_largest_weighted_cliques(graph, vertex_weights, res); +} + + +/** + * \function igraph_weighted_clique_number + * \brief Find the weight of the largest weight clique in the graph + * + * The current implementation of this function + * uses version 1.21 of the Cliquer library by Sampo Niskanen and + * Patric R. J. ÖstergÃ¥rd, http://users.aalto.fi/~pat/cliquer.html + * + * Only positive integer vertex weights are supported. + * + * \param graph The input graph. + * \param vertex_weights A vector of vertex weights. The current implementation + * will truncate all weights to their integer parts. + * \param res The largest weight will be returned to the \c igraph_real_t + * pointed to by this variable. + * \return Error code. + * + * \sa \ref igraph_weighted_cliques(), \ref igraph_largest_weighted_cliques(), \ref igraph_clique_number() + * + * Time complexity: TODO + * + */ +int igraph_weighted_clique_number(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_real_t *res) { + return igraph_i_weighted_clique_number(graph, vertex_weights, res); +} + +typedef int(*igraph_i_maximal_clique_func_t)(const igraph_vector_t*, void*, igraph_bool_t*); +typedef struct { + igraph_vector_ptr_t* result; + igraph_integer_t min_size; + igraph_integer_t max_size; +} igraph_i_maximal_clique_data_t; + +static int igraph_i_maximal_cliques(const igraph_t *graph, igraph_i_maximal_clique_func_t func, void* data); + +static int igraph_i_maximal_or_largest_cliques_or_indsets( + const igraph_t *graph, + igraph_vector_ptr_t *res, + igraph_integer_t *clique_number, + igraph_bool_t keep_only_largest, + igraph_bool_t complementer); + +/** + * \function igraph_independent_vertex_sets + * \brief Find all independent vertex sets in a graph + * + * + * A vertex set is considered independent if there are no edges between + * them. + * + * + * If you are interested in the size of the largest independent vertex set, + * use \ref igraph_independence_number() instead. + * + * + * The current implementation was ported to igraph from the Very Nauty Graph + * Library by Keith Briggs and uses the algorithm from the paper + * S. Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm + * for generating all the maximal independent sets. SIAM J Computing, + * 6:505--517, 1977. + * + * \param graph The input graph. + * \param res Pointer to a pointer vector, the result will be stored + * here, ie. \c res will contain pointers to \c igraph_vector_t + * objects which contain the indices of vertices involved in an independent + * vertex set. The pointer vector will be resized if needed but note that the + * objects in the pointer vector will not be freed. + * \param min_size Integer giving the minimum size of the sets to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the sets to be + * returned. If negative or zero, no upper bound will be used. + * \return Error code. + * + * \sa \ref igraph_largest_independent_vertex_sets(), + * \ref igraph_independence_number(). + * + * Time complexity: TODO + * + * \example examples/simple/igraph_independent_sets.c + */ +int igraph_independent_vertex_sets(const igraph_t *graph, + igraph_vector_ptr_t *res, + igraph_integer_t min_size, + igraph_integer_t max_size) { + return igraph_i_cliques(graph, res, min_size, max_size, 1); +} + +/** + * \function igraph_largest_independent_vertex_sets + * \brief Finds the largest independent vertex set(s) in a graph. + * + * + * An independent vertex set is largest if there is no other + * independent vertex set with more vertices in the graph. + * + * + * The current implementation was ported to igraph from the Very Nauty Graph + * Library by Keith Briggs and uses the algorithm from the paper + * S. Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm + * for generating all the maximal independent sets. SIAM J Computing, + * 6:505--517, 1977. + * + * \param graph The input graph. + * \param res Pointer to a pointer vector, the result will be stored + * here. It will be resized as needed. + * \return Error code. + * + * \sa \ref igraph_independent_vertex_sets(), \ref + * igraph_maximal_independent_vertex_sets(). + * + * Time complexity: TODO + */ + +int igraph_largest_independent_vertex_sets(const igraph_t *graph, + igraph_vector_ptr_t *res) { + return igraph_i_maximal_or_largest_cliques_or_indsets(graph, res, 0, 1, 0); +} + +typedef struct igraph_i_max_ind_vsets_data_t { + igraph_integer_t matrix_size; + igraph_adjlist_t adj_list; /* Adjacency list of the graph */ + igraph_vector_t deg; /* Degrees of individual nodes */ + igraph_set_t* buckets; /* Bucket array */ + /* The IS value for each node. Still to be explained :) */ + igraph_integer_t* IS; + igraph_integer_t largest_set_size; /* Size of the largest set encountered */ + igraph_bool_t keep_only_largest; /* True if we keep only the largest sets */ +} igraph_i_max_ind_vsets_data_t; + +static int igraph_i_maximal_independent_vertex_sets_backtrack( + const igraph_t *graph, + igraph_vector_ptr_t *res, + igraph_i_max_ind_vsets_data_t *clqdata, + igraph_integer_t level) { + long int v1, v2, v3, c, j, k; + igraph_vector_int_t *neis1, *neis2; + igraph_bool_t f; + igraph_integer_t j1; + long int it_state; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (level >= clqdata->matrix_size - 1) { + igraph_integer_t size = 0; + if (res) { + igraph_vector_t *vec; + vec = igraph_Calloc(1, igraph_vector_t); + if (vec == 0) { + IGRAPH_ERROR("igraph_i_maximal_independent_vertex_sets failed", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(vec, 0); + for (v1 = 0; v1 < clqdata->matrix_size; v1++) + if (clqdata->IS[v1] == 0) { + IGRAPH_CHECK(igraph_vector_push_back(vec, v1)); + } + size = (igraph_integer_t) igraph_vector_size(vec); + if (!clqdata->keep_only_largest) { + IGRAPH_CHECK(igraph_vector_ptr_push_back(res, vec)); + } else { + if (size > clqdata->largest_set_size) { + /* We are keeping only the largest sets, and we've found one that's + * larger than all previous sets, so we have to clear the list */ + j = igraph_vector_ptr_size(res); + for (v1 = 0; v1 < j; v1++) { + igraph_vector_destroy(VECTOR(*res)[v1]); + free(VECTOR(*res)[v1]); + } + igraph_vector_ptr_clear(res); + IGRAPH_CHECK(igraph_vector_ptr_push_back(res, vec)); + } else if (size == clqdata->largest_set_size) { + IGRAPH_CHECK(igraph_vector_ptr_push_back(res, vec)); + } else { + igraph_vector_destroy(vec); + free(vec); + } + } + IGRAPH_FINALLY_CLEAN(1); + } else { + for (v1 = 0, size = 0; v1 < clqdata->matrix_size; v1++) + if (clqdata->IS[v1] == 0) { + size++; + } + } + if (size > clqdata->largest_set_size) { + clqdata->largest_set_size = size; + } + } else { + v1 = level + 1; + /* Count the number of vertices with an index less than v1 that have + * an IS value of zero */ + neis1 = igraph_adjlist_get(&clqdata->adj_list, v1); + c = 0; + j = 0; + while (j < VECTOR(clqdata->deg)[v1] && + (v2 = (long int) VECTOR(*neis1)[j]) <= level) { + if (clqdata->IS[v2] == 0) { + c++; + } + j++; + } + + if (c == 0) { + /* If there are no such nodes... */ + j = 0; + while (j < VECTOR(clqdata->deg)[v1] && + (v2 = (long int) VECTOR(*neis1)[j]) <= level) { + clqdata->IS[v2]++; + j++; + } + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, res, clqdata, (igraph_integer_t) v1)); + j = 0; + while (j < VECTOR(clqdata->deg)[v1] && + (v2 = (long int) VECTOR(*neis1)[j]) <= level) { + clqdata->IS[v2]--; + j++; + } + } else { + /* If there are such nodes, store the count in the IS value of v1 */ + clqdata->IS[v1] = (igraph_integer_t) c; + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, res, clqdata, (igraph_integer_t) v1)); + clqdata->IS[v1] = 0; + + f = 1; + j = 0; + while (j < VECTOR(clqdata->deg)[v1] && + (v2 = (long int) VECTOR(*neis1)[j]) <= level) { + if (clqdata->IS[v2] == 0) { + IGRAPH_CHECK(igraph_set_add(&clqdata->buckets[v1], + (igraph_integer_t) j)); + neis2 = igraph_adjlist_get(&clqdata->adj_list, v2); + k = 0; + while (k < VECTOR(clqdata->deg)[v2] && + (v3 = (long int) VECTOR(*neis2)[k]) <= level) { + clqdata->IS[v3]--; + if (clqdata->IS[v3] == 0) { + f = 0; + } + k++; + } + } + clqdata->IS[v2]++; + j++; + } + + if (f) { + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, res, clqdata, (igraph_integer_t) v1)); + } + + j = 0; + while (j < VECTOR(clqdata->deg)[v1] && + (v2 = (long int) VECTOR(*neis1)[j]) <= level) { + clqdata->IS[v2]--; + j++; + } + + it_state = 0; + while (igraph_set_iterate(&clqdata->buckets[v1], &it_state, &j1)) { + j = (long)j1; + v2 = (long int) VECTOR(*neis1)[j]; + neis2 = igraph_adjlist_get(&clqdata->adj_list, v2); + k = 0; + while (k < VECTOR(clqdata->deg)[v2] && + (v3 = (long int) VECTOR(*neis2)[k]) <= level) { + clqdata->IS[v3]++; + k++; + } + } + igraph_set_clear(&clqdata->buckets[v1]); + } + } + + return 0; +} + +static void igraph_i_free_set_array(igraph_set_t* array) { + long int i = 0; + while (igraph_set_inited(array + i)) { + igraph_set_destroy(array + i); + i++; + } + igraph_Free(array); +} + +/** + * \function igraph_maximal_independent_vertex_sets + * \brief Find all maximal independent vertex sets of a graph + * + * + * A maximal independent vertex set is an independent vertex set which + * can't be extended any more by adding a new vertex to it. + * + * + * The algorithm used here is based on the following paper: + * S. Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm for + * generating all the maximal independent sets. SIAM J Computing, + * 6:505--517, 1977. + * + * + * The implementation was originally written by Kevin O'Neill and modified + * by K M Briggs in the Very Nauty Graph Library. I simply re-wrote it to + * use igraph's data structures. + * + * + * If you are interested in the size of the largest independent vertex set, + * use \ref igraph_independence_number() instead. + * + * \param graph The input graph. + * \param res Pointer to a pointer vector, the result will be stored + * here, ie. \c res will contain pointers to \c igraph_vector_t + * objects which contain the indices of vertices involved in an independent + * vertex set. The pointer vector will be resized if needed but note that the + * objects in the pointer vector will not be freed. + * \return Error code. + * + * \sa \ref igraph_maximal_cliques(), \ref + * igraph_independence_number() + * + * Time complexity: TODO. + */ +int igraph_maximal_independent_vertex_sets(const igraph_t *graph, + igraph_vector_ptr_t *res) { + igraph_i_max_ind_vsets_data_t clqdata; + igraph_integer_t no_of_nodes = (igraph_integer_t) igraph_vcount(graph), i; + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("directionality of edges is ignored for directed graphs"); + } + + clqdata.matrix_size = no_of_nodes; + clqdata.keep_only_largest = 0; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &clqdata.adj_list, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &clqdata.adj_list); + + clqdata.IS = igraph_Calloc(no_of_nodes, igraph_integer_t); + if (clqdata.IS == 0) { + IGRAPH_ERROR("igraph_maximal_independent_vertex_sets failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, clqdata.IS); + + IGRAPH_VECTOR_INIT_FINALLY(&clqdata.deg, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(clqdata.deg)[i] = igraph_vector_int_size(igraph_adjlist_get(&clqdata.adj_list, i)); + } + + clqdata.buckets = igraph_Calloc(no_of_nodes + 1, igraph_set_t); + if (clqdata.buckets == 0) { + IGRAPH_ERROR("igraph_maximal_independent_vertex_sets failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_i_free_set_array, clqdata.buckets); + + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_set_init(&clqdata.buckets[i], 0)); + } + + igraph_vector_ptr_clear(res); + + /* Do the show */ + clqdata.largest_set_size = 0; + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, res, &clqdata, 0)); + + /* Cleanup */ + for (i = 0; i < no_of_nodes; i++) { + igraph_set_destroy(&clqdata.buckets[i]); + } + igraph_adjlist_destroy(&clqdata.adj_list); + igraph_vector_destroy(&clqdata.deg); + igraph_free(clqdata.IS); + igraph_free(clqdata.buckets); + IGRAPH_FINALLY_CLEAN(4); + return 0; +} + +/** + * \function igraph_independence_number + * \brief Find the independence number of the graph + * + * + * The independence number of a graph is the cardinality of the largest + * independent vertex set. + * + * + * The current implementation was ported to igraph from the Very Nauty Graph + * Library by Keith Briggs and uses the algorithm from the paper + * S. Tsukiyama, M. Ide, H. Ariyoshi and I. Shirawaka. A new algorithm + * for generating all the maximal independent sets. SIAM J Computing, + * 6:505--517, 1977. + * + * \param graph The input graph. + * \param no The independence number will be returned to the \c + * igraph_integer_t pointed by this variable. + * \return Error code. + * + * \sa \ref igraph_independent_vertex_sets(). + * + * Time complexity: TODO. + */ +int igraph_independence_number(const igraph_t *graph, igraph_integer_t *no) { + igraph_i_max_ind_vsets_data_t clqdata; + igraph_integer_t no_of_nodes = (igraph_integer_t) igraph_vcount(graph), i; + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("directionality of edges is ignored for directed graphs"); + } + + clqdata.matrix_size = no_of_nodes; + clqdata.keep_only_largest = 0; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &clqdata.adj_list, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &clqdata.adj_list); + + clqdata.IS = igraph_Calloc(no_of_nodes, igraph_integer_t); + if (clqdata.IS == 0) { + IGRAPH_ERROR("igraph_independence_number failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, clqdata.IS); + + IGRAPH_VECTOR_INIT_FINALLY(&clqdata.deg, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(clqdata.deg)[i] = igraph_vector_int_size(igraph_adjlist_get(&clqdata.adj_list, i)); + } + + clqdata.buckets = igraph_Calloc(no_of_nodes + 1, igraph_set_t); + if (clqdata.buckets == 0) { + IGRAPH_ERROR("igraph_independence_number failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_i_free_set_array, clqdata.buckets); + + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_set_init(&clqdata.buckets[i], 0)); + } + + /* Do the show */ + clqdata.largest_set_size = 0; + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, 0, &clqdata, 0)); + *no = clqdata.largest_set_size; + + /* Cleanup */ + for (i = 0; i < no_of_nodes; i++) { + igraph_set_destroy(&clqdata.buckets[i]); + } + igraph_adjlist_destroy(&clqdata.adj_list); + igraph_vector_destroy(&clqdata.deg); + igraph_free(clqdata.IS); + igraph_free(clqdata.buckets); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +/*************************************************************************/ +/* MAXIMAL CLIQUES, LARGEST CLIQUES */ +/*************************************************************************/ + +static int igraph_i_maximal_cliques_store_max_size(const igraph_vector_t* clique, void* data, + igraph_bool_t* cont) { + igraph_integer_t* result = (igraph_integer_t*)data; + IGRAPH_UNUSED(cont); + if (*result < igraph_vector_size(clique)) { + *result = (igraph_integer_t) igraph_vector_size(clique); + } + return IGRAPH_SUCCESS; +} + +static int igraph_i_maximal_cliques_store(const igraph_vector_t* clique, void* data, igraph_bool_t* cont) { + igraph_vector_ptr_t* result = (igraph_vector_ptr_t*)data; + igraph_vector_t* vec; + + IGRAPH_UNUSED(cont); + vec = igraph_Calloc(1, igraph_vector_t); + if (vec == 0) { + IGRAPH_ERROR("cannot allocate memory for storing next clique", IGRAPH_ENOMEM); + } + + IGRAPH_CHECK(igraph_vector_copy(vec, clique)); + IGRAPH_CHECK(igraph_vector_ptr_push_back(result, vec)); + + return IGRAPH_SUCCESS; +} + +static int igraph_i_maximal_cliques_store_size_check(const igraph_vector_t* clique, void* data_, igraph_bool_t* cont) { + igraph_i_maximal_clique_data_t* data = (igraph_i_maximal_clique_data_t*)data_; + igraph_vector_t* vec; + igraph_integer_t size = (igraph_integer_t) igraph_vector_size(clique); + + IGRAPH_UNUSED(cont); + if (size < data->min_size || size > data->max_size) { + return IGRAPH_SUCCESS; + } + + vec = igraph_Calloc(1, igraph_vector_t); + if (vec == 0) { + IGRAPH_ERROR("cannot allocate memory for storing next clique", IGRAPH_ENOMEM); + } + + IGRAPH_CHECK(igraph_vector_copy(vec, clique)); + IGRAPH_CHECK(igraph_vector_ptr_push_back(data->result, vec)); + + return IGRAPH_SUCCESS; +} + +static int igraph_i_largest_cliques_store(const igraph_vector_t* clique, void* data, igraph_bool_t* cont) { + igraph_vector_ptr_t* result = (igraph_vector_ptr_t*)data; + igraph_vector_t* vec; + long int i, n; + + IGRAPH_UNUSED(cont); + /* Is the current clique at least as large as the others that we have found? */ + if (!igraph_vector_ptr_empty(result)) { + n = igraph_vector_size(clique); + if (n < igraph_vector_size(VECTOR(*result)[0])) { + return IGRAPH_SUCCESS; + } + + if (n > igraph_vector_size(VECTOR(*result)[0])) { + for (i = 0; i < igraph_vector_ptr_size(result); i++) { + igraph_vector_destroy(VECTOR(*result)[i]); + } + igraph_vector_ptr_free_all(result); + igraph_vector_ptr_resize(result, 0); + } + } + + vec = igraph_Calloc(1, igraph_vector_t); + if (vec == 0) { + IGRAPH_ERROR("cannot allocate memory for storing next clique", IGRAPH_ENOMEM); + } + + IGRAPH_CHECK(igraph_vector_copy(vec, clique)); + IGRAPH_CHECK(igraph_vector_ptr_push_back(result, vec)); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_largest_cliques + * \brief Finds the largest clique(s) in a graph. + * + * + * A clique is largest (quite intuitively) if there is no other clique + * in the graph which contains more vertices. + * + * + * Note that this is not necessarily the same as a maximal clique, + * ie. the largest cliques are always maximal but a maximal clique is + * not always largest. + * + * The current implementation of this function searches + * for maximal cliques using \ref igraph_maximal_cliques() and drops + * those that are not the largest. + * + * The implementation of this function changed between + * igraph 0.5 and 0.6, so the order of the cliques and the order of + * vertices within the cliques will almost surely be different between + * these two versions. + * + * \param graph The input graph. + * \param res Pointer to an initialized pointer vector, the result + * will be stored here. It will be resized as needed. Note that + * vertices of a clique may be returned in arbitrary order. + * \return Error code. + * + * \sa \ref igraph_cliques(), \ref igraph_maximal_cliques() + * + * Time complexity: O(3^(|V|/3)) worst case. + */ + +int igraph_largest_cliques(const igraph_t *graph, igraph_vector_ptr_t *res) { + igraph_vector_ptr_clear(res); + IGRAPH_FINALLY(igraph_i_cliques_free_res, res); + IGRAPH_CHECK(igraph_i_maximal_cliques(graph, &igraph_i_largest_cliques_store, (void*)res)); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_clique_number + * \brief Find the clique number of the graph + * + * + * The clique number of a graph is the size of the largest clique. + * + * \param graph The input graph. + * \param no The clique number will be returned to the \c igraph_integer_t + * pointed by this variable. + * \return Error code. + * + * \sa \ref igraph_cliques(), \ref igraph_largest_cliques(). + * + * Time complexity: O(3^(|V|/3)) worst case. + */ +int igraph_clique_number(const igraph_t *graph, igraph_integer_t *no) { + *no = 0; + return igraph_i_maximal_cliques(graph, &igraph_i_maximal_cliques_store_max_size, (void*)no); +} + +typedef struct { + igraph_vector_int_t cand; + igraph_vector_int_t fini; + igraph_vector_int_t cand_filtered; +} igraph_i_maximal_cliques_stack_frame; + +static void igraph_i_maximal_cliques_stack_frame_destroy(igraph_i_maximal_cliques_stack_frame *frame) { + igraph_vector_int_destroy(&frame->cand); + igraph_vector_int_destroy(&frame->fini); + igraph_vector_int_destroy(&frame->cand_filtered); +} + +static void igraph_i_maximal_cliques_stack_destroy(igraph_stack_ptr_t *stack) { + igraph_i_maximal_cliques_stack_frame *frame; + + while (!igraph_stack_ptr_empty(stack)) { + frame = (igraph_i_maximal_cliques_stack_frame*)igraph_stack_ptr_pop(stack); + igraph_i_maximal_cliques_stack_frame_destroy(frame); + free(frame); + } + + igraph_stack_ptr_destroy(stack); +} + +static int igraph_i_maximal_cliques(const igraph_t *graph, igraph_i_maximal_clique_func_t func, void* data) { + int directed = igraph_is_directed(graph); + long int i, j, k, l; + igraph_integer_t no_of_nodes, nodes_to_check, nodes_done; + igraph_integer_t best_cand = 0, best_cand_degree = 0, best_fini_cand_degree; + igraph_adjlist_t adj_list; + igraph_stack_ptr_t stack; + igraph_i_maximal_cliques_stack_frame frame, *new_frame_ptr; + igraph_vector_t clique; + igraph_vector_int_t new_cand, new_fini, cn, best_cand_nbrs, + best_fini_cand_nbrs; + igraph_bool_t cont = 1; + int assret; + + if (directed) { + IGRAPH_WARNING("directionality of edges is ignored for directed graphs"); + } + + no_of_nodes = igraph_vcount(graph); + if (no_of_nodes == 0) { + return IGRAPH_SUCCESS; + } + + /* Construct an adjacency list representation */ + IGRAPH_CHECK(igraph_adjlist_init(graph, &adj_list, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adj_list); + IGRAPH_CHECK(igraph_adjlist_simplify(&adj_list)); + igraph_adjlist_sort(&adj_list); + + /* Initialize stack */ + IGRAPH_CHECK(igraph_stack_ptr_init(&stack, 0)); + IGRAPH_FINALLY(igraph_i_maximal_cliques_stack_destroy, &stack); + + /* Create the initial (empty) clique */ + IGRAPH_VECTOR_INIT_FINALLY(&clique, 0); + + /* Initialize new_cand, new_fini, cn, best_cand_nbrs and best_fini_cand_nbrs (will be used later) */ + igraph_vector_int_init(&new_cand, 0); + IGRAPH_FINALLY(igraph_vector_int_destroy, &new_cand); + igraph_vector_int_init(&new_fini, 0); + IGRAPH_FINALLY(igraph_vector_int_destroy, &new_fini); + igraph_vector_int_init(&cn, 0); + IGRAPH_FINALLY(igraph_vector_int_destroy, &cn); + igraph_vector_int_init(&best_cand_nbrs, 0); + IGRAPH_FINALLY(igraph_vector_int_destroy, &best_cand_nbrs); + igraph_vector_int_init(&best_fini_cand_nbrs, 0); + IGRAPH_FINALLY(igraph_vector_int_destroy, &best_fini_cand_nbrs); + + /* Find the vertex with the highest degree */ + best_cand = 0; best_cand_degree = (igraph_integer_t) igraph_vector_int_size(igraph_adjlist_get(&adj_list, 0)); + for (i = 1; i < no_of_nodes; i++) { + j = igraph_vector_int_size(igraph_adjlist_get(&adj_list, i)); + if (j > best_cand_degree) { + best_cand = (igraph_integer_t) i; + best_cand_degree = (igraph_integer_t) j; + } + } + + /* Create the initial stack frame */ + IGRAPH_CHECK(igraph_vector_int_init_seq(&frame.cand, 0, no_of_nodes - 1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &frame.cand); + IGRAPH_CHECK(igraph_vector_int_init(&frame.fini, 0)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &frame.fini); + IGRAPH_CHECK(igraph_vector_int_init(&frame.cand_filtered, 0)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &frame.cand_filtered); + IGRAPH_CHECK(igraph_vector_int_difference_sorted(&frame.cand, + igraph_adjlist_get(&adj_list, best_cand), &frame.cand_filtered)); + IGRAPH_FINALLY_CLEAN(3); + IGRAPH_FINALLY(igraph_i_maximal_cliques_stack_frame_destroy, &frame); + + /* TODO: frame.cand and frame.fini should be a set instead of a vector */ + + /* Main loop starts here */ + nodes_to_check = (igraph_integer_t) igraph_vector_int_size(&frame.cand_filtered); nodes_done = 0; + while (!igraph_vector_int_empty(&frame.cand_filtered) || !igraph_stack_ptr_empty(&stack)) { + if (igraph_vector_int_empty(&frame.cand_filtered)) { + /* No candidates left to check in this stack frame, pop out the previous stack frame */ + igraph_i_maximal_cliques_stack_frame *newframe = igraph_stack_ptr_pop(&stack); + igraph_i_maximal_cliques_stack_frame_destroy(&frame); + frame = *newframe; + free(newframe); + + if (igraph_stack_ptr_size(&stack) == 1) { + /* We will be using the next candidate node in the next iteration, so we can increase + * nodes_done by 1 */ + nodes_done++; + } + + /* For efficiency reasons, we only check for interruption and show progress here */ + IGRAPH_PROGRESS("Maximal cliques: ", 100.0 * nodes_done / nodes_to_check, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_vector_pop_back(&clique); + continue; + } + + /* Try the next node in the clique */ + i = (long int) igraph_vector_int_pop_back(&frame.cand_filtered); + IGRAPH_CHECK(igraph_vector_push_back(&clique, i)); + + /* Remove the node from the candidate list */ + assret = igraph_vector_int_binsearch(&frame.cand, i, &j); assert(assret); + igraph_vector_int_remove(&frame.cand, j); + + /* Add the node to the finished list */ + assret = !igraph_vector_int_binsearch(&frame.fini, i, &j); assert(assret); + IGRAPH_CHECK(igraph_vector_int_insert(&frame.fini, j, i)); + + /* Create new_cand and new_fini */ + IGRAPH_CHECK(igraph_vector_int_intersect_sorted(&frame.cand, igraph_adjlist_get(&adj_list, i), &new_cand)); + IGRAPH_CHECK(igraph_vector_int_intersect_sorted(&frame.fini, igraph_adjlist_get(&adj_list, i), &new_fini)); + + /* Do we have anything more to search? */ + if (igraph_vector_int_empty(&new_cand)) { + if (igraph_vector_int_empty(&new_fini)) { + /* We have a maximal clique here */ + IGRAPH_CHECK(func(&clique, data, &cont)); + if (!cont) { + /* The callback function requested to stop the search */ + break; + } + } + igraph_vector_pop_back(&clique); + continue; + } + if (igraph_vector_int_empty(&new_fini) && + igraph_vector_int_size(&new_cand) == 1) { + /* Shortcut: only one node left */ + IGRAPH_CHECK(igraph_vector_push_back(&clique, VECTOR(new_cand)[0])); + IGRAPH_CHECK(func(&clique, data, &cont)); + if (!cont) { + /* The callback function requested to stop the search */ + break; + } + igraph_vector_pop_back(&clique); + igraph_vector_pop_back(&clique); + continue; + } + + /* Find the next best candidate node in new_fini */ + l = igraph_vector_int_size(&new_cand); + best_cand_degree = -1; + j = igraph_vector_int_size(&new_fini); + for (i = 0; i < j; i++) { + k = (long int)VECTOR(new_fini)[i]; + IGRAPH_CHECK(igraph_vector_int_intersect_sorted(&new_cand, igraph_adjlist_get(&adj_list, k), &cn)); + if (igraph_vector_int_size(&cn) > best_cand_degree) { + best_cand_degree = (igraph_integer_t) igraph_vector_int_size(&cn); + IGRAPH_CHECK(igraph_vector_int_update(&best_fini_cand_nbrs, &cn)); + if (best_cand_degree == l) { + /* Cool, we surely have the best candidate node here as best_cand_degree can't get any better */ + break; + } + } + } + /* Shortcut here: we don't have to examine new_cand */ + if (best_cand_degree == l) { + igraph_vector_pop_back(&clique); + continue; + } + /* Still finding best candidate node */ + best_fini_cand_degree = best_cand_degree; + best_cand_degree = -1; + j = igraph_vector_int_size(&new_cand); + l = l - 1; + for (i = 0; i < j; i++) { + k = (long int)VECTOR(new_cand)[i]; + IGRAPH_CHECK(igraph_vector_int_intersect_sorted(&new_cand, igraph_adjlist_get(&adj_list, k), &cn)); + if (igraph_vector_int_size(&cn) > best_cand_degree) { + best_cand_degree = (igraph_integer_t) igraph_vector_int_size(&cn); + IGRAPH_CHECK(igraph_vector_int_update(&best_cand_nbrs, &cn)); + if (best_cand_degree == l) { + /* Cool, we surely have the best candidate node here as best_cand_degree can't get any better */ + break; + } + } + } + + /* Create a new stack frame in case we back out later */ + new_frame_ptr = igraph_Calloc(1, igraph_i_maximal_cliques_stack_frame); + if (new_frame_ptr == 0) { + IGRAPH_ERROR("cannot allocate new stack frame", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, new_frame_ptr); + *new_frame_ptr = frame; + memset(&frame, 0, sizeof(frame)); + IGRAPH_CHECK(igraph_stack_ptr_push(&stack, new_frame_ptr)); + IGRAPH_FINALLY_CLEAN(1); /* ownership of new_frame_ptr taken by the stack */ + /* Ownership of the current frame and its vectors (frame.cand, frame.done, frame.cand_filtered) + * is taken by the stack from now on. Vectors in frame must be re-initialized with new_cand, + * new_fini and stuff. The old frame.cand and frame.fini won't be leaked because they are + * managed by the stack now. */ + frame.cand = new_cand; + frame.fini = new_fini; + IGRAPH_CHECK(igraph_vector_int_init(&new_cand, 0)); + IGRAPH_CHECK(igraph_vector_int_init(&new_fini, 0)); + IGRAPH_CHECK(igraph_vector_int_init(&frame.cand_filtered, 0)); + + /* Adjust frame.cand_filtered */ + if (best_cand_degree < best_fini_cand_degree) { + IGRAPH_CHECK(igraph_vector_int_difference_sorted(&frame.cand, &best_fini_cand_nbrs, &frame.cand_filtered)); + } else { + IGRAPH_CHECK(igraph_vector_int_difference_sorted(&frame.cand, &best_cand_nbrs, &frame.cand_filtered)); + } + } + + IGRAPH_PROGRESS("Maximal cliques: ", 100.0, NULL); + + igraph_adjlist_destroy(&adj_list); + igraph_vector_destroy(&clique); + igraph_vector_int_destroy(&new_cand); + igraph_vector_int_destroy(&new_fini); + igraph_vector_int_destroy(&cn); + igraph_vector_int_destroy(&best_cand_nbrs); + igraph_vector_int_destroy(&best_fini_cand_nbrs); + igraph_i_maximal_cliques_stack_frame_destroy(&frame); + igraph_i_maximal_cliques_stack_destroy(&stack); + IGRAPH_FINALLY_CLEAN(9); + + return IGRAPH_SUCCESS; +} + +static int igraph_i_maximal_or_largest_cliques_or_indsets(const igraph_t *graph, + igraph_vector_ptr_t *res, + igraph_integer_t *clique_number, + igraph_bool_t keep_only_largest, + igraph_bool_t complementer) { + igraph_i_max_ind_vsets_data_t clqdata; + igraph_integer_t no_of_nodes = (igraph_integer_t) igraph_vcount(graph), i; + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("directionality of edges is ignored for directed graphs"); + } + + clqdata.matrix_size = no_of_nodes; + clqdata.keep_only_largest = keep_only_largest; + + if (complementer) { + IGRAPH_CHECK(igraph_adjlist_init_complementer(graph, &clqdata.adj_list, IGRAPH_ALL, 0)); + } else { + IGRAPH_CHECK(igraph_adjlist_init(graph, &clqdata.adj_list, IGRAPH_ALL)); + } + IGRAPH_FINALLY(igraph_adjlist_destroy, &clqdata.adj_list); + + clqdata.IS = igraph_Calloc(no_of_nodes, igraph_integer_t); + if (clqdata.IS == 0) { + IGRAPH_ERROR("igraph_i_maximal_or_largest_cliques_or_indsets failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, clqdata.IS); + + IGRAPH_VECTOR_INIT_FINALLY(&clqdata.deg, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(clqdata.deg)[i] = igraph_vector_int_size(igraph_adjlist_get(&clqdata.adj_list, i)); + } + + clqdata.buckets = igraph_Calloc(no_of_nodes + 1, igraph_set_t); + if (clqdata.buckets == 0) { + IGRAPH_ERROR("igraph_maximal_or_largest_cliques_or_indsets failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_i_free_set_array, clqdata.buckets); + + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_set_init(&clqdata.buckets[i], 0)); + } + + if (res) { + igraph_vector_ptr_clear(res); + } + + /* Do the show */ + clqdata.largest_set_size = 0; + IGRAPH_CHECK(igraph_i_maximal_independent_vertex_sets_backtrack(graph, res, &clqdata, 0)); + + /* Cleanup */ + for (i = 0; i < no_of_nodes; i++) { + igraph_set_destroy(&clqdata.buckets[i]); + } + igraph_adjlist_destroy(&clqdata.adj_list); + igraph_vector_destroy(&clqdata.deg); + igraph_free(clqdata.IS); + igraph_free(clqdata.buckets); + IGRAPH_FINALLY_CLEAN(4); + + if (clique_number) { + *clique_number = clqdata.largest_set_size; + } + return 0; +} diff --git a/src/clustertool.cpp b/src/clustertool.cpp new file mode 100644 index 0000000..708972d --- /dev/null +++ b/src/clustertool.cpp @@ -0,0 +1,692 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Joerg Reichardt + The original copyright notice follows here */ + +/*************************************************************************** + main.cpp - description + ------------------- + begin : Tue Jul 13 11:26:47 CEST 2004 + copyright : (C) 2004 by + email : + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include "NetDataTypes.h" +#include "NetRoutines.h" +#include "pottsmodel_2.h" + +#include "igraph_community.h" +#include "igraph_error.h" +#include "igraph_random.h" +#include "igraph_math.h" +#include "igraph_interface.h" +#include "igraph_components.h" +#include "igraph_interrupt_internal.h" + +static int igraph_i_community_spinglass_orig( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_t *membership, + igraph_vector_t *csize, + igraph_integer_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma); + +static int igraph_i_community_spinglass_negative( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_t *membership, + igraph_vector_t *csize, + igraph_integer_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma, + /* igraph_matrix_t *adhesion, */ + /* igraph_matrix_t *normalised_adhesion, */ + /* igraph_real_t *polarization, */ + igraph_real_t gamma_minus); + +/** + * \function igraph_community_spinglass + * \brief Community detection based on statistical mechanics + * + * This function implements the community structure detection + * algorithm proposed by Joerg Reichardt and Stefan Bornholdt. + * The algorithm is described in their paper: Statistical Mechanics of + * Community Detection, http://arxiv.org/abs/cond-mat/0603718 . + * + * From version 0.6 igraph also supports an extension to + * the algorithm that allows negative edge weights. This is described + * in V.A. Traag and Jeroen Bruggeman: Community detection in networks + * with positive and negative links, http://arxiv.org/abs/0811.2329 . + * \param graph The input graph, it may be directed but the direction + * of the edge is not used in the algorithm. + * \param weights The vector giving the edge weights, it may be \c NULL, + * in which case all edges are weighted equally. Edge weights + * should be positive, altough this is not tested. + * \param modularity Pointer to a real number, if not \c NULL then the + * modularity score of the solution will be stored here. This is the + * gereralized modularity that simplifies to the one defined in + * M. E. J. Newman and M. Girvan, Phys. Rev. E 69, 026113 (2004), + * if the gamma parameter is one. + * \param temperature Pointer to a real number, if not \c NULL then + * the temperature at the end of the algorithm will be stored + * here. + * \param membership Pointer to an initialized vector or \c NULL. If + * not \c NULL then the result of the clustering will be stored + * here, for each vertex the number of its cluster is given, the + * first cluster is numbered zero. The vector will be resized as + * needed. + * \param csize Pointer to an initialized vector or \c NULL. If not \c + * NULL then the sizes of the clusters will stored here in cluster + * number order. The vector will be resized as needed. + * \param spins Integer giving the number of spins, ie. the maximum + * number of clusters. Usually it is not a program to give a high + * number here, the default was 25 in the original code. Even if + * the number of spins is high the number of clusters in the + * result might small. + * \param parupdate A logical constant, whether to update all spins in + * parallel. The default for this argument was \c FALSE (ie. 0) in + * the original code. It is not implemented in the \c + * IGRAPH_SPINCOMM_INP_NEG implementation. + * \param starttemp Real number, the temperature at the start. The + * value of this argument was 1.0 in the original code. + * \param stoptemp Real number, the algorithm stops at this + * temperature. The default was 0.01 in the original code. + * \param coolfact Real number, the coolinf factor for the simulated + * annealing. The default was 0.99 in the original code. + * \param update_rule The type of the update rule. Possible values: \c + * IGRAPH_SPINCOMM_UPDATE_SIMPLE and \c + * IGRAPH_SPINCOMM_UPDATE_CONFIG. Basically this parameter defined + * the null model based on which the actual clustering is done. If + * this is \c IGRAPH_SPINCOMM_UPDATE_SIMPLE then the random graph + * (ie. G(n,p)), if it is \c IGRAPH_SPINCOMM_UPDATE then the + * configuration model is used. The configuration means that the + * baseline for the clustering is a random graph with the same + * degree distribution as the input graph. + * \param gamma Real number. The gamma parameter of the + * algorithm. This defined the weight of the missing and existing + * links in the quality function for the clustering. The default + * value in the original code was 1.0, which is equal weight to + * missing and existing edges. Smaller values make the existing + * links contibute more to the energy function which is minimized + * in the algorithm. Bigger values make the missing links more + * important. (If my understanding is correct.) + * \param implementation Constant, chooses between the two + * implementations of the spin-glass algorithm that are included + * in igraph. \c IGRAPH_SPINCOMM_IMP_ORIG selects the original + * implementation, this is faster, \c IGRAPH_SPINCOMM_INP_NEG selects + * a new implementation by Vincent Traag that allows negative edge + * weights. + * \param gamma_minus Real number. Parameter for the \c + * IGRAPH_SPINCOMM_IMP_NEG implementation. This + * specifies the balance between the importance of present and + * non-present negative weighted edges in a community. Smaller values of + * \p gamma_minus lead to communities with lesser + * negative intra-connectivity. + * If this argument is set to zero, the algorithm reduces to a graph + * coloring algorithm, using the number of spins as the number of + * colors. + * \return Error code. + * + * \sa igraph_community_spinglass_single() for calculating the community + * of a single vertex. + * + * Time complexity: TODO. + * + * \example examples/simple/spinglass.c + */ + +int igraph_community_spinglass(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_t *membership, + igraph_vector_t *csize, + igraph_integer_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma, + /* the rest is for the NegSpin implementation */ + igraph_spinglass_implementation_t implementation, + /* igraph_matrix_t *adhesion, */ + /* igraph_matrix_t *normalised_adhesion, */ + /* igraph_real_t *polarization, */ + igraph_real_t gamma_minus) { + + switch (implementation) { + case IGRAPH_SPINCOMM_IMP_ORIG: + return igraph_i_community_spinglass_orig(graph, weights, modularity, + temperature, membership, csize, + spins, parupdate, starttemp, + stoptemp, coolfact, update_rule, + gamma); + break; + case IGRAPH_SPINCOMM_IMP_NEG: + return igraph_i_community_spinglass_negative(graph, weights, modularity, + temperature, membership, csize, + spins, parupdate, starttemp, + stoptemp, coolfact, + update_rule, gamma, + /* adhesion, normalised_adhesion, */ + /* polarization, */ + gamma_minus); + break; + default: + IGRAPH_ERROR("Unknown `implementation' in spinglass community finding", + IGRAPH_EINVAL); + } + + return 0; +} + +static int igraph_i_community_spinglass_orig( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_t *membership, + igraph_vector_t *csize, + igraph_integer_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma) { + + unsigned long changes, runs; + igraph_bool_t use_weights = 0; + bool zeroT; + double kT, acc, prob; + ClusterList *cl_cur; + network *net; + PottsModel *pm; + + /* Check arguments */ + + if (spins < 2 || spins > 500) { + IGRAPH_ERROR("Invalid number of spins", IGRAPH_EINVAL); + } + if (update_rule != IGRAPH_SPINCOMM_UPDATE_SIMPLE && + update_rule != IGRAPH_SPINCOMM_UPDATE_CONFIG) { + IGRAPH_ERROR("Invalid update rule", IGRAPH_EINVAL); + } + if (weights) { + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + use_weights = 1; + } + if (coolfact < 0 || coolfact >= 1.0) { + IGRAPH_ERROR("Invalid cooling factor", IGRAPH_EINVAL); + } + if (gamma < 0.0) { + IGRAPH_ERROR("Invalid gamma value", IGRAPH_EINVAL); + } + if (starttemp / stoptemp < 1.0) { + IGRAPH_ERROR("starttemp should be larger in absolute value than stoptemp", + IGRAPH_EINVAL); + } + + /* Check whether we have a single component */ + igraph_bool_t conn; + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (!conn) { + IGRAPH_ERROR("Cannot work with unconnected graph", IGRAPH_EINVAL); + } + + net = new network; + net->node_list = new DL_Indexed_List(); + net->link_list = new DL_Indexed_List(); + net->cluster_list = new DL_Indexed_List*>(); + + /* Transform the igraph_t */ + IGRAPH_CHECK(igraph_i_read_network(graph, weights, + net, use_weights, 0)); + + prob = 2.0 * net->sum_weights / double(net->node_list->Size()) + / double(net->node_list->Size() - 1); + + pm = new PottsModel(net, (unsigned int)spins, update_rule); + + /* initialize the random number generator */ + RNG_BEGIN(); + + if ((stoptemp == 0.0) && (starttemp == 0.0)) { + zeroT = true; + } else { + zeroT = false; + } + if (!zeroT) { + kT = pm->FindStartTemp(gamma, prob, starttemp); + } else { + kT = stoptemp; + } + /* assign random initial configuration */ + pm->assign_initial_conf(-1); + runs = 0; + changes = 1; + + while (changes > 0 && (kT / stoptemp > 1.0 || (zeroT && runs < 150))) { + + IGRAPH_ALLOW_INTERRUPTION(); /* This is not clean.... */ + + runs++; + if (!zeroT) { + kT *= coolfact; + if (parupdate) { + changes = pm->HeatBathParallelLookup(gamma, prob, kT, 50); + } else { + acc = pm->HeatBathLookup(gamma, prob, kT, 50); + if (acc < (1.0 - 1.0 / double(spins)) * 0.01) { + changes = 0; + } else { + changes = 1; + } + } + } else { + if (parupdate) { + changes = pm->HeatBathParallelLookupZeroTemp(gamma, prob, 50); + } else { + acc = pm->HeatBathLookupZeroTemp(gamma, prob, 50); + /* less than 1 percent acceptance ratio */ + if (acc < (1.0 - 1.0 / double(spins)) * 0.01) { + changes = 0; + } else { + changes = 1; + } + } + } + } /* while loop */ + + pm->WriteClusters(modularity, temperature, csize, membership, kT, gamma); + + while (net->link_list->Size()) { + delete net->link_list->Pop(); + } + while (net->node_list->Size()) { + delete net->node_list->Pop(); + } + while (net->cluster_list->Size()) { + cl_cur = net->cluster_list->Pop(); + while (cl_cur->Size()) { + cl_cur->Pop(); + } + delete cl_cur; + } + delete net->link_list; + delete net->node_list; + delete net->cluster_list; + + RNG_END(); + + delete net; + delete pm; + + return 0; +} + +/** + * \function igraph_community_spinglass_single + * \brief Community of a single node based on statistical mechanics + * + * This function implements the community structure detection + * algorithm proposed by Joerg Reichardt and Stefan Bornholdt. It is + * described in their paper: Statistical Mechanics of + * Community Detection, http://arxiv.org/abs/cond-mat/0603718 . + * + * + * This function calculates the community of a single vertex without + * calculating all the communities in the graph. + * + * \param graph The input graph, it may be directed but the direction + * of the edges is not used in the algorithm. + * \param weights Pointer to a vector with the weights of the edges. + * Alternatively \c NULL can be supplied to have the same weight + * for every edge. + * \param vertex The vertex id of the vertex of which ths community is + * calculated. + * \param community Pointer to an initialized vector, the result, the + * ids of the vertices in the community of the input vertex will be + * stored here. The vector will be resized as needed. + * \param cohesion Pointer to a real variable, if not \c NULL the + * cohesion index of the community will be stored here. + * \param adhesion Pointer to a real variable, if not \c NULL the + * adhesion index of the community will be stored here. + * \param inner_links Pointer to an integer, if not \c NULL the + * number of edges within the community is stored here. + * \param outer_links Pointer to an integer, if not \c NULL the + * number of edges between the community and the rest of the graph + * will be stored here. + * \param spins The number of spins to use, this can be higher than + * the actual number of clusters in the network, in which case some + * clusters will contain zero vertices. + * \param update_rule The type of the update rule. Possible values: \c + * IGRAPH_SPINCOMM_UPDATE_SIMPLE and \c + * IGRAPH_SPINCOMM_UPDATE_CONFIG. Basically this parameter defined + * the null model based on which the actual clustering is done. If + * this is \c IGRAPH_SPINCOMM_UPDATE_SIMPLE then the random graph + * (ie. G(n,p)), if it is \c IGRAPH_SPINCOMM_UPDATE then the + * configuration model is used. The configuration means that the + * baseline for the clustering is a random graph with the same + * degree distribution as the input graph. + * \param gamma Real number. The gamma parameter of the + * algorithm. This defined the weight of the missing and existing + * links in the quality function for the clustering. The default + * value in the original code was 1.0, which is equal weight to + * missing and existing edges. Smaller values make the existing + * links contibute more to the energy function which is minimized + * in the algorithm. Bigger values make the missing links more + * important. (If my understanding is correct.) + * \return Error code. + * + * \sa igraph_community_spinglass() for the traditional version of the + * algorithm. + * + * Time complexity: TODO. + */ + +int igraph_community_spinglass_single(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_integer_t vertex, + igraph_vector_t *community, + igraph_real_t *cohesion, + igraph_real_t *adhesion, + igraph_integer_t *inner_links, + igraph_integer_t *outer_links, + igraph_integer_t spins, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma) { + + igraph_bool_t use_weights = 0; + double prob; + ClusterList *cl_cur; + network *net; + PottsModel *pm; + char startnode[255]; + + /* Check arguments */ + + if (spins < 2 || spins > 500) { + IGRAPH_ERROR("Invalid number of spins", IGRAPH_EINVAL); + } + if (update_rule != IGRAPH_SPINCOMM_UPDATE_SIMPLE && + update_rule != IGRAPH_SPINCOMM_UPDATE_CONFIG) { + IGRAPH_ERROR("Invalid update rule", IGRAPH_EINVAL); + } + if (weights) { + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + use_weights = 1; + } + if (gamma < 0.0) { + IGRAPH_ERROR("Invalid gamme value", IGRAPH_EINVAL); + } + if (vertex < 0 || vertex > igraph_vcount(graph)) { + IGRAPH_ERROR("Invalid vertex id", IGRAPH_EINVAL); + } + + /* Check whether we have a single component */ + igraph_bool_t conn; + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (!conn) { + IGRAPH_ERROR("Cannot work with unconnected graph", IGRAPH_EINVAL); + } + + net = new network; + net->node_list = new DL_Indexed_List(); + net->link_list = new DL_Indexed_List(); + net->cluster_list = new DL_Indexed_List*>(); + + /* Transform the igraph_t */ + IGRAPH_CHECK(igraph_i_read_network(graph, weights, + net, use_weights, 0)); + + prob = 2.0 * net->sum_weights / double(net->node_list->Size()) + / double(net->node_list->Size() - 1); + + pm = new PottsModel(net, (unsigned int)spins, update_rule); + + /* initialize the random number generator */ + RNG_BEGIN(); + + /* to be exected, if we want to find the community around a particular node*/ + /* the initial conf is needed, because otherwise, + the degree of the nodes is not in the weight property, stupid!!! */ + pm->assign_initial_conf(-1); + snprintf(startnode, 255, "%li", (long int)vertex + 1); + pm->FindCommunityFromStart(gamma, prob, startnode, community, + cohesion, adhesion, inner_links, outer_links); + + while (net->link_list->Size()) { + delete net->link_list->Pop(); + } + while (net->node_list->Size()) { + delete net->node_list->Pop(); + } + while (net->cluster_list->Size()) { + cl_cur = net->cluster_list->Pop(); + while (cl_cur->Size()) { + cl_cur->Pop(); + } + delete cl_cur; + } + delete net->link_list; + delete net->node_list; + delete net->cluster_list; + + RNG_END(); + + delete net; + delete pm; + + return 0; +} + +static int igraph_i_community_spinglass_negative( + const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_t *membership, + igraph_vector_t *csize, + igraph_integer_t spins, + igraph_bool_t parupdate, + igraph_real_t starttemp, + igraph_real_t stoptemp, + igraph_real_t coolfact, + igraph_spincomm_update_t update_rule, + igraph_real_t gamma, + /* igraph_matrix_t *adhesion, */ + /* igraph_matrix_t *normalised_adhesion, */ + /* igraph_real_t *polarization, */ + igraph_real_t gamma_minus) { + + unsigned long changes, runs; + igraph_bool_t use_weights = 0; + bool zeroT; + double kT, acc; + ClusterList *cl_cur; + network *net; + PottsModelN *pm; + igraph_real_t d_n; + igraph_real_t d_p; + + /* Check arguments */ + + if (parupdate) { + IGRAPH_ERROR("Parallel spin update not implemented with " + "negative gamma", IGRAPH_UNIMPLEMENTED); + } + + if (spins < 2 || spins > 500) { + IGRAPH_ERROR("Invalid number of spins", IGRAPH_EINVAL); + } + if (update_rule != IGRAPH_SPINCOMM_UPDATE_SIMPLE && + update_rule != IGRAPH_SPINCOMM_UPDATE_CONFIG) { + IGRAPH_ERROR("Invalid update rule", IGRAPH_EINVAL); + } + if (weights) { + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + use_weights = 1; + } + if (coolfact < 0 || coolfact >= 1.0) { + IGRAPH_ERROR("Invalid cooling factor", IGRAPH_EINVAL); + } + if (gamma < 0.0) { + IGRAPH_ERROR("Invalid gamma value", IGRAPH_EINVAL); + } + if (starttemp / stoptemp < 1.0) { + IGRAPH_ERROR("starttemp should be larger in absolute value than stoptemp", + IGRAPH_EINVAL); + } + + /* Check whether we have a single component */ + igraph_bool_t conn; + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (!conn) { + IGRAPH_ERROR("Cannot work with unconnected graph", IGRAPH_EINVAL); + } + + if (weights) { + igraph_vector_minmax(weights, &d_n, &d_p); + } else { + d_n = d_p = 1; + } + + if (d_n > 0) { + d_n = 0; + } + if (d_p < 0) { + d_p = 0; + } + d_n = -d_n; + + net = new network; + net->node_list = new DL_Indexed_List(); + net->link_list = new DL_Indexed_List(); + net->cluster_list = new DL_Indexed_List*>(); + + /* Transform the igraph_t */ + IGRAPH_CHECK(igraph_i_read_network(graph, weights, + net, use_weights, 0)); + + bool directed = igraph_is_directed(graph); + + pm = new PottsModelN(net, (unsigned int)spins, directed); + + /* initialize the random number generator */ + RNG_BEGIN(); + + if ((stoptemp == 0.0) && (starttemp == 0.0)) { + zeroT = true; + } else { + zeroT = false; + } + + //Begin at a high enough temperature + kT = pm->FindStartTemp(gamma, gamma_minus, starttemp); + + /* assign random initial configuration */ + pm->assign_initial_conf(true); + + runs = 0; + changes = 1; + acc = 0; + while (changes > 0 && (kT / stoptemp > 1.0 || (zeroT && runs < 150))) { + + IGRAPH_ALLOW_INTERRUPTION(); /* This is not clean.... */ + + runs++; + kT = kT * coolfact; + acc = pm->HeatBathLookup(gamma, gamma_minus, kT, 50); + if (acc < (1.0 - 1.0 / double(spins)) * 0.001) { + changes = 0; + } else { + changes = 1; + } + + } /* while loop */ + + /* These are needed, otherwise 'modularity' is not calculated */ + igraph_matrix_t adhesion, normalized_adhesion; + igraph_real_t polarization; + IGRAPH_MATRIX_INIT_FINALLY(&adhesion, 0, 0); + IGRAPH_MATRIX_INIT_FINALLY(&normalized_adhesion, 0, 0); + pm->WriteClusters(modularity, temperature, csize, membership, + &adhesion, &normalized_adhesion, &polarization, + kT, d_p, d_n, gamma, gamma_minus); + igraph_matrix_destroy(&normalized_adhesion); + igraph_matrix_destroy(&adhesion); + IGRAPH_FINALLY_CLEAN(2); + + while (net->link_list->Size()) { + delete net->link_list->Pop(); + } + while (net->node_list->Size()) { + delete net->node_list->Pop(); + } + while (net->cluster_list->Size()) { + cl_cur = net->cluster_list->Pop(); + while (cl_cur->Size()) { + cl_cur->Pop(); + } + delete cl_cur; + } + + RNG_END(); + + return 0; +} diff --git a/src/cocitation.c b/src/cocitation.c new file mode 100644 index 0000000..ca8020d --- /dev/null +++ b/src/cocitation.c @@ -0,0 +1,777 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph R package. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_cocitation.h" +#include "igraph_memory.h" +#include "igraph_adjlist.h" +#include "igraph_interrupt_internal.h" +#include "igraph_interface.h" +#include "config.h" +#include + +int igraph_cocitation_real(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t vids, igraph_neimode_t mode, + igraph_vector_t *weights); + +/** + * \ingroup structural + * \function igraph_cocitation + * \brief Cocitation coupling. + * + * + * Two vertices are cocited if there is another vertex citing both of + * them. \ref igraph_cocitation() simply counts how many times two vertices are + * cocited. + * The cocitation score for each given vertex and all other vertices + * in the graph will be calculated. + * \param graph The graph object to analyze. + * \param res Pointer to a matrix, the result of the calculation will + * be stored here. The number of its rows is the same as the + * number of vertex ids in \p vids, the number of + * columns is the number of vertices in the graph. + * \param vids The vertex ids of the vertices for which the + * calculation will be done. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex id. + * + * Time complexity: O(|V|d^2), |V| is + * the number of vertices in the graph, + * d is the (maximum) degree of + * the vertices in the graph. + * + * \sa \ref igraph_bibcoupling() + * + * \example examples/simple/igraph_cocitation.c + */ + +int igraph_cocitation(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t vids) { + return igraph_cocitation_real(graph, res, vids, IGRAPH_OUT, 0); +} + +/** + * \ingroup structural + * \function igraph_bibcoupling + * \brief Bibliographic coupling. + * + * + * The bibliographic coupling of two vertices is the number + * of other vertices they both cite, \ref igraph_bibcoupling() calculates + * this. + * The bibliographic coupling score for each given vertex and all + * other vertices in the graph will be calculated. + * \param graph The graph object to analyze. + * \param res Pointer to a matrix, the result of the calculation will + * be stored here. The number of its rows is the same as the + * number of vertex ids in \p vids, the number of + * columns is the number of vertices in the graph. + * \param vids The vertex ids of the vertices for which the + * calculation will be done. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex id. + * + * Time complexity: O(|V|d^2), + * |V| is the number of vertices in + * the graph, d is the (maximum) + * degree of the vertices in the graph. + * + * \sa \ref igraph_cocitation() + */ + +int igraph_bibcoupling(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t vids) { + return igraph_cocitation_real(graph, res, vids, IGRAPH_IN, 0); +} + +/** + * \ingroup structural + * \function igraph_similarity_inverse_log_weighted + * \brief Vertex similarity based on the inverse logarithm of vertex degrees. + * + * + * The inverse log-weighted similarity of two vertices is the number of + * their common neighbors, weighted by the inverse logarithm of their degrees. + * It is based on the assumption that two vertices should be considered + * more similar if they share a low-degree common neighbor, since high-degree + * common neighbors are more likely to appear even by pure chance. + * + * + * Isolated vertices will have zero similarity to any other vertex. + * Self-similarities are not calculated. + * + * + * See the following paper for more details: Lada A. Adamic and Eytan Adar: + * Friends and neighbors on the Web. Social Networks, 25(3):211-230, 2003. + * + * \param graph The graph object to analyze. + * \param res Pointer to a matrix, the result of the calculation will + * be stored here. The number of its rows is the same as the + * number of vertex ids in \p vids, the number of + * columns is the number of vertices in the graph. + * \param vids The vertex ids of the vertices for which the + * calculation will be done. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. Nodes + * will be weighted according to their in-degree. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. Nodes + * will be weighted according to their out-degree. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. Every node is weighted according to its undirected + * degree. + * \endclist + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex id. + * + * Time complexity: O(|V|d^2), + * |V| is the number of vertices in + * the graph, d is the (maximum) + * degree of the vertices in the graph. + * + * \example examples/simple/igraph_similarity.c + */ + +int igraph_similarity_inverse_log_weighted(const igraph_t *graph, + igraph_matrix_t *res, const igraph_vs_t vids, igraph_neimode_t mode) { + igraph_vector_t weights; + igraph_neimode_t mode0; + long int i, no_of_nodes; + + switch (mode) { + case IGRAPH_OUT: mode0 = IGRAPH_IN; break; + case IGRAPH_IN: mode0 = IGRAPH_OUT; break; + default: mode0 = IGRAPH_ALL; + } + + no_of_nodes = igraph_vcount(graph); + + IGRAPH_VECTOR_INIT_FINALLY(&weights, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, &weights, igraph_vss_all(), mode0, 1)); + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(weights)[i] > 1) { + VECTOR(weights)[i] = 1.0 / log(VECTOR(weights)[i]); + } + } + + IGRAPH_CHECK(igraph_cocitation_real(graph, res, vids, mode0, &weights)); + igraph_vector_destroy(&weights); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +int igraph_cocitation_real(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t vids, + igraph_neimode_t mode, + igraph_vector_t *weights) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_vids; + long int from, i, j, k, l, u, v; + igraph_vector_t neis = IGRAPH_VECTOR_NULL; + igraph_vector_t vid_reverse_index; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + no_of_vids = IGRAPH_VIT_SIZE(vit); + + /* Create a mapping from vertex IDs to the row of the matrix where + * the result for this vertex will appear */ + IGRAPH_VECTOR_INIT_FINALLY(&vid_reverse_index, no_of_nodes); + igraph_vector_fill(&vid_reverse_index, -1); + for (IGRAPH_VIT_RESET(vit), i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + v = IGRAPH_VIT_GET(vit); + if (v < 0 || v >= no_of_nodes) { + IGRAPH_ERROR("invalid vertex ID in vertex selector", IGRAPH_EINVAL); + } + VECTOR(vid_reverse_index)[v] = i; + } + + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_vids, no_of_nodes)); + igraph_matrix_null(res); + + /* The result */ + + for (from = 0; from < no_of_nodes; from++) { + igraph_real_t weight = 1; + + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_CHECK(igraph_neighbors(graph, &neis, + (igraph_integer_t) from, mode)); + if (weights) { + weight = VECTOR(*weights)[from]; + } + + for (i = 0; i < igraph_vector_size(&neis) - 1; i++) { + u = (long int) VECTOR(neis)[i]; + k = (long int) VECTOR(vid_reverse_index)[u]; + for (j = i + 1; j < igraph_vector_size(&neis); j++) { + v = (long int) VECTOR(neis)[j]; + l = (long int) VECTOR(vid_reverse_index)[v]; + if (k != -1) { + MATRIX(*res, k, v) += weight; + } + if (l != -1) { + MATRIX(*res, l, u) += weight; + } + } + } + } + + /* Clean up */ + igraph_vector_destroy(&neis); + igraph_vector_destroy(&vid_reverse_index); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + + +static int igraph_i_neisets_intersect(const igraph_vector_t *v1, + const igraph_vector_t *v2, long int *len_union, + long int *len_intersection) { + /* ASSERT: v1 and v2 are sorted */ + long int i, j, i0, jj0; + i0 = igraph_vector_size(v1); jj0 = igraph_vector_size(v2); + *len_union = i0 + jj0; *len_intersection = 0; + i = 0; j = 0; + while (i < i0 && j < jj0) { + if (VECTOR(*v1)[i] == VECTOR(*v2)[j]) { + (*len_intersection)++; (*len_union)--; + i++; j++; + } else if (VECTOR(*v1)[i] < VECTOR(*v2)[j]) { + i++; + } else { + j++; + } + } + return 0; +} + +/** + * \ingroup structural + * \function igraph_similarity_jaccard + * \brief Jaccard similarity coefficient for the given vertices. + * + * + * The Jaccard similarity coefficient of two vertices is the number of common + * neighbors divided by the number of vertices that are neighbors of at + * least one of the two vertices being considered. This function calculates + * the pairwise Jaccard similarities for some (or all) of the vertices. + * + * \param graph The graph object to analyze + * \param res Pointer to a matrix, the result of the calculation will + * be stored here. The number of its rows and columns is the same + * as the number of vertex ids in \p vids. + * \param vids The vertex ids of the vertices for which the + * calculation will be done. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves in the neighbor + * sets. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex id passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|V|^2 d), + * |V| is the number of vertices in the vertex iterator given, d is the + * (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_dice(), a measure very similar to the Jaccard + * coefficient + * + * \example examples/simple/igraph_similarity.c + */ +int igraph_similarity_jaccard(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t vids, igraph_neimode_t mode, igraph_bool_t loops) { + igraph_lazy_adjlist_t al; + igraph_vit_t vit, vit2; + long int i, j, k; + long int len_union, len_intersection; + igraph_vector_t *v1, *v2; + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit2)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit2); + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &al, mode, IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &al); + + IGRAPH_CHECK(igraph_matrix_resize(res, IGRAPH_VIT_SIZE(vit), IGRAPH_VIT_SIZE(vit))); + + if (loops) { + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + i = IGRAPH_VIT_GET(vit); + v1 = igraph_lazy_adjlist_get(&al, (igraph_integer_t) i); + if (!igraph_vector_binsearch(v1, i, &k)) { + igraph_vector_insert(v1, k, i); + } + } + } + + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + MATRIX(*res, i, i) = 1.0; + for (IGRAPH_VIT_RESET(vit2), j = 0; + !IGRAPH_VIT_END(vit2); IGRAPH_VIT_NEXT(vit2), j++) { + if (j <= i) { + continue; + } + v1 = igraph_lazy_adjlist_get(&al, IGRAPH_VIT_GET(vit)); + v2 = igraph_lazy_adjlist_get(&al, IGRAPH_VIT_GET(vit2)); + igraph_i_neisets_intersect(v1, v2, &len_union, &len_intersection); + if (len_union > 0) { + MATRIX(*res, i, j) = ((igraph_real_t)len_intersection) / len_union; + } else { + MATRIX(*res, i, j) = 0.0; + } + MATRIX(*res, j, i) = MATRIX(*res, i, j); + } + } + + igraph_lazy_adjlist_destroy(&al); + igraph_vit_destroy(&vit); + igraph_vit_destroy(&vit2); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \ingroup structural + * \function igraph_similarity_jaccard_pairs + * \brief Jaccard similarity coefficient for given vertex pairs. + * + * + * The Jaccard similarity coefficient of two vertices is the number of common + * neighbors divided by the number of vertices that are neighbors of at + * least one of the two vertices being considered. This function calculates + * the pairwise Jaccard similarities for a list of vertex pairs. + * + * \param graph The graph object to analyze + * \param res Pointer to a vector, the result of the calculation will + * be stored here. The number of elements is the same as the number + * of pairs in \p pairs. + * \param pairs A vector that contains the pairs for which the similarity + * will be calculated. Each pair is defined by two consecutive elements, + * i.e. the first and second element of the vector specifies the first + * pair, the third and fourth element specifies the second pair and so on. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves in the neighbor + * sets. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex id passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(nd), n is the number of pairs in the given vector, d is + * the (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_jaccard() to calculate the Jaccard similarity + * between all pairs of a vertex set, or \ref igraph_similarity_dice() and + * \ref igraph_similarity_dice_pairs() for a measure very similar to the + * Jaccard coefficient + * + * \example examples/simple/igraph_similarity.c + */ +int igraph_similarity_jaccard_pairs(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *pairs, igraph_neimode_t mode, igraph_bool_t loops) { + igraph_lazy_adjlist_t al; + long int i, j, k, u, v; + long int len_union, len_intersection; + igraph_vector_t *v1, *v2; + igraph_bool_t *seen; + + k = igraph_vector_size(pairs); + if (k % 2 != 0) { + IGRAPH_ERROR("number of elements in `pairs' must be even", IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_vector_resize(res, k / 2)); + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &al, mode, IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &al); + + if (loops) { + /* Add the loop edges */ + i = igraph_vcount(graph); + seen = igraph_Calloc(i, igraph_bool_t); + if (seen == 0) { + IGRAPH_ERROR("cannot calculate Jaccard similarity", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, seen); + + for (i = 0; i < k; i++) { + j = (long int) VECTOR(*pairs)[i]; + if (seen[j]) { + continue; + } + seen[j] = 1; + v1 = igraph_lazy_adjlist_get(&al, (igraph_integer_t) j); + if (!igraph_vector_binsearch(v1, j, &u)) { + igraph_vector_insert(v1, u, j); + } + } + + igraph_Free(seen); + IGRAPH_FINALLY_CLEAN(1); + } + + for (i = 0, j = 0; i < k; i += 2, j++) { + u = (long int) VECTOR(*pairs)[i]; + v = (long int) VECTOR(*pairs)[i + 1]; + + if (u == v) { + VECTOR(*res)[j] = 1.0; + continue; + } + + v1 = igraph_lazy_adjlist_get(&al, (igraph_integer_t) u); + v2 = igraph_lazy_adjlist_get(&al, (igraph_integer_t) v); + igraph_i_neisets_intersect(v1, v2, &len_union, &len_intersection); + if (len_union > 0) { + VECTOR(*res)[j] = ((igraph_real_t)len_intersection) / len_union; + } else { + VECTOR(*res)[j] = 0.0; + } + } + + igraph_lazy_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \ingroup structural + * \function igraph_similarity_jaccard_es + * \brief Jaccard similarity coefficient for a given edge selector. + * + * + * The Jaccard similarity coefficient of two vertices is the number of common + * neighbors divided by the number of vertices that are neighbors of at + * least one of the two vertices being considered. This function calculates + * the pairwise Jaccard similarities for the endpoints of edges in a given edge + * selector. + * + * \param graph The graph object to analyze + * \param res Pointer to a vector, the result of the calculation will + * be stored here. The number of elements is the same as the number + * of edges in \p es. + * \param es An edge selector that specifies the edges to be included in the + * result. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves in the neighbor + * sets. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex id passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(nd), n is the number of edges in the edge selector, d is + * the (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_jaccard() and \ref igraph_similarity_jaccard_pairs() + * to calculate the Jaccard similarity between all pairs of a vertex set or + * some selected vertex pairs, or \ref igraph_similarity_dice(), + * \ref igraph_similarity_dice_pairs() and \ref igraph_similarity_dice_es() for a + * measure very similar to the Jaccard coefficient + * + * \example examples/simple/igraph_similarity.c + */ +int igraph_similarity_jaccard_es(const igraph_t *graph, igraph_vector_t *res, + const igraph_es_t es, igraph_neimode_t mode, igraph_bool_t loops) { + igraph_vector_t v; + igraph_eit_t eit; + + IGRAPH_VECTOR_INIT_FINALLY(&v, 0); + + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + while (!IGRAPH_EIT_END(eit)) { + long int eid = IGRAPH_EIT_GET(eit); + igraph_vector_push_back(&v, IGRAPH_FROM(graph, eid)); + igraph_vector_push_back(&v, IGRAPH_TO(graph, eid)); + IGRAPH_EIT_NEXT(eit); + } + + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_similarity_jaccard_pairs(graph, res, &v, mode, loops)); + igraph_vector_destroy(&v); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_similarity_dice + * \brief Dice similarity coefficient. + * + * + * The Dice similarity coefficient of two vertices is twice the number of common + * neighbors divided by the sum of the degrees of the vertices. This function + * calculates the pairwise Dice similarities for some (or all) of the vertices. + * + * \param graph The graph object to analyze + * \param res Pointer to a matrix, the result of the calculation will + * be stored here. The number of its rows and columns is the same + * as the number of vertex ids in \p vids. + * \param vids The vertex ids of the vertices for which the + * calculation will be done. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves as their own + * neighbors. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex id passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|V|^2 d), + * |V| is the number of vertices in the vertex iterator given, d is the + * (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_jaccard(), a measure very similar to the Dice + * coefficient + * + * \example examples/simple/igraph_similarity.c + */ +int igraph_similarity_dice(const igraph_t *graph, igraph_matrix_t *res, + const igraph_vs_t vids, igraph_neimode_t mode, igraph_bool_t loops) { + long int i, j, nr, nc; + + IGRAPH_CHECK(igraph_similarity_jaccard(graph, res, vids, mode, loops)); + + nr = igraph_matrix_nrow(res); + nc = igraph_matrix_ncol(res); + for (i = 0; i < nr; i++) { + for (j = 0; j < nc; j++) { + igraph_real_t x = MATRIX(*res, i, j); + MATRIX(*res, i, j) = 2 * x / (1 + x); + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_similarity_dice_pairs + * \brief Dice similarity coefficient for given vertex pairs. + * + * + * The Dice similarity coefficient of two vertices is twice the number of common + * neighbors divided by the sum of the degrees of the vertices. This function + * calculates the pairwise Dice similarities for a list of vertex pairs. + * + * \param graph The graph object to analyze + * \param res Pointer to a vector, the result of the calculation will + * be stored here. The number of elements is the same as the number + * of pairs in \p pairs. + * \param pairs A vector that contains the pairs for which the similarity + * will be calculated. Each pair is defined by two consecutive elements, + * i.e. the first and second element of the vector specifies the first + * pair, the third and fourth element specifies the second pair and so on. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves as their own + * neighbors. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex id passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(nd), n is the number of pairs in the given vector, d is + * the (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_dice() to calculate the Dice similarity + * between all pairs of a vertex set, or \ref igraph_similarity_jaccard(), + * \ref igraph_similarity_jaccard_pairs() and \ref igraph_similarity_jaccard_es() + * for a measure very similar to the Dice coefficient + * + * \example examples/simple/igraph_similarity.c + */ +int igraph_similarity_dice_pairs(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *pairs, igraph_neimode_t mode, igraph_bool_t loops) { + long int i, n; + + IGRAPH_CHECK(igraph_similarity_jaccard_pairs(graph, res, pairs, mode, loops)); + n = igraph_vector_size(res); + for (i = 0; i < n; i++) { + igraph_real_t x = VECTOR(*res)[i]; + VECTOR(*res)[i] = 2 * x / (1 + x); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_similarity_dice_es + * \brief Dice similarity coefficient for a given edge selector. + * + * + * The Dice similarity coefficient of two vertices is twice the number of common + * neighbors divided by the sum of the degrees of the vertices. This function + * calculates the pairwise Dice similarities for the endpoints of edges in a given + * edge selector. + * + * \param graph The graph object to analyze + * \param res Pointer to a vector, the result of the calculation will + * be stored here. The number of elements is the same as the number + * of edges in \p es. + * \param es An edge selector that specifies the edges to be included in the + * result. + * \param mode The type of neighbors to be used for the calculation in + * directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing edges will be considered for each node. + * \cli IGRAPH_IN + * the incoming edges will be considered for each node. + * \cli IGRAPH_ALL + * the directed graph is considered as an undirected one for the + * computation. + * \endclist + * \param loops Whether to include the vertices themselves as their own + * neighbors. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * invalid vertex id passed. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(nd), n is the number of pairs in the given vector, d is + * the (maximum) degree of the vertices in the graph. + * + * \sa \ref igraph_similarity_dice() and \ref igraph_similarity_dice_pairs() + * to calculate the Dice similarity between all pairs of a vertex set or + * some selected vertex pairs, or \ref igraph_similarity_jaccard(), + * \ref igraph_similarity_jaccard_pairs() and \ref igraph_similarity_jaccard_es() + * for a measure very similar to the Dice coefficient + * + * \example examples/simple/igraph_similarity.c + */ +int igraph_similarity_dice_es(const igraph_t *graph, igraph_vector_t *res, + const igraph_es_t es, igraph_neimode_t mode, igraph_bool_t loops) { + long int i, n; + + IGRAPH_CHECK(igraph_similarity_jaccard_es(graph, res, es, mode, loops)); + n = igraph_vector_size(res); + for (i = 0; i < n; i++) { + igraph_real_t x = VECTOR(*res)[i]; + VECTOR(*res)[i] = 2 * x / (1 + x); + } + + return IGRAPH_SUCCESS; +} + diff --git a/src/cohesive_blocks.c b/src/cohesive_blocks.c new file mode 100644 index 0000000..6cfb5c0 --- /dev/null +++ b/src/cohesive_blocks.c @@ -0,0 +1,611 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_cohesive_blocks.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_flow.h" +#include "igraph_separators.h" +#include "igraph_structural.h" +#include "igraph_dqueue.h" +#include "igraph_constructors.h" +#include "igraph_interrupt_internal.h" +#include "igraph_statusbar.h" + +static void igraph_i_cohesive_blocks_free(igraph_vector_ptr_t *ptr) { + long int i, n = igraph_vector_ptr_size(ptr); + + for (i = 0; i < n; i++) { + igraph_t *g = VECTOR(*ptr)[i]; + if (g) { + igraph_destroy(g); + igraph_free(g); + } + } +} + +static void igraph_i_cohesive_blocks_free2(igraph_vector_ptr_t *ptr) { + long int i, n = igraph_vector_ptr_size(ptr); + + for (i = 0; i < n; i++) { + igraph_vector_long_t *v = VECTOR(*ptr)[i]; + if (v) { + igraph_vector_long_destroy(v); + igraph_free(v); + } + } +} + +static void igraph_i_cohesive_blocks_free3(igraph_vector_ptr_t *ptr) { + long int i, n = igraph_vector_ptr_size(ptr); + + for (i = 0; i < n; i++) { + igraph_vector_t *v = VECTOR(*ptr)[i]; + if (v) { + igraph_vector_destroy(v); + igraph_free(v); + } + } +} + +/* This is kind of a BFS to find the components of the graph, after + * deleting the vertices marked in 'excluded'. + * These vertices are not put in the BFS queue, but they are added to + * all neighboring components. + */ + +static int igraph_i_cb_components(igraph_t *graph, + const igraph_vector_bool_t *excluded, + igraph_vector_long_t *components, + long int *no, + /* working area follows */ + igraph_vector_long_t *compid, + igraph_dqueue_t *Q, + igraph_vector_t *neis) { + + long int no_of_nodes = igraph_vcount(graph); + long int i; + long int cno = 0; + + igraph_vector_long_clear(components); + igraph_dqueue_clear(Q); + IGRAPH_CHECK(igraph_vector_long_resize(compid, no_of_nodes)); + igraph_vector_long_null(compid); + + for (i = 0; i < no_of_nodes; i++) { + + if (VECTOR(*compid)[i]) { + continue; + } + if (VECTOR(*excluded)[i]) { + continue; + } + + IGRAPH_CHECK(igraph_dqueue_push(Q, i)); + IGRAPH_CHECK(igraph_vector_long_push_back(components, i)); + VECTOR(*compid)[i] = ++cno; + + while (!igraph_dqueue_empty(Q)) { + igraph_integer_t node = (igraph_integer_t) igraph_dqueue_pop(Q); + long int j, n; + IGRAPH_CHECK(igraph_neighbors(graph, neis, node, IGRAPH_ALL)); + n = igraph_vector_size(neis); + for (j = 0; j < n; j++) { + long int v = (long int) VECTOR(*neis)[j]; + if (VECTOR(*excluded)[v]) { + if (VECTOR(*compid)[v] != cno) { + VECTOR(*compid)[v] = cno; + IGRAPH_CHECK(igraph_vector_long_push_back(components, v)); + } + } else { + if (!VECTOR(*compid)[v]) { + VECTOR(*compid)[v] = cno; /* could be anything positive */ + IGRAPH_CHECK(igraph_vector_long_push_back(components, v)); + IGRAPH_CHECK(igraph_dqueue_push(Q, v)); + } + } + } + } /* while !igraph_dqueue_empty */ + + IGRAPH_CHECK(igraph_vector_long_push_back(components, -1)); + + } /* for ik. Thus a hiearchy of vertex subsets + * is found, whith the entire graph G at its root. See the following + * reference for details: J. Moody and D. R. White. Structural + * cohesion and embeddedness: A hierarchical concept of social + * groups. American Sociological Review, 68(1):103--127, Feb 2003. + * + * This function implements cohesive blocking and + * calculates the complete cohesive block hierarchy of a graph. + * + * \param graph The input graph. It must be undirected and simple. See + * \ref igraph_is_simple(). + * \param blocks If not a null pointer, then it must be an initialized + * vector of pointers and the cohesive blocks are stored here. + * Each block is encoded with a numeric vector, that contains the + * vertex ids of the block. + * \param cohesion If not a null pointer, then it must be an initialized + * vector and the cohesion of the blocks is stored here, in the same + * order as the blocks in the \p blocks pointer vector. + * \param parent If not a null pointer, then it must be an initialized + * vector and the block hierarchy is stored here. For each block, the + * id (i.e. the position in the \p blocks pointer vector) of its + * parent block is stored. For the top block in the hierarchy, + * -1 is stored. + * \param block_tree If not a null pointer, then it must be a pointer + * to an uninitialized graph, and the block hierarchy is stored + * here as an igraph graph. The vertex ids correspond to the order + * of the blocks in the \p blocks vector. + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/cohesive_blocks.c + */ + +int igraph_cohesive_blocks(const igraph_t *graph, + igraph_vector_ptr_t *blocks, + igraph_vector_t *cohesion, + igraph_vector_t *parent, + igraph_t *block_tree) { + + /* Some implementation comments. Everything is relatively + straightforward, except, that we need to follow the vertex ids + of the various subgraphs, without having to store two-way + mappings at each level. The subgraphs can overlap, this + complicates things a bit. + + The 'Q' vector is used as a double ended queue and it contains + the subgraphs to work on in the future. Some other vectors are + associated with it. 'Qparent' gives the parent graph of a graph + in Q. Qmapping gives the mapping of the vertices from the graph + to the parent graph. Qcohesion is the vertex connectivity of the + graph. + + Qptr is an integer and points to the next graph to work on. + */ + + igraph_vector_ptr_t Q; + igraph_vector_ptr_t Qmapping; + igraph_vector_long_t Qparent; + igraph_vector_long_t Qcohesion; + igraph_vector_bool_t Qcheck; + long int Qptr = 0; + igraph_integer_t conn; + igraph_bool_t is_simple; + + igraph_t *graph_copy; + + igraph_vector_ptr_t separators; + igraph_vector_t compvertices; + igraph_vector_long_t components; + igraph_vector_bool_t marked; + + igraph_vector_long_t compid; + igraph_dqueue_t bfsQ; + igraph_vector_t neis; + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Cohesive blocking only works on undirected graphs", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_is_simple(graph, &is_simple)); + if (!is_simple) { + IGRAPH_ERROR("Cohesive blocking only works on simple graphs", + IGRAPH_EINVAL); + } + + IGRAPH_STATUS("Starting cohesive block calculation.\n", 0); + + if (blocks) { + igraph_vector_ptr_clear(blocks); + } + if (cohesion) { + igraph_vector_clear(cohesion); + } + if (parent) { + igraph_vector_clear(parent); + } + + IGRAPH_CHECK(igraph_vector_ptr_init(&Q, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &Q); + IGRAPH_FINALLY(igraph_i_cohesive_blocks_free, &Q); + + IGRAPH_CHECK(igraph_vector_ptr_init(&Qmapping, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &Qmapping); + IGRAPH_FINALLY(igraph_i_cohesive_blocks_free2, &Qmapping); + + IGRAPH_CHECK(igraph_vector_long_init(&Qparent, 1)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &Qparent); + + IGRAPH_CHECK(igraph_vector_long_init(&Qcohesion, 1)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &Qcohesion); + + IGRAPH_CHECK(igraph_vector_bool_init(&Qcheck, 1)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &Qcheck); + + IGRAPH_CHECK(igraph_vector_ptr_init(&separators, 0)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &separators); + + IGRAPH_VECTOR_INIT_FINALLY(&compvertices, 0); + IGRAPH_CHECK(igraph_vector_bool_init(&marked, 0)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &marked); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_dqueue_init(&bfsQ, 100)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &bfsQ); + IGRAPH_CHECK(igraph_vector_long_init(&compid, 0)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &compid); + IGRAPH_CHECK(igraph_vector_long_init(&components, 0)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &components); + + /* Put the input graph in the queue */ + graph_copy = igraph_Calloc(1, igraph_t); + if (!graph_copy) { + IGRAPH_ERROR("Cannot do cohesive blocking", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_copy(graph_copy, graph)); + VECTOR(Q)[0] = graph_copy; + VECTOR(Qmapping)[0] = 0; /* Identity mapping */ + VECTOR(Qparent)[0] = -1; /* Has no parent */ + IGRAPH_CHECK(igraph_vertex_connectivity(graph, &conn, /*checks=*/ 1)); + VECTOR(Qcohesion)[0] = conn; + VECTOR(Qcheck)[0] = 0; + + /* Then work until the queue is empty */ + while (Qptr < igraph_vector_ptr_size(&Q)) { + igraph_t *mygraph = VECTOR(Q)[Qptr]; + igraph_bool_t mycheck = VECTOR(Qcheck)[Qptr]; + long int mynodes = igraph_vcount(mygraph); + long int i, nsep; + long int no, kept = 0; + long int cptr = 0; + long int nsepv = 0; + igraph_bool_t addedsep = 0; + + IGRAPH_STATUSF(("Candidate %li: %li vertices,", + 0, Qptr, mynodes)); + IGRAPH_ALLOW_INTERRUPTION(); + + /* Get the separators */ + IGRAPH_CHECK(igraph_minimum_size_separators(mygraph, &separators)); + IGRAPH_FINALLY(igraph_i_cohesive_blocks_free3, &separators); + nsep = igraph_vector_ptr_size(&separators); + + IGRAPH_STATUSF((" %li separators,", 0, nsep)); + + /* Remove them from the graph, also mark them */ + IGRAPH_CHECK(igraph_vector_bool_resize(&marked, mynodes)); + igraph_vector_bool_null(&marked); + for (i = 0; i < nsep; i++) { + igraph_vector_t *v = VECTOR(separators)[i]; + long int j, n = igraph_vector_size(v); + for (j = 0; j < n; j++) { + long int vv = (long int) VECTOR(*v)[j]; + if (!VECTOR(marked)[vv]) { + nsepv++; + VECTOR(marked)[vv] = 1; + } + } + } + + /* Find the connected components, omitting the separator vertices, + but including the neighboring separator vertices + */ + IGRAPH_CHECK(igraph_i_cb_components(mygraph, &marked, + &components, &no, + &compid, &bfsQ, &neis)); + + /* Add the separator vertices themselves, as another component, + but only if there is at least one vertex not included in any + separator. */ + if (nsepv != mynodes) { + addedsep = 1; + for (i = 0; i < mynodes; i++) { + if (VECTOR(marked)[i]) { + IGRAPH_CHECK(igraph_vector_long_push_back(&components, i)); + } + } + IGRAPH_CHECK(igraph_vector_long_push_back(&components, -1)); + no++; + } + + IGRAPH_STATUSF((" %li new candidates,", 0, no)); + + for (i = 0; i < no; i++) { + igraph_vector_t *newmapping; + igraph_t *newgraph; + igraph_integer_t maxdeg; + + igraph_vector_clear(&compvertices); + + while (1) { + long int v = VECTOR(components)[cptr++]; + if (v < 0) { + break; + } + IGRAPH_CHECK(igraph_vector_push_back(&compvertices, v)); + } + + newmapping = igraph_Calloc(1, igraph_vector_t); + if (!newmapping) { + IGRAPH_ERROR("Cannot do cohesive blocking", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newmapping); + IGRAPH_VECTOR_INIT_FINALLY(newmapping, 0); + newgraph = igraph_Calloc(1, igraph_t); + if (!newgraph) { + IGRAPH_ERROR("Cannot do cohesive blocking", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newgraph); + IGRAPH_CHECK(igraph_induced_subgraph_map(mygraph, newgraph, + igraph_vss_vector(&compvertices), + IGRAPH_SUBGRAPH_AUTO, + /*map=*/ 0, + /*invmap=*/ newmapping)); + IGRAPH_FINALLY(igraph_destroy, newgraph); + + IGRAPH_CHECK(igraph_maxdegree(newgraph, &maxdeg, igraph_vss_all(), + IGRAPH_ALL, IGRAPH_LOOPS)); + if (maxdeg > VECTOR(Qcohesion)[Qptr]) { + igraph_integer_t newconn; + kept++; + IGRAPH_CHECK(igraph_vector_ptr_push_back(&Q, newgraph)); + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_CHECK(igraph_vector_ptr_push_back(&Qmapping, newmapping)); + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_CHECK(igraph_vertex_connectivity(newgraph, &newconn, + /*checks=*/ 1)); + IGRAPH_CHECK(igraph_vector_long_push_back(&Qcohesion, newconn)); + IGRAPH_CHECK(igraph_vector_long_push_back(&Qparent, Qptr)); + IGRAPH_CHECK(igraph_vector_bool_push_back(&Qcheck, + mycheck || addedsep)); + } else { + igraph_destroy(newgraph); + igraph_free(newgraph); + igraph_vector_destroy(newmapping); + igraph_free(newmapping); + IGRAPH_FINALLY_CLEAN(4); + } + } + + IGRAPH_STATUSF((" keeping %li.\n", 0, kept)); + + igraph_destroy(mygraph); + igraph_free(mygraph); + VECTOR(Q)[Qptr] = 0; + igraph_i_cohesive_blocks_free3(&separators); + IGRAPH_FINALLY_CLEAN(1); + + Qptr++; + } + + igraph_vector_long_destroy(&components); + igraph_vector_long_destroy(&compid); + igraph_dqueue_destroy(&bfsQ); + igraph_vector_destroy(&neis); + igraph_vector_bool_destroy(&marked); + igraph_vector_destroy(&compvertices); + igraph_vector_ptr_destroy(&separators); + IGRAPH_FINALLY_CLEAN(7); + + if (blocks || cohesion || parent || block_tree) { + igraph_integer_t noblocks = (igraph_integer_t) Qptr, badblocks = 0; + igraph_vector_bool_t removed; + long int i, resptr = 0; + igraph_vector_long_t rewritemap; + + IGRAPH_CHECK(igraph_vector_bool_init(&removed, noblocks)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &removed); + IGRAPH_CHECK(igraph_vector_long_init(&rewritemap, noblocks)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &rewritemap); + + for (i = 1; i < noblocks; i++) { + long int p = VECTOR(Qparent)[i]; + while (VECTOR(removed)[p]) { + p = VECTOR(Qparent)[p]; + } + if (VECTOR(Qcohesion)[p] >= VECTOR(Qcohesion)[i]) { + VECTOR(removed)[i] = 1; + badblocks++; + } + } + + /* Rewrite the mappings */ + for (i = 1; i < Qptr; i++) { + long int p = VECTOR(Qparent)[i]; + igraph_vector_t *mapping = VECTOR(Qmapping)[i]; + igraph_vector_t *pmapping = VECTOR(Qmapping)[p]; + long int j, n = igraph_vector_size(mapping); + + if (!pmapping) { + continue; + } + for (j = 0; j < n; j++) { + long int v = (long int) VECTOR(*mapping)[j]; + VECTOR(*mapping)[j] = VECTOR(*pmapping)[v]; + } + } + + /* Because we also put the separator vertices in the queue, it is + not ensured that the found blocks are not subsets of each other. + We check this now. */ + for (i = 1; i < noblocks; i++) { + long int j, ic; + igraph_vector_t *ivec; + if (!VECTOR(Qcheck)[i] || VECTOR(removed)[i]) { + continue; + } + ivec = VECTOR(Qmapping)[i]; + ic = VECTOR(Qcohesion)[i]; + for (j = 1; j < noblocks; j++) { + igraph_vector_t *jvec; + long int jc; + if (j == i || !VECTOR(Qcheck)[j] || VECTOR(removed)[j]) { + continue; + } + jvec = VECTOR(Qmapping)[j]; + jc = VECTOR(Qcohesion)[j]; + if (igraph_i_cb_isin(ivec, jvec) && jc >= ic) { + badblocks++; + VECTOR(removed)[i] = 1; + break; + } + } + } + + noblocks -= badblocks; + + if (blocks) { + IGRAPH_CHECK(igraph_vector_ptr_resize(blocks, noblocks)); + } + if (cohesion) { + IGRAPH_CHECK(igraph_vector_resize(cohesion, noblocks)); + } + if (parent) { + IGRAPH_CHECK(igraph_vector_resize(parent, noblocks)); + } + + for (i = 0; i < Qptr; i++) { + if (VECTOR(removed)[i]) { + IGRAPH_STATUSF(("Candidate %li ignored.\n", 0, i)); + continue; + } else { + IGRAPH_STATUSF(("Candidate %li is a cohesive (sub)block\n", 0, i)); + } + VECTOR(rewritemap)[i] = resptr; + if (cohesion) { + VECTOR(*cohesion)[resptr] = VECTOR(Qcohesion)[i]; + } + if (parent || block_tree) { + long int p = VECTOR(Qparent)[i]; + while (p >= 0 && VECTOR(removed)[p]) { + p = VECTOR(Qparent)[p]; + } + if (p >= 0) { + p = VECTOR(rewritemap)[p]; + } + VECTOR(Qparent)[i] = p; + if (parent) { + VECTOR(*parent)[resptr] = p; + } + } + if (blocks) { + VECTOR(*blocks)[resptr] = VECTOR(Qmapping)[i]; + VECTOR(Qmapping)[i] = 0; + } + resptr++; + } + + /* Plus the original graph */ + if (blocks) { + igraph_vector_t *orig = igraph_Calloc(1, igraph_vector_t); + if (!orig) { + IGRAPH_ERROR("Cannot do cohesive blocking", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, orig); + IGRAPH_CHECK(igraph_vector_init_seq(orig, 0, igraph_vcount(graph) - 1)); + VECTOR(*blocks)[0] = orig; + IGRAPH_FINALLY_CLEAN(1); + } + + if (block_tree) { + igraph_vector_t edges; + long int eptr = 0; + IGRAPH_VECTOR_INIT_FINALLY(&edges, noblocks * 2 - 2); + for (i = 1; i < Qptr; i++) { + if (VECTOR(removed)[i]) { + continue; + } + VECTOR(edges)[eptr++] = VECTOR(Qparent)[i]; + VECTOR(edges)[eptr++] = VECTOR(rewritemap)[i]; + } + + IGRAPH_CHECK(igraph_create(block_tree, &edges, noblocks, + IGRAPH_DIRECTED)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_long_destroy(&rewritemap); + igraph_vector_bool_destroy(&removed); + IGRAPH_FINALLY_CLEAN(2); + + } + + igraph_vector_bool_destroy(&Qcheck); + igraph_vector_long_destroy(&Qcohesion); + igraph_vector_long_destroy(&Qparent); + igraph_i_cohesive_blocks_free2(&Qmapping); + IGRAPH_FINALLY_CLEAN(4); + + igraph_vector_ptr_destroy(&Qmapping); + igraph_vector_ptr_destroy(&Q); + IGRAPH_FINALLY_CLEAN(3); /* + the elements of Q, they were + already destroyed */ + + IGRAPH_STATUS("Cohesive blocking done.\n", 0); + + return 0; +} diff --git a/src/coloring.c b/src/coloring.c new file mode 100644 index 0000000..ccd6612 --- /dev/null +++ b/src/coloring.c @@ -0,0 +1,161 @@ +/* + Heuristic graph coloring algorithms. + Copyright (C) 2017 Szabolcs Horvat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_coloring.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include "igraph_interrupt_internal.h" +#include "igraph_types_internal.h" + + +static int igraph_i_vertex_coloring_greedy_cn(const igraph_t *graph, igraph_vector_int_t *colors) { + long i, vertex, maxdeg; + long vc = igraph_vcount(graph); + igraph_2wheap_t cn; /* indexed heap storing number of already coloured neighbours */ + igraph_vector_int_t neigh_colors; + igraph_adjlist_t adjlist; + + IGRAPH_CHECK(igraph_vector_int_resize(colors, vc)); + igraph_vector_int_fill(colors, 0); + + /* Nothing to do for 0 or 1 vertices. + * Remember that colours are integers starting from 0, + * and the 'colors' vector is already 0-initialized above. + */ + if (vc <= 1) { + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + /* find maximum degree and a corresponding vertex */ + { + igraph_vector_t degree; + + IGRAPH_CHECK(igraph_vector_init(°ree, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, °ree); + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, 0)); + + vertex = igraph_vector_which_max(°ree); + maxdeg = VECTOR(degree)[vertex]; + + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_CHECK(igraph_vector_int_init(&neigh_colors, 0)); + IGRAPH_CHECK(igraph_vector_int_reserve(&neigh_colors, maxdeg)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &neigh_colors); + + IGRAPH_CHECK(igraph_2wheap_init(&cn, vc)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &cn); + for (i = 0; i < vc; ++i) + if (i != vertex) { + igraph_2wheap_push_with_index(&cn, i, 0); /* should not fail since memory was already reserved */ + } + + while (1) { + igraph_vector_int_t *neighbors = igraph_adjlist_get(&adjlist, vertex); + long neigh_count = igraph_vector_int_size(neighbors); + + /* colour current vertex */ + { + igraph_integer_t col; + + IGRAPH_CHECK(igraph_vector_int_resize(&neigh_colors, neigh_count)); + for (i = 0; i < neigh_count; ++i) { + VECTOR(neigh_colors)[i] = VECTOR(*colors)[ VECTOR(*neighbors)[i] ]; + } + igraph_vector_int_sort(&neigh_colors); + + i = 0; + col = 0; + do { + while (i < neigh_count && VECTOR(neigh_colors)[i] == col) { + i++; + } + col++; + } while (i < neigh_count && VECTOR(neigh_colors)[i] == col); + + VECTOR(*colors)[vertex] = col; + } + + /* increment number of coloured neighbours for each neighbour of vertex */ + for (i = 0; i < neigh_count; ++i) { + long idx = VECTOR(*neighbors)[i]; + if (igraph_2wheap_has_elem(&cn, idx)) { + igraph_2wheap_modify(&cn, idx, igraph_2wheap_get(&cn, idx) + 1); + } + } + + /* stop if no more vertices left to colour */ + if (igraph_2wheap_empty(&cn)) { + break; + } + + igraph_2wheap_delete_max_index(&cn, &vertex); + + IGRAPH_ALLOW_INTERRUPTION(); + } + + /* subtract 1 from each colour value, so that colours start at 0 */ + igraph_vector_int_add_constant(colors, -1); + + /* free data structures */ + igraph_vector_int_destroy(&neigh_colors); + igraph_adjlist_destroy(&adjlist); + igraph_2wheap_destroy(&cn); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_vertex_coloring_greedy + * \brief Computes a vertex coloring using a greedy algorithm. + * + * + * This function assigns a "color"---represented as a non-negative integer---to + * each vertex of the graph in such a way that neighboring vertices never have + * the same color. The obtained coloring is not necessarily minimal. + * + * + * Vertices are colored one by one, choosing the smallest color index that + * differs from that of already colored neighbors. + * Colors are represented with non-negative integers 0, 1, 2, ... + * + * \param graph The input graph. + * \param colors Pointer to an initialized integer vector. The vertex colors will be stored here. + * \param heuristic The vertex ordering heuristic to use during greedy coloring. See \ref igraph_coloring_greedy_t + * + * \return Error code. + * + * \example examples/simple/igraph_coloring.c + */ +int igraph_vertex_coloring_greedy(const igraph_t *graph, igraph_vector_int_t *colors, igraph_coloring_greedy_t heuristic) { + switch (heuristic) { + case IGRAPH_COLORING_GREEDY_COLORED_NEIGHBORS: + return igraph_i_vertex_coloring_greedy_cn(graph, colors); + default: + return IGRAPH_EINVAL; + } +} diff --git a/src/community.c b/src/community.c new file mode 100644 index 0000000..b40af71 --- /dev/null +++ b/src/community.c @@ -0,0 +1,3845 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_community.h" +#include "igraph_constructors.h" +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_arpack.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_interrupt_internal.h" +#include "igraph_components.h" +#include "igraph_dqueue.h" +#include "igraph_progress.h" +#include "igraph_stack.h" +#include "igraph_spmatrix.h" +#include "igraph_statusbar.h" +#include "igraph_types_internal.h" +#include "igraph_conversion.h" +#include "igraph_centrality.h" +#include "igraph_structural.h" +#include "config.h" + +#include +#include + +#ifdef USING_R + #include +#endif + +static int igraph_i_rewrite_membership_vector(igraph_vector_t *membership) { + long int no = (long int) igraph_vector_max(membership) + 1; + igraph_vector_t idx; + long int realno = 0; + long int i; + long int len = igraph_vector_size(membership); + + IGRAPH_VECTOR_INIT_FINALLY(&idx, no); + for (i = 0; i < len; i++) { + long int t = (long int) VECTOR(*membership)[i]; + if (VECTOR(idx)[t]) { + VECTOR(*membership)[i] = VECTOR(idx)[t] - 1; + } else { + VECTOR(idx)[t] = ++realno; + VECTOR(*membership)[i] = VECTOR(idx)[t] - 1; + } + } + igraph_vector_destroy(&idx); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_community_eb_get_merges2(const igraph_t *graph, + const igraph_vector_t *edges, + const igraph_vector_t *weights, + igraph_matrix_t *res, + igraph_vector_t *bridges, + igraph_vector_t *modularity, + igraph_vector_t *membership) { + + igraph_vector_t mymembership; + long int no_of_nodes = igraph_vcount(graph); + long int i; + igraph_real_t maxmod = -1; + long int midx = 0; + igraph_integer_t no_comps; + + IGRAPH_VECTOR_INIT_FINALLY(&mymembership, no_of_nodes); + + if (membership) { + IGRAPH_CHECK(igraph_vector_resize(membership, no_of_nodes)); + } + + if (modularity || res || bridges) { + IGRAPH_CHECK(igraph_clusters(graph, 0, 0, &no_comps, + IGRAPH_WEAK)); + + if (modularity) { + IGRAPH_CHECK(igraph_vector_resize(modularity, + no_of_nodes - no_comps + 1)); + } + if (res) { + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes - no_comps, + 2)); + } + if (bridges) { + IGRAPH_CHECK(igraph_vector_resize(bridges, + no_of_nodes - no_comps)); + } + } + + for (i = 0; i < no_of_nodes; i++) { + VECTOR(mymembership)[i] = i; + } + if (membership) { + igraph_vector_update(membership, &mymembership); + } + + IGRAPH_CHECK(igraph_modularity(graph, &mymembership, &maxmod, weights)); + if (modularity) { + VECTOR(*modularity)[0] = maxmod; + } + + for (i = igraph_vector_size(edges) - 1; i >= 0; i--) { + long int edge = (long int) VECTOR(*edges)[i]; + long int from = IGRAPH_FROM(graph, edge); + long int to = IGRAPH_TO(graph, edge); + long int c1 = (long int) VECTOR(mymembership)[from]; + long int c2 = (long int) VECTOR(mymembership)[to]; + igraph_real_t actmod; + long int j; + if (c1 != c2) { /* this is a merge */ + if (res) { + MATRIX(*res, midx, 0) = c1; + MATRIX(*res, midx, 1) = c2; + } + if (bridges) { + VECTOR(*bridges)[midx] = i + 1; + } + + /* The new cluster has id no_of_nodes+midx+1 */ + for (j = 0; j < no_of_nodes; j++) { + if (VECTOR(mymembership)[j] == c1 || + VECTOR(mymembership)[j] == c2) { + VECTOR(mymembership)[j] = no_of_nodes + midx; + } + } + + IGRAPH_CHECK(igraph_modularity(graph, &mymembership, &actmod, weights)); + if (modularity) { + VECTOR(*modularity)[midx + 1] = actmod; + if (actmod > maxmod) { + maxmod = actmod; + if (membership) { + igraph_vector_update(membership, &mymembership); + } + } + } + + midx++; + } + } + + if (membership) { + IGRAPH_CHECK(igraph_i_rewrite_membership_vector(membership)); + } + + igraph_vector_destroy(&mymembership); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + + +/** + * \function igraph_community_eb_get_merges + * \brief Calculating the merges, ie. the dendrogram for an edge betweenness community structure + * + * + * This function is handy if you have a sequence of edge which are + * gradually removed from the network and you would like to know how + * the network falls apart into separate components. The edge sequence + * may come from the \ref igraph_community_edge_betweenness() + * function, but this is not necessary. Note that \ref + * igraph_community_edge_betweenness can also calculate the + * dendrogram, via its \p merges argument. + * + * \param graph The input graph. + * \param edges Vector containing the edges to be removed from the + * network, all edges are expected to appear exactly once in the + * vector. + * \param weights An optional vector containing edge weights. If null, + * the unweighted modularity scores will be calculated. If not null, + * the weighted modularity scores will be calculated. Ignored if both + * \p modularity and \p membership are nulls. + * \param res Pointer to an initialized matrix, if not NULL then the + * dendrogram will be stored here, in the same form as for the \ref + * igraph_community_walktrap() function: the matrix has two columns + * and each line is a merge given by the ids of the merged + * components. The component ids are number from zero and + * component ids smaller than the number of vertices in the graph + * belong to individual vertices. The non-trivial components + * containing at least two vertices are numbered from \c n, \c n is + * the number of vertices in the graph. So if the first line + * contains \c a and \c b that means that components \c a and \c b + * are merged into component \c n, the second line creates + * component \c n+1, etc. The matrix will be resized as needed. + * \param bridges Pointer to an initialized vector or NULL. If not + * null then the index of the edge removals which split the network + * will be stored here. The vector will be resized as needed. + * \param modularity If not a null pointer, then the modularity values + * for the different divisions, corresponding to the merges matrix, + * will be stored here. + * \param membership If not a null pointer, then the membership vector + * for the best division (in terms of modularity) will be stored + * here. + * \return Error code. + * + * \sa \ref igraph_community_edge_betweenness(). + * + * Time complexity: O(|E|+|V|log|V|), |V| is the number of vertices, + * |E| is the number of edges. + */ + +int igraph_community_eb_get_merges(const igraph_t *graph, + const igraph_vector_t *edges, + const igraph_vector_t *weights, + igraph_matrix_t *res, + igraph_vector_t *bridges, + igraph_vector_t *modularity, + igraph_vector_t *membership) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t ptr; + long int i, midx = 0; + igraph_integer_t no_comps; + + if (membership || modularity) { + return igraph_i_community_eb_get_merges2(graph, edges, weights, res, + bridges, modularity, + membership); + } + + IGRAPH_CHECK(igraph_clusters(graph, 0, 0, &no_comps, IGRAPH_WEAK)); + + IGRAPH_VECTOR_INIT_FINALLY(&ptr, no_of_nodes * 2 - 1); + if (res) { + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes - no_comps, 2)); + } + if (bridges) { + IGRAPH_CHECK(igraph_vector_resize(bridges, no_of_nodes - no_comps)); + } + + for (i = igraph_vector_size(edges) - 1; i >= 0; i--) { + igraph_integer_t edge = (igraph_integer_t) VECTOR(*edges)[i]; + igraph_integer_t from, to, c1, c2, idx; + igraph_edge(graph, edge, &from, &to); + idx = from + 1; + while (VECTOR(ptr)[idx - 1] != 0) { + idx = (igraph_integer_t) VECTOR(ptr)[idx - 1]; + } + c1 = idx - 1; + idx = to + 1; + while (VECTOR(ptr)[idx - 1] != 0) { + idx = (igraph_integer_t) VECTOR(ptr)[idx - 1]; + } + c2 = idx - 1; + if (c1 != c2) { /* this is a merge */ + if (res) { + MATRIX(*res, midx, 0) = c1; + MATRIX(*res, midx, 1) = c2; + } + if (bridges) { + VECTOR(*bridges)[midx] = i + 1; + } + + VECTOR(ptr)[c1] = no_of_nodes + midx + 1; + VECTOR(ptr)[c2] = no_of_nodes + midx + 1; + VECTOR(ptr)[from] = no_of_nodes + midx + 1; + VECTOR(ptr)[to] = no_of_nodes + midx + 1; + + midx++; + } + } + + igraph_vector_destroy(&ptr); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/* Find the smallest active element in the vector */ +static long int igraph_i_vector_which_max_not_null(const igraph_vector_t *v, + const char *passive) { + long int which, i = 0, size = igraph_vector_size(v); + igraph_real_t max; + while (passive[i]) { + i++; + } + which = i; + max = VECTOR(*v)[which]; + for (i++; i < size; i++) { + igraph_real_t elem = VECTOR(*v)[i]; + if (!passive[i] && elem > max) { + max = elem; + which = i; + } + } + + return which; +} + +/** + * \function igraph_community_edge_betweenness + * \brief Community finding based on edge betweenness + * + * Community structure detection based on the betweenness of the edges + * in the network. The algorithm was invented by M. Girvan and + * M. Newman, see: M. Girvan and M. E. J. Newman: Community structure in + * social and biological networks, Proc. Nat. Acad. Sci. USA 99, 7821-7826 + * (2002). + * + * + * The idea is that the betweenness of the edges connecting two + * communities is typically high, as many of the shortest paths + * between nodes in separate communities go through them. So we + * gradually remove the edge with highest betweenness from the + * network, and recalculate edge betweenness after every removal. + * This way sooner or later the network falls off to two components, + * then after a while one of these components falls off to two smaller + * components, etc. until all edges are removed. This is a divisive + * hierarchical approach, the result is a dendrogram. + * \param graph The input graph. + * \param result Pointer to an initialized vector, the result will be + * stored here, the ids of the removed edges in the order of their + * removal. It will be resized as needed. It may be NULL if + * the edge IDs are not needed by the caller. + * \param edge_betweenness Pointer to an initialized vector or + * NULL. In the former case the edge betweenness of the removed + * edge is stored here. The vector will be resized as needed. + * \param merges Pointer to an initialized matrix or NULL. If not NULL + * then merges performed by the algorithm are stored here. Even if + * this is a divisive algorithm, we can replay it backwards and + * note which two clusters were merged. Clusters are numbered from + * zero, see the \p merges argument of \ref + * igraph_community_walktrap() for details. The matrix will be + * resized as needed. + * \param bridges Pointer to an initialized vector of NULL. If not + * NULL then all edge removals which separated the network into + * more components are marked here. + * \param modularity If not a null pointer, then the modularity values + * of the different divisions are stored here, in the order + * corresponding to the merge matrix. The modularity values will + * take weights into account if \p weights is not null. + * \param membership If not a null pointer, then the membership vector, + * corresponding to the highest modularity value, is stored here. + * \param directed Logical constant, whether to calculate directed + * betweenness (ie. directed paths) for directed graphs. It is + * ignored for undirected graphs. + * \param weights An optional vector containing edge weights. If null, + * the unweighted edge betweenness scores will be calculated and + * used. If not null, the weighted edge betweenness scores will be + * calculated and used. + * \return Error code. + * + * \sa \ref igraph_community_eb_get_merges(), \ref + * igraph_community_spinglass(), \ref igraph_community_walktrap(). + * + * Time complexity: O(|V||E|^2), as the betweenness calculation requires + * O(|V||E|) and we do it |E|-1 times. + * + * \example examples/simple/igraph_community_edge_betweenness.c + */ + +int igraph_community_edge_betweenness(const igraph_t *graph, + igraph_vector_t *result, + igraph_vector_t *edge_betweenness, + igraph_matrix_t *merges, + igraph_vector_t *bridges, + igraph_vector_t *modularity, + igraph_vector_t *membership, + igraph_bool_t directed, + const igraph_vector_t *weights) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + double *distance, *tmpscore; + unsigned long long int *nrgeo; + long int source, i, e; + + igraph_inclist_t elist_out, elist_in, fathers; + igraph_inclist_t *elist_out_p, *elist_in_p; + igraph_vector_int_t *neip; + long int neino; + igraph_vector_t eb; + long int maxedge, pos; + igraph_integer_t from, to; + igraph_bool_t result_owned = 0; + igraph_stack_t stack = IGRAPH_STACK_NULL; + igraph_real_t steps, steps_done; + + char *passive; + + /* Needed only for the unweighted case */ + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + + /* Needed only for the weighted case */ + igraph_2wheap_t heap; + + if (result == 0) { + result = igraph_Calloc(1, igraph_vector_t); + if (result == 0) { + IGRAPH_ERROR("edge betweenness community structure failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, result); + IGRAPH_VECTOR_INIT_FINALLY(result, 0); + result_owned = 1; + } + + directed = directed && igraph_is_directed(graph); + if (directed) { + IGRAPH_CHECK(igraph_inclist_init(graph, &elist_out, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_inclist_destroy, &elist_out); + IGRAPH_CHECK(igraph_inclist_init(graph, &elist_in, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_inclist_destroy, &elist_in); + elist_out_p = &elist_out; + elist_in_p = &elist_in; + } else { + IGRAPH_CHECK(igraph_inclist_init(graph, &elist_out, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &elist_out); + elist_out_p = elist_in_p = &elist_out; + } + + distance = igraph_Calloc(no_of_nodes, double); + if (distance == 0) { + IGRAPH_ERROR("edge betweenness community structure failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, distance); + nrgeo = igraph_Calloc(no_of_nodes, unsigned long long int); + if (nrgeo == 0) { + IGRAPH_ERROR("edge betweenness community structure failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, nrgeo); + tmpscore = igraph_Calloc(no_of_nodes, double); + if (tmpscore == 0) { + IGRAPH_ERROR("edge betweenness community structure failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmpscore); + + if (weights == 0) { + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + } else { + if (igraph_vector_min(weights) <= 0) { + IGRAPH_ERROR("weights must be strictly positive", IGRAPH_EINVAL); + } + + if (membership != 0) { + IGRAPH_WARNING("Membership vector will be selected based on the lowest "\ + "modularity score."); + } + + if (modularity != 0 || membership != 0) { + IGRAPH_WARNING("Modularity calculation with weighted edge betweenness "\ + "community detection might not make sense -- modularity treats edge "\ + "weights as similarities while edge betwenness treats them as "\ + "distances"); + } + + IGRAPH_CHECK(igraph_2wheap_init(&heap, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &heap); + IGRAPH_CHECK(igraph_inclist_init_empty(&fathers, + (igraph_integer_t) no_of_nodes)); + IGRAPH_FINALLY(igraph_inclist_destroy, &fathers); + } + + IGRAPH_CHECK(igraph_stack_init(&stack, no_of_nodes)); + IGRAPH_FINALLY(igraph_stack_destroy, &stack); + + IGRAPH_CHECK(igraph_vector_resize(result, no_of_edges)); + if (edge_betweenness) { + IGRAPH_CHECK(igraph_vector_resize(edge_betweenness, no_of_edges)); + if (no_of_edges > 0) { + VECTOR(*edge_betweenness)[no_of_edges - 1] = 0; + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&eb, no_of_edges); + + passive = igraph_Calloc(no_of_edges, char); + if (!passive) { + IGRAPH_ERROR("edge betweenness community structure failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, passive); + + /* Estimate the number of steps to be taken. + * It is assumed that one iteration is O(|E||V|), but |V| is constant + * anyway, so we will have approximately |E|^2 / 2 steps, and one + * iteration of the outer loop advances the step counter by the number + * of remaining edges at that iteration. + */ + steps = no_of_edges / 2.0 * (no_of_edges + 1); + steps_done = 0; + + for (e = 0; e < no_of_edges; steps_done += no_of_edges - e, e++) { + IGRAPH_PROGRESS("Edge betweenness community detection: ", + 100.0 * steps_done / steps, NULL); + + igraph_vector_null(&eb); + + if (weights == 0) { + /* Unweighted variant follows */ + + /* The following for loop is copied almost intact from + * igraph_edge_betweenness_estimate */ + for (source = 0; source < no_of_nodes; source++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + memset(distance, 0, (size_t) no_of_nodes * sizeof(double)); + memset(nrgeo, 0, (size_t) no_of_nodes * sizeof(unsigned long long int)); + memset(tmpscore, 0, (size_t) no_of_nodes * sizeof(double)); + igraph_stack_clear(&stack); /* it should be empty anyway... */ + + IGRAPH_CHECK(igraph_dqueue_push(&q, source)); + + nrgeo[source] = 1; + distance[source] = 0; + + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + + neip = igraph_inclist_get(elist_out_p, actnode); + neino = igraph_vector_int_size(neip); + for (i = 0; i < neino; i++) { + igraph_integer_t edge = (igraph_integer_t) VECTOR(*neip)[i], from, to; + long int neighbor; + igraph_edge(graph, edge, &from, &to); + neighbor = actnode != from ? from : to; + if (nrgeo[neighbor] != 0) { + /* we've already seen this node, another shortest path? */ + if (distance[neighbor] == distance[actnode] + 1) { + nrgeo[neighbor] += nrgeo[actnode]; + } + } else { + /* we haven't seen this node yet */ + nrgeo[neighbor] += nrgeo[actnode]; + distance[neighbor] = distance[actnode] + 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + IGRAPH_CHECK(igraph_stack_push(&stack, neighbor)); + } + } + } /* while !igraph_dqueue_empty */ + + /* Ok, we've the distance of each node and also the number of + shortest paths to them. Now we do an inverse search, starting + with the farthest nodes. */ + while (!igraph_stack_empty(&stack)) { + long int actnode = (long int) igraph_stack_pop(&stack); + if (distance[actnode] < 1) { + continue; /* skip source node */ + } + + /* set the temporary score of the friends */ + neip = igraph_inclist_get(elist_in_p, actnode); + neino = igraph_vector_int_size(neip); + for (i = 0; i < neino; i++) { + long int edge = (long int) VECTOR(*neip)[i]; + long int neighbor = IGRAPH_OTHER(graph, edge, actnode); + if (distance[neighbor] == distance[actnode] - 1 && + nrgeo[neighbor] != 0) { + tmpscore[neighbor] += + (tmpscore[actnode] + 1) * nrgeo[neighbor] / nrgeo[actnode]; + VECTOR(eb)[edge] += + (tmpscore[actnode] + 1) * nrgeo[neighbor] / nrgeo[actnode]; + } + } + } + /* Ok, we've the scores for this source */ + } /* for source <= no_of_nodes */ + } else { + /* Weighted variant follows */ + + /* The following for loop is copied almost intact from + * igraph_i_edge_betweenness_estimate_weighted */ + for (source = 0; source < no_of_nodes; source++) { + /* This will contain the edge betweenness in the current step */ + IGRAPH_ALLOW_INTERRUPTION(); + + memset(distance, 0, (size_t) no_of_nodes * sizeof(double)); + memset(nrgeo, 0, (size_t) no_of_nodes * sizeof(unsigned long long int)); + memset(tmpscore, 0, (size_t) no_of_nodes * sizeof(double)); + + igraph_2wheap_push_with_index(&heap, source, 0); + distance[source] = 1.0; + nrgeo[source] = 1; + + while (!igraph_2wheap_empty(&heap)) { + long int minnei = igraph_2wheap_max_index(&heap); + igraph_real_t mindist = -igraph_2wheap_delete_max(&heap); + + igraph_stack_push(&stack, minnei); + + neip = igraph_inclist_get(elist_out_p, minnei); + neino = igraph_vector_int_size(neip); + + for (i = 0; i < neino; i++) { + long int edge = VECTOR(*neip)[i]; + long int to = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = distance[to]; + igraph_vector_int_t *v; + + if (curdist == 0) { + /* This is the first finite distance to 'to' */ + v = igraph_inclist_get(&fathers, to); + igraph_vector_int_resize(v, 1); + VECTOR(*v)[0] = edge; + nrgeo[to] = nrgeo[minnei]; + distance[to] = altdist + 1.0; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&heap, to, -altdist)); + } else if (altdist < curdist - 1) { + /* This is a shorter path */ + v = igraph_inclist_get(&fathers, to); + igraph_vector_int_resize(v, 1); + VECTOR(*v)[0] = edge; + nrgeo[to] = nrgeo[minnei]; + distance[to] = altdist + 1.0; + IGRAPH_CHECK(igraph_2wheap_modify(&heap, to, -altdist)); + } else if (altdist == curdist - 1) { + /* Another path with the same length */ + v = igraph_inclist_get(&fathers, to); + igraph_vector_int_push_back(v, edge); + nrgeo[to] += nrgeo[minnei]; + } + } + } /* igraph_2wheap_empty(&Q) */ + + while (!igraph_stack_empty(&stack)) { + long int w = (long int) igraph_stack_pop(&stack); + igraph_vector_int_t *fatv = igraph_inclist_get(&fathers, w); + long int fatv_len = igraph_vector_int_size(fatv); + + for (i = 0; i < fatv_len; i++) { + long int fedge = (long int) VECTOR(*fatv)[i]; + long int neighbor = IGRAPH_OTHER(graph, fedge, w); + tmpscore[neighbor] += (tmpscore[w] + 1) * nrgeo[neighbor] / nrgeo[w]; + VECTOR(eb)[fedge] += (tmpscore[w] + 1) * nrgeo[neighbor] / nrgeo[w]; + } + + tmpscore[w] = 0; + distance[w] = 0; + nrgeo[w] = 0; + igraph_vector_int_clear(fatv); + } + } /* source < no_of_nodes */ + } + + /* Now look for the smallest edge betweenness */ + /* and eliminate that edge from the network */ + maxedge = igraph_i_vector_which_max_not_null(&eb, passive); + VECTOR(*result)[e] = maxedge; + if (edge_betweenness) { + VECTOR(*edge_betweenness)[e] = VECTOR(eb)[maxedge]; + if (!directed) { + VECTOR(*edge_betweenness)[e] /= 2.0; + } + } + passive[maxedge] = 1; + igraph_edge(graph, (igraph_integer_t) maxedge, &from, &to); + + neip = igraph_inclist_get(elist_in_p, to); + neino = igraph_vector_int_size(neip); + igraph_vector_int_search(neip, 0, maxedge, &pos); + VECTOR(*neip)[pos] = VECTOR(*neip)[neino - 1]; + igraph_vector_int_pop_back(neip); + + neip = igraph_inclist_get(elist_out_p, from); + neino = igraph_vector_int_size(neip); + igraph_vector_int_search(neip, 0, maxedge, &pos); + VECTOR(*neip)[pos] = VECTOR(*neip)[neino - 1]; + igraph_vector_int_pop_back(neip); + } + + IGRAPH_PROGRESS("Edge betweenness community detection: ", 100.0, NULL); + + igraph_free(passive); + igraph_vector_destroy(&eb); + igraph_stack_destroy(&stack); + IGRAPH_FINALLY_CLEAN(3); + + if (weights == 0) { + igraph_dqueue_destroy(&q); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_2wheap_destroy(&heap); + igraph_inclist_destroy(&fathers); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_free(tmpscore); + igraph_free(nrgeo); + igraph_free(distance); + IGRAPH_FINALLY_CLEAN(3); + + if (directed) { + igraph_inclist_destroy(&elist_out); + igraph_inclist_destroy(&elist_in); + IGRAPH_FINALLY_CLEAN(2); + } else { + igraph_inclist_destroy(&elist_out); + IGRAPH_FINALLY_CLEAN(1); + } + + if (merges || bridges || modularity || membership) { + IGRAPH_CHECK(igraph_community_eb_get_merges(graph, result, weights, merges, + bridges, modularity, + membership)); + } + + if (result_owned) { + igraph_vector_destroy(result); + igraph_Free(result); + IGRAPH_FINALLY_CLEAN(2); + } + + return 0; +} + + +/** + * \function igraph_community_to_membership + * \brief Create membership vector from community structure dendrogram + * + * This function creates a membership vector from a community + * structure dendrogram. A membership vector contains for each vertex + * the id of its graph component, the graph components are numbered + * from zero, see the same argument of \ref igraph_clusters() for an + * example of a membership vector. + * + * + * Many community detection algorithms return with a \em merges + * matrix, \ref igraph_community_walktrap() and \ref + * igraph_community_edge_betweenness() are two examples. The matrix + * contains the merge operations performed while mapping the + * hierarchical structure of a network. If the matrix has \c n-1 rows, + * where \c n is the number of vertices in the graph, then it contains + * the hierarchical structure of the whole network and it is called a + * dendrogram. + * + * + * This function performs \p steps merge operations as prescribed by + * the \p merges matrix and returns the current state of the network. + * + * + * If \p merges is not a complete dendrogram, it is possible to + * take \p steps steps if \p steps is not bigger than the number + * lines in \p merges. + * \param merges The two-column matrix containing the merge + * operations. See \ref igraph_community_walktrap() for the + * detailed syntax. + * \param nodes The number of leaf nodes in the dendrogram + * \param steps Integer constant, the number of steps to take. + * \param membership Pointer to an initialized vector, the membership + * results will be stored here, if not NULL. The vector will be + * resized as needed. + * \param csize Pointer to an initialized vector, or NULL. If not NULL + * then the sizes of the components will be stored here, the vector + * will be resized as needed. + * + * \sa \ref igraph_community_walktrap(), \ref + * igraph_community_edge_betweenness(), \ref + * igraph_community_fastgreedy() for community structure detection + * algorithms. + * + * Time complexity: O(|V|), the number of vertices in the graph. + */ + +int igraph_community_to_membership(const igraph_matrix_t *merges, + igraph_integer_t nodes, + igraph_integer_t steps, + igraph_vector_t *membership, + igraph_vector_t *csize) { + + long int no_of_nodes = nodes; + long int components = no_of_nodes - steps; + long int i, found = 0; + igraph_vector_t tmp; + + if (steps > igraph_matrix_nrow(merges)) { + IGRAPH_ERROR("`steps' to big or `merges' matrix too short", IGRAPH_EINVAL); + } + + if (membership) { + IGRAPH_CHECK(igraph_vector_resize(membership, no_of_nodes)); + igraph_vector_null(membership); + } + if (csize) { + IGRAPH_CHECK(igraph_vector_resize(csize, components)); + igraph_vector_null(csize); + } + + IGRAPH_VECTOR_INIT_FINALLY(&tmp, steps); + + for (i = steps - 1; i >= 0; i--) { + long int c1 = (long int) MATRIX(*merges, i, 0); + long int c2 = (long int) MATRIX(*merges, i, 1); + + /* new component? */ + if (VECTOR(tmp)[i] == 0) { + found++; + VECTOR(tmp)[i] = found; + } + + if (c1 < no_of_nodes) { + long int cid = (long int) VECTOR(tmp)[i] - 1; + if (membership) { + VECTOR(*membership)[c1] = cid + 1; + } + if (csize) { + VECTOR(*csize)[cid] += 1; + } + } else { + VECTOR(tmp)[c1 - no_of_nodes] = VECTOR(tmp)[i]; + } + + if (c2 < no_of_nodes) { + long int cid = (long int) VECTOR(tmp)[i] - 1; + if (membership) { + VECTOR(*membership)[c2] = cid + 1; + } + if (csize) { + VECTOR(*csize)[cid] += 1; + } + } else { + VECTOR(tmp)[c2 - no_of_nodes] = VECTOR(tmp)[i]; + } + + } + + if (membership || csize) { + for (i = 0; i < no_of_nodes; i++) { + long int tmp = (long int) VECTOR(*membership)[i]; + if (tmp != 0) { + if (membership) { + VECTOR(*membership)[i] = tmp - 1; + } + } else { + if (csize) { + VECTOR(*csize)[found] += 1; + } + if (membership) { + VECTOR(*membership)[i] = found; + } + found++; + } + } + } + + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_modularity + * \brief Calculate the modularity of a graph with respect to some vertex types + * + * The modularity of a graph with respect to some division (or vertex + * types) measures how good the division is, or how separated are the + * different vertex types from each other. It is defined as + * Q=1/(2m) * sum((Aij - ki*kj / (2m)) delta(ci,cj), i, j), here `m' is the + * number of edges, `Aij' is the element of the `A' adjacency matrix + * in row `i' and column `j', `ki' is the degree of `i', `kj' is the + * degree of `j', `ci' is the type (or component) of `i', `cj' that of + * `j', the sum goes over all `i' and `j' pairs of vertices, and + * `delta(x,y)' is one if x=y and zero otherwise. + * + * + * Modularity on weighted graphs is also meaningful. When taking edge + * weights into account, `Aij' becomes the weight of the corresponding + * edge (or 0 if there is no edge), `ki' is the total weight of edges + * incident on vertex `i', `kj' is the total weight of edges incident + * on vertex `j' and `m' is the total weight of all edges. + * + * + * See also Clauset, A.; Newman, M. E. J.; Moore, C. Finding + * community structure in very large networks, Physical Review E, + * 2004, 70, 066111. + * \param graph The input graph. It must be undirected; directed graphs are + * not supported yet. + * \param membership Numeric vector which gives the type of each + * vertex, ie. the component to which it belongs. + * It does not have to be consecutive, i.e. empty communities are + * allowed. + * \param modularity Pointer to a real number, the result will be + * stored here. + * \param weights Weight vector or NULL if no weights are specified. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + */ + +int igraph_modularity(const igraph_t *graph, + const igraph_vector_t *membership, + igraph_real_t *modularity, + const igraph_vector_t *weights) { + + igraph_vector_t e, a; + long int types = (long int) igraph_vector_max(membership) + 1; + long int no_of_edges = igraph_ecount(graph); + long int i; + igraph_integer_t from, to; + igraph_real_t m; + long int c1, c2; + + if (igraph_is_directed(graph)) { +#ifndef USING_R + IGRAPH_ERROR("modularity is implemented for undirected graphs", IGRAPH_EINVAL); +#else + REprintf("Modularity is implemented for undirected graphs only.\n"); +#endif + } + + if (igraph_vector_size(membership) < igraph_vcount(graph)) { + IGRAPH_ERROR("cannot calculate modularity, membership vector too short", + IGRAPH_EINVAL); + } + if (igraph_vector_min(membership) < 0) { + IGRAPH_ERROR("Invalid membership vector", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&e, types); + IGRAPH_VECTOR_INIT_FINALLY(&a, types); + + if (weights) { + if (igraph_vector_size(weights) < no_of_edges) + IGRAPH_ERROR("cannot calculate modularity, weight vector too short", + IGRAPH_EINVAL); + m = igraph_vector_sum(weights); + for (i = 0; i < no_of_edges; i++) { + igraph_real_t w = VECTOR(*weights)[i]; + if (w < 0) { + IGRAPH_ERROR("negative weight in weight vector", IGRAPH_EINVAL); + } + igraph_edge(graph, (igraph_integer_t) i, &from, &to); + c1 = (long int) VECTOR(*membership)[from]; + c2 = (long int) VECTOR(*membership)[to]; + if (c1 == c2) { + VECTOR(e)[c1] += 2 * w; + } + VECTOR(a)[c1] += w; + VECTOR(a)[c2] += w; + } + } else { + m = no_of_edges; + for (i = 0; i < no_of_edges; i++) { + igraph_edge(graph, (igraph_integer_t) i, &from, &to); + c1 = (long int) VECTOR(*membership)[from]; + c2 = (long int) VECTOR(*membership)[to]; + if (c1 == c2) { + VECTOR(e)[c1] += 2; + } + VECTOR(a)[c1] += 1; + VECTOR(a)[c2] += 1; + } + } + + *modularity = 0.0; + if (m > 0) { + for (i = 0; i < types; i++) { + igraph_real_t tmp = VECTOR(a)[i] / 2 / m; + *modularity += VECTOR(e)[i] / 2 / m; + *modularity -= tmp * tmp; + } + } + + igraph_vector_destroy(&e); + igraph_vector_destroy(&a); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_modularity_matrix + * \brief Calculate the modularity matrix + * + * This function returns the modularity matrix defined as + * `B_ij = A_ij - k_i k_j * / 2 m` + * where `A_ij` denotes the adjacency matrix, `k_i` is the degree of node `i` + * and `m` is the total weight in the graph. Note that self-loops are multiplied + * by 2 in this implementation. If weights are specified, the weighted + * counterparts are used. + * + * \param graph The input graph + * \param modmat Pointer to an initialized matrix in which the modularity + * matrix is stored. + * \param weights Edge weights, pointer to a vector. If this is a null pointer + * then every edge is assumed to have a weight of 1. + */ + +int igraph_modularity_matrix(const igraph_t *graph, + igraph_matrix_t *modmat, + const igraph_vector_t *weights) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_real_t sw = weights ? igraph_vector_sum(weights) : no_of_edges; + igraph_vector_t deg; + long int i, j; + + if (weights && igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(°, no_of_nodes); + if (!weights) { + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS)); + } else { + IGRAPH_CHECK(igraph_strength(graph, °, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS, weights)); + } + IGRAPH_CHECK(igraph_get_adjacency(graph, modmat, IGRAPH_GET_ADJACENCY_BOTH, + /*eids=*/ 0)); + + for (i = 0; i < no_of_nodes; i++) { + MATRIX(*modmat, i, i) *= 2; + } + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < no_of_nodes; j++) { + MATRIX(*modmat, i, j) -= VECTOR(deg)[i] * VECTOR(deg)[j] / 2.0 / sw; + } + } + + igraph_vector_destroy(°); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_reindex_membership + * \brief Makes the IDs in a membership vector continuous + * + * This function reindexes component IDs in a membership vector + * in a way that the new IDs start from zero and go up to C-1, + * where C is the number of unique component IDs in the original + * vector. The supplied membership is expected to fall in the + * range 0, ..., n - 1. + * + * \param membership Numeric vector which gives the type of each + * vertex, ie. the component to which it belongs. + * The vector will be altered in-place. + * \param new_to_old Pointer to a vector which will contain the + * old component ID for each new one, or NULL, + * in which case it is not returned. The vector + * will be resized as needed. + * \param nb_clusters Pointer to an integer for the number of + * distinct clusters. If not NULL, this will be + * updated to reflect the number of distinct + * clusters found in membership. + * + * Time complexity: should be O(n) for n elements. + */ +int igraph_reindex_membership(igraph_vector_t *membership, + igraph_vector_t *new_to_old, + igraph_integer_t *nb_clusters) { + + long int i, n = igraph_vector_size(membership); + igraph_vector_t new_cluster; + igraph_integer_t i_nb_clusters; + + /* We allow original cluster indices in the range 0, ..., n - 1 */ + IGRAPH_CHECK(igraph_vector_init(&new_cluster, n)); + IGRAPH_FINALLY(igraph_vector_destroy, &new_cluster); + + if (new_to_old) { + igraph_vector_clear(new_to_old); + } + + /* Clean clusters. We will store the new cluster + 1 so that membership == 0 + * indicates that no cluster was assigned yet. */ + i_nb_clusters = 1; + for (i = 0; i < n; i++) { + long int c = (long int)VECTOR(*membership)[i]; + + if (c >= n) { + IGRAPH_ERROR("Cluster out of range", IGRAPH_EINVAL); + } + + if (VECTOR(new_cluster)[c] == 0) { + VECTOR(new_cluster)[c] = (igraph_real_t)i_nb_clusters; + i_nb_clusters += 1; + if (new_to_old) { + IGRAPH_CHECK(igraph_vector_push_back(new_to_old, c)); + } + } + } + + /* Assign new membership */ + for (i = 0; i < n; i++) { + long int c = (long int)VECTOR(*membership)[i]; + VECTOR(*membership)[i] = VECTOR(new_cluster)[c] - 1; + } + if (nb_clusters) { + /* We used the cluster + 1, so correct */ + *nb_clusters = i_nb_clusters - 1; + } + + igraph_vector_destroy(&new_cluster); + + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/********************************************************************/ + +/** + * \section about_leading_eigenvector_methods + * + * + * The function documented in these section implements the + * leading eigenvector method developed by Mark Newman and + * published in MEJ Newman: Finding community structure using the + * eigenvectors of matrices, Phys Rev E 74:036104 (2006). + * + * + * The heart of the method is the definition of the modularity matrix, + * B, which is B=A-P, A being the adjacency matrix of the (undirected) + * network, and P contains the probability that certain edges are + * present according to the configuration model In + * other words, a Pij element of P is the probability that there is an + * edge between vertices i and j in a random network in which the + * degrees of all vertices are the same as in the input graph. + * + * + * The leading eigenvector method works by calculating the eigenvector + * of the modularity matrix for the largest positive eigenvalue and + * then separating vertices into two community based on the sign of + * the corresponding element in the eigenvector. If all elements in + * the eigenvector are of the same sign that means that the network + * has no underlying community structure. + * Check Newman's paper to understand why this is a good method for + * detecting community structure. + * + * + * The leading eigenvector community structure detection method is + * implemented in \ref igraph_community_leading_eigenvector(). After + * the initial split, the following splits are done in a way to + * optimize modularity regarding to the original network. Note that + * any further refinement, for example using Kernighan-Lin, as + * proposed in Section V.A of Newman (2006), is not implemented here. + * + * + * + * \example examples/simple/igraph_community_leading_eigenvector.c + * + */ + +typedef struct igraph_i_community_leading_eigenvector_data_t { + igraph_vector_t *idx; + igraph_vector_t *idx2; + igraph_adjlist_t *adjlist; + igraph_inclist_t *inclist; + igraph_vector_t *tmp; + long int no_of_edges; + igraph_vector_t *mymembership; + long int comm; + const igraph_vector_t *weights; + const igraph_t *graph; + igraph_vector_t *strength; + igraph_real_t sumweights; +} igraph_i_community_leading_eigenvector_data_t; + +static int igraph_i_community_leading_eigenvector(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + + igraph_i_community_leading_eigenvector_data_t *data = extra; + long int j, k, nlen, size = n; + igraph_vector_t *idx = data->idx; + igraph_vector_t *idx2 = data->idx2; + igraph_vector_t *tmp = data->tmp; + igraph_adjlist_t *adjlist = data->adjlist; + igraph_real_t ktx, ktx2; + long int no_of_edges = data->no_of_edges; + igraph_vector_t *mymembership = data->mymembership; + long int comm = data->comm; + + /* Ax */ + for (j = 0; j < size; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid); + nlen = igraph_vector_int_size(neis); + to[j] = 0.0; + VECTOR(*tmp)[j] = 0.0; + for (k = 0; k < nlen; k++) { + long int nei = (long int) VECTOR(*neis)[k]; + long int neimemb = (long int) VECTOR(*mymembership)[nei]; + if (neimemb == comm) { + to[j] += from[ (long int) VECTOR(*idx2)[nei] ]; + VECTOR(*tmp)[j] += 1; + } + } + } + + /* Now calculate k^Tx/2m */ + ktx = 0.0; ktx2 = 0.0; + for (j = 0; j < size; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid); + long int degree = igraph_vector_int_size(neis); + ktx += from[j] * degree; + ktx2 += degree; + } + ktx = ktx / no_of_edges / 2.0; + ktx2 = ktx2 / no_of_edges / 2.0; + + /* Now calculate Bx */ + for (j = 0; j < size; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid); + igraph_real_t degree = igraph_vector_int_size(neis); + to[j] = to[j] - ktx * degree; + VECTOR(*tmp)[j] = VECTOR(*tmp)[j] - ktx2 * degree; + } + + /* -d_ij summa l in G B_il */ + for (j = 0; j < size; j++) { + to[j] -= VECTOR(*tmp)[j] * from[j]; + } + + return 0; +} + +static int igraph_i_community_leading_eigenvector2(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + + igraph_i_community_leading_eigenvector_data_t *data = extra; + long int j, k, nlen, size = n; + igraph_vector_t *idx = data->idx; + igraph_vector_t *idx2 = data->idx2; + igraph_vector_t *tmp = data->tmp; + igraph_adjlist_t *adjlist = data->adjlist; + igraph_real_t ktx, ktx2; + long int no_of_edges = data->no_of_edges; + igraph_vector_t *mymembership = data->mymembership; + long int comm = data->comm; + + /* Ax */ + for (j = 0; j < size; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid); + nlen = igraph_vector_int_size(neis); + to[j] = 0.0; + VECTOR(*tmp)[j] = 0.0; + for (k = 0; k < nlen; k++) { + long int nei = (long int) VECTOR(*neis)[k]; + long int neimemb = (long int) VECTOR(*mymembership)[nei]; + if (neimemb == comm) { + long int fi = (long int) VECTOR(*idx2)[nei]; + if (fi < size) { + to[j] += from[fi]; + } + VECTOR(*tmp)[j] += 1; + } + } + } + + /* Now calculate k^Tx/2m */ + ktx = 0.0; ktx2 = 0.0; + for (j = 0; j < size + 1; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid); + long int degree = igraph_vector_int_size(neis); + if (j < size) { + ktx += from[j] * degree; + } + ktx2 += degree; + } + ktx = ktx / no_of_edges / 2.0; + ktx2 = ktx2 / no_of_edges / 2.0; + + /* Now calculate Bx */ + for (j = 0; j < size; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid); + igraph_real_t degree = igraph_vector_int_size(neis); + to[j] = to[j] - ktx * degree; + VECTOR(*tmp)[j] = VECTOR(*tmp)[j] - ktx2 * degree; + } + + /* -d_ij summa l in G B_il */ + for (j = 0; j < size; j++) { + to[j] -= VECTOR(*tmp)[j] * from[j]; + } + + return 0; +} + +static int igraph_i_community_leading_eigenvector_weighted(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + + igraph_i_community_leading_eigenvector_data_t *data = extra; + long int j, k, nlen, size = n; + igraph_vector_t *idx = data->idx; + igraph_vector_t *idx2 = data->idx2; + igraph_vector_t *tmp = data->tmp; + igraph_inclist_t *inclist = data->inclist; + igraph_real_t ktx, ktx2; + igraph_vector_t *mymembership = data->mymembership; + long int comm = data->comm; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_t *strength = data->strength; + igraph_real_t sw = data->sumweights; + + /* Ax */ + for (j = 0; j < size; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_vector_int_t *inc = igraph_inclist_get(inclist, oldid); + nlen = igraph_vector_int_size(inc); + to[j] = 0.0; + VECTOR(*tmp)[j] = 0.0; + for (k = 0; k < nlen; k++) { + long int edge = (long int) VECTOR(*inc)[k]; + igraph_real_t w = VECTOR(*weights)[edge]; + long int nei = IGRAPH_OTHER(graph, edge, oldid); + long int neimemb = (long int) VECTOR(*mymembership)[nei]; + if (neimemb == comm) { + to[j] += from[ (long int) VECTOR(*idx2)[nei] ] * w; + VECTOR(*tmp)[j] += w; + } + } + } + + /* k^Tx/2m */ + ktx = 0.0; ktx2 = 0.0; + for (j = 0; j < size; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_real_t str = VECTOR(*strength)[oldid]; + ktx += from[j] * str; + ktx2 += str; + } + ktx = ktx / sw / 2.0; + ktx2 = ktx2 / sw / 2.0; + + /* Bx */ + for (j = 0; j < size; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_real_t str = VECTOR(*strength)[oldid]; + to[j] = to[j] - ktx * str; + VECTOR(*tmp)[j] = VECTOR(*tmp)[j] - ktx2 * str; + } + + /* -d_ij summa l in G B_il */ + for (j = 0; j < size; j++) { + to[j] -= VECTOR(*tmp)[j] * from[j]; + } + + return 0; +} + +static int igraph_i_community_leading_eigenvector2_weighted(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + + igraph_i_community_leading_eigenvector_data_t *data = extra; + long int j, k, nlen, size = n; + igraph_vector_t *idx = data->idx; + igraph_vector_t *idx2 = data->idx2; + igraph_vector_t *tmp = data->tmp; + igraph_inclist_t *inclist = data->inclist; + igraph_real_t ktx, ktx2; + igraph_vector_t *mymembership = data->mymembership; + long int comm = data->comm; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_t *strength = data->strength; + igraph_real_t sw = data->sumweights; + + /* Ax */ + for (j = 0; j < size; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_vector_int_t *inc = igraph_inclist_get(inclist, oldid); + nlen = igraph_vector_int_size(inc); + to[j] = 0.0; + VECTOR(*tmp)[j] = 0.0; + for (k = 0; k < nlen; k++) { + long int edge = (long int) VECTOR(*inc)[k]; + igraph_real_t w = VECTOR(*weights)[edge]; + long int nei = IGRAPH_OTHER(graph, edge, oldid); + long int neimemb = (long int) VECTOR(*mymembership)[nei]; + if (neimemb == comm) { + long int fi = (long int) VECTOR(*idx2)[nei]; + if (fi < size) { + to[j] += from[fi] * w; + } + VECTOR(*tmp)[j] += w; + } + } + } + + /* k^Tx/2m */ + ktx = 0.0; ktx2 = 0.0; + for (j = 0; j < size + 1; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_real_t str = VECTOR(*strength)[oldid]; + if (j < size) { + ktx += from[j] * str; + } + ktx2 += str; + } + ktx = ktx / sw / 2.0; + ktx2 = ktx2 / sw / 2.0; + + /* Bx */ + for (j = 0; j < size; j++) { + long int oldid = (long int) VECTOR(*idx)[j]; + igraph_real_t str = VECTOR(*strength)[oldid]; + to[j] = to[j] - ktx * str; + VECTOR(*tmp)[j] = VECTOR(*tmp)[j] - ktx2 * str; + } + + /* -d_ij summa l in G B_il */ + for (j = 0; j < size; j++) { + to[j] -= VECTOR(*tmp)[j] * from[j]; + } + + return 0; +} + +static void igraph_i_levc_free(igraph_vector_ptr_t *ptr) { + long int i, n = igraph_vector_ptr_size(ptr); + for (i = 0; i < n; i++) { + igraph_vector_t *v = VECTOR(*ptr)[i]; + if (v) { + igraph_vector_destroy(v); + igraph_free(v); + } + } +} + +static void igraph_i_error_handler_none(const char *reason, const char *file, + int line, int igraph_errno) { + IGRAPH_UNUSED(reason); + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + IGRAPH_UNUSED(igraph_errno); + /* do nothing */ +} + + +/** + * \ingroup communities + * \function igraph_community_leading_eigenvector + * \brief Leading eigenvector community finding (proper version). + * + * Newman's leading eigenvector method for detecting community + * structure. This is the proper implementation of the recursive, + * divisive algorithm: each split is done by maximizing the modularity + * regarding the original network, see MEJ Newman: Finding community + * structure in networks using the eigenvectors of matrices, + * Phys Rev E 74:036104 (2006). + * + * \param graph The undirected input graph. + * \param weights The weights of the edges, or a null pointer for + * unweighted graphs. + * \param merges The result of the algorithm, a matrix containing the + * information about the splits performed. The matrix is built in + * the opposite way however, it is like the result of an + * agglomerative algorithm. If at the end of the algorithm (after + * \p steps steps was done) there are p communities, + * then these are numbered from zero to p-1. The + * first line of the matrix contains the first merge + * (which is in reality the last split) of two communities into + * community p, the merge in the second line forms + * community p+1, etc. The matrix should be + * initialized before calling and will be resized as needed. + * This argument is ignored of it is \c NULL. + * \param membership The membership of the vertices after all the + * splits were performed will be stored here. The vector must be + * initialized before calling and will be resized as needed. + * This argument is ignored if it is \c NULL. This argument can + * also be used to supply a starting configuration for the community + * finding, in the format of a membership vector. In this case the + * \p start argument must be set to 1. + * \param steps The maximum number of steps to perform. It might + * happen that some component (or the whole network) has no + * underlying community structure and no further steps can be + * done. If you want as many steps as possible then supply the + * number of vertices in the network here. + * \param options The options for ARPACK. \c n is always + * overwritten. \c ncv is set to at least 4. + * \param modularity If not a null pointer, then it must be a pointer + * to a real number and the modularity score of the final division + * is stored here. + * \param start Boolean, whether to use the community structure given + * in the \p membership argument as a starting point. + * \param eigenvalues Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the eigenvalues calculated + * along the community structure detection are stored here. The + * non-positive eigenvalues, that do not result a split, are stored + * as well. + * \param eigenvectors If not a null pointer, then the eigenvectors + * that are calculated in each step of the algorithm, are stored here, + * in a pointer vector. Each eigenvector is stored in an + * \ref igraph_vector_t object. The user is responsible of + * deallocating the memory that belongs to the individual vectors, + * by calling first \ref igraph_vector_destroy(), and then + * \ref igraph_free() on them. + * \param history Pointer to an initialized vector or a null pointer. + * If not a null pointer, then a trace of the algorithm is stored + * here, encoded numerically. The various operations: + * \clist + * \cli IGRAPH_LEVC_HIST_START_FULL + * Start the algorithm from an initial state where each connected + * component is a separate community. + * \cli IGRAPH_LEVC_HIST_START_GIVEN + * Start the algorithm from a given community structure. The next + * value in the vector contains the initial number of + * communities. + * \cli IGRAPH_LEVC_HIST_SPLIT + * Split a community into two communities. The id of the splitted + * community is given in the next element of the history vector. + * The id of the first new community is the same as the id of the + * splitted community. The id of the second community equals to + * the number of communities before the split. + * \cli IGRAPH_LEVC_HIST_FAILED + * Tried to split a community, but it was not worth it, as it + * does not result in a bigger modularity value. The id of the + * community is given in the next element of the vector. + * \endclist + * \param callback A null pointer or a function of type \ref + * igraph_community_leading_eigenvector_callback_t. If given, this + * callback function is called after each eigenvector/eigenvalue + * calculation. If the callback returns a non-zero value, then the + * community finding algorithm stops. See the arguments passed to + * the callback at the documentation of \ref + * igraph_community_leading_eigenvector_callback_t. + * \param callback_extra Extra argument to pass to the callback + * function. + * \return Error code. + * + * \sa \ref igraph_community_walktrap() and \ref + * igraph_community_spinglass() for other community structure + * detection methods. + * + * Time complexity: O(|E|+|V|^2*steps), |V| is the number of vertices, + * |E| the number of edges, steps the number of splits + * performed. + */ + +int igraph_community_leading_eigenvector(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_t *merges, + igraph_vector_t *membership, + igraph_integer_t steps, + igraph_arpack_options_t *options, + igraph_real_t *modularity, + igraph_bool_t start, + igraph_vector_t *eigenvalues, + igraph_vector_ptr_t *eigenvectors, + igraph_vector_t *history, + igraph_community_leading_eigenvector_callback_t *callback, + void *callback_extra) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_dqueue_t tosplit; + igraph_vector_t idx, idx2, mymerges; + igraph_vector_t strength, tmp; + long int staken = 0; + igraph_adjlist_t adjlist; + igraph_inclist_t inclist; + long int i, j, k, l; + long int communities; + igraph_vector_t vmembership, *mymembership = membership; + igraph_i_community_leading_eigenvector_data_t extra; + igraph_arpack_storage_t storage; + igraph_real_t mod = 0; + igraph_arpack_function_t *arpcb1 = + weights ? igraph_i_community_leading_eigenvector_weighted : + igraph_i_community_leading_eigenvector; + igraph_arpack_function_t *arpcb2 = + weights ? igraph_i_community_leading_eigenvector2_weighted : + igraph_i_community_leading_eigenvector2; + igraph_real_t sumweights = 0.0; + + if (weights && no_of_edges != igraph_vector_size(weights)) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + if (start && !membership) { + IGRAPH_ERROR("Cannot start from given configuration if memberships " + "missing", IGRAPH_EINVAL); + } + + if (start && membership && + igraph_vector_size(membership) != no_of_nodes) { + IGRAPH_ERROR("Wrong length for vector of predefined memberships", + IGRAPH_EINVAL); + } + + if (start && membership && igraph_vector_max(membership) >= no_of_nodes) { + IGRAPH_WARNING("Too many communities in membership start vector"); + } + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("This method was developed for undirected graphs"); + } + + if (steps < 0 || steps > no_of_nodes - 1) { + steps = (igraph_integer_t) no_of_nodes - 1; + } + + if (!membership) { + mymembership = &vmembership; + IGRAPH_VECTOR_INIT_FINALLY(mymembership, 0); + } + + IGRAPH_VECTOR_INIT_FINALLY(&mymerges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&mymerges, steps * 2)); + IGRAPH_VECTOR_INIT_FINALLY(&idx, 0); + if (eigenvalues) { + igraph_vector_clear(eigenvalues); + } + if (eigenvectors) { + igraph_vector_ptr_clear(eigenvectors); + IGRAPH_FINALLY(igraph_i_levc_free, eigenvectors); + } + + IGRAPH_STATUS("Starting leading eigenvector method.\n", 0); + + if (!start) { + /* Calculate the weakly connected components in the graph and use them as + * an initial split */ + IGRAPH_CHECK(igraph_clusters(graph, mymembership, &idx, 0, IGRAPH_WEAK)); + communities = igraph_vector_size(&idx); + IGRAPH_STATUSF(("Starting from %li component(s).\n", 0, communities)); + if (history) { + IGRAPH_CHECK(igraph_vector_push_back(history, + IGRAPH_LEVC_HIST_START_FULL)); + } + } else { + /* Just create the idx vector for the given membership vector */ + communities = (long int) igraph_vector_max(mymembership) + 1; + IGRAPH_STATUSF(("Starting from given membership vector with %li " + "communities.\n", 0, communities)); + if (history) { + IGRAPH_CHECK(igraph_vector_push_back(history, + IGRAPH_LEVC_HIST_START_GIVEN)); + IGRAPH_CHECK(igraph_vector_push_back(history, communities)); + } + IGRAPH_CHECK(igraph_vector_resize(&idx, communities)); + igraph_vector_null(&idx); + for (i = 0; i < no_of_nodes; i++) { + int t = (int) VECTOR(*mymembership)[i]; + VECTOR(idx)[t] += 1; + } + } + + IGRAPH_DQUEUE_INIT_FINALLY(&tosplit, 100); + for (i = 0; i < communities; i++) { + if (VECTOR(idx)[i] > 2) { + igraph_dqueue_push(&tosplit, i); + } + } + for (i = 1; i < communities; i++) { + /* Record merge */ + IGRAPH_CHECK(igraph_vector_push_back(&mymerges, i - 1)); + IGRAPH_CHECK(igraph_vector_push_back(&mymerges, i)); + if (eigenvalues) { + IGRAPH_CHECK(igraph_vector_push_back(eigenvalues, IGRAPH_NAN)); + } + if (eigenvectors) { + igraph_vector_t *v = igraph_Calloc(1, igraph_vector_t); + if (!v) { + IGRAPH_ERROR("Cannot do leading eigenvector community detection", + IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, v); + IGRAPH_VECTOR_INIT_FINALLY(v, 0); + IGRAPH_CHECK(igraph_vector_ptr_push_back(eigenvectors, v)); + IGRAPH_FINALLY_CLEAN(2); + } + if (history) { + IGRAPH_CHECK(igraph_vector_push_back(history, IGRAPH_LEVC_HIST_SPLIT)); + IGRAPH_CHECK(igraph_vector_push_back(history, i - 1)); + } + } + staken = communities - 1; + + IGRAPH_VECTOR_INIT_FINALLY(&tmp, no_of_nodes); + IGRAPH_CHECK(igraph_vector_resize(&idx, no_of_nodes)); + igraph_vector_null(&idx); + IGRAPH_VECTOR_INIT_FINALLY(&idx2, no_of_nodes); + if (!weights) { + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + } else { + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + IGRAPH_VECTOR_INIT_FINALLY(&strength, no_of_nodes); + IGRAPH_CHECK(igraph_strength(graph, &strength, igraph_vss_all(), + IGRAPH_ALL, IGRAPH_LOOPS, weights)); + sumweights = igraph_vector_sum(weights); + } + + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rssolve */ + options->start = 0; + options->which[0] = 'L'; options->which[1] = 'A'; + + /* Memory for ARPACK */ + /* We are allocating memory for 20 eigenvectors since options->ncv won't be + * larger than 20 when using automatic mode in igraph_arpack_rssolve */ + IGRAPH_CHECK(igraph_arpack_storage_init(&storage, (int) no_of_nodes, 20, + (int) no_of_nodes, 1)); + IGRAPH_FINALLY(igraph_arpack_storage_destroy, &storage); + extra.idx = &idx; + extra.idx2 = &idx2; + extra.tmp = &tmp; + extra.adjlist = &adjlist; + extra.inclist = &inclist; + extra.weights = weights; + extra.sumweights = sumweights; + extra.graph = graph; + extra.strength = &strength; + extra.no_of_edges = no_of_edges; + extra.mymembership = mymembership; + + while (!igraph_dqueue_empty(&tosplit) && staken < steps) { + long int comm = (long int) igraph_dqueue_pop_back(&tosplit); + /* depth first search */ + long int size = 0; + igraph_real_t tmpev; + + IGRAPH_STATUSF(("Trying to split community %li... ", 0, comm)); + IGRAPH_ALLOW_INTERRUPTION(); + + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*mymembership)[i] == comm) { + VECTOR(idx)[size] = i; + VECTOR(idx2)[i] = size++; + } + } + + staken++; + if (size <= 2) { + continue; + } + + /* We solve two eigenproblems, one for the original modularity + matrix, and one for the modularity matrix after deleting the + last row and last column from it. This is a trick to find + multiple leading eigenvalues, because ARPACK is sometimes + unstable when the first two eigenvalues are requested, but it + does much better for the single principal eigenvalue. */ + + /* We start with the smaller eigenproblem. */ + + options->n = (int) size - 1; + options->info = 0; + options->nev = 1; + options->ldv = 0; + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rssolve */ + options->nconv = 0; + options->lworkl = 0; /* we surely have enough space */ + extra.comm = comm; + + /* We try calling the solver twice, once from a random starting + point, once from a fixed one. This is because for some hard + cases it tends to fail. We need to suppress error handling for + the first call. */ + { + int i; + igraph_error_handler_t *errh = + igraph_set_error_handler(igraph_i_error_handler_none); + igraph_warning_handler_t *warnh = + igraph_set_warning_handler(igraph_warning_handler_ignore); + igraph_arpack_rssolve(arpcb2, &extra, options, &storage, + /*values=*/ 0, /*vectors=*/ 0); + igraph_set_error_handler(errh); + igraph_set_warning_handler(warnh); + if (options->nconv < 1) { + /* Call again from a fixed starting point. Note that we cannot use a + * fixed all-1 starting vector as sometimes ARPACK would return a + * 'starting vector is zero' error -- this is of course not true but + * it's a result of ARPACK >= 3.6.3 trying to force the starting vector + * into the range of OP (i.e. the matrix being solved). The initial + * vector we use here seems to work, but I have no theoretical argument + * for its usage; it just happens to work. */ + options->start = 1; + options->info = 0; + options->ncv = 0; + options->lworkl = 0; /* we surely have enough space */ + for (i = 0; i < options->n ; i++) { + storage.resid[i] = i % 2 ? 1 : -1; + } + IGRAPH_CHECK(igraph_arpack_rssolve(arpcb2, &extra, options, &storage, + /*values=*/ 0, /*vectors=*/ 0)); + options->start = 0; + } + } + + if (options->nconv < 1) { + IGRAPH_ERROR("ARPACK did not converge", IGRAPH_ARPACK_FAILED); + } + + tmpev = storage.d[0]; + + /* Now we do the original eigenproblem, again, twice if needed */ + + options->n = (int) size; + options->info = 0; + options->nev = 1; + options->ldv = 0; + options->nconv = 0; + options->lworkl = 0; /* we surely have enough space */ + options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rssolve */ + + { + int i; + igraph_error_handler_t *errh = + igraph_set_error_handler(igraph_i_error_handler_none); + igraph_arpack_rssolve(arpcb1, &extra, options, &storage, + /*values=*/ 0, /*vectors=*/ 0); + igraph_set_error_handler(errh); + if (options->nconv < 1) { + /* Call again from a fixed starting point. See the comment a few lines + * above about the exact choice of this starting vector */ + options->start = 1; + options->info = 0; + options->ncv = 0; + options->lworkl = 0; /* we surely have enough space */ + for (i = 0; i < options->n; i++) { + storage.resid[i] = i % 2 ? 1 : -1; + } + IGRAPH_CHECK(igraph_arpack_rssolve(arpcb1, &extra, options, &storage, + /*values=*/ 0, /*vectors=*/ 0)); + options->start = 0; + } + } + + if (options->nconv < 1) { + IGRAPH_ERROR("ARPACK did not converge", IGRAPH_ARPACK_FAILED); + } + + /* Ok, we have the leading eigenvector of the modularity matrix*/ + + /* ---------------------------------------------------------------*/ + /* To avoid numeric errors */ + if (fabs(storage.d[0]) < 1e-8) { + storage.d[0] = 0; + } + + /* We replace very small (in absolute value) elements of the + leading eigenvector with zero, to get the same result, + consistently.*/ + for (i = 0; i < size; i++) { + if (fabs(storage.v[i]) < 1e-8) { + storage.v[i] = 0; + } + } + + /* Just to have the always the same result, we multiply by -1 + if the first (nonzero) element is not positive. */ + for (i = 0; i < size; i++) { + if (storage.v[i] != 0) { + break; + } + } + if (i < size && storage.v[i] < 0) { + for (i = 0; i < size; i++) { + storage.v[i] = - storage.v[i]; + } + } + /* ---------------------------------------------------------------*/ + + if (callback) { + igraph_vector_t vv; + int ret; + igraph_vector_view(&vv, storage.v, size); + ret = callback(mymembership, comm, storage.d[0], &vv, + arpcb1, &extra, callback_extra); + if (ret) { + break; + } + } + + if (eigenvalues) { + IGRAPH_CHECK(igraph_vector_push_back(eigenvalues, storage.d[0])); + } + + if (eigenvectors) { + igraph_vector_t *v = igraph_Calloc(1, igraph_vector_t); + if (!v) { + IGRAPH_ERROR("Cannot do leading eigenvector community detection", + IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, v); + IGRAPH_VECTOR_INIT_FINALLY(v, size); + for (i = 0; i < size; i++) { + VECTOR(*v)[i] = storage.v[i]; + } + IGRAPH_CHECK(igraph_vector_ptr_push_back(eigenvectors, v)); + IGRAPH_FINALLY_CLEAN(2); + } + + if (storage.d[0] <= 0) { + IGRAPH_STATUS("no split.\n", 0); + if (history) { + IGRAPH_CHECK(igraph_vector_push_back(history, + IGRAPH_LEVC_HIST_FAILED)); + IGRAPH_CHECK(igraph_vector_push_back(history, comm)); + } + continue; + } + + /* Check for multiple leading eigenvalues */ + + if (fabs(storage.d[0] - tmpev) < 1e-8) { + IGRAPH_STATUS("multiple principal eigenvalue, no split.\n", 0); + if (history) { + IGRAPH_CHECK(igraph_vector_push_back(history, + IGRAPH_LEVC_HIST_FAILED)); + IGRAPH_CHECK(igraph_vector_push_back(history, comm)); + } + continue; + } + + /* Count the number of vertices in each community after the split */ + l = 0; + for (j = 0; j < size; j++) { + if (storage.v[j] < 0) { + storage.v[j] = -1; + l++; + } else { + storage.v[j] = 1; + } + } + if (l == 0 || l == size) { + IGRAPH_STATUS("no split.\n", 0); + if (history) { + IGRAPH_CHECK(igraph_vector_push_back(history, + IGRAPH_LEVC_HIST_FAILED)); + IGRAPH_CHECK(igraph_vector_push_back(history, comm)); + } + continue; + } + + /* Check that Q increases with our choice of split */ + arpcb1(storage.v + size, storage.v, (int) size, &extra); + mod = 0; + for (i = 0; i < size; i++) { + mod += storage.v[size + i] * storage.v[i]; + } + if (mod <= 1e-8) { + IGRAPH_STATUS("no modularity increase, no split.\n", 0); + if (history) { + IGRAPH_CHECK(igraph_vector_push_back(history, + IGRAPH_LEVC_HIST_FAILED)); + IGRAPH_CHECK(igraph_vector_push_back(history, comm)); + } + continue; + } + + communities++; + IGRAPH_STATUS("split.\n", 0); + + /* Rewrite the mymembership vector */ + for (j = 0; j < size; j++) { + if (storage.v[j] < 0) { + long int oldid = (long int) VECTOR(idx)[j]; + VECTOR(*mymembership)[oldid] = communities - 1; + } + } + + /* Record merge */ + IGRAPH_CHECK(igraph_vector_push_back(&mymerges, comm)); + IGRAPH_CHECK(igraph_vector_push_back(&mymerges, communities - 1)); + if (history) { + IGRAPH_CHECK(igraph_vector_push_back(history, IGRAPH_LEVC_HIST_SPLIT)); + IGRAPH_CHECK(igraph_vector_push_back(history, comm)); + } + + /* Store the resulting communities in the queue if needed */ + if (l > 1) { + IGRAPH_CHECK(igraph_dqueue_push(&tosplit, communities - 1)); + } + if (size - l > 1) { + IGRAPH_CHECK(igraph_dqueue_push(&tosplit, comm)); + } + + } + + igraph_arpack_storage_destroy(&storage); + IGRAPH_FINALLY_CLEAN(1); + if (!weights) { + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_inclist_destroy(&inclist); + igraph_vector_destroy(&strength); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_dqueue_destroy(&tosplit); + igraph_vector_destroy(&tmp); + igraph_vector_destroy(&idx2); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_STATUS("Done.\n", 0); + + /* reform the mymerges vector */ + if (merges) { + igraph_vector_null(&idx); + l = igraph_vector_size(&mymerges); + k = communities; + j = 0; + IGRAPH_CHECK(igraph_matrix_resize(merges, l / 2, 2)); + for (i = l; i > 0; i -= 2) { + long int from = (long int) VECTOR(mymerges)[i - 1]; + long int to = (long int) VECTOR(mymerges)[i - 2]; + MATRIX(*merges, j, 0) = VECTOR(mymerges)[i - 2]; + MATRIX(*merges, j, 1) = VECTOR(mymerges)[i - 1]; + if (VECTOR(idx)[from] != 0) { + MATRIX(*merges, j, 1) = VECTOR(idx)[from] - 1; + } + if (VECTOR(idx)[to] != 0) { + MATRIX(*merges, j, 0) = VECTOR(idx)[to] - 1; + } + VECTOR(idx)[to] = ++k; + j++; + } + } + + if (eigenvectors) { + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&idx); + igraph_vector_destroy(&mymerges); + IGRAPH_FINALLY_CLEAN(2); + + if (modularity) { + IGRAPH_CHECK(igraph_modularity(graph, mymembership, modularity, + weights)); + } + + if (!membership) { + igraph_vector_destroy(mymembership); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_le_community_to_membership + * Vertex membership from the leading eigenvector community structure + * + * This function creates a membership vector from the + * result of \ref igraph_community_leading_eigenvector(), + * It takes \c membership + * and performs \c steps merges, according to the supplied + * \c merges matrix. + * \param merges The matrix defining the merges to make. + * This is usually from the output of the leading eigenvector community + * structure detection routines. + * \param steps The number of steps to make according to \c merges. + * \param membership Initially the starting membership vector, + * on output the resulting membership vector, after performing \c steps merges. + * \param csize Optionally the sizes of the communities is stored here, + * if this is not a null pointer, but an initialized vector. + * \return Error code. + * + * Time complexity: O(|V|), the number of vertices. + */ + +int igraph_le_community_to_membership(const igraph_matrix_t *merges, + igraph_integer_t steps, + igraph_vector_t *membership, + igraph_vector_t *csize) { + + long int no_of_nodes = igraph_vector_size(membership); + igraph_vector_t fake_memb; + long int components, i; + + if (igraph_matrix_nrow(merges) < steps) { + IGRAPH_ERROR("`steps' to big or `merges' matrix too short", IGRAPH_EINVAL); + } + + components = (long int) igraph_vector_max(membership) + 1; + if (components > no_of_nodes) { + IGRAPH_ERROR("Invalid membership vector, too many components", IGRAPH_EINVAL); + } + if (steps >= components) { + IGRAPH_ERROR("Cannot make `steps' steps from supplied membership vector", + IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&fake_memb, components); + + /* Check membership vector */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*membership)[i] < 0) { + IGRAPH_ERROR("Invalid membership vector, negative id", IGRAPH_EINVAL); + } + VECTOR(fake_memb)[ (long int) VECTOR(*membership)[i] ] += 1; + } + for (i = 0; i < components; i++) { + if (VECTOR(fake_memb)[i] == 0) { + IGRAPH_ERROR("Invalid membership vector, empty cluster", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_community_to_membership(merges, (igraph_integer_t) + components, steps, + &fake_memb, 0)); + + /* Ok, now we have the membership of the initial components, + rewrite the original membership vector. */ + + if (csize) { + IGRAPH_CHECK(igraph_vector_resize(csize, components - steps)); + igraph_vector_null(csize); + } + + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*membership)[i] = VECTOR(fake_memb)[ (long int) VECTOR(*membership)[i] ]; + if (csize) { + VECTOR(*csize)[ (long int) VECTOR(*membership)[i] ] += 1; + } + } + + igraph_vector_destroy(&fake_memb); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/********************************************************************/ + +/** + * \ingroup communities + * \function igraph_community_fluid_communities + * \brief Community detection algorithm based on the simple idea of + * several fluids interacting in a non-homogeneous environment + * (the graph topology), expanding and contracting based on their + * interaction and density. + * + * This function implements the community detection method described in: + * Parés F, Gasulla DG, et. al. (2018) Fluid Communities: A Competitive, + * Scalable and Diverse Community Detection Algorithm. In: Complex Networks + * & Their Applications VI: Proceedings of Complex Networks 2017 (The Sixth + * International Conference on Complex Networks and Their Applications), + * Springer, vol 689, p 229. + * + * \param graph The input graph. The graph must be simple and connected. + * Empty graphs are not supported as well as single vertex graphs. + * Edge directions are ignored. Weights are not considered. + * \param no_of_communities The number of communities to be found. Must be + * greater than 0 and fewer than number of vertices in the graph. + * \param membership The result vector mapping vertices to the communities + * they are assigned to. + * \param modularity If not a null pointer, then it must be a pointer + * to a real number. The modularity score of the detected community + * structure is stored here. + * \return Error code. + * + * Time complexity: O(|E|) + * + * \example examples/tests/igraph_community_fluid_communities.c + */ +int igraph_community_fluid_communities(const igraph_t *graph, + igraph_integer_t no_of_communities, + igraph_vector_t *membership, + igraph_real_t *modularity) { + /* Declaration of variables */ + long int no_of_nodes, i, j, k, kv1; + igraph_adjlist_t al; + double max_density; + igraph_bool_t res, running; + igraph_vector_t node_order, density, label_counters, dominant_labels, nonzero_labels; + igraph_vector_int_t com_to_numvertices; + + /* Initialization of variables needed for initial checking */ + no_of_nodes = igraph_vcount(graph); + + /* Checking input values */ + if (no_of_nodes < 2) { + IGRAPH_ERROR("Empty and single vertex graphs are not supported.", IGRAPH_EINVAL); + } + if ((long int) no_of_communities < 1) { + IGRAPH_ERROR("'no_of_communities' must be greater than 0.", IGRAPH_EINVAL); + } + if ((long int) no_of_communities > no_of_nodes) { + IGRAPH_ERROR("'no_of_communities' can not be greater than number of nodes in " + "the graph.", IGRAPH_EINVAL); + } + igraph_is_simple(graph, &res); + if (!res) { + IGRAPH_ERROR("Only simple graphs are supported.", IGRAPH_EINVAL); + } + igraph_is_connected(graph, &res, IGRAPH_WEAK); + if (!res) { + IGRAPH_ERROR("Disconnected graphs are not supported.", IGRAPH_EINVAL); + } + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Edge directions are ignored."); + } + + /* Internal variables initialization */ + max_density = 1.0; + running = 1; + + /* Resize membership vector (number of nodes) */ + IGRAPH_CHECK(igraph_vector_resize(membership, no_of_nodes)); + + /* Initialize density and com_to_numvertices vectors */ + IGRAPH_CHECK(igraph_vector_init(&density, (long int) no_of_communities)); + IGRAPH_FINALLY(igraph_vector_destroy, &density); + IGRAPH_CHECK(igraph_vector_int_init(&com_to_numvertices, (long int) no_of_communities)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &com_to_numvertices); + + /* Initialize node ordering vector */ + IGRAPH_CHECK(igraph_vector_init_seq(&node_order, 0, no_of_nodes - 1)); + IGRAPH_FINALLY(igraph_vector_destroy, &node_order); + + /* Initialize the membership vector with 0 values */ + igraph_vector_null(membership); + /* Initialize densities to max_density */ + igraph_vector_fill(&density, max_density); + + RNG_BEGIN(); + + /* Initialize com_to_numvertices and initialize communities into membership vector */ + IGRAPH_CHECK(igraph_vector_shuffle(&node_order)); + for (i = 0; i < no_of_communities; i++) { + /* Initialize membership at initial nodes for each community + * where 0 refers to have no label*/ + VECTOR(*membership)[(long int)VECTOR(node_order)[i]] = i + 1.0; + /* Initialize com_to_numvertices list: Number of vertices for each community */ + VECTOR(com_to_numvertices)[i] = 1; + } + + /* Create an adjacency list representation for efficiency. */ + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + + /* Create storage space for counting distinct labels and dominant ones */ + IGRAPH_VECTOR_INIT_FINALLY(&dominant_labels, (long int) no_of_communities); + IGRAPH_VECTOR_INIT_FINALLY(&nonzero_labels, (long int) no_of_communities); + + IGRAPH_CHECK(igraph_vector_init(&label_counters, (long int) no_of_communities)); + IGRAPH_FINALLY(igraph_vector_destroy, &label_counters); + + /* running is the convergence boolean variable */ + running = 1; + while (running) { + /* Declarations of varibales used inside main loop */ + long int v1, size, rand_idx; + igraph_real_t max_count, label_counter_diff; + igraph_vector_int_t *neis; + igraph_bool_t same_label_in_dominant; + + running = 0; + + /* Shuffle the node ordering vector */ + IGRAPH_CHECK(igraph_vector_shuffle(&node_order)); + /* In the prescribed order, loop over the vertices and reassign labels */ + for (i = 0; i < no_of_nodes; i++) { + /* Clear dominant_labels and nonzero_labels vectors */ + igraph_vector_clear(&dominant_labels); + igraph_vector_null(&label_counters); + + /* Obtain actual node index */ + v1 = (long int) VECTOR(node_order)[i]; + /* Take into account same label in updating rule */ + kv1 = (long int) VECTOR(*membership)[v1]; + max_count = 0.0; + if (kv1 != 0) { + VECTOR(label_counters)[kv1 - 1] += VECTOR(density)[kv1 - 1]; + /* Set up max_count */ + max_count = VECTOR(density)[kv1 - 1]; + /* Initialize dominant_labels */ + IGRAPH_CHECK(igraph_vector_resize(&dominant_labels, 1)); + VECTOR(dominant_labels)[0] = kv1; + } + + /* Count the weights corresponding to different labels */ + neis = igraph_adjlist_get(&al, v1); + size = igraph_vector_int_size(neis); + for (j = 0; j < size; j++) { + k = (long int) VECTOR(*membership)[(long)VECTOR(*neis)[j]]; + /* skip if it has no label yet */ + if (k == 0) { + continue; + } + /* Update label counter and evaluate diff against max_count*/ + VECTOR(label_counters)[k - 1] += VECTOR(density)[k - 1]; + label_counter_diff = VECTOR(label_counters)[k - 1] - max_count; + /* Check if this label must be included in dominant_labels vector */ + if (label_counter_diff > 0.0001) { + max_count = VECTOR(label_counters)[k - 1]; + IGRAPH_CHECK(igraph_vector_resize(&dominant_labels, 1)); + VECTOR(dominant_labels)[0] = k; + } else if (-0.0001 < label_counter_diff && label_counter_diff < 0.0001) { + IGRAPH_CHECK(igraph_vector_push_back(&dominant_labels, k)); + } + } + + if (!igraph_vector_empty(&dominant_labels)) { + /* Maintain same label if it exists in dominant_labels */ + same_label_in_dominant = igraph_vector_contains(&dominant_labels, kv1); + + if (!same_label_in_dominant) { + /* We need at least one more iteration */ + running = 1; + + /* Select randomly from the dominant labels */ + rand_idx = RNG_INTEGER(0, igraph_vector_size(&dominant_labels) - 1); + k = (long int) VECTOR(dominant_labels)[rand_idx]; + + if (kv1 != 0) { + /* Subtract 1 vertex from corresponding community in com_to_numvertices */ + VECTOR(com_to_numvertices)[kv1 - 1] -= 1; + /* Re-calculate density for community kv1 */ + VECTOR(density)[kv1 - 1] = max_density / VECTOR(com_to_numvertices)[kv1 - 1]; + } + + /* Update vertex new label */ + VECTOR(*membership)[v1] = k; + + /* Add 1 vertex to corresponding new community in com_to_numvertices */ + VECTOR(com_to_numvertices)[k - 1] += 1; + /* Re-calculate density for new community k */ + VECTOR(density)[k - 1] = max_density / VECTOR(com_to_numvertices)[k - 1]; + } + } + } + } + + RNG_END(); + + + /* Shift back the membership vector */ + /* There must be no 0 labels in membership vector at this point */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*membership)[i] -= 1; + /* Something went wrong: At least one vertex has no community assigned */ + if (VECTOR(*membership)[i] < 0) { + IGRAPH_ERROR("Something went wrong during execution. One or more vertices got " + "no community assigned at algorithm convergence.", IGRAPH_EINTERNAL); + } + } + + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(1); + + if (modularity) { + IGRAPH_CHECK(igraph_modularity(graph, membership, modularity, + NULL)); + } + + igraph_vector_destroy(&node_order); + igraph_vector_destroy(&density); + igraph_vector_int_destroy(&com_to_numvertices); + igraph_vector_destroy(&label_counters); + igraph_vector_destroy(&dominant_labels); + igraph_vector_destroy(&nonzero_labels); + IGRAPH_FINALLY_CLEAN(6); + + return 0; +} + +/********************************************************************/ + +/** + * \ingroup communities + * \function igraph_community_label_propagation + * \brief Community detection based on label propagation + * + * This function implements the community detection method described in: + * Raghavan, U.N. and Albert, R. and Kumara, S.: Near linear time algorithm + * to detect community structures in large-scale networks. Phys Rev E + * 76, 036106. (2007). This version extends the original method by + * the ability to take edge weights into consideration and also + * by allowing some labels to be fixed. + * + * + * Weights are taken into account as follows: when the new label of node + * i is determined, the algorithm iterates over all edges incident on + * node i and calculate the total weight of edges leading to other + * nodes with label 0, 1, 2, ..., k-1 (where k is the number of possible + * labels). The new label of node i will then be the label whose edges + * (among the ones incident on node i) have the highest total weight. + * + * \param graph The input graph, should be undirected to make sense. + * \param membership The membership vector, the result is returned here. + * For each vertex it gives the ID of its community (label). + * \param weights The weight vector, it should contain a positive + * weight for all the edges. + * \param initial The initial state. If NULL, every vertex will have + * a different label at the beginning. Otherwise it must be a vector + * with an entry for each vertex. Non-negative values denote different + * labels, negative entries denote vertices without labels. + * \param fixed Boolean vector denoting which labels are fixed. Of course + * this makes sense only if you provided an initial state, otherwise + * this element will be ignored. Also note that vertices without labels + * cannot be fixed. + * \param modularity If not a null pointer, then it must be a pointer + * to a real number. The modularity score of the detected community + * structure is stored here. + * \return Error code. + * + * Time complexity: O(m+n) + * + * \example examples/simple/igraph_community_label_propagation.c + */ +int igraph_community_label_propagation(const igraph_t *graph, + igraph_vector_t *membership, + const igraph_vector_t *weights, + const igraph_vector_t *initial, + igraph_vector_bool_t *fixed, + igraph_real_t *modularity) { + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + long int no_of_not_fixed_nodes = no_of_nodes; + long int i, j, k; + igraph_adjlist_t al; + igraph_inclist_t il; + igraph_bool_t running = 1; + + igraph_vector_t label_counters, dominant_labels, nonzero_labels, node_order; + + /* The implementation uses a trick to avoid negative array indexing: + * elements of the membership vector are increased by 1 at the start + * of the algorithm; this to allow us to denote unlabeled vertices + * (if any) by zeroes. The membership vector is shifted back in the end + */ + + /* Do some initial checks */ + if (fixed && igraph_vector_bool_size(fixed) != no_of_nodes) { + IGRAPH_ERROR("Invalid fixed labeling vector length", IGRAPH_EINVAL); + } + if (weights) { + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } else if (igraph_vector_min(weights) < 0) { + IGRAPH_ERROR("Weights must be non-negative", IGRAPH_EINVAL); + } + } + if (fixed && !initial) { + IGRAPH_WARNING("Ignoring fixed vertices as no initial labeling given"); + } + + IGRAPH_CHECK(igraph_vector_resize(membership, no_of_nodes)); + + if (initial) { + if (igraph_vector_size(initial) != no_of_nodes) { + IGRAPH_ERROR("Invalid initial labeling vector length", IGRAPH_EINVAL); + } + /* Check if the labels used are valid, initialize membership vector */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*initial)[i] < 0) { + VECTOR(*membership)[i] = 0; + } else { + VECTOR(*membership)[i] = floor(VECTOR(*initial)[i]) + 1; + } + } + if (fixed) { + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*fixed)[i]) { + if (VECTOR(*membership)[i] == 0) { + IGRAPH_WARNING("Fixed nodes cannot be unlabeled, ignoring them"); + VECTOR(*fixed)[i] = 0; + } else { + no_of_not_fixed_nodes--; + } + } + } + } + + i = (long int) igraph_vector_max(membership); + if (i > no_of_nodes) { + IGRAPH_ERROR("elements of the initial labeling vector must be between 0 and |V|-1", IGRAPH_EINVAL); + } + if (i <= 0) { + IGRAPH_ERROR("at least one vertex must be labeled in the initial labeling", IGRAPH_EINVAL); + } + } else { + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*membership)[i] = i + 1; + } + } + + /* Create an adjacency/incidence list representation for efficiency. + * For the unweighted case, the adjacency list is enough. For the + * weighted case, we need the incidence list */ + if (weights) { + IGRAPH_CHECK(igraph_inclist_init(graph, &il, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + } else { + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + } + + /* Create storage space for counting distinct labels and dominant ones */ + IGRAPH_VECTOR_INIT_FINALLY(&label_counters, no_of_nodes + 1); + IGRAPH_VECTOR_INIT_FINALLY(&dominant_labels, 0); + IGRAPH_VECTOR_INIT_FINALLY(&nonzero_labels, 0); + IGRAPH_CHECK(igraph_vector_reserve(&dominant_labels, 2)); + + RNG_BEGIN(); + + /* Initialize node ordering vector with only the not fixed nodes */ + if (fixed) { + IGRAPH_VECTOR_INIT_FINALLY(&node_order, no_of_not_fixed_nodes); + for (i = 0, j = 0; i < no_of_nodes; i++) { + if (!VECTOR(*fixed)[i]) { + VECTOR(node_order)[j] = i; + j++; + } + } + } else { + IGRAPH_CHECK(igraph_vector_init_seq(&node_order, 0, no_of_nodes - 1)); + IGRAPH_FINALLY(igraph_vector_destroy, &node_order); + } + + running = 1; + while (running) { + long int v1, num_neis; + igraph_real_t max_count; + igraph_vector_int_t *neis; + igraph_vector_int_t *ineis; + igraph_bool_t was_zero; + + running = 0; + + /* Shuffle the node ordering vector */ + IGRAPH_CHECK(igraph_vector_shuffle(&node_order)); + /* In the prescribed order, loop over the vertices and reassign labels */ + for (i = 0; i < no_of_not_fixed_nodes; i++) { + v1 = (long int) VECTOR(node_order)[i]; + + /* Count the weights corresponding to different labels */ + igraph_vector_clear(&dominant_labels); + igraph_vector_clear(&nonzero_labels); + max_count = 0.0; + if (weights) { + ineis = igraph_inclist_get(&il, v1); + num_neis = igraph_vector_int_size(ineis); + for (j = 0; j < num_neis; j++) { + k = (long int) VECTOR(*membership)[ + (long)IGRAPH_OTHER(graph, VECTOR(*ineis)[j], v1) ]; + if (k == 0) { + continue; /* skip if it has no label yet */ + } + was_zero = (VECTOR(label_counters)[k] == 0); + VECTOR(label_counters)[k] += VECTOR(*weights)[(long)VECTOR(*ineis)[j]]; + if (was_zero && VECTOR(label_counters)[k] != 0) { + /* counter just became nonzero */ + IGRAPH_CHECK(igraph_vector_push_back(&nonzero_labels, k)); + } + if (max_count < VECTOR(label_counters)[k]) { + max_count = VECTOR(label_counters)[k]; + IGRAPH_CHECK(igraph_vector_resize(&dominant_labels, 1)); + VECTOR(dominant_labels)[0] = k; + } else if (max_count == VECTOR(label_counters)[k]) { + IGRAPH_CHECK(igraph_vector_push_back(&dominant_labels, k)); + } + } + } else { + neis = igraph_adjlist_get(&al, v1); + num_neis = igraph_vector_int_size(neis); + for (j = 0; j < num_neis; j++) { + k = (long int) VECTOR(*membership)[(long)VECTOR(*neis)[j]]; + if (k == 0) { + continue; /* skip if it has no label yet */ + } + VECTOR(label_counters)[k]++; + if (VECTOR(label_counters)[k] == 1) { + /* counter just became nonzero */ + IGRAPH_CHECK(igraph_vector_push_back(&nonzero_labels, k)); + } + if (max_count < VECTOR(label_counters)[k]) { + max_count = VECTOR(label_counters)[k]; + IGRAPH_CHECK(igraph_vector_resize(&dominant_labels, 1)); + VECTOR(dominant_labels)[0] = k; + } else if (max_count == VECTOR(label_counters)[k]) { + IGRAPH_CHECK(igraph_vector_push_back(&dominant_labels, k)); + } + } + } + + if (igraph_vector_size(&dominant_labels) > 0) { + /* Select randomly from the dominant labels */ + k = RNG_INTEGER(0, igraph_vector_size(&dominant_labels) - 1); + k = (long int) VECTOR(dominant_labels)[k]; + /* Check if the _current_ label of the node is also dominant */ + if (VECTOR(label_counters)[(long)VECTOR(*membership)[v1]] != max_count) { + /* Nope, we need at least one more iteration */ + running = 1; + } + VECTOR(*membership)[v1] = k; + } + + /* Clear the nonzero elements in label_counters */ + num_neis = igraph_vector_size(&nonzero_labels); + for (j = 0; j < num_neis; j++) { + VECTOR(label_counters)[(long int)VECTOR(nonzero_labels)[j]] = 0; + } + } + } + + RNG_END(); + + /* Shift back the membership vector, permute labels in increasing order */ + /* We recycle label_counters here :) */ + igraph_vector_fill(&label_counters, -1); + j = 0; + for (i = 0; i < no_of_nodes; i++) { + k = (long)VECTOR(*membership)[i] - 1; + if (k >= 0) { + if (VECTOR(label_counters)[k] == -1) { + /* We have seen this label for the first time */ + VECTOR(label_counters)[k] = j; + k = j; + j++; + } else { + k = (long int) VECTOR(label_counters)[k]; + } + } else { + /* This is an unlabeled vertex */ + } + VECTOR(*membership)[i] = k; + } + + if (weights) { + igraph_inclist_destroy(&il); + } else { + igraph_adjlist_destroy(&al); + } + IGRAPH_FINALLY_CLEAN(1); + + if (modularity) { + IGRAPH_CHECK(igraph_modularity(graph, membership, modularity, + weights)); + } + + igraph_vector_destroy(&node_order); + igraph_vector_destroy(&label_counters); + igraph_vector_destroy(&dominant_labels); + igraph_vector_destroy(&nonzero_labels); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +/********************************************************************/ + +/* Structure storing a community */ +typedef struct { + igraph_integer_t size; /* Size of the community */ + igraph_real_t weight_inside; /* Sum of edge weights inside community */ + igraph_real_t weight_all; /* Sum of edge weights starting/ending + in the community */ +} igraph_i_multilevel_community; + +/* Global community list structure */ +typedef struct { + long int communities_no, vertices_no; /* Number of communities, number of vertices */ + igraph_real_t weight_sum; /* Sum of edges weight in the whole graph */ + igraph_i_multilevel_community *item; /* List of communities */ + igraph_vector_t *membership; /* Community IDs */ + igraph_vector_t *weights; /* Graph edge weights */ +} igraph_i_multilevel_community_list; + +/* Computes the modularity of a community partitioning */ +static igraph_real_t igraph_i_multilevel_community_modularity( + const igraph_i_multilevel_community_list *communities) { + igraph_real_t result = 0; + long int i; + igraph_real_t m = communities->weight_sum; + + for (i = 0; i < communities->vertices_no; i++) { + if (communities->item[i].size > 0) { + result += (communities->item[i].weight_inside - communities->item[i].weight_all * communities->item[i].weight_all / m) / m; + } + } + + return result; +} + +typedef struct { + long int from; + long int to; + long int id; +} igraph_i_multilevel_link; + +static int igraph_i_multilevel_link_cmp(const void *a, const void *b) { + long int r = (((igraph_i_multilevel_link*)a)->from - + ((igraph_i_multilevel_link*)b)->from); + if (r != 0) { + return (int) r; + } + + return (int) (((igraph_i_multilevel_link*)a)->to - + ((igraph_i_multilevel_link*)b)->to); +} + +/* removes multiple edges and returns new edge id's for each edge in |E|log|E| */ +static int igraph_i_multilevel_simplify_multiple(igraph_t *graph, igraph_vector_t *eids) { + long int ecount = igraph_ecount(graph); + long int i, l = -1, last_from = -1, last_to = -1; + igraph_bool_t directed = igraph_is_directed(graph); + igraph_integer_t from, to; + igraph_vector_t edges; + igraph_i_multilevel_link *links; + + /* Make sure there's enough space in eids to store the new edge IDs */ + IGRAPH_CHECK(igraph_vector_resize(eids, ecount)); + + links = igraph_Calloc(ecount, igraph_i_multilevel_link); + if (links == 0) { + IGRAPH_ERROR("multi-level community structure detection failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, links); + + for (i = 0; i < ecount; i++) { + igraph_edge(graph, (igraph_integer_t) i, &from, &to); + links[i].from = from; + links[i].to = to; + links[i].id = i; + } + + qsort((void*)links, (size_t) ecount, sizeof(igraph_i_multilevel_link), + igraph_i_multilevel_link_cmp); + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + for (i = 0; i < ecount; i++) { + if (links[i].from == last_from && links[i].to == last_to) { + VECTOR(*eids)[links[i].id] = l; + continue; + } + + last_from = links[i].from; + last_to = links[i].to; + + igraph_vector_push_back(&edges, last_from); + igraph_vector_push_back(&edges, last_to); + + l++; + + VECTOR(*eids)[links[i].id] = l; + } + + igraph_Free(links); + IGRAPH_FINALLY_CLEAN(1); + + igraph_destroy(graph); + IGRAPH_CHECK(igraph_create(graph, &edges, igraph_vcount(graph), directed)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +typedef struct { + long int community; + igraph_real_t weight; +} igraph_i_multilevel_community_link; + +static int igraph_i_multilevel_community_link_cmp(const void *a, const void *b) { + return (int) (((igraph_i_multilevel_community_link*)a)->community - + ((igraph_i_multilevel_community_link*)b)->community); +} + +/** + * Given a graph, a community structure and a vertex ID, this method + * calculates: + * + * - edges: the list of edge IDs that are incident on the vertex + * - weight_all: the total weight of these edges + * - weight_inside: the total weight of edges that stay within the same + * community where the given vertex is right now, excluding loop edges + * - weight_loop: the total weight of loop edges + * - links_community and links_weight: together these two vectors list the + * communities incident on this vertex and the total weight of edges + * pointing to these communities + */ +static int igraph_i_multilevel_community_links( + const igraph_t *graph, + const igraph_i_multilevel_community_list *communities, + igraph_integer_t vertex, igraph_vector_t *edges, + igraph_real_t *weight_all, igraph_real_t *weight_inside, igraph_real_t *weight_loop, + igraph_vector_t *links_community, igraph_vector_t *links_weight) { + + long int i, n, last = -1, c = -1; + igraph_real_t weight = 1; + long int to, to_community; + long int community = (long int) VECTOR(*(communities->membership))[(long int)vertex]; + igraph_i_multilevel_community_link *links; + + *weight_all = *weight_inside = *weight_loop = 0; + + igraph_vector_clear(links_community); + igraph_vector_clear(links_weight); + + /* Get the list of incident edges */ + igraph_incident(graph, edges, vertex, IGRAPH_ALL); + + n = igraph_vector_size(edges); + links = igraph_Calloc(n, igraph_i_multilevel_community_link); + if (links == 0) { + IGRAPH_ERROR("multi-level community structure detection failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, links); + + for (i = 0; i < n; i++) { + long int eidx = (long int) VECTOR(*edges)[i]; + weight = VECTOR(*communities->weights)[eidx]; + + to = IGRAPH_OTHER(graph, eidx, vertex); + + *weight_all += weight; + if (to == vertex) { + *weight_loop += weight; + + links[i].community = community; + links[i].weight = 0; + continue; + } + + to_community = (long int)VECTOR(*(communities->membership))[to]; + if (community == to_community) { + *weight_inside += weight; + } + + /* debug("Link %ld (C: %ld) <-> %ld (C: %ld)\n", vertex, community, to, to_community); */ + + links[i].community = to_community; + links[i].weight = weight; + } + + /* Sort links by community ID and merge the same */ + qsort((void*)links, (size_t) n, sizeof(igraph_i_multilevel_community_link), + igraph_i_multilevel_community_link_cmp); + for (i = 0; i < n; i++) { + to_community = links[i].community; + if (to_community != last) { + igraph_vector_push_back(links_community, to_community); + igraph_vector_push_back(links_weight, links[i].weight); + last = to_community; + c++; + } else { + VECTOR(*links_weight)[c] += links[i].weight; + } + } + + igraph_free(links); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static igraph_real_t igraph_i_multilevel_community_modularity_gain( + const igraph_i_multilevel_community_list *communities, + igraph_integer_t community, igraph_integer_t vertex, + igraph_real_t weight_all, igraph_real_t weight_inside) { + IGRAPH_UNUSED(vertex); + return weight_inside - + communities->item[(long int)community].weight_all * weight_all / communities->weight_sum; +} + +/* Shrinks communities into single vertices, keeping all the edges. + * This method is internal because it destroys the graph in-place and + * creates a new one -- this is fine for the multilevel community + * detection where a copy of the original graph is used anyway. + * The membership vector will also be rewritten by the underlying + * igraph_membership_reindex call */ +static int igraph_i_multilevel_shrink(igraph_t *graph, igraph_vector_t *membership) { + igraph_vector_t edges; + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + + long int i; + igraph_eit_t eit; + + if (no_of_nodes == 0) { + return 0; + } + + if (igraph_vector_size(membership) < no_of_nodes) { + IGRAPH_ERROR("cannot shrink graph, membership vector too short", + IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + + IGRAPH_CHECK(igraph_reindex_membership(membership, 0, NULL)); + + /* Create the new edgelist */ + igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &eit); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + i = 0; + while (!IGRAPH_EIT_END(eit)) { + igraph_integer_t from, to; + IGRAPH_CHECK(igraph_edge(graph, IGRAPH_EIT_GET(eit), &from, &to)); + VECTOR(edges)[i++] = VECTOR(*membership)[(long int) from]; + VECTOR(edges)[i++] = VECTOR(*membership)[(long int) to]; + IGRAPH_EIT_NEXT(eit); + } + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + + /* Create the new graph */ + igraph_destroy(graph); + no_of_nodes = (long int) igraph_vector_max(membership) + 1; + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) no_of_nodes, + directed)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \ingroup communities + * \function igraph_i_community_multilevel_step + * \brief Performs a single step of the multi-level modularity optimization method + * + * This function implements a single step of the multi-level modularity optimization + * algorithm for finding community structure, see VD Blondel, J-L Guillaume, + * R Lambiotte and E Lefebvre: Fast unfolding of community hierarchies in large + * networks, http://arxiv.org/abs/0803.0476 for the details. + * + * This function was contributed by Tom Gregorovic. + * + * \param graph The input graph. It must be an undirected graph. + * \param weights Numeric vector containing edge weights. If \c NULL, every edge + * has equal weight. The weights are expected to be non-negative. + * \param membership The membership vector, the result is returned here. + * For each vertex it gives the ID of its community. + * \param modularity The modularity of the partition is returned here. + * \c NULL means that the modularity is not needed. + * \return Error code. + * + * Time complexity: in average near linear on sparse graphs. + */ +static int igraph_i_community_multilevel_step( + igraph_t *graph, + igraph_vector_t *weights, + igraph_vector_t *membership, + igraph_real_t *modularity) { + + long int i, j; + long int vcount = igraph_vcount(graph); + long int ecount = igraph_ecount(graph); + igraph_integer_t ffrom, fto; + igraph_real_t q, pass_q; + int pass; + igraph_bool_t changed = 0; + igraph_vector_t links_community; + igraph_vector_t links_weight; + igraph_vector_t edges; + igraph_vector_t temp_membership; + igraph_i_multilevel_community_list communities; + + /* Initial sanity checks on the input parameters */ + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("multi-level community detection works for undirected graphs only", + IGRAPH_UNIMPLEMENTED); + } + if (igraph_vector_size(weights) < igraph_ecount(graph)) { + IGRAPH_ERROR("multi-level community detection: weight vector too short", IGRAPH_EINVAL); + } + if (igraph_vector_any_smaller(weights, 0)) { + IGRAPH_ERROR("weights must be positive", IGRAPH_EINVAL); + } + + /* Initialize data structures */ + IGRAPH_VECTOR_INIT_FINALLY(&links_community, 0); + IGRAPH_VECTOR_INIT_FINALLY(&links_weight, 0); + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&temp_membership, vcount); + IGRAPH_CHECK(igraph_vector_resize(membership, vcount)); + + /* Initialize list of communities from graph vertices */ + communities.vertices_no = vcount; + communities.communities_no = vcount; + communities.weights = weights; + communities.weight_sum = 2 * igraph_vector_sum(weights); + communities.membership = membership; + communities.item = igraph_Calloc(vcount, igraph_i_multilevel_community); + if (communities.item == 0) { + IGRAPH_ERROR("multi-level community structure detection failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, communities.item); + + /* Still initializing the communities data structure */ + for (i = 0; i < vcount; i++) { + VECTOR(*communities.membership)[i] = i; + communities.item[i].size = 1; + communities.item[i].weight_inside = 0; + communities.item[i].weight_all = 0; + } + + /* Some more initialization :) */ + for (i = 0; i < ecount; i++) { + igraph_real_t weight = 1; + igraph_edge(graph, (igraph_integer_t) i, &ffrom, &fto); + + weight = VECTOR(*weights)[i]; + communities.item[(long int) ffrom].weight_all += weight; + communities.item[(long int) fto].weight_all += weight; + if (ffrom == fto) { + communities.item[(long int) ffrom].weight_inside += 2 * weight; + } + } + + q = igraph_i_multilevel_community_modularity(&communities); + pass = 1; + + do { /* Pass begin */ + long int temp_communities_no = communities.communities_no; + + pass_q = q; + changed = 0; + + /* Save the current membership, it will be restored in case of worse result */ + IGRAPH_CHECK(igraph_vector_update(&temp_membership, communities.membership)); + + for (i = 0; i < vcount; i++) { + /* Exclude vertex from its current community */ + igraph_real_t weight_all = 0; + igraph_real_t weight_inside = 0; + igraph_real_t weight_loop = 0; + igraph_real_t max_q_gain = 0; + igraph_real_t max_weight; + long int old_id, new_id, n; + + igraph_i_multilevel_community_links(graph, &communities, + (igraph_integer_t) i, &edges, + &weight_all, &weight_inside, + &weight_loop, &links_community, + &links_weight); + + old_id = (long int)VECTOR(*(communities.membership))[i]; + new_id = old_id; + + /* Update old community */ + igraph_vector_set(communities.membership, i, -1); + communities.item[old_id].size--; + if (communities.item[old_id].size == 0) { + communities.communities_no--; + } + communities.item[old_id].weight_all -= weight_all; + communities.item[old_id].weight_inside -= 2 * weight_inside + weight_loop; + + /* debug("Remove %ld all: %lf Inside: %lf\n", i, -weight_all, -2*weight_inside + weight_loop); */ + + /* Find new community to join with the best modification gain */ + max_q_gain = 0; + max_weight = weight_inside; + n = igraph_vector_size(&links_community); + + for (j = 0; j < n; j++) { + long int c = (long int) VECTOR(links_community)[j]; + igraph_real_t w = VECTOR(links_weight)[j]; + + igraph_real_t q_gain = + igraph_i_multilevel_community_modularity_gain(&communities, + (igraph_integer_t) c, + (igraph_integer_t) i, + weight_all, w); + /* debug("Link %ld -> %ld weight: %lf gain: %lf\n", i, c, (double) w, (double) q_gain); */ + if (q_gain > max_q_gain) { + new_id = c; + max_q_gain = q_gain; + max_weight = w; + } + } + + /* debug("Added vertex %ld to community %ld (gain %lf).\n", i, new_id, (double) max_q_gain); */ + + /* Add vertex to "new" community and update it */ + igraph_vector_set(communities.membership, i, new_id); + if (communities.item[new_id].size == 0) { + communities.communities_no++; + } + communities.item[new_id].size++; + communities.item[new_id].weight_all += weight_all; + communities.item[new_id].weight_inside += 2 * max_weight + weight_loop; + + if (new_id != old_id) { + changed++; + } + } + + q = igraph_i_multilevel_community_modularity(&communities); + + if (changed && (q > pass_q)) { + /* debug("Pass %d (changed: %d) Communities: %ld Modularity from %lf to %lf\n", + pass, changed, communities.communities_no, (double) pass_q, (double) q); */ + pass++; + } else { + /* No changes or the modularity became worse, restore last membership */ + IGRAPH_CHECK(igraph_vector_update(communities.membership, &temp_membership)); + communities.communities_no = temp_communities_no; + break; + } + + IGRAPH_ALLOW_INTERRUPTION(); + } while (changed && (q > pass_q)); /* Pass end */ + + if (modularity) { + *modularity = q; + } + + /* debug("Result Communities: %ld Modularity: %lf\n", + communities.communities_no, (double) q); */ + + IGRAPH_CHECK(igraph_reindex_membership(membership, 0, NULL)); + + /* Shrink the nodes of the graph according to the present community structure + * and simplify the resulting graph */ + + /* TODO: check if we really need to copy temp_membership */ + IGRAPH_CHECK(igraph_vector_update(&temp_membership, membership)); + IGRAPH_CHECK(igraph_i_multilevel_shrink(graph, &temp_membership)); + igraph_vector_destroy(&temp_membership); + IGRAPH_FINALLY_CLEAN(1); + + /* Update edge weights after shrinking and simplification */ + /* Here we reuse the edges vector as we don't need the previous contents anymore */ + /* TODO: can we use igraph_simplify here? */ + IGRAPH_CHECK(igraph_i_multilevel_simplify_multiple(graph, &edges)); + + /* We reuse the links_weight vector to store the old edge weights */ + IGRAPH_CHECK(igraph_vector_update(&links_weight, weights)); + igraph_vector_fill(weights, 0); + + for (i = 0; i < ecount; i++) { + VECTOR(*weights)[(long int)VECTOR(edges)[i]] += VECTOR(links_weight)[i]; + } + + igraph_free(communities.item); + igraph_vector_destroy(&links_community); + igraph_vector_destroy(&links_weight); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +/** + * \ingroup communities + * \function igraph_community_multilevel + * \brief Finding community structure by multi-level optimization of modularity + * + * This function implements the multi-level modularity optimization + * algorithm for finding community structure, see + * VD Blondel, J-L Guillaume, R Lambiotte and E Lefebvre: Fast unfolding of + * community hierarchies in large networks, J Stat Mech P10008 (2008) + * for the details (preprint: http://arxiv.org/abs/arXiv:0803.0476). + * + * It is based on the modularity measure and a hierarchical approach. + * Initially, each vertex is assigned to a community on its own. In every step, + * vertices are re-assigned to communities in a local, greedy way: each vertex + * is moved to the community with which it achieves the highest contribution to + * modularity. When no vertices can be reassigned, each community is considered + * a vertex on its own, and the process starts again with the merged communities. + * The process stops when there is only a single vertex left or when the modularity + * cannot be increased any more in a step. + * + * This function was contributed by Tom Gregorovic. + * + * \param graph The input graph. It must be an undirected graph. + * \param weights Numeric vector containing edge weights. If \c NULL, every edge + * has equal weight. The weights are expected to be non-negative. + * \param membership The membership vector, the result is returned here. + * For each vertex it gives the ID of its community. The vector + * must be initialized and it will be resized accordingly. + * \param memberships Numeric matrix that will contain the membership + * vector after each level, if not \c NULL. It must be initialized and + * it will be resized accordingly. + * \param modularity Numeric vector that will contain the modularity score + * after each level, if not \c NULL. It must be initialized and it + * will be resized accordingly. + * \return Error code. + * + * Time complexity: in average near linear on sparse graphs. + * + * \example examples/simple/igraph_community_multilevel.c + */ + +int igraph_community_multilevel(const igraph_t *graph, + const igraph_vector_t *weights, igraph_vector_t *membership, + igraph_matrix_t *memberships, igraph_vector_t *modularity) { + + igraph_t g; + igraph_vector_t w, m, level_membership; + igraph_real_t prev_q = -1, q = -1; + int i, level = 1; + long int vcount = igraph_vcount(graph); + + /* Make a copy of the original graph, we will do the merges on the copy */ + IGRAPH_CHECK(igraph_copy(&g, graph)); + IGRAPH_FINALLY(igraph_destroy, &g); + + if (weights) { + IGRAPH_CHECK(igraph_vector_copy(&w, weights)); + IGRAPH_FINALLY(igraph_vector_destroy, &w); + } else { + IGRAPH_VECTOR_INIT_FINALLY(&w, igraph_ecount(&g)); + igraph_vector_fill(&w, 1); + } + + IGRAPH_VECTOR_INIT_FINALLY(&m, vcount); + IGRAPH_VECTOR_INIT_FINALLY(&level_membership, vcount); + + if (memberships || membership) { + /* Put each vertex in its own community */ + for (i = 0; i < vcount; i++) { + VECTOR(level_membership)[i] = i; + } + } + if (memberships) { + /* Resize the membership matrix to have vcount columns and no rows */ + IGRAPH_CHECK(igraph_matrix_resize(memberships, 0, vcount)); + } + if (modularity) { + /* Clear the modularity vector */ + igraph_vector_clear(modularity); + } + + while (1) { + /* Remember the previous modularity and vertex count, do a single step */ + igraph_integer_t step_vcount = igraph_vcount(&g); + + prev_q = q; + IGRAPH_CHECK(igraph_i_community_multilevel_step(&g, &w, &m, &q)); + + /* Were there any merges? If not, we have to stop the process */ + if (igraph_vcount(&g) == step_vcount || q < prev_q) { + break; + } + + if (memberships || membership) { + for (i = 0; i < vcount; i++) { + /* Readjust the membership vector */ + VECTOR(level_membership)[i] = VECTOR(m)[(long int) VECTOR(level_membership)[i]]; + } + } + + if (modularity) { + /* If we have to return the modularity scores, add it to the modularity vector */ + IGRAPH_CHECK(igraph_vector_push_back(modularity, q)); + } + + if (memberships) { + /* If we have to return the membership vectors at each level, store the new + * membership vector */ + IGRAPH_CHECK(igraph_matrix_add_rows(memberships, 1)); + IGRAPH_CHECK(igraph_matrix_set_row(memberships, &level_membership, level - 1)); + } + + /* debug("Level: %d Communities: %ld Modularity: %f\n", level, (long int) igraph_vcount(&g), + (double) q); */ + + /* Increase the level counter */ + level++; + } + + /* It might happen that there are no merges, so every vertex is in its + own community. We still might want the modularity score for that. */ + if (modularity && igraph_vector_size(modularity) == 0) { + igraph_vector_t tmp; + igraph_real_t mod; + int i; + IGRAPH_VECTOR_INIT_FINALLY(&tmp, vcount); + for (i = 0; i < vcount; i++) { + VECTOR(tmp)[i] = i; + } + IGRAPH_CHECK(igraph_modularity(graph, &tmp, &mod, weights)); + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_CHECK(igraph_vector_resize(modularity, 1)); + VECTOR(*modularity)[0] = mod; + } + + /* If we need the final membership vector, copy it to the output */ + if (membership) { + IGRAPH_CHECK(igraph_vector_resize(membership, vcount)); + for (i = 0; i < vcount; i++) { + VECTOR(*membership)[i] = VECTOR(level_membership)[i]; + } + } + + /* Destroy the copy of the graph */ + igraph_destroy(&g); + + /* Destroy the temporary vectors */ + igraph_vector_destroy(&m); + igraph_vector_destroy(&w); + igraph_vector_destroy(&level_membership); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + + +static int igraph_i_compare_communities_vi(const igraph_vector_t *v1, + const igraph_vector_t *v2, igraph_real_t* result); +static int igraph_i_compare_communities_nmi(const igraph_vector_t *v1, + const igraph_vector_t *v2, igraph_real_t* result); +static int igraph_i_compare_communities_rand(const igraph_vector_t *v1, + const igraph_vector_t *v2, igraph_real_t* result, igraph_bool_t adjust); +static int igraph_i_split_join_distance(const igraph_vector_t *v1, + const igraph_vector_t *v2, igraph_integer_t* distance12, + igraph_integer_t* distance21); + +/** + * \ingroup communities + * \function igraph_compare_communities + * \brief Compares community structures using various metrics + * + * This function assesses the distance between two community structures + * using the variation of information (VI) metric of Meila (2003), the + * normalized mutual information (NMI) of Danon et al (2005), the + * split-join distance of van Dongen (2000), the Rand index of Rand (1971) + * or the adjusted Rand index of Hubert and Arabie (1985). + * + * + * References: + * + * + * Meila M: Comparing clusterings by the variation of information. + * In: Schölkopf B, Warmuth MK (eds.). Learning Theory and Kernel Machines: + * 16th Annual Conference on Computational Learning Theory and 7th Kernel + * Workshop, COLT/Kernel 2003, Washington, DC, USA. Lecture Notes in Computer + * Science, vol. 2777, Springer, 2003. ISBN: 978-3-540-40720-1. + * + * + * Danon L, Diaz-Guilera A, Duch J, Arenas A: Comparing community structure + * identification. J Stat Mech P09008, 2005. + * + * + * van Dongen S: Performance criteria for graph clustering and Markov cluster + * experiments. Technical Report INS-R0012, National Research Institute for + * Mathematics and Computer Science in the Netherlands, Amsterdam, May 2000. + * + * + * Rand WM: Objective criteria for the evaluation of clustering methods. + * J Am Stat Assoc 66(336):846-850, 1971. + * + * + * Hubert L and Arabie P: Comparing partitions. Journal of Classification + * 2:193-218, 1985. + * + * \param comm1 the membership vector of the first community structure + * \param comm2 the membership vector of the second community structure + * \param result the result is stored here. + * \param method the comparison method to use. \c IGRAPH_COMMCMP_VI + * selects the variation of information (VI) metric of + * Meila (2003), \c IGRAPH_COMMCMP_NMI selects the + * normalized mutual information measure proposed by + * Danon et al (2005), \c IGRAPH_COMMCMP_SPLIT_JOIN + * selects the split-join distance of van Dongen (2000), + * \c IGRAPH_COMMCMP_RAND selects the unadjusted Rand + * index (1971) and \c IGRAPH_COMMCMP_ADJUSTED_RAND + * selects the adjusted Rand index. + * + * \return Error code. + * + * Time complexity: O(n log(n)). + */ +int igraph_compare_communities(const igraph_vector_t *comm1, + const igraph_vector_t *comm2, igraph_real_t* result, + igraph_community_comparison_t method) { + igraph_vector_t c1, c2; + + if (igraph_vector_size(comm1) != igraph_vector_size(comm2)) { + IGRAPH_ERROR("community membership vectors have different lengths", IGRAPH_EINVAL); + } + + /* Copy and reindex membership vectors to make sure they are continuous */ + IGRAPH_CHECK(igraph_vector_copy(&c1, comm1)); + IGRAPH_FINALLY(igraph_vector_destroy, &c1); + + IGRAPH_CHECK(igraph_vector_copy(&c2, comm2)); + IGRAPH_FINALLY(igraph_vector_destroy, &c2); + + IGRAPH_CHECK(igraph_reindex_membership(&c1, 0, NULL)); + IGRAPH_CHECK(igraph_reindex_membership(&c2, 0, NULL)); + + switch (method) { + case IGRAPH_COMMCMP_VI: + IGRAPH_CHECK(igraph_i_compare_communities_vi(&c1, &c2, result)); + break; + + case IGRAPH_COMMCMP_NMI: + IGRAPH_CHECK(igraph_i_compare_communities_nmi(&c1, &c2, result)); + break; + + case IGRAPH_COMMCMP_SPLIT_JOIN: { + igraph_integer_t d12, d21; + IGRAPH_CHECK(igraph_i_split_join_distance(&c1, &c2, &d12, &d21)); + *result = d12 + d21; + } + break; + + case IGRAPH_COMMCMP_RAND: + case IGRAPH_COMMCMP_ADJUSTED_RAND: + IGRAPH_CHECK(igraph_i_compare_communities_rand(&c1, &c2, result, + method == IGRAPH_COMMCMP_ADJUSTED_RAND)); + break; + + default: + IGRAPH_ERROR("unknown community comparison method", IGRAPH_EINVAL); + } + + /* Clean up everything */ + igraph_vector_destroy(&c1); + igraph_vector_destroy(&c2); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \ingroup communities + * \function igraph_split_join_distance + * \brief Calculates the split-join distance of two community structures + * + * The split-join distance between partitions A and B is the sum of the + * projection distance of A from B and the projection distance of B from + * A. The projection distance is an asymmetric measure and it is defined + * as follows: + * + * + * First, each set in partition A is evaluated against all sets in partition + * B. For each set in partition A, the best matching set in partition B is + * found and the overlap size is calculated. (Matching is quantified by the + * size of the overlap between the two sets). Then, the maximal overlap sizes + * for each set in A are summed together and subtracted from the number of + * elements in A. + * + * + * The split-join distance will be returned in two arguments, \c distance12 + * will contain the projection distance of the first partition from the + * second, while \c distance21 will be the projection distance of the second + * partition from the first. This makes it easier to detect whether a + * partition is a subpartition of the other, since in this case, the + * corresponding distance will be zero. + * + * + * Reference: + * + * + * van Dongen S: Performance criteria for graph clustering and Markov cluster + * experiments. Technical Report INS-R0012, National Research Institute for + * Mathematics and Computer Science in the Netherlands, Amsterdam, May 2000. + * + * \param comm1 the membership vector of the first community structure + * \param comm2 the membership vector of the second community structure + * \param distance12 pointer to an \c igraph_integer_t, the projection distance + * of the first community structure from the second one will be + * returned here. + * \param distance21 pointer to an \c igraph_integer_t, the projection distance + * of the second community structure from the first one will be + * returned here. + * \return Error code. + * + * \see \ref igraph_compare_communities() with the \c IGRAPH_COMMCMP_SPLIT_JOIN + * method if you are not interested in the individual distances but only the sum + * of them. + * + * Time complexity: O(n log(n)). + */ +int igraph_split_join_distance(const igraph_vector_t *comm1, + const igraph_vector_t *comm2, igraph_integer_t *distance12, + igraph_integer_t *distance21) { + igraph_vector_t c1, c2; + + if (igraph_vector_size(comm1) != igraph_vector_size(comm2)) { + IGRAPH_ERROR("community membership vectors have different lengths", IGRAPH_EINVAL); + } + + /* Copy and reindex membership vectors to make sure they are continuous */ + IGRAPH_CHECK(igraph_vector_copy(&c1, comm1)); + IGRAPH_FINALLY(igraph_vector_destroy, &c1); + + IGRAPH_CHECK(igraph_vector_copy(&c2, comm2)); + IGRAPH_FINALLY(igraph_vector_destroy, &c2); + + IGRAPH_CHECK(igraph_reindex_membership(&c1, 0, NULL)); + IGRAPH_CHECK(igraph_reindex_membership(&c2, 0, NULL)); + + IGRAPH_CHECK(igraph_i_split_join_distance(&c1, &c2, distance12, distance21)); + + /* Clean up everything */ + igraph_vector_destroy(&c1); + igraph_vector_destroy(&c2); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * Calculates the entropy and the mutual information for two reindexed community + * membership vectors v1 and v2. This is needed by both Meila's and Danon's + * community comparison measure. + */ +static int igraph_i_entropy_and_mutual_information(const igraph_vector_t* v1, + const igraph_vector_t* v2, double* h1, double* h2, double* mut_inf) { + long int i, n = igraph_vector_size(v1); + long int k1 = (long int)igraph_vector_max(v1) + 1; + long int k2 = (long int)igraph_vector_max(v2) + 1; + double *p1, *p2; + igraph_spmatrix_t m; + igraph_spmatrix_iter_t mit; + + p1 = igraph_Calloc(k1, double); + if (p1 == 0) { + IGRAPH_ERROR("igraph_i_entropy_and_mutual_information failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, p1); + p2 = igraph_Calloc(k2, double); + if (p2 == 0) { + IGRAPH_ERROR("igraph_i_entropy_and_mutual_information failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, p2); + + /* Calculate the entropy of v1 */ + *h1 = 0.0; + for (i = 0; i < n; i++) { + p1[(long int)VECTOR(*v1)[i]]++; + } + for (i = 0; i < k1; i++) { + p1[i] /= n; + *h1 -= p1[i] * log(p1[i]); + } + + /* Calculate the entropy of v2 */ + *h2 = 0.0; + for (i = 0; i < n; i++) { + p2[(long int)VECTOR(*v2)[i]]++; + } + for (i = 0; i < k2; i++) { + p2[i] /= n; + *h2 -= p2[i] * log(p2[i]); + } + + /* We will only need the logs of p1 and p2 from now on */ + for (i = 0; i < k1; i++) { + p1[i] = log(p1[i]); + } + for (i = 0; i < k2; i++) { + p2[i] = log(p2[i]); + } + + /* Calculate the mutual information of v1 and v2 */ + *mut_inf = 0.0; + IGRAPH_CHECK(igraph_spmatrix_init(&m, k1, k2)); + IGRAPH_FINALLY(igraph_spmatrix_destroy, &m); + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_spmatrix_add_e(&m, + (int)VECTOR(*v1)[i], (int)VECTOR(*v2)[i], 1)); + } + IGRAPH_CHECK(igraph_spmatrix_iter_create(&mit, &m)); + IGRAPH_FINALLY(igraph_spmatrix_iter_destroy, &mit); + while (!igraph_spmatrix_iter_end(&mit)) { + double p = mit.value / n; + *mut_inf += p * (log(p) - p1[mit.ri] - p2[mit.ci]); + igraph_spmatrix_iter_next(&mit); + } + + igraph_spmatrix_iter_destroy(&mit); + igraph_spmatrix_destroy(&m); + igraph_Free(p1); igraph_Free(p2); + + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +/** + * Implementation of the normalized mutual information (NMI) measure of + * Danon et al. This function assumes that the community membership + * vectors have already been normalized using igraph_reindex_communities(). + * + * + * Reference: Danon L, Diaz-Guilera A, Duch J, Arenas A: Comparing community + * structure identification. J Stat Mech P09008, 2005. + * + * + * Time complexity: O(n log(n)) + */ +static int igraph_i_compare_communities_nmi(const igraph_vector_t *v1, const igraph_vector_t *v2, + igraph_real_t* result) { + double h1, h2, mut_inf; + + IGRAPH_CHECK(igraph_i_entropy_and_mutual_information(v1, v2, &h1, &h2, &mut_inf)); + + if (h1 == 0 && h2 == 0) { + *result = 1; + } else { + *result = 2 * mut_inf / (h1 + h2); + } + + return IGRAPH_SUCCESS; +} + +/** + * Implementation of the variation of information metric (VI) of + * Meila et al. This function assumes that the community membership + * vectors have already been normalized using igraph_reindex_communities(). + * + * + * Reference: Meila M: Comparing clusterings by the variation of information. + * In: Schölkopf B, Warmuth MK (eds.). Learning Theory and Kernel Machines: + * 16th Annual Conference on Computational Learning Theory and 7th Kernel + * Workshop, COLT/Kernel 2003, Washington, DC, USA. Lecture Notes in Computer + * Science, vol. 2777, Springer, 2003. ISBN: 978-3-540-40720-1. + * + * + * Time complexity: O(n log(n)) + */ +static int igraph_i_compare_communities_vi(const igraph_vector_t *v1, const igraph_vector_t *v2, + igraph_real_t* result) { + double h1, h2, mut_inf; + + IGRAPH_CHECK(igraph_i_entropy_and_mutual_information(v1, v2, &h1, &h2, &mut_inf)); + *result = h1 + h2 - 2 * mut_inf; + + return IGRAPH_SUCCESS; +} + +/** + * \brief Calculates the confusion matrix for two clusterings. + * + * + * This function assumes that the community membership vectors have already + * been normalized using igraph_reindex_communities(). + * + * + * Time complexity: O(n log(max(k1, k2))), where n is the number of vertices, k1 + * and k2 are the number of clusters in each of the clusterings. + */ +static int igraph_i_confusion_matrix(const igraph_vector_t *v1, const igraph_vector_t *v2, + igraph_spmatrix_t *m) { + long int k1 = (long int)igraph_vector_max(v1) + 1; + long int k2 = (long int)igraph_vector_max(v2) + 1; + long int i, n = igraph_vector_size(v1); + + IGRAPH_CHECK(igraph_spmatrix_resize(m, k1, k2)); + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_spmatrix_add_e(m, + (int)VECTOR(*v1)[i], (int)VECTOR(*v2)[i], 1)); + } + + return IGRAPH_SUCCESS; +} + +/** + * Implementation of the split-join distance of van Dongen. + * + * + * This function assumes that the community membership vectors have already + * been normalized using igraph_reindex_communities(). + * + * + * Reference: van Dongen S: Performance criteria for graph clustering and Markov + * cluster experiments. Technical Report INS-R0012, National Research Institute + * for Mathematics and Computer Science in the Netherlands, Amsterdam, May 2000. + * + * + * Time complexity: O(n log(max(k1, k2))), where n is the number of vertices, k1 + * and k2 are the number of clusters in each of the clusterings. + */ +static int igraph_i_split_join_distance(const igraph_vector_t *v1, const igraph_vector_t *v2, + igraph_integer_t* distance12, igraph_integer_t* distance21) { + long int n = igraph_vector_size(v1); + igraph_vector_t rowmax, colmax; + igraph_spmatrix_t m; + igraph_spmatrix_iter_t mit; + + /* Calculate the confusion matrix */ + IGRAPH_CHECK(igraph_spmatrix_init(&m, 1, 1)); + IGRAPH_FINALLY(igraph_spmatrix_destroy, &m); + IGRAPH_CHECK(igraph_i_confusion_matrix(v1, v2, &m)); + + /* Initialize vectors that will store the row/columnwise maxima */ + IGRAPH_VECTOR_INIT_FINALLY(&rowmax, igraph_spmatrix_nrow(&m)); + IGRAPH_VECTOR_INIT_FINALLY(&colmax, igraph_spmatrix_ncol(&m)); + + /* Find the row/columnwise maxima */ + IGRAPH_CHECK(igraph_spmatrix_iter_create(&mit, &m)); + IGRAPH_FINALLY(igraph_spmatrix_iter_destroy, &mit); + while (!igraph_spmatrix_iter_end(&mit)) { + if (mit.value > VECTOR(rowmax)[mit.ri]) { + VECTOR(rowmax)[mit.ri] = mit.value; + } + if (mit.value > VECTOR(colmax)[mit.ci]) { + VECTOR(colmax)[mit.ci] = mit.value; + } + igraph_spmatrix_iter_next(&mit); + } + igraph_spmatrix_iter_destroy(&mit); + IGRAPH_FINALLY_CLEAN(1); + + /* Calculate the distances */ + *distance12 = (igraph_integer_t) (n - igraph_vector_sum(&rowmax)); + *distance21 = (igraph_integer_t) (n - igraph_vector_sum(&colmax)); + + igraph_vector_destroy(&rowmax); + igraph_vector_destroy(&colmax); + igraph_spmatrix_destroy(&m); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * Implementation of the adjusted and unadjusted Rand indices. + * + * + * This function assumes that the community membership vectors have already + * been normalized using igraph_reindex_communities(). + * + * + * References: + * + * + * Rand WM: Objective criteria for the evaluation of clustering methods. J Am + * Stat Assoc 66(336):846-850, 1971. + * + * + * Hubert L and Arabie P: Comparing partitions. Journal of Classification + * 2:193-218, 1985. + * + * + * Time complexity: O(n log(max(k1, k2))), where n is the number of vertices, k1 + * and k2 are the number of clusters in each of the clusterings. + */ +static int igraph_i_compare_communities_rand( + const igraph_vector_t *v1, const igraph_vector_t *v2, + igraph_real_t *result, igraph_bool_t adjust) { + igraph_spmatrix_t m; + igraph_spmatrix_iter_t mit; + igraph_vector_t rowsums, colsums; + long int i, nrow, ncol; + double rand, n; + double frac_pairs_in_1, frac_pairs_in_2; + + /* Calculate the confusion matrix */ + IGRAPH_CHECK(igraph_spmatrix_init(&m, 1, 1)); + IGRAPH_FINALLY(igraph_spmatrix_destroy, &m); + IGRAPH_CHECK(igraph_i_confusion_matrix(v1, v2, &m)); + + /* The unadjusted Rand index is defined as (a+d) / (a+b+c+d), where: + * + * - a is the number of pairs in the same cluster both in v1 and v2. This + * equals the sum of n(i,j) choose 2 for all i and j. + * + * - b is the number of pairs in the same cluster in v1 and in different + * clusters in v2. This is sum n(i,*) choose 2 for all i minus a. + * n(i,*) is the number of elements in cluster i in v1. + * + * - c is the number of pairs in the same cluster in v2 and in different + * clusters in v1. This is sum n(*,j) choose 2 for all j minus a. + * n(*,j) is the number of elements in cluster j in v2. + * + * - d is (n choose 2) - a - b - c. + * + * Therefore, a+d = (n choose 2) - b - c + * = (n choose 2) - sum (n(i,*) choose 2) + * - sum (n(*,j) choose 2) + * + 2 * sum (n(i,j) choose 2). + * + * Since a+b+c+d = (n choose 2) and this goes in the denominator, we can + * just as well start dividing each term in a+d by (n choose 2), which + * yields: + * + * 1 - sum( n(i,*)/n * (n(i,*)-1)/(n-1) ) + * - sum( n(*,i)/n * (n(*,i)-1)/(n-1) ) + * + sum( n(i,j)/n * (n(i,j)-1)/(n-1) ) * 2 + */ + + /* Calculate row and column sums */ + nrow = igraph_spmatrix_nrow(&m); + ncol = igraph_spmatrix_ncol(&m); + n = igraph_vector_size(v1) + 0.0; + IGRAPH_VECTOR_INIT_FINALLY(&rowsums, nrow); + IGRAPH_VECTOR_INIT_FINALLY(&colsums, ncol); + IGRAPH_CHECK(igraph_spmatrix_rowsums(&m, &rowsums)); + IGRAPH_CHECK(igraph_spmatrix_colsums(&m, &colsums)); + + /* Start calculating the unadjusted Rand index */ + rand = 0.0; + IGRAPH_CHECK(igraph_spmatrix_iter_create(&mit, &m)); + IGRAPH_FINALLY(igraph_spmatrix_iter_destroy, &mit); + while (!igraph_spmatrix_iter_end(&mit)) { + rand += (mit.value / n) * (mit.value - 1) / (n - 1); + igraph_spmatrix_iter_next(&mit); + } + igraph_spmatrix_iter_destroy(&mit); + IGRAPH_FINALLY_CLEAN(1); + + frac_pairs_in_1 = frac_pairs_in_2 = 0.0; + for (i = 0; i < nrow; i++) { + frac_pairs_in_1 += (VECTOR(rowsums)[i] / n) * (VECTOR(rowsums)[i] - 1) / (n - 1); + } + for (i = 0; i < ncol; i++) { + frac_pairs_in_2 += (VECTOR(colsums)[i] / n) * (VECTOR(colsums)[i] - 1) / (n - 1); + } + + rand = 1.0 + 2 * rand - frac_pairs_in_1 - frac_pairs_in_2; + + if (adjust) { + double expected = frac_pairs_in_1 * frac_pairs_in_2 + + (1 - frac_pairs_in_1) * (1 - frac_pairs_in_2); + rand = (rand - expected) / (1 - expected); + } + + igraph_vector_destroy(&rowsums); + igraph_vector_destroy(&colsums); + igraph_spmatrix_destroy(&m); + IGRAPH_FINALLY_CLEAN(3); + + *result = rand; + + return IGRAPH_SUCCESS; +} diff --git a/src/community_leiden.c b/src/community_leiden.c new file mode 100644 index 0000000..4296657 --- /dev/null +++ b/src/community_leiden.c @@ -0,0 +1,1086 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_adjlist.h" +#include "igraph_community.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_interrupt_internal.h" +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_stack.h" +#include "igraph_constructors.h" + +/* Move nodes in order to improve the quality of a partition. + * + * This function considers each node and greedily moves it to a neighboring + * community that maximizes the improvement in the quality of a partition. + * + * The nodes are examined in a queue, and initially all nodes are put in the + * queue in a random order. Nodes are popped from the queue when they are + * examined, and only neighbors of nodes that are moved (which are not part of + * the cluster the node was moved to) are pushed to the queue again. + * + * The \c membership vector is used as the starting point to move around nodes, + * and is updated in-place. + * + */ +static int igraph_i_community_leiden_fastmovenodes( + const igraph_t *graph, + const igraph_inclist_t *edges_per_node, + const igraph_vector_t *edge_weights, const igraph_vector_t *node_weights, + const igraph_real_t resolution_parameter, + igraph_integer_t *nb_clusters, + igraph_vector_t *membership) { + + igraph_dqueue_t unstable_nodes; + igraph_real_t max_diff = 0.0, diff = 0.0; + igraph_integer_t n = igraph_vcount(graph); + igraph_vector_bool_t neighbor_cluster_added, node_is_stable; + igraph_vector_t node_order, cluster_weights, edge_weights_per_cluster, neighbor_clusters; + igraph_vector_int_t nb_nodes_per_cluster; + igraph_stack_t empty_clusters; + long int i, j, c, nb_neigh_clusters; + + /* Initialize queue of unstable nodes and whether node is stable. Only + * unstable nodes are in the queue. */ + IGRAPH_CHECK(igraph_vector_bool_init(&node_is_stable, n)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &node_is_stable); + + IGRAPH_CHECK(igraph_dqueue_init(&unstable_nodes, n)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &unstable_nodes); + + /* Shuffle nodes */ + IGRAPH_CHECK(igraph_vector_init_seq(&node_order, 0, n - 1)); + IGRAPH_FINALLY(igraph_vector_destroy, &node_order); + IGRAPH_CHECK(igraph_vector_shuffle(&node_order)); + + /* Add to the queue */ + for (i = 0; i < n; i++) { + igraph_dqueue_push(&unstable_nodes, (long int)VECTOR(node_order)[i]); + } + + /* Initialize cluster weights and nb nodes */ + IGRAPH_CHECK(igraph_vector_init(&cluster_weights, n)); + IGRAPH_FINALLY(igraph_vector_destroy, &cluster_weights); + IGRAPH_CHECK(igraph_vector_int_init(&nb_nodes_per_cluster, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &nb_nodes_per_cluster); + for (i = 0; i < n; i++) { + c = (long int)VECTOR(*membership)[i]; + VECTOR(cluster_weights)[c] += VECTOR(*node_weights)[i]; + VECTOR(nb_nodes_per_cluster)[c] += 1; + } + + /* Initialize empty clusters */ + IGRAPH_CHECK(igraph_stack_init(&empty_clusters, n)); + IGRAPH_FINALLY(igraph_stack_destroy, &empty_clusters); + for (c = 0; c < n; c++) + if (VECTOR(nb_nodes_per_cluster)[c] == 0) { + igraph_stack_push(&empty_clusters, c); + } + + /* Initialize vectors to be used in calculating differences */ + IGRAPH_CHECK(igraph_vector_init(&edge_weights_per_cluster, n)); + IGRAPH_FINALLY(igraph_vector_destroy, &edge_weights_per_cluster); + + /* Initialize neighboring cluster */ + IGRAPH_CHECK(igraph_vector_bool_init(&neighbor_cluster_added, n)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &neighbor_cluster_added); + IGRAPH_CHECK(igraph_vector_init(&neighbor_clusters, n)); + IGRAPH_FINALLY(igraph_vector_destroy, &neighbor_clusters); + + /* Iterate while the queue is not empty */ + j = 0; + while (!igraph_dqueue_empty(&unstable_nodes)) { + long int v = (long int)igraph_dqueue_pop(&unstable_nodes); + long int best_cluster, current_cluster = VECTOR(*membership)[v]; + long int degree, i; + igraph_vector_int_t *edges; + + /* Remove node from current cluster */ + VECTOR(cluster_weights)[current_cluster] -= VECTOR(*node_weights)[v]; + VECTOR(nb_nodes_per_cluster)[current_cluster]--; + if (VECTOR(nb_nodes_per_cluster)[current_cluster] == 0) { + igraph_stack_push(&empty_clusters, current_cluster); + } + + /* Find out neighboring clusters */ + c = (long int)igraph_stack_top(&empty_clusters); + VECTOR(neighbor_clusters)[0] = c; + VECTOR(neighbor_cluster_added)[c] = 1; + nb_neigh_clusters = 1; + + /* Determine the edge weight to each neighboring cluster */ + edges = igraph_inclist_get(edges_per_node, v); + degree = igraph_vector_int_size(edges); + for (i = 0; i < degree; i++) { + long int e = VECTOR(*edges)[i]; + long int u = (long int)IGRAPH_OTHER(graph, e, v); + c = VECTOR(*membership)[u]; + if (!VECTOR(neighbor_cluster_added)[c]) { + VECTOR(neighbor_cluster_added)[c] = 1; + VECTOR(neighbor_clusters)[nb_neigh_clusters++] = c; + } + VECTOR(edge_weights_per_cluster)[c] += VECTOR(*edge_weights)[e]; + } + + /* Calculate maximum diff */ + best_cluster = current_cluster; + max_diff = VECTOR(edge_weights_per_cluster)[current_cluster] - VECTOR(*node_weights)[v] * VECTOR(cluster_weights)[current_cluster] * resolution_parameter; + for (i = 0; i < nb_neigh_clusters; i++) { + c = VECTOR(neighbor_clusters)[i]; + diff = VECTOR(edge_weights_per_cluster)[c] - VECTOR(*node_weights)[v] * VECTOR(cluster_weights)[c] * resolution_parameter; + if (diff > max_diff) { + best_cluster = c; + max_diff = diff; + } + VECTOR(edge_weights_per_cluster)[c] = 0.0; + VECTOR(neighbor_cluster_added)[c] = 0; + } + + /* Move node to best cluster */ + VECTOR(cluster_weights)[best_cluster] += VECTOR(*node_weights)[v]; + VECTOR(nb_nodes_per_cluster)[best_cluster]++; + if (best_cluster == igraph_stack_top(&empty_clusters)) { + igraph_stack_pop(&empty_clusters); + } + + /* Mark node as stable */ + VECTOR(node_is_stable)[v] = 1; + + /* Add stable neighbours that are not part of the new cluster to the queue */ + if (best_cluster != current_cluster) { + VECTOR(*membership)[v] = best_cluster; + + for (i = 0; i < degree; i++) { + long int e = VECTOR(*edges)[i]; + long int u = (long int)IGRAPH_OTHER(graph, e, v); + if (VECTOR(node_is_stable)[u] && VECTOR(*membership)[u] != best_cluster) { + igraph_dqueue_push(&unstable_nodes, u); + VECTOR(node_is_stable)[u] = 0; + } + } + } + + j++; + if (j > 10000) { + IGRAPH_ALLOW_INTERRUPTION(); + j = 0; + } + } + + IGRAPH_CHECK(igraph_reindex_membership(membership, NULL, nb_clusters)); + + igraph_vector_destroy(&neighbor_clusters); + igraph_vector_bool_destroy(&neighbor_cluster_added); + igraph_vector_destroy(&edge_weights_per_cluster); + igraph_stack_destroy(&empty_clusters); + igraph_vector_int_destroy(&nb_nodes_per_cluster); + igraph_vector_destroy(&cluster_weights); + igraph_vector_destroy(&node_order); + igraph_dqueue_destroy(&unstable_nodes); + igraph_vector_bool_destroy(&node_is_stable); + + IGRAPH_FINALLY_CLEAN(9); + + return IGRAPH_SUCCESS; +} + +/* Clean a refined membership vector. + * + * This function examines all nodes in \c node_subset and updates \c + * refined_membership to ensure that the clusters are numbered consecutively, + * starting from \c nb_refined_clusters. The \c nb_refined_clusters is also + * updated itself. If C is the initial \c nb_refined_clusters and C' the + * resulting \c nb_refined_clusters, then nodes in \c node_subset are numbered + * C, C + 1, ..., C' - 1. + */ +static int igraph_i_community_leiden_clean_refined_membership( + const igraph_vector_t* node_subset, + igraph_vector_t *refined_membership, + igraph_integer_t* nb_refined_clusters) { + long int i, n = igraph_vector_size(node_subset); + igraph_vector_t new_cluster; + + IGRAPH_CHECK(igraph_vector_init(&new_cluster, n)); + IGRAPH_FINALLY(igraph_vector_destroy, &new_cluster); + + /* Clean clusters. We will store the new cluster + 1 so that cluster == 0 + * indicates that no membership was assigned yet. */ + *nb_refined_clusters += 1; + for (i = 0; i < n; i++) { + long int v = (long int)VECTOR(*node_subset)[i]; + long int c = (long int)VECTOR(*refined_membership)[v]; + if (VECTOR(new_cluster)[c] == 0) { + VECTOR(new_cluster)[c] = (igraph_real_t)(*nb_refined_clusters); + *nb_refined_clusters += 1; + } + } + + /* Assign new cluster */ + for (i = 0; i < n; i++) { + long int v = (long int)VECTOR(*node_subset)[i]; + long int c = (long int)VECTOR(*refined_membership)[v]; + VECTOR(*refined_membership)[v] = VECTOR(new_cluster)[c] - 1; + } + /* We used the cluster + 1, so correct */ + *nb_refined_clusters -= 1; + + igraph_vector_destroy(&new_cluster); + + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* Merge nodes for a subset of the nodes. This is used to refine a partition. + * + * The nodes included in \c node_subset are assumed to be the nodes i for which + * membership[i] = cluster_subset. + * + * All nodes in \c node_subset are initialized to a singleton partition in \c + * refined_membership. Only singleton clusters can be merged if they are + * sufficiently well connected to the current subgraph induced by \c + * node_subset. + * + * We only examine each node once. Instead of greedily choosing the maximum + * possible cluster to merge with, the cluster is chosen randomly among all + * possibilities that do not decrease the quality of the partition. The + * probability of choosing a certain cluster is proportional to exp(diff/beta). + * For beta to 0 this converges to selecting a cluster with the maximum + * improvement. For beta to infinity this converges to a uniform distribution + * among all eligible clusters. + * + * The \c refined_membership is updated for node in \c node_subset. The number + * of refined clusters, \c nb_refined_clusters is used to set the actual refined + * cluster membership and is updated after this routine. Within each cluster + * (i.e. for a given \c node_subset), the refined membership is initially simply + * set to 0, ..., n - 1 (for n nodes in \c node_subset). However, for each \c + * node_subset the refined membership should of course be unique. Hence, after + * merging, the refined membership starts with \c nb_refined_clusters, which is + * also updated to ensure that the resulting \c nb_refined_clusters counts all + * refined clusters that have already been processed. See + * igraph_i_community_leiden_clean_refined_membership for more information about + * this aspect. + */ +static int igraph_i_community_leiden_mergenodes( + const igraph_t *graph, + const igraph_inclist_t *edges_per_node, + const igraph_vector_t *edge_weights, const igraph_vector_t *node_weights, + const igraph_vector_t *node_subset, + const igraph_vector_t *membership, + const igraph_integer_t cluster_subset, + const igraph_real_t resolution_parameter, + const igraph_real_t beta, + igraph_integer_t *nb_refined_clusters, + igraph_vector_t *refined_membership) { + igraph_vector_t node_order; + igraph_vector_bool_t non_singleton_cluster, neighbor_cluster_added; + igraph_real_t max_diff, total_cum_trans_diff, diff = 0.0, total_node_weight = 0.0; + igraph_integer_t n = igraph_vector_size(node_subset); + igraph_vector_t cluster_weights, cum_trans_diff, edge_weights_per_cluster, external_edge_weight_per_cluster_in_subset, neighbor_clusters; + igraph_vector_int_t *edges, nb_nodes_per_cluster; + long int i, j, degree, nb_neigh_clusters; + + /* Initialize cluster weights */ + IGRAPH_CHECK(igraph_vector_init(&cluster_weights, n)); + IGRAPH_FINALLY(igraph_vector_destroy, &cluster_weights); + + /* Initialize number of nodes per cluster */ + IGRAPH_CHECK(igraph_vector_int_init(&nb_nodes_per_cluster, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &nb_nodes_per_cluster); + + /* Initialize external edge weight per cluster in subset */ + IGRAPH_CHECK(igraph_vector_init(&external_edge_weight_per_cluster_in_subset, n)); + IGRAPH_FINALLY(igraph_vector_destroy, &external_edge_weight_per_cluster_in_subset); + + /* Initialize administration for a singleton partition */ + for (i = 0; i < n; i++) { + long int v = (long int)VECTOR(*node_subset)[i]; + VECTOR(*refined_membership)[v] = i; + VECTOR(cluster_weights)[i] += VECTOR(*node_weights)[v]; + VECTOR(nb_nodes_per_cluster)[i] += 1; + total_node_weight += VECTOR(*node_weights)[v]; + + /* Find out neighboring clusters */ + edges = igraph_inclist_get(edges_per_node, v); + degree = igraph_vector_int_size(edges); + for (j = 0; j < degree; j++) { + long int e = VECTOR(*edges)[j]; + long int u = (long int)IGRAPH_OTHER(graph, e, v); + if (VECTOR(*membership)[u] == cluster_subset) { + VECTOR(external_edge_weight_per_cluster_in_subset)[i] += VECTOR(*edge_weights)[e]; + } + } + } + + /* Shuffle nodes */ + IGRAPH_CHECK(igraph_vector_copy(&node_order, node_subset)); + IGRAPH_FINALLY(igraph_vector_destroy, &node_order); + IGRAPH_CHECK(igraph_vector_shuffle(&node_order)); + + /* Initialize non singleton clusters */ + IGRAPH_CHECK(igraph_vector_bool_init(&non_singleton_cluster, n)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &non_singleton_cluster); + + /* Initialize vectors to be used in calculating differences */ + IGRAPH_CHECK(igraph_vector_init(&edge_weights_per_cluster, n)); + IGRAPH_FINALLY(igraph_vector_destroy, &edge_weights_per_cluster); + + /* Initialize neighboring cluster */ + IGRAPH_CHECK(igraph_vector_bool_init(&neighbor_cluster_added, n)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &neighbor_cluster_added); + IGRAPH_CHECK(igraph_vector_init(&neighbor_clusters, n)); + IGRAPH_FINALLY(igraph_vector_destroy, &neighbor_clusters); + + /* Initialize cumulative transformed difference */ + IGRAPH_CHECK(igraph_vector_init(&cum_trans_diff, n)); + IGRAPH_FINALLY(igraph_vector_destroy, &cum_trans_diff); + + RNG_BEGIN(); + + for (i = 0; i < n; i++) { + long int v = (long int)VECTOR(node_order)[i]; + long int chosen_cluster, best_cluster, current_cluster = (long int)VECTOR(*refined_membership)[v]; + + if (!VECTOR(non_singleton_cluster)[current_cluster] && + (VECTOR(external_edge_weight_per_cluster_in_subset)[current_cluster] >= + VECTOR(cluster_weights)[current_cluster] * (total_node_weight - VECTOR(cluster_weights)[current_cluster]) * resolution_parameter)) { + /* Remove node from current cluster, which is then a singleton by + * definition. */ + VECTOR(cluster_weights)[current_cluster] = 0.0; + VECTOR(nb_nodes_per_cluster)[current_cluster] = 0; + + /* Find out neighboring clusters */ + edges = igraph_inclist_get(edges_per_node, v); + degree = igraph_vector_int_size(edges); + + /* Also add current cluster to ensure it can be chosen. */ + VECTOR(neighbor_clusters)[0] = current_cluster; + VECTOR(neighbor_cluster_added)[current_cluster] = 1; + nb_neigh_clusters = 1; + for (j = 0; j < degree; j++) { + long int e = (long int)VECTOR(*edges)[j]; + long int u = (long int)IGRAPH_OTHER(graph, e, v); + if (VECTOR(*membership)[u] == cluster_subset) { + long int c = VECTOR(*refined_membership)[u]; + if (!VECTOR(neighbor_cluster_added)[c]) { + VECTOR(neighbor_cluster_added)[c] = 1; + VECTOR(neighbor_clusters)[nb_neigh_clusters++] = c; + } + VECTOR(edge_weights_per_cluster)[c] += VECTOR(*edge_weights)[e]; + } + } + + /* Calculate diffs */ + best_cluster = current_cluster; + max_diff = 0.0; + total_cum_trans_diff = 0.0; + for (j = 0; j < nb_neigh_clusters; j++) { + long int c = (long int)VECTOR(neighbor_clusters)[j]; + if (VECTOR(external_edge_weight_per_cluster_in_subset)[c] >= VECTOR(cluster_weights)[c] * (total_node_weight - VECTOR(cluster_weights)[c]) * resolution_parameter) { + diff = VECTOR(edge_weights_per_cluster)[c] - VECTOR(*node_weights)[v] * VECTOR(cluster_weights)[c] * resolution_parameter; + + if (diff > max_diff) { + best_cluster = c; + max_diff = diff; + } + + /* Calculate the transformed difference for sampling */ + if (diff >= 0) { + total_cum_trans_diff += exp(diff / beta); + } + + } + + VECTOR(cum_trans_diff)[j] = total_cum_trans_diff; + VECTOR(edge_weights_per_cluster)[c] = 0.0; + VECTOR(neighbor_cluster_added)[c] = 0; + } + + /* Determine the neighboring cluster to which the currently selected node + * will be moved. + */ + if (total_cum_trans_diff < IGRAPH_INFINITY) { + igraph_real_t r = igraph_rng_get_unif(igraph_rng_default(), 0, total_cum_trans_diff); + long int chosen_idx; + igraph_i_vector_binsearch_slice(&cum_trans_diff, r, &chosen_idx, 0, nb_neigh_clusters); + chosen_cluster = VECTOR(neighbor_clusters)[chosen_idx]; + } else { + chosen_cluster = best_cluster; + } + + /* Move node to randomly chosen cluster */ + VECTOR(cluster_weights)[chosen_cluster] += VECTOR(*node_weights)[v]; + VECTOR(nb_nodes_per_cluster)[chosen_cluster]++; + + for (j = 0; j < degree; j++) { + long int e = (long int)VECTOR(*edges)[j]; + long int u = (long int)IGRAPH_OTHER(graph, e, v); + if (VECTOR(*membership)[u] == cluster_subset) { + if (VECTOR(*refined_membership)[u] == chosen_cluster) { + VECTOR(external_edge_weight_per_cluster_in_subset)[chosen_cluster] -= VECTOR(*edge_weights)[e]; + } else { + VECTOR(external_edge_weight_per_cluster_in_subset)[chosen_cluster] += VECTOR(*edge_weights)[e]; + } + } + } + + /* Set cluster */ + if (chosen_cluster != current_cluster) { + VECTOR(*refined_membership)[v] = chosen_cluster; + + VECTOR(non_singleton_cluster)[chosen_cluster] = 1; + } + } /* end if singleton and may be merged */ + } + + RNG_END(); + + IGRAPH_CHECK(igraph_i_community_leiden_clean_refined_membership(node_subset, refined_membership, nb_refined_clusters)); + + igraph_vector_destroy(&cum_trans_diff); + igraph_vector_destroy(&neighbor_clusters); + igraph_vector_bool_destroy(&neighbor_cluster_added); + igraph_vector_destroy(&edge_weights_per_cluster); + igraph_vector_bool_destroy(&non_singleton_cluster); + igraph_vector_destroy(&node_order); + igraph_vector_destroy(&external_edge_weight_per_cluster_in_subset); + igraph_vector_int_destroy(&nb_nodes_per_cluster); + igraph_vector_destroy(&cluster_weights); + + IGRAPH_FINALLY_CLEAN(9); + + return IGRAPH_SUCCESS; +} + +/* Create clusters out of a membership vector. + * + * The cluster pointer vector should be initialized for all entries of the + * membership vector, no range checking is performed. If a vector for a cluster + * does not yet exist it will be created and initialized. If a vector for a + * cluster already does exist it will not be emptied on first use. Hence, it + * should be ensured that all clusters are always properly empty (or + * non-existing) before calling this function. + */ +static int igraph_i_community_get_clusters(const igraph_vector_t *membership, igraph_vector_ptr_t *clusters) { + long int i, c, n = igraph_vector_size(membership); + igraph_vector_t *cluster; + for (i = 0; i < n; i++) { + /* Get cluster for node i */ + c = VECTOR(*membership)[i]; + cluster = (igraph_vector_t*)VECTOR(*clusters)[c]; + + /* No cluster vector exists yet, so we create a new one */ + if (!cluster) { + cluster = igraph_Calloc(1, igraph_vector_t); + if (cluster == 0) { + IGRAPH_ERROR("Cannot allocate memory for assigning cluster", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(cluster, 0)); + VECTOR(*clusters)[c] = cluster; + } + + /* Add node i to cluster vector */ + igraph_vector_push_back(cluster, i); + } + + return IGRAPH_SUCCESS; +} + +/* Aggregate the graph based on the \c refined membership while setting the + * membership of each aggregated node according to the \c membership. + * + * Technically speaking we have that + * aggregated_membership[refined_membership[v]] = membership[v] for each node v. + * + * The new aggregated graph is returned in \c aggregated_graph. This graph + * object should not yet be initialized, `igraph_create` is called on it, and + * responsibility for destroying the object lies with the calling method + * + * The remaining results, aggregated_edge_weights, aggregate_node_weights and + * aggregated_membership are all expected to be initialized. + * + */ +static int igraph_i_community_leiden_aggregate( + const igraph_t *graph, const igraph_inclist_t *edges_per_node, const igraph_vector_t *edge_weights, const igraph_vector_t *node_weights, + const igraph_vector_t *membership, const igraph_vector_t *refined_membership, const igraph_integer_t nb_refined_clusters, + igraph_t *aggregated_graph, igraph_vector_t *aggregated_edge_weights, igraph_vector_t *aggregated_node_weights, igraph_vector_t *aggregated_membership) { + igraph_vector_t aggregated_edges, edge_weight_to_cluster; + igraph_vector_ptr_t refined_clusters; + igraph_vector_int_t *incident_edges; + igraph_vector_t neighbor_clusters; + igraph_vector_bool_t neighbor_cluster_added; + long int i, j, c, degree, nb_neigh_clusters; + + /* Get refined clusters */ + IGRAPH_CHECK(igraph_vector_ptr_init(&refined_clusters, nb_refined_clusters)); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&refined_clusters, igraph_vector_destroy); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &refined_clusters); + IGRAPH_CHECK(igraph_i_community_get_clusters(refined_membership, &refined_clusters)); + + /* Initialize new edges */ + IGRAPH_CHECK(igraph_vector_init(&aggregated_edges, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, &aggregated_edges); + + /* We clear the aggregated edge weights, we will push each new edge weight */ + igraph_vector_clear(aggregated_edge_weights); + /* Simply resize the aggregated node weights and membership, they can be set + * directly */ + IGRAPH_CHECK(igraph_vector_resize(aggregated_node_weights, nb_refined_clusters)); + IGRAPH_CHECK(igraph_vector_resize(aggregated_membership, nb_refined_clusters)); + + IGRAPH_CHECK(igraph_vector_init(&edge_weight_to_cluster, nb_refined_clusters)); + IGRAPH_FINALLY(igraph_vector_destroy, &edge_weight_to_cluster); + + /* Initialize neighboring cluster */ + IGRAPH_CHECK(igraph_vector_bool_init(&neighbor_cluster_added, nb_refined_clusters)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &neighbor_cluster_added); + IGRAPH_CHECK(igraph_vector_init(&neighbor_clusters, nb_refined_clusters)); + IGRAPH_FINALLY(igraph_vector_destroy, &neighbor_clusters); + + /* Check per cluster */ + for (c = 0; c < nb_refined_clusters; c++) { + igraph_vector_t* refined_cluster = (igraph_vector_t*)VECTOR(refined_clusters)[c]; + long int n_c = igraph_vector_size(refined_cluster); + long int v = -1; + + /* Calculate the total edge weight to other clusters */ + VECTOR(*aggregated_node_weights)[c] = 0.0; + nb_neigh_clusters = 0; + for (i = 0; i < n_c; i++) { + v = (long int)VECTOR(*refined_cluster)[i]; + incident_edges = igraph_inclist_get(edges_per_node, v); + degree = igraph_vector_int_size(incident_edges); + + for (j = 0; j < degree; j++) { + long int e = VECTOR(*incident_edges)[j]; + long int u = (long int)IGRAPH_OTHER(graph, e, v); + long int c2 = VECTOR(*refined_membership)[u]; + + if (c2 > c) { + if (!VECTOR(neighbor_cluster_added)[c2]) { + VECTOR(neighbor_cluster_added)[c2] = 1; + VECTOR(neighbor_clusters)[nb_neigh_clusters++] = c2; + } + VECTOR(edge_weight_to_cluster)[c2] += VECTOR(*edge_weights)[e]; + } + } + + VECTOR(*aggregated_node_weights)[c] += VECTOR(*node_weights)[v]; + } + + /* Add actual edges from this cluster to the other clusters */ + for (i = 0; i < nb_neigh_clusters; i++) { + long int c2 = VECTOR(neighbor_clusters)[i]; + + /* Add edge */ + igraph_vector_push_back(&aggregated_edges, c); igraph_vector_push_back(&aggregated_edges, c2); + + /* Add edge weight */ + igraph_vector_push_back(aggregated_edge_weights, VECTOR(edge_weight_to_cluster)[c2]); + + VECTOR(edge_weight_to_cluster)[c2] = 0.0; + VECTOR(neighbor_cluster_added)[c2] = 0; + } + + VECTOR(*aggregated_membership)[c] = VECTOR(*membership)[v]; + + } + + IGRAPH_CHECK(igraph_create(aggregated_graph, &aggregated_edges, nb_refined_clusters, + IGRAPH_UNDIRECTED)); + + igraph_vector_destroy(&neighbor_clusters); + igraph_vector_bool_destroy(&neighbor_cluster_added); + igraph_vector_destroy(&edge_weight_to_cluster); + igraph_vector_destroy(&aggregated_edges); + igraph_vector_ptr_destroy_all(&refined_clusters); + + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} + +/* Calculate the quality of the partition. + * + * The quality is defined as + * + * 1 / 2m sum_ij (A_ij - gamma n_i n_j)d(s_i, s_j) + * + * where m is the total edge weight, A_ij is the weight of edge (i, j), gamma is + * the so-called resolution parameter, n_i is the node weight of node i, s_i is + * the cluster of node i and d(x, y) = 1 if and only if x = y and 0 otherwise. + * + * Note that by setting n_i = k_i the degree of node i and dividing gamma by 2m, + * we effectively optimize modularity. By setting n_i = 1 we optimize the + * Constant Potts Model. + * + * This can be represented as a sum over clusters as + * + * 1 / 2m sum_c (e_c - gamma N_c^2) + * + * where e_c = sum_ij A_ij d(s_i, c)d(s_j, c) is (twice) the internal edge + * weight in cluster c and N_c = sum_i n_i d(s_i, c) is the sum of the node + * weights inside cluster c. This is how the quality is calculated in practice. + * + */ +static int igraph_i_community_leiden_quality( + const igraph_t *graph, const igraph_vector_t *edge_weights, const igraph_vector_t *node_weights, + const igraph_vector_t *membership, const igraph_integer_t nb_comms, const igraph_real_t resolution_parameter, + igraph_real_t *quality) { + igraph_vector_t cluster_weights; + igraph_real_t total_edge_weight = 0.0; + igraph_eit_t eit; + long int i, c, n = igraph_vcount(graph);; + + *quality = 0.0; + + /* Create the edgelist */ + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + i = 0; + while (!IGRAPH_EIT_END(eit)) { + igraph_integer_t e = IGRAPH_EIT_GET(eit), from, to; + IGRAPH_CHECK(igraph_edge(graph, e, &from, &to)); + total_edge_weight += VECTOR(*edge_weights)[e]; + /* We add the internal edge weights */ + if (VECTOR(*membership)[(long int) from] == VECTOR(*membership)[(long int) to]) { + *quality += 2 * VECTOR(*edge_weights)[e]; + } + IGRAPH_EIT_NEXT(eit); + } + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + + /* Initialize cluster weights and nb nodes */ + IGRAPH_CHECK(igraph_vector_init(&cluster_weights, n)); + IGRAPH_FINALLY(igraph_vector_destroy, &cluster_weights); + for (i = 0; i < n; i++) { + c = VECTOR(*membership)[i]; + VECTOR(cluster_weights)[c] += VECTOR(*node_weights)[i]; + } + + /* We subtract gamma * N_c^2 */ + for (c = 0; c < nb_comms; c++) { + *quality -= resolution_parameter * VECTOR(cluster_weights)[c] * VECTOR(cluster_weights)[c]; + } + + igraph_vector_destroy(&cluster_weights); + IGRAPH_FINALLY_CLEAN(1); + + /* We normalise by 2m */ + *quality /= (2.0 * total_edge_weight); + + return IGRAPH_SUCCESS; +} + +/* This is the core of the Leiden algorithm and relies on subroutines to + * perform the three different phases: (1) local moving of nodes, (2) + * refinement of the partition and (3) aggregation of the network based on the + * refined partition, using the non-refined partition to create an initial + * partition for the aggregate network. + */ +int igraph_i_community_leiden(const igraph_t *graph, + igraph_vector_t *edge_weights, igraph_vector_t *node_weights, + const igraph_real_t resolution_parameter, const igraph_real_t beta, + igraph_vector_t *membership, igraph_integer_t *nb_clusters, igraph_real_t *quality) { + igraph_integer_t nb_refined_clusters; + long int i, c, n = igraph_vcount(graph); + igraph_t *aggregated_graph, *tmp_graph; + igraph_vector_t *aggregated_edge_weights, *aggregated_node_weights, *aggregated_membership; + igraph_vector_t tmp_edge_weights, tmp_node_weights, tmp_membership; + igraph_vector_t refined_membership; + igraph_vector_int_t aggregate_node; + igraph_vector_ptr_t clusters; + igraph_inclist_t edges_per_node; + igraph_bool_t continue_clustering; + igraph_integer_t level = 0; + + /* Initialize temporary weights and membership to be used in aggregation */ + IGRAPH_CHECK(igraph_vector_init(&tmp_edge_weights, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, &tmp_edge_weights); + IGRAPH_CHECK(igraph_vector_init(&tmp_node_weights, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, &tmp_node_weights); + IGRAPH_CHECK(igraph_vector_init(&tmp_membership, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, &tmp_membership); + + /* Initialize clusters */ + IGRAPH_CHECK(igraph_vector_ptr_init(&clusters, n)); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&clusters, igraph_vector_destroy); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &clusters); + /* Initialize aggregate nodes, which initially is identical to simply the + * nodes in the graph. */ + IGRAPH_CHECK(igraph_vector_int_init(&aggregate_node, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &aggregate_node); + for (i = 0; i < n; i++) { + VECTOR(aggregate_node)[i] = i; + } + + IGRAPH_CHECK(igraph_vector_init(&refined_membership, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, &refined_membership); + + /* Initialize aggregated graph, weights and membership. */ + aggregated_graph = (igraph_t*)graph; + aggregated_edge_weights = edge_weights; + aggregated_node_weights = node_weights; + aggregated_membership = membership; + + /* Clean membership and count number of *clusters */ + IGRAPH_CHECK(igraph_reindex_membership(aggregated_membership, NULL, nb_clusters)); + + if (*nb_clusters > n) { + IGRAPH_ERROR("Too many communities in membership vector", IGRAPH_EINVAL); + } + + do { + + /* Get incidence list for fast iteration */ + IGRAPH_CHECK(igraph_inclist_init(aggregated_graph, &edges_per_node, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &edges_per_node); + + /* Move around the nodes in order to increase the quality */ + IGRAPH_CHECK(igraph_i_community_leiden_fastmovenodes(aggregated_graph, + &edges_per_node, + aggregated_edge_weights, aggregated_node_weights, + resolution_parameter, + nb_clusters, + aggregated_membership)); + + /* We only continue clustering if not all clusters are represented by a + * single node yet + */ + continue_clustering = (*nb_clusters < igraph_vcount(aggregated_graph)); + + if (continue_clustering) { + /* Set original membership */ + if (level > 0) { + for (i = 0; i < n; i++) { + long int v_aggregate = VECTOR(aggregate_node)[i]; + VECTOR(*membership)[i] = VECTOR(*aggregated_membership)[v_aggregate]; + } + } + + /* Get node sets for each cluster. */ + IGRAPH_CHECK(igraph_i_community_get_clusters(aggregated_membership, &clusters)); + + /* Ensure refined membership is correct size */ + IGRAPH_CHECK(igraph_vector_resize(&refined_membership, igraph_vcount(aggregated_graph))); + + /* Refine each cluster */ + nb_refined_clusters = 0; + for (c = 0; c < *nb_clusters; c++) { + igraph_vector_t* cluster = (igraph_vector_t*)VECTOR(clusters)[c]; + IGRAPH_CHECK(igraph_i_community_leiden_mergenodes(aggregated_graph, + &edges_per_node, + aggregated_edge_weights, aggregated_node_weights, + cluster, aggregated_membership, c, + resolution_parameter, beta, + &nb_refined_clusters, &refined_membership)); + /* Empty cluster */ + igraph_vector_clear(cluster); + } + + /* If refinement didn't aggregate anything, we aggregate on the basis of + * the actual clustering */ + if (nb_refined_clusters >= igraph_vcount(aggregated_graph)) { + igraph_vector_update(&refined_membership, aggregated_membership); + nb_refined_clusters = *nb_clusters; + } + + /* Keep track of aggregate node. */ + for (i = 0; i < n; i++) { + /* Current aggregate node */ + igraph_integer_t v_aggregate = VECTOR(aggregate_node)[i]; + /* New aggregate node */ + VECTOR(aggregate_node)[i] = (igraph_integer_t)VECTOR(refined_membership)[v_aggregate]; + } + + /* Allocate temporary graph */ + tmp_graph = igraph_Calloc(1, igraph_t); + if (tmp_graph == 0) { + IGRAPH_ERROR("Leiden algorithm failed, could not allocate memory for aggregate graph", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmp_graph); + + IGRAPH_CHECK(igraph_i_community_leiden_aggregate( + aggregated_graph, &edges_per_node, aggregated_edge_weights, aggregated_node_weights, + aggregated_membership, &refined_membership, nb_refined_clusters, + tmp_graph, &tmp_edge_weights, &tmp_node_weights, &tmp_membership)); + + /* Graph has been created by aggregation, ensure it is properly destroyed if + * an error occurs. */ + IGRAPH_FINALLY(igraph_destroy, tmp_graph); + + if (level >= 1) { + /* Destroy previously allocated graph (note that aggregated_graph points to + * the previously allocated tmp_graph). */ + igraph_destroy(aggregated_graph); + igraph_Free(aggregated_graph); + IGRAPH_FINALLY_CLEAN(2); + } + + /* On the lowest level, the actual graph and node and edge weights and + * membership are used. On higher levels, we will have to use a new graph + * and node and edge weights to represent them. We perform the allocation + * of memory here. We only allocate the memory once, and simply update + * them in any subsequent rounds. + */ + if (level == 0) { + aggregated_edge_weights = igraph_Calloc(1, igraph_vector_t); + if (aggregated_edge_weights == 0) { + IGRAPH_ERROR("Leiden algorithm failed, could not allocate memory for aggregate edge weights", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, aggregated_edge_weights); + IGRAPH_CHECK(igraph_vector_init(aggregated_edge_weights, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, aggregated_edge_weights); + + aggregated_node_weights = igraph_Calloc(1, igraph_vector_t); + if (aggregated_node_weights == 0) { + IGRAPH_ERROR("Leiden algorithm failed, could not allocate memory for aggregate node weights", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, aggregated_node_weights); + IGRAPH_CHECK(igraph_vector_init(aggregated_node_weights, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, aggregated_node_weights); + + aggregated_membership = igraph_Calloc(1, igraph_vector_t); + if (aggregated_membership == 0) { + IGRAPH_ERROR("Leiden algorithm failed, could not allocate memory for aggregate membership", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, aggregated_membership); + IGRAPH_CHECK(igraph_vector_init(aggregated_membership, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, aggregated_membership); + } + + /* Set the aggregated graph correctly */ + aggregated_graph = tmp_graph; + + /* Update the aggregated administration. This does not allocate memory, + * it will always fit in existing memory allocated previously. */ + igraph_vector_update(aggregated_edge_weights, &tmp_edge_weights); + igraph_vector_update(aggregated_node_weights, &tmp_node_weights); + igraph_vector_update(aggregated_membership, &tmp_membership); + + level += 1; + } + + /* We are done iterating, so we destroy the incidence list */ + igraph_inclist_destroy(&edges_per_node); + IGRAPH_FINALLY_CLEAN(1); + } while (continue_clustering); + + /* If memory was allocated to represent the aggregated administration we need + * to make sure it is properly freed. This is only done if we have at least + * passed on to the next level of aggregation. + */ + if (level > 0) { + igraph_destroy(aggregated_graph); + igraph_Free(aggregated_graph); + igraph_vector_destroy(aggregated_membership); + igraph_Free(aggregated_membership); + igraph_vector_destroy(aggregated_node_weights); + igraph_Free(aggregated_node_weights); + igraph_vector_destroy(aggregated_edge_weights); + igraph_Free(aggregated_edge_weights); + IGRAPH_FINALLY_CLEAN(8); + } + + /* Free remaining memory */ + igraph_vector_destroy(&refined_membership); + igraph_vector_int_destroy(&aggregate_node); + igraph_vector_ptr_destroy_all(&clusters); + igraph_vector_destroy(&tmp_membership); + igraph_vector_destroy(&tmp_node_weights); + igraph_vector_destroy(&tmp_edge_weights); + IGRAPH_FINALLY_CLEAN(6); + + /* Calculate quality */ + if (quality) { + igraph_i_community_leiden_quality(graph, edge_weights, node_weights, membership, *nb_clusters, resolution_parameter, quality); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup communities + * \function igraph_community_leiden + * \brief Finding community structure using the Leiden algorithm. + * + * This function implements the Leiden algorithm for finding community + * structure, see Traag, V. A., Waltman, L., & van Eck, N. J. (2019). From + * Louvain to Leiden: guaranteeing well-connected communities. Scientific + * reports, 9(1), 5233. http://dx.doi.org/10.1038/s41598-019-41695-z + * + * + * It is similar to the multilevel algorithm, often called the Louvain + * algorithm, but it is faster and yields higher quality solutions. It can + * optimize both modularity and the Constant Potts Model, which does not suffer + * from the resolution-limit (see preprint http://arxiv.org/abs/1104.3083). + * + * + * The Leiden algorithm consists of three phases: (1) local moving of nodes, + * (2) refinement of the partition and (3) aggregation of the network based on + * the refined partition, using the non-refined partition to create an initial + * partition for the aggregate network. In the local move procedure in the + * Leiden algorithm, only nodes whose neighborhood has changed are visited. The + * refinement is done by restarting from a singleton partition within each + * cluster and gradually merging the subclusters. When aggregating, a single + * cluster may then be represented by several nodes (which are the subclusters + * identified in the refinement). + * + * + * The Leiden algorithm provides several guarantees. The Leiden algorithm is + * typically iterated: the output of one iteration is used as the input for the + * next iteration. At each iteration all clusters are guaranteed to be + * connected and well-separated. After an iteration in which nothing has + * changed, all nodes and some parts are guaranteed to be locally optimally + * assigned. Finally, asymptotically, all subsets of all clusters are + * guaranteed to be locally optimally assigned. For more details, please see + * Traag, Waltman & van Eck (2019). + * + * + * The objective function being optimized is + * + * + * 1 / 2m sum_ij (A_ij - gamma n_i n_j)d(s_i, s_j) + * + * + * where m is the total edge weight, A_ij is the weight of edge (i, j), gamma is + * the so-called resolution parameter, n_i is the node weight of node i, s_i is + * the cluster of node i and d(x, y) = 1 if and only if x = y and 0 otherwise. + * By setting n_i = k_i, the degree of node i, and dividing gamma by 2m, you + * effectively obtain an expression for modularity. Hence, the standard + * modularity will be optimized when you supply the degrees as \c node_weights + * and by supplying as a resolution parameter 1.0/(2*m), with m the number of + * edges. + * + * \param graph The input graph. It must be an undirected graph. + * \param edge_weights Numeric vector containing edge weights. If \c NULL, every edge + * has equal weight of 1. The weights need not be non-negative. + * \param node_weights Numeric vector containing node weights. + * \param resolution_parameter The resolution parameter used, which is + * represented by gamma in the objective function mentioned in the + * documentation. + * \param beta The randomness used in the refinement step when merging. A small + * amount of randomness (\c beta = 0.01) typically works well. + * \param start Start from membership vector. If this is true, the optimization + * will start from the provided membership vector. If this is false, the + * optimization will start from a singleton partition. + * \param membership The membership vector. This is both used as the initial + * membership from which optimisation starts and is updated in place. It + * must hence be properly initialized. When finding clusters from scratch it + * is typically started using a singleton clustering. This can be achieved + * using \c igraph_vector_init_seq. + * \param nb_clusters The number of clusters contained in \c membership. Must + * not be a \c NULL pointer. + * \param quality The quality of the partition, in terms of the objective + * function as included in the documentation. If \c NULL the quality will + * not be calculated. + * \return Error code. + * + * Time complexity: near linear on sparse graphs. + * + * \example examples/simple/igraph_community_leiden.c + */ +int igraph_community_leiden(const igraph_t *graph, + const igraph_vector_t *edge_weights, const igraph_vector_t *node_weights, + const igraph_real_t resolution_parameter, const igraph_real_t beta, const igraph_bool_t start, + igraph_vector_t *membership, igraph_integer_t *nb_clusters, igraph_real_t *quality) { + igraph_vector_t *i_edge_weights, *i_node_weights; + int ret; + igraph_integer_t n = igraph_vcount(graph); + + if (start) { + if (!membership) { + IGRAPH_ERROR("Cannot start optimization if membership is missing", IGRAPH_EINVAL); + } + + if (igraph_vector_size(membership) != n) { + IGRAPH_ERROR("Initial membership length does not equal the number of vertices", IGRAPH_EINVAL); + } + } else { + int i; + if (!membership) + IGRAPH_ERROR("Membership vector should be supplied and initialized, " + "even when not starting optimization from it", IGRAPH_EINVAL); + + igraph_vector_resize(membership, n); + for (i = 0; i < n; i++) { + VECTOR(*membership)[i] = i; + } + } + + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Leiden algorithm is only implemented for undirected graphs", IGRAPH_EINVAL); + } + + /* Check edge weights to possibly use default */ + if (!edge_weights) { + i_edge_weights = igraph_Calloc(1, igraph_vector_t); + if (i_edge_weights == 0) { + IGRAPH_ERROR("Leiden algorithm failed, could not allocate memory for edge weights", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(i_edge_weights, igraph_ecount(graph))); + IGRAPH_FINALLY(igraph_free, i_edge_weights); + IGRAPH_FINALLY(igraph_vector_destroy, i_edge_weights); + igraph_vector_fill(i_edge_weights, 1); + } else { + i_edge_weights = (igraph_vector_t*)edge_weights; + } + + /* Check edge weights to possibly use default */ + if (!node_weights) { + i_node_weights = igraph_Calloc(1, igraph_vector_t); + if (i_node_weights == 0) { + IGRAPH_ERROR("Leiden algorithm failed, could not allocate memory for node weights", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(i_node_weights, n)); + IGRAPH_FINALLY(igraph_free, i_node_weights); + IGRAPH_FINALLY(igraph_vector_destroy, i_node_weights); + igraph_vector_fill(i_node_weights, 1); + } else { + i_node_weights = (igraph_vector_t*)node_weights; + } + + /* Perform actual Leiden algorithm */ + ret = igraph_i_community_leiden(graph, i_edge_weights, i_node_weights, + resolution_parameter, beta, + membership, nb_clusters, quality); + + if (!edge_weights) { + igraph_vector_destroy(i_edge_weights); + igraph_Free(i_edge_weights); + IGRAPH_FINALLY_CLEAN(2); + } + + if (!node_weights) { + igraph_vector_destroy(i_node_weights); + igraph_Free(i_node_weights); + IGRAPH_FINALLY_CLEAN(2); + } + + return ret; +} diff --git a/src/complex.c b/src/complex.c new file mode 100644 index 0000000..23c6275 --- /dev/null +++ b/src/complex.c @@ -0,0 +1,392 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_complex.h" +#include "igraph_math.h" +#include + +/** + * \example igraph_complex.c + */ + +igraph_complex_t igraph_complex(igraph_real_t x, igraph_real_t y) { + igraph_complex_t res; + IGRAPH_REAL(res) = x; + IGRAPH_IMAG(res) = y; + return res; +} + +igraph_complex_t igraph_complex_polar(igraph_real_t r, igraph_real_t theta) { + igraph_complex_t res; + IGRAPH_REAL(res) = r * cos(theta); + IGRAPH_IMAG(res) = r * sin(theta); + return res; +} + +igraph_bool_t igraph_complex_eq_tol(igraph_complex_t z1, + igraph_complex_t z2, + igraph_real_t tol) { + if (fabs(IGRAPH_REAL(z1) - IGRAPH_REAL(z2)) > tol || + fabs(IGRAPH_IMAG(z1) - IGRAPH_IMAG(z2)) > tol) { + return 0; + } + return 1; +} + +igraph_real_t igraph_complex_mod(igraph_complex_t z) { + igraph_real_t x = IGRAPH_REAL(z); + igraph_real_t y = IGRAPH_IMAG(z); + return hypot(x, y); +} + +igraph_real_t igraph_complex_arg(igraph_complex_t z) { + igraph_real_t x = IGRAPH_REAL(z); + igraph_real_t y = IGRAPH_IMAG(z); + if (x == 0.0 && y == 0.0) { + return 0.0; + } + return atan2(y, x); +} + +igraph_complex_t igraph_complex_add(igraph_complex_t z1, + igraph_complex_t z2) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z1) + IGRAPH_REAL(z2); + IGRAPH_IMAG(res) = IGRAPH_IMAG(z1) + IGRAPH_IMAG(z2); + return res; +} + +igraph_complex_t igraph_complex_sub(igraph_complex_t z1, + igraph_complex_t z2) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z1) - IGRAPH_REAL(z2); + IGRAPH_IMAG(res) = IGRAPH_IMAG(z1) - IGRAPH_IMAG(z2); + return res; +} + +igraph_complex_t igraph_complex_mul(igraph_complex_t z1, + igraph_complex_t z2) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z1) * IGRAPH_REAL(z2) - + IGRAPH_IMAG(z1) * IGRAPH_IMAG(z2); + IGRAPH_IMAG(res) = IGRAPH_REAL(z1) * IGRAPH_IMAG(z2) + + IGRAPH_IMAG(z1) * IGRAPH_REAL(z2); + return res; +} + +igraph_complex_t igraph_complex_div(igraph_complex_t z1, + igraph_complex_t z2) { + igraph_complex_t res; + igraph_real_t z1r = IGRAPH_REAL(z1), z1i = IGRAPH_IMAG(z1); + igraph_real_t z2r = IGRAPH_REAL(z2), z2i = IGRAPH_IMAG(z2); + igraph_real_t s = 1.0 / igraph_complex_abs(z2); + igraph_real_t sz2r = s * z2r; + igraph_real_t sz2i = s * z2i; + IGRAPH_REAL(res) = (z1r * sz2r + z1i * sz2i) * s; + IGRAPH_IMAG(res) = (z1i * sz2r - z1r * sz2i) * s; + return res; +} + +igraph_complex_t igraph_complex_add_real(igraph_complex_t z, + igraph_real_t x) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z) + x; + IGRAPH_IMAG(res) = IGRAPH_IMAG(z); + return res; +} + +igraph_complex_t igraph_complex_add_imag(igraph_complex_t z, + igraph_real_t y) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z); + IGRAPH_IMAG(res) = IGRAPH_IMAG(z) + y; + return res; +} + +igraph_complex_t igraph_complex_sub_real(igraph_complex_t z, + igraph_real_t x) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z) - x; + IGRAPH_IMAG(res) = IGRAPH_IMAG(z); + return res; +} + +igraph_complex_t igraph_complex_sub_imag(igraph_complex_t z, + igraph_real_t y) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z); + IGRAPH_IMAG(res) = IGRAPH_IMAG(z) - y; + return res; +} + +igraph_complex_t igraph_complex_mul_real(igraph_complex_t z, + igraph_real_t x) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z) * x; + IGRAPH_IMAG(res) = IGRAPH_IMAG(z) * x; + return res; +} + +igraph_complex_t igraph_complex_mul_imag(igraph_complex_t z, + igraph_real_t y) { + igraph_complex_t res; + IGRAPH_REAL(res) = - IGRAPH_IMAG(z) * y; + IGRAPH_IMAG(res) = IGRAPH_REAL(z) * y; + return res; +} + +igraph_complex_t igraph_complex_div_real(igraph_complex_t z, + igraph_real_t x) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z) / x; + IGRAPH_IMAG(res) = IGRAPH_IMAG(z) / x; + return res; +} + +igraph_complex_t igraph_complex_div_imag(igraph_complex_t z, + igraph_real_t y) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_IMAG(z) / y; + IGRAPH_IMAG(res) = - IGRAPH_REAL(z) / y; + return res; +} + +igraph_complex_t igraph_complex_conj(igraph_complex_t z) { + igraph_complex_t res; + IGRAPH_REAL(res) = IGRAPH_REAL(z); + IGRAPH_IMAG(res) = - IGRAPH_IMAG(z); + return res; +} + +igraph_complex_t igraph_complex_neg(igraph_complex_t z) { + igraph_complex_t res; + IGRAPH_REAL(res) = - IGRAPH_REAL(z); + IGRAPH_IMAG(res) = - IGRAPH_IMAG(z); + return res; +} + +igraph_complex_t igraph_complex_inv(igraph_complex_t z) { + igraph_complex_t res; + igraph_real_t s = 1.0 / igraph_complex_abs(z); + IGRAPH_REAL(res) = (IGRAPH_REAL(z) * s) * s; + IGRAPH_IMAG(res) = - (IGRAPH_IMAG(z) * s) * s; + return res; +} + +igraph_real_t igraph_complex_abs(igraph_complex_t z) { + return hypot(IGRAPH_REAL(z), IGRAPH_IMAG(z)); +} + +igraph_real_t igraph_complex_logabs(igraph_complex_t z) { + igraph_real_t xabs = fabs(IGRAPH_REAL(z)); + igraph_real_t yabs = fabs(IGRAPH_IMAG(z)); + igraph_real_t max, u; + if (xabs >= yabs) { + max = xabs; + u = yabs / xabs; + } else { + max = yabs; + u = xabs / yabs; + } + return log (max) + 0.5 * log1p (u * u); +} + +igraph_complex_t igraph_complex_sqrt(igraph_complex_t z) { + igraph_complex_t res; + + if (IGRAPH_REAL(z) == 0.0 && IGRAPH_IMAG(z) == 0.0) { + IGRAPH_REAL(res) = IGRAPH_IMAG(res) = 0.0; + } else { + igraph_real_t x = fabs (IGRAPH_REAL(z)); + igraph_real_t y = fabs (IGRAPH_IMAG(z)); + igraph_real_t w; + if (x >= y) { + igraph_real_t t = y / x; + w = sqrt (x) * sqrt (0.5 * (1.0 + sqrt (1.0 + t * t))); + } else { + igraph_real_t t = x / y; + w = sqrt (y) * sqrt (0.5 * (t + sqrt (1.0 + t * t))); + } + + if (IGRAPH_REAL(z) >= 0.0) { + igraph_real_t ai = IGRAPH_IMAG(z); + IGRAPH_REAL(res) = w; + IGRAPH_IMAG(res) = ai / (2.0 * w); + } else { + igraph_real_t ai = IGRAPH_IMAG(z); + igraph_real_t vi = (ai >= 0) ? w : -w; + IGRAPH_REAL(res) = ai / (2.0 * vi); + IGRAPH_IMAG(res) = vi; + } + } + + return res; +} + +igraph_complex_t igraph_complex_sqrt_real(igraph_real_t x) { + igraph_complex_t res; + if (x >= 0) { + IGRAPH_REAL(res) = sqrt(x); + IGRAPH_IMAG(res) = 0.0; + } else { + IGRAPH_REAL(res) = 0.0; + IGRAPH_IMAG(res) = sqrt(-x); + } + return res; +} + +igraph_complex_t igraph_complex_exp(igraph_complex_t z) { + igraph_real_t rho = exp(IGRAPH_REAL(z)); + igraph_real_t theta = IGRAPH_IMAG(z); + igraph_complex_t res; + IGRAPH_REAL(res) = rho * cos(theta); + IGRAPH_IMAG(res) = rho * sin(theta); + return res; +} + +igraph_complex_t igraph_complex_pow(igraph_complex_t z1, + igraph_complex_t z2) { + igraph_complex_t res; + + if (IGRAPH_REAL(z1) == 0 && IGRAPH_IMAG(z1) == 0.0) { + if (IGRAPH_REAL(z2) == 0 && IGRAPH_IMAG(z2) == 0.0) { + IGRAPH_REAL(res) = 1.0; + IGRAPH_IMAG(res) = 0.0; + } else { + IGRAPH_REAL(res) = IGRAPH_IMAG(res) = 0.0; + } + } else if (IGRAPH_REAL(z2) == 1.0 && IGRAPH_IMAG(z2) == 0.0) { + IGRAPH_REAL(res) = IGRAPH_REAL(z1); + IGRAPH_IMAG(res) = IGRAPH_IMAG(z1); + } else if (IGRAPH_REAL(z2) == -1.0 && IGRAPH_IMAG(z2) == 0.0) { + res = igraph_complex_inv(z1); + } else { + igraph_real_t logr = igraph_complex_logabs (z1); + igraph_real_t theta = igraph_complex_arg (z1); + igraph_real_t z2r = IGRAPH_REAL(z2), z2i = IGRAPH_IMAG(z2); + igraph_real_t rho = exp (logr * z2r - z2i * theta); + igraph_real_t beta = theta * z2r + z2i * logr; + IGRAPH_REAL(res) = rho * cos(beta); + IGRAPH_IMAG(res) = rho * sin(beta); + } + + return res; +} + +igraph_complex_t igraph_complex_pow_real(igraph_complex_t z, + igraph_real_t x) { + igraph_complex_t res; + if (IGRAPH_REAL(z) == 0.0 && IGRAPH_IMAG(z) == 0.0) { + if (x == 0) { + IGRAPH_REAL(res) = 1.0; + IGRAPH_IMAG(res) = 0.0; + } else { + IGRAPH_REAL(res) = IGRAPH_IMAG(res) = 0.0; + } + } else { + igraph_real_t logr = igraph_complex_logabs(z); + igraph_real_t theta = igraph_complex_arg(z); + igraph_real_t rho = exp (logr * x); + igraph_real_t beta = theta * x; + IGRAPH_REAL(res) = rho * cos(beta); + IGRAPH_IMAG(res) = rho * sin(beta); + } + return res; +} + +igraph_complex_t igraph_complex_log(igraph_complex_t z) { + igraph_complex_t res; + IGRAPH_REAL(res) = igraph_complex_logabs(z); + IGRAPH_IMAG(res) = igraph_complex_arg(z); + return res; +} + +igraph_complex_t igraph_complex_log10(igraph_complex_t z) { + return igraph_complex_mul_real(igraph_complex_log(z), 1 / log(10.0)); +} + +igraph_complex_t igraph_complex_log_b(igraph_complex_t z, + igraph_complex_t b) { + return igraph_complex_div (igraph_complex_log(z), igraph_complex_log(b)); +} + +igraph_complex_t igraph_complex_sin(igraph_complex_t z) { + igraph_real_t zr = IGRAPH_REAL(z); + igraph_real_t zi = IGRAPH_IMAG(z); + igraph_complex_t res; + if (zi == 0.0) { + IGRAPH_REAL(res) = sin(zr); + IGRAPH_IMAG(res) = 0.0; + } else { + IGRAPH_REAL(res) = sin(zr) * cosh(zi); + IGRAPH_IMAG(res) = cos(zr) * sinh(zi); + } + return res; +} + +igraph_complex_t igraph_complex_cos(igraph_complex_t z) { + igraph_real_t zr = IGRAPH_REAL(z); + igraph_real_t zi = IGRAPH_IMAG(z); + igraph_complex_t res; + if (zi == 0.0) { + IGRAPH_REAL(res) = cos(zr); + IGRAPH_IMAG(res) = 0.0; + } else { + IGRAPH_REAL(res) = cos(zr) * cosh(zi); + IGRAPH_IMAG(res) = sin(zr) * sinh(-zi); + } + return res; +} + +igraph_complex_t igraph_complex_tan(igraph_complex_t z) { + igraph_real_t zr = IGRAPH_REAL(z); + igraph_real_t zi = IGRAPH_IMAG(z); + igraph_complex_t res; + if (fabs (zi) < 1) { + igraph_real_t D = pow (cos (zr), 2.0) + pow (sinh (zi), 2.0); + IGRAPH_REAL(res) = 0.5 * sin (2 * zr) / D; + IGRAPH_IMAG(res) = 0.5 * sinh (2 * zi) / D; + } else { + igraph_real_t u = exp (-zi); + igraph_real_t C = 2 * u / (1 - pow (u, 2.0)); + igraph_real_t D = 1 + pow (cos (zr), 2.0) * pow (C, 2.0); + igraph_real_t S = pow (C, 2.0); + igraph_real_t T = 1.0 / tanh (zi); + IGRAPH_REAL(res) = 0.5 * sin (2 * zr) * S / D; + IGRAPH_IMAG(res) = T / D; + } + return res; +} + +igraph_complex_t igraph_complex_sec(igraph_complex_t z) { + return igraph_complex_inv(igraph_complex_cos(z)); +} + +igraph_complex_t igraph_complex_csc(igraph_complex_t z) { + return igraph_complex_inv(igraph_complex_sin(z)); +} + +igraph_complex_t igraph_complex_cot(igraph_complex_t z) { + return igraph_complex_inv(igraph_complex_tan(z)); +} + diff --git a/src/components.c b/src/components.c new file mode 100644 index 0000000..77ae51d --- /dev/null +++ b/src/components.c @@ -0,0 +1,1253 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_components.h" +#include "igraph_memory.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include "igraph_interrupt_internal.h" +#include "igraph_progress.h" +#include "igraph_structural.h" +#include "igraph_dqueue.h" +#include "igraph_stack.h" +#include "igraph_vector.h" +#include "config.h" +#include + +static int igraph_i_clusters_weak(const igraph_t *graph, igraph_vector_t *membership, + igraph_vector_t *csize, igraph_integer_t *no); + +static int igraph_i_clusters_strong(const igraph_t *graph, igraph_vector_t *membership, + igraph_vector_t *csize, igraph_integer_t *no); + +/** + * \ingroup structural + * \function igraph_clusters + * \brief Calculates the (weakly or strongly) connected components in a graph. + * + * \param graph The graph object to analyze. + * \param membership First half of the result will be stored here. For + * every vertex the id of its component is given. The vector + * has to be preinitialized and will be resized. Alternatively + * this argument can be \c NULL, in which case it is ignored. + * \param csize The second half of the result. For every component it + * gives its size, the order is defined by the component ids. + * The vector has to be preinitialized and will be resized. + * Alternatively this argument can be \c NULL, in which + * case it is ignored. + * \param no Pointer to an integer, if not \c NULL then the number of + * clusters will be stored here. + * \param mode For directed graph this specifies whether to calculate + * weakly or strongly connected components. Possible values: + * \c IGRAPH_WEAK, + * \c IGRAPH_STRONG. This argument is + * ignored for undirected graphs. + * \return Error code: + * \c IGRAPH_EINVAL: invalid mode argument. + * + * Time complexity: O(|V|+|E|), + * |V| and + * |E| are the number of vertices and + * edges in the graph. + */ + +int igraph_clusters(const igraph_t *graph, igraph_vector_t *membership, + igraph_vector_t *csize, igraph_integer_t *no, + igraph_connectedness_t mode) { + if (mode == IGRAPH_WEAK || !igraph_is_directed(graph)) { + return igraph_i_clusters_weak(graph, membership, csize, no); + } else if (mode == IGRAPH_STRONG) { + return igraph_i_clusters_strong(graph, membership, csize, no); + } else { + IGRAPH_ERROR("Cannot calculate clusters", IGRAPH_EINVAL); + } + + return 1; +} + +static int igraph_i_clusters_weak(const igraph_t *graph, igraph_vector_t *membership, + igraph_vector_t *csize, igraph_integer_t *no) { + + long int no_of_nodes = igraph_vcount(graph); + char *already_added; + long int first_node, act_cluster_size = 0, no_of_clusters = 1; + + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + + long int i; + igraph_vector_t neis = IGRAPH_VECTOR_NULL; + + already_added = igraph_Calloc(no_of_nodes, char); + if (already_added == 0) { + IGRAPH_ERROR("Cannot calculate clusters", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, already_added); + + IGRAPH_DQUEUE_INIT_FINALLY(&q, no_of_nodes > 100000 ? 10000 : no_of_nodes / 10); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + /* Memory for result, csize is dynamically allocated */ + if (membership) { + IGRAPH_CHECK(igraph_vector_resize(membership, no_of_nodes)); + } + if (csize) { + igraph_vector_clear(csize); + } + + /* The algorithm */ + + for (first_node = 0; first_node < no_of_nodes; ++first_node) { + if (already_added[first_node] == 1) { + continue; + } + IGRAPH_ALLOW_INTERRUPTION(); + + already_added[first_node] = 1; + act_cluster_size = 1; + if (membership) { + VECTOR(*membership)[first_node] = no_of_clusters - 1; + } + IGRAPH_CHECK(igraph_dqueue_push(&q, first_node)); + + while ( !igraph_dqueue_empty(&q) ) { + long int act_node = (long int) igraph_dqueue_pop(&q); + IGRAPH_CHECK(igraph_neighbors(graph, &neis, + (igraph_integer_t) act_node, IGRAPH_ALL)); + for (i = 0; i < igraph_vector_size(&neis); i++) { + long int neighbor = (long int) VECTOR(neis)[i]; + if (already_added[neighbor] == 1) { + continue; + } + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + already_added[neighbor] = 1; + act_cluster_size++; + if (membership) { + VECTOR(*membership)[neighbor] = no_of_clusters - 1; + } + } + } + no_of_clusters++; + if (csize) { + IGRAPH_CHECK(igraph_vector_push_back(csize, act_cluster_size)); + } + } + + /* Cleaning up */ + + if (no) { + *no = (igraph_integer_t) no_of_clusters - 1; + } + + igraph_Free(already_added); + igraph_dqueue_destroy(&q); + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +static int igraph_i_clusters_strong(const igraph_t *graph, igraph_vector_t *membership, + igraph_vector_t *csize, igraph_integer_t *no) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t next_nei = IGRAPH_VECTOR_NULL; + + long int i, n, num_seen; + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + + long int no_of_clusters = 1; + long int act_cluster_size; + + igraph_vector_t out = IGRAPH_VECTOR_NULL; + const igraph_vector_int_t* tmp; + + igraph_adjlist_t adjlist; + + /* The result */ + + IGRAPH_VECTOR_INIT_FINALLY(&next_nei, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&out, 0); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + + if (membership) { + IGRAPH_CHECK(igraph_vector_resize(membership, no_of_nodes)); + } + IGRAPH_CHECK(igraph_vector_reserve(&out, no_of_nodes)); + + igraph_vector_null(&out); + if (csize) { + igraph_vector_clear(csize); + } + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + num_seen = 0; + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + + tmp = igraph_adjlist_get(&adjlist, i); + if (VECTOR(next_nei)[i] > igraph_vector_int_size(tmp)) { + continue; + } + + IGRAPH_CHECK(igraph_dqueue_push(&q, i)); + while (!igraph_dqueue_empty(&q)) { + long int act_node = (long int) igraph_dqueue_back(&q); + tmp = igraph_adjlist_get(&adjlist, act_node); + if (VECTOR(next_nei)[act_node] == 0) { + /* this is the first time we've met this vertex */ + VECTOR(next_nei)[act_node]++; + } else if (VECTOR(next_nei)[act_node] <= igraph_vector_int_size(tmp)) { + /* we've already met this vertex but it has more children */ + long int neighbor = (long int) VECTOR(*tmp)[(long int) + VECTOR(next_nei)[act_node] - 1]; + if (VECTOR(next_nei)[neighbor] == 0) { + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + } + VECTOR(next_nei)[act_node]++; + } else { + /* we've met this vertex and it has no more children */ + IGRAPH_CHECK(igraph_vector_push_back(&out, act_node)); + igraph_dqueue_pop_back(&q); + num_seen++; + + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + } + } /* while q */ + } /* for */ + + IGRAPH_PROGRESS("Strongly connected components: ", 50.0, NULL); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + /* OK, we've the 'out' values for the nodes, let's use them in + decreasing order with the help of a heap */ + + igraph_vector_null(&next_nei); /* mark already added vertices */ + num_seen = 0; + + while (!igraph_vector_empty(&out)) { + long int grandfather = (long int) igraph_vector_pop_back(&out); + + if (VECTOR(next_nei)[grandfather] != 0) { + continue; + } + VECTOR(next_nei)[grandfather] = 1; + act_cluster_size = 1; + if (membership) { + VECTOR(*membership)[grandfather] = no_of_clusters - 1; + } + IGRAPH_CHECK(igraph_dqueue_push(&q, grandfather)); + + num_seen++; + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + 50.0 + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + + while (!igraph_dqueue_empty(&q)) { + long int act_node = (long int) igraph_dqueue_pop_back(&q); + tmp = igraph_adjlist_get(&adjlist, act_node); + n = igraph_vector_int_size(tmp); + for (i = 0; i < n; i++) { + long int neighbor = (long int) VECTOR(*tmp)[i]; + if (VECTOR(next_nei)[neighbor] != 0) { + continue; + } + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + VECTOR(next_nei)[neighbor] = 1; + act_cluster_size++; + if (membership) { + VECTOR(*membership)[neighbor] = no_of_clusters - 1; + } + + num_seen++; + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + 50.0 + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + } + } + + no_of_clusters++; + if (csize) { + IGRAPH_CHECK(igraph_vector_push_back(csize, act_cluster_size)); + } + } + + IGRAPH_PROGRESS("Strongly connected components: ", 100.0, NULL); + + if (no) { + *no = (igraph_integer_t) no_of_clusters - 1; + } + + /* Clean up, return */ + + igraph_adjlist_destroy(&adjlist); + igraph_vector_destroy(&out); + igraph_dqueue_destroy(&q); + igraph_vector_destroy(&next_nei); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +int igraph_is_connected_weak(const igraph_t *graph, igraph_bool_t *res); + +/** + * \ingroup structural + * \function igraph_is_connected + * \brief Decides whether the graph is (weakly or strongly) connected. + * + * A graph with zero vertices (i.e. the null graph) is connected by definition. + * + * \param graph The graph object to analyze. + * \param res Pointer to a logical variable, the result will be stored + * here. + * \param mode For a directed graph this specifies whether to calculate + * weak or strong connectedness. Possible values: + * \c IGRAPH_WEAK, + * \c IGRAPH_STRONG. This argument is + * ignored for undirected graphs. + * \return Error code: + * \c IGRAPH_EINVAL: invalid mode argument. + * + * Time complexity: O(|V|+|E|), the + * number of vertices + * plus the number of edges in the graph. + */ + +int igraph_is_connected(const igraph_t *graph, igraph_bool_t *res, + igraph_connectedness_t mode) { + if (igraph_vcount(graph) == 0) { + *res = 1; + return IGRAPH_SUCCESS; + } + + if (mode == IGRAPH_WEAK || !igraph_is_directed(graph)) { + return igraph_is_connected_weak(graph, res); + } else if (mode == IGRAPH_STRONG) { + int retval; + igraph_integer_t no; + retval = igraph_i_clusters_strong(graph, 0, 0, &no); + *res = (no == 1); + return retval; + } else { + IGRAPH_ERROR("mode argument", IGRAPH_EINVAL); + } + return 0; +} + +/** + * \ingroup structural + * \function igraph_is_connected_weak + * \brief Query whether the graph is weakly connected. + * + * A graph with zero vertices (i.e. the null graph) is weakly connected by + * definition. A directed graph is weakly connected if its undirected version + * is connected. In the case of undirected graphs, weakly connected and + * connected are equivalent. + * + * \param graph The graph object to analyze. + * \param res Pointer to a logical variable; the result will be stored here. + * \return Error code: + * \c IGRAPH_ENOMEM: unable to allocate requested memory. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number of + * edges in the graph. + */ + +int igraph_is_connected_weak(const igraph_t *graph, igraph_bool_t *res) { + + long int no_of_nodes = igraph_vcount(graph); + char *already_added; + igraph_vector_t neis = IGRAPH_VECTOR_NULL; + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + + long int i, j; + + if (no_of_nodes == 0) { + *res = 1; + return IGRAPH_SUCCESS; + } + + already_added = igraph_Calloc(no_of_nodes, char); + if (already_added == 0) { + IGRAPH_ERROR("is connected (weak) failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, already_added); + + IGRAPH_DQUEUE_INIT_FINALLY(&q, 10); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + /* Try to find at least two clusters */ + already_added[0] = 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, 0)); + + j = 1; + while ( !igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) actnode, + IGRAPH_ALL)); + for (i = 0; i < igraph_vector_size(&neis); i++) { + long int neighbor = (long int) VECTOR(neis)[i]; + if (already_added[neighbor] != 0) { + continue; + } + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + j++; + already_added[neighbor]++; + } + } + + /* Connected? */ + *res = (j == no_of_nodes); + + igraph_Free(already_added); + igraph_dqueue_destroy(&q); + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_decompose_destroy + * \brief Free the memory allocated by \ref igraph_decompose(). + * + * \param complist The list of graph components, as returned by + * \ref igraph_decompose(). + * + * Time complexity: O(c), c is the number of components. + */ + +void igraph_decompose_destroy(igraph_vector_ptr_t *complist) { + long int i; + for (i = 0; i < igraph_vector_ptr_size(complist); i++) { + if (VECTOR(*complist)[i] != 0) { + igraph_destroy(VECTOR(*complist)[i]); + igraph_free(VECTOR(*complist)[i]); + } + } +} + +static int igraph_i_decompose_weak(const igraph_t *graph, + igraph_vector_ptr_t *components, + long int maxcompno, long int minelements); + +static int igraph_i_decompose_strong(const igraph_t *graph, + igraph_vector_ptr_t *components, + long int maxcompno, long int minelements); + +/** + * \function igraph_decompose + * \brief Decompose a graph into connected components. + * + * Create separate graph for each component of a graph. Note that the + * vertex ids in the new graphs will be different than in the original + * graph. (Except if there is only one component in the original graph.) + * + * \param graph The original graph. + * \param components This pointer vector will contain pointers to the + * subcomponent graphs. It should be initialized before calling this + * function and will be resized to hold the graphs. Don't forget to + * call \ref igraph_destroy() and free() on the elements of + * this pointer vector to free unneeded memory. Alternatively, you can + * simply call \ref igraph_decompose_destroy() that does this for you. + * \param mode Either \c IGRAPH_WEAK or \c IGRAPH_STRONG for weakly + * and strongly connected components respectively. + * \param maxcompno The maximum number of components to return. The + * first \p maxcompno components will be returned (which hold at + * least \p minelements vertices, see the next parameter), the + * others will be ignored. Supply -1 here if you don't want to limit + * the number of components. + * \param minelements The minimum number of vertices a component + * should contain in order to place it in the \p components + * vector. Eg. supply 2 here to ignore isolated vertices. + * \return Error code, \c IGRAPH_ENOMEM if there is not enough memory + * to perform the operation. + * + * Added in version 0.2. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + * + * \example examples/simple/igraph_decompose.c + */ + +int igraph_decompose(const igraph_t *graph, igraph_vector_ptr_t *components, + igraph_connectedness_t mode, + long int maxcompno, long int minelements) { + if (mode == IGRAPH_WEAK || !igraph_is_directed(graph)) { + return igraph_i_decompose_weak(graph, components, maxcompno, minelements); + } else if (mode == IGRAPH_STRONG) { + return igraph_i_decompose_strong(graph, components, maxcompno, minelements); + } else { + IGRAPH_ERROR("Cannot decompose graph", IGRAPH_EINVAL); + } + + return 1; +} + +static int igraph_i_decompose_weak(const igraph_t *graph, + igraph_vector_ptr_t *components, + long int maxcompno, long int minelements) { + + long int actstart; + long int no_of_nodes = igraph_vcount(graph); + long int resco = 0; /* number of graphs created so far */ + char *already_added; + igraph_dqueue_t q; + igraph_vector_t verts; + igraph_vector_t neis; + long int i; + igraph_t *newg; + + + if (maxcompno < 0) { + maxcompno = LONG_MAX; + } + + igraph_vector_ptr_clear(components); + IGRAPH_FINALLY(igraph_decompose_destroy, components); + + /* already_added keeps track of what nodes made it into a graph already */ + already_added = igraph_Calloc(no_of_nodes, char); + if (already_added == 0) { + IGRAPH_ERROR("Cannot decompose graph", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, already_added); + + IGRAPH_CHECK(igraph_dqueue_init(&q, 100)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &q); + IGRAPH_VECTOR_INIT_FINALLY(&verts, 0); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + /* add a node and its neighbors at once, recursively + then switch to next node that has not been added already */ + for (actstart = 0; resco < maxcompno && actstart < no_of_nodes; actstart++) { + + if (already_added[actstart]) { + continue; + } + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_vector_clear(&verts); + + /* add the node itself */ + already_added[actstart] = 1; + IGRAPH_CHECK(igraph_vector_push_back(&verts, actstart)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actstart)); + + /* add the neighbors, recursively */ + while (!igraph_dqueue_empty(&q) ) { + /* pop from the queue of this component */ + long int actvert = (long int) igraph_dqueue_pop(&q); + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) actvert, + IGRAPH_ALL)); + /* iterate over the neighbors */ + for (i = 0; i < igraph_vector_size(&neis); i++) { + long int neighbor = (long int) VECTOR(neis)[i]; + if (already_added[neighbor] == 1) { + continue; + } + /* add neighbor */ + already_added[neighbor] = 1; + + /* recursion: append neighbor to the queues */ + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + IGRAPH_CHECK(igraph_vector_push_back(&verts, neighbor)); + } + } + + /* ok, we have a component */ + if (igraph_vector_size(&verts) < minelements) { + continue; + } + + newg = igraph_Calloc(1, igraph_t); + if (newg == 0) { + IGRAPH_ERROR("Cannot decompose graph", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_ptr_push_back(components, newg)); + IGRAPH_CHECK(igraph_induced_subgraph(graph, newg, + igraph_vss_vector(&verts), + IGRAPH_SUBGRAPH_AUTO)); + resco++; + + } /* for actstart++ */ + + igraph_vector_destroy(&neis); + igraph_vector_destroy(&verts); + igraph_dqueue_destroy(&q); + igraph_Free(already_added); + IGRAPH_FINALLY_CLEAN(5); /* + components */ + + return 0; +} + +static int igraph_i_decompose_strong(const igraph_t *graph, + igraph_vector_ptr_t *components, + long int maxcompno, long int minelements) { + + + long int no_of_nodes = igraph_vcount(graph); + + /* this is a heap used twice for checking what nodes have + * been counted already */ + igraph_vector_t next_nei = IGRAPH_VECTOR_NULL; + + long int i, n, num_seen; + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + + long int no_of_clusters = 1; + long int act_cluster_size; + + igraph_vector_t out = IGRAPH_VECTOR_NULL; + const igraph_vector_int_t* tmp; + + igraph_adjlist_t adjlist; + igraph_vector_t verts; + igraph_t *newg; + + igraph_vector_ptr_clear(components); + IGRAPH_FINALLY(igraph_decompose_destroy, components); + + /* The result */ + + IGRAPH_VECTOR_INIT_FINALLY(&verts, 0); + IGRAPH_VECTOR_INIT_FINALLY(&next_nei, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&out, 0); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_vector_reserve(&out, no_of_nodes)); + + igraph_vector_null(&out); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + /* number of components seen */ + num_seen = 0; + /* populate the 'out' vector by browsing a node and following up + all its neighbors recursively, then switching to the next + unassigned node */ + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + + /* get all the 'out' neighbors of this node + * NOTE: next_nei is initialized [0, 0, ...] */ + tmp = igraph_adjlist_get(&adjlist, i); + if (VECTOR(next_nei)[i] > igraph_vector_int_size(tmp)) { + continue; + } + + /* add this node to the queue for this component */ + IGRAPH_CHECK(igraph_dqueue_push(&q, i)); + + /* consume the tree from this node ("root") recursively + * until there is no more */ + while (!igraph_dqueue_empty(&q)) { + /* this looks up but does NOT consume the queue */ + long int act_node = (long int) igraph_dqueue_back(&q); + + /* get all neighbors of this node */ + tmp = igraph_adjlist_get(&adjlist, act_node); + if (VECTOR(next_nei)[act_node] == 0) { + /* this is the first time we've met this vertex, + * because next_nei is initialized [0, 0, ...] */ + VECTOR(next_nei)[act_node]++; + /* back to the queue, same vertex is up again */ + + } else if (VECTOR(next_nei)[act_node] <= igraph_vector_int_size(tmp)) { + /* we've already met this vertex but it has more children */ + long int neighbor = (long int) VECTOR(*tmp)[(long int) + VECTOR(next_nei)[act_node] - 1]; + if (VECTOR(next_nei)[neighbor] == 0) { + /* add the root of the other children to the queue */ + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + } + VECTOR(next_nei)[act_node]++; + } else { + /* we've met this vertex and it has no more children */ + IGRAPH_CHECK(igraph_vector_push_back(&out, act_node)); + /* this consumes the queue, since there's nowhere to go */ + igraph_dqueue_pop_back(&q); + num_seen++; + + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + } + } /* while q */ + } /* for */ + + IGRAPH_PROGRESS("Strongly connected components: ", 50.0, NULL); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + /* OK, we've the 'out' values for the nodes, let's use them in + * decreasing order with the help of the next_nei heap */ + + igraph_vector_null(&next_nei); /* mark already added vertices */ + + /* number of components built */ + num_seen = 0; + while (!igraph_vector_empty(&out)) { + /* consume the vector from the last element */ + long int grandfather = (long int) igraph_vector_pop_back(&out); + + /* been here, done that + * NOTE: next_nei is initialized as [0, 0, ...] */ + if (VECTOR(next_nei)[grandfather] != 0) { + continue; + } + + /* collect all the members of this component */ + igraph_vector_clear(&verts); + + /* this node is gone for any future components */ + VECTOR(next_nei)[grandfather] = 1; + act_cluster_size = 1; + + /* add to component */ + IGRAPH_CHECK(igraph_vector_push_back(&verts, grandfather)); + IGRAPH_CHECK(igraph_dqueue_push(&q, grandfather)); + + num_seen++; + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + 50.0 + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + + while (!igraph_dqueue_empty(&q)) { + /* consume the queue from this node */ + long int act_node = (long int) igraph_dqueue_pop_back(&q); + tmp = igraph_adjlist_get(&adjlist, act_node); + n = igraph_vector_int_size(tmp); + for (i = 0; i < n; i++) { + long int neighbor = (long int) VECTOR(*tmp)[i]; + if (VECTOR(next_nei)[neighbor] != 0) { + continue; + } + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + VECTOR(next_nei)[neighbor] = 1; + act_cluster_size++; + + /* add to component */ + IGRAPH_CHECK(igraph_vector_push_back(&verts, neighbor)); + + num_seen++; + if (num_seen % 10000 == 0) { + /* time to report progress and allow the user to interrupt */ + IGRAPH_PROGRESS("Strongly connected components: ", + 50.0 + num_seen * 50.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + } + } + + /* ok, we have a component */ + if (igraph_vector_size(&verts) < minelements) { + continue; + } + + newg = igraph_Calloc(1, igraph_t); + if (newg == 0) { + IGRAPH_ERROR("Cannot decompose graph", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_ptr_push_back(components, newg)); + IGRAPH_CHECK(igraph_induced_subgraph(graph, newg, + igraph_vss_vector(&verts), + IGRAPH_SUBGRAPH_AUTO)); + + no_of_clusters++; + } + + IGRAPH_PROGRESS("Strongly connected components: ", 100.0, NULL); + + /* Clean up, return */ + + igraph_vector_destroy(&verts); + igraph_adjlist_destroy(&adjlist); + igraph_vector_destroy(&out); + igraph_dqueue_destroy(&q); + igraph_vector_destroy(&next_nei); + IGRAPH_FINALLY_CLEAN(6); /* + components */ + + return 0; + +} + +/** + * \function igraph_articulation_points + * Find the articulation points in a graph. + * + * A vertex is an articulation point if its removal increases + * the number of connected components in the graph. + * \param graph The input graph. + * \param res Pointer to an initialized vector, the + * articulation points will be stored here. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and edges. + * + * \sa \ref igraph_biconnected_components(), \ref igraph_clusters(), \ref igraph_bridges() + */ + +int igraph_articulation_points(const igraph_t *graph, + igraph_vector_t *res) { + + igraph_integer_t no; + return igraph_biconnected_components(graph, &no, 0, 0, 0, res); +} + +void igraph_i_free_vectorlist(igraph_vector_ptr_t *list); + +void igraph_i_free_vectorlist(igraph_vector_ptr_t *list) { + long int i, n = igraph_vector_ptr_size(list); + for (i = 0; i < n; i++) { + igraph_vector_t *v = VECTOR(*list)[i]; + if (v) { + igraph_vector_destroy(v); + igraph_Free(v); + } + } + igraph_vector_ptr_destroy(list); +} + +/** + * \function igraph_biconnected_components + * Calculate biconnected components + * + * A graph is biconnected if the removal of any single vertex (and + * its incident edges) does not disconnect it. + * + * + * A biconnected component of a graph is a maximal biconnected + * subgraph of it. The biconnected components of a graph can be given + * by the partition of its edges: every edge is a member of exactly + * one biconnected component. Note that this is not true for + * vertices: the same vertex can be part of many biconnected + * components. + * + * + * Somewhat arbitrarily, igraph does not consider comppnents containing + * a single vertex only as being biconnected. Isolated vertices will + * not be part of any of the biconnected components. + * + * \param graph The input graph. + * \param no The number of biconnected components will be stored here. + * \param tree_edges If not a NULL pointer, then the found components + * are stored here, in a list of vectors. Every vector in the list + * is a biconnected component, represented by its edges. More precisely, + * a spanning tree of the biconnected component is returned. + * Note you'll have to + * destroy each vector first by calling \ref igraph_vector_destroy() + * and then \ref igraph_free() on it, plus you need to call + * \ref igraph_vector_ptr_destroy() on the list to regain all + * allocated memory. + * \param component_edges If not a NULL pointer, then the edges of the + * biconnected components are stored here, in the same form as for + * \c tree_edges. + * \param components If not a NULL pointer, then the vertices of the + * biconnected components are stored here, in the same format as + * for the previous two arguments. + * \param articulation_points If not a NULL pointer, then the + * articulation points of the graph are stored in this vector. + * A vertex is an articulation point if its removal increases the + * number of (weakly) connected components in the graph. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges, but only if you do not calculate \c components and + * \c component_edges. If you calculate \c components, then it is + * quadratic in the number of vertices. If you calculate \c + * component_edges as well, then it is cubic in the number of + * vertices. + * + * \sa \ref igraph_articulation_points(), \ref igraph_clusters(). + * + * \example examples/simple/igraph_biconnected_components.c + */ + +int igraph_biconnected_components(const igraph_t *graph, + igraph_integer_t *no, + igraph_vector_ptr_t *tree_edges, + igraph_vector_ptr_t *component_edges, + igraph_vector_ptr_t *components, + igraph_vector_t *articulation_points) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_long_t nextptr; + igraph_vector_long_t num, low; + igraph_vector_bool_t found; + igraph_vector_int_t *adjedges; + igraph_stack_t path; + igraph_vector_t edgestack; + igraph_inclist_t inclist; + long int i, counter, rootdfs = 0; + igraph_vector_long_t vertex_added; + long int comps = 0; + igraph_vector_ptr_t *mycomponents = components, vcomponents; + + IGRAPH_CHECK(igraph_vector_long_init(&nextptr, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &nextptr); + IGRAPH_CHECK(igraph_vector_long_init(&num, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &num); + IGRAPH_CHECK(igraph_vector_long_init(&low, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &low); + IGRAPH_CHECK(igraph_vector_bool_init(&found, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &found); + + IGRAPH_CHECK(igraph_stack_init(&path, 100)); + IGRAPH_FINALLY(igraph_stack_destroy, &path); + IGRAPH_VECTOR_INIT_FINALLY(&edgestack, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edgestack, 100)); + + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_vector_long_init(&vertex_added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &vertex_added); + + if (no) { + *no = 0; + } + if (tree_edges) { + igraph_vector_ptr_clear(tree_edges); + } + if (components) { + igraph_vector_ptr_clear(components); + } + if (component_edges) { + igraph_vector_ptr_clear(component_edges); + } + if (articulation_points) { + igraph_vector_clear(articulation_points); + } + if (component_edges && !components) { + mycomponents = &vcomponents; + IGRAPH_CHECK(igraph_vector_ptr_init(mycomponents, 0)); + IGRAPH_FINALLY(igraph_i_free_vectorlist, mycomponents); + } + + for (i = 0; i < no_of_nodes; i++) { + + if (VECTOR(low)[i] != 0) { + continue; /* already visited */ + } + + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_stack_push(&path, i)); + counter = 1; + rootdfs = 0; + VECTOR(low)[i] = VECTOR(num)[i] = counter++; + while (!igraph_stack_empty(&path)) { + long int n; + long int act = (long int) igraph_stack_top(&path); + long int actnext = VECTOR(nextptr)[act]; + + adjedges = igraph_inclist_get(&inclist, act); + n = igraph_vector_int_size(adjedges); + if (actnext < n) { + /* Step down (maybe) */ + long int edge = (long int) VECTOR(*adjedges)[actnext]; + long int nei = IGRAPH_OTHER(graph, edge, act); + if (VECTOR(low)[nei] == 0) { + if (act == i) { + rootdfs++; + } + IGRAPH_CHECK(igraph_vector_push_back(&edgestack, edge)); + IGRAPH_CHECK(igraph_stack_push(&path, nei)); + VECTOR(low)[nei] = VECTOR(num)[nei] = counter++; + } else { + /* Update low value if needed */ + if (VECTOR(num)[nei] < VECTOR(low)[act]) { + VECTOR(low)[act] = VECTOR(num)[nei]; + } + } + VECTOR(nextptr)[act] += 1; + } else { + /* Step up */ + igraph_stack_pop(&path); + if (!igraph_stack_empty(&path)) { + long int prev = (long int) igraph_stack_top(&path); + /* Update LOW value if needed */ + if (VECTOR(low)[act] < VECTOR(low)[prev]) { + VECTOR(low)[prev] = VECTOR(low)[act]; + } + /* Check for articulation point */ + if (VECTOR(low)[act] >= VECTOR(num)[prev]) { + if (articulation_points && !VECTOR(found)[prev] + && prev != i /* the root */) { + IGRAPH_CHECK(igraph_vector_push_back(articulation_points, prev)); + VECTOR(found)[prev] = 1; + } + if (no) { + *no += 1; + } + + /*------------------------------------*/ + /* Record the biconnected component just found */ + if (tree_edges || mycomponents) { + igraph_vector_t *v = 0, *v2 = 0; + comps++; + if (tree_edges) { + v = igraph_Calloc(1, igraph_vector_t); + if (!v) { + IGRAPH_ERROR("Out of memory", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(v, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, v); + } + if (mycomponents) { + v2 = igraph_Calloc(1, igraph_vector_t); + if (!v2) { + IGRAPH_ERROR("Out of memory", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(v2, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, v2); + } + + while (!igraph_vector_empty(&edgestack)) { + long int e = (long int) igraph_vector_pop_back(&edgestack); + long int from = IGRAPH_FROM(graph, e); + long int to = IGRAPH_TO(graph, e); + if (tree_edges) { + IGRAPH_CHECK(igraph_vector_push_back(v, e)); + } + if (mycomponents) { + if (VECTOR(vertex_added)[from] != comps) { + VECTOR(vertex_added)[from] = comps; + IGRAPH_CHECK(igraph_vector_push_back(v2, from)); + } + if (VECTOR(vertex_added)[to] != comps) { + VECTOR(vertex_added)[to] = comps; + IGRAPH_CHECK(igraph_vector_push_back(v2, to)); + } + } + if (from == prev || to == prev) { + break; + } + } + + if (mycomponents) { + IGRAPH_CHECK(igraph_vector_ptr_push_back(mycomponents, v2)); + IGRAPH_FINALLY_CLEAN(1); + } + if (tree_edges) { + IGRAPH_CHECK(igraph_vector_ptr_push_back(tree_edges, v)); + IGRAPH_FINALLY_CLEAN(1); + } + if (component_edges) { + igraph_vector_t *nodes = VECTOR(*mycomponents)[comps - 1]; + igraph_vector_t *vv = igraph_Calloc(1, igraph_vector_t); + long int ii, no_vert = igraph_vector_size(nodes); + if (!vv) { + IGRAPH_ERROR("Out of memory", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(vv, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, vv); + for (ii = 0; ii < no_vert; ii++) { + long int vert = (long int) VECTOR(*nodes)[ii]; + igraph_vector_int_t *edges = igraph_inclist_get(&inclist, + vert); + long int j, nn = igraph_vector_int_size(edges); + for (j = 0; j < nn; j++) { + long int e = (long int) VECTOR(*edges)[j]; + long int nei = IGRAPH_OTHER(graph, e, vert); + if (VECTOR(vertex_added)[nei] == comps && nei < vert) { + IGRAPH_CHECK(igraph_vector_push_back(vv, e)); + } + } + } + IGRAPH_CHECK(igraph_vector_ptr_push_back(component_edges, vv)); + IGRAPH_FINALLY_CLEAN(1); + } + } /* record component if requested */ + /*------------------------------------*/ + + } + } /* !igraph_stack_empty(&path) */ + } + + } /* !igraph_stack_empty(&path) */ + + if (articulation_points && rootdfs >= 2) { + IGRAPH_CHECK(igraph_vector_push_back(articulation_points, i)); + } + + } /* i < no_of_nodes */ + + if (mycomponents != components) { + igraph_i_free_vectorlist(mycomponents); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_long_destroy(&vertex_added); + igraph_inclist_destroy(&inclist); + igraph_vector_destroy(&edgestack); + igraph_stack_destroy(&path); + igraph_vector_bool_destroy(&found); + igraph_vector_long_destroy(&low); + igraph_vector_long_destroy(&num); + igraph_vector_long_destroy(&nextptr); + IGRAPH_FINALLY_CLEAN(8); + + return 0; +} + + +/* igraph_bridges -- find all bridges in the graph */ +/* The algorithm is based on https://www.geeksforgeeks.org/bridge-in-a-graph/ + but instead of keeping track of the parent of each vertex in the DFS tree + we keep track of its incoming edge. This is necessary to support multigraphs. */ + +static int igraph_i_bridges_rec( + const igraph_t *graph, const igraph_inclist_t *il, igraph_integer_t u, + igraph_integer_t *time, igraph_vector_t *bridges, igraph_vector_bool_t *visited, + igraph_vector_int_t *disc, igraph_vector_int_t *low, igraph_vector_int_t *incoming_edge) +{ + igraph_vector_int_t *incedges; + long nc; /* neighbour count */ + long i; + + VECTOR(*visited)[u] = 1; + + *time += 1; + + VECTOR(*disc)[u] = *time; + VECTOR(*low)[u] = *time; + + incedges = igraph_inclist_get(il, u); + nc = igraph_vector_int_size(incedges); + for (i = 0; i < nc; ++i) { + long edge = (long) VECTOR(*incedges)[i]; + igraph_integer_t v = IGRAPH_TO(graph, edge) == u ? IGRAPH_FROM(graph, edge) : IGRAPH_TO(graph, edge); + + if (! VECTOR(*visited)[v]) { + VECTOR(*incoming_edge)[v] = edge; + IGRAPH_CHECK(igraph_i_bridges_rec(graph, il, v, time, bridges, visited, disc, low, incoming_edge)); + + VECTOR(*low)[u] = VECTOR(*low)[u] < VECTOR(*low)[v] ? VECTOR(*low)[u] : VECTOR(*low)[v]; + + if (VECTOR(*low)[v] > VECTOR(*disc)[u]) { + IGRAPH_CHECK(igraph_vector_push_back(bridges, edge)); + } + } else if (edge != VECTOR(*incoming_edge)[u]) { + VECTOR(*low)[u] = VECTOR(*low)[u] < VECTOR(*disc)[v] ? VECTOR(*low)[u] : VECTOR(*disc)[v]; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_bridges + * Find all bridges in a graph. + * + * An edge is a bridge if its removal increases the number of (weakly) + * connected components in the graph. + * + * \param graph The input graph. + * \param res Pointer to an initialized vector, the + * bridges will be stored here as edge indices. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and edges. + * + * \sa \ref igraph_articulation_points(), \ref igraph_biconnected_components(), \ref igraph_clusters() + */ + +int igraph_bridges(const igraph_t *graph, igraph_vector_t *bridges) { + igraph_inclist_t il; + igraph_vector_bool_t visited; + igraph_vector_int_t disc, low; + igraph_vector_int_t incoming_edge; + long n; + long i; + igraph_integer_t time; + + n = igraph_vcount(graph); + + IGRAPH_CHECK(igraph_inclist_init(graph, &il, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + + IGRAPH_CHECK(igraph_vector_bool_init(&visited, n)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &visited); + + IGRAPH_CHECK(igraph_vector_int_init(&disc, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &disc); + + IGRAPH_CHECK(igraph_vector_int_init(&low, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &low); + + IGRAPH_CHECK(igraph_vector_int_init(&incoming_edge, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &incoming_edge); + for (i = 0; i < n; ++i) { + VECTOR(incoming_edge)[i] = -1; + } + + igraph_vector_clear(bridges); + + time = 0; + for (i = 0; i < n; ++i) + if (! VECTOR(visited)[i]) { + IGRAPH_CHECK(igraph_i_bridges_rec(graph, &il, i, &time, bridges, &visited, &disc, &low, &incoming_edge)); + } + + igraph_vector_int_destroy(&incoming_edge); + igraph_vector_int_destroy(&low); + igraph_vector_int_destroy(&disc); + igraph_vector_bool_destroy(&visited); + igraph_inclist_destroy(&il); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +} diff --git a/src/conversion.c b/src/conversion.c new file mode 100644 index 0000000..a3696d6 --- /dev/null +++ b/src/conversion.c @@ -0,0 +1,951 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_conversion.h" +#include "igraph_iterators.h" +#include "igraph_interface.h" +#include "igraph_attributes.h" +#include "igraph_constructors.h" +#include "igraph_structural.h" +#include "igraph_types_internal.h" +#include "igraph_sparsemat.h" +#include "config.h" + +/** + * \ingroup conversion + * \function igraph_get_adjacency + * \brief Returns the adjacency matrix of a graph + * + * + * The result is an incidence matrix, it contains numbers greater + * than one if there are multiple edges in the graph. + * \param graph Pointer to the graph to convert + * \param res Pointer to an initialized matrix object, it will be + * resized if needed. + * \param type Constant giving the type of the adjacency matrix to + * create for undirected graphs. It is ignored for directed + * graphs. Possible values: + * \clist + * \cli IGRAPH_GET_ADJACENCY_UPPER + * the upper right triangle of the matrix is used. + * \cli IGRAPH_GET_ADJACENCY_LOWER + * the lower left triangle of the matrix is used. + * \cli IGRAPH_GET_ADJACENCY_BOTH + * the whole matrix is used, a symmetric matrix is returned. + * \endclist + * \param type eids Logical, if true, then the edges ids plus one + * are stored in the adjacency matrix, instead of the number of + * edges between the two vertices. (The plus one is needed, since + * edge ids start from zero, and zero means no edge in this case.) + * \return Error code: + * \c IGRAPH_EINVAL invalid type argument. + * + * \sa igraph_get_adjacency_sparse if you want a sparse matrix representation + * + * Time complexity: O(|V||V|), + * |V| is the + * number of vertices in the graph. + */ + +int igraph_get_adjacency(const igraph_t *graph, igraph_matrix_t *res, + igraph_get_adjacency_t type, igraph_bool_t eids) { + + igraph_eit_t edgeit; + long int no_of_nodes = igraph_vcount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + int retval = 0; + long int from, to; + igraph_integer_t ffrom, fto; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, no_of_nodes)); + igraph_matrix_null(res); + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(0), &edgeit)); + IGRAPH_FINALLY(igraph_eit_destroy, &edgeit); + + if (directed) { + while (!IGRAPH_EIT_END(edgeit)) { + long int edge = IGRAPH_EIT_GET(edgeit); + igraph_edge(graph, (igraph_integer_t) edge, &ffrom, &fto); + from = ffrom; + to = fto; + if (eids) { + MATRIX(*res, from, to) = edge + 1; + } else { + MATRIX(*res, from, to) += 1; + } + IGRAPH_EIT_NEXT(edgeit); + } + } else if (type == IGRAPH_GET_ADJACENCY_UPPER) { + while (!IGRAPH_EIT_END(edgeit)) { + long int edge = IGRAPH_EIT_GET(edgeit); + igraph_edge(graph, (igraph_integer_t) edge, &ffrom, &fto); + from = ffrom; + to = fto; + if (to < from) { + if (eids) { + MATRIX(*res, to, from) = edge + 1; + } else { + MATRIX(*res, to, from) += 1; + } + } else { + if (eids) { + MATRIX(*res, from, to) = edge + 1; + } else { + MATRIX(*res, from, to) += 1; + } + } + IGRAPH_EIT_NEXT(edgeit); + } + } else if (type == IGRAPH_GET_ADJACENCY_LOWER) { + while (!IGRAPH_EIT_END(edgeit)) { + long int edge = IGRAPH_EIT_GET(edgeit); + igraph_edge(graph, (igraph_integer_t) edge, &ffrom, &fto); + from = ffrom; + to = fto; + if (to < from) { + if (eids) { + MATRIX(*res, from, to) = edge + 1; + } else { + MATRIX(*res, from, to) += 1; + } + } else { + if (eids) { + MATRIX(*res, to, from) = edge + 1; + } else { + MATRIX(*res, to, from) += 1; + } + } + IGRAPH_EIT_NEXT(edgeit); + } + } else if (type == IGRAPH_GET_ADJACENCY_BOTH) { + while (!IGRAPH_EIT_END(edgeit)) { + long int edge = IGRAPH_EIT_GET(edgeit); + igraph_edge(graph, (igraph_integer_t) edge, &ffrom, &fto); + from = ffrom; + to = fto; + if (eids) { + MATRIX(*res, from, to) = edge + 1; + } else { + MATRIX(*res, from, to) += 1; + } + if (from != to) { + if (eids) { + MATRIX(*res, to, from) = edge + 1; + } else { + MATRIX(*res, to, from) += 1; + } + } + IGRAPH_EIT_NEXT(edgeit); + } + } else { + IGRAPH_ERROR("Invalid type argument", IGRAPH_EINVAL); + } + + igraph_eit_destroy(&edgeit); + IGRAPH_FINALLY_CLEAN(1); + return retval; +} + +/** + * \ingroup conversion + * \function igraph_get_adjacency_sparse + * \brief Returns the adjacency matrix of a graph in sparse matrix format + * + * + * The result is an incidence matrix, it contains numbers greater + * than one if there are multiple edges in the graph. + * \param graph Pointer to the graph to convert + * \param res Pointer to an initialized sparse matrix object, it will be + * resized if needed. + * \param type Constant giving the type of the adjacency matrix to + * create for undirected graphs. It is ignored for directed + * graphs. Possible values: + * \clist + * \cli IGRAPH_GET_ADJACENCY_UPPER + * the upper right triangle of the matrix is used. + * \cli IGRAPH_GET_ADJACENCY_LOWER + * the lower left triangle of the matrix is used. + * \cli IGRAPH_GET_ADJACENCY_BOTH + * the whole matrix is used, a symmetric matrix is returned. + * \endclist + * \return Error code: + * \c IGRAPH_EINVAL invalid type argument. + * + * \sa igraph_get_adjacency if you would like to get a normal matrix + * ( \type igraph_matrix_t ) + * + * Time complexity: O(|V||V|), + * |V| is the + * number of vertices in the graph. + */ + +int igraph_get_adjacency_sparse(const igraph_t *graph, igraph_spmatrix_t *res, + igraph_get_adjacency_t type) { + + igraph_eit_t edgeit; + long int no_of_nodes = igraph_vcount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + int retval = 0; + long int from, to; + igraph_integer_t ffrom, fto; + + igraph_spmatrix_null(res); + IGRAPH_CHECK(igraph_spmatrix_resize(res, no_of_nodes, no_of_nodes)); + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(0), &edgeit)); + IGRAPH_FINALLY(igraph_eit_destroy, &edgeit); + + if (directed) { + while (!IGRAPH_EIT_END(edgeit)) { + igraph_edge(graph, IGRAPH_EIT_GET(edgeit), &ffrom, &fto); + from = ffrom; + to = fto; + igraph_spmatrix_add_e(res, from, to, 1); + IGRAPH_EIT_NEXT(edgeit); + } + } else if (type == IGRAPH_GET_ADJACENCY_UPPER) { + while (!IGRAPH_EIT_END(edgeit)) { + igraph_edge(graph, IGRAPH_EIT_GET(edgeit), &ffrom, &fto); + from = ffrom; + to = fto; + if (to < from) { + igraph_spmatrix_add_e(res, to, from, 1); + } else { + igraph_spmatrix_add_e(res, from, to, 1); + } + IGRAPH_EIT_NEXT(edgeit); + } + } else if (type == IGRAPH_GET_ADJACENCY_LOWER) { + while (!IGRAPH_EIT_END(edgeit)) { + igraph_edge(graph, IGRAPH_EIT_GET(edgeit), &ffrom, &fto); + from = ffrom; + to = fto; + if (to > from) { + igraph_spmatrix_add_e(res, to, from, 1); + } else { + igraph_spmatrix_add_e(res, from, to, 1); + } + IGRAPH_EIT_NEXT(edgeit); + } + } else if (type == IGRAPH_GET_ADJACENCY_BOTH) { + while (!IGRAPH_EIT_END(edgeit)) { + igraph_edge(graph, IGRAPH_EIT_GET(edgeit), &ffrom, &fto); + from = ffrom; + to = fto; + igraph_spmatrix_add_e(res, from, to, 1); + if (from != to) { + igraph_spmatrix_add_e(res, to, from, 1); + } + IGRAPH_EIT_NEXT(edgeit); + } + } else { + IGRAPH_ERROR("Invalid type argument", IGRAPH_EINVAL); + } + + igraph_eit_destroy(&edgeit); + IGRAPH_FINALLY_CLEAN(1); + return retval; +} + +/** + * \ingroup conversion + * \function igraph_get_edgelist + * \brief Returns the list of edges in a graph + * + * The order of the edges is given by the edge ids. + * \param graph Pointer to the graph object + * \param res Pointer to an initialized vector object, it will be + * resized. + * \param bycol Logical, if true, the edges will be returned + * columnwise, eg. the first edge is + * res[0]->res[|E|], the second is + * res[1]->res[|E|+1], etc. + * \return Error code. + * + * Time complexity: O(|E|), the + * number of edges in the graph. + */ + +int igraph_get_edgelist(const igraph_t *graph, igraph_vector_t *res, igraph_bool_t bycol) { + + igraph_eit_t edgeit; + long int no_of_edges = igraph_ecount(graph); + long int vptr = 0; + igraph_integer_t from, to; + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_edges * 2)); + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), + &edgeit)); + IGRAPH_FINALLY(igraph_eit_destroy, &edgeit); + + if (bycol) { + while (!IGRAPH_EIT_END(edgeit)) { + igraph_edge(graph, IGRAPH_EIT_GET(edgeit), &from, &to); + VECTOR(*res)[vptr] = from; + VECTOR(*res)[vptr + no_of_edges] = to; + vptr++; + IGRAPH_EIT_NEXT(edgeit); + } + } else { + while (!IGRAPH_EIT_END(edgeit)) { + igraph_edge(graph, IGRAPH_EIT_GET(edgeit), &from, &to); + VECTOR(*res)[vptr++] = from; + VECTOR(*res)[vptr++] = to; + IGRAPH_EIT_NEXT(edgeit); + } + } + + igraph_eit_destroy(&edgeit); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_to_directed + * \brief Convert an undirected graph to a directed one + * + * + * If the supplied graph is directed, this function does nothing. + * \param graph The graph object to convert. + * \param mode Constant, specifies the details of how exactly the + * conversion is done. Possible values: \c + * IGRAPH_TO_DIRECTED_ARBITRARY: the number of edges in the + * graph stays the same, an arbitrarily directed edge is + * created for each undirected edge; + * \c IGRAPH_TO_DIRECTED_MUTUAL: two directed edges are + * created for each undirected edge, one in each direction. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + */ + +int igraph_to_directed(igraph_t *graph, + igraph_to_directed_t mode) { + + if (mode != IGRAPH_TO_DIRECTED_ARBITRARY && + mode != IGRAPH_TO_DIRECTED_MUTUAL) { + IGRAPH_ERROR("Cannot direct graph, invalid mode", IGRAPH_EINVAL); + } + + if (igraph_is_directed(graph)) { + return 0; + } + + if (mode == IGRAPH_TO_DIRECTED_ARBITRARY) { + + igraph_t newgraph; + igraph_vector_t edges; + long int no_of_edges = igraph_ecount(graph); + long int no_of_nodes = igraph_vcount(graph); + long int size = no_of_edges * 2; + IGRAPH_VECTOR_INIT_FINALLY(&edges, size); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, + (igraph_integer_t) no_of_nodes, + IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + igraph_vector_destroy(&edges); + IGRAPH_I_ATTRIBUTE_DESTROY(&newgraph); + IGRAPH_I_ATTRIBUTE_COPY(&newgraph, graph, 1, 1, 1); + IGRAPH_FINALLY_CLEAN(2); + igraph_destroy(graph); + *graph = newgraph; + + } else if (mode == IGRAPH_TO_DIRECTED_MUTUAL) { + + igraph_t newgraph; + igraph_vector_t edges; + igraph_vector_t index; + long int no_of_edges = igraph_ecount(graph); + long int no_of_nodes = igraph_vcount(graph); + long int size = no_of_edges * 4; + long int i; + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, size)); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + IGRAPH_CHECK(igraph_vector_resize(&edges, no_of_edges * 4)); + IGRAPH_VECTOR_INIT_FINALLY(&index, no_of_edges * 2); + for (i = 0; i < no_of_edges; i++) { + VECTOR(edges)[no_of_edges * 2 + i * 2] = VECTOR(edges)[i * 2 + 1]; + VECTOR(edges)[no_of_edges * 2 + i * 2 + 1] = VECTOR(edges)[i * 2]; + VECTOR(index)[i] = VECTOR(index)[no_of_edges + i] = i; + } + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, + (igraph_integer_t) no_of_nodes, + IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_I_ATTRIBUTE_DESTROY(&newgraph); + IGRAPH_I_ATTRIBUTE_COPY(&newgraph, graph, 1, 1,/*edges=*/0); + IGRAPH_CHECK(igraph_i_attribute_permute_edges(graph, &newgraph, &index)); + + igraph_vector_destroy(&index); + igraph_vector_destroy(&edges); + igraph_destroy(graph); + IGRAPH_FINALLY_CLEAN(3); + *graph = newgraph; + } + + return 0; +} + +/** + * \function igraph_to_undirected + * \brief Convert a directed graph to an undirected one. + * + * + * If the supplied graph is undirected, this function does nothing. + * \param graph The graph object to convert. + * \param mode Constant, specifies the details of how exactly the + * conversion is done. Possible values: \c + * IGRAPH_TO_UNDIRECTED_EACH: the number of edges remains + * constant, an undirected edge is created for each directed + * one, this version might create graphs with multiple edges; + * \c IGRAPH_TO_UNDIRECTED_COLLAPSE: one undirected edge will + * be created for each pair of vertices which are connected + * with at least one directed edge, no multiple edges will be + * created. \c IGRAPH_TO_UNDIRECTED_MUTUAL creates an undirected + * edge for each pair of mutual edges in the directed graph. + * Non-mutual edges are lost. This mode might create multiple + * edges. + * \param edge_comb What to do with the edge attributes. See the igraph + * manual section about attributes for details. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + * + * \example examples/simple/igraph_to_undirected.c + */ + +int igraph_to_undirected(igraph_t *graph, + igraph_to_undirected_t mode, + const igraph_attribute_combination_t *edge_comb) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_vector_t edges; + igraph_t newgraph; + igraph_bool_t attr = edge_comb && igraph_has_attribute_table(); + + if (mode != IGRAPH_TO_UNDIRECTED_EACH && + mode != IGRAPH_TO_UNDIRECTED_COLLAPSE && + mode != IGRAPH_TO_UNDIRECTED_MUTUAL) { + IGRAPH_ERROR("Cannot undirect graph, invalid mode", IGRAPH_EINVAL); + } + + if (!igraph_is_directed(graph)) { + return 0; + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + if (mode == IGRAPH_TO_UNDIRECTED_EACH) { + igraph_es_t es; + igraph_eit_t eit; + + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges * 2)); + IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_ID)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + while (!IGRAPH_EIT_END(eit)) { + long int edge = IGRAPH_EIT_GET(eit); + igraph_integer_t from, to; + igraph_edge(graph, (igraph_integer_t) edge, &from, &to); + IGRAPH_CHECK(igraph_vector_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to)); + IGRAPH_EIT_NEXT(eit); + } + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, + (igraph_integer_t) no_of_nodes, + IGRAPH_UNDIRECTED)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + igraph_vector_destroy(&edges); + IGRAPH_I_ATTRIBUTE_DESTROY(&newgraph); + IGRAPH_I_ATTRIBUTE_COPY(&newgraph, graph, 1, 1, 1); + IGRAPH_FINALLY_CLEAN(2); + igraph_destroy(graph); + *graph = newgraph; + + } else if (mode == IGRAPH_TO_UNDIRECTED_COLLAPSE) { + igraph_vector_t inadj, outadj; + long int i; + igraph_vector_t mergeinto; + long int actedge = 0; + + if (attr) { + IGRAPH_VECTOR_INIT_FINALLY(&mergeinto, no_of_edges); + } + + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges * 2)); + IGRAPH_VECTOR_INIT_FINALLY(&inadj, 0); + IGRAPH_VECTOR_INIT_FINALLY(&outadj, 0); + + for (i = 0; i < no_of_nodes; i++) { + long int n_out, n_in; + long int p1 = -1, p2 = -1; + long int e1 = 0, e2 = 0, n1 = 0, n2 = 0; + IGRAPH_CHECK(igraph_incident(graph, &outadj, (igraph_integer_t) i, + IGRAPH_OUT)); + IGRAPH_CHECK(igraph_incident(graph, &inadj, (igraph_integer_t) i, + IGRAPH_IN)); + n_out = igraph_vector_size(&outadj); + n_in = igraph_vector_size(&inadj); + +#define STEPOUT() if ( (++p1) < n_out) { \ + e1 = (long int) VECTOR(outadj)[p1]; \ + n1 = IGRAPH_TO(graph, e1); \ + } +#define STEPIN() if ( (++p2) < n_in) { \ + e2 = (long int) VECTOR(inadj )[p2]; \ + n2 = IGRAPH_FROM(graph, e2); \ + } + + STEPOUT(); + STEPIN(); + + while (p1 < n_out && n1 <= i && p2 < n_in && n2 <= i) { + long int last; + if (n1 == n2) { + last = n1; + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, n1)); + if (attr) { + VECTOR(mergeinto)[e1] = actedge; + VECTOR(mergeinto)[e2] = actedge; + actedge++; + } + while (p1 < n_out && last == n1) { + STEPOUT(); + } + while (p2 < n_in && last == n2) { + STEPIN (); + } + } else if (n1 < n2) { + last = n1; + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, n1)); + if (attr) { + VECTOR(mergeinto)[e1] = actedge; + actedge++; + } + while (p1 < n_out && last == n1) { + STEPOUT(); + } + } else { /* n2= 2 vertices can be represented by a + * sequence of n-2 integers, each between 0 and n-1 (inclusive). + * + * \param graph Pointer to an initialized graph object which + must be a tree on n >= 2 vertices. + * \param prufer A pointer to the integer vector that should hold the Prüfer sequence; + the vector must be initialized and will be resized to n - 2. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * there is not enough memory to perform the operation. + * \cli IGRAPH_EINVAL + * the graph is not a tree or it is has less than vertices + * \endclist + * + * \sa \ref igraph_from_prufer() + * + */ +int igraph_to_prufer(const igraph_t *graph, igraph_vector_int_t* prufer) { + /* For generating the Prüfer sequence, we enumerate the vertices u of the tree. + We keep track of the degrees of all vertices, treating vertices + of degree 0 as removed. We maintain the invariant that all leafs + that are still contained in the tree are >= u. + If u is a leaf, we remove it and add its unique neighbor to the prüfer + sequence. If the removal of u turns the neighbor into a leaf which is < u, + we repeat the procedure for the new leaf and so on. */ + igraph_integer_t u; + igraph_vector_t degrees, neighbors; + igraph_integer_t prufer_index = 0; + igraph_integer_t n = igraph_vcount(graph); + igraph_bool_t is_tree = 0; + + IGRAPH_CHECK(igraph_is_tree(graph, &is_tree, NULL, IGRAPH_ALL)); + + if (!is_tree) { + IGRAPH_ERROR("The graph must be a tree", IGRAPH_EINVAL); + } + + if (n < 2) { + IGRAPH_ERROR("The tree must have at least 2 vertices", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_int_resize(prufer, n - 2)); + IGRAPH_VECTOR_INIT_FINALLY(°rees, n); + IGRAPH_VECTOR_INIT_FINALLY(&neighbors, 1); + + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS)); + + for (u = 0; u < n; ++u) { + igraph_integer_t degree = VECTOR(degrees)[u]; + igraph_integer_t leaf = u; + + while (degree == 1 && leaf <= u) { + igraph_integer_t i; + igraph_integer_t neighbor = 0; + igraph_integer_t neighbor_count = 0; + + VECTOR(degrees)[leaf] = 0; /* mark leaf v as deleted */ + + IGRAPH_CHECK(igraph_neighbors(graph, &neighbors, leaf, IGRAPH_ALL)); + + /* Find the unique remaining neighbor of the leaf */ + neighbor_count = igraph_vector_size(&neighbors); + for (i = 0; i < neighbor_count; i++) { + neighbor = VECTOR(neighbors)[i]; + if (VECTOR(degrees)[neighbor] > 0) { + break; + } + } + + /* remember that we have removed the leaf */ + VECTOR(degrees)[neighbor]--; + degree = VECTOR(degrees)[neighbor]; + + /* Add the neighbor to the prufer sequence unless it is the last vertex + (i.e. degree == 0) */ + if (degree > 0) { + VECTOR(*prufer)[prufer_index] = neighbor; + prufer_index++; + } + leaf = neighbor; + } + } + + igraph_vector_destroy(°rees); + igraph_vector_destroy(&neighbors); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/cores.c b/src/cores.c new file mode 100644 index 0000000..dd265a9 --- /dev/null +++ b/src/cores.c @@ -0,0 +1,159 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_community.h" +#include "igraph_memory.h" +#include "igraph_interface.h" +#include "igraph_iterators.h" +#include "config.h" + +/** + * \function igraph_coreness + * \brief Finding the coreness of the vertices in a network. + * + * The k-core of a graph is a maximal subgraph in which each vertex + * has at least degree k. (Degree here means the degree in the + * subgraph of course.). The coreness of a vertex is the highest order + * of a k-core containing the vertex. + * + * + * This function implements the algorithm presented in Vladimir + * Batagelj, Matjaz Zaversnik: An O(m) Algorithm for Cores + * Decomposition of Networks. + * \param graph The input graph. + * \param cores Pointer to an initialized vector, the result of the + * computation will be stored here. It will be resized as + * needed. For each vertex it contains the highest order of a + * core containing the vertex. + * \param mode For directed graph it specifies whether to calculate + * in-cores, out-cores or the undirected version. It is ignored + * for undirected graphs. Possible values: \c IGRAPH_ALL + * undirected version, \c IGRAPH_IN in-cores, \c IGRAPH_OUT + * out-cores. + * \return Error code. + * + * Time complexity: O(|E|), the number of edges. + */ + +int igraph_coreness(const igraph_t *graph, igraph_vector_t *cores, + igraph_neimode_t mode) { + + long int no_of_nodes = igraph_vcount(graph); + long int *bin, *vert, *pos; + long int maxdeg; + long int i, j = 0; + igraph_vector_t neis; + igraph_neimode_t omode; + + if (mode != IGRAPH_ALL && mode != IGRAPH_OUT && mode != IGRAPH_IN) { + IGRAPH_ERROR("Invalid mode in k-cores", IGRAPH_EINVAL); + } + if (!igraph_is_directed(graph) || mode == IGRAPH_ALL) { + mode = omode = IGRAPH_ALL; + } else if (mode == IGRAPH_IN) { + omode = IGRAPH_OUT; + } else { + omode = IGRAPH_IN; + } + + vert = igraph_Calloc(no_of_nodes, long int); + if (vert == 0) { + IGRAPH_ERROR("Cannot calculate k-cores", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, vert); + pos = igraph_Calloc(no_of_nodes, long int); + if (pos == 0) { + IGRAPH_ERROR("Cannot calculate k-cores", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, pos); + + /* maximum degree + degree of vertices */ + IGRAPH_CHECK(igraph_degree(graph, cores, igraph_vss_all(), mode, + IGRAPH_LOOPS)); + maxdeg = (long int) igraph_vector_max(cores); + + bin = igraph_Calloc(maxdeg + 1, long int); + if (bin == 0) { + IGRAPH_ERROR("Cannot calculate k-cores", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, bin); + + /* degree histogram */ + for (i = 0; i < no_of_nodes; i++) { + bin[ (long int)VECTOR(*cores)[i] ] += 1; + } + + /* start pointers */ + j = 0; + for (i = 0; i <= maxdeg; i++) { + long int k = bin[i]; + bin[i] = j; + j += k; + } + + /* sort in vert (and corrupt bin) */ + for (i = 0; i < no_of_nodes; i++) { + pos[i] = bin[(long int)VECTOR(*cores)[i]]; + vert[pos[i]] = i; + bin[(long int)VECTOR(*cores)[i]] += 1; + } + + /* correct bin */ + for (i = maxdeg; i > 0; i--) { + bin[i] = bin[i - 1]; + } + bin[0] = 0; + + /* this is the main algorithm */ + IGRAPH_VECTOR_INIT_FINALLY(&neis, maxdeg); + for (i = 0; i < no_of_nodes; i++) { + long int v = vert[i]; + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) v, omode)); + for (j = 0; j < igraph_vector_size(&neis); j++) { + long int u = (long int) VECTOR(neis)[j]; + if (VECTOR(*cores)[u] > VECTOR(*cores)[v]) { + long int du = (long int) VECTOR(*cores)[u]; + long int pu = pos[u]; + long int pw = bin[du]; + long int w = vert[pw]; + if (u != w) { + pos[u] = pw; + pos[w] = pu; + vert[pu] = w; + vert[pw] = u; + } + bin[du] += 1; + VECTOR(*cores)[u] -= 1; + } + } + } + + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + + igraph_free(bin); + igraph_free(pos); + igraph_free(vert); + IGRAPH_FINALLY_CLEAN(3); + return 0; +} diff --git a/src/cs/UFconfig.h b/src/cs/UFconfig.h new file mode 100644 index 0000000..3844ccc --- /dev/null +++ b/src/cs/UFconfig.h @@ -0,0 +1,118 @@ +/* ========================================================================== */ +/* === UFconfig.h =========================================================== */ +/* ========================================================================== */ + +/* Configuration file for SuiteSparse: a Suite of Sparse matrix packages + * (AMD, COLAMD, CCOLAMD, CAMD, CHOLMOD, UMFPACK, CXSparse, and others). + * + * UFconfig.h provides the definition of the long integer. On most systems, + * a C program can be compiled in LP64 mode, in which long's and pointers are + * both 64-bits, and int's are 32-bits. Windows 64, however, uses the LLP64 + * model, in which int's and long's are 32-bits, and long long's and pointers + * are 64-bits. + * + * SuiteSparse packages that include long integer versions are + * intended for the LP64 mode. However, as a workaround for Windows 64 + * (and perhaps other systems), the long integer can be redefined. + * + * If _WIN64 is defined, then the __int64 type is used instead of long. + * + * The long integer can also be defined at compile time. For example, this + * could be added to UFconfig.mk: + * + * CFLAGS = -O -D'UF_long=long long' -D'UF_long_max=9223372036854775801' \ + * -D'UF_long_id="%lld"' + * + * This file defines UF_long as either long (on all but _WIN64) or + * __int64 on Windows 64. The intent is that a UF_long is always a 64-bit + * integer in a 64-bit code. ptrdiff_t might be a better choice than long; + * it is always the same size as a pointer. + * + * This file also defines the SUITESPARSE_VERSION and related definitions. + * + * Copyright (c) 2007, University of Florida. No licensing restrictions + * apply to this file or to the UFconfig directory. Author: Timothy A. Davis. + */ + +#ifndef _UFCONFIG_H +#define _UFCONFIG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* ========================================================================== */ +/* === UF_long ============================================================== */ +/* ========================================================================== */ + +#ifndef UF_long + +#ifdef _WIN64 + +#define UF_long __int64 +#define UF_long_max _I64_MAX +#define UF_long_id "%I64d" + +#else + +#define UF_long long +#define UF_long_max LONG_MAX +#define UF_long_id "%ld" + +#endif +#endif + +/* ========================================================================== */ +/* === SuiteSparse version ================================================== */ +/* ========================================================================== */ + +/* SuiteSparse is not a package itself, but a collection of packages, some of + * which must be used together (UMFPACK requires AMD, CHOLMOD requires AMD, + * COLAMD, CAMD, and CCOLAMD, etc). A version number is provided here for the + * collection itself. The versions of packages within each version of + * SuiteSparse are meant to work together. Combining one packge from one + * version of SuiteSparse, with another package from another version of + * SuiteSparse, may or may not work. + * + * SuiteSparse Version 3.3.0 contains the following packages: + * + * AMD version 2.2.0 + * CAMD version 2.2.0 + * COLAMD version 2.7.1 + * CCOLAMD version 2.7.1 + * CHOLMOD version 1.7.1 + * CSparse version 2.2.3 + * CXSparse version 2.2.3 + * KLU version 1.1.0 + * BTF version 1.0.1 + * LDL version 2.0.1 + * UFconfig version number is the same as SuiteSparse + * UMFPACK version 5.3.0 + * RBio version 1.1.1 + * UFcollection version 1.2.0 + * LINFACTOR version 1.1.0 + * MESHND version 1.1.1 + * SSMULT version 2.0.0 + * MATLAB_Tools no specific version number + * SuiteSparseQR version 1.1.1 + * + * Other package dependencies: + * BLAS required by CHOLMOD and UMFPACK + * LAPACK required by CHOLMOD + * METIS 4.0.1 required by CHOLMOD (optional) and KLU (optional) + */ + +#define SUITESPARSE_DATE "Mar 24, 2009" +#define SUITESPARSE_VER_CODE(main,sub) ((main) * 1000 + (sub)) +#define SUITESPARSE_MAIN_VERSION 3 +#define SUITESPARSE_SUB_VERSION 3 +#define SUITESPARSE_SUBSUB_VERSION 0 +#define SUITESPARSE_VERSION \ + SUITESPARSE_VER_CODE(SUITESPARSE_MAIN_VERSION,SUITESPARSE_SUB_VERSION) + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/cs/cs.h b/src/cs/cs.h new file mode 100644 index 0000000..abaf267 --- /dev/null +++ b/src/cs/cs.h @@ -0,0 +1,756 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CXS_H +#define _CXS_H +#include +#include +#include +#include +#ifdef MATLAB_MEX_FILE +#include "mex.h" +#endif + + +#ifdef __cplusplus +#ifndef NCOMPLEX +#include +typedef std::complex cs_complex_t ; +#endif +extern "C" { +#else +#ifndef NCOMPLEX +#include +#define cs_complex_t double _Complex +#endif +#endif + +#define CS_VER 2 /* CXSparse Version 2.2.3 */ +#define CS_SUBVER 2 +#define CS_SUBSUB 3 +#define CS_DATE "Mar 24, 2009" /* CXSparse release date */ +#define CS_COPYRIGHT "Copyright (c) Timothy A. Davis, 2006-2009" +#define CXSPARSE + +/* define UF_long */ +#include "UFconfig.h" + +/* -------------------------------------------------------------------------- */ +/* double/int version of CXSparse */ +/* -------------------------------------------------------------------------- */ + +/* --- primary CSparse routines and data structures ------------------------- */ + +typedef struct cs_di_sparse /* matrix in compressed-column or triplet form */ +{ + int nzmax ; /* maximum number of entries */ + int m ; /* number of rows */ + int n ; /* number of columns */ + int *p ; /* column pointers (size n+1) or col indices (size nzmax) */ + int *i ; /* row indices, size nzmax */ + double *x ; /* numerical values, size nzmax */ + int nz ; /* # of entries in triplet matrix, -1 for compressed-col */ +} cs_di ; + +cs_di *cs_di_add (const cs_di *A, const cs_di *B, double alpha, double beta) ; +int cs_di_cholsol (int order, const cs_di *A, double *b) ; +int cs_di_dupl (cs_di *A) ; +int cs_di_entry (cs_di *T, int i, int j, double x) ; +int cs_di_lusol (int order, const cs_di *A, double *b, double tol) ; +int cs_di_gaxpy (const cs_di *A, const double *x, double *y) ; +cs_di *cs_di_multiply (const cs_di *A, const cs_di *B) ; +int cs_di_qrsol (int order, const cs_di *A, double *b) ; +cs_di *cs_di_transpose (const cs_di *A, int values) ; +cs_di *cs_di_compress (const cs_di *T) ; +double cs_di_norm (const cs_di *A) ; +int cs_di_print (const cs_di *A, int brief) ; +cs_di *cs_di_load (FILE *f) ; + +/* utilities */ +void *cs_di_calloc (int n, size_t size) ; +void *cs_di_free (void *p) ; +void *cs_di_realloc (void *p, int n, size_t size, int *ok) ; +cs_di *cs_di_spalloc (int m, int n, int nzmax, int values, int t) ; +cs_di *cs_di_spfree (cs_di *A) ; +int cs_di_sprealloc (cs_di *A, int nzmax) ; +void *cs_di_malloc (int n, size_t size) ; + +/* --- secondary CSparse routines and data structures ----------------------- */ + +typedef struct cs_di_symbolic /* symbolic Cholesky, LU, or QR analysis */ +{ + int *pinv ; /* inverse row perm. for QR, fill red. perm for Chol */ + int *q ; /* fill-reducing column permutation for LU and QR */ + int *parent ; /* elimination tree for Cholesky and QR */ + int *cp ; /* column pointers for Cholesky, row counts for QR */ + int *leftmost ; /* leftmost[i] = min(find(A(i,:))), for QR */ + int m2 ; /* # of rows for QR, after adding fictitious rows */ + double lnz ; /* # entries in L for LU or Cholesky; in V for QR */ + double unz ; /* # entries in U for LU; in R for QR */ +} cs_dis ; + +typedef struct cs_di_numeric /* numeric Cholesky, LU, or QR factorization */ +{ + cs_di *L ; /* L for LU and Cholesky, V for QR */ + cs_di *U ; /* U for LU, r for QR, not used for Cholesky */ + int *pinv ; /* partial pivoting for LU */ + double *B ; /* beta [0..n-1] for QR */ +} cs_din ; + +typedef struct cs_di_dmperm_results /* cs_di_dmperm or cs_di_scc output */ +{ + int *p ; /* size m, row permutation */ + int *q ; /* size n, column permutation */ + int *r ; /* size nb+1, block k is rows r[k] to r[k+1]-1 in A(p,q) */ + int *s ; /* size nb+1, block k is cols s[k] to s[k+1]-1 in A(p,q) */ + int nb ; /* # of blocks in fine dmperm decomposition */ + int rr [5] ; /* coarse row decomposition */ + int cc [5] ; /* coarse column decomposition */ +} cs_did ; + +int *cs_di_amd (int order, const cs_di *A) ; +cs_din *cs_di_chol (const cs_di *A, const cs_dis *S) ; +cs_did *cs_di_dmperm (const cs_di *A, int seed) ; +int cs_di_droptol (cs_di *A, double tol) ; +int cs_di_dropzeros (cs_di *A) ; +int cs_di_happly (const cs_di *V, int i, double beta, double *x) ; +int cs_di_ipvec (const int *p, const double *b, double *x, int n) ; +int cs_di_lsolve (const cs_di *L, double *x) ; +int cs_di_ltsolve (const cs_di *L, double *x) ; +cs_din *cs_di_lu (const cs_di *A, const cs_dis *S, double tol) ; +cs_di *cs_di_permute (const cs_di *A, const int *pinv, const int *q, + int values) ; +int *cs_di_pinv (const int *p, int n) ; +int cs_di_pvec (const int *p, const double *b, double *x, int n) ; +cs_din *cs_di_qr (const cs_di *A, const cs_dis *S) ; +cs_dis *cs_di_schol (int order, const cs_di *A) ; +cs_dis *cs_di_sqr (int order, const cs_di *A, int qr) ; +cs_di *cs_di_symperm (const cs_di *A, const int *pinv, int values) ; +int cs_di_usolve (const cs_di *U, double *x) ; +int cs_di_utsolve (const cs_di *U, double *x) ; +int cs_di_updown (cs_di *L, int sigma, const cs_di *C, const int *parent) ; + +/* utilities */ +cs_dis *cs_di_sfree (cs_dis *S) ; +cs_din *cs_di_nfree (cs_din *N) ; +cs_did *cs_di_dfree (cs_did *D) ; + +/* --- tertiary CSparse routines -------------------------------------------- */ + +int *cs_di_counts (const cs_di *A, const int *parent, const int *post, + int ata) ; +double cs_di_cumsum (int *p, int *c, int n) ; +int cs_di_dfs (int j, cs_di *G, int top, int *xi, int *pstack, + const int *pinv) ; +int *cs_di_etree (const cs_di *A, int ata) ; +int cs_di_fkeep (cs_di *A, int (*fkeep) (int, int, double, void *), + void *other) ; +double cs_di_house (double *x, double *beta, int n) ; +int *cs_di_maxtrans (const cs_di *A, int seed) ; +int *cs_di_post (const int *parent, int n) ; +cs_did *cs_di_scc (cs_di *A) ; +int cs_di_scatter (const cs_di *A, int j, double beta, int *w, double *x, + int mark, cs_di *C, int nz) ; +int cs_di_tdfs (int j, int k, int *head, const int *next, int *post, + int *stack) ; +int cs_di_leaf (int i, int j, const int *first, int *maxfirst, int *prevleaf, + int *ancestor, int *jleaf) ; +int cs_di_reach (cs_di *G, const cs_di *B, int k, int *xi, const int *pinv) ; +int cs_di_spsolve (cs_di *L, const cs_di *B, int k, int *xi, double *x, + const int *pinv, int lo) ; +int cs_di_ereach (const cs_di *A, int k, const int *parent, int *s, int *w) ; +int *cs_di_randperm (int n, int seed) ; + +/* utilities */ +cs_did *cs_di_dalloc (int m, int n) ; +cs_di *cs_di_done (cs_di *C, void *w, void *x, int ok) ; +int *cs_di_idone (int *p, cs_di *C, void *w, int ok) ; +cs_din *cs_di_ndone (cs_din *N, cs_di *C, void *w, void *x, int ok) ; +cs_did *cs_di_ddone (cs_did *D, cs_di *C, void *w, int ok) ; + + +/* -------------------------------------------------------------------------- */ +/* double/UF_long version of CXSparse */ +/* -------------------------------------------------------------------------- */ + +/* --- primary CSparse routines and data structures ------------------------- */ + +typedef struct cs_dl_sparse /* matrix in compressed-column or triplet form */ +{ + UF_long nzmax ; /* maximum number of entries */ + UF_long m ; /* number of rows */ + UF_long n ; /* number of columns */ + UF_long *p ; /* column pointers (size n+1) or col indlces (size nzmax) */ + UF_long *i ; /* row indices, size nzmax */ + double *x ; /* numerical values, size nzmax */ + UF_long nz ; /* # of entries in triplet matrix, -1 for compressed-col */ +} cs_dl ; + +cs_dl *cs_dl_add (const cs_dl *A, const cs_dl *B, double alpha, double beta) ; +UF_long cs_dl_cholsol (UF_long order, const cs_dl *A, double *b) ; +UF_long cs_dl_dupl (cs_dl *A) ; +UF_long cs_dl_entry (cs_dl *T, UF_long i, UF_long j, double x) ; +UF_long cs_dl_lusol (UF_long order, const cs_dl *A, double *b, double tol) ; +UF_long cs_dl_gaxpy (const cs_dl *A, const double *x, double *y) ; +cs_dl *cs_dl_multiply (const cs_dl *A, const cs_dl *B) ; +UF_long cs_dl_qrsol (UF_long order, const cs_dl *A, double *b) ; +cs_dl *cs_dl_transpose (const cs_dl *A, UF_long values) ; +cs_dl *cs_dl_compress (const cs_dl *T) ; +double cs_dl_norm (const cs_dl *A) ; +UF_long cs_dl_print (const cs_dl *A, UF_long brief) ; +cs_dl *cs_dl_load (FILE *f) ; + +/* utilities */ +void *cs_dl_calloc (UF_long n, size_t size) ; +void *cs_dl_free (void *p) ; +void *cs_dl_realloc (void *p, UF_long n, size_t size, UF_long *ok) ; +cs_dl *cs_dl_spalloc (UF_long m, UF_long n, UF_long nzmax, UF_long values, + UF_long t) ; +cs_dl *cs_dl_spfree (cs_dl *A) ; +UF_long cs_dl_sprealloc (cs_dl *A, UF_long nzmax) ; +void *cs_dl_malloc (UF_long n, size_t size) ; + +/* --- secondary CSparse routines and data structures ----------------------- */ + +typedef struct cs_dl_symbolic /* symbolic Cholesky, LU, or QR analysis */ +{ + UF_long *pinv ; /* inverse row perm. for QR, fill red. perm for Chol */ + UF_long *q ; /* fill-reducing column permutation for LU and QR */ + UF_long *parent ; /* elimination tree for Cholesky and QR */ + UF_long *cp ; /* column pointers for Cholesky, row counts for QR */ + UF_long *leftmost ; /* leftmost[i] = min(find(A(i,:))), for QR */ + UF_long m2 ; /* # of rows for QR, after adding fictitious rows */ + double lnz ; /* # entries in L for LU or Cholesky; in V for QR */ + double unz ; /* # entries in U for LU; in R for QR */ +} cs_dls ; + +typedef struct cs_dl_numeric /* numeric Cholesky, LU, or QR factorization */ +{ + cs_dl *L ; /* L for LU and Cholesky, V for QR */ + cs_dl *U ; /* U for LU, r for QR, not used for Cholesky */ + UF_long *pinv ; /* partial pivoting for LU */ + double *B ; /* beta [0..n-1] for QR */ +} cs_dln ; + +typedef struct cs_dl_dmperm_results /* cs_dl_dmperm or cs_dl_scc output */ +{ + UF_long *p ; /* size m, row permutation */ + UF_long *q ; /* size n, column permutation */ + UF_long *r ; /* size nb+1, block k is rows r[k] to r[k+1]-1 in A(p,q) */ + UF_long *s ; /* size nb+1, block k is cols s[k] to s[k+1]-1 in A(p,q) */ + UF_long nb ; /* # of blocks in fine dmperm decomposition */ + UF_long rr [5] ; /* coarse row decomposition */ + UF_long cc [5] ; /* coarse column decomposition */ +} cs_dld ; + +UF_long *cs_dl_amd (UF_long order, const cs_dl *A) ; +cs_dln *cs_dl_chol (const cs_dl *A, const cs_dls *S) ; +cs_dld *cs_dl_dmperm (const cs_dl *A, UF_long seed) ; +UF_long cs_dl_droptol (cs_dl *A, double tol) ; +UF_long cs_dl_dropzeros (cs_dl *A) ; +UF_long cs_dl_happly (const cs_dl *V, UF_long i, double beta, double *x) ; +UF_long cs_dl_ipvec (const UF_long *p, const double *b, double *x, UF_long n) ; +UF_long cs_dl_lsolve (const cs_dl *L, double *x) ; +UF_long cs_dl_ltsolve (const cs_dl *L, double *x) ; +cs_dln *cs_dl_lu (const cs_dl *A, const cs_dls *S, double tol) ; +cs_dl *cs_dl_permute (const cs_dl *A, const UF_long *pinv, const UF_long *q, + UF_long values) ; +UF_long *cs_dl_pinv (const UF_long *p, UF_long n) ; +UF_long cs_dl_pvec (const UF_long *p, const double *b, double *x, UF_long n) ; +cs_dln *cs_dl_qr (const cs_dl *A, const cs_dls *S) ; +cs_dls *cs_dl_schol (UF_long order, const cs_dl *A) ; +cs_dls *cs_dl_sqr (UF_long order, const cs_dl *A, UF_long qr) ; +cs_dl *cs_dl_symperm (const cs_dl *A, const UF_long *pinv, UF_long values) ; +UF_long cs_dl_usolve (const cs_dl *U, double *x) ; +UF_long cs_dl_utsolve (const cs_dl *U, double *x) ; +UF_long cs_dl_updown (cs_dl *L, UF_long sigma, const cs_dl *C, + const UF_long *parent) ; + +/* utilities */ +cs_dls *cs_dl_sfree (cs_dls *S) ; +cs_dln *cs_dl_nfree (cs_dln *N) ; +cs_dld *cs_dl_dfree (cs_dld *D) ; + +/* --- tertiary CSparse routines -------------------------------------------- */ + +UF_long *cs_dl_counts (const cs_dl *A, const UF_long *parent, + const UF_long *post, UF_long ata) ; +double cs_dl_cumsum (UF_long *p, UF_long *c, UF_long n) ; +UF_long cs_dl_dfs (UF_long j, cs_dl *G, UF_long top, UF_long *xi, + UF_long *pstack, const UF_long *pinv) ; +UF_long *cs_dl_etree (const cs_dl *A, UF_long ata) ; +UF_long cs_dl_fkeep (cs_dl *A, + UF_long (*fkeep) (UF_long, UF_long, double, void *), void *other) ; +double cs_dl_house (double *x, double *beta, UF_long n) ; +UF_long *cs_dl_maxtrans (const cs_dl *A, UF_long seed) ; +UF_long *cs_dl_post (const UF_long *parent, UF_long n) ; +cs_dld *cs_dl_scc (cs_dl *A) ; +UF_long cs_dl_scatter (const cs_dl *A, UF_long j, double beta, UF_long *w, + double *x, UF_long mark,cs_dl *C, UF_long nz) ; +UF_long cs_dl_tdfs (UF_long j, UF_long k, UF_long *head, const UF_long *next, + UF_long *post, UF_long *stack) ; +UF_long cs_dl_leaf (UF_long i, UF_long j, const UF_long *first, + UF_long *maxfirst, UF_long *prevleaf, UF_long *ancestor, UF_long *jleaf) ; +UF_long cs_dl_reach (cs_dl *G, const cs_dl *B, UF_long k, UF_long *xi, + const UF_long *pinv) ; +UF_long cs_dl_spsolve (cs_dl *L, const cs_dl *B, UF_long k, UF_long *xi, + double *x, const UF_long *pinv, UF_long lo) ; +UF_long cs_dl_ereach (const cs_dl *A, UF_long k, const UF_long *parent, + UF_long *s, UF_long *w) ; +UF_long *cs_dl_randperm (UF_long n, UF_long seed) ; + +/* utilities */ +cs_dld *cs_dl_dalloc (UF_long m, UF_long n) ; +cs_dl *cs_dl_done (cs_dl *C, void *w, void *x, UF_long ok) ; +UF_long *cs_dl_idone (UF_long *p, cs_dl *C, void *w, UF_long ok) ; +cs_dln *cs_dl_ndone (cs_dln *N, cs_dl *C, void *w, void *x, UF_long ok) ; +cs_dld *cs_dl_ddone (cs_dld *D, cs_dl *C, void *w, UF_long ok) ; + + +/* -------------------------------------------------------------------------- */ +/* complex/int version of CXSparse */ +/* -------------------------------------------------------------------------- */ + +#ifndef NCOMPLEX + +/* --- primary CSparse routines and data structures ------------------------- */ + +typedef struct cs_ci_sparse /* matrix in compressed-column or triplet form */ +{ + int nzmax ; /* maximum number of entries */ + int m ; /* number of rows */ + int n ; /* number of columns */ + int *p ; /* column pointers (size n+1) or col indices (size nzmax) */ + int *i ; /* row indices, size nzmax */ + cs_complex_t *x ; /* numerical values, size nzmax */ + int nz ; /* # of entries in triplet matrix, -1 for compressed-col */ +} cs_ci ; + +cs_ci *cs_ci_add (const cs_ci *A, const cs_ci *B, cs_complex_t alpha, + cs_complex_t beta) ; +int cs_ci_cholsol (int order, const cs_ci *A, cs_complex_t *b) ; +int cs_ci_dupl (cs_ci *A) ; +int cs_ci_entry (cs_ci *T, int i, int j, cs_complex_t x) ; +int cs_ci_lusol (int order, const cs_ci *A, cs_complex_t *b, double tol) ; +int cs_ci_gaxpy (const cs_ci *A, const cs_complex_t *x, cs_complex_t *y) ; +cs_ci *cs_ci_multiply (const cs_ci *A, const cs_ci *B) ; +int cs_ci_qrsol (int order, const cs_ci *A, cs_complex_t *b) ; +cs_ci *cs_ci_transpose (const cs_ci *A, int values) ; +cs_ci *cs_ci_compress (const cs_ci *T) ; +double cs_ci_norm (const cs_ci *A) ; +int cs_ci_print (const cs_ci *A, int brief) ; +cs_ci *cs_ci_load (FILE *f) ; + +/* utilities */ +void *cs_ci_calloc (int n, size_t size) ; +void *cs_ci_free (void *p) ; +void *cs_ci_realloc (void *p, int n, size_t size, int *ok) ; +cs_ci *cs_ci_spalloc (int m, int n, int nzmax, int values, int t) ; +cs_ci *cs_ci_spfree (cs_ci *A) ; +int cs_ci_sprealloc (cs_ci *A, int nzmax) ; +void *cs_ci_malloc (int n, size_t size) ; + +/* --- secondary CSparse routines and data structures ----------------------- */ + +typedef struct cs_ci_symbolic /* symbolic Cholesky, LU, or QR analysis */ +{ + int *pinv ; /* inverse row perm. for QR, fill red. perm for Chol */ + int *q ; /* fill-reducing column permutation for LU and QR */ + int *parent ; /* elimination tree for Cholesky and QR */ + int *cp ; /* column pointers for Cholesky, row counts for QR */ + int *leftmost ; /* leftmost[i] = min(find(A(i,:))), for QR */ + int m2 ; /* # of rows for QR, after adding fictitious rows */ + double lnz ; /* # entries in L for LU or Cholesky; in V for QR */ + double unz ; /* # entries in U for LU; in R for QR */ +} cs_cis ; + +typedef struct cs_ci_numeric /* numeric Cholesky, LU, or QR factorization */ +{ + cs_ci *L ; /* L for LU and Cholesky, V for QR */ + cs_ci *U ; /* U for LU, r for QR, not used for Cholesky */ + int *pinv ; /* partial pivoting for LU */ + double *B ; /* beta [0..n-1] for QR */ +} cs_cin ; + +typedef struct cs_ci_dmperm_results /* cs_ci_dmperm or cs_ci_scc output */ +{ + int *p ; /* size m, row permutation */ + int *q ; /* size n, column permutation */ + int *r ; /* size nb+1, block k is rows r[k] to r[k+1]-1 in A(p,q) */ + int *s ; /* size nb+1, block k is cols s[k] to s[k+1]-1 in A(p,q) */ + int nb ; /* # of blocks in fine dmperm decomposition */ + int rr [5] ; /* coarse row decomposition */ + int cc [5] ; /* coarse column decomposition */ +} cs_cid ; + +int *cs_ci_amd (int order, const cs_ci *A) ; +cs_cin *cs_ci_chol (const cs_ci *A, const cs_cis *S) ; +cs_cid *cs_ci_dmperm (const cs_ci *A, int seed) ; +int cs_ci_droptol (cs_ci *A, double tol) ; +int cs_ci_dropzeros (cs_ci *A) ; +int cs_ci_happly (const cs_ci *V, int i, double beta, cs_complex_t *x) ; +int cs_ci_ipvec (const int *p, const cs_complex_t *b, cs_complex_t *x, int n) ; +int cs_ci_lsolve (const cs_ci *L, cs_complex_t *x) ; +int cs_ci_ltsolve (const cs_ci *L, cs_complex_t *x) ; +cs_cin *cs_ci_lu (const cs_ci *A, const cs_cis *S, double tol) ; +cs_ci *cs_ci_permute (const cs_ci *A, const int *pinv, const int *q, + int values) ; +int *cs_ci_pinv (const int *p, int n) ; +int cs_ci_pvec (const int *p, const cs_complex_t *b, cs_complex_t *x, int n) ; +cs_cin *cs_ci_qr (const cs_ci *A, const cs_cis *S) ; +cs_cis *cs_ci_schol (int order, const cs_ci *A) ; +cs_cis *cs_ci_sqr (int order, const cs_ci *A, int qr) ; +cs_ci *cs_ci_symperm (const cs_ci *A, const int *pinv, int values) ; +int cs_ci_usolve (const cs_ci *U, cs_complex_t *x) ; +int cs_ci_utsolve (const cs_ci *U, cs_complex_t *x) ; +int cs_ci_updown (cs_ci *L, int sigma, const cs_ci *C, const int *parent) ; + +/* utilities */ +cs_cis *cs_ci_sfree (cs_cis *S) ; +cs_cin *cs_ci_nfree (cs_cin *N) ; +cs_cid *cs_ci_dfree (cs_cid *D) ; + +/* --- tertiary CSparse routines -------------------------------------------- */ + +int *cs_ci_counts (const cs_ci *A, const int *parent, const int *post, + int ata) ; +double cs_ci_cumsum (int *p, int *c, int n) ; +int cs_ci_dfs (int j, cs_ci *G, int top, int *xi, int *pstack, + const int *pinv) ; +int *cs_ci_etree (const cs_ci *A, int ata) ; +int cs_ci_fkeep (cs_ci *A, int (*fkeep) (int, int, cs_complex_t, void *), + void *other) ; +cs_complex_t cs_ci_house (cs_complex_t *x, double *beta, int n) ; +int *cs_ci_maxtrans (const cs_ci *A, int seed) ; +int *cs_ci_post (const int *parent, int n) ; +cs_cid *cs_ci_scc (cs_ci *A) ; +int cs_ci_scatter (const cs_ci *A, int j, cs_complex_t beta, int *w, + cs_complex_t *x, int mark,cs_ci *C, int nz) ; +int cs_ci_tdfs (int j, int k, int *head, const int *next, int *post, + int *stack) ; +int cs_ci_leaf (int i, int j, const int *first, int *maxfirst, int *prevleaf, + int *ancestor, int *jleaf) ; +int cs_ci_reach (cs_ci *G, const cs_ci *B, int k, int *xi, const int *pinv) ; +int cs_ci_spsolve (cs_ci *L, const cs_ci *B, int k, int *xi, + cs_complex_t *x, const int *pinv, int lo) ; +int cs_ci_ereach (const cs_ci *A, int k, const int *parent, int *s, int *w) ; +int *cs_ci_randperm (int n, int seed) ; + +/* utilities */ +cs_cid *cs_ci_dalloc (int m, int n) ; +cs_ci *cs_ci_done (cs_ci *C, void *w, void *x, int ok) ; +int *cs_ci_idone (int *p, cs_ci *C, void *w, int ok) ; +cs_cin *cs_ci_ndone (cs_cin *N, cs_ci *C, void *w, void *x, int ok) ; +cs_cid *cs_ci_ddone (cs_cid *D, cs_ci *C, void *w, int ok) ; + + +/* -------------------------------------------------------------------------- */ +/* complex/UF_long version of CXSparse */ +/* -------------------------------------------------------------------------- */ + +/* --- primary CSparse routines and data structures ------------------------- */ + +typedef struct cs_cl_sparse /* matrix in compressed-column or triplet form */ +{ + UF_long nzmax ; /* maximum number of entries */ + UF_long m ; /* number of rows */ + UF_long n ; /* number of columns */ + UF_long *p ; /* column pointers (size n+1) or col indlces (size nzmax) */ + UF_long *i ; /* row indices, size nzmax */ + cs_complex_t *x ; /* numerical values, size nzmax */ + UF_long nz ; /* # of entries in triplet matrix, -1 for compressed-col */ +} cs_cl ; + +cs_cl *cs_cl_add (const cs_cl *A, const cs_cl *B, cs_complex_t alpha, + cs_complex_t beta) ; +UF_long cs_cl_cholsol (UF_long order, const cs_cl *A, cs_complex_t *b) ; +UF_long cs_cl_dupl (cs_cl *A) ; +UF_long cs_cl_entry (cs_cl *T, UF_long i, UF_long j, cs_complex_t x) ; +UF_long cs_cl_lusol (UF_long order, const cs_cl *A, cs_complex_t *b, + double tol) ; +UF_long cs_cl_gaxpy (const cs_cl *A, const cs_complex_t *x, cs_complex_t *y) ; +cs_cl *cs_cl_multiply (const cs_cl *A, const cs_cl *B) ; +UF_long cs_cl_qrsol (UF_long order, const cs_cl *A, cs_complex_t *b) ; +cs_cl *cs_cl_transpose (const cs_cl *A, UF_long values) ; +cs_cl *cs_cl_compress (const cs_cl *T) ; +double cs_cl_norm (const cs_cl *A) ; +UF_long cs_cl_print (const cs_cl *A, UF_long brief) ; +cs_cl *cs_cl_load (FILE *f) ; + +/* utilities */ +void *cs_cl_calloc (UF_long n, size_t size) ; +void *cs_cl_free (void *p) ; +void *cs_cl_realloc (void *p, UF_long n, size_t size, UF_long *ok) ; +cs_cl *cs_cl_spalloc (UF_long m, UF_long n, UF_long nzmax, UF_long values, + UF_long t) ; +cs_cl *cs_cl_spfree (cs_cl *A) ; +UF_long cs_cl_sprealloc (cs_cl *A, UF_long nzmax) ; +void *cs_cl_malloc (UF_long n, size_t size) ; + +/* --- secondary CSparse routines and data structures ----------------------- */ + +typedef struct cs_cl_symbolic /* symbolic Cholesky, LU, or QR analysis */ +{ + UF_long *pinv ; /* inverse row perm. for QR, fill red. perm for Chol */ + UF_long *q ; /* fill-reducing column permutation for LU and QR */ + UF_long *parent ; /* elimination tree for Cholesky and QR */ + UF_long *cp ; /* column pointers for Cholesky, row counts for QR */ + UF_long *leftmost ; /* leftmost[i] = min(find(A(i,:))), for QR */ + UF_long m2 ; /* # of rows for QR, after adding fictitious rows */ + double lnz ; /* # entries in L for LU or Cholesky; in V for QR */ + double unz ; /* # entries in U for LU; in R for QR */ +} cs_cls ; + +typedef struct cs_cl_numeric /* numeric Cholesky, LU, or QR factorization */ +{ + cs_cl *L ; /* L for LU and Cholesky, V for QR */ + cs_cl *U ; /* U for LU, r for QR, not used for Cholesky */ + UF_long *pinv ; /* partial pivoting for LU */ + double *B ; /* beta [0..n-1] for QR */ +} cs_cln ; + +typedef struct cs_cl_dmperm_results /* cs_cl_dmperm or cs_cl_scc output */ +{ + UF_long *p ; /* size m, row permutation */ + UF_long *q ; /* size n, column permutation */ + UF_long *r ; /* size nb+1, block k is rows r[k] to r[k+1]-1 in A(p,q) */ + UF_long *s ; /* size nb+1, block k is cols s[k] to s[k+1]-1 in A(p,q) */ + UF_long nb ; /* # of blocks in fine dmperm decomposition */ + UF_long rr [5] ; /* coarse row decomposition */ + UF_long cc [5] ; /* coarse column decomposition */ +} cs_cld ; + +UF_long *cs_cl_amd (UF_long order, const cs_cl *A) ; +cs_cln *cs_cl_chol (const cs_cl *A, const cs_cls *S) ; +cs_cld *cs_cl_dmperm (const cs_cl *A, UF_long seed) ; +UF_long cs_cl_droptol (cs_cl *A, double tol) ; +UF_long cs_cl_dropzeros (cs_cl *A) ; +UF_long cs_cl_happly (const cs_cl *V, UF_long i, double beta, cs_complex_t *x) ; +UF_long cs_cl_ipvec (const UF_long *p, const cs_complex_t *b, + cs_complex_t *x, UF_long n) ; +UF_long cs_cl_lsolve (const cs_cl *L, cs_complex_t *x) ; +UF_long cs_cl_ltsolve (const cs_cl *L, cs_complex_t *x) ; +cs_cln *cs_cl_lu (const cs_cl *A, const cs_cls *S, double tol) ; +cs_cl *cs_cl_permute (const cs_cl *A, const UF_long *pinv, const UF_long *q, + UF_long values) ; +UF_long *cs_cl_pinv (const UF_long *p, UF_long n) ; +UF_long cs_cl_pvec (const UF_long *p, const cs_complex_t *b, + cs_complex_t *x, UF_long n) ; +cs_cln *cs_cl_qr (const cs_cl *A, const cs_cls *S) ; +cs_cls *cs_cl_schol (UF_long order, const cs_cl *A) ; +cs_cls *cs_cl_sqr (UF_long order, const cs_cl *A, UF_long qr) ; +cs_cl *cs_cl_symperm (const cs_cl *A, const UF_long *pinv, UF_long values) ; +UF_long cs_cl_usolve (const cs_cl *U, cs_complex_t *x) ; +UF_long cs_cl_utsolve (const cs_cl *U, cs_complex_t *x) ; +UF_long cs_cl_updown (cs_cl *L, UF_long sigma, const cs_cl *C, + const UF_long *parent) ; + +/* utilities */ +cs_cls *cs_cl_sfree (cs_cls *S) ; +cs_cln *cs_cl_nfree (cs_cln *N) ; +cs_cld *cs_cl_dfree (cs_cld *D) ; + +/* --- tertiary CSparse routines -------------------------------------------- */ + +UF_long *cs_cl_counts (const cs_cl *A, const UF_long *parent, + const UF_long *post, UF_long ata) ; +double cs_cl_cumsum (UF_long *p, UF_long *c, UF_long n) ; +UF_long cs_cl_dfs (UF_long j, cs_cl *G, UF_long top, UF_long *xi, + UF_long *pstack, const UF_long *pinv) ; +UF_long *cs_cl_etree (const cs_cl *A, UF_long ata) ; +UF_long cs_cl_fkeep (cs_cl *A, + UF_long (*fkeep) (UF_long, UF_long, cs_complex_t, void *), void *other) ; +cs_complex_t cs_cl_house (cs_complex_t *x, double *beta, UF_long n) ; +UF_long *cs_cl_maxtrans (const cs_cl *A, UF_long seed) ; +UF_long *cs_cl_post (const UF_long *parent, UF_long n) ; +cs_cld *cs_cl_scc (cs_cl *A) ; +UF_long cs_cl_scatter (const cs_cl *A, UF_long j, cs_complex_t beta, + UF_long *w, cs_complex_t *x, UF_long mark,cs_cl *C, UF_long nz) ; +UF_long cs_cl_tdfs (UF_long j, UF_long k, UF_long *head, const UF_long *next, + UF_long *post, UF_long *stack) ; +UF_long cs_cl_leaf (UF_long i, UF_long j, const UF_long *first, + UF_long *maxfirst, UF_long *prevleaf, UF_long *ancestor, UF_long *jleaf) ; +UF_long cs_cl_reach (cs_cl *G, const cs_cl *B, UF_long k, UF_long *xi, + const UF_long *pinv) ; +UF_long cs_cl_spsolve (cs_cl *L, const cs_cl *B, UF_long k, UF_long *xi, + cs_complex_t *x, const UF_long *pinv, UF_long lo) ; +UF_long cs_cl_ereach (const cs_cl *A, UF_long k, const UF_long *parent, + UF_long *s, UF_long *w) ; +UF_long *cs_cl_randperm (UF_long n, UF_long seed) ; + +/* utilities */ +cs_cld *cs_cl_dalloc (UF_long m, UF_long n) ; +cs_cl *cs_cl_done (cs_cl *C, void *w, void *x, UF_long ok) ; +UF_long *cs_cl_idone (UF_long *p, cs_cl *C, void *w, UF_long ok) ; +cs_cln *cs_cl_ndone (cs_cln *N, cs_cl *C, void *w, void *x, UF_long ok) ; +cs_cld *cs_cl_ddone (cs_cld *D, cs_cl *C, void *w, UF_long ok) ; + +#endif + +/* -------------------------------------------------------------------------- */ +/* Macros for constructing each version of CSparse */ +/* -------------------------------------------------------------------------- */ + +#ifdef CS_LONG +#define CS_INT UF_long +#define CS_INT_MAX UF_long_max +#define CS_ID UF_long_id +#ifdef CS_COMPLEX +#define CS_ENTRY cs_complex_t +#define CS_NAME(nm) cs_cl ## nm +#define cs cs_cl +#else +#define CS_ENTRY double +#define CS_NAME(nm) cs_dl ## nm +#define cs cs_dl +#endif +#else +#define CS_INT int +#define CS_INT_MAX INT_MAX +#define CS_ID "%d" +#ifdef CS_COMPLEX +#define CS_ENTRY cs_complex_t +#define CS_NAME(nm) cs_ci ## nm +#define cs cs_ci +#else +#define CS_ENTRY double +#define CS_NAME(nm) cs_di ## nm +#define cs cs_di +#endif +#endif + +#ifdef CS_COMPLEX +#define CS_REAL(x) creal(x) +#define CS_IMAG(x) cimag(x) +#define CS_CONJ(x) conj(x) +#define CS_ABS(x) cabs(x) +#else +#define CS_REAL(x) (x) +#define CS_IMAG(x) (0.) +#define CS_CONJ(x) (x) +#define CS_ABS(x) fabs(x) +#endif + +#define CS_MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define CS_MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define CS_FLIP(i) (-(i)-2) +#define CS_UNFLIP(i) (((i) < 0) ? CS_FLIP(i) : (i)) +#define CS_MARKED(w,j) (w [j] < 0) +#define CS_MARK(w,j) { w [j] = CS_FLIP (w [j]) ; } +#define CS_CSC(A) (A && (A->nz == -1)) +#define CS_TRIPLET(A) (A && (A->nz >= 0)) + +/* --- primary CSparse routines and data structures ------------------------- */ + +#define cs_add CS_NAME (_add) +#define cs_cholsol CS_NAME (_cholsol) +#define cs_dupl CS_NAME (_dupl) +#define cs_entry CS_NAME (_entry) +#define cs_lusol CS_NAME (_lusol) +#define cs_gaxpy CS_NAME (_gaxpy) +#define cs_multiply CS_NAME (_multiply) +#define cs_qrsol CS_NAME (_qrsol) +#define cs_transpose CS_NAME (_transpose) +#define cs_compress CS_NAME (_compress) +#define cs_norm CS_NAME (_norm) +#define cs_print CS_NAME (_print) +#define cs_load CS_NAME (_load) + +/* utilities */ +#define cs_calloc CS_NAME (_calloc) +#define cs_free CS_NAME (_free) +#define cs_realloc CS_NAME (_realloc) +#define cs_spalloc CS_NAME (_spalloc) +#define cs_spfree CS_NAME (_spfree) +#define cs_sprealloc CS_NAME (_sprealloc) +#define cs_malloc CS_NAME (_malloc) + +/* --- secondary CSparse routines and data structures ----------------------- */ +#define css CS_NAME (s) +#define csn CS_NAME (n) +#define csd CS_NAME (d) + +#define cs_amd CS_NAME (_amd) +#define cs_chol CS_NAME (_chol) +#define cs_dmperm CS_NAME (_dmperm) +#define cs_droptol CS_NAME (_droptol) +#define cs_dropzeros CS_NAME (_dropzeros) +#define cs_happly CS_NAME (_happly) +#define cs_ipvec CS_NAME (_ipvec) +#define cs_lsolve CS_NAME (_lsolve) +#define cs_ltsolve CS_NAME (_ltsolve) +#define cs_lu CS_NAME (_lu) +#define cs_permute CS_NAME (_permute) +#define cs_pinv CS_NAME (_pinv) +#define cs_pvec CS_NAME (_pvec) +#define cs_qr CS_NAME (_qr) +#define cs_schol CS_NAME (_schol) +#define cs_sqr CS_NAME (_sqr) +#define cs_symperm CS_NAME (_symperm) +#define cs_usolve CS_NAME (_usolve) +#define cs_utsolve CS_NAME (_utsolve) +#define cs_updown CS_NAME (_updown) + +/* utilities */ +#define cs_sfree CS_NAME (_sfree) +#define cs_nfree CS_NAME (_nfree) +#define cs_dfree CS_NAME (_dfree) + +/* --- tertiary CSparse routines -------------------------------------------- */ +#define cs_counts CS_NAME (_counts) +#define cs_cumsum CS_NAME (_cumsum) +#define cs_dfs CS_NAME (_dfs) +#define cs_etree CS_NAME (_etree) +#define cs_fkeep CS_NAME (_fkeep) +#define cs_house CS_NAME (_house) +#define cs_invmatch CS_NAME (_invmatch) +#define cs_maxtrans CS_NAME (_maxtrans) +#define cs_post CS_NAME (_post) +#define cs_scc CS_NAME (_scc) +#define cs_scatter CS_NAME (_scatter) +#define cs_tdfs CS_NAME (_tdfs) +#define cs_reach CS_NAME (_reach) +#define cs_spsolve CS_NAME (_spsolve) +#define cs_ereach CS_NAME (_ereach) +#define cs_randperm CS_NAME (_randperm) +#define cs_leaf CS_NAME (_leaf) + +/* utilities */ +#define cs_dalloc CS_NAME (_dalloc) +#define cs_done CS_NAME (_done) +#define cs_idone CS_NAME (_idone) +#define cs_ndone CS_NAME (_ndone) +#define cs_ddone CS_NAME (_ddone) + +/* -------------------------------------------------------------------------- */ +/* Conversion routines */ +/* -------------------------------------------------------------------------- */ + +#ifndef NCOMPLEX +cs_di *cs_i_real (cs_ci *A, int real) ; +cs_ci *cs_i_complex (cs_di *A, int real) ; +cs_dl *cs_l_real (cs_cl *A, UF_long real) ; +cs_cl *cs_l_complex (cs_dl *A, UF_long real) ; +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/cs/cs_add.c b/src/cs/cs_add.c new file mode 100644 index 0000000..0bb7b4d --- /dev/null +++ b/src/cs/cs_add.c @@ -0,0 +1,48 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* C = alpha*A + beta*B */ +cs *cs_add (const cs *A, const cs *B, CS_ENTRY alpha, CS_ENTRY beta) +{ + CS_INT p, j, nz = 0, anz, *Cp, *Ci, *Bp, m, n, bnz, *w, values ; + CS_ENTRY *x, *Bx, *Cx ; + cs *C ; + if (!CS_CSC (A) || !CS_CSC (B)) return (NULL) ; /* check inputs */ + if (A->m != B->m || A->n != B->n) return (NULL) ; + m = A->m ; anz = A->p [A->n] ; + n = B->n ; Bp = B->p ; Bx = B->x ; bnz = Bp [n] ; + w = cs_calloc (m, sizeof (CS_INT)) ; /* get workspace */ + values = (A->x != NULL) && (Bx != NULL) ; + x = values ? cs_malloc (m, sizeof (CS_ENTRY)) : NULL ; /* get workspace */ + C = cs_spalloc (m, n, anz + bnz, values, 0) ; /* allocate result*/ + if (!C || !w || (values && !x)) return (cs_done (C, w, x, 0)) ; + Cp = C->p ; Ci = C->i ; Cx = C->x ; + for (j = 0 ; j < n ; j++) + { + Cp [j] = nz ; /* column j of C starts here */ + nz = cs_scatter (A, j, alpha, w, x, j+1, C, nz) ; /* alpha*A(:,j)*/ + nz = cs_scatter (B, j, beta, w, x, j+1, C, nz) ; /* beta*B(:,j) */ + if (values) for (p = Cp [j] ; p < nz ; p++) Cx [p] = x [Ci [p]] ; + } + Cp [n] = nz ; /* finalize the last column of C */ + cs_sprealloc (C, 0) ; /* remove extra space from C */ + return (cs_done (C, w, x, 1)) ; /* success; free workspace, return C */ +} diff --git a/src/cs/cs_amd.c b/src/cs/cs_amd.c new file mode 100644 index 0000000..791c354 --- /dev/null +++ b/src/cs/cs_amd.c @@ -0,0 +1,384 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* clear w */ +static CS_INT cs_wclear (CS_INT mark, CS_INT lemax, CS_INT *w, CS_INT n) +{ + CS_INT k ; + if (mark < 2 || (mark + lemax < 0)) + { + for (k = 0 ; k < n ; k++) if (w [k] != 0) w [k] = 1 ; + mark = 2 ; + } + return (mark) ; /* at this point, w [0..n-1] < mark holds */ +} + +/* keep off-diagonal entries; drop diagonal entries */ +static CS_INT cs_diag (CS_INT i, CS_INT j, CS_ENTRY aij, void *other) { return (i != j) ; } + +/* p = amd(A+A') if symmetric is true, or amd(A'A) otherwise */ +CS_INT *cs_amd (CS_INT order, const cs *A) /* order 0:natural, 1:Chol, 2:LU, 3:QR */ +{ + cs *C, *A2, *AT ; + CS_INT *Cp, *Ci, *last, *W, *len, *nv, *next, *P, *head, *elen, *degree, *w, + *hhead, *ATp, *ATi, d, dk, dext, lemax = 0, e, elenk, eln, i, j, k, k1, + k2, k3, jlast, ln, dense, nzmax, mindeg = 0, nvi, nvj, nvk, mark, wnvi, + ok, cnz, nel = 0, p, p1, p2, p3, p4, pj, pk, pk1, pk2, pn, q, n, m, t ; + unsigned CS_INT h ; + /* --- Construct matrix C ----------------------------------------------- */ + if (!CS_CSC (A) || order <= 0 || order > 3) return (NULL) ; /* check */ + AT = cs_transpose (A, 0) ; /* compute A' */ + if (!AT) return (NULL) ; + m = A->m ; n = A->n ; + dense = CS_MAX (16, 10 * sqrt ((double) n)) ; /* find dense threshold */ + dense = CS_MIN (n-2, dense) ; + if (order == 1 && n == m) + { + C = cs_add (A, AT, 0, 0) ; /* C = A+A' */ + } + else if (order == 2) + { + ATp = AT->p ; /* drop dense columns from AT */ + ATi = AT->i ; + for (p2 = 0, j = 0 ; j < m ; j++) + { + p = ATp [j] ; /* column j of AT starts here */ + ATp [j] = p2 ; /* new column j starts here */ + if (ATp [j+1] - p > dense) continue ; /* skip dense col j */ + for ( ; p < ATp [j+1] ; p++) ATi [p2++] = ATi [p] ; + } + ATp [m] = p2 ; /* finalize AT */ + A2 = cs_transpose (AT, 0) ; /* A2 = AT' */ + C = A2 ? cs_multiply (AT, A2) : NULL ; /* C=A'*A with no dense rows */ + cs_spfree (A2) ; + } + else + { + C = cs_multiply (AT, A) ; /* C=A'*A */ + } + cs_spfree (AT) ; + if (!C) return (NULL) ; + cs_fkeep (C, &cs_diag, NULL) ; /* drop diagonal entries */ + Cp = C->p ; + cnz = Cp [n] ; + P = cs_malloc (n+1, sizeof (CS_INT)) ; /* allocate result */ + W = cs_malloc (8*(n+1), sizeof (CS_INT)) ; /* get workspace */ + t = cnz + cnz/5 + 2*n ; /* add elbow room to C */ + if (!P || !W || !cs_sprealloc (C, t)) return (cs_idone (P, C, W, 0)) ; + len = W ; nv = W + (n+1) ; next = W + 2*(n+1) ; + head = W + 3*(n+1) ; elen = W + 4*(n+1) ; degree = W + 5*(n+1) ; + w = W + 6*(n+1) ; hhead = W + 7*(n+1) ; + last = P ; /* use P as workspace for last */ + /* --- Initialize quotient graph ---------------------------------------- */ + for (k = 0 ; k < n ; k++) len [k] = Cp [k+1] - Cp [k] ; + len [n] = 0 ; + nzmax = C->nzmax ; + Ci = C->i ; + for (i = 0 ; i <= n ; i++) + { + head [i] = -1 ; /* degree list i is empty */ + last [i] = -1 ; + next [i] = -1 ; + hhead [i] = -1 ; /* hash list i is empty */ + nv [i] = 1 ; /* node i is just one node */ + w [i] = 1 ; /* node i is alive */ + elen [i] = 0 ; /* Ek of node i is empty */ + degree [i] = len [i] ; /* degree of node i */ + } + mark = cs_wclear (0, 0, w, n) ; /* clear w */ + elen [n] = -2 ; /* n is a dead element */ + Cp [n] = -1 ; /* n is a root of assembly tree */ + w [n] = 0 ; /* n is a dead element */ + /* --- Initialize degree lists ------------------------------------------ */ + for (i = 0 ; i < n ; i++) + { + d = degree [i] ; + if (d == 0) /* node i is empty */ + { + elen [i] = -2 ; /* element i is dead */ + nel++ ; + Cp [i] = -1 ; /* i is a root of assembly tree */ + w [i] = 0 ; + } + else if (d > dense) /* node i is dense */ + { + nv [i] = 0 ; /* absorb i into element n */ + elen [i] = -1 ; /* node i is dead */ + nel++ ; + Cp [i] = CS_FLIP (n) ; + nv [n]++ ; + } + else + { + if (head [d] != -1) last [head [d]] = i ; + next [i] = head [d] ; /* put node i in degree list d */ + head [d] = i ; + } + } + while (nel < n) /* while (selecting pivots) do */ + { + /* --- Select node of minimum approximate degree -------------------- */ + for (k = -1 ; mindeg < n && (k = head [mindeg]) == -1 ; mindeg++) ; + if (next [k] != -1) last [next [k]] = -1 ; + head [mindeg] = next [k] ; /* remove k from degree list */ + elenk = elen [k] ; /* elenk = |Ek| */ + nvk = nv [k] ; /* # of nodes k represents */ + nel += nvk ; /* nv[k] nodes of A eliminated */ + /* --- Garbage collection ------------------------------------------- */ + if (elenk > 0 && cnz + mindeg >= nzmax) + { + for (j = 0 ; j < n ; j++) + { + if ((p = Cp [j]) >= 0) /* j is a live node or element */ + { + Cp [j] = Ci [p] ; /* save first entry of object */ + Ci [p] = CS_FLIP (j) ; /* first entry is now CS_FLIP(j) */ + } + } + for (q = 0, p = 0 ; p < cnz ; ) /* scan all of memory */ + { + if ((j = CS_FLIP (Ci [p++])) >= 0) /* found object j */ + { + Ci [q] = Cp [j] ; /* restore first entry of object */ + Cp [j] = q++ ; /* new pointer to object j */ + for (k3 = 0 ; k3 < len [j]-1 ; k3++) Ci [q++] = Ci [p++] ; + } + } + cnz = q ; /* Ci [cnz...nzmax-1] now free */ + } + /* --- Construct new element ---------------------------------------- */ + dk = 0 ; + nv [k] = -nvk ; /* flag k as in Lk */ + p = Cp [k] ; + pk1 = (elenk == 0) ? p : cnz ; /* do in place if elen[k] == 0 */ + pk2 = pk1 ; + for (k1 = 1 ; k1 <= elenk + 1 ; k1++) + { + if (k1 > elenk) + { + e = k ; /* search the nodes in k */ + pj = p ; /* list of nodes starts at Ci[pj]*/ + ln = len [k] - elenk ; /* length of list of nodes in k */ + } + else + { + e = Ci [p++] ; /* search the nodes in e */ + pj = Cp [e] ; + ln = len [e] ; /* length of list of nodes in e */ + } + for (k2 = 1 ; k2 <= ln ; k2++) + { + i = Ci [pj++] ; + if ((nvi = nv [i]) <= 0) continue ; /* node i dead, or seen */ + dk += nvi ; /* degree[Lk] += size of node i */ + nv [i] = -nvi ; /* negate nv[i] to denote i in Lk*/ + Ci [pk2++] = i ; /* place i in Lk */ + if (next [i] != -1) last [next [i]] = last [i] ; + if (last [i] != -1) /* remove i from degree list */ + { + next [last [i]] = next [i] ; + } + else + { + head [degree [i]] = next [i] ; + } + } + if (e != k) + { + Cp [e] = CS_FLIP (k) ; /* absorb e into k */ + w [e] = 0 ; /* e is now a dead element */ + } + } + if (elenk != 0) cnz = pk2 ; /* Ci [cnz...nzmax] is free */ + degree [k] = dk ; /* external degree of k - |Lk\i| */ + Cp [k] = pk1 ; /* element k is in Ci[pk1..pk2-1] */ + len [k] = pk2 - pk1 ; + elen [k] = -2 ; /* k is now an element */ + /* --- Find set differences ----------------------------------------- */ + mark = cs_wclear (mark, lemax, w, n) ; /* clear w if necessary */ + for (pk = pk1 ; pk < pk2 ; pk++) /* scan 1: find |Le\Lk| */ + { + i = Ci [pk] ; + if ((eln = elen [i]) <= 0) continue ;/* skip if elen[i] empty */ + nvi = -nv [i] ; /* nv [i] was negated */ + wnvi = mark - nvi ; + for (p = Cp [i] ; p <= Cp [i] + eln - 1 ; p++) /* scan Ei */ + { + e = Ci [p] ; + if (w [e] >= mark) + { + w [e] -= nvi ; /* decrement |Le\Lk| */ + } + else if (w [e] != 0) /* ensure e is a live element */ + { + w [e] = degree [e] + wnvi ; /* 1st time e seen in scan 1 */ + } + } + } + /* --- Degree update ------------------------------------------------ */ + for (pk = pk1 ; pk < pk2 ; pk++) /* scan2: degree update */ + { + i = Ci [pk] ; /* consider node i in Lk */ + p1 = Cp [i] ; + p2 = p1 + elen [i] - 1 ; + pn = p1 ; + for (h = 0, d = 0, p = p1 ; p <= p2 ; p++) /* scan Ei */ + { + e = Ci [p] ; + if (w [e] != 0) /* e is an unabsorbed element */ + { + dext = w [e] - mark ; /* dext = |Le\Lk| */ + if (dext > 0) + { + d += dext ; /* sum up the set differences */ + Ci [pn++] = e ; /* keep e in Ei */ + h += e ; /* compute the hash of node i */ + } + else + { + Cp [e] = CS_FLIP (k) ; /* aggressive absorb. e->k */ + w [e] = 0 ; /* e is a dead element */ + } + } + } + elen [i] = pn - p1 + 1 ; /* elen[i] = |Ei| */ + p3 = pn ; + p4 = p1 + len [i] ; + for (p = p2 + 1 ; p < p4 ; p++) /* prune edges in Ai */ + { + j = Ci [p] ; + if ((nvj = nv [j]) <= 0) continue ; /* node j dead or in Lk */ + d += nvj ; /* degree(i) += |j| */ + Ci [pn++] = j ; /* place j in node list of i */ + h += j ; /* compute hash for node i */ + } + if (d == 0) /* check for mass elimination */ + { + Cp [i] = CS_FLIP (k) ; /* absorb i into k */ + nvi = -nv [i] ; + dk -= nvi ; /* |Lk| -= |i| */ + nvk += nvi ; /* |k| += nv[i] */ + nel += nvi ; + nv [i] = 0 ; + elen [i] = -1 ; /* node i is dead */ + } + else + { + degree [i] = CS_MIN (degree [i], d) ; /* update degree(i) */ + Ci [pn] = Ci [p3] ; /* move first node to end */ + Ci [p3] = Ci [p1] ; /* move 1st el. to end of Ei */ + Ci [p1] = k ; /* add k as 1st element in of Ei */ + len [i] = pn - p1 + 1 ; /* new len of adj. list of node i */ + h %= n ; /* finalize hash of i */ + next [i] = hhead [h] ; /* place i in hash bucket */ + hhead [h] = i ; + last [i] = h ; /* save hash of i in last[i] */ + } + } /* scan2 is done */ + degree [k] = dk ; /* finalize |Lk| */ + lemax = CS_MAX (lemax, dk) ; + mark = cs_wclear (mark+lemax, lemax, w, n) ; /* clear w */ + /* --- Supernode detection ------------------------------------------ */ + for (pk = pk1 ; pk < pk2 ; pk++) + { + i = Ci [pk] ; + if (nv [i] >= 0) continue ; /* skip if i is dead */ + h = last [i] ; /* scan hash bucket of node i */ + i = hhead [h] ; + hhead [h] = -1 ; /* hash bucket will be empty */ + for ( ; i != -1 && next [i] != -1 ; i = next [i], mark++) + { + ln = len [i] ; + eln = elen [i] ; + for (p = Cp [i]+1 ; p <= Cp [i] + ln-1 ; p++) w [Ci [p]] = mark; + jlast = i ; + for (j = next [i] ; j != -1 ; ) /* compare i with all j */ + { + ok = (len [j] == ln) && (elen [j] == eln) ; + for (p = Cp [j] + 1 ; ok && p <= Cp [j] + ln - 1 ; p++) + { + if (w [Ci [p]] != mark) ok = 0 ; /* compare i and j*/ + } + if (ok) /* i and j are identical */ + { + Cp [j] = CS_FLIP (i) ; /* absorb j into i */ + nv [i] += nv [j] ; + nv [j] = 0 ; + elen [j] = -1 ; /* node j is dead */ + j = next [j] ; /* delete j from hash bucket */ + next [jlast] = j ; + } + else + { + jlast = j ; /* j and i are different */ + j = next [j] ; + } + } + } + } + /* --- Finalize new element------------------------------------------ */ + for (p = pk1, pk = pk1 ; pk < pk2 ; pk++) /* finalize Lk */ + { + i = Ci [pk] ; + if ((nvi = -nv [i]) <= 0) continue ;/* skip if i is dead */ + nv [i] = nvi ; /* restore nv[i] */ + d = degree [i] + dk - nvi ; /* compute external degree(i) */ + d = CS_MIN (d, n - nel - nvi) ; + if (head [d] != -1) last [head [d]] = i ; + next [i] = head [d] ; /* put i back in degree list */ + last [i] = -1 ; + head [d] = i ; + mindeg = CS_MIN (mindeg, d) ; /* find new minimum degree */ + degree [i] = d ; + Ci [p++] = i ; /* place i in Lk */ + } + nv [k] = nvk ; /* # nodes absorbed into k */ + if ((len [k] = p-pk1) == 0) /* length of adj list of element k*/ + { + Cp [k] = -1 ; /* k is a root of the tree */ + w [k] = 0 ; /* k is now a dead element */ + } + if (elenk != 0) cnz = p ; /* free unused space in Lk */ + } + /* --- Postordering ----------------------------------------------------- */ + for (i = 0 ; i < n ; i++) Cp [i] = CS_FLIP (Cp [i]) ;/* fix assembly tree */ + for (j = 0 ; j <= n ; j++) head [j] = -1 ; + for (j = n ; j >= 0 ; j--) /* place unordered nodes in lists */ + { + if (nv [j] > 0) continue ; /* skip if j is an element */ + next [j] = head [Cp [j]] ; /* place j in list of its parent */ + head [Cp [j]] = j ; + } + for (e = n ; e >= 0 ; e--) /* place elements in lists */ + { + if (nv [e] <= 0) continue ; /* skip unless e is an element */ + if (Cp [e] != -1) + { + next [e] = head [Cp [e]] ; /* place e in list of its parent */ + head [Cp [e]] = e ; + } + } + for (k = 0, i = 0 ; i <= n ; i++) /* postorder the assembly tree */ + { + if (Cp [i] == -1) k = cs_tdfs (i, k, head, next, P, w) ; + } + return (cs_idone (P, C, W, 1)) ; +} diff --git a/src/cs/cs_chol.c b/src/cs/cs_chol.c new file mode 100644 index 0000000..489a7d0 --- /dev/null +++ b/src/cs/cs_chol.c @@ -0,0 +1,79 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* L = chol (A, [pinv parent cp]), pinv is optional */ +csn *cs_chol (const cs *A, const css *S) +{ + CS_ENTRY d, lki, *Lx, *x, *Cx ; + CS_INT top, i, p, k, n, *Li, *Lp, *cp, *pinv, *s, *c, *parent, *Cp, *Ci ; + cs *L, *C, *E ; + csn *N ; + if (!CS_CSC (A) || !S || !S->cp || !S->parent) return (NULL) ; + n = A->n ; + N = cs_calloc (1, sizeof (csn)) ; /* allocate result */ + c = cs_malloc (2*n, sizeof (CS_INT)) ; /* get CS_INT workspace */ + x = cs_malloc (n, sizeof (CS_ENTRY)) ; /* get CS_ENTRY workspace */ + cp = S->cp ; pinv = S->pinv ; parent = S->parent ; + C = pinv ? cs_symperm (A, pinv, 1) : ((cs *) A) ; + E = pinv ? C : NULL ; /* E is alias for A, or a copy E=A(p,p) */ + if (!N || !c || !x || !C) return (cs_ndone (N, E, c, x, 0)) ; + s = c + n ; + Cp = C->p ; Ci = C->i ; Cx = C->x ; + N->L = L = cs_spalloc (n, n, cp [n], 1, 0) ; /* allocate result */ + if (!L) return (cs_ndone (N, E, c, x, 0)) ; + Lp = L->p ; Li = L->i ; Lx = L->x ; + for (k = 0 ; k < n ; k++) Lp [k] = c [k] = cp [k] ; + for (k = 0 ; k < n ; k++) /* compute L(k,:) for L*L' = C */ + { + /* --- Nonzero pattern of L(k,:) ------------------------------------ */ + top = cs_ereach (C, k, parent, s, c) ; /* find pattern of L(k,:) */ + x [k] = 0 ; /* x (0:k) is now zero */ + for (p = Cp [k] ; p < Cp [k+1] ; p++) /* x = full(triu(C(:,k))) */ + { + if (Ci [p] <= k) x [Ci [p]] = Cx [p] ; + } + d = x [k] ; /* d = C(k,k) */ + x [k] = 0 ; /* clear x for k+1st iteration */ + /* --- Triangular solve --------------------------------------------- */ + for ( ; top < n ; top++) /* solve L(0:k-1,0:k-1) * x = C(:,k) */ + { + i = s [top] ; /* s [top..n-1] is pattern of L(k,:) */ + lki = x [i] / Lx [Lp [i]] ; /* L(k,i) = x (i) / L(i,i) */ + x [i] = 0 ; /* clear x for k+1st iteration */ + for (p = Lp [i] + 1 ; p < c [i] ; p++) + { + x [Li [p]] -= Lx [p] * lki ; + } + d -= lki * CS_CONJ (lki) ; /* d = d - L(k,i)*L(k,i) */ + p = c [i]++ ; + Li [p] = k ; /* store L(k,i) in column i */ + Lx [p] = CS_CONJ (lki) ; + } + /* --- Compute L(k,k) ----------------------------------------------- */ + if (CS_REAL (d) <= 0 || CS_IMAG (d) != 0) + return (cs_ndone (N, E, c, x, 0)) ; /* not pos def */ + p = c [k]++ ; + Li [p] = k ; /* store L(k,k) = sqrt (d) in column k */ + Lx [p] = sqrt (d) ; + } + Lp [n] = cp [n] ; /* finalize L */ + return (cs_ndone (N, E, c, x, 1)) ; /* success: free E,s,x; return N */ +} diff --git a/src/cs/cs_cholsol.c b/src/cs/cs_cholsol.c new file mode 100644 index 0000000..45db65a --- /dev/null +++ b/src/cs/cs_cholsol.c @@ -0,0 +1,46 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* x=A\b where A is symmetric positive definite; b overwritten with solution */ +CS_INT cs_cholsol (CS_INT order, const cs *A, CS_ENTRY *b) +{ + CS_ENTRY *x ; + css *S ; + csn *N ; + CS_INT n, ok ; + if (!CS_CSC (A) || !b) return (0) ; /* check inputs */ + n = A->n ; + S = cs_schol (order, A) ; /* ordering and symbolic analysis */ + N = cs_chol (A, S) ; /* numeric Cholesky factorization */ + x = cs_malloc (n, sizeof (CS_ENTRY)) ; /* get workspace */ + ok = (S && N && x) ; + if (ok) + { + cs_ipvec (S->pinv, b, x, n) ; /* x = P*b */ + cs_lsolve (N->L, x) ; /* x = L\x */ + cs_ltsolve (N->L, x) ; /* x = L'\x */ + cs_pvec (S->pinv, x, b, n) ; /* b = P'*x */ + } + cs_free (x) ; + cs_sfree (S) ; + cs_nfree (N) ; + return (ok) ; +} diff --git a/src/cs/cs_compress.c b/src/cs/cs_compress.c new file mode 100644 index 0000000..c9a639a --- /dev/null +++ b/src/cs/cs_compress.c @@ -0,0 +1,42 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* C = compressed-column form of a triplet matrix T */ +cs *cs_compress (const cs *T) +{ + CS_INT m, n, nz, p, k, *Cp, *Ci, *w, *Ti, *Tj ; + CS_ENTRY *Cx, *Tx ; + cs *C ; + if (!CS_TRIPLET (T)) return (NULL) ; /* check inputs */ + m = T->m ; n = T->n ; Ti = T->i ; Tj = T->p ; Tx = T->x ; nz = T->nz ; + C = cs_spalloc (m, n, nz, Tx != NULL, 0) ; /* allocate result */ + w = cs_calloc (n, sizeof (CS_INT)) ; /* get workspace */ + if (!C || !w) return (cs_done (C, w, NULL, 0)) ; /* out of memory */ + Cp = C->p ; Ci = C->i ; Cx = C->x ; + for (k = 0 ; k < nz ; k++) w [Tj [k]]++ ; /* column counts */ + cs_cumsum (Cp, w, n) ; /* column pointers */ + for (k = 0 ; k < nz ; k++) + { + Ci [p = w [Tj [k]]++] = Ti [k] ; /* A(i,j) is the pth entry in C */ + if (Cx) Cx [p] = Tx [k] ; + } + return (cs_done (C, w, NULL, 1)) ; /* success; free w and return C */ +} diff --git a/src/cs/cs_counts.c b/src/cs/cs_counts.c new file mode 100644 index 0000000..118a260 --- /dev/null +++ b/src/cs/cs_counts.c @@ -0,0 +1,81 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* column counts of LL'=A or LL'=A'A, given parent & post ordering */ +#define HEAD(k,j) (ata ? head [k] : j) +#define NEXT(J) (ata ? next [J] : -1) +static void init_ata (cs *AT, const CS_INT *post, CS_INT *w, CS_INT **head, CS_INT **next) +{ + CS_INT i, k, p, m = AT->n, n = AT->m, *ATp = AT->p, *ATi = AT->i ; + *head = w+4*n, *next = w+5*n+1 ; + for (k = 0 ; k < n ; k++) w [post [k]] = k ; /* invert post */ + for (i = 0 ; i < m ; i++) + { + for (k = n, p = ATp[i] ; p < ATp[i+1] ; p++) k = CS_MIN (k, w [ATi[p]]); + (*next) [i] = (*head) [k] ; /* place row i in linked list k */ + (*head) [k] = i ; + } +} +CS_INT *cs_counts (const cs *A, const CS_INT *parent, const CS_INT *post, CS_INT ata) +{ + CS_INT i, j, k, n, m, J, s, p, q, jleaf, *ATp, *ATi, *maxfirst, *prevleaf, + *ancestor, *head = NULL, *next = NULL, *colcount, *w, *first, *delta ; + cs *AT ; + if (!CS_CSC (A) || !parent || !post) return (NULL) ; /* check inputs */ + m = A->m ; n = A->n ; + s = 4*n + (ata ? (n+m+1) : 0) ; + delta = colcount = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result */ + w = cs_malloc (s, sizeof (CS_INT)) ; /* get workspace */ + AT = cs_transpose (A, 0) ; /* AT = A' */ + if (!AT || !colcount || !w) return (cs_idone (colcount, AT, w, 0)) ; + ancestor = w ; maxfirst = w+n ; prevleaf = w+2*n ; first = w+3*n ; + for (k = 0 ; k < s ; k++) w [k] = -1 ; /* clear workspace w [0..s-1] */ + for (k = 0 ; k < n ; k++) /* find first [j] */ + { + j = post [k] ; + delta [j] = (first [j] == -1) ? 1 : 0 ; /* delta[j]=1 if j is a leaf */ + for ( ; j != -1 && first [j] == -1 ; j = parent [j]) first [j] = k ; + } + ATp = AT->p ; ATi = AT->i ; + if (ata) init_ata (AT, post, w, &head, &next) ; + for (i = 0 ; i < n ; i++) ancestor [i] = i ; /* each node in its own set */ + for (k = 0 ; k < n ; k++) + { + j = post [k] ; /* j is the kth node in postordered etree */ + if (parent [j] != -1) delta [parent [j]]-- ; /* j is not a root */ + for (J = HEAD (k,j) ; J != -1 ; J = NEXT (J)) /* J=j for LL'=A case */ + { + for (p = ATp [J] ; p < ATp [J+1] ; p++) + { + i = ATi [p] ; + q = cs_leaf (i, j, first, maxfirst, prevleaf, ancestor, &jleaf); + if (jleaf >= 1) delta [j]++ ; /* A(i,j) is in skeleton */ + if (jleaf == 2) delta [q]-- ; /* account for overlap in q */ + } + } + if (parent [j] != -1) ancestor [j] = parent [j] ; + } + for (j = 0 ; j < n ; j++) /* sum up delta's of each child */ + { + if (parent [j] != -1) colcount [parent [j]] += colcount [j] ; + } + return (cs_idone (colcount, AT, w, 1)) ; /* success: free workspace */ +} diff --git a/src/cs/cs_cumsum.c b/src/cs/cs_cumsum.c new file mode 100644 index 0000000..d7145f2 --- /dev/null +++ b/src/cs/cs_cumsum.c @@ -0,0 +1,37 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* p [0..n] = cumulative sum of c [0..n-1], and then copy p [0..n-1] into c */ +double cs_cumsum (CS_INT *p, CS_INT *c, CS_INT n) +{ + CS_INT i, nz = 0 ; + double nz2 = 0 ; + if (!p || !c) return (-1) ; /* check inputs */ + for (i = 0 ; i < n ; i++) + { + p [i] = nz ; + nz += c [i] ; + nz2 += c [i] ; /* also in double to avoid CS_INT overflow */ + c [i] = p [i] ; /* also copy p[0..n-1] back into c[0..n-1]*/ + } + p [n] = nz ; + return (nz2) ; /* return sum (c [0..n-1]) */ +} diff --git a/src/cs/cs_dfs.c b/src/cs/cs_dfs.c new file mode 100644 index 0000000..0dd3b6f --- /dev/null +++ b/src/cs/cs_dfs.c @@ -0,0 +1,56 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* depth-first-search of the graph of a matrix, starting at node j */ +CS_INT cs_dfs (CS_INT j, cs *G, CS_INT top, CS_INT *xi, CS_INT *pstack, const CS_INT *pinv) +{ + CS_INT i, p, p2, done, jnew, head = 0, *Gp, *Gi ; + if (!CS_CSC (G) || !xi || !pstack) return (-1) ; /* check inputs */ + Gp = G->p ; Gi = G->i ; + xi [0] = j ; /* initialize the recursion stack */ + while (head >= 0) + { + j = xi [head] ; /* get j from the top of the recursion stack */ + jnew = pinv ? (pinv [j]) : j ; + if (!CS_MARKED (Gp, j)) + { + CS_MARK (Gp, j) ; /* mark node j as visited */ + pstack [head] = (jnew < 0) ? 0 : CS_UNFLIP (Gp [jnew]) ; + } + done = 1 ; /* node j done if no unvisited neighbors */ + p2 = (jnew < 0) ? 0 : CS_UNFLIP (Gp [jnew+1]) ; + for (p = pstack [head] ; p < p2 ; p++) /* examine all neighbors of j */ + { + i = Gi [p] ; /* consider neighbor node i */ + if (CS_MARKED (Gp, i)) continue ; /* skip visited node i */ + pstack [head] = p ; /* pause depth-first search of node j */ + xi [++head] = i ; /* start dfs at node i */ + done = 0 ; /* node j is not done */ + break ; /* break, to start dfs (i) */ + } + if (done) /* depth-first search at node j is done */ + { + head-- ; /* remove j from the recursion stack */ + xi [--top] = j ; /* and place in the output stack */ + } + } + return (top) ; +} diff --git a/src/cs/cs_dmperm.c b/src/cs/cs_dmperm.c new file mode 100644 index 0000000..a989028 --- /dev/null +++ b/src/cs/cs_dmperm.c @@ -0,0 +1,164 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* breadth-first search for coarse decomposition (C0,C1,R1 or R0,R3,C3) */ +static CS_INT cs_bfs (const cs *A, CS_INT n, CS_INT *wi, CS_INT *wj, CS_INT *queue, + const CS_INT *imatch, const CS_INT *jmatch, CS_INT mark) +{ + CS_INT *Ap, *Ai, head = 0, tail = 0, j, i, p, j2 ; + cs *C ; + for (j = 0 ; j < n ; j++) /* place all unmatched nodes in queue */ + { + if (imatch [j] >= 0) continue ; /* skip j if matched */ + wj [j] = 0 ; /* j in set C0 (R0 if transpose) */ + queue [tail++] = j ; /* place unmatched col j in queue */ + } + if (tail == 0) return (1) ; /* quick return if no unmatched nodes */ + C = (mark == 1) ? ((cs *) A) : cs_transpose (A, 0) ; + if (!C) return (0) ; /* bfs of C=A' to find R3,C3 from R0 */ + Ap = C->p ; Ai = C->i ; + while (head < tail) /* while queue is not empty */ + { + j = queue [head++] ; /* get the head of the queue */ + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; + if (wi [i] >= 0) continue ; /* skip if i is marked */ + wi [i] = mark ; /* i in set R1 (C3 if transpose) */ + j2 = jmatch [i] ; /* traverse alternating path to j2 */ + if (wj [j2] >= 0) continue ;/* skip j2 if it is marked */ + wj [j2] = mark ; /* j2 in set C1 (R3 if transpose) */ + queue [tail++] = j2 ; /* add j2 to queue */ + } + } + if (mark != 1) cs_spfree (C) ; /* free A' if it was created */ + return (1) ; +} + +/* collect matched rows and columns into p and q */ +static void cs_matched (CS_INT n, const CS_INT *wj, const CS_INT *imatch, CS_INT *p, CS_INT *q, + CS_INT *cc, CS_INT *rr, CS_INT set, CS_INT mark) +{ + CS_INT kc = cc [set], j ; + CS_INT kr = rr [set-1] ; + for (j = 0 ; j < n ; j++) + { + if (wj [j] != mark) continue ; /* skip if j is not in C set */ + p [kr++] = imatch [j] ; + q [kc++] = j ; + } + cc [set+1] = kc ; + rr [set] = kr ; +} + +/* collect unmatched rows into the permutation vector p */ +static void cs_unmatched (CS_INT m, const CS_INT *wi, CS_INT *p, CS_INT *rr, CS_INT set) +{ + CS_INT i, kr = rr [set] ; + for (i = 0 ; i < m ; i++) if (wi [i] == 0) p [kr++] = i ; + rr [set+1] = kr ; +} + +/* return 1 if row i is in R2 */ +static CS_INT cs_rprune (CS_INT i, CS_INT j, CS_ENTRY aij, void *other) +{ + CS_INT *rr = (CS_INT *) other ; + return (i >= rr [1] && i < rr [2]) ; +} + +/* Given A, compute coarse and then fine dmperm */ +csd *cs_dmperm (const cs *A, CS_INT seed) +{ + CS_INT m, n, i, j, k, cnz, nc, *jmatch, *imatch, *wi, *wj, *pinv, *Cp, *Ci, + *ps, *rs, nb1, nb2, *p, *q, *cc, *rr, *r, *s, ok ; + cs *C ; + csd *D, *scc ; + /* --- Maximum matching ------------------------------------------------- */ + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + m = A->m ; n = A->n ; + D = cs_dalloc (m, n) ; /* allocate result */ + if (!D) return (NULL) ; + p = D->p ; q = D->q ; r = D->r ; s = D->s ; cc = D->cc ; rr = D->rr ; + jmatch = cs_maxtrans (A, seed) ; /* max transversal */ + imatch = jmatch + m ; /* imatch = inverse of jmatch */ + if (!jmatch) return (cs_ddone (D, NULL, jmatch, 0)) ; + /* --- Coarse decomposition --------------------------------------------- */ + wi = r ; wj = s ; /* use r and s as workspace */ + for (j = 0 ; j < n ; j++) wj [j] = -1 ; /* unmark all cols for bfs */ + for (i = 0 ; i < m ; i++) wi [i] = -1 ; /* unmark all rows for bfs */ + cs_bfs (A, n, wi, wj, q, imatch, jmatch, 1) ; /* find C1, R1 from C0*/ + ok = cs_bfs (A, m, wj, wi, p, jmatch, imatch, 3) ; /* find R3, C3 from R0*/ + if (!ok) return (cs_ddone (D, NULL, jmatch, 0)) ; + cs_unmatched (n, wj, q, cc, 0) ; /* unmatched set C0 */ + cs_matched (n, wj, imatch, p, q, cc, rr, 1, 1) ; /* set R1 and C1 */ + cs_matched (n, wj, imatch, p, q, cc, rr, 2, -1) ; /* set R2 and C2 */ + cs_matched (n, wj, imatch, p, q, cc, rr, 3, 3) ; /* set R3 and C3 */ + cs_unmatched (m, wi, p, rr, 3) ; /* unmatched set R0 */ + cs_free (jmatch) ; + /* --- Fine decomposition ----------------------------------------------- */ + pinv = cs_pinv (p, m) ; /* pinv=p' */ + if (!pinv) return (cs_ddone (D, NULL, NULL, 0)) ; + C = cs_permute (A, pinv, q, 0) ;/* C=A(p,q) (it will hold A(R2,C2)) */ + cs_free (pinv) ; + if (!C) return (cs_ddone (D, NULL, NULL, 0)) ; + Cp = C->p ; + nc = cc [3] - cc [2] ; /* delete cols C0, C1, and C3 from C */ + if (cc [2] > 0) for (j = cc [2] ; j <= cc [3] ; j++) Cp [j-cc[2]] = Cp [j] ; + C->n = nc ; + if (rr [2] - rr [1] < m) /* delete rows R0, R1, and R3 from C */ + { + cs_fkeep (C, cs_rprune, rr) ; + cnz = Cp [nc] ; + Ci = C->i ; + if (rr [1] > 0) for (k = 0 ; k < cnz ; k++) Ci [k] -= rr [1] ; + } + C->m = nc ; + scc = cs_scc (C) ; /* find strongly connected components of C*/ + if (!scc) return (cs_ddone (D, C, NULL, 0)) ; + /* --- Combine coarse and fine decompositions --------------------------- */ + ps = scc->p ; /* C(ps,ps) is the permuted matrix */ + rs = scc->r ; /* kth block is rs[k]..rs[k+1]-1 */ + nb1 = scc->nb ; /* # of blocks of A(R2,C2) */ + for (k = 0 ; k < nc ; k++) wj [k] = q [ps [k] + cc [2]] ; + for (k = 0 ; k < nc ; k++) q [k + cc [2]] = wj [k] ; + for (k = 0 ; k < nc ; k++) wi [k] = p [ps [k] + rr [1]] ; + for (k = 0 ; k < nc ; k++) p [k + rr [1]] = wi [k] ; + nb2 = 0 ; /* create the fine block partitions */ + r [0] = s [0] = 0 ; + if (cc [2] > 0) nb2++ ; /* leading coarse block A (R1, [C0 C1]) */ + for (k = 0 ; k < nb1 ; k++) /* coarse block A (R2,C2) */ + { + r [nb2] = rs [k] + rr [1] ; /* A (R2,C2) splits into nb1 fine blocks */ + s [nb2] = rs [k] + cc [2] ; + nb2++ ; + } + if (rr [2] < m) + { + r [nb2] = rr [2] ; /* trailing coarse block A ([R3 R0], C3) */ + s [nb2] = cc [3] ; + nb2++ ; + } + r [nb2] = m ; + s [nb2] = n ; + D->nb = nb2 ; + cs_dfree (scc) ; + return (cs_ddone (D, C, NULL, 1)) ; +} diff --git a/src/cs/cs_droptol.c b/src/cs/cs_droptol.c new file mode 100644 index 0000000..15cbdcc --- /dev/null +++ b/src/cs/cs_droptol.c @@ -0,0 +1,29 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +static CS_INT cs_tol (CS_INT i, CS_INT j, CS_ENTRY aij, void *tol) +{ + return (CS_ABS (aij) > *((double *) tol)) ; +} +CS_INT cs_droptol (cs *A, double tol) +{ + return (cs_fkeep (A, &cs_tol, &tol)) ; /* keep all large entries */ +} diff --git a/src/cs/cs_dropzeros.c b/src/cs/cs_dropzeros.c new file mode 100644 index 0000000..1cceb20 --- /dev/null +++ b/src/cs/cs_dropzeros.c @@ -0,0 +1,29 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +static CS_INT cs_nonzero (CS_INT i, CS_INT j, CS_ENTRY aij, void *other) +{ + return (aij != 0) ; +} +CS_INT cs_dropzeros (cs *A) +{ + return (cs_fkeep (A, &cs_nonzero, NULL)) ; /* keep all nonzero entries */ +} diff --git a/src/cs/cs_dupl.c b/src/cs/cs_dupl.c new file mode 100644 index 0000000..a775cdb --- /dev/null +++ b/src/cs/cs_dupl.c @@ -0,0 +1,54 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* remove duplicate entries from A */ +CS_INT cs_dupl (cs *A) +{ + CS_INT i, j, p, q, nz = 0, n, m, *Ap, *Ai, *w ; + CS_ENTRY *Ax ; + if (!CS_CSC (A)) return (0) ; /* check inputs */ + m = A->m ; n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + w = cs_malloc (m, sizeof (CS_INT)) ; /* get workspace */ + if (!w) return (0) ; /* out of memory */ + for (i = 0 ; i < m ; i++) w [i] = -1 ; /* row i not yet seen */ + for (j = 0 ; j < n ; j++) + { + q = nz ; /* column j will start at q */ + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; /* A(i,j) is nonzero */ + if (w [i] >= q) + { + Ax [w [i]] += Ax [p] ; /* A(i,j) is a duplicate */ + } + else + { + w [i] = nz ; /* record where row i occurs */ + Ai [nz] = i ; /* keep A(i,j) */ + Ax [nz++] = Ax [p] ; + } + } + Ap [j] = q ; /* record start of column j */ + } + Ap [n] = nz ; /* finalize A */ + cs_free (w) ; /* free workspace */ + return (cs_sprealloc (A, 0)) ; /* remove extra space from A */ +} diff --git a/src/cs/cs_entry.c b/src/cs/cs_entry.c new file mode 100644 index 0000000..1630c76 --- /dev/null +++ b/src/cs/cs_entry.c @@ -0,0 +1,33 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* add an entry to a triplet matrix; return 1 if ok, 0 otherwise */ +CS_INT cs_entry (cs *T, CS_INT i, CS_INT j, CS_ENTRY x) +{ + if (!CS_TRIPLET (T) || i < 0 || j < 0) return (0) ; /* check inputs */ + if (T->nz >= T->nzmax && !cs_sprealloc (T,2*(T->nzmax))) return (0) ; + if (T->x) T->x [T->nz] = x ; + T->i [T->nz] = i ; + T->p [T->nz++] = j ; + T->m = CS_MAX (T->m, i+1) ; + T->n = CS_MAX (T->n, j+1) ; + return (1) ; +} diff --git a/src/cs/cs_ereach.c b/src/cs/cs_ereach.c new file mode 100644 index 0000000..8ae79e4 --- /dev/null +++ b/src/cs/cs_ereach.c @@ -0,0 +1,43 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* find nonzero pattern of Cholesky L(k,1:k-1) using etree and triu(A(:,k)) */ +CS_INT cs_ereach (const cs *A, CS_INT k, const CS_INT *parent, CS_INT *s, CS_INT *w) +{ + CS_INT i, p, n, len, top, *Ap, *Ai ; + if (!CS_CSC (A) || !parent || !s || !w) return (-1) ; /* check inputs */ + top = n = A->n ; Ap = A->p ; Ai = A->i ; + CS_MARK (w, k) ; /* mark node k as visited */ + for (p = Ap [k] ; p < Ap [k+1] ; p++) + { + i = Ai [p] ; /* A(i,k) is nonzero */ + if (i > k) continue ; /* only use upper triangular part of A */ + for (len = 0 ; !CS_MARKED (w,i) ; i = parent [i]) /* traverse up etree*/ + { + s [len++] = i ; /* L(k,i) is nonzero */ + CS_MARK (w, i) ; /* mark i as visited */ + } + while (len > 0) s [--top] = s [--len] ; /* push path onto stack */ + } + for (p = top ; p < n ; p++) CS_MARK (w, s [p]) ; /* unmark all nodes */ + CS_MARK (w, k) ; /* unmark node k */ + return (top) ; /* s [top..n-1] contains pattern of L(k,:)*/ +} diff --git a/src/cs/cs_etree.c b/src/cs/cs_etree.c new file mode 100644 index 0000000..0d815b7 --- /dev/null +++ b/src/cs/cs_etree.c @@ -0,0 +1,50 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* compute the etree of A (using triu(A), or A'A without forming A'A */ +CS_INT *cs_etree (const cs *A, CS_INT ata) +{ + CS_INT i, k, p, m, n, inext, *Ap, *Ai, *w, *parent, *ancestor, *prev ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + m = A->m ; n = A->n ; Ap = A->p ; Ai = A->i ; + parent = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result */ + w = cs_malloc (n + (ata ? m : 0), sizeof (CS_INT)) ; /* get workspace */ + if (!w || !parent) return (cs_idone (parent, NULL, w, 0)) ; + ancestor = w ; prev = w + n ; + if (ata) for (i = 0 ; i < m ; i++) prev [i] = -1 ; + for (k = 0 ; k < n ; k++) + { + parent [k] = -1 ; /* node k has no parent yet */ + ancestor [k] = -1 ; /* nor does k have an ancestor */ + for (p = Ap [k] ; p < Ap [k+1] ; p++) + { + i = ata ? (prev [Ai [p]]) : (Ai [p]) ; + for ( ; i != -1 && i < k ; i = inext) /* traverse from i to k */ + { + inext = ancestor [i] ; /* inext = ancestor of i */ + ancestor [i] = k ; /* path compression */ + if (inext == -1) parent [i] = k ; /* no anc., parent is k */ + } + if (ata) prev [Ai [p]] = k ; + } + } + return (cs_idone (parent, NULL, w, 1)) ; +} diff --git a/src/cs/cs_fkeep.c b/src/cs/cs_fkeep.c new file mode 100644 index 0000000..91820bb --- /dev/null +++ b/src/cs/cs_fkeep.c @@ -0,0 +1,45 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* drop entries for which fkeep(A(i,j)) is false; return nz if OK, else -1 */ +CS_INT cs_fkeep (cs *A, CS_INT (*fkeep) (CS_INT, CS_INT, CS_ENTRY, void *), void *other) +{ + CS_INT j, p, nz = 0, n, *Ap, *Ai ; + CS_ENTRY *Ax ; + if (!CS_CSC (A) || !fkeep) return (-1) ; /* check inputs */ + n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + for (j = 0 ; j < n ; j++) + { + p = Ap [j] ; /* get current location of col j */ + Ap [j] = nz ; /* record new location of col j */ + for ( ; p < Ap [j+1] ; p++) + { + if (fkeep (Ai [p], j, Ax ? Ax [p] : 1, other)) + { + if (Ax) Ax [nz] = Ax [p] ; /* keep A(i,j) */ + Ai [nz++] = Ai [p] ; + } + } + } + Ap [n] = nz ; /* finalize A */ + cs_sprealloc (A, 0) ; /* remove extra space from A */ + return (nz) ; +} diff --git a/src/cs/cs_gaxpy.c b/src/cs/cs_gaxpy.c new file mode 100644 index 0000000..8c70bd8 --- /dev/null +++ b/src/cs/cs_gaxpy.c @@ -0,0 +1,37 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* y = A*x+y */ +CS_INT cs_gaxpy (const cs *A, const CS_ENTRY *x, CS_ENTRY *y) +{ + CS_INT p, j, n, *Ap, *Ai ; + CS_ENTRY *Ax ; + if (!CS_CSC (A) || !x || !y) return (0) ; /* check inputs */ + n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + for (j = 0 ; j < n ; j++) + { + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + y [Ai [p]] += Ax [p] * x [j] ; + } + } + return (1) ; +} diff --git a/src/cs/cs_happly.c b/src/cs/cs_happly.c new file mode 100644 index 0000000..02431be --- /dev/null +++ b/src/cs/cs_happly.c @@ -0,0 +1,39 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* apply the ith Householder vector to x */ +CS_INT cs_happly (const cs *V, CS_INT i, double beta, CS_ENTRY *x) +{ + CS_INT p, *Vp, *Vi ; + CS_ENTRY *Vx, tau = 0 ; + if (!CS_CSC (V) || !x) return (0) ; /* check inputs */ + Vp = V->p ; Vi = V->i ; Vx = V->x ; + for (p = Vp [i] ; p < Vp [i+1] ; p++) /* tau = v'*x */ + { + tau += CS_CONJ (Vx [p]) * x [Vi [p]] ; + } + tau *= beta ; /* tau = beta*(v'*x) */ + for (p = Vp [i] ; p < Vp [i+1] ; p++) /* x = x - v*tau */ + { + x [Vi [p]] -= Vx [p] * tau ; + } + return (1) ; +} diff --git a/src/cs/cs_house.c b/src/cs/cs_house.c new file mode 100644 index 0000000..f719db7 --- /dev/null +++ b/src/cs/cs_house.c @@ -0,0 +1,50 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* create a Householder reflection [v,beta,s]=house(x), overwrite x with v, + * where (I-beta*v*v')*x = s*e1 and e1 = [1 0 ... 0]'. + * Note that this CXSparse version is different than CSparse. See Higham, + * Accuracy & Stability of Num Algorithms, 2nd ed, 2002, page 357. */ +CS_ENTRY cs_house (CS_ENTRY *x, double *beta, CS_INT n) +{ + CS_ENTRY s = 0 ; + CS_INT i ; + if (!x || !beta) return (-1) ; /* check inputs */ + /* s = norm(x) */ + for (i = 0 ; i < n ; i++) s += x [i] * CS_CONJ (x [i]) ; + s = sqrt (s) ; + if (s == 0) + { + (*beta) = 0 ; + x [0] = 1 ; + } + else + { + /* s = sign(x[0]) * norm (x) ; */ + if (x [0] != 0) + { + s *= x [0] / CS_ABS (x [0]) ; + } + x [0] += s ; + (*beta) = 1. / CS_REAL (CS_CONJ (s) * x [0]) ; + } + return (-s) ; +} diff --git a/src/cs/cs_ipvec.c b/src/cs/cs_ipvec.c new file mode 100644 index 0000000..ad58333 --- /dev/null +++ b/src/cs/cs_ipvec.c @@ -0,0 +1,29 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* x(p) = b, for dense vectors x and b; p=NULL denotes identity */ +CS_INT cs_ipvec (const CS_INT *p, const CS_ENTRY *b, CS_ENTRY *x, CS_INT n) +{ + CS_INT k ; + if (!x || !b) return (0) ; /* check inputs */ + for (k = 0 ; k < n ; k++) x [p ? p [k] : k] = b [k] ; + return (1) ; +} diff --git a/src/cs/cs_leaf.c b/src/cs/cs_leaf.c new file mode 100644 index 0000000..6e6cceb --- /dev/null +++ b/src/cs/cs_leaf.c @@ -0,0 +1,42 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* consider A(i,j), node j in ith row subtree and return lca(jprev,j) */ +CS_INT cs_leaf (CS_INT i, CS_INT j, const CS_INT *first, CS_INT *maxfirst, CS_INT *prevleaf, + CS_INT *ancestor, CS_INT *jleaf) +{ + CS_INT q, s, sparent, jprev ; + if (!first || !maxfirst || !prevleaf || !ancestor || !jleaf) return (-1) ; + *jleaf = 0 ; + if (i <= j || first [j] <= maxfirst [i]) return (-1) ; /* j not a leaf */ + maxfirst [i] = first [j] ; /* update max first[j] seen so far */ + jprev = prevleaf [i] ; /* jprev = previous leaf of ith subtree */ + prevleaf [i] = j ; + *jleaf = (jprev == -1) ? 1: 2 ; /* j is first or subsequent leaf */ + if (*jleaf == 1) return (i) ; /* if 1st leaf, q = root of ith subtree */ + for (q = jprev ; q != ancestor [q] ; q = ancestor [q]) ; + for (s = jprev ; s != q ; s = sparent) + { + sparent = ancestor [s] ; /* path compression */ + ancestor [s] = q ; + } + return (q) ; /* q = least common ancester (jprev,j) */ +} diff --git a/src/cs/cs_load.c b/src/cs/cs_load.c new file mode 100644 index 0000000..0730d82 --- /dev/null +++ b/src/cs/cs_load.c @@ -0,0 +1,46 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* load a triplet matrix from a file */ +cs *cs_load (FILE *f) +{ + CS_INT i, j ; + double x ; +#ifdef CS_COMPLEX + double xi ; +#endif + cs *T ; + if (!f) return (NULL) ; /* check inputs */ + T = cs_spalloc (0, 0, 1, 1, 1) ; /* allocate result */ +#ifdef CS_COMPLEX + while (fscanf (f, ""CS_ID" "CS_ID" %lg %lg\n", &i, &j, &x, &xi) == 4) +#else + while (fscanf (f, ""CS_ID" "CS_ID" %lg\n", &i, &j, &x) == 3) +#endif + { +#ifdef CS_COMPLEX + if (!cs_entry (T, i, j, x + xi*I)) return (cs_spfree (T)) ; +#else + if (!cs_entry (T, i, j, x)) return (cs_spfree (T)) ; +#endif + } + return (T) ; +} diff --git a/src/cs/cs_lsolve.c b/src/cs/cs_lsolve.c new file mode 100644 index 0000000..1ec645d --- /dev/null +++ b/src/cs/cs_lsolve.c @@ -0,0 +1,38 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* solve Lx=b where x and b are dense. x=b on input, solution on output. */ +CS_INT cs_lsolve (const cs *L, CS_ENTRY *x) +{ + CS_INT p, j, n, *Lp, *Li ; + CS_ENTRY *Lx ; + if (!CS_CSC (L) || !x) return (0) ; /* check inputs */ + n = L->n ; Lp = L->p ; Li = L->i ; Lx = L->x ; + for (j = 0 ; j < n ; j++) + { + x [j] /= Lx [Lp [j]] ; + for (p = Lp [j]+1 ; p < Lp [j+1] ; p++) + { + x [Li [p]] -= Lx [p] * x [j] ; + } + } + return (1) ; +} diff --git a/src/cs/cs_ltsolve.c b/src/cs/cs_ltsolve.c new file mode 100644 index 0000000..6610216 --- /dev/null +++ b/src/cs/cs_ltsolve.c @@ -0,0 +1,38 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* solve L'x=b where x and b are dense. x=b on input, solution on output. */ +CS_INT cs_ltsolve (const cs *L, CS_ENTRY *x) +{ + CS_INT p, j, n, *Lp, *Li ; + CS_ENTRY *Lx ; + if (!CS_CSC (L) || !x) return (0) ; /* check inputs */ + n = L->n ; Lp = L->p ; Li = L->i ; Lx = L->x ; + for (j = n-1 ; j >= 0 ; j--) + { + for (p = Lp [j]+1 ; p < Lp [j+1] ; p++) + { + x [j] -= CS_CONJ (Lx [p]) * x [Li [p]] ; + } + x [j] /= CS_CONJ (Lx [Lp [j]]) ; + } + return (1) ; +} diff --git a/src/cs/cs_lu.c b/src/cs/cs_lu.c new file mode 100644 index 0000000..3f82e83 --- /dev/null +++ b/src/cs/cs_lu.c @@ -0,0 +1,107 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* [L,U,pinv]=lu(A, [q lnz unz]). lnz and unz can be guess */ +csn *cs_lu (const cs *A, const css *S, double tol) +{ + cs *L, *U ; + csn *N ; + CS_ENTRY pivot, *Lx, *Ux, *x ; + double a, t ; + CS_INT *Lp, *Li, *Up, *Ui, *pinv, *xi, *q, n, ipiv, k, top, p, i, col, lnz,unz; + if (!CS_CSC (A) || !S) return (NULL) ; /* check inputs */ + n = A->n ; + q = S->q ; lnz = S->lnz ; unz = S->unz ; + x = cs_malloc (n, sizeof (CS_ENTRY)) ; /* get CS_ENTRY workspace */ + xi = cs_malloc (2*n, sizeof (CS_INT)) ; /* get CS_INT workspace */ + N = cs_calloc (1, sizeof (csn)) ; /* allocate result */ + if (!x || !xi || !N) return (cs_ndone (N, NULL, xi, x, 0)) ; + N->L = L = cs_spalloc (n, n, lnz, 1, 0) ; /* allocate result L */ + N->U = U = cs_spalloc (n, n, unz, 1, 0) ; /* allocate result U */ + N->pinv = pinv = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result pinv */ + if (!L || !U || !pinv) return (cs_ndone (N, NULL, xi, x, 0)) ; + Lp = L->p ; Up = U->p ; + for (i = 0 ; i < n ; i++) x [i] = 0 ; /* clear workspace */ + for (i = 0 ; i < n ; i++) pinv [i] = -1 ; /* no rows pivotal yet */ + for (k = 0 ; k <= n ; k++) Lp [k] = 0 ; /* no cols of L yet */ + lnz = unz = 0 ; + for (k = 0 ; k < n ; k++) /* compute L(:,k) and U(:,k) */ + { + /* --- Triangular solve --------------------------------------------- */ + Lp [k] = lnz ; /* L(:,k) starts here */ + Up [k] = unz ; /* U(:,k) starts here */ + if ((lnz + n > L->nzmax && !cs_sprealloc (L, 2*L->nzmax + n)) || + (unz + n > U->nzmax && !cs_sprealloc (U, 2*U->nzmax + n))) + { + return (cs_ndone (N, NULL, xi, x, 0)) ; + } + Li = L->i ; Lx = L->x ; Ui = U->i ; Ux = U->x ; + col = q ? (q [k]) : k ; + top = cs_spsolve (L, A, col, xi, x, pinv, 1) ; /* x = L\A(:,col) */ + /* --- Find pivot --------------------------------------------------- */ + ipiv = -1 ; + a = -1 ; + for (p = top ; p < n ; p++) + { + i = xi [p] ; /* x(i) is nonzero */ + if (pinv [i] < 0) /* row i is not yet pivotal */ + { + if ((t = CS_ABS (x [i])) > a) + { + a = t ; /* largest pivot candidate so far */ + ipiv = i ; + } + } + else /* x(i) is the entry U(pinv[i],k) */ + { + Ui [unz] = pinv [i] ; + Ux [unz++] = x [i] ; + } + } + if (ipiv == -1 || a <= 0) return (cs_ndone (N, NULL, xi, x, 0)) ; + if (pinv [col] < 0 && CS_ABS (x [col]) >= a*tol) ipiv = col ; + /* --- Divide by pivot ---------------------------------------------- */ + pivot = x [ipiv] ; /* the chosen pivot */ + Ui [unz] = k ; /* last entry in U(:,k) is U(k,k) */ + Ux [unz++] = pivot ; + pinv [ipiv] = k ; /* ipiv is the kth pivot row */ + Li [lnz] = ipiv ; /* first entry in L(:,k) is L(k,k) = 1 */ + Lx [lnz++] = 1 ; + for (p = top ; p < n ; p++) /* L(k+1:n,k) = x / pivot */ + { + i = xi [p] ; + if (pinv [i] < 0) /* x(i) is an entry in L(:,k) */ + { + Li [lnz] = i ; /* save unpermuted row in L */ + Lx [lnz++] = x [i] / pivot ; /* scale pivot column */ + } + x [i] = 0 ; /* x [0..n-1] = 0 for next k */ + } + } + /* --- Finalize L and U ------------------------------------------------- */ + Lp [n] = lnz ; + Up [n] = unz ; + Li = L->i ; /* fix row indices of L for final pinv */ + for (p = 0 ; p < lnz ; p++) Li [p] = pinv [Li [p]] ; + cs_sprealloc (L, 0) ; /* remove extra space from L and U */ + cs_sprealloc (U, 0) ; + return (cs_ndone (N, NULL, xi, x, 1)) ; /* success */ +} diff --git a/src/cs/cs_lusol.c b/src/cs/cs_lusol.c new file mode 100644 index 0000000..492fcd6 --- /dev/null +++ b/src/cs/cs_lusol.c @@ -0,0 +1,46 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* x=A\b where A is unsymmetric; b overwritten with solution */ +CS_INT cs_lusol (CS_INT order, const cs *A, CS_ENTRY *b, double tol) +{ + CS_ENTRY *x ; + css *S ; + csn *N ; + CS_INT n, ok ; + if (!CS_CSC (A) || !b) return (0) ; /* check inputs */ + n = A->n ; + S = cs_sqr (order, A, 0) ; /* ordering and symbolic analysis */ + N = cs_lu (A, S, tol) ; /* numeric LU factorization */ + x = cs_malloc (n, sizeof (CS_ENTRY)) ; /* get workspace */ + ok = (S && N && x) ; + if (ok) + { + cs_ipvec (N->pinv, b, x, n) ; /* x = b(p) */ + cs_lsolve (N->L, x) ; /* x = L\x */ + cs_usolve (N->U, x) ; /* x = U\x */ + cs_ipvec (S->q, x, b, n) ; /* b(q) = x */ + } + cs_free (x) ; + cs_sfree (S) ; + cs_nfree (N) ; + return (ok) ; +} diff --git a/src/cs/cs_malloc.c b/src/cs/cs_malloc.c new file mode 100644 index 0000000..2e43c91 --- /dev/null +++ b/src/cs/cs_malloc.c @@ -0,0 +1,55 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +#ifdef MATLAB_MEX_FILE +#define malloc mxMalloc +#define free mxFree +#define realloc mxRealloc +#define calloc mxCalloc +#endif + +/* wrapper for malloc */ +void *cs_malloc (CS_INT n, size_t size) +{ + return (malloc (CS_MAX (n,1) * size)) ; +} + +/* wrapper for calloc */ +void *cs_calloc (CS_INT n, size_t size) +{ + return (calloc (CS_MAX (n,1), size)) ; +} + +/* wrapper for free */ +void *cs_free (void *p) +{ + if (p) free (p) ; /* free p if it is not already NULL */ + return (NULL) ; /* return NULL to simplify the use of cs_free */ +} + +/* wrapper for realloc */ +void *cs_realloc (void *p, CS_INT n, size_t size, CS_INT *ok) +{ + void *pnew ; + pnew = realloc (p, CS_MAX (n,1) * size) ; /* realloc the block */ + *ok = (pnew != NULL) ; /* realloc fails if pnew is NULL */ + return ((*ok) ? pnew : p) ; /* return original p if failure */ +} diff --git a/src/cs/cs_maxtrans.c b/src/cs/cs_maxtrans.c new file mode 100644 index 0000000..b5edb3d --- /dev/null +++ b/src/cs/cs_maxtrans.c @@ -0,0 +1,112 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* find an augmenting path starting at column k and extend the match if found */ +static void cs_augment (CS_INT k, const cs *A, CS_INT *jmatch, CS_INT *cheap, CS_INT *w, + CS_INT *js, CS_INT *is, CS_INT *ps) +{ + CS_INT found = 0, p, i = -1, *Ap = A->p, *Ai = A->i, head = 0, j ; + js [0] = k ; /* start with just node k in jstack */ + while (head >= 0) + { + /* --- Start (or continue) depth-first-search at node j ------------- */ + j = js [head] ; /* get j from top of jstack */ + if (w [j] != k) /* 1st time j visited for kth path */ + { + w [j] = k ; /* mark j as visited for kth path */ + for (p = cheap [j] ; p < Ap [j+1] && !found ; p++) + { + i = Ai [p] ; /* try a cheap assignment (i,j) */ + found = (jmatch [i] == -1) ; + } + cheap [j] = p ; /* start here next time j is traversed*/ + if (found) + { + is [head] = i ; /* column j matched with row i */ + break ; /* end of augmenting path */ + } + ps [head] = Ap [j] ; /* no cheap match: start dfs for j */ + } + /* --- Depth-first-search of neighbors of j ------------------------- */ + for (p = ps [head] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; /* consider row i */ + if (w [jmatch [i]] == k) continue ; /* skip jmatch [i] if marked */ + ps [head] = p + 1 ; /* pause dfs of node j */ + is [head] = i ; /* i will be matched with j if found */ + js [++head] = jmatch [i] ; /* start dfs at column jmatch [i] */ + break ; + } + if (p == Ap [j+1]) head-- ; /* node j is done; pop from stack */ + } /* augment the match if path found: */ + if (found) for (p = head ; p >= 0 ; p--) jmatch [is [p]] = js [p] ; +} + +/* find a maximum transveral */ +CS_INT *cs_maxtrans (const cs *A, CS_INT seed) /*[jmatch [0..m-1]; imatch [0..n-1]]*/ +{ + CS_INT i, j, k, n, m, p, n2 = 0, m2 = 0, *Ap, *jimatch, *w, *cheap, *js, *is, + *ps, *Ai, *Cp, *jmatch, *imatch, *q ; + cs *C ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + n = A->n ; m = A->m ; Ap = A->p ; Ai = A->i ; + w = jimatch = cs_calloc (m+n, sizeof (CS_INT)) ; /* allocate result */ + if (!jimatch) return (NULL) ; + for (k = 0, j = 0 ; j < n ; j++) /* count nonempty rows and columns */ + { + n2 += (Ap [j] < Ap [j+1]) ; + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + w [Ai [p]] = 1 ; + k += (j == Ai [p]) ; /* count entries already on diagonal */ + } + } + if (k == CS_MIN (m,n)) /* quick return if diagonal zero-free */ + { + jmatch = jimatch ; imatch = jimatch + m ; + for (i = 0 ; i < k ; i++) jmatch [i] = i ; + for ( ; i < m ; i++) jmatch [i] = -1 ; + for (j = 0 ; j < k ; j++) imatch [j] = j ; + for ( ; j < n ; j++) imatch [j] = -1 ; + return (cs_idone (jimatch, NULL, NULL, 1)) ; + } + for (i = 0 ; i < m ; i++) m2 += w [i] ; + C = (m2 < n2) ? cs_transpose (A,0) : ((cs *) A) ; /* transpose if needed */ + if (!C) return (cs_idone (jimatch, (m2 < n2) ? C : NULL, NULL, 0)) ; + n = C->n ; m = C->m ; Cp = C->p ; + jmatch = (m2 < n2) ? jimatch + n : jimatch ; + imatch = (m2 < n2) ? jimatch : jimatch + m ; + w = cs_malloc (5*n, sizeof (CS_INT)) ; /* get workspace */ + if (!w) return (cs_idone (jimatch, (m2 < n2) ? C : NULL, w, 0)) ; + cheap = w + n ; js = w + 2*n ; is = w + 3*n ; ps = w + 4*n ; + for (j = 0 ; j < n ; j++) cheap [j] = Cp [j] ; /* for cheap assignment */ + for (j = 0 ; j < n ; j++) w [j] = -1 ; /* all columns unflagged */ + for (i = 0 ; i < m ; i++) jmatch [i] = -1 ; /* nothing matched yet */ + q = cs_randperm (n, seed) ; /* q = random permutation */ + for (k = 0 ; k < n ; k++) /* augment, starting at column q[k] */ + { + cs_augment (q ? q [k]: k, C, jmatch, cheap, w, js, is, ps) ; + } + cs_free (q) ; + for (j = 0 ; j < n ; j++) imatch [j] = -1 ; /* find row match */ + for (i = 0 ; i < m ; i++) if (jmatch [i] >= 0) imatch [jmatch [i]] = i ; + return (cs_idone (jimatch, (m2 < n2) ? C : NULL, w, 1)) ; +} diff --git a/src/cs/cs_multiply.c b/src/cs/cs_multiply.c new file mode 100644 index 0000000..c630894 --- /dev/null +++ b/src/cs/cs_multiply.c @@ -0,0 +1,55 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* C = A*B */ +cs *cs_multiply (const cs *A, const cs *B) +{ + CS_INT p, j, nz = 0, anz, *Cp, *Ci, *Bp, m, n, bnz, *w, values, *Bi ; + CS_ENTRY *x, *Bx, *Cx ; + cs *C ; + if (!CS_CSC (A) || !CS_CSC (B)) return (NULL) ; /* check inputs */ + if (A->n != B->m) return (NULL) ; + m = A->m ; anz = A->p [A->n] ; + n = B->n ; Bp = B->p ; Bi = B->i ; Bx = B->x ; bnz = Bp [n] ; + w = cs_calloc (m, sizeof (CS_INT)) ; /* get workspace */ + values = (A->x != NULL) && (Bx != NULL) ; + x = values ? cs_malloc (m, sizeof (CS_ENTRY)) : NULL ; /* get workspace */ + C = cs_spalloc (m, n, anz + bnz, values, 0) ; /* allocate result */ + if (!C || !w || (values && !x)) return (cs_done (C, w, x, 0)) ; + Cp = C->p ; + for (j = 0 ; j < n ; j++) + { + if (nz + m > C->nzmax && !cs_sprealloc (C, 2*(C->nzmax)+m)) + { + return (cs_done (C, w, x, 0)) ; /* out of memory */ + } + Ci = C->i ; Cx = C->x ; /* C->i and C->x may be reallocated */ + Cp [j] = nz ; /* column j of C starts here */ + for (p = Bp [j] ; p < Bp [j+1] ; p++) + { + nz = cs_scatter (A, Bi [p], Bx ? Bx [p] : 1, w, x, j+1, C, nz) ; + } + if (values) for (p = Cp [j] ; p < nz ; p++) Cx [p] = x [Ci [p]] ; + } + Cp [n] = nz ; /* finalize the last column of C */ + cs_sprealloc (C, 0) ; /* remove extra space from C */ + return (cs_done (C, w, x, 1)) ; /* success; free workspace, return C */ +} diff --git a/src/cs/cs_norm.c b/src/cs/cs_norm.c new file mode 100644 index 0000000..a095219 --- /dev/null +++ b/src/cs/cs_norm.c @@ -0,0 +1,36 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* 1-norm of a sparse matrix = max (sum (abs (A))), largest column sum */ +double cs_norm (const cs *A) +{ + CS_INT p, j, n, *Ap ; + CS_ENTRY *Ax ; + double norm = 0, s ; + if (!CS_CSC (A) || !A->x) return (-1) ; /* check inputs */ + n = A->n ; Ap = A->p ; Ax = A->x ; + for (j = 0 ; j < n ; j++) + { + for (s = 0, p = Ap [j] ; p < Ap [j+1] ; p++) s += CS_ABS (Ax [p]) ; + norm = CS_MAX (norm, s) ; + } + return (norm) ; +} diff --git a/src/cs/cs_permute.c b/src/cs/cs_permute.c new file mode 100644 index 0000000..74f96dd --- /dev/null +++ b/src/cs/cs_permute.c @@ -0,0 +1,45 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* C = A(p,q) where p and q are permutations of 0..m-1 and 0..n-1. */ +cs *cs_permute (const cs *A, const CS_INT *pinv, const CS_INT *q, CS_INT values) +{ + CS_INT t, j, k, nz = 0, m, n, *Ap, *Ai, *Cp, *Ci ; + CS_ENTRY *Cx, *Ax ; + cs *C ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + m = A->m ; n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + C = cs_spalloc (m, n, Ap [n], values && Ax != NULL, 0) ; /* alloc result */ + if (!C) return (cs_done (C, NULL, NULL, 0)) ; /* out of memory */ + Cp = C->p ; Ci = C->i ; Cx = C->x ; + for (k = 0 ; k < n ; k++) + { + Cp [k] = nz ; /* column k of C is column q[k] of A */ + j = q ? (q [k]) : k ; + for (t = Ap [j] ; t < Ap [j+1] ; t++) + { + if (Cx) Cx [nz] = Ax [t] ; /* row i of A is row pinv[i] of C */ + Ci [nz++] = pinv ? (pinv [Ai [t]]) : Ai [t] ; + } + } + Cp [n] = nz ; /* finalize the last column of C */ + return (cs_done (C, NULL, NULL, 1)) ; +} diff --git a/src/cs/cs_pinv.c b/src/cs/cs_pinv.c new file mode 100644 index 0000000..644024c --- /dev/null +++ b/src/cs/cs_pinv.c @@ -0,0 +1,31 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* pinv = p', or p = pinv' */ +CS_INT *cs_pinv (CS_INT const *p, CS_INT n) +{ + CS_INT k, *pinv ; + if (!p) return (NULL) ; /* p = NULL denotes identity */ + pinv = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result */ + if (!pinv) return (NULL) ; /* out of memory */ + for (k = 0 ; k < n ; k++) pinv [p [k]] = k ;/* invert the permutation */ + return (pinv) ; /* return result */ +} diff --git a/src/cs/cs_post.c b/src/cs/cs_post.c new file mode 100644 index 0000000..209433f --- /dev/null +++ b/src/cs/cs_post.c @@ -0,0 +1,44 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* post order a forest */ +CS_INT *cs_post (const CS_INT *parent, CS_INT n) +{ + CS_INT j, k = 0, *post, *w, *head, *next, *stack ; + if (!parent) return (NULL) ; /* check inputs */ + post = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result */ + w = cs_malloc (3*n, sizeof (CS_INT)) ; /* get workspace */ + if (!w || !post) return (cs_idone (post, NULL, w, 0)) ; + head = w ; next = w + n ; stack = w + 2*n ; + for (j = 0 ; j < n ; j++) head [j] = -1 ; /* empty linked lists */ + for (j = n-1 ; j >= 0 ; j--) /* traverse nodes in reverse order*/ + { + if (parent [j] == -1) continue ; /* j is a root */ + next [j] = head [parent [j]] ; /* add j to list of its parent */ + head [parent [j]] = j ; + } + for (j = 0 ; j < n ; j++) + { + if (parent [j] != -1) continue ; /* skip j if it is not a root */ + k = cs_tdfs (j, k, head, next, post, stack) ; + } + return (cs_idone (post, NULL, w, 1)) ; /* success; free w, return post */ +} diff --git a/src/cs/cs_print.c b/src/cs/cs_print.c new file mode 100644 index 0000000..5fb67a8 --- /dev/null +++ b/src/cs/cs_print.c @@ -0,0 +1,66 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* print a sparse matrix */ +/* CS_INT cs_print (const cs *A, CS_INT brief) */ +/* { */ +/* CS_INT p, j, m, n, nzmax, nz, *Ap, *Ai ; */ +/* CS_ENTRY *Ax ; */ +/* if (!A) { printf ("(null)\n") ; return (0) ; } */ +/* m = A->m ; n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; */ +/* nzmax = A->nzmax ; nz = A->nz ; */ +/* printf ("CXSparse Version %d.%d.%d, %s. %s\n", CS_VER, CS_SUBVER, */ +/* CS_SUBSUB, CS_DATE, CS_COPYRIGHT) ; */ +/* if (nz < 0) */ +/* { */ +/* printf (""CS_ID"-by-"CS_ID", nzmax: "CS_ID" nnz: "CS_ID", 1-norm: %g\n", m, n, nzmax, */ +/* Ap [n], cs_norm (A)) ; */ +/* for (j = 0 ; j < n ; j++) */ +/* { */ +/* printf (" col "CS_ID" : locations "CS_ID" to "CS_ID"\n", j, Ap [j], Ap [j+1]-1); */ +/* for (p = Ap [j] ; p < Ap [j+1] ; p++) */ +/* { */ +/* #ifdef CS_COMPLEX */ +/* printf (" "CS_ID" : (%g, %g)\n", Ai [p], */ +/* Ax ? CS_REAL (Ax [p]) : 1, Ax ? CS_IMAG (Ax [p]) : 0) ; */ +/* #else */ +/* printf (" "CS_ID" : %g\n", Ai [p], Ax ? Ax [p] : 1) ; */ +/* #endif */ +/* if (brief && p > 20) { printf (" ...\n") ; return (1) ; } */ +/* } */ +/* } */ +/* } */ +/* else */ +/* { */ +/* printf ("triplet: "CS_ID"-by-"CS_ID", nzmax: "CS_ID" nnz: "CS_ID"\n", m, n, nzmax, nz) ; */ +/* for (p = 0 ; p < nz ; p++) */ +/* { */ +/* #ifdef CS_COMPLEX */ +/* printf (" "CS_ID" "CS_ID" : (%g, %g)\n", Ai [p], Ap [p], */ +/* Ax ? CS_REAL (Ax [p]) : 1, Ax ? CS_IMAG (Ax [p]) : 0) ; */ +/* #else */ +/* printf (" "CS_ID" "CS_ID" : %g\n", Ai [p], Ap [p], Ax ? Ax [p] : 1) ; */ +/* #endif */ +/* if (brief && p > 20) { printf (" ...\n") ; return (1) ; } */ +/* } */ +/* } */ +/* return (1) ; */ +/* } */ diff --git a/src/cs/cs_pvec.c b/src/cs/cs_pvec.c new file mode 100644 index 0000000..ac7f1d7 --- /dev/null +++ b/src/cs/cs_pvec.c @@ -0,0 +1,29 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* x = b(p), for dense vectors x and b; p=NULL denotes identity */ +CS_INT cs_pvec (const CS_INT *p, const CS_ENTRY *b, CS_ENTRY *x, CS_INT n) +{ + CS_INT k ; + if (!x || !b) return (0) ; /* check inputs */ + for (k = 0 ; k < n ; k++) x [k] = b [p ? p [k] : k] ; + return (1) ; +} diff --git a/src/cs/cs_qr.c b/src/cs/cs_qr.c new file mode 100644 index 0000000..7eb994d --- /dev/null +++ b/src/cs/cs_qr.c @@ -0,0 +1,94 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* sparse QR factorization [V,beta,pinv,R] = qr (A) */ +csn *cs_qr (const cs *A, const css *S) +{ + CS_ENTRY *Rx, *Vx, *Ax, *x ; + double *Beta ; + CS_INT i, k, p, m, n, vnz, p1, top, m2, len, col, rnz, *s, *leftmost, *Ap, *Ai, + *parent, *Rp, *Ri, *Vp, *Vi, *w, *pinv, *q ; + cs *R, *V ; + csn *N ; + if (!CS_CSC (A) || !S) return (NULL) ; + m = A->m ; n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + q = S->q ; parent = S->parent ; pinv = S->pinv ; m2 = S->m2 ; + vnz = S->lnz ; rnz = S->unz ; leftmost = S->leftmost ; + w = cs_malloc (m2+n, sizeof (CS_INT)) ; /* get CS_INT workspace */ + x = cs_malloc (m2, sizeof (CS_ENTRY)) ; /* get CS_ENTRY workspace */ + N = cs_calloc (1, sizeof (csn)) ; /* allocate result */ + if (!w || !x || !N) return (cs_ndone (N, NULL, w, x, 0)) ; + s = w + m2 ; /* s is size n */ + for (k = 0 ; k < m2 ; k++) x [k] = 0 ; /* clear workspace x */ + N->L = V = cs_spalloc (m2, n, vnz, 1, 0) ; /* allocate result V */ + N->U = R = cs_spalloc (m2, n, rnz, 1, 0) ; /* allocate result R */ + N->B = Beta = cs_malloc (n, sizeof (double)) ; /* allocate result Beta */ + if (!R || !V || !Beta) return (cs_ndone (N, NULL, w, x, 0)) ; + Rp = R->p ; Ri = R->i ; Rx = R->x ; + Vp = V->p ; Vi = V->i ; Vx = V->x ; + for (i = 0 ; i < m2 ; i++) w [i] = -1 ; /* clear w, to mark nodes */ + rnz = 0 ; vnz = 0 ; + for (k = 0 ; k < n ; k++) /* compute V and R */ + { + Rp [k] = rnz ; /* R(:,k) starts here */ + Vp [k] = p1 = vnz ; /* V(:,k) starts here */ + w [k] = k ; /* add V(k,k) to pattern of V */ + Vi [vnz++] = k ; + top = n ; + col = q ? q [k] : k ; + for (p = Ap [col] ; p < Ap [col+1] ; p++) /* find R(:,k) pattern */ + { + i = leftmost [Ai [p]] ; /* i = min(find(A(i,q))) */ + for (len = 0 ; w [i] != k ; i = parent [i]) /* traverse up to k */ + { + s [len++] = i ; + w [i] = k ; + } + while (len > 0) s [--top] = s [--len] ; /* push path on stack */ + i = pinv [Ai [p]] ; /* i = permuted row of A(:,col) */ + x [i] = Ax [p] ; /* x (i) = A(:,col) */ + if (i > k && w [i] < k) /* pattern of V(:,k) = x (k+1:m) */ + { + Vi [vnz++] = i ; /* add i to pattern of V(:,k) */ + w [i] = k ; + } + } + for (p = top ; p < n ; p++) /* for each i in pattern of R(:,k) */ + { + i = s [p] ; /* R(i,k) is nonzero */ + cs_happly (V, i, Beta [i], x) ; /* apply (V(i),Beta(i)) to x */ + Ri [rnz] = i ; /* R(i,k) = x(i) */ + Rx [rnz++] = x [i] ; + x [i] = 0 ; + if (parent [i] == k) vnz = cs_scatter (V, i, 0, w, NULL, k, V, vnz); + } + for (p = p1 ; p < vnz ; p++) /* gather V(:,k) = x */ + { + Vx [p] = x [Vi [p]] ; + x [Vi [p]] = 0 ; + } + Ri [rnz] = k ; /* R(k,k) = norm (x) */ + Rx [rnz++] = cs_house (Vx+p1, Beta+k, vnz-p1) ; /* [v,beta]=house(x) */ + } + Rp [n] = rnz ; /* finalize R */ + Vp [n] = vnz ; /* finalize V */ + return (cs_ndone (N, NULL, w, x, 1)) ; /* success */ +} diff --git a/src/cs/cs_qrsol.c b/src/cs/cs_qrsol.c new file mode 100644 index 0000000..0ad45ff --- /dev/null +++ b/src/cs/cs_qrsol.c @@ -0,0 +1,73 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* x=A\b where A can be rectangular; b overwritten with solution */ +CS_INT cs_qrsol (CS_INT order, const cs *A, CS_ENTRY *b) +{ + CS_ENTRY *x ; + css *S ; + csn *N ; + cs *AT = NULL ; + CS_INT k, m, n, ok ; + if (!CS_CSC (A) || !b) return (0) ; /* check inputs */ + n = A->n ; + m = A->m ; + if (m >= n) + { + S = cs_sqr (order, A, 1) ; /* ordering and symbolic analysis */ + N = cs_qr (A, S) ; /* numeric QR factorization */ + x = cs_calloc (S ? S->m2 : 1, sizeof (CS_ENTRY)) ; /* get workspace */ + ok = (S && N && x) ; + if (ok) + { + cs_ipvec (S->pinv, b, x, m) ; /* x(0:m-1) = b(p(0:m-1) */ + for (k = 0 ; k < n ; k++) /* apply Householder refl. to x */ + { + cs_happly (N->L, k, N->B [k], x) ; + } + cs_usolve (N->U, x) ; /* x = R\x */ + cs_ipvec (S->q, x, b, n) ; /* b(q(0:n-1)) = x(0:n-1) */ + } + } + else + { + AT = cs_transpose (A, 1) ; /* Ax=b is underdetermined */ + S = cs_sqr (order, AT, 1) ; /* ordering and symbolic analysis */ + N = cs_qr (AT, S) ; /* numeric QR factorization of A' */ + x = cs_calloc (S ? S->m2 : 1, sizeof (CS_ENTRY)) ; /* get workspace */ + ok = (AT && S && N && x) ; + if (ok) + { + cs_pvec (S->q, b, x, m) ; /* x(q(0:m-1)) = b(0:m-1) */ + cs_utsolve (N->U, x) ; /* x = R'\x */ + for (k = m-1 ; k >= 0 ; k--) /* apply Householder refl. to x */ + { + cs_happly (N->L, k, N->B [k], x) ; + } + cs_pvec (S->pinv, x, b, n) ; /* b(0:n-1) = x(p(0:n-1)) */ + } + } + cs_free (x) ; + cs_sfree (S) ; + cs_nfree (N) ; + cs_spfree (AT) ; + return (ok) ; +} diff --git a/src/cs/cs_randperm.c b/src/cs/cs_randperm.c new file mode 100644 index 0000000..1359820 --- /dev/null +++ b/src/cs/cs_randperm.c @@ -0,0 +1,47 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "igraph_random.h" + +#include "cs.h" +/* return a random permutation vector, the identity perm, or p = n-1:-1:0. + * seed = -1 means p = n-1:-1:0. seed = 0 means p = identity. otherwise + * p = random permutation. */ +CS_INT *cs_randperm (CS_INT n, CS_INT seed) +{ + CS_INT *p, k, j, t ; + if (seed == 0) return (NULL) ; /* return p = NULL (identity) */ + p = cs_malloc (n, sizeof (CS_INT)) ; /* allocate result */ + if (!p) return (NULL) ; /* out of memory */ + for (k = 0 ; k < n ; k++) p [k] = n-k-1 ; + if (seed == -1) return (p) ; /* return reverse permutation */ + /* srand (seed) ; /\* get new random number seed *\/ */ + RNG_BEGIN(); + for (k = 0 ; k < n ; k++) + { + /* j = k + (rand ( ) % (n-k)) ; /\* j = rand CS_INT in range k to n-1 *\/ */ + j = k + RNG_INTEGER(k, n-1) ; + t = p [j] ; /* swap p[k] and p[j] */ + p [j] = p [k] ; + p [k] = t ; + } + RNG_END(); + return (p) ; +} diff --git a/src/cs/cs_reach.c b/src/cs/cs_reach.c new file mode 100644 index 0000000..35b43f8 --- /dev/null +++ b/src/cs/cs_reach.c @@ -0,0 +1,39 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* xi [top...n-1] = nodes reachable from graph of G*P' via nodes in B(:,k). + * xi [n...2n-1] used as workspace */ +CS_INT cs_reach (cs *G, const cs *B, CS_INT k, CS_INT *xi, const CS_INT *pinv) +{ + CS_INT p, n, top, *Bp, *Bi, *Gp ; + if (!CS_CSC (G) || !CS_CSC (B) || !xi) return (-1) ; /* check inputs */ + n = G->n ; Bp = B->p ; Bi = B->i ; Gp = G->p ; + top = n ; + for (p = Bp [k] ; p < Bp [k+1] ; p++) + { + if (!CS_MARKED (Gp, Bi [p])) /* start a dfs at unmarked node i */ + { + top = cs_dfs (Bi [p], G, top, xi, xi+n, pinv) ; + } + } + for (p = top ; p < n ; p++) CS_MARK (Gp, xi [p]) ; /* restore G */ + return (top) ; +} diff --git a/src/cs/cs_scatter.c b/src/cs/cs_scatter.c new file mode 100644 index 0000000..8910c62 --- /dev/null +++ b/src/cs/cs_scatter.c @@ -0,0 +1,42 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* x = x + beta * A(:,j), where x is a dense vector and A(:,j) is sparse */ +CS_INT cs_scatter (const cs *A, CS_INT j, CS_ENTRY beta, CS_INT *w, CS_ENTRY *x, CS_INT mark, + cs *C, CS_INT nz) +{ + CS_INT i, p, *Ap, *Ai, *Ci ; + CS_ENTRY *Ax ; + if (!CS_CSC (A) || !w || !CS_CSC (C)) return (-1) ; /* check inputs */ + Ap = A->p ; Ai = A->i ; Ax = A->x ; Ci = C->i ; + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; /* A(i,j) is nonzero */ + if (w [i] < mark) + { + w [i] = mark ; /* i is new entry in column j */ + Ci [nz++] = i ; /* add i to pattern of C(:,j) */ + if (x) x [i] = beta * Ax [p] ; /* x(i) = beta*A(i,j) */ + } + else if (x) x [i] += beta * Ax [p] ; /* i exists in C(:,j) already */ + } + return (nz) ; +} diff --git a/src/cs/cs_scc.c b/src/cs/cs_scc.c new file mode 100644 index 0000000..c9637ba --- /dev/null +++ b/src/cs/cs_scc.c @@ -0,0 +1,61 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* find the strongly connected components of a square matrix */ +csd *cs_scc (cs *A) /* matrix A temporarily modified, then restored */ +{ + CS_INT n, i, k, b, nb = 0, top, *xi, *pstack, *p, *r, *Ap, *ATp, *rcopy, *Blk ; + cs *AT ; + csd *D ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + n = A->n ; Ap = A->p ; + D = cs_dalloc (n, 0) ; /* allocate result */ + AT = cs_transpose (A, 0) ; /* AT = A' */ + xi = cs_malloc (2*n+1, sizeof (CS_INT)) ; /* get workspace */ + if (!D || !AT || !xi) return (cs_ddone (D, AT, xi, 0)) ; + Blk = xi ; rcopy = pstack = xi + n ; + p = D->p ; r = D->r ; ATp = AT->p ; + top = n ; + for (i = 0 ; i < n ; i++) /* first dfs(A) to find finish times (xi) */ + { + if (!CS_MARKED (Ap, i)) top = cs_dfs (i, A, top, xi, pstack, NULL) ; + } + for (i = 0 ; i < n ; i++) CS_MARK (Ap, i) ; /* restore A; unmark all nodes*/ + top = n ; + nb = n ; + for (k = 0 ; k < n ; k++) /* dfs(A') to find strongly connnected comp */ + { + i = xi [k] ; /* get i in reverse order of finish times */ + if (CS_MARKED (ATp, i)) continue ; /* skip node i if already ordered */ + r [nb--] = top ; /* node i is the start of a component in p */ + top = cs_dfs (i, AT, top, p, pstack, NULL) ; + } + r [nb] = 0 ; /* first block starts at zero; shift r up */ + for (k = nb ; k <= n ; k++) r [k-nb] = r [k] ; + D->nb = nb = n-nb ; /* nb = # of strongly connected components */ + for (b = 0 ; b < nb ; b++) /* sort each block in natural order */ + { + for (k = r [b] ; k < r [b+1] ; k++) Blk [p [k]] = b ; + } + for (b = 0 ; b <= nb ; b++) rcopy [b] = r [b] ; + for (i = 0 ; i < n ; i++) p [rcopy [Blk [i]]++] = i ; + return (cs_ddone (D, AT, xi, 1)) ; +} diff --git a/src/cs/cs_schol.c b/src/cs/cs_schol.c new file mode 100644 index 0000000..20f6d1e --- /dev/null +++ b/src/cs/cs_schol.c @@ -0,0 +1,46 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* ordering and symbolic analysis for a Cholesky factorization */ +css *cs_schol (CS_INT order, const cs *A) +{ + CS_INT n, *c, *post, *P ; + cs *C ; + css *S ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + n = A->n ; + S = cs_calloc (1, sizeof (css)) ; /* allocate result S */ + if (!S) return (NULL) ; /* out of memory */ + P = cs_amd (order, A) ; /* P = amd(A+A'), or natural */ + S->pinv = cs_pinv (P, n) ; /* find inverse permutation */ + cs_free (P) ; + if (order && !S->pinv) return (cs_sfree (S)) ; + C = cs_symperm (A, S->pinv, 0) ; /* C = spones(triu(A(P,P))) */ + S->parent = cs_etree (C, 0) ; /* find etree of C */ + post = cs_post (S->parent, n) ; /* postorder the etree */ + c = cs_counts (C, S->parent, post, 0) ; /* find column counts of chol(C) */ + cs_free (post) ; + cs_spfree (C) ; + S->cp = cs_malloc (n+1, sizeof (CS_INT)) ; /* allocate result S->cp */ + S->unz = S->lnz = cs_cumsum (S->cp, c, n) ; /* find column pointers for L */ + cs_free (c) ; + return ((S->lnz >= 0) ? S : cs_sfree (S)) ; +} diff --git a/src/cs/cs_spsolve.c b/src/cs/cs_spsolve.c new file mode 100644 index 0000000..175ab55 --- /dev/null +++ b/src/cs/cs_spsolve.c @@ -0,0 +1,48 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* solve Gx=b(:,k), where G is either upper (lo=0) or lower (lo=1) triangular */ +CS_INT cs_spsolve (cs *G, const cs *B, CS_INT k, CS_INT *xi, CS_ENTRY *x, const CS_INT *pinv, + CS_INT lo) +{ + CS_INT j, J, p, q, px, top, n, *Gp, *Gi, *Bp, *Bi ; + CS_ENTRY *Gx, *Bx ; + if (!CS_CSC (G) || !CS_CSC (B) || !xi || !x) return (-1) ; + Gp = G->p ; Gi = G->i ; Gx = G->x ; n = G->n ; + Bp = B->p ; Bi = B->i ; Bx = B->x ; + top = cs_reach (G, B, k, xi, pinv) ; /* xi[top..n-1]=Reach(B(:,k)) */ + for (p = top ; p < n ; p++) x [xi [p]] = 0 ; /* clear x */ + for (p = Bp [k] ; p < Bp [k+1] ; p++) x [Bi [p]] = Bx [p] ; /* scatter B */ + for (px = top ; px < n ; px++) + { + j = xi [px] ; /* x(j) is nonzero */ + J = pinv ? (pinv [j]) : j ; /* j maps to col J of G */ + if (J < 0) continue ; /* column J is empty */ + x [j] /= Gx [lo ? (Gp [J]) : (Gp [J+1]-1)] ;/* x(j) /= G(j,j) */ + p = lo ? (Gp [J]+1) : (Gp [J]) ; /* lo: L(j,j) 1st entry */ + q = lo ? (Gp [J+1]) : (Gp [J+1]-1) ; /* up: U(j,j) last entry */ + for ( ; p < q ; p++) + { + x [Gi [p]] -= Gx [p] * x [j] ; /* x(i) -= G(i,j) * x(j) */ + } + } + return (top) ; /* return top of stack */ +} diff --git a/src/cs/cs_sqr.c b/src/cs/cs_sqr.c new file mode 100644 index 0000000..1a7ec7f --- /dev/null +++ b/src/cs/cs_sqr.c @@ -0,0 +1,108 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* compute nnz(V) = S->lnz, S->pinv, S->leftmost, S->m2 from A and S->parent */ +static CS_INT cs_vcount (const cs *A, css *S) +{ + CS_INT i, k, p, pa, n = A->n, m = A->m, *Ap = A->p, *Ai = A->i, *next, *head, + *tail, *nque, *pinv, *leftmost, *w, *parent = S->parent ; + S->pinv = pinv = cs_malloc (m+n, sizeof (CS_INT)) ; /* allocate pinv, */ + S->leftmost = leftmost = cs_malloc (m, sizeof (CS_INT)) ; /* and leftmost */ + w = cs_malloc (m+3*n, sizeof (CS_INT)) ; /* get workspace */ + if (!pinv || !w || !leftmost) + { + cs_free (w) ; /* pinv and leftmost freed later */ + return (0) ; /* out of memory */ + } + next = w ; head = w + m ; tail = w + m + n ; nque = w + m + 2*n ; + for (k = 0 ; k < n ; k++) head [k] = -1 ; /* queue k is empty */ + for (k = 0 ; k < n ; k++) tail [k] = -1 ; + for (k = 0 ; k < n ; k++) nque [k] = 0 ; + for (i = 0 ; i < m ; i++) leftmost [i] = -1 ; + for (k = n-1 ; k >= 0 ; k--) + { + for (p = Ap [k] ; p < Ap [k+1] ; p++) + { + leftmost [Ai [p]] = k ; /* leftmost[i] = min(find(A(i,:)))*/ + } + } + for (i = m-1 ; i >= 0 ; i--) /* scan rows in reverse order */ + { + pinv [i] = -1 ; /* row i is not yet ordered */ + k = leftmost [i] ; + if (k == -1) continue ; /* row i is empty */ + if (nque [k]++ == 0) tail [k] = i ; /* first row in queue k */ + next [i] = head [k] ; /* put i at head of queue k */ + head [k] = i ; + } + S->lnz = 0 ; + S->m2 = m ; + for (k = 0 ; k < n ; k++) /* find row permutation and nnz(V)*/ + { + i = head [k] ; /* remove row i from queue k */ + S->lnz++ ; /* count V(k,k) as nonzero */ + if (i < 0) i = S->m2++ ; /* add a fictitious row */ + pinv [i] = k ; /* associate row i with V(:,k) */ + if (--nque [k] <= 0) continue ; /* skip if V(k+1:m,k) is empty */ + S->lnz += nque [k] ; /* nque [k] is nnz (V(k+1:m,k)) */ + if ((pa = parent [k]) != -1) /* move all rows to parent of k */ + { + if (nque [pa] == 0) tail [pa] = tail [k] ; + next [tail [k]] = head [pa] ; + head [pa] = next [i] ; + nque [pa] += nque [k] ; + } + } + for (i = 0 ; i < m ; i++) if (pinv [i] < 0) pinv [i] = k++ ; + cs_free (w) ; + return (1) ; +} + +/* symbolic ordering and analysis for QR or LU */ +css *cs_sqr (CS_INT order, const cs *A, CS_INT qr) +{ + CS_INT n, k, ok = 1, *post ; + css *S ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + n = A->n ; + S = cs_calloc (1, sizeof (css)) ; /* allocate result S */ + if (!S) return (NULL) ; /* out of memory */ + S->q = cs_amd (order, A) ; /* fill-reducing ordering */ + if (order && !S->q) return (cs_sfree (S)) ; + if (qr) /* QR symbolic analysis */ + { + cs *C = order ? cs_permute (A, NULL, S->q, 0) : ((cs *) A) ; + S->parent = cs_etree (C, 1) ; /* etree of C'*C, where C=A(:,q) */ + post = cs_post (S->parent, n) ; + S->cp = cs_counts (C, S->parent, post, 1) ; /* col counts chol(C'*C) */ + cs_free (post) ; + ok = C && S->parent && S->cp && cs_vcount (C, S) ; + if (ok) for (S->unz = 0, k = 0 ; k < n ; k++) S->unz += S->cp [k] ; + ok = ok && S->lnz >= 0 && S->unz >= 0 ; /* CS_INT overflow guard */ + if (order) cs_spfree (C) ; + } + else + { + S->unz = 4*(A->p [n]) + n ; /* for LU factorization only, */ + S->lnz = S->unz ; /* guess nnz(L) and nnz(U) */ + } + return (ok ? S : cs_sfree (S)) ; /* return result S */ +} diff --git a/src/cs/cs_symperm.c b/src/cs/cs_symperm.c new file mode 100644 index 0000000..33c24bc --- /dev/null +++ b/src/cs/cs_symperm.c @@ -0,0 +1,59 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* C = A(p,p) where A and C are symmetric the upper part stored; pinv not p */ +cs *cs_symperm (const cs *A, const CS_INT *pinv, CS_INT values) +{ + CS_INT i, j, p, q, i2, j2, n, *Ap, *Ai, *Cp, *Ci, *w ; + CS_ENTRY *Cx, *Ax ; + cs *C ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + C = cs_spalloc (n, n, Ap [n], values && (Ax != NULL), 0) ; /* alloc result*/ + w = cs_calloc (n, sizeof (CS_INT)) ; /* get workspace */ + if (!C || !w) return (cs_done (C, w, NULL, 0)) ; /* out of memory */ + Cp = C->p ; Ci = C->i ; Cx = C->x ; + for (j = 0 ; j < n ; j++) /* count entries in each column of C */ + { + j2 = pinv ? pinv [j] : j ; /* column j of A is column j2 of C */ + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; + if (i > j) continue ; /* skip lower triangular part of A */ + i2 = pinv ? pinv [i] : i ; /* row i of A is row i2 of C */ + w [CS_MAX (i2, j2)]++ ; /* column count of C */ + } + } + cs_cumsum (Cp, w, n) ; /* compute column pointers of C */ + for (j = 0 ; j < n ; j++) + { + j2 = pinv ? pinv [j] : j ; /* column j of A is column j2 of C */ + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + i = Ai [p] ; + if (i > j) continue ; /* skip lower triangular part of A*/ + i2 = pinv ? pinv [i] : i ; /* row i of A is row i2 of C */ + Ci [q = w [CS_MAX (i2, j2)]++] = CS_MIN (i2, j2) ; + if (Cx) Cx [q] = (i2 <= j2) ? Ax [p] : CS_CONJ (Ax [p]) ; + } + } + return (cs_done (C, w, NULL, 1)) ; /* success; free workspace, return C */ +} diff --git a/src/cs/cs_tdfs.c b/src/cs/cs_tdfs.c new file mode 100644 index 0000000..4b11ea1 --- /dev/null +++ b/src/cs/cs_tdfs.c @@ -0,0 +1,44 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* depth-first search and postorder of a tree rooted at node j */ +CS_INT cs_tdfs (CS_INT j, CS_INT k, CS_INT *head, const CS_INT *next, CS_INT *post, CS_INT *stack) +{ + CS_INT i, p, top = 0 ; + if (!head || !next || !post || !stack) return (-1) ; /* check inputs */ + stack [0] = j ; /* place j on the stack */ + while (top >= 0) /* while (stack is not empty) */ + { + p = stack [top] ; /* p = top of stack */ + i = head [p] ; /* i = youngest child of p */ + if (i == -1) + { + top-- ; /* p has no unordered children left */ + post [k++] = p ; /* node p is the kth postordered node */ + } + else + { + head [p] = next [i] ; /* remove i from children of p */ + stack [++top] = i ; /* start dfs on child node i */ + } + } + return (k) ; +} diff --git a/src/cs/cs_transpose.c b/src/cs/cs_transpose.c new file mode 100644 index 0000000..bf51a03 --- /dev/null +++ b/src/cs/cs_transpose.c @@ -0,0 +1,45 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* C = A' */ +cs *cs_transpose (const cs *A, CS_INT values) +{ + CS_INT p, q, j, *Cp, *Ci, n, m, *Ap, *Ai, *w ; + CS_ENTRY *Cx, *Ax ; + cs *C ; + if (!CS_CSC (A)) return (NULL) ; /* check inputs */ + m = A->m ; n = A->n ; Ap = A->p ; Ai = A->i ; Ax = A->x ; + C = cs_spalloc (n, m, Ap [n], values && Ax, 0) ; /* allocate result */ + w = cs_calloc (m, sizeof (CS_INT)) ; /* get workspace */ + if (!C || !w) return (cs_done (C, w, NULL, 0)) ; /* out of memory */ + Cp = C->p ; Ci = C->i ; Cx = C->x ; + for (p = 0 ; p < Ap [n] ; p++) w [Ai [p]]++ ; /* row counts */ + cs_cumsum (Cp, w, m) ; /* row pointers */ + for (j = 0 ; j < n ; j++) + { + for (p = Ap [j] ; p < Ap [j+1] ; p++) + { + Ci [q = w [Ai [p]]++] = j ; /* place A(i,j) as entry C(j,i) */ + if (Cx) Cx [q] = (values > 0) ? CS_CONJ (Ax [p]) : Ax [p] ; + } + } + return (cs_done (C, w, NULL, 1)) ; /* success; free w and return C */ +} diff --git a/src/cs/cs_updown.c b/src/cs/cs_updown.c new file mode 100644 index 0000000..d6a2f19 --- /dev/null +++ b/src/cs/cs_updown.c @@ -0,0 +1,68 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* sparse Cholesky update/downdate, L*L' + sigma*w*w' (sigma = +1 or -1) */ +CS_INT cs_updown (cs *L, CS_INT sigma, const cs *C, const CS_INT *parent) +{ + CS_INT n, p, f, j, *Lp, *Li, *Cp, *Ci ; + CS_ENTRY *Lx, *Cx, alpha, gamma, w1, w2, *w ; + double beta = 1, beta2 = 1, delta ; +#ifdef CS_COMPLEX + cs_complex_t phase ; +#endif + if (!CS_CSC (L) || !CS_CSC (C) || !parent) return (0) ; /* check inputs */ + Lp = L->p ; Li = L->i ; Lx = L->x ; n = L->n ; + Cp = C->p ; Ci = C->i ; Cx = C->x ; + if ((p = Cp [0]) >= Cp [1]) return (1) ; /* return if C empty */ + w = cs_malloc (n, sizeof (CS_ENTRY)) ; /* get workspace */ + if (!w) return (0) ; /* out of memory */ + f = Ci [p] ; + for ( ; p < Cp [1] ; p++) f = CS_MIN (f, Ci [p]) ; /* f = min (find (C)) */ + for (j = f ; j != -1 ; j = parent [j]) w [j] = 0 ; /* clear workspace w */ + for (p = Cp [0] ; p < Cp [1] ; p++) w [Ci [p]] = Cx [p] ; /* w = C */ + for (j = f ; j != -1 ; j = parent [j]) /* walk path f up to root */ + { + p = Lp [j] ; + alpha = w [j] / Lx [p] ; /* alpha = w(j) / L(j,j) */ + beta2 = beta*beta + sigma*alpha*CS_CONJ(alpha) ; + if (beta2 <= 0) break ; /* not positive definite */ + beta2 = sqrt (beta2) ; + delta = (sigma > 0) ? (beta / beta2) : (beta2 / beta) ; + gamma = sigma * CS_CONJ(alpha) / (beta2 * beta) ; + Lx [p] = delta * Lx [p] + ((sigma > 0) ? (gamma * w [j]) : 0) ; + beta = beta2 ; +#ifdef CS_COMPLEX + phase = CS_ABS (Lx [p]) / Lx [p] ; /* phase = abs(L(j,j))/L(j,j)*/ + Lx [p] *= phase ; /* L(j,j) = L(j,j) * phase */ +#endif + for (p++ ; p < Lp [j+1] ; p++) + { + w1 = w [Li [p]] ; + w [Li [p]] = w2 = w1 - alpha * Lx [p] ; + Lx [p] = delta * Lx [p] + gamma * ((sigma > 0) ? w1 : w2) ; +#ifdef CS_COMPLEX + Lx [p] *= phase ; /* L(i,j) = L(i,j) * phase */ +#endif + } + } + cs_free (w) ; + return (beta2 > 0) ; +} diff --git a/src/cs/cs_usolve.c b/src/cs/cs_usolve.c new file mode 100644 index 0000000..a94fa26 --- /dev/null +++ b/src/cs/cs_usolve.c @@ -0,0 +1,38 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* solve Ux=b where x and b are dense. x=b on input, solution on output. */ +CS_INT cs_usolve (const cs *U, CS_ENTRY *x) +{ + CS_INT p, j, n, *Up, *Ui ; + CS_ENTRY *Ux ; + if (!CS_CSC (U) || !x) return (0) ; /* check inputs */ + n = U->n ; Up = U->p ; Ui = U->i ; Ux = U->x ; + for (j = n-1 ; j >= 0 ; j--) + { + x [j] /= Ux [Up [j+1]-1] ; + for (p = Up [j] ; p < Up [j+1]-1 ; p++) + { + x [Ui [p]] -= Ux [p] * x [j] ; + } + } + return (1) ; +} diff --git a/src/cs/cs_util.c b/src/cs/cs_util.c new file mode 100644 index 0000000..8e10a94 --- /dev/null +++ b/src/cs/cs_util.c @@ -0,0 +1,139 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* allocate a sparse matrix (triplet form or compressed-column form) */ +cs *cs_spalloc (CS_INT m, CS_INT n, CS_INT nzmax, CS_INT values, CS_INT triplet) +{ + cs *A = cs_calloc (1, sizeof (cs)) ; /* allocate the cs struct */ + if (!A) return (NULL) ; /* out of memory */ + A->m = m ; /* define dimensions and nzmax */ + A->n = n ; + A->nzmax = nzmax = CS_MAX (nzmax, 1) ; + A->nz = triplet ? 0 : -1 ; /* allocate triplet or comp.col */ + A->p = cs_malloc (triplet ? nzmax : n+1, sizeof (CS_INT)) ; + A->i = cs_malloc (nzmax, sizeof (CS_INT)) ; + A->x = values ? cs_malloc (nzmax, sizeof (CS_ENTRY)) : NULL ; + return ((!A->p || !A->i || (values && !A->x)) ? cs_spfree (A) : A) ; +} + +/* change the max # of entries sparse matrix */ +CS_INT cs_sprealloc (cs *A, CS_INT nzmax) +{ + CS_INT ok, oki, okj = 1, okx = 1 ; + if (!A) return (0) ; + if (nzmax <= 0) nzmax = (CS_CSC (A)) ? (A->p [A->n]) : A->nz ; + A->i = cs_realloc (A->i, nzmax, sizeof (CS_INT), &oki) ; + if (CS_TRIPLET (A)) A->p = cs_realloc (A->p, nzmax, sizeof (CS_INT), &okj) ; + if (A->x) A->x = cs_realloc (A->x, nzmax, sizeof (CS_ENTRY), &okx) ; + ok = (oki && okj && okx) ; + if (ok) A->nzmax = nzmax ; + return (ok) ; +} + +/* free a sparse matrix */ +cs *cs_spfree (cs *A) +{ + if (!A) return (NULL) ; /* do nothing if A already NULL */ + cs_free (A->p) ; + cs_free (A->i) ; + cs_free (A->x) ; + return (cs_free (A)) ; /* free the cs struct and return NULL */ +} + +/* free a numeric factorization */ +csn *cs_nfree (csn *N) +{ + if (!N) return (NULL) ; /* do nothing if N already NULL */ + cs_spfree (N->L) ; + cs_spfree (N->U) ; + cs_free (N->pinv) ; + cs_free (N->B) ; + return (cs_free (N)) ; /* free the csn struct and return NULL */ +} + +/* free a symbolic factorization */ +css *cs_sfree (css *S) +{ + if (!S) return (NULL) ; /* do nothing if S already NULL */ + cs_free (S->pinv) ; + cs_free (S->q) ; + cs_free (S->parent) ; + cs_free (S->cp) ; + cs_free (S->leftmost) ; + return (cs_free (S)) ; /* free the css struct and return NULL */ +} + +/* allocate a cs_dmperm or cs_scc result */ +csd *cs_dalloc (CS_INT m, CS_INT n) +{ + csd *D ; + D = cs_calloc (1, sizeof (csd)) ; + if (!D) return (NULL) ; + D->p = cs_malloc (m, sizeof (CS_INT)) ; + D->r = cs_malloc (m+6, sizeof (CS_INT)) ; + D->q = cs_malloc (n, sizeof (CS_INT)) ; + D->s = cs_malloc (n+6, sizeof (CS_INT)) ; + return ((!D->p || !D->r || !D->q || !D->s) ? cs_dfree (D) : D) ; +} + +/* free a cs_dmperm or cs_scc result */ +csd *cs_dfree (csd *D) +{ + if (!D) return (NULL) ; /* do nothing if D already NULL */ + cs_free (D->p) ; + cs_free (D->q) ; + cs_free (D->r) ; + cs_free (D->s) ; + return (cs_free (D)) ; +} + +/* free workspace and return a sparse matrix result */ +cs *cs_done (cs *C, void *w, void *x, CS_INT ok) +{ + cs_free (w) ; /* free workspace */ + cs_free (x) ; + return (ok ? C : cs_spfree (C)) ; /* return result if OK, else free it */ +} + +/* free workspace and return CS_INT array result */ +CS_INT *cs_idone (CS_INT *p, cs *C, void *w, CS_INT ok) +{ + cs_spfree (C) ; /* free temporary matrix */ + cs_free (w) ; /* free workspace */ + return (ok ? p : cs_free (p)) ; /* return result if OK, else free it */ +} + +/* free workspace and return a numeric factorization (Cholesky, LU, or QR) */ +csn *cs_ndone (csn *N, cs *C, void *w, void *x, CS_INT ok) +{ + cs_spfree (C) ; /* free temporary matrix */ + cs_free (w) ; /* free workspace */ + cs_free (x) ; + return (ok ? N : cs_nfree (N)) ; /* return result if OK, else free it */ +} + +/* free workspace and return a csd result */ +csd *cs_ddone (csd *D, cs *C, void *w, CS_INT ok) +{ + cs_spfree (C) ; /* free temporary matrix */ + cs_free (w) ; /* free workspace */ + return (ok ? D : cs_dfree (D)) ; /* return result if OK, else free it */ +} diff --git a/src/cs/cs_utsolve.c b/src/cs/cs_utsolve.c new file mode 100644 index 0000000..c4522ab --- /dev/null +++ b/src/cs/cs_utsolve.c @@ -0,0 +1,38 @@ +/* + * CXSPARSE: a Concise Sparse Matrix package - Extended. + * Copyright (c) 2006-2009, Timothy A. Davis. + * http://www.cise.ufl.edu/research/sparse/CXSparse + * + * CXSparse is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * CXSparse is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this Module; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cs.h" +/* solve U'x=b where x and b are dense. x=b on input, solution on output. */ +CS_INT cs_utsolve (const cs *U, CS_ENTRY *x) +{ + CS_INT p, j, n, *Up, *Ui ; + CS_ENTRY *Ux ; + if (!CS_CSC (U) || !x) return (0) ; /* check inputs */ + n = U->n ; Up = U->p ; Ui = U->i ; Ux = U->x ; + for (j = 0 ; j < n ; j++) + { + for (p = Up [j] ; p < Up [j+1]-1 ; p++) + { + x [j] -= CS_CONJ (Ux [p]) * x [Ui [p]] ; + } + x [j] /= CS_CONJ (Ux [Up [j+1]-1]) ; + } + return (1) ; +} diff --git a/src/decomposition.c b/src/decomposition.c new file mode 100644 index 0000000..ed2397d --- /dev/null +++ b/src/decomposition.c @@ -0,0 +1,471 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2008-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_structural.h" +#include "igraph_error.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" + +/** + * \function igraph_maximum_cardinality_search + * Maximum cardinality search + * + * This function implements the maximum cardinality search algorithm + * discussed in + * Robert E Tarjan and Mihalis Yannakakis: Simple linear-time + * algorithms to test chordality of graphs, test acyclicity of + * hypergraphs, and selectively reduce acyclic hypergraphs. + * SIAM Journal of Computation 13, 566--579, 1984. + * + * \param graph The input graph, which should be undirected and simple. + * of the edges is ignored. + * \param alpha Pointer to an initialized vector, the result is stored here. + * It will be resized, as needed. Upon return it contains + * the rank of the each vertex. + * \param alpham1 Pointer to an initialized vector or a \c NULL + * pointer. If not \c NULL, then the inverse of \p alpha is stored + * here. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in terms of the number of + * vertices and edges. + * + * \sa \ref igraph_is_chordal(). + */ + +int igraph_maximum_cardinality_search(const igraph_t *graph, + igraph_vector_t *alpha, + igraph_vector_t *alpham1) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_long_t size; + igraph_vector_long_t head, next, prev; /* doubly linked list with head */ + long int i; + igraph_adjlist_t adjlist; + igraph_bool_t simple; + + /***************/ + /* local j, v; */ + /***************/ + + long int j, v; + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Maximum cardinality search works on undirected graphs only", IGRAPH_EINVAL); + } + + igraph_is_simple(graph, &simple); + if (!simple) { + IGRAPH_ERROR("Maximum cardinality search works on simple graphs only", IGRAPH_EINVAL); + } + + if (no_of_nodes == 0) { + igraph_vector_clear(alpha); + if (alpham1) { + igraph_vector_clear(alpham1); + } + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_vector_long_init(&size, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &size); + IGRAPH_CHECK(igraph_vector_long_init(&head, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &head); + IGRAPH_CHECK(igraph_vector_long_init(&next, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &next); + IGRAPH_CHECK(igraph_vector_long_init(&prev, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &prev); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_vector_resize(alpha, no_of_nodes)); + if (alpham1) { + IGRAPH_CHECK(igraph_vector_resize(alpham1, no_of_nodes)); + } + + /***********************************************/ + /* for i in [0,n-1] -> set(i) := emptyset rof; */ + /***********************************************/ + + /* nothing to do, 'head' contains all zeros */ + + /*********************************************************/ + /* for v in vertices -> size(v):=0; add v to set(0) rof; */ + /*********************************************************/ + + VECTOR(head)[0] = 1; + for (v = 0; v < no_of_nodes; v++) { + VECTOR(next)[v] = v + 2; + VECTOR(prev)[v] = v; + } + VECTOR(next)[no_of_nodes - 1] = 0; + /* size is already all zero */ + + /***************/ + /* i:=n; j:=0; */ + /***************/ + + i = no_of_nodes; j = 0; + + /**************/ + /* do i>=1 -> */ + /**************/ + + while (i >= 1) { + long int x, k, len; + igraph_vector_int_t *neis; + + /********************************/ + /* v := delete any from set(j) */ + /********************************/ + + v = VECTOR(head)[j] - 1; + x = VECTOR(next)[v]; + VECTOR(head)[j] = x; + if (x != 0) { + VECTOR(prev)[x - 1] = 0; + } + + /*************************************************/ + /* alpha(v) := i; alpham1(i) := v; size(v) := -1 */ + /*************************************************/ + + VECTOR(*alpha)[v] = i - 1; + if (alpham1) { + VECTOR(*alpham1)[i - 1] = v; + } + VECTOR(size)[v] = -1; + + /********************************************/ + /* for {v,w} in E such that size(w) >= 0 -> */ + /********************************************/ + + neis = igraph_adjlist_get(&adjlist, v); + len = igraph_vector_int_size(neis); + for (k = 0; k < len; k++) { + long int w = (long int) VECTOR(*neis)[k]; + long int ws = VECTOR(size)[w]; + if (ws >= 0) { + + /******************************/ + /* delete w from set(size(w)) */ + /******************************/ + + long int nw = VECTOR(next)[w]; + long int pw = VECTOR(prev)[w]; + if (nw != 0) { + VECTOR(prev)[nw - 1] = pw; + } + if (pw != 0) { + VECTOR(next)[pw - 1] = nw; + } else { + VECTOR(head)[ws] = nw; + } + + /******************************/ + /* size(w) := size(w)+1 */ + /******************************/ + + VECTOR(size)[w] += 1; + + /******************************/ + /* add w to set(size(w)) */ + /******************************/ + + ws = VECTOR(size)[w]; + nw = VECTOR(head)[ws]; + VECTOR(next)[w] = nw; + VECTOR(prev)[w] = 0; + if (nw != 0) { + VECTOR(prev)[nw - 1] = w + 1; + } + VECTOR(head)[ws] = w + 1; + + } + } + + /***********************/ + /* i := i-1; j := j+1; */ + /***********************/ + + i -= 1; + j += 1; + + /*********************************************/ + /* do j>=0 and set(j)=emptyset -> j:=j-1; od */ + /*********************************************/ + + if (j < no_of_nodes) { + while (j >= 0 && VECTOR(head)[j] == 0) { + j--; + } + } + } + + igraph_adjlist_destroy(&adjlist); + igraph_vector_long_destroy(&prev); + igraph_vector_long_destroy(&next); + igraph_vector_long_destroy(&head); + igraph_vector_long_destroy(&size); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +/** + * \function igraph_is_chordal + * Decides whether a graph is chordal + * + * A graph is chordal if each of its cycles of four or more nodes + * has a chord, which is an edge joining two nodes that are not + * adjacent in the cycle. An equivalent definition is that any + * chordless cycles have at most three nodes. + * + * If either \p alpha or \p alpha1 is given, then the other is + * calculated by taking simply the inverse. If neither are given, + * then \ref igraph_maximum_cardinality_search() is called to calculate + * them. + * \param graph The input graph, it might be directed, but edge + * direction is ignored. + * \param alpha Either an alpha vector coming from + * \ref igraph_maximum_cardinality_search() (on the same graph), or a + * null pointer. + * \param alpham1 Either an inverse alpha vector coming from \ref + * igraph_maximum_cardinality_search() (on the same graph) or a null + * pointer. + * \param chordal Pointer to a boolean, the result is stored here. + * \param fill_in Pointer to an initialized vector, or a null + * pointer. If not a null pointer, then the fill-in of the graph is + * stored here. The fill-in is the set of edges that are needed to + * make the graph chordal. The vector is resized as needed. + * \param newgraph Pointer to an uninitialized graph, or a null + * pointer. If not a null pointer, then a new triangulated graph is + * created here. This essentially means adding the fill-in edges to + * the original graph. + * \return Error code. + * + * Time complexity: O(n). + * + * \sa \ref igraph_maximum_cardinality_search(). + */ + +int igraph_is_chordal(const igraph_t *graph, + const igraph_vector_t *alpha, + const igraph_vector_t *alpham1, + igraph_bool_t *chordal, + igraph_vector_t *fill_in, + igraph_t *newgraph) { + + long int no_of_nodes = igraph_vcount(graph); + const igraph_vector_t *my_alpha = alpha, *my_alpham1 = alpham1; + igraph_vector_t v_alpha, v_alpham1; + igraph_vector_long_t f, index; + long int i; + igraph_adjlist_t adjlist; + igraph_vector_long_t mark; + igraph_bool_t calc_edges = fill_in || newgraph; + igraph_vector_t *my_fill_in = fill_in, v_fill_in; + + /*****************/ + /* local v, w, x */ + /*****************/ + + long int v, w, x; + + if (!chordal && !calc_edges) { + /* Nothing to calculate */ + return 0; + } + + if (!alpha && !alpham1) { + IGRAPH_VECTOR_INIT_FINALLY(&v_alpha, no_of_nodes); + my_alpha = &v_alpha; + IGRAPH_VECTOR_INIT_FINALLY(&v_alpham1, no_of_nodes); + my_alpham1 = &v_alpham1; + IGRAPH_CHECK(igraph_maximum_cardinality_search(graph, + (igraph_vector_t*) my_alpha, + (igraph_vector_t*) my_alpham1)); + } else if (alpha && !alpham1) { + long int v; + IGRAPH_VECTOR_INIT_FINALLY(&v_alpham1, no_of_nodes); + my_alpham1 = &v_alpham1; + for (v = 0; v < no_of_nodes; v++) { + long int i = (long int) VECTOR(*my_alpha)[v]; + VECTOR(*my_alpham1)[i] = v; + } + } else if (!alpha && alpham1) { + long int i; + IGRAPH_VECTOR_INIT_FINALLY(&v_alpha, no_of_nodes); + my_alpha = &v_alpha; + for (i = 0; i < no_of_nodes; i++) { + long int v = (long int) VECTOR(*my_alpham1)[i]; + VECTOR(*my_alpha)[v] = i; + } + } + + if (!fill_in && newgraph) { + IGRAPH_VECTOR_INIT_FINALLY(&v_fill_in, 0); + my_fill_in = &v_fill_in; + } + + IGRAPH_CHECK(igraph_vector_long_init(&f, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &f); + IGRAPH_CHECK(igraph_vector_long_init(&index, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &index); + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + IGRAPH_CHECK(igraph_vector_long_init(&mark, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &mark); + if (my_fill_in) { + igraph_vector_clear(my_fill_in); + } + + if (chordal) { + *chordal = 1; + } + + /*********************/ + /* for i in [1,n] -> */ + /*********************/ + + for (i = 0; i < no_of_nodes; i++) { + igraph_vector_int_t *neis; + long int j, len; + + /**********************************************/ + /* w := alpham1(i); f(w) := w; index(w) := i; */ + /**********************************************/ + + w = (long int) VECTOR(*my_alpham1)[i]; + VECTOR(f)[w] = w; + VECTOR(index)[w] = i; + + /******************************************/ + /* for {v,w} in E such that alpha(v) */ + /******************************************/ + + neis = igraph_adjlist_get(&adjlist, w); + len = igraph_vector_int_size(neis); + for (j = 0; j < len; j++) { + v = (long int) VECTOR(*neis)[j]; + VECTOR(mark)[v] = w + 1; + } + + for (j = 0; j < len; j++) { + v = (long int) VECTOR(*neis)[j]; + if (VECTOR(*my_alpha)[v] >= i) { + continue; + } + + /**********/ + /* x := v */ + /**********/ + + x = v; + + /********************/ + /* do index(x) */ + /********************/ + + while (VECTOR(index)[x] < i) { + + /******************/ + /* index(x) := i; */ + /******************/ + + VECTOR(index)[x] = i; + + /**********************************/ + /* add {x,w} to E union F(alpha); */ + /**********************************/ + + if (VECTOR(mark)[x] != w + 1) { + + if (chordal) { + *chordal = 0; + } + + if (my_fill_in) { + IGRAPH_CHECK(igraph_vector_push_back(my_fill_in, x)); + IGRAPH_CHECK(igraph_vector_push_back(my_fill_in, w)); + } + + if (!calc_edges) { + /* make sure that we exit from all loops */ + i = no_of_nodes; + j = len; + break; + } + } + + /*************/ + /* x := f(x) */ + /*************/ + + x = VECTOR(f)[x]; + + } /* while (VECTOR(index)[x] < i) */ + + /*****************************/ + /* if (f(x)=x -> f(x):=w; fi */ + /*****************************/ + + if (VECTOR(f)[x] == x) { + VECTOR(f)[x] = w; + } + } + } + + igraph_vector_long_destroy(&mark); + igraph_adjlist_destroy(&adjlist); + igraph_vector_long_destroy(&index); + igraph_vector_long_destroy(&f); + IGRAPH_FINALLY_CLEAN(4); + + if (newgraph) { + IGRAPH_CHECK(igraph_copy(newgraph, graph)); + IGRAPH_FINALLY(igraph_destroy, newgraph); + IGRAPH_CHECK(igraph_add_edges(newgraph, my_fill_in, 0)); + IGRAPH_FINALLY_CLEAN(1); + } + + if (!fill_in && newgraph) { + igraph_vector_destroy(&v_fill_in); + IGRAPH_FINALLY_CLEAN(1); + } + + if (!alpha && !alpham1) { + igraph_vector_destroy(&v_alpham1); + igraph_vector_destroy(&v_alpha); + IGRAPH_FINALLY_CLEAN(2); + } else if (alpha && !alpham1) { + igraph_vector_destroy(&v_alpham1); + IGRAPH_FINALLY_CLEAN(1); + } else if (!alpha && alpham1) { + igraph_vector_destroy(&v_alpha); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} diff --git a/src/degree_sequence.cpp b/src/degree_sequence.cpp new file mode 100644 index 0000000..31c078e --- /dev/null +++ b/src/degree_sequence.cpp @@ -0,0 +1,490 @@ +/* + Constructing realizations of degree sequences and bi-degree sequences. + Copyright (C) 2018 Szabolcs Horvat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_constructors.h" +#include "igraph_interface.h" + +#include +#include +#include +#include + + +// (vertex, degree) pair +struct vd_pair { + long vertex; + igraph_integer_t degree; + + vd_pair(long vertex, igraph_integer_t degree) : vertex(vertex), degree(degree) {} +}; + +// (indegree, outdegree) +typedef std::pair bidegree; + +// (vertex, bidegree) pair +struct vbd_pair { + long vertex; + bidegree degree; + + vbd_pair(long vertex, bidegree degree) : vertex(vertex), degree(degree) {} +}; + +// Comparison function for vertex-degree pairs. +// Also used for lexicographic sorting of bi-degrees. +template inline bool degree_greater(const T &a, const T &b) { + return a.degree > b.degree; +} + +template inline bool degree_less(const T &a, const T &b) { + return a.degree < b.degree; +} + + +// Generate undirected realization as edge-list. +// If largest=true, always choose the vertex with the largest remaining degree to connect up next. +// Otherwise, always choose the one with the smallest remaining degree. +static int igraph_i_havel_hakimi(const igraph_vector_t *deg, igraph_vector_t *edges, bool largest) { + long n = igraph_vector_size(deg); + + long ec = 0; // number of edges added so far + + std::vector vertices; + vertices.reserve(n); + for (int i = 0; i < n; ++i) { + vertices.push_back(vd_pair(i, VECTOR(*deg)[i])); + } + + while (! vertices.empty()) { + if (largest) { + std::stable_sort(vertices.begin(), vertices.end(), degree_less); + } else { + std::stable_sort(vertices.begin(), vertices.end(), degree_greater); + } + + // take the next vertex to be connected up + vd_pair vd = vertices.back(); + vertices.pop_back(); + + if (vd.degree < 0) { + IGRAPH_ERROR("Vertex degrees must be positive", IGRAPH_EINVAL); + } + + if (vd.degree == 0) { + continue; + } + + if (vertices.size() < size_t(vd.degree)) { + goto fail; + } + + if (largest) { + for (int i = 0; i < vd.degree; ++i) { + if (--(vertices[vertices.size() - 1 - i].degree) < 0) { + goto fail; + } + + VECTOR(*edges)[2 * (ec + i)] = vd.vertex; + VECTOR(*edges)[2 * (ec + i) + 1] = vertices[vertices.size() - 1 - i].vertex; + } + } else { + // this loop can only be reached if all zero-degree nodes have already been removed + // therefore decrementing remaining degrees is safe + for (int i = 0; i < vd.degree; ++i) { + vertices[i].degree--; + + VECTOR(*edges)[2 * (ec + i)] = vd.vertex; + VECTOR(*edges)[2 * (ec + i) + 1] = vertices[i].vertex; + } + } + + ec += vd.degree; + } + + return IGRAPH_SUCCESS; + +fail: + IGRAPH_ERROR("The given degree sequence is not realizable", IGRAPH_EINVAL); +} + + +// Choose vertices in the order of their IDs. +static int igraph_i_havel_hakimi_index(const igraph_vector_t *deg, igraph_vector_t *edges) { + long n = igraph_vector_size(deg); + + long ec = 0; // number of edges added so far + + typedef std::list vlist; + vlist vertices; + for (int i = 0; i < n; ++i) { + vertices.push_back(vd_pair(i, VECTOR(*deg)[i])); + } + + std::vector pointers; + pointers.reserve(n); + for (vlist::iterator it = vertices.begin(); it != vertices.end(); ++it) { + pointers.push_back(it); + } + + for (std::vector::iterator pt = pointers.begin(); pt != pointers.end(); ++pt) { + vertices.sort(degree_greater); + + vd_pair vd = **pt; + vertices.erase(*pt); + + if (vd.degree < 0) { + IGRAPH_ERROR("Vertex degrees must be positive", IGRAPH_EINVAL); + } + + if (vd.degree == 0) { + continue; + } + + int k; + vlist::iterator it; + for (it = vertices.begin(), k = 0; + k != vd.degree && it != vertices.end(); + ++it, ++k) { + if (--(it->degree) < 0) { + goto fail; + } + + VECTOR(*edges)[2 * (ec + k)] = vd.vertex; + VECTOR(*edges)[2 * (ec + k) + 1] = it->vertex; + } + if (it == vertices.end() && k < vd.degree) { + goto fail; + } + + ec += vd.degree; + } + + return IGRAPH_SUCCESS; + +fail: + IGRAPH_ERROR("The given degree sequence is not realizable", IGRAPH_EINVAL); +} + + +inline bool is_nonzero_outdeg(const vbd_pair &vd) { + return (vd.degree.second != 0); +} + + +// The below implementations of the Kleitman-Wang algorithm follow the description in https://arxiv.org/abs/0905.4913 + +// Realize bi-degree sequence as edge list +// If smallest=true, always choose the vertex with "smallest" bi-degree for connecting up next, +// otherwise choose the "largest" (based on lexicographic bi-degree ordering). +static int igraph_i_kleitman_wang(const igraph_vector_t *outdeg, const igraph_vector_t *indeg, igraph_vector_t *edges, bool smallest) { + long n = igraph_vector_size(indeg); // number of vertices + + long ec = 0; // number of edges added so far + + std::vector vertices; + vertices.reserve(n); + for (int i = 0; i < n; ++i) { + vertices.push_back(vbd_pair(i, bidegree(VECTOR(*indeg)[i], VECTOR(*outdeg)[i]))); + } + + while (true) { + // sort vertices by (in, out) degree pairs in decreasing order + std::stable_sort(vertices.begin(), vertices.end(), degree_greater); + + // remove (0,0)-degree vertices + while (!vertices.empty() && vertices.back().degree == bidegree(0, 0)) { + vertices.pop_back(); + } + + // if no vertices remain, stop + if (vertices.empty()) { + break; + } + + // choose a vertex the out-stubs of which will be connected + vbd_pair *vdp; + if (smallest) { + vdp = &*std::find_if(vertices.rbegin(), vertices.rend(), is_nonzero_outdeg); + } else { + vdp = &*std::find_if(vertices.begin(), vertices.end(), is_nonzero_outdeg); + } + + + if (vdp->degree.first < 0 || vdp->degree.second < 0) { + IGRAPH_ERROR("Vertex degrees must be positive", IGRAPH_EINVAL); + } + + // are there a sufficient number of other vertices to connect to? + if (vertices.size() < vdp->degree.second - 1) { + goto fail; + } + + // create the connections + int k = 0; + for (std::vector::iterator it = vertices.begin(); + k < vdp->degree.second; + ++it) { + if (it->vertex == vdp->vertex) { + continue; // do not create a self-loop + } + if (--(it->degree.first) < 0) { + goto fail; + } + + VECTOR(*edges)[2 * (ec + k)] = vdp->vertex; + VECTOR(*edges)[2 * (ec + k) + 1] = it->vertex; + + k++; + } + + ec += vdp->degree.second; + vdp->degree.second = 0; + } + + return IGRAPH_SUCCESS; + +fail: + IGRAPH_ERROR("The given directed degree sequence is not realizable", IGRAPH_EINVAL); +} + + +// Choose vertices in the order of their IDs. +static int igraph_i_kleitman_wang_index(const igraph_vector_t *outdeg, const igraph_vector_t *indeg, igraph_vector_t *edges) { + long n = igraph_vector_size(indeg); // number of vertices + + long ec = 0; // number of edges added so far + + typedef std::list vlist; + vlist vertices; + for (int i = 0; i < n; ++i) { + vertices.push_back(vbd_pair(i, bidegree(VECTOR(*indeg)[i], VECTOR(*outdeg)[i]))); + } + + std::vector pointers; + pointers.reserve(n); + for (vlist::iterator it = vertices.begin(); it != vertices.end(); ++it) { + pointers.push_back(it); + } + + for (std::vector::iterator pt = pointers.begin(); pt != pointers.end(); ++pt) { + // sort vertices by (in, out) degree pairs in decreasing order + // note: std::list::sort does a stable sort + vertices.sort(degree_greater); + + // choose a vertex the out-stubs of which will be connected + vbd_pair &vd = **pt; + + if (vd.degree.second == 0) { + continue; + } + + if (vd.degree.first < 0 || vd.degree.second < 0) { + IGRAPH_ERROR("Vertex degrees must be positive", IGRAPH_EINVAL); + } + + int k = 0; + vlist::iterator it; + for (it = vertices.begin(); + k != vd.degree.second && it != vertices.end(); + ++it) { + if (it->vertex == vd.vertex) { + continue; + } + + if (--(it->degree.first) < 0) { + goto fail; + } + + VECTOR(*edges)[2 * (ec + k)] = vd.vertex; + VECTOR(*edges)[2 * (ec + k) + 1] = it->vertex; + + ++k; + } + if (it == vertices.end() && k < vd.degree.second) { + goto fail; + } + + ec += vd.degree.second; + vd.degree.second = 0; + } + + return IGRAPH_SUCCESS; + +fail: + IGRAPH_ERROR("The given directed degree sequence is not realizable", IGRAPH_EINVAL); +} + + +static int igraph_i_realize_undirected_degree_sequence( + igraph_t *graph, + const igraph_vector_t *deg, + igraph_realize_degseq_t method) { + long node_count = igraph_vector_size(deg); + long deg_sum = long(igraph_vector_sum(deg)); + + if (deg_sum % 2 != 0) { + IGRAPH_ERROR("The sum of degrees must be even for an undirected graph", IGRAPH_EINVAL); + } + + igraph_vector_t edges; + IGRAPH_CHECK(igraph_vector_init(&edges, deg_sum)); + IGRAPH_FINALLY(igraph_vector_destroy, &edges); + + switch (method) { + case IGRAPH_REALIZE_DEGSEQ_SMALLEST: + IGRAPH_CHECK(igraph_i_havel_hakimi(deg, &edges, false)); + break; + case IGRAPH_REALIZE_DEGSEQ_LARGEST: + IGRAPH_CHECK(igraph_i_havel_hakimi(deg, &edges, true)); + break; + case IGRAPH_REALIZE_DEGSEQ_INDEX: + IGRAPH_CHECK(igraph_i_havel_hakimi_index(deg, &edges)); + break; + default: + IGRAPH_ERROR("Invalid degree sequence realization method", IGRAPH_EINVAL); + } + + igraph_create(graph, &edges, igraph_integer_t(node_count), false); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +static int igraph_i_realize_directed_degree_sequence( + igraph_t *graph, + const igraph_vector_t *outdeg, + const igraph_vector_t *indeg, + igraph_realize_degseq_t method) { + long node_count = igraph_vector_size(outdeg); + long edge_count = long(igraph_vector_sum(outdeg)); + + if (igraph_vector_size(indeg) != node_count) { + IGRAPH_ERROR("In- and out-degree sequences must have the same length", IGRAPH_EINVAL); + } + if (igraph_vector_sum(indeg) != edge_count) { + IGRAPH_ERROR("In- and out-degree sequences do not sum to the same value", IGRAPH_EINVAL); + } + + igraph_vector_t edges; + IGRAPH_CHECK(igraph_vector_init(&edges, 2 * edge_count)); + IGRAPH_FINALLY(igraph_vector_destroy, &edges); + + switch (method) { + case IGRAPH_REALIZE_DEGSEQ_SMALLEST: + IGRAPH_CHECK(igraph_i_kleitman_wang(outdeg, indeg, &edges, true)); + break; + case IGRAPH_REALIZE_DEGSEQ_LARGEST: + IGRAPH_CHECK(igraph_i_kleitman_wang(outdeg, indeg, &edges, false)); + break; + case IGRAPH_REALIZE_DEGSEQ_INDEX: + IGRAPH_CHECK(igraph_i_kleitman_wang_index(outdeg, indeg, &edges)); + break; + default: + IGRAPH_ERROR("Invalid bi-degree sequence realization method", IGRAPH_EINVAL); + } + + igraph_create(graph, &edges, igraph_integer_t(node_count), true); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup generators + * \function igraph_realize_degree_sequence + * \brief Generates a graph with the given degree sequence + * + * This function constructs a simple graph that realizes the given degree sequence + * using the Havel-Hakimi algorithm, or the given (directed) out- and in-degree + * sequences using the related Kleitman-Wang algorithm. + * + * The algorithms work by choosing an arbitrary vertex and connecting all its stubs + * to other vertices of highest degree. In the directed case, the "highest" (in, out) degree + * pairs are determined based on lexicographic ordering. + * + * The \c method parameter controls the order in which the vertices to be connected are chosen. + * + * \param graph Pointer to an uninitialized graph object. + * \param outdeg The degree sequence for a simple undirected graph + * (if \p indeg is NULL or of length zero), or the out-degree sequence of + * a directed graph (if \p indeg is of nonzero size). + * \param indeg It is either a zero-length vector or \c NULL (if an undirected graph + * is generated), or the in-degree sequence. + * \param method The method to generate the graph. Possible values: + * \clist + * \cli IGRAPH_REALIZE_DEGSEQ_SMALLEST + * The vertex with smallest remaining degree is selected first. The result is usually + * a graph with high negative degree assortativity. In the undirected case, this method + * is guaranteed to generate a connected graph, provided that a connected realization exists. + * See http://szhorvat.net/pelican/hh-connected-graphs.html for a proof. + * In the directed case it tends to generate weakly connected graphs, but this is not + * guaranteed. + * \cli IGRAPH_REALIZE_DEGSEQ_LARGEST + * The vertex with the largest remaining degree is selected first. The result + * is usually a graph with high positive degree assortativity, and is often disconnected. + * \cli IGRAPH_REALIZE_DEGSEQ_INDEX + * The vertices are selected in order of their index (i.e. their position in the degree vector). + * Note that sorting the degree vector and using the \c INDEX method is not equivalent + * to the \c SMALLEST method above, as \c SMALLEST uses the smallest \em remaining + * degree for selecting vertices, not the smallest \em initial degree. + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * There is not enough memory to perform the operation. + * \cli IGRAPH_EINVAL + * Invalid method parameter, or invalid in- and/or out-degree vectors. + * The degree vectors should be non-negative, the length + * and sum of \p outdeg and \p indeg should match for directed graphs. + * \endclist + * + * \sa \ref igraph_is_graphical_degree_sequence() + * \ref igraph_degree_sequence_game() + * \ref igraph_k_regular_game() + * \ref igraph_rewire() + * + */ + +int igraph_realize_degree_sequence( + igraph_t *graph, + const igraph_vector_t *outdeg, const igraph_vector_t *indeg, + igraph_realize_degseq_t method) { + long n = igraph_vector_size(outdeg); + if (n != igraph_integer_t(n)) { // does the vector size fit into an igraph_integer_t ? + IGRAPH_ERROR("Degree sequence vector too long", IGRAPH_EINVAL); + } + + bool directed = bool(indeg) && igraph_vector_size(indeg) != 0; + + try { + if (directed) { + return igraph_i_realize_directed_degree_sequence(graph, outdeg, indeg, method); + } else { + return igraph_i_realize_undirected_degree_sequence(graph, outdeg, method); + } + } catch (const std::bad_alloc &) { + IGRAPH_ERROR("Cannot realize degree sequence due to insufficient memory", IGRAPH_ENOMEM); + } +} diff --git a/src/distances.c b/src/distances.c new file mode 100644 index 0000000..21c7056 --- /dev/null +++ b/src/distances.c @@ -0,0 +1,211 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_datatype.h" +#include "igraph_dqueue.h" +#include "igraph_iterators.h" +#include "igraph_interrupt_internal.h" +#include "igraph_vector.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" + +static int igraph_i_eccentricity(const igraph_t *graph, + igraph_vector_t *res, + igraph_vs_t vids, + igraph_neimode_t mode, + const igraph_adjlist_t *adjlist) { + + int no_of_nodes = igraph_vcount(graph); + igraph_dqueue_long_t q; + igraph_vit_t vit; + igraph_vector_int_t counted; + int i, mark = 1; + igraph_vector_t vneis; + igraph_vector_int_t *neis; + + IGRAPH_CHECK(igraph_dqueue_long_init(&q, 100)); + IGRAPH_FINALLY(igraph_dqueue_long_destroy, &q); + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + IGRAPH_CHECK(igraph_vector_int_init(&counted, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &counted); + + if (!adjlist) { + IGRAPH_VECTOR_INIT_FINALLY(&vneis, 0); + } + + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_VIT_SIZE(vit))); + igraph_vector_fill(res, -1); + + for (i = 0, IGRAPH_VIT_RESET(vit); + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), mark++, i++) { + + long int source; + source = IGRAPH_VIT_GET(vit); + IGRAPH_CHECK(igraph_dqueue_long_push(&q, source)); + IGRAPH_CHECK(igraph_dqueue_long_push(&q, 0)); + VECTOR(counted)[source] = mark; + + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_long_empty(&q)) { + long int act = igraph_dqueue_long_pop(&q); + long int dist = igraph_dqueue_long_pop(&q); + int j, n; + + if (dist > VECTOR(*res)[i]) { + VECTOR(*res)[i] = dist; + } + + if (adjlist) { + neis = igraph_adjlist_get(adjlist, act); + n = (int) igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + int nei = (int) VECTOR(*neis)[j]; + if (VECTOR(counted)[nei] != mark) { + VECTOR(counted)[nei] = mark; + IGRAPH_CHECK(igraph_dqueue_long_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_long_push(&q, dist + 1)); + } + } + } else { + IGRAPH_CHECK(igraph_neighbors(graph, &vneis, + (igraph_integer_t) act, mode)); + n = (int) igraph_vector_size(&vneis); + for (j = 0; j < n; j++) { + int nei = (int) VECTOR(vneis)[j]; + if (VECTOR(counted)[nei] != mark) { + VECTOR(counted)[nei] = mark; + IGRAPH_CHECK(igraph_dqueue_long_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_long_push(&q, dist + 1)); + } + } + } + } /* while !igraph_dqueue_long_empty(dqueue) */ + + } /* for IGRAPH_VIT_NEXT(vit) */ + + if (!adjlist) { + igraph_vector_destroy(&vneis); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_int_destroy(&counted); + igraph_vit_destroy(&vit); + igraph_dqueue_long_destroy(&q); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_eccentricity + * Eccentricity of some vertices + * + * The eccentricity of a vertex is calculated by measuring the shortest + * distance from (or to) the vertex, to (or from) all vertices in the + * graph, and taking the maximum. + * + * + * This implementation ignores vertex pairs that are in different + * components. Isolated vertices have eccentricity zero. + * + * \param graph The input graph, it can be directed or undirected. + * \param res Pointer to an initialized vector, the result is stored + * here. + * \param vids The vertices for which the eccentricity is calculated. + * \param mode What kind of paths to consider for the calculation: + * \c IGRAPH_OUT, paths that follow edge directions; + * \c IGRAPH_IN, paths that follow the opposite directions; and + * \c IGRAPH_ALL, paths that ignore edge directions. This argument + * is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(v*(|V|+|E|)), where |V| is the number of + * vertices, |E| is the number of edges and v is the number of + * vertices for which eccentricity is calculated. + * + * \sa \ref igraph_radius(). + * + * \example examples/simple/igraph_eccentricity.c + */ + +int igraph_eccentricity(const igraph_t *graph, + igraph_vector_t *res, + igraph_vs_t vids, + igraph_neimode_t mode) { + + return igraph_i_eccentricity(graph, res, vids, mode, /*adjlist=*/ 0); +} + +/** + * \function igraph_radius + * Radius of a graph + * + * The radius of a graph is the defined as the minimum eccentricity of + * its vertices, see \ref igraph_eccentricity(). + * + * \param graph The input graph, it can be directed or undirected. + * \param radius Pointer to a real variable, the result is stored + * here. + * \param mode What kind of paths to consider for the calculation: + * \c IGRAPH_OUT, paths that follow edge directions; + * \c IGRAPH_IN, paths that follow the opposite directions; and + * \c IGRAPH_ALL, paths that ignore edge directions. This argument + * is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V|(|V|+|E|)), where |V| is the number of + * vertices and |E| is the number of edges. + * + * \sa \ref igraph_eccentricity(). + * + * \example examples/simple/igraph_radius.c + */ + +int igraph_radius(const igraph_t *graph, igraph_real_t *radius, + igraph_neimode_t mode) { + + int no_of_nodes = igraph_vcount(graph); + + if (no_of_nodes == 0) { + *radius = IGRAPH_NAN; + } else { + igraph_adjlist_t adjlist; + igraph_vector_t ecc; + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, mode)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + IGRAPH_VECTOR_INIT_FINALLY(&ecc, igraph_vcount(graph)); + IGRAPH_CHECK(igraph_i_eccentricity(graph, &ecc, igraph_vss_all(), + mode, &adjlist)); + *radius = igraph_vector_min(&ecc); + igraph_vector_destroy(&ecc); + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(2); + } + + return 0; +} diff --git a/src/dotproduct.c b/src/dotproduct.c new file mode 100644 index 0000000..647cfbe --- /dev/null +++ b/src/dotproduct.c @@ -0,0 +1,280 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" +#include "igraph_random.h" +#include "igraph_constructors.h" +#include "igraph_lapack.h" + +/** + * \function igraph_dot_product_game + * Generate a random dot product graph + * + * In this model, each vertex is represented by a latent + * position vector. Probability of an edge between two vertices are given + * by the dot product of their latent position vectors. + * + * + * See also Christine Leigh Myers Nickel: Random dot product graphs, a + * model for social networks. Dissertation, Johns Hopkins University, + * Maryland, USA, 2006. + * + * \param graph The output graph is stored here. + * \param vecs A matrix in which each latent position vector is a + * column. The dot product of the latent position vectors should be + * in the [0,1] interval, otherwise a warning is given. For + * negative dot products, no edges are added; dot products that are + * larger than one always add an edge. + * \param directed Should the generated graph be directed? + * \return Error code. + * + * Time complexity: O(n*n*m), where n is the number of vertices, + * and m is the length of the latent vectors. + * + * \sa \ref igraph_sample_dirichlet(), \ref + * igraph_sample_sphere_volume(), \ref igraph_sample_sphere_surface() + * for functions to generate the latent vectors. + */ + +int igraph_dot_product_game(igraph_t *graph, const igraph_matrix_t *vecs, + igraph_bool_t directed) { + + igraph_integer_t nrow = igraph_matrix_nrow(vecs); + igraph_integer_t ncol = igraph_matrix_ncol(vecs); + int i, j; + igraph_vector_t edges; + igraph_bool_t warned_neg = 0, warned_big = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + RNG_BEGIN(); + + for (i = 0; i < ncol; i++) { + int from = directed ? 0 : i + 1; + igraph_vector_t v1; + igraph_vector_view(&v1, &MATRIX(*vecs, 0, i), nrow); + for (j = from; j < ncol; j++) { + igraph_real_t prob; + igraph_vector_t v2; + if (i == j) { + continue; + } + igraph_vector_view(&v2, &MATRIX(*vecs, 0, j), nrow); + igraph_lapack_ddot(&v1, &v2, &prob); + if (prob < 0 && ! warned_neg) { + warned_neg = 1; + IGRAPH_WARNING("Negative connection probability in " + "dot-product graph"); + } else if (prob > 1 && ! warned_big) { + warned_big = 1; + IGRAPH_WARNING("Greater than 1 connection probability in " + "dot-product graph"); + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, j)); + } else if (RNG_UNIF01() < prob) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, j)); + } + } + } + + RNG_END(); + + igraph_create(graph, &edges, ncol, directed); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_sample_sphere_surface + * Sample points uniformly from the surface of a sphere + * + * The center of the sphere is at the origin. + * + * \param dim The dimension of the random vectors. + * \param n The number of vectors to sample. + * \param radius Radius of the sphere, it must be positive. + * \param positive Whether to restrict sampling to the positive + * orthant. + * \param res Pointer to an initialized matrix, the result is + * stored here, each column will be a sampled vector. The matrix is + * resized, as needed. + * \return Error code. + * + * Time complexity: O(n*dim*g), where g is the time complexity of + * generating a standard normal random number. + * + * \sa \ref igraph_sample_sphere_volume(), \ref + * igraph_sample_dirichlet() for other similar samplers. + */ + +int igraph_sample_sphere_surface(igraph_integer_t dim, igraph_integer_t n, + igraph_real_t radius, + igraph_bool_t positive, + igraph_matrix_t *res) { + igraph_integer_t i, j; + + if (dim < 2) { + IGRAPH_ERROR("Sphere must be at least two dimensional to sample from " + "surface", IGRAPH_EINVAL); + } + if (n < 0) { + IGRAPH_ERROR("Number of samples must be non-negative", IGRAPH_EINVAL); + } + if (radius <= 0) { + IGRAPH_ERROR("Sphere radius must be positive", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, dim, n)); + + RNG_BEGIN(); + + for (i = 0; i < n; i++) { + igraph_real_t *col = &MATRIX(*res, 0, i); + igraph_real_t sum = 0.0; + for (j = 0; j < dim; j++) { + col[j] = RNG_NORMAL(0, 1); + sum += col[j] * col[j]; + } + sum = sqrt(sum); + for (j = 0; j < dim; j++) { + col[j] = radius * col[j] / sum; + } + if (positive) { + for (j = 0; j < dim; j++) { + col[j] = fabs(col[j]); + } + } + } + + RNG_END(); + + return 0; +} + +/** + * \function igraph_sample_sphere_volume + * Sample points uniformly from the volume of a sphere + * + * The center of the sphere is at the origin. + * + * \param dim The dimension of the random vectors. + * \param n The number of vectors to sample. + * \param radius Radius of the sphere, it must be positive. + * \param positive Whether to restrict sampling to the positive + * orthant. + * \param res Pointer to an initialized matrix, the result is + * stored here, each column will be a sampled vector. The matrix is + * resized, as needed. + * \return Error code. + * + * Time complexity: O(n*dim*g), where g is the time complexity of + * generating a standard normal random number. + * + * \sa \ref igraph_sample_sphere_surface(), \ref + * igraph_sample_dirichlet() for other similar samplers. + */ + + +int igraph_sample_sphere_volume(igraph_integer_t dim, igraph_integer_t n, + igraph_real_t radius, + igraph_bool_t positive, + igraph_matrix_t *res) { + + igraph_integer_t i, j; + + /* Arguments are checked by the following call */ + + IGRAPH_CHECK(igraph_sample_sphere_surface(dim, n, radius, positive, res)); + + RNG_BEGIN(); + + for (i = 0; i < n; i++) { + igraph_real_t *col = &MATRIX(*res, 0, i); + igraph_real_t U = pow(RNG_UNIF01(), 1.0 / dim); + for (j = 0; j < dim; j++) { + col[j] *= U; + } + } + + RNG_END(); + + return 0; +} + +/** + * \function igraph_sample_dirichlet + * Sample points from a Dirichlet distribution + * + * \param n The number of vectors to sample. + * \param alpha The parameters of the Dirichlet distribution. They + * must be positive. The length of this vector gives the dimension + * of the generated samples. + * \param res Pointer to an initialized matrix, the result is stored + * here, one sample in each column. It will be resized, as needed. + * \return Error code. + * + * Time complexity: O(n * dim * g), where dim is the dimension of the + * sample vectors, set by the length of alpha, and g is the time + * complexity of sampling from a Gamma distribution. + * + * \sa \ref igraph_sample_sphere_surface() and + * \ref igraph_sample_sphere_volume() for other methods to sample + * latent vectors. + */ + +int igraph_sample_dirichlet(igraph_integer_t n, const igraph_vector_t *alpha, + igraph_matrix_t *res) { + + igraph_integer_t len = igraph_vector_size(alpha); + igraph_integer_t i; + igraph_vector_t vec; + + if (n < 0) { + IGRAPH_ERROR("Number of samples should be non-negative", + IGRAPH_EINVAL); + } + if (len < 2) { + IGRAPH_ERROR("Dirichlet parameter vector too short, must " + "have at least two entries", IGRAPH_EINVAL); + } + if (igraph_vector_min(alpha) <= 0) { + IGRAPH_ERROR("Dirichlet concentration parameters must be positive", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, len, n)); + + RNG_BEGIN(); + + for (i = 0; i < n; i++) { + igraph_vector_view(&vec, &MATRIX(*res, 0, i), len); + igraph_rng_get_dirichlet(igraph_rng_default(), alpha, &vec); + } + + RNG_END(); + + return 0; +} diff --git a/src/dqueue.c b/src/dqueue.c new file mode 100644 index 0000000..212b242 --- /dev/null +++ b/src/dqueue.c @@ -0,0 +1,55 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_dqueue.h" + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "dqueue.pmt" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_LONG +#include "igraph_pmt.h" +#include "dqueue.pmt" +#include "igraph_pmt_off.h" +#undef BASE_LONG + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "dqueue.pmt" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "dqueue.pmt" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_INT +#include "igraph_pmt.h" +#include "dqueue.pmt" +#include "igraph_pmt_off.h" +#undef BASE_INT diff --git a/src/dqueue.pmt b/src/dqueue.pmt new file mode 100644 index 0000000..0be9397 --- /dev/null +++ b/src/dqueue.pmt @@ -0,0 +1,384 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_memory.h" +#include "igraph_error.h" +#include "config.h" + +#include +#include /* memcpy & co. */ +#include + +/** + * \section igraph_dqueue + * + * This is the classic data type of the double ended queue. Most of + * the time it is used if a First-In-First-Out (FIFO) behavior is + * needed. See the operations below. + * + * + * + * \example examples/simple/dqueue.c + * + */ + +/** + * \ingroup dqueue + * \function igraph_dqueue_init + * \brief Initialize a double ended queue (deque). + * + * The queue will be always empty. + * \param q Pointer to an uninitialized deque. + * \param size How many elements to allocate memory for. + * \return Error code. + * + * Time complexity: O(\p size). + */ + +int FUNCTION(igraph_dqueue, init) (TYPE(igraph_dqueue)* q, long int size) { + assert(q != 0); + if (size <= 0 ) { + size = 1; + } + q->stor_begin = igraph_Calloc(size, BASE); + if (q->stor_begin == 0) { + IGRAPH_ERROR("dqueue init failed", IGRAPH_ENOMEM); + } + q->stor_end = q->stor_begin + size; + q->begin = q->stor_begin; + q->end = NULL; + + return 0; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_destroy + * \brief Destroy a double ended queue. + * + * \param q The queue to destroy + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_dqueue, destroy) (TYPE(igraph_dqueue)* q) { + assert(q != 0); + if (q->stor_begin != 0) { + igraph_Free(q->stor_begin); + q->stor_begin = 0; + } +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_empty + * \brief Decide whether the queue is empty. + * + * \param q The queue. + * \return Boolean, \c TRUE if \p q contains at least one element, \c + * FALSE otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_dqueue, empty) (const TYPE(igraph_dqueue)* q) { + assert(q != 0); + assert(q->stor_begin != 0); + return q->end == NULL; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_clear + * \brief Remove all elements from the queue. + * + * \param q The queue + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_dqueue, clear) (TYPE(igraph_dqueue)* q) { + assert(q != 0); + assert(q->stor_begin != 0); + q->begin = q->stor_begin; + q->end = NULL; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_full + * \brief Check whether the queue is full. + * + * If a queue is full the next igraph_dqueue_push() operation will allocate + * more memory. + * \param q The queue. + * \return \c TRUE if \p q is full, \c FALSE otherwise. + * + * Time complecity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_dqueue, full) (TYPE(igraph_dqueue)* q) { + assert(q != 0); + assert(q->stor_begin != 0); + return q->begin == q->end; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_size + * \brief Number of elements in the queue. + * + * \param q The queue. + * \return Integer, the number of elements currently in the queue. + * + * Time complexity: O(1). + */ + +long int FUNCTION(igraph_dqueue, size) (const TYPE(igraph_dqueue)* q) { + assert(q != 0); + assert(q->stor_begin != 0); + if (q->end == NULL) { + return 0; + } else if (q->begin < q->end) { + return q->end - q->begin; + } else { + return q->stor_end - q->begin + q->end - q->stor_begin; + } +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_head + * \brief Head of the queue. + * + * The queue must contain at least one element. + * \param q The queue. + * \return The first element in the queue. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_dqueue, head) (const TYPE(igraph_dqueue)* q) { + assert(q != 0); + assert(q->stor_begin != 0); + return *(q->begin); +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_back + * \brief Tail of the queue. + * + * The queue must contain at least one element. + * \param q The queue. + * \return The last element in the queue. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_dqueue, back) (const TYPE(igraph_dqueue)* q) { + assert(q != 0); + assert(q->stor_begin != 0); + if (q->end == q->stor_begin) { + return *(q->stor_end - 1); + } + return *(q->end - 1); +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_pop + * \brief Remove the head. + * + * Removes and returns the first element in the queue. The queue must + * be non-empty. + * \param q The input queue. + * \return The first element in the queue. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_dqueue, pop) (TYPE(igraph_dqueue)* q) { + BASE tmp = *(q->begin); + assert(q != 0); + assert(q->stor_begin != 0); + (q->begin)++; + if (q->begin == q->stor_end) { + q->begin = q->stor_begin; + } + if (q->begin == q->end) { + q->end = NULL; + } + + return tmp; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_pop_back + * \brief Remove the tail + * + * Removes and returns the last element in the queue. The queue must + * be non-empty. + * \param q The queue. + * \return The last element in the queue. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_dqueue, pop_back) (TYPE(igraph_dqueue)* q) { + BASE tmp; + assert(q != 0); + assert(q->stor_begin != 0); + if (q->end != q->stor_begin) { + tmp = *((q->end) - 1); + q->end = (q->end) - 1; + } else { + tmp = *((q->stor_end) - 1); + q->end = (q->stor_end) - 1; + } + if (q->begin == q->end) { + q->end = NULL; + } + + return tmp; +} + +/** + * \ingroup dqueue + * \function igraph_dqueue_push + * \brief Appends an element. + * + * Append an element to the end of the queue. + * \param q The queue. + * \param elem The element to append. + * \return Error code. + * + * Time complexity: O(1) if no memory allocation is needed, O(n), the + * number of elements in the queue otherwise. But not that by + * allocating always twice as much memory as the current size of the + * queue we ensure that n push operations can always be done in at + * most O(n) time. (Assuming memory allocation is at most linear.) + */ + +int FUNCTION(igraph_dqueue, push) (TYPE(igraph_dqueue)* q, BASE elem) { + assert(q != 0); + assert(q->stor_begin != 0); + if (q->begin != q->end) { + /* not full */ + if (q->end == NULL) { + q->end = q->begin; + } + *(q->end) = elem; + (q->end)++; + if (q->end == q->stor_end) { + q->end = q->stor_begin; + } + } else { + /* full, allocate more storage */ + + BASE *bigger = NULL, *old = q->stor_begin; + + bigger = igraph_Calloc( 2 * (q->stor_end - q->stor_begin) + 1, BASE ); + if (bigger == 0) { + IGRAPH_ERROR("dqueue push failed", IGRAPH_ENOMEM); + } + + if (q->stor_end - q->begin) { + memcpy(bigger, q->begin, + (size_t)(q->stor_end - q->begin) * sizeof(BASE)); + } + if (q->end - q->stor_begin > 0) { + memcpy(bigger + (q->stor_end - q->begin), q->stor_begin, + (size_t)(q->end - q->stor_begin) * sizeof(BASE)); + } + + q->end = bigger + (q->stor_end - q->stor_begin); + q->stor_end = bigger + 2 * (q->stor_end - q->stor_begin) + 1; + q->stor_begin = bigger; + q->begin = bigger; + + *(q->end) = elem; + (q->end)++; + if (q->end == q->stor_end) { + q->end = q->stor_begin; + } + + igraph_Free(old); + } + + return 0; +} + +#if defined (OUT_FORMAT) + +#ifndef USING_R +int FUNCTION(igraph_dqueue, print)(const TYPE(igraph_dqueue)* q) { + return FUNCTION(igraph_dqueue, fprint)(q, stdout); +} +#endif + +int FUNCTION(igraph_dqueue, fprint)(const TYPE(igraph_dqueue)* q, FILE *file) { + if (q->end != NULL) { + /* There is one element at least */ + BASE *p = q->begin; + fprintf(file, OUT_FORMAT, *p); + p++; + if (q->end > q->begin) { + /* Q is in one piece */ + while (p != q->end) { + fprintf(file, " " OUT_FORMAT, *p); + p++; + } + } else { + /* Q is in two pieces */ + while (p != q->stor_end) { + fprintf(file, " " OUT_FORMAT, *p); + p++; + } + p = q->stor_begin; + while (p != q->end) { + fprintf(file, " " OUT_FORMAT, *p); + p++; + } + } + } + + fprintf(file, "\n"); + + return 0; +} + +#endif + +BASE FUNCTION(igraph_dqueue, e)(const TYPE(igraph_dqueue) *q, long int idx) { + if ((q->begin + idx < q->end) || + (q->begin >= q->end && q->begin + idx < q->stor_end)) { + return q->begin[idx]; + } else if (q->begin >= q->end && q->stor_begin + idx < q->end) { + idx = idx - (q->stor_end - q->begin); + return q->stor_begin[idx]; + } else { + return 0; /* Error */ + } +} diff --git a/src/drl_Node.h b/src/drl_Node.h new file mode 100644 index 0000000..bc894c2 --- /dev/null +++ b/src/drl_Node.h @@ -0,0 +1,68 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __NODE_H__ +#define __NODE_H__ + +// The node class contains information about a given node for +// use by the density server process. + +// structure coord used to pass position information between +// density server and graph class + +namespace drl { + +class Node { + +public: + + bool fixed; // if true do not change the + // position of this node + int id; + + float x, y; + float sub_x, sub_y; + float energy; + +public: + + Node( int node_id ) { + x = y = 0.0; fixed = false; + id = node_id; + } + ~Node() { } + +}; + +} // namespace drl + +#endif //__NODE_H__ diff --git a/src/drl_Node_3d.h b/src/drl_Node_3d.h new file mode 100644 index 0000000..e373d9a --- /dev/null +++ b/src/drl_Node_3d.h @@ -0,0 +1,68 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __NODE_H__ +#define __NODE_H__ + +// The node class contains information about a given node for +// use by the density server process. + +// structure coord used to pass position information between +// density server and graph class + +namespace drl3d { + +class Node { + +public: + + bool fixed; // if true do not change the + // position of this node + int id; + + float x, y, z; + float sub_x, sub_y, sub_z; + float energy; + +public: + + Node( int node_id ) { + x = y = z = 0.0; fixed = false; + id = node_id; + } + ~Node() { } + +}; + +} // namespace drl3d + +#endif //__NODE_H__ diff --git a/src/drl_graph.cpp b/src/drl_graph.cpp new file mode 100644 index 0000000..bca1c0a --- /dev/null +++ b/src/drl_graph.cpp @@ -0,0 +1,1306 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains the member definitions of the master class + + +#include +#include +#include + +using namespace std; + +#include "drl_graph.h" +#include "igraph_random.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_interrupt_internal.h" +#ifdef MUSE_MPI + #include +#endif + +namespace drl { + +// constructor -- initializes the schedule variables (as in +// graph constructor) + +// graph::graph ( int proc_id, int tot_procs, char *int_file ) +// { + +// // MPI parameters +// myid = proc_id; +// num_procs = tot_procs; + +// // initial annealing parameters +// STAGE = 0; +// iterations = 0; +// temperature = 2000; +// attraction = 10; +// damping_mult = 1.0; +// min_edges = 20; +// first_add = fine_first_add = true; +// fineDensity = false; + +// // Brian's original Vx schedule +// liquid.iterations = 200; +// liquid.temperature = 2000; +// liquid.attraction = 2; +// liquid.damping_mult = 1.0; +// liquid.time_elapsed = 0; + +// expansion.iterations = 200; +// expansion.temperature = 2000; +// expansion.attraction = 10; +// expansion.damping_mult = 1.0; +// expansion.time_elapsed = 0; + +// cooldown.iterations = 200; +// cooldown.temperature = 2000; +// cooldown.attraction = 1; +// cooldown.damping_mult = .1; +// cooldown.time_elapsed = 0; + +// crunch.iterations = 50; +// crunch.temperature = 250; +// crunch.attraction = 1; +// crunch. damping_mult = .25; +// crunch.time_elapsed = 0; + +// simmer.iterations = 100; +// simmer.temperature = 250; +// simmer.attraction = .5; +// simmer.damping_mult = 0.0; +// simmer.time_elapsed = 0; + +// // scan .int file for node info +// scan_int ( int_file ); + +// // populate node positions and ids +// positions.reserve ( num_nodes ); +// map < int, int >::iterator cat_iter; +// for ( cat_iter = id_catalog.begin(); +// cat_iter != id_catalog.end(); +// cat_iter++ ) +// positions.push_back ( Node( cat_iter->first ) ); + +// /* +// // output positions .ids for debugging +// for ( int id = 0; id < num_nodes; id++ ) +// cout << positions[id].id << endl; +// */ + +// // read .int file for graph info +// read_int ( int_file ); + +// // initialize density server +// density_server.Init(); + +// } + +graph::graph(const igraph_t *igraph, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights) { + myid = 0; + num_procs = 1; + + STAGE = 0; + iterations = options->init_iterations; + temperature = options->init_temperature; + attraction = options->init_attraction; + damping_mult = options->init_damping_mult; + min_edges = 20; + first_add = fine_first_add = true; + fineDensity = false; + + // Brian's original Vx schedule + liquid.iterations = options->liquid_iterations; + liquid.temperature = options->liquid_temperature; + liquid.attraction = options->liquid_attraction; + liquid.damping_mult = options->liquid_damping_mult; + liquid.time_elapsed = 0; + + expansion.iterations = options->expansion_iterations; + expansion.temperature = options->expansion_temperature; + expansion.attraction = options->expansion_attraction; + expansion.damping_mult = options->expansion_damping_mult; + expansion.time_elapsed = 0; + + cooldown.iterations = options->cooldown_iterations; + cooldown.temperature = options->cooldown_temperature; + cooldown.attraction = options->cooldown_attraction; + cooldown.damping_mult = options->cooldown_damping_mult; + cooldown.time_elapsed = 0; + + crunch.iterations = options->crunch_iterations; + crunch.temperature = options->crunch_temperature; + crunch.attraction = options->crunch_attraction; + crunch.damping_mult = options->crunch_damping_mult; + crunch.time_elapsed = 0; + + simmer.iterations = options->simmer_iterations; + simmer.temperature = options->simmer_temperature; + simmer.attraction = options->simmer_attraction; + simmer.damping_mult = options->simmer_damping_mult; + simmer.time_elapsed = 0; + + // scan .int file for node info + highest_sim = 1.0; + num_nodes = igraph_vcount(igraph); + long int no_of_edges = igraph_ecount(igraph); + for (long int i = 0; i < num_nodes; i++) { + id_catalog[i] = 1; + } + map< int, int>::iterator cat_iter; + for ( cat_iter = id_catalog.begin(); + cat_iter != id_catalog.end(); cat_iter++) { + cat_iter->second = cat_iter->first; + } + + // populate node positions and ids + positions.reserve ( num_nodes ); + for ( cat_iter = id_catalog.begin(); + cat_iter != id_catalog.end(); + cat_iter++ ) { + positions.push_back ( Node( cat_iter->first ) ); + } + + // read .int file for graph info + long int node_1, node_2; + double weight; + for (long int i = 0; i < no_of_edges; i++) { + node_1 = IGRAPH_FROM(igraph, i); + node_2 = IGRAPH_TO(igraph, i); + weight = weights ? VECTOR(*weights)[i] : 1.0 ; + (neighbors[id_catalog[node_1]])[id_catalog[node_2]] = weight; + (neighbors[id_catalog[node_2]])[id_catalog[node_1]] = weight; + } + + // initialize density server + density_server.Init(); + +} + +// The following subroutine scans the .int file for the following +// information: number nodes, node ids, and highest similarity. The +// corresponding graph globals are populated: num_nodes, id_catalog, +// and highest_sim. + +// void graph::scan_int ( char *filename ) +// { + +// cout << "Proc. " << myid << " scanning .int file ..." << endl; + +// // Open (sim) File +// ifstream fp ( filename ); +// if ( !fp ) +// { +// cout << "Error: could not open " << filename << ". Program terminated." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// // Read file, parse, and add into data structure +// int id1, id2; +// float edge_weight; +// highest_sim = -1.0; +// while ( !fp.eof () ) +// { +// fp >> id1 >> id2 >> edge_weight; + +// // ignore negative weights! +// if ( edge_weight <= 0 ) +// { +// cout << "Error: found negative edge weight in " << filename << ". Program stopped." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// if ( highest_sim < edge_weight ) +// highest_sim = edge_weight; + +// id_catalog[id1] = 1; +// id_catalog[id2] = 1; +// } + +// fp.close(); + +// if ( id_catalog.size() == 0 ) +// { +// cout << "Error: Proc. " << myid << ": " << filename << " is empty. Program terminated." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// // label nodes with sequential integers starting at 0 +// map< int, int>::iterator cat_iter; +// int id_label; +// for ( cat_iter = id_catalog.begin(), id_label = 0; +// cat_iter != id_catalog.end(); cat_iter++, id_label++ ) +// cat_iter->second = id_label; + +// /* +// // output id_catalog for debugging: +// for ( cat_iter = id_catalog.begin(); +// cat_iter != id_catalog.end(); +// cat_iter++ ) +// cout << cat_iter->first << "\t" << cat_iter->second << endl; +// */ + +// num_nodes = id_catalog.size(); +// } + +// read in .parms file, if present + +/* +void graph::read_parms ( char *parms_file ) +{ + + // read from .parms file + ifstream parms_in ( parms_file ); + if ( !parms_in ) + { + cout << "Error: could not open .parms file! Program stopped." << endl; + #ifdef MUSE_MPI + MPI_Abort ( MPI_COMM_WORLD, 1 ); + #else + exit (1); + #endif + } + + cout << "Processor " << myid << " reading .parms file." << endl; + + // read in stage parameters + string parm_label; // this is ignored in the .parms file + + // initial parameters + parms_in >> parm_label >> iterations; + parms_in >> parm_label >> temperature; + parms_in >> parm_label >> attraction; + parms_in >> parm_label >> damping_mult; + + // liquid stage + parms_in >> parm_label >> liquid.iterations; + parms_in >> parm_label >> liquid.temperature; + parms_in >> parm_label >> liquid.attraction; + parms_in >> parm_label >> liquid.damping_mult; + + // expansion stage + parms_in >> parm_label >> expansion.iterations; + parms_in >> parm_label >> expansion.temperature; + parms_in >> parm_label >> expansion.attraction; + parms_in >> parm_label >> expansion.damping_mult; + + // cooldown stage + parms_in >> parm_label >> cooldown.iterations; + parms_in >> parm_label >> cooldown.temperature; + parms_in >> parm_label >> cooldown.attraction; + parms_in >> parm_label >> cooldown.damping_mult; + + // crunch stage + parms_in >> parm_label >> crunch.iterations; + parms_in >> parm_label >> crunch.temperature; + parms_in >> parm_label >> crunch.attraction; + parms_in >> parm_label >> crunch.damping_mult; + + // simmer stage + parms_in >> parm_label >> simmer.iterations; + parms_in >> parm_label >> simmer.temperature; + parms_in >> parm_label >> simmer.attraction; + parms_in >> parm_label >> simmer.damping_mult; + + parms_in.close(); + + // print out parameters for double checking + if ( myid == 0 ) + { + cout << "Processor 0 reports the following inputs:" << endl; + cout << "inital.iterations = " << iterations << endl; + cout << "initial.temperature = " << temperature << endl; + cout << "initial.attraction = " << attraction << endl; + cout << "initial.damping_mult = " << damping_mult << endl; + cout << " ..." << endl; + cout << "liquid.iterations = " << liquid.iterations << endl; + cout << "liquid.temperature = " << liquid.temperature << endl; + cout << "liquid.attraction = " << liquid.attraction << endl; + cout << "liquid.damping_mult = " << liquid.damping_mult << endl; + cout << " ..." << endl; + cout << "simmer.iterations = " << simmer.iterations << endl; + cout << "simmer.temperature = " << simmer.temperature << endl; + cout << "simmer.attraction = " << simmer.attraction << endl; + cout << "simmer.damping_mult = " << simmer.damping_mult << endl; + } + +} +*/ + +// init_parms -- this subroutine initializes the edge_cut variables +// used in the original VxOrd starting with the edge_cut parameter. +// In our version, edge_cut = 0 means no cutting, 1 = maximum cut. +// We also set the random seed here. + +void graph::init_parms ( int rand_seed, float edge_cut, float real_parm ) { + IGRAPH_UNUSED(rand_seed); + + // first we translate edge_cut the former tcl sliding scale + //CUT_END = cut_length_end = 39000.0 * (1.0 - edge_cut) + 1000.0; + CUT_END = cut_length_end = 40000.0 * (1.0 - edge_cut); + + // cut_length_end cannot actually be 0 + if ( cut_length_end <= 1.0 ) { + cut_length_end = 1.0; + } + + float cut_length_start = 4.0 * cut_length_end; + + // now we set the parameters used by ReCompute + cut_off_length = cut_length_start; + cut_rate = ( cut_length_start - cut_length_end ) / 400.0; + + // finally set the number of iterations to leave .real coords fixed + int full_comp_iters; + full_comp_iters = liquid.iterations + expansion.iterations + + cooldown.iterations + crunch.iterations + 3; + + // adjust real parm to iterations (do not enter simmer halfway) + if ( real_parm < 0 ) { + real_iterations = (int)real_parm; + } else if ( real_parm == 1) { + real_iterations = full_comp_iters + simmer.iterations + 100; + } else { + real_iterations = (int)(real_parm * full_comp_iters); + } + + tot_iterations = 0; + if ( real_iterations > 0 ) { + real_fixed = true; + } else { + real_fixed = false; + } + + // calculate total expected iterations (for progress bar display) + tot_expected_iterations = liquid.iterations + + expansion.iterations + cooldown.iterations + + crunch.iterations + simmer.iterations; + + /* + // output edge_cutting parms (for debugging) + cout << "Processor " << myid << ": " + << "cut_length_end = CUT_END = " << cut_length_end + << ", cut_length_start = " << cut_length_start + << ", cut_rate = " << cut_rate << endl; + */ + + // set random seed + // srand ( rand_seed ); // Don't need this in igraph + +} + +void graph::init_parms(const igraph_layout_drl_options_t *options) { + double rand_seed = 0.0; + double real_in = -1.0; + init_parms(rand_seed, options->edge_cut, real_in); +} + +// The following subroutine reads a .real file to obtain initial +// coordinates. If a node is missing coordinates the coordinates +// are computed + +// void graph::read_real ( char *real_file ) +// { +// cout << "Processor " << myid << " reading .real file ..." << endl; + +// // read in .real file and mark as fixed +// ifstream real_in ( real_file ); +// if ( !real_in ) +// { +// cout << "Error: proc. " << myid << " could not open .real file." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// int real_id; +// float real_x, real_y; +// while ( !real_in.eof () ) +// { +// real_id = -1; +// real_in >> real_id >> real_x >> real_y; +// if ( real_id >= 0 ) +// { +// positions[id_catalog[real_id]].x = real_x; +// positions[id_catalog[real_id]].y = real_y; +// positions[id_catalog[real_id]].fixed = true; + +// /* +// // output positions read (for debugging) +// cout << id_catalog[real_id] << " (" << positions[id_catalog[real_id]].x +// << ", " << positions[id_catalog[real_id]].y << ") " +// << positions[id_catalog[real_id]].fixed << endl; +// */ + +// // add node to density grid +// if ( real_iterations > 0 ) +// density_server.Add ( positions[id_catalog[real_id]], fineDensity ); +// } + +// } + +// real_in.close(); +// } + +int graph::read_real ( const igraph_matrix_t *real_mat, + const igraph_vector_bool_t *fixed) { + long int n = igraph_matrix_nrow(real_mat); + for (long int i = 0; i < n; i++) { + positions[id_catalog[i]].x = MATRIX(*real_mat, i, 0); + positions[id_catalog[i]].y = MATRIX(*real_mat, i, 1); + positions[id_catalog[i]].fixed = fixed ? VECTOR(*fixed)[i] : false; + + if ( real_iterations > 0 ) { + density_server.Add ( positions[id_catalog[i]], fineDensity ); + } + } + + return 0; +} + +// The read_part_int subroutine reads the .int +// file produced by convert_sim and gathers the nodes and their +// neighbors in the range start_ind to end_ind. + +// void graph::read_int ( char *file_name ) +// { + +// ifstream int_file; + +// int_file.open ( file_name ); +// if ( !int_file ) +// { +// cout << "Error (worker process " << myid << "): could not open .int file." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// cout << "Processor " << myid << " reading .int file ..." << endl; + +// int node_1, node_2; +// float weight; + +// while ( !int_file.eof() ) +// { +// weight = 0; // all weights should be >= 0 +// int_file >> node_1 >> node_2 >> weight; +// if ( weight ) // otherwise we are at end of file +// // or it is a self-connected node +// { +// // normalization from original vxord +// weight /= highest_sim; +// weight = weight*fabs(weight); + +// // initialize graph +// if ( ( node_1 % num_procs ) == myid ) +// (neighbors[id_catalog[node_1]])[id_catalog[node_2]] = weight; +// if ( ( node_2 % num_procs ) == myid ) +// (neighbors[id_catalog[node_2]])[id_catalog[node_1]] = weight; +// } +// } +// int_file.close(); + +// /* +// // the following code outputs the contents of the neighbors structure +// // (to be used for debugging) + +// map >::iterator i; +// map::iterator j; + +// for ( i = neighbors.begin(); i != neighbors.end(); i++ ) { +// cout << myid << ": " << i->first << " "; +// for (j = (i->second).begin(); j != (i->second).end(); j++ ) +// cout << j->first << " (" << j->second << ") "; +// cout << endl; +// } +// */ + +// } + +/********************************************* + * Function: ReCompute * + * Description: Compute the graph locations * + * Modified from original code by B. Wylie * + ********************************************/ + +int graph::ReCompute( ) { + + // carryover from original VxOrd + int MIN = 1; + + /* + // output parameters (for debugging) + cout << "ReCompute is using the following parameters: "<< endl; + cout << "STAGE: " << STAGE << ", iter: " << iterations << ", temp = " << temperature + << ", attract = " << attraction << ", damping_mult = " << damping_mult + << ", min_edges = " << min_edges << ", cut_off_length = " << cut_off_length + << ", fineDensity = " << fineDensity << endl; + */ + + /* igraph progress report */ + float progress = (tot_iterations * 100.0 / tot_expected_iterations); + + switch (STAGE) { + case 0: + if (iterations == 0) { + IGRAPH_PROGRESS("DrL layout (initialization stage)", progress, 0); + } else { + IGRAPH_PROGRESS("DrL layout (liquid stage)", progress, 0); + } + break; + case 1: + IGRAPH_PROGRESS("DrL layout (expansion stage)", progress, 0); break; + case 2: + IGRAPH_PROGRESS("DrL layout (cooldown and cluster phase)", progress, 0); break; + case 3: + IGRAPH_PROGRESS("DrL layout (crunch phase)", progress, 0); break; + case 5: + IGRAPH_PROGRESS("DrL layout (simmer phase)", progress, 0); break; + case 6: + IGRAPH_PROGRESS("DrL layout (final phase)", 100.0, 0); break; + default: + IGRAPH_PROGRESS("DrL layout (unknown phase)", 0.0, 0); break; + } + + /* Compute Energies for individual nodes */ + update_nodes (); + + // check to see if we need to free fixed nodes + tot_iterations++; + if ( tot_iterations >= real_iterations ) { + real_fixed = false; + } + + + // **************************************** + // AUTOMATIC CONTROL SECTION + // **************************************** + + // STAGE 0: LIQUID + if (STAGE == 0) { + + if ( iterations == 0 ) { + start_time = time( NULL ); +// if ( myid == 0 ) +// cout << "Entering liquid stage ..."; + } + + if (iterations < liquid.iterations) { + temperature = liquid.temperature; + attraction = liquid.attraction; + damping_mult = liquid.damping_mult; + iterations++; +// if ( myid == 0 ) +// cout << "." << flush; + + } else { + + stop_time = time( NULL ); + liquid.time_elapsed = liquid.time_elapsed + (stop_time - start_time); + temperature = expansion.temperature; + attraction = expansion.attraction; + damping_mult = expansion.damping_mult; + iterations = 0; + + // go to next stage + STAGE = 1; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering expansion stage ..."; + } + } + + // STAGE 1: EXPANSION + if (STAGE == 1) { + + if (iterations < expansion.iterations) { + + // Play with vars + if (attraction > 1) { + attraction -= .05; + } + if (min_edges > 12) { + min_edges -= .05; + } + cut_off_length -= cut_rate; + if (damping_mult > .1) { + damping_mult -= .005; + } + iterations++; +// if ( myid == 0 ) cout << "." << flush; + + } else { + + stop_time = time( NULL ); + expansion.time_elapsed = expansion.time_elapsed + (stop_time - start_time); + min_edges = 12; + damping_mult = cooldown.damping_mult; + + STAGE = 2; + attraction = cooldown.attraction; + temperature = cooldown.temperature; + iterations = 0; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering cool-down stage ..."; + } + } + + // STAGE 2: Cool down and cluster + else if (STAGE == 2) { + + if (iterations < cooldown.iterations) { + + // Reduce temperature + if (temperature > 50) { + temperature -= 10; + } + + // Reduce cut length + if (cut_off_length > cut_length_end) { + cut_off_length -= cut_rate * 2; + } + if (min_edges > MIN) { + min_edges -= .2; + } + //min_edges = 99; + iterations++; +// if ( myid == 0 ) +// cout << "." << flush; + + } else { + + stop_time = time( NULL ); + cooldown.time_elapsed = cooldown.time_elapsed + (stop_time - start_time); + cut_off_length = cut_length_end; + temperature = crunch.temperature; + damping_mult = crunch.damping_mult; + min_edges = MIN; + //min_edges = 99; // In other words: no more cutting + + STAGE = 3; + iterations = 0; + attraction = crunch.attraction; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering crunch stage ..."; + } + } + + // STAGE 3: Crunch + else if (STAGE == 3) { + + if (iterations < crunch.iterations) { + iterations++; +// if ( myid == 0 ) cout << "." << flush; + } else { + + stop_time = time( NULL ); + crunch.time_elapsed = crunch.time_elapsed + (stop_time - start_time); + iterations = 0; + temperature = simmer.temperature; + attraction = simmer.attraction; + damping_mult = simmer.damping_mult; + min_edges = 99; + fineDensity = true; + + STAGE = 5; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering simmer stage ..."; + } + } + + // STAGE 5: Simmer + else if ( STAGE == 5 ) { + + if (iterations < simmer.iterations) { + if (temperature > 50) { + temperature -= 2; + } + iterations++; +// if ( myid == 0 ) cout << "." << flush; + } else { + stop_time = time( NULL ); + simmer.time_elapsed = simmer.time_elapsed + (stop_time - start_time); + + STAGE = 6; + +// if ( myid == 0 ) +// cout << "Layout calculation completed in " << +// ( liquid.time_elapsed + expansion.time_elapsed + +// cooldown.time_elapsed + crunch.time_elapsed + +// simmer.time_elapsed ) +// << " seconds (not including I/O)." +// << endl; + } + } + + // STAGE 6: All Done! + else if ( STAGE == 6) { + + /* + // output parameters (for debugging) + cout << "ReCompute is using the following parameters: "<< endl; + cout << "STAGE: " << STAGE << ", iter: " << iterations << ", temp = " << temperature + << ", attract = " << attraction << ", damping_mult = " << damping_mult + << ", min_edges = " << min_edges << ", cut_off_length = " << cut_off_length + << ", fineDensity = " << fineDensity << endl; + */ + + return 0; + } + + // **************************************** + // END AUTOMATIC CONTROL SECTION + // **************************************** + + // Still need more recomputation + return 1; + +} + +// update_nodes -- this function will complete the primary node update +// loop in layout's recompute routine. It follows exactly the same +// sequence to ensure similarity of parallel layout to the standard layout + +void graph::update_nodes ( ) { + + vector node_indices; // node list of nodes currently being updated + float old_positions[2 * MAX_PROCS]; // positions before update + float new_positions[2 * MAX_PROCS]; // positions after update + + bool all_fixed; // check if all nodes are fixed + + // initial node list consists of 0,1,...,num_procs + for ( int i = 0; i < num_procs; i++ ) { + node_indices.push_back( i ); + } + + // next we calculate the number of nodes there would be if the + // num_nodes by num_procs schedule grid were perfectly square + int square_num_nodes = (int)(num_procs + num_procs * floor ((float)(num_nodes - 1) / (float)num_procs )); + + for ( int i = myid; i < square_num_nodes; i += num_procs ) { + + // get old positions + get_positions ( node_indices, old_positions ); + + // default new position is old position + get_positions ( node_indices, new_positions ); + + if ( i < num_nodes ) { + + // advance random sequence according to myid + for ( int j = 0; j < 2 * myid; j++ ) { + RNG_UNIF01(); + } + // rand(); + + // calculate node energy possibilities + if ( !(positions[i].fixed && real_fixed) ) { + update_node_pos ( i, old_positions, new_positions ); + } + + // advance random sequence for next iteration + for ( unsigned int j = 2 * myid; j < 2 * (node_indices.size() - 1); j++ ) { + RNG_UNIF01(); + } + // rand(); + + } else { + // advance random sequence according to use by + // the other processors + for ( unsigned int j = 0; j < 2 * (node_indices.size()); j++ ) { + RNG_UNIF01(); + } + //rand(); + } + + // check if anything was actually updated (e.g. everything was fixed) + all_fixed = true; + for ( unsigned int j = 0; j < node_indices.size (); j++ ) + if ( !(positions [ node_indices[j] ].fixed && real_fixed) ) { + all_fixed = false; + } + + // update positions across processors (if not all fixed) + if ( !all_fixed ) { +#ifdef MUSE_MPI + MPI_Allgather ( &new_positions[2 * myid], 2, MPI_FLOAT, + new_positions, 2, MPI_FLOAT, MPI_COMM_WORLD ); +#endif + + // update positions (old to new) + update_density ( node_indices, old_positions, new_positions ); + } + + /* + if ( myid == 0 ) + { + // output node list (for debugging) + for ( unsigned int j = 0; j < node_indices.size(); j++ ) + cout << node_indices[j] << " "; + cout << endl; + } + */ + + // compute node list for next update + for ( unsigned int j = 0; j < node_indices.size(); j++ ) { + node_indices [j] += num_procs; + } + + while ( !node_indices.empty() && node_indices.back() >= num_nodes ) { + node_indices.pop_back ( ); + } + + } + + // update first_add and fine_first_add + first_add = false; + if ( fineDensity ) { + fine_first_add = false; + } + +} + +// The get_positions function takes the node_indices list +// and returns the corresponding positions in an array. + +void graph::get_positions ( vector &node_indices, + float return_positions[2 * MAX_PROCS] ) { + + // fill positions + for (unsigned int i = 0; i < node_indices.size(); i++) { + return_positions[2 * i] = positions[ node_indices[i] ].x; + return_positions[2 * i + 1] = positions[ node_indices[i] ].y; + } + +} + +// update_node_pos -- this subroutine does the actual work of computing +// the new position of a given node. num_act_proc gives the number +// of active processes at this level for use by the random number +// generators. + +void graph::update_node_pos ( int node_ind, + float old_positions[2 * MAX_PROCS], + float new_positions[2 * MAX_PROCS] ) { + + float energies[2]; // node energies for possible positions + float updated_pos[2][2]; // possible positions + float pos_x, pos_y; + + // old VxOrd parameter + float jump_length = .010 * temperature; + + // subtract old node + density_server.Subtract ( positions[node_ind], first_add, fine_first_add, fineDensity ); + + // compute node energy for old solution + energies[0] = Compute_Node_Energy ( node_ind ); + + // move node to centroid position + Solve_Analytic ( node_ind, pos_x, pos_y ); + positions[node_ind].x = updated_pos[0][0] = pos_x; + positions[node_ind].y = updated_pos[0][1] = pos_y; + + /* + // ouput random numbers (for debugging) + int rand_0, rand_1; + rand_0 = rand(); + rand_1 = rand(); + cout << myid << ": " << rand_0 << ", " << rand_1 << endl; + */ + + // Do random method (RAND_MAX is C++ maximum random number) + updated_pos[1][0] = updated_pos[0][0] + (.5 - RNG_UNIF01()) * jump_length; + updated_pos[1][1] = updated_pos[0][1] + (.5 - RNG_UNIF01()) * jump_length; + + // compute node energy for random position + positions[node_ind].x = updated_pos[1][0]; + positions[node_ind].y = updated_pos[1][1]; + energies[1] = Compute_Node_Energy ( node_ind ); + + /* + // output update possiblities (debugging): + cout << node_ind << ": (" << updated_pos[0][0] << "," << updated_pos[0][1] + << "), " << energies[0] << "; (" << updated_pos[1][0] << "," + << updated_pos[1][1] << "), " << energies[1] << endl; + */ + + // add back old position + positions[node_ind].x = old_positions[2 * myid]; + positions[node_ind].y = old_positions[2 * myid + 1]; + if ( !fineDensity && !first_add ) { + density_server.Add ( positions[node_ind], fineDensity ); + } else if ( !fine_first_add ) { + density_server.Add ( positions[node_ind], fineDensity ); + } + + // choose updated node position with lowest energy + if ( energies[0] < energies[1] ) { + new_positions[2 * myid] = updated_pos[0][0]; + new_positions[2 * myid + 1] = updated_pos[0][1]; + positions[node_ind].energy = energies[0]; + } else { + new_positions[2 * myid] = updated_pos[1][0]; + new_positions[2 * myid + 1] = updated_pos[1][1]; + positions[node_ind].energy = energies[1]; + } + +} + +// update_density takes a sequence of node_indices and their positions and +// updates the positions by subtracting the old positions and adding the +// new positions to the density grid. + +void graph::update_density ( vector &node_indices, + float old_positions[2 * MAX_PROCS], + float new_positions[2 * MAX_PROCS] ) { + + // go through each node and subtract old position from + // density grid before adding new position + for ( unsigned int i = 0; i < node_indices.size(); i++ ) { + positions[node_indices[i]].x = old_positions[2 * i]; + positions[node_indices[i]].y = old_positions[2 * i + 1]; + density_server.Subtract ( positions[node_indices[i]], + first_add, fine_first_add, fineDensity ); + + positions[node_indices[i]].x = new_positions[2 * i]; + positions[node_indices[i]].y = new_positions[2 * i + 1]; + density_server.Add ( positions[node_indices[i]], fineDensity ); + } + +} + +/******************************************** +* Function: Compute_Node_Energy * +* Description: Compute the node energy * +* This code has been modified from the * +* original code by B. Wylie. * +*********************************************/ + +float graph::Compute_Node_Energy( int node_ind ) { + + /* Want to expand 4th power range of attraction */ + float attraction_factor = attraction * attraction * + attraction * attraction * 2e-2; + + map ::iterator EI; + float x_dis, y_dis; + float energy_distance, weight; + float node_energy = 0; + + // Add up all connection energies + for (EI = neighbors[node_ind].begin(); EI != neighbors[node_ind].end(); ++EI) { + + // Get edge weight + weight = EI->second; + + // Compute x,y distance + x_dis = positions[ node_ind ].x - positions[ EI->first ].x; + y_dis = positions[ node_ind ].y - positions[ EI->first ].y; + + // Energy Distance + energy_distance = x_dis * x_dis + y_dis * y_dis; + if (STAGE < 2) { + energy_distance *= energy_distance; + } + + // In the liquid phase we want to discourage long link distances + if (STAGE == 0) { + energy_distance *= energy_distance; + } + + node_energy += weight * attraction_factor * energy_distance; + } + + // output effect of density (debugging) + //cout << "[before: " << node_energy; + + // add density + node_energy += density_server.GetDensity ( positions[ node_ind ].x, positions[ node_ind ].y, + fineDensity ); + + // after calling density server (debugging) + //cout << ", after: " << node_energy << "]" << endl; + + // return computated energy + return node_energy; +} + + +/********************************************* +* Function: Solve_Analytic * +* Description: Compute the node position * +* This is a modified version of the function * +* originally written by B. Wylie * +*********************************************/ + +void graph::Solve_Analytic( int node_ind, float &pos_x, float &pos_y ) { + + map ::iterator EI; + float total_weight = 0; + float x_dis, y_dis, x_cen = 0, y_cen = 0; + float x = 0, y = 0, dis; + float damping, weight; + + // Sum up all connections + for (EI = neighbors[node_ind].begin(); EI != neighbors[node_ind].end(); ++EI) { + weight = EI->second; + total_weight += weight; + x += weight * positions[ EI->first ].x; + y += weight * positions[ EI->first ].y; + } + + // Now set node position + if (total_weight > 0) { + + // Compute centriod + x_cen = x / total_weight; + y_cen = y / total_weight; + damping = 1.0 - damping_mult; + pos_x = damping * positions[ node_ind ].x + (1.0 - damping) * x_cen; + pos_y = damping * positions[ node_ind ].y + (1.0 - damping) * y_cen; + } else { + pos_x = positions[ node_ind ].x; + pos_y = positions[ node_ind ].y; + } + + // No cut edge flag (?) + if (min_edges == 99) { + return; + } + + // Don't cut at end of scale + if ( CUT_END >= 39500 ) { + return; + } + + float num_connections = sqrt((double)neighbors[node_ind].size()); + float maxLength = 0; + + map::iterator maxIndex; + + // Go through nodes edges... cutting if necessary + for (EI = maxIndex = neighbors[node_ind].begin(); + EI != neighbors[node_ind].end(); ++EI) { + + // Check for at least min edges + if (neighbors[node_ind].size() < min_edges) { + continue; + } + + x_dis = x_cen - positions[ EI->first ].x; + y_dis = y_cen - positions[ EI->first ].y; + dis = x_dis * x_dis + y_dis * y_dis; + dis *= num_connections; + + // Store maximum edge + if (dis > maxLength) { + maxLength = dis; + maxIndex = EI; + } + } + + // If max length greater than cut_length then cut + if (maxLength > cut_off_length) { + neighbors[ node_ind ].erase( maxIndex ); + } + +} + + +// write_coord writes out the coordinate file of the final solutions + +// void graph::write_coord( const char *file_name ) +// { + +// ofstream coordOUT( file_name ); +// if ( !coordOUT ) +// { +// cout << "Could not open " << file_name << ". Program terminated." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// cout << "Writing out solution to " << file_name << " ..." << endl; + +// for (unsigned int i = 0; i < positions.size(); i++) { +// coordOUT << positions[i].id << "\t" << positions[i].x << "\t" << positions[i].y < >::iterator i; + map::iterator j; + + for ( i = neighbors.begin(); i != neighbors.end(); i++ ) + for (j = (i->second).begin(); j != (i->second).end(); j++ ) + simOUT << positions[i->first].id << "\t" + << positions[j->first].id << "\t" + << j->second << endl; + + simOUT.close(); + +} +*/ + +// get_tot_energy adds up the energy for each node to give an estimate of the +// quality of the minimization. + +float graph::get_tot_energy ( ) { + + float my_tot_energy, tot_energy; + my_tot_energy = 0; + for ( int i = myid; i < num_nodes; i += num_procs ) { + my_tot_energy += positions[i].energy; + } + + //vector::iterator i; + //for ( i = positions.begin(); i != positions.end(); i++ ) + // tot_energy += i->energy; + +#ifdef MUSE_MPI + MPI_Reduce ( &my_tot_energy, &tot_energy, 1, MPI_FLOAT, MPI_SUM, 0, MPI_COMM_WORLD ); +#else + tot_energy = my_tot_energy; +#endif + + return tot_energy; + +} + + +// The following subroutine draws the graph with possible intermediate +// output (int_out is set to 0 if not proc. 0). int_out is the parameter +// passed by the user, and coord_file is the .coord file. + +// void graph::draw_graph ( int int_out, char *coord_file ) +// { + +// // layout graph (with possible intermediate output) +// int count_iter = 0, count_file = 1; +// char int_coord_file [MAX_FILE_NAME + MAX_INT_LENGTH]; +// while ( ReCompute( ) ) +// if ( (int_out > 0) && (count_iter == int_out) ) +// { +// // output intermediate solution +// sprintf ( int_coord_file, "%s.%d", coord_file, count_file ); +// write_coord ( int_coord_file ); + +// count_iter = 0; +// count_file++; +// } +// else +// count_iter++; + +// } + +int graph::draw_graph(igraph_matrix_t *res) { + int count_iter = 0; + while (ReCompute()) { + IGRAPH_ALLOW_INTERRUPTION(); + count_iter++; + } + long int n = positions.size(); + IGRAPH_CHECK(igraph_matrix_resize(res, n, 2)); + for (long int i = 0; i < n; i++) { + MATRIX(*res, i, 0) = positions[i].x; + MATRIX(*res, i, 1) = positions[i].y; + } + return 0; +} + +} // namespace drl diff --git a/src/drl_graph.h b/src/drl_graph.h new file mode 100644 index 0000000..1a2804d --- /dev/null +++ b/src/drl_graph.h @@ -0,0 +1,132 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// The graph class contains the methods necessary to draw the +// graph. It calls on the density server class to obtain +// position and density information + +#include "DensityGrid.h" +#include "igraph_layout.h" + +#include +#include +#include + +namespace drl { + +// layout schedule information +struct layout_schedule { + int iterations; + float temperature; + float attraction; + float damping_mult; + time_t time_elapsed; +}; + +class graph { + +public: + + // Methods + void init_parms ( int rand_seed, float edge_cut, float real_parm ); + void init_parms ( const igraph_layout_drl_options_t *options ); + void read_parms ( char *parms_file ); + void read_real ( char *real_file ); + int read_real ( const igraph_matrix_t *real_mat, + const igraph_vector_bool_t *fixed); + void scan_int ( char *filename ); + void read_int ( char *file_name ); + void draw_graph ( int int_out, char *coord_file ); + int draw_graph (igraph_matrix_t *res); + void write_coord ( const char *file_name ); + void write_sim ( const char *file_name ); + float get_tot_energy ( ); + + // Con/Decon + graph( int proc_id, int tot_procs, char *int_file ); + ~graph( ) { } + graph( const igraph_t *igraph, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights); + +private: + + // Methods + int ReCompute ( ); + void update_nodes ( ); + float Compute_Node_Energy ( int node_ind ); + void Solve_Analytic ( int node_ind, float &pos_x, float &pos_y ); + void get_positions ( std::vector &node_indices, float return_positions[2 * MAX_PROCS] ); + void update_density ( std::vector &node_indices, + float old_positions[2 * MAX_PROCS], + float new_positions[2 * MAX_PROCS] ); + void update_node_pos ( int node_ind, + float old_positions[2 * MAX_PROCS], + float new_positions[2 * MAX_PROCS] ); + + // MPI information + int myid, num_procs; + + // graph decomposition information + int num_nodes; // number of nodes in graph + float highest_sim; // highest sim for normalization + std::map id_catalog; // id_catalog[file id] = internal id + std::map > neighbors; // neighbors of nodes on this proc. + + // graph layout information + std::vector positions; + DensityGrid density_server; + + // original VxOrd information + int STAGE, iterations; + float temperature, attraction, damping_mult; + float min_edges, CUT_END, cut_length_end, cut_off_length, cut_rate; + bool first_add, fine_first_add, fineDensity; + + // scheduling variables + layout_schedule liquid; + layout_schedule expansion; + layout_schedule cooldown; + layout_schedule crunch; + layout_schedule simmer; + + // timing statistics + time_t start_time, stop_time; + + // online clustering information + int real_iterations; // number of iterations to hold .real input fixed + int tot_iterations; + int tot_expected_iterations; // for progress bar + bool real_fixed; +}; + +} // namespace drl diff --git a/src/drl_graph_3d.cpp b/src/drl_graph_3d.cpp new file mode 100644 index 0000000..9a303ab --- /dev/null +++ b/src/drl_graph_3d.cpp @@ -0,0 +1,873 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains the member definitions of the master class + +#include +#include +#include + +using namespace std; + +#include "drl_graph_3d.h" +#include "igraph_random.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_interrupt_internal.h" +#ifdef MUSE_MPI + #include +#endif + +namespace drl3d { + +graph::graph(const igraph_t *igraph, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights) { + myid = 0; + num_procs = 1; + + STAGE = 0; + iterations = options->init_iterations; + temperature = options->init_temperature; + attraction = options->init_attraction; + damping_mult = options->init_damping_mult; + min_edges = 20; + first_add = fine_first_add = true; + fineDensity = false; + + // Brian's original Vx schedule + liquid.iterations = options->liquid_iterations; + liquid.temperature = options->liquid_temperature; + liquid.attraction = options->liquid_attraction; + liquid.damping_mult = options->liquid_damping_mult; + liquid.time_elapsed = 0; + + expansion.iterations = options->expansion_iterations; + expansion.temperature = options->expansion_temperature; + expansion.attraction = options->expansion_attraction; + expansion.damping_mult = options->expansion_damping_mult; + expansion.time_elapsed = 0; + + cooldown.iterations = options->cooldown_iterations; + cooldown.temperature = options->cooldown_temperature; + cooldown.attraction = options->cooldown_attraction; + cooldown.damping_mult = options->cooldown_damping_mult; + cooldown.time_elapsed = 0; + + crunch.iterations = options->crunch_iterations; + crunch.temperature = options->crunch_temperature; + crunch.attraction = options->crunch_attraction; + crunch.damping_mult = options->crunch_damping_mult; + crunch.time_elapsed = 0; + + simmer.iterations = options->simmer_iterations; + simmer.temperature = options->simmer_temperature; + simmer.attraction = options->simmer_attraction; + simmer.damping_mult = options->simmer_damping_mult; + simmer.time_elapsed = 0; + + // scan .int file for node info + highest_sim = 1.0; + num_nodes = igraph_vcount(igraph); + long int no_of_edges = igraph_ecount(igraph); + for (long int i = 0; i < num_nodes; i++) { + id_catalog[i] = 1; + } + map< int, int>::iterator cat_iter; + for ( cat_iter = id_catalog.begin(); + cat_iter != id_catalog.end(); cat_iter++) { + cat_iter->second = cat_iter->first; + } + + // populate node positions and ids + positions.reserve ( num_nodes ); + for ( cat_iter = id_catalog.begin(); + cat_iter != id_catalog.end(); + cat_iter++ ) { + positions.push_back ( Node( cat_iter->first ) ); + } + + // read .int file for graph info + long int node_1, node_2; + double weight; + for (long int i = 0; i < no_of_edges; i++) { + node_1 = IGRAPH_FROM(igraph, i); + node_2 = IGRAPH_TO(igraph, i); + weight = weights ? VECTOR(*weights)[i] : 1.0 ; + (neighbors[id_catalog[node_1]])[id_catalog[node_2]] = weight; + (neighbors[id_catalog[node_2]])[id_catalog[node_1]] = weight; + } + + // initialize density server + density_server.Init(); + +} + +// init_parms -- this subroutine initializes the edge_cut variables +// used in the original VxOrd starting with the edge_cut parameter. +// In our version, edge_cut = 0 means no cutting, 1 = maximum cut. +// We also set the random seed here. + +void graph::init_parms ( int rand_seed, float edge_cut, float real_parm ) { + + IGRAPH_UNUSED(rand_seed); + // first we translate edge_cut the former tcl sliding scale + //CUT_END = cut_length_end = 39000.0 * (1.0 - edge_cut) + 1000.0; + CUT_END = cut_length_end = 40000.0 * (1.0 - edge_cut); + + // cut_length_end cannot actually be 0 + if ( cut_length_end <= 1.0 ) { + cut_length_end = 1.0; + } + + float cut_length_start = 4.0 * cut_length_end; + + // now we set the parameters used by ReCompute + cut_off_length = cut_length_start; + cut_rate = ( cut_length_start - cut_length_end ) / 400.0; + + // finally set the number of iterations to leave .real coords fixed + int full_comp_iters; + full_comp_iters = liquid.iterations + expansion.iterations + + cooldown.iterations + crunch.iterations + 3; + + // adjust real parm to iterations (do not enter simmer halfway) + if ( real_parm < 0 ) { + real_iterations = (int)real_parm; + } else if ( real_parm == 1) { + real_iterations = full_comp_iters + simmer.iterations + 100; + } else { + real_iterations = (int)(real_parm * full_comp_iters); + } + + tot_iterations = 0; + if ( real_iterations > 0 ) { + real_fixed = true; + } else { + real_fixed = false; + } + + // calculate total expected iterations (for progress bar display) + tot_expected_iterations = liquid.iterations + + expansion.iterations + cooldown.iterations + + crunch.iterations + simmer.iterations; + + /* + // output edge_cutting parms (for debugging) + cout << "Processor " << myid << ": " + << "cut_length_end = CUT_END = " << cut_length_end + << ", cut_length_start = " << cut_length_start + << ", cut_rate = " << cut_rate << endl; + */ + + // set random seed + // srand ( rand_seed ); // Don't need this in igraph + +} + +void graph::init_parms(const igraph_layout_drl_options_t *options) { + double rand_seed = 0.0; + double real_in = -1.0; + init_parms(rand_seed, options->edge_cut, real_in); +} + +int graph::read_real ( const igraph_matrix_t *real_mat, + const igraph_vector_bool_t *fixed) { + long int n = igraph_matrix_nrow(real_mat); + for (long int i = 0; i < n; i++) { + positions[id_catalog[i]].x = MATRIX(*real_mat, i, 0); + positions[id_catalog[i]].y = MATRIX(*real_mat, i, 1); + positions[id_catalog[i]].z = MATRIX(*real_mat, i, 2); + positions[id_catalog[i]].fixed = fixed ? VECTOR(*fixed)[i] : false; + + if ( real_iterations > 0 ) { + density_server.Add ( positions[id_catalog[i]], fineDensity ); + } + } + + return 0; +} + +/********************************************* + * Function: ReCompute * + * Description: Compute the graph locations * + * Modified from original code by B. Wylie * + ********************************************/ + +int graph::ReCompute( ) { + + // carryover from original VxOrd + int MIN = 1; + + /* + // output parameters (for debugging) + cout << "ReCompute is using the following parameters: "<< endl; + cout << "STAGE: " << STAGE << ", iter: " << iterations << ", temp = " << temperature + << ", attract = " << attraction << ", damping_mult = " << damping_mult + << ", min_edges = " << min_edges << ", cut_off_length = " << cut_off_length + << ", fineDensity = " << fineDensity << endl; + */ + + /* igraph progress report */ + float progress = (tot_iterations * 100.0 / tot_expected_iterations); + + switch (STAGE) { + case 0: + if (iterations == 0) { + IGRAPH_PROGRESS("DrL layout (initialization stage)", progress, 0); + } else { + IGRAPH_PROGRESS("DrL layout (liquid stage)", progress, 0); + } + break; + case 1: + IGRAPH_PROGRESS("DrL layout (expansion stage)", progress, 0); break; + case 2: + IGRAPH_PROGRESS("DrL layout (cooldown and cluster phase)", progress, 0); break; + case 3: + IGRAPH_PROGRESS("DrL layout (crunch phase)", progress, 0); break; + case 5: + IGRAPH_PROGRESS("DrL layout (simmer phase)", progress, 0); break; + case 6: + IGRAPH_PROGRESS("DrL layout (final phase)", 100.0, 0); break; + default: + IGRAPH_PROGRESS("DrL layout (unknown phase)", 0.0, 0); break; + } + + /* Compute Energies for individual nodes */ + update_nodes (); + + // check to see if we need to free fixed nodes + tot_iterations++; + if ( tot_iterations >= real_iterations ) { + real_fixed = false; + } + + + // **************************************** + // AUTOMATIC CONTROL SECTION + // **************************************** + + // STAGE 0: LIQUID + if (STAGE == 0) { + + if ( iterations == 0 ) { + start_time = time( NULL ); +// if ( myid == 0 ) +// cout << "Entering liquid stage ..."; + } + + if (iterations < liquid.iterations) { + temperature = liquid.temperature; + attraction = liquid.attraction; + damping_mult = liquid.damping_mult; + iterations++; +// if ( myid == 0 ) +// cout << "." << flush; + + } else { + + stop_time = time( NULL ); + liquid.time_elapsed = liquid.time_elapsed + (stop_time - start_time); + temperature = expansion.temperature; + attraction = expansion.attraction; + damping_mult = expansion.damping_mult; + iterations = 0; + + // go to next stage + STAGE = 1; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering expansion stage ..."; + } + } + + // STAGE 1: EXPANSION + if (STAGE == 1) { + + if (iterations < expansion.iterations) { + + // Play with vars + if (attraction > 1) { + attraction -= .05; + } + if (min_edges > 12) { + min_edges -= .05; + } + cut_off_length -= cut_rate; + if (damping_mult > .1) { + damping_mult -= .005; + } + iterations++; +// if ( myid == 0 ) cout << "." << flush; + + } else { + + stop_time = time( NULL ); + expansion.time_elapsed = expansion.time_elapsed + (stop_time - start_time); + min_edges = 12; + damping_mult = cooldown.damping_mult; + + STAGE = 2; + attraction = cooldown.attraction; + temperature = cooldown.temperature; + iterations = 0; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering cool-down stage ..."; + } + } + + // STAGE 2: Cool down and cluster + else if (STAGE == 2) { + + if (iterations < cooldown.iterations) { + + // Reduce temperature + if (temperature > 50) { + temperature -= 10; + } + + // Reduce cut length + if (cut_off_length > cut_length_end) { + cut_off_length -= cut_rate * 2; + } + if (min_edges > MIN) { + min_edges -= .2; + } + //min_edges = 99; + iterations++; +// if ( myid == 0 ) +// cout << "." << flush; + + } else { + + stop_time = time( NULL ); + cooldown.time_elapsed = cooldown.time_elapsed + (stop_time - start_time); + cut_off_length = cut_length_end; + temperature = crunch.temperature; + damping_mult = crunch.damping_mult; + min_edges = MIN; + //min_edges = 99; // In other words: no more cutting + + STAGE = 3; + iterations = 0; + attraction = crunch.attraction; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering crunch stage ..."; + } + } + + // STAGE 3: Crunch + else if (STAGE == 3) { + + if (iterations < crunch.iterations) { + iterations++; +// if ( myid == 0 ) cout << "." << flush; + } else { + + stop_time = time( NULL ); + crunch.time_elapsed = crunch.time_elapsed + (stop_time - start_time); + iterations = 0; + temperature = simmer.temperature; + attraction = simmer.attraction; + damping_mult = simmer.damping_mult; + min_edges = 99; + fineDensity = true; + + STAGE = 5; + start_time = time( NULL ); + +// if ( myid == 0 ) +// cout << "Entering simmer stage ..."; + } + } + + // STAGE 5: Simmer + else if ( STAGE == 5 ) { + + if (iterations < simmer.iterations) { + if (temperature > 50) { + temperature -= 2; + } + iterations++; +// if ( myid == 0 ) cout << "." << flush; + } else { + stop_time = time( NULL ); + simmer.time_elapsed = simmer.time_elapsed + (stop_time - start_time); + + STAGE = 6; + +// if ( myid == 0 ) +// cout << "Layout calculation completed in " << +// ( liquid.time_elapsed + expansion.time_elapsed + +// cooldown.time_elapsed + crunch.time_elapsed + +// simmer.time_elapsed ) +// << " seconds (not including I/O)." +// << endl; + } + } + + // STAGE 6: All Done! + else if ( STAGE == 6) { + + /* + // output parameters (for debugging) + cout << "ReCompute is using the following parameters: "<< endl; + cout << "STAGE: " << STAGE << ", iter: " << iterations << ", temp = " << temperature + << ", attract = " << attraction << ", damping_mult = " << damping_mult + << ", min_edges = " << min_edges << ", cut_off_length = " << cut_off_length + << ", fineDensity = " << fineDensity << endl; + */ + + return 0; + } + + // **************************************** + // END AUTOMATIC CONTROL SECTION + // **************************************** + + // Still need more recomputation + return 1; + +} + +// update_nodes -- this function will complete the primary node update +// loop in layout's recompute routine. It follows exactly the same +// sequence to ensure similarity of parallel layout to the standard layout + +void graph::update_nodes ( ) { + + vector node_indices; // node list of nodes currently being updated + float old_positions[2 * MAX_PROCS]; // positions before update + float new_positions[2 * MAX_PROCS]; // positions after update + + bool all_fixed; // check if all nodes are fixed + + // initial node list consists of 0,1,...,num_procs + for ( int i = 0; i < num_procs; i++ ) { + node_indices.push_back( i ); + } + + // next we calculate the number of nodes there would be if the + // num_nodes by num_procs schedule grid were perfectly square + int square_num_nodes = (int)(num_procs + num_procs * floor ((float)(num_nodes - 1) / (float)num_procs )); + + for ( int i = myid; i < square_num_nodes; i += num_procs ) { + + // get old positions + get_positions ( node_indices, old_positions ); + + // default new position is old position + get_positions ( node_indices, new_positions ); + + if ( i < num_nodes ) { + + // advance random sequence according to myid + for ( int j = 0; j < 2 * myid; j++ ) { + RNG_UNIF01(); + } + // rand(); + + // calculate node energy possibilities + if ( !(positions[i].fixed && real_fixed) ) { + update_node_pos ( i, old_positions, new_positions ); + } + + // advance random sequence for next iteration + for ( unsigned int j = 2 * myid; j < 2 * (node_indices.size() - 1); j++ ) { + RNG_UNIF01(); + } + // rand(); + + } else { + // advance random sequence according to use by + // the other processors + for ( unsigned int j = 0; j < 2 * (node_indices.size()); j++ ) { + RNG_UNIF01(); + } + //rand(); + } + + // check if anything was actually updated (e.g. everything was fixed) + all_fixed = true; + for ( unsigned int j = 0; j < node_indices.size (); j++ ) + if ( !(positions [ node_indices[j] ].fixed && real_fixed) ) { + all_fixed = false; + } + + // update positions across processors (if not all fixed) + if ( !all_fixed ) { +#ifdef MUSE_MPI + MPI_Allgather ( &new_positions[2 * myid], 2, MPI_FLOAT, + new_positions, 2, MPI_FLOAT, MPI_COMM_WORLD ); +#endif + + // update positions (old to new) + update_density ( node_indices, old_positions, new_positions ); + } + + /* + if ( myid == 0 ) + { + // output node list (for debugging) + for ( unsigned int j = 0; j < node_indices.size(); j++ ) + cout << node_indices[j] << " "; + cout << endl; + } + */ + + // compute node list for next update + for ( unsigned int j = 0; j < node_indices.size(); j++ ) { + node_indices [j] += num_procs; + } + + while ( !node_indices.empty() && node_indices.back() >= num_nodes ) { + node_indices.pop_back ( ); + } + + } + + // update first_add and fine_first_add + first_add = false; + if ( fineDensity ) { + fine_first_add = false; + } + +} + +// The get_positions function takes the node_indices list +// and returns the corresponding positions in an array. + +void graph::get_positions ( vector &node_indices, + float return_positions[3 * MAX_PROCS] ) { + + // fill positions + for (unsigned int i = 0; i < node_indices.size(); i++) { + return_positions[3 * i] = positions[ node_indices[i] ].x; + return_positions[3 * i + 1] = positions[ node_indices[i] ].y; + return_positions[3 * i + 2] = positions[ node_indices[i] ].z; + } + +} + +// update_node_pos -- this subroutine does the actual work of computing +// the new position of a given node. num_act_proc gives the number +// of active processes at this level for use by the random number +// generators. + +void graph::update_node_pos ( int node_ind, + float old_positions[3 * MAX_PROCS], + float new_positions[3 * MAX_PROCS] ) { + + float energies[2]; // node energies for possible positions + float updated_pos[2][3]; // possible positions + float pos_x, pos_y, pos_z; + + // old VxOrd parameter + float jump_length = .010 * temperature; + + // subtract old node + density_server.Subtract ( positions[node_ind], first_add, fine_first_add, fineDensity ); + + // compute node energy for old solution + energies[0] = Compute_Node_Energy ( node_ind ); + + // move node to centroid position + Solve_Analytic ( node_ind, pos_x, pos_y, pos_z ); + positions[node_ind].x = updated_pos[0][0] = pos_x; + positions[node_ind].y = updated_pos[0][1] = pos_y; + positions[node_ind].z = updated_pos[0][2] = pos_z; + + /* + // ouput random numbers (for debugging) + int rand_0, rand_1; + rand_0 = rand(); + rand_1 = rand(); + cout << myid << ": " << rand_0 << ", " << rand_1 << endl; + */ + + // Do random method (RAND_MAX is C++ maximum random number) + updated_pos[1][0] = updated_pos[0][0] + (.5 - RNG_UNIF01()) * jump_length; + updated_pos[1][1] = updated_pos[0][1] + (.5 - RNG_UNIF01()) * jump_length; + updated_pos[1][2] = updated_pos[0][2] + (.5 - RNG_UNIF01()) * jump_length; + + // compute node energy for random position + positions[node_ind].x = updated_pos[1][0]; + positions[node_ind].y = updated_pos[1][1]; + positions[node_ind].z = updated_pos[1][2]; + energies[1] = Compute_Node_Energy ( node_ind ); + + /* + // output update possiblities (debugging): + cout << node_ind << ": (" << updated_pos[0][0] << "," << updated_pos[0][1] + << "), " << energies[0] << "; (" << updated_pos[1][0] << "," + << updated_pos[1][1] << "), " << energies[1] << endl; + */ + + // add back old position + positions[node_ind].x = old_positions[3 * myid]; + positions[node_ind].y = old_positions[3 * myid + 1]; + positions[node_ind].z = old_positions[3 * myid + 2]; + if ( !fineDensity && !first_add ) { + density_server.Add ( positions[node_ind], fineDensity ); + } else if ( !fine_first_add ) { + density_server.Add ( positions[node_ind], fineDensity ); + } + + // choose updated node position with lowest energy + if ( energies[0] < energies[1] ) { + new_positions[3 * myid] = updated_pos[0][0]; + new_positions[3 * myid + 1] = updated_pos[0][1]; + new_positions[3 * myid + 2] = updated_pos[0][2]; + positions[node_ind].energy = energies[0]; + } else { + new_positions[3 * myid] = updated_pos[1][0]; + new_positions[3 * myid + 1] = updated_pos[1][1]; + new_positions[3 * myid + 2] = updated_pos[1][2]; + positions[node_ind].energy = energies[1]; + } + +} + +// update_density takes a sequence of node_indices and their positions and +// updates the positions by subtracting the old positions and adding the +// new positions to the density grid. + +void graph::update_density ( vector &node_indices, + float old_positions[3 * MAX_PROCS], + float new_positions[3 * MAX_PROCS] ) { + + // go through each node and subtract old position from + // density grid before adding new position + for ( unsigned int i = 0; i < node_indices.size(); i++ ) { + positions[node_indices[i]].x = old_positions[3 * i]; + positions[node_indices[i]].y = old_positions[3 * i + 1]; + positions[node_indices[i]].z = old_positions[3 * i + 2]; + density_server.Subtract ( positions[node_indices[i]], + first_add, fine_first_add, fineDensity ); + + positions[node_indices[i]].x = new_positions[3 * i]; + positions[node_indices[i]].y = new_positions[3 * i + 1]; + positions[node_indices[i]].z = new_positions[3 * i + 2]; + density_server.Add ( positions[node_indices[i]], fineDensity ); + } + +} + +/******************************************** +* Function: Compute_Node_Energy * +* Description: Compute the node energy * +* This code has been modified from the * +* original code by B. Wylie. * +*********************************************/ + +float graph::Compute_Node_Energy( int node_ind ) { + + /* Want to expand 4th power range of attraction */ + float attraction_factor = attraction * attraction * + attraction * attraction * 2e-2; + + map ::iterator EI; + float x_dis, y_dis, z_dis; + float energy_distance, weight; + float node_energy = 0; + + // Add up all connection energies + for (EI = neighbors[node_ind].begin(); EI != neighbors[node_ind].end(); ++EI) { + + // Get edge weight + weight = EI->second; + + // Compute x,y distance + x_dis = positions[ node_ind ].x - positions[ EI->first ].x; + y_dis = positions[ node_ind ].y - positions[ EI->first ].y; + z_dis = positions[ node_ind ].z - positions[ EI->first ].z; + + // Energy Distance + energy_distance = x_dis * x_dis + y_dis * y_dis + z_dis * z_dis; + if (STAGE < 2) { + energy_distance *= energy_distance; + } + + // In the liquid phase we want to discourage long link distances + if (STAGE == 0) { + energy_distance *= energy_distance; + } + + node_energy += weight * attraction_factor * energy_distance; + } + + // output effect of density (debugging) + //cout << "[before: " << node_energy; + + // add density + node_energy += density_server.GetDensity ( positions[ node_ind ].x, positions[ node_ind ].y, + positions[ node_ind ].z, fineDensity ); + + // after calling density server (debugging) + //cout << ", after: " << node_energy << "]" << endl; + + // return computated energy + return node_energy; +} + + +/********************************************* +* Function: Solve_Analytic * +* Description: Compute the node position * +* This is a modified version of the function * +* originally written by B. Wylie * +*********************************************/ + +void graph::Solve_Analytic( int node_ind, float &pos_x, float &pos_y, + float &pos_z) { + + map ::iterator EI; + float total_weight = 0; + float x_dis, y_dis, z_dis, x_cen = 0, y_cen = 0, z_cen = 0; + float x = 0, y = 0, z = 0, dis; + float damping, weight; + + // Sum up all connections + for (EI = neighbors[node_ind].begin(); EI != neighbors[node_ind].end(); ++EI) { + weight = EI->second; + total_weight += weight; + x += weight * positions[ EI->first ].x; + y += weight * positions[ EI->first ].y; + z += weight * positions[ EI->first ].z; + } + + // Now set node position + if (total_weight > 0) { + + // Compute centriod + x_cen = x / total_weight; + y_cen = y / total_weight; + z_cen = z / total_weight; + damping = 1.0 - damping_mult; + pos_x = damping * positions[ node_ind ].x + (1.0 - damping) * x_cen; + pos_y = damping * positions[ node_ind ].y + (1.0 - damping) * y_cen; + pos_z = damping * positions[ node_ind ].z + (1.0 - damping) * z_cen; + } + + // No cut edge flag (?) + if (min_edges == 99) { + return; + } + + // Don't cut at end of scale + if ( CUT_END >= 39500 ) { + return; + } + + float num_connections = (float)sqrt((float)neighbors[node_ind].size()); + float maxLength = 0; + + map::iterator maxIndex; + + // Go through nodes edges... cutting if necessary + for (EI = maxIndex = neighbors[node_ind].begin(); + EI != neighbors[node_ind].end(); ++EI) { + + // Check for at least min edges + if (neighbors[node_ind].size() < min_edges) { + continue; + } + + x_dis = x_cen - positions[ EI->first ].x; + y_dis = y_cen - positions[ EI->first ].y; + z_dis = z_cen - positions[ EI->first ].z; + dis = x_dis * x_dis + y_dis * y_dis + z_dis * z_dis; + dis *= num_connections; + + // Store maximum edge + if (dis > maxLength) { + maxLength = dis; + maxIndex = EI; + } + } + + // If max length greater than cut_length then cut + if (maxLength > cut_off_length) { + neighbors[ node_ind ].erase( maxIndex ); + } + +} + + +// get_tot_energy adds up the energy for each node to give an estimate of the +// quality of the minimization. + +float graph::get_tot_energy ( ) { + + float my_tot_energy, tot_energy; + my_tot_energy = 0; + for ( int i = myid; i < num_nodes; i += num_procs ) { + my_tot_energy += positions[i].energy; + } + + //vector::iterator i; + //for ( i = positions.begin(); i != positions.end(); i++ ) + // tot_energy += i->energy; + +#ifdef MUSE_MPI + MPI_Reduce ( &my_tot_energy, &tot_energy, 1, MPI_FLOAT, MPI_SUM, 0, MPI_COMM_WORLD ); +#else + tot_energy = my_tot_energy; +#endif + + return tot_energy; + +} + + +int graph::draw_graph(igraph_matrix_t *res) { + int count_iter = 0; + while (ReCompute()) { + IGRAPH_ALLOW_INTERRUPTION(); + count_iter++; + } + long int n = positions.size(); + IGRAPH_CHECK(igraph_matrix_resize(res, n, 3)); + for (long int i = 0; i < n; i++) { + MATRIX(*res, i, 0) = positions[i].x; + MATRIX(*res, i, 1) = positions[i].y; + MATRIX(*res, i, 2) = positions[i].z; + } + return 0; +} + +} // namespace drl3d diff --git a/src/drl_graph_3d.h b/src/drl_graph_3d.h new file mode 100644 index 0000000..c61612c --- /dev/null +++ b/src/drl_graph_3d.h @@ -0,0 +1,124 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// The graph class contains the methods necessary to draw the +// graph. It calls on the density server class to obtain +// position and density information + +#include "DensityGrid_3d.h" +#include "igraph_layout.h" + +#include +#include +#include + +namespace drl3d { + +// layout schedule information +struct layout_schedule { + int iterations; + float temperature; + float attraction; + float damping_mult; + time_t time_elapsed; +}; + +class graph { + +public: + + // Methods + void init_parms ( int rand_seed, float edge_cut, float real_parm ); + void init_parms ( const igraph_layout_drl_options_t *options ); + int read_real ( const igraph_matrix_t *real_mat, + const igraph_vector_bool_t *fixed); + int draw_graph (igraph_matrix_t *res); + float get_tot_energy ( ); + + // Con/Decon + graph( const igraph_t *igraph, + const igraph_layout_drl_options_t *options, + const igraph_vector_t *weights); + ~graph( ) { } + +private: + + // Methods + int ReCompute ( ); + void update_nodes ( ); + float Compute_Node_Energy ( int node_ind ); + void Solve_Analytic ( int node_ind, float &pos_x, float &pos_y, float &pos_z ); + void get_positions ( std::vector &node_indices, float return_positions[3 * MAX_PROCS] ); + void update_density ( std::vector &node_indices, + float old_positions[3 * MAX_PROCS], + float new_positions[3 * MAX_PROCS] ); + void update_node_pos ( int node_ind, + float old_positions[3 * MAX_PROCS], + float new_positions[3 * MAX_PROCS] ); + + // MPI information + int myid, num_procs; + + // graph decomposition information + int num_nodes; // number of nodes in graph + float highest_sim; // highest sim for normalization + std::map id_catalog; // id_catalog[file id] = internal id + std::map > neighbors; // neighbors of nodes on this proc. + + // graph layout information + std::vector positions; + DensityGrid density_server; + + // original VxOrd information + int STAGE, iterations; + float temperature, attraction, damping_mult; + float min_edges, CUT_END, cut_length_end, cut_off_length, cut_rate; + bool first_add, fine_first_add, fineDensity; + + // scheduling variables + layout_schedule liquid; + layout_schedule expansion; + layout_schedule cooldown; + layout_schedule crunch; + layout_schedule simmer; + + // timing statistics + time_t start_time, stop_time; + + // online clustering information + int real_iterations; // number of iterations to hold .real input fixed + int tot_iterations; + int tot_expected_iterations; // for progress bar + bool real_fixed; +}; + +} // namespace drl3d diff --git a/src/drl_layout.cpp b/src/drl_layout.cpp new file mode 100644 index 0000000..2d0c822 --- /dev/null +++ b/src/drl_layout.cpp @@ -0,0 +1,471 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// Layout +// +// This program implements a parallel force directed graph drawing +// algorithm. The algorithm used is based upon a random decomposition +// of the graph and simulated shared memory of node position and density. +// In this version, the simulated shared memory is spread among all processors +// +// The structure of the inputs and outputs of this code will be displayed +// if the program is called without parameters, or if an erroneous +// parameter is passed to the program. +// +// S. Martin +// 5/6/2005 + +// C++ library routines +#include +#include + +using namespace std; + +// layout routines and constants +#include "drl_layout.h" +#include "drl_parse.h" +#include "drl_graph.h" + +// MPI +#ifdef MUSE_MPI + #include +#endif + +using namespace drl; +#include "igraph_layout.h" +#include "igraph_random.h" +#include "igraph_interface.h" + +namespace drl { + +// int main(int argc, char **argv) { + + +// // initialize MPI +// int myid, num_procs; + +// #ifdef MUSE_MPI +// MPI_Init ( &argc, &argv ); +// MPI_Comm_size ( MPI_COMM_WORLD, &num_procs ); +// MPI_Comm_rank ( MPI_COMM_WORLD, &myid ); +// #else +// myid = 0; +// num_procs = 1; +// #endif + +// // parameters that must be broadcast to all processors +// int rand_seed; +// float edge_cut; + +// char int_file[MAX_FILE_NAME]; +// char coord_file[MAX_FILE_NAME]; +// char real_file[MAX_FILE_NAME]; +// char parms_file[MAX_FILE_NAME]; + +// int int_out = 0; +// int edges_out = 0; +// int parms_in = 0; +// float real_in = -1.0; + +// // user interaction is handled by processor 0 +// if ( myid == 0 ) +// { +// if ( num_procs > MAX_PROCS ) +// { +// cout << "Error: Maximum number of processors is " << MAX_PROCS << "." << endl; +// cout << "Adjust compile time parameter." << endl; +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// // get user input +// parse command_line ( argc, argv ); +// rand_seed = command_line.rand_seed; +// edge_cut = command_line.edge_cut; +// int_out = command_line.int_out; +// edges_out = command_line.edges_out; +// parms_in = command_line.parms_in; +// real_in = command_line.real_in; +// strcpy ( coord_file, command_line.coord_file.c_str() ); +// strcpy ( int_file, command_line.sim_file.c_str() ); +// strcpy ( real_file, command_line.real_file.c_str() ); +// strcpy ( parms_file, command_line.parms_file.c_str() ); + +// } + +// // now we initialize all processors by reading .int file +// #ifdef MUSE_MPI +// MPI_Bcast ( &int_file, MAX_FILE_NAME, MPI_CHAR, 0, MPI_COMM_WORLD ); +// #endif +// graph neighbors ( myid, num_procs, int_file ); + +// // check for user supplied parameters +// #ifdef MUSE_MPI +// MPI_Bcast ( &parms_in, 1, MPI_INT, 0, MPI_COMM_WORLD ); +// #endif +// if ( parms_in ) +// { +// #ifdef MUSE_MPI +// MPI_Bcast ( &parms_file, MAX_FILE_NAME, MPI_CHAR, 0, MPI_COMM_WORLD ); +// #endif +// neighbors.read_parms ( parms_file ); +// } + +// // set random seed, edge cutting, and real iterations parameters +// #ifdef MUSE_MPI +// MPI_Bcast ( &rand_seed, 1, MPI_INT, 0, MPI_COMM_WORLD ); +// MPI_Bcast ( &edge_cut, 1, MPI_FLOAT, 0, MPI_COMM_WORLD ); +// MPI_Bcast ( &real_in, 1, MPI_INT, 0, MPI_COMM_WORLD ); +// #endif +// neighbors.init_parms ( rand_seed, edge_cut, real_in ); + +// // check for .real file with existing coordinates +// if ( real_in >= 0 ) +// { +// #ifdef MUSE_MPI +// MPI_Bcast ( &real_file, MAX_FILE_NAME, MPI_CHAR, 0, MPI_COMM_WORLD ); +// #endif +// neighbors.read_real ( real_file ); +// } + +// neighbors.draw_graph ( int_out, coord_file ); + +// // do we have to write out the edges? +// #ifdef MUSE_MPI +// MPI_Bcast ( &edges_out, 1, MPI_INT, 0, MPI_COMM_WORLD ); +// #endif +// if ( edges_out ) +// { +// #ifdef MUSE_MPI +// MPI_Bcast ( &coord_file, MAX_FILE_NAME, MPI_CHAR, 0, MPI_COMM_WORLD ); +// #endif +// for ( int i = 0; i < num_procs; i++ ) +// { +// if ( myid == i ) +// neighbors.write_sim ( coord_file ); +// #ifdef MUSE_MPI +// MPI_Barrier ( MPI_COMM_WORLD ); +// #endif +// } +// } + +// // finally we output file and quit +// float tot_energy; +// tot_energy = neighbors.get_tot_energy (); +// if ( myid == 0 ) +// { +// neighbors.write_coord ( coord_file ); +// cout << "Total Energy: " << tot_energy << "." << endl +// << "Program terminated successfully." << endl; +// } + +// // MPI finalize +// #ifdef MUSE_MPI +// MPI_Finalize (); +// #endif + +// return 0; +// } + +} // namespace drl + +/** + * \section about_drl + * + * + * DrL is a sophisticated layout generator developed and implemented by + * Shawn Martin et al. As of October 2012 the original DrL homepage is + * unfortunately not available. You can read more about this algorithm + * in the following technical report: Martin, S., Brown, W.M., + * Klavans, R., Boyack, K.W., DrL: Distributed Recursive (Graph) + * Layout. SAND Reports, 2008. 2936: p. 1-10. + * + * + * + * Only a subset of the complete DrL functionality is + * included in igraph, parallel runs and recursive, multi-level + * layouting is not supported. + * + * + * + * The parameters of the layout are stored in an \ref + * igraph_layout_drl_options_t structure, this can be initialized by + * calling the function \ref igraph_layout_drl_options_init(). + * The fields of this structure can then be adjusted by hand if needed. + * The layout is calculated by an \ref igraph_layout_drl() call. + * + */ + +/** + * \function igraph_layout_drl_options_init + * Initialize parameters for the DrL layout generator + * + * This function can be used to initialize the struct holding the + * parameters for the DrL layout generator. There are a number of + * predefined templates available, it is a good idea to start from one + * of these by modifying some parameters. + * \param options The struct to initialize. + * \param templ The template to use. Currently the following templates + * are supplied: \c IGRAPH_LAYOUT_DRL_DEFAULT, \c + * IGRAPH_LAYOUT_DRL_COARSEN, \c IGRAPH_LAYOUT_DRL_COARSEST, + * \c IGRAPH_LAYOUT_DRL_REFINE and \c IGRAPH_LAYOUT_DRL_FINAL. + * \return Error code. + * + * Time complexity: O(1). + */ + +int igraph_layout_drl_options_init(igraph_layout_drl_options_t *options, + igraph_layout_drl_default_t templ) { + + options->edge_cut = 32.0 / 40.0; + + switch (templ) { + case IGRAPH_LAYOUT_DRL_DEFAULT: + options->init_iterations = 0; + options->init_temperature = 2000; + options->init_attraction = 10; + options->init_damping_mult = 1.0; + + options->liquid_iterations = 200; + options->liquid_temperature = 2000; + options->liquid_attraction = 10; + options->liquid_damping_mult = 1.0; + + options->expansion_iterations = 200; + options->expansion_temperature = 2000; + options->expansion_attraction = 2; + options->expansion_damping_mult = 1.0; + + options->cooldown_iterations = 200; + options->cooldown_temperature = 2000; + options->cooldown_attraction = 1; + options->cooldown_damping_mult = .1; + + options->crunch_iterations = 50; + options->crunch_temperature = 250; + options->crunch_attraction = 1; + options->crunch_damping_mult = 0.25; + + options->simmer_iterations = 100; + options->simmer_temperature = 250; + options->simmer_attraction = .5; + options->simmer_damping_mult = 0; + + break; + case IGRAPH_LAYOUT_DRL_COARSEN: + options->init_iterations = 0; + options->init_temperature = 2000; + options->init_attraction = 10; + options->init_damping_mult = 1.0; + + options->liquid_iterations = 200; + options->liquid_temperature = 2000; + options->liquid_attraction = 2; + options->liquid_damping_mult = 1.0; + + options->expansion_iterations = 200; + options->expansion_temperature = 2000; + options->expansion_attraction = 10; + options->expansion_damping_mult = 1.0; + + options->cooldown_iterations = 200; + options->cooldown_temperature = 2000; + options->cooldown_attraction = 1; + options->cooldown_damping_mult = .1; + + options->crunch_iterations = 50; + options->crunch_temperature = 250; + options->crunch_attraction = 1; + options->crunch_damping_mult = 0.25; + + options->simmer_iterations = 100; + options->simmer_temperature = 250; + options->simmer_attraction = .5; + options->simmer_damping_mult = 0; + + break; + case IGRAPH_LAYOUT_DRL_COARSEST: + options->init_iterations = 0; + options->init_temperature = 2000; + options->init_attraction = 10; + options->init_damping_mult = 1.0; + + options->liquid_iterations = 200; + options->liquid_temperature = 2000; + options->liquid_attraction = 2; + options->liquid_damping_mult = 1.0; + + options->expansion_iterations = 200; + options->expansion_temperature = 2000; + options->expansion_attraction = 10; + options->expansion_damping_mult = 1.0; + + options->cooldown_iterations = 200; + options->cooldown_temperature = 2000; + options->cooldown_attraction = 1; + options->cooldown_damping_mult = .1; + + options->crunch_iterations = 200; + options->crunch_temperature = 250; + options->crunch_attraction = 1; + options->crunch_damping_mult = 0.25; + + options->simmer_iterations = 100; + options->simmer_temperature = 250; + options->simmer_attraction = .5; + options->simmer_damping_mult = 0; + + break; + case IGRAPH_LAYOUT_DRL_REFINE: + options->init_iterations = 0; + options->init_temperature = 50; + options->init_attraction = .5; + options->init_damping_mult = 0; + + options->liquid_iterations = 0; + options->liquid_temperature = 2000; + options->liquid_attraction = 2; + options->liquid_damping_mult = 1.0; + + options->expansion_iterations = 50; + options->expansion_temperature = 500; + options->expansion_attraction = .1; + options->expansion_damping_mult = .25; + + options->cooldown_iterations = 50; + options->cooldown_temperature = 200; + options->cooldown_attraction = 1; + options->cooldown_damping_mult = .1; + + options->crunch_iterations = 50; + options->crunch_temperature = 250; + options->crunch_attraction = 1; + options->crunch_damping_mult = 0.25; + + options->simmer_iterations = 0; + options->simmer_temperature = 250; + options->simmer_attraction = .5; + options->simmer_damping_mult = 0; + + break; + case IGRAPH_LAYOUT_DRL_FINAL: + options->init_iterations = 0; + options->init_temperature = 50; + options->init_attraction = .5; + options->init_damping_mult = 0; + + options->liquid_iterations = 0; + options->liquid_temperature = 2000; + options->liquid_attraction = 2; + options->liquid_damping_mult = 1.0; + + options->expansion_iterations = 50; + options->expansion_temperature = 50; + options->expansion_attraction = .1; + options->expansion_damping_mult = .25; + + options->cooldown_iterations = 50; + options->cooldown_temperature = 200; + options->cooldown_attraction = 1; + options->cooldown_damping_mult = .1; + + options->crunch_iterations = 50; + options->crunch_temperature = 250; + options->crunch_attraction = 1; + options->crunch_damping_mult = 0.25; + + options->simmer_iterations = 25; + options->simmer_temperature = 250; + options->simmer_attraction = .5; + options->simmer_damping_mult = 0; + + break; + default: + IGRAPH_ERROR("Unknown DrL template", IGRAPH_EINVAL); + break; + } + + return 0; +} + +/** + * \function igraph_layout_drl + * The DrL layout generator + * + * This function implements the force-directed DrL layout generator. + * Please see more in the following technical report: Martin, S., + * Brown, W.M., Klavans, R., Boyack, K.W., DrL: Distributed Recursive + * (Graph) Layout. SAND Reports, 2008. 2936: p. 1-10. + * \param graph The input graph. + * \param use_seed Logical scalar, if true, then the coordinates + * supplied in the \p res argument are used as starting points. + * \param res Pointer to a matrix, the result layout is stored + * here. It will be resized as needed. + * \param options The parameters to pass to the layout generator. + * \param weights Edge weights, pointer to a vector. If this is a null + * pointer then every edge will have the same weight. + * \param fixed Pointer to a logical vector, or a null pointer. Originally, + * this argument was used in the DrL algorithm to keep the nodes marked + * with this argument as fixed; fixed nodes would then keep their + * positions in the initial stages of the algorithm. However, due to how + * the DrL code imported into igraph is organized, it seems that the + * argument does not do anything and we are not sure whether this is a + * bug or a feature in DrL. We are leaving the argument here in order not + * to break the API, but note that at the present stage it has no effect. + * \return Error code. + * + * Time complexity: ???. + */ + +int igraph_layout_drl(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_layout_drl_options_t *options, + const igraph_vector_t *weights, + const igraph_vector_bool_t *fixed) { + + RNG_BEGIN(); + + drl::graph neighbors(graph, options, weights); + neighbors.init_parms(options); + if (use_seed) { + IGRAPH_CHECK(igraph_matrix_resize(res, igraph_vcount(graph), 2)); + neighbors.read_real(res, fixed); + } + neighbors.draw_graph(res); + + RNG_END(); + + return 0; +} diff --git a/src/drl_layout.h b/src/drl_layout.h new file mode 100644 index 0000000..8d3cd29 --- /dev/null +++ b/src/drl_layout.h @@ -0,0 +1,65 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains compile time parameters which affect the entire +// DrL program. + +#define DRL_VERSION "3.2 5/5/2006" + +// compile time parameters for MPI message passing +#define MAX_PROCS 256 // maximum number of processors +#define MAX_FILE_NAME 250 // max length of filename +#define MAX_INT_LENGTH 4 // max length of integer suffix of intermediate .coord file + +// Compile time adjustable parameters for the Density grid + +#define GRID_SIZE 1000 // size of Density grid +#define VIEW_SIZE 4000.0 // actual physical size of layout plane +// these values use more memory but have +// little effect on performance or layout + +#define RADIUS 10 // radius for density fall-off: +// larger values tends to slow down +// the program and clump the data + +#define HALF_VIEW 2000 // 1/2 of VIEW_SIZE +#define VIEW_TO_GRID .25 // ratio of GRID_SIZE to VIEW_SIZE + +/* +// original values for VxOrd +#define GRID_SIZE 400 // size of VxOrd Density grid +#define VIEW_SIZE 1600.0 // actual physical size of VxOrd plane +#define RADIUS 10 // radius for density fall-off + +#define HALF_VIEW 800 // 1/2 of VIEW_SIZE +#define VIEW_TO_GRID .25 // ratio of GRID_SIZE to VIEW_SIZE +*/ diff --git a/src/drl_layout_3d.cpp b/src/drl_layout_3d.cpp new file mode 100644 index 0000000..796704b --- /dev/null +++ b/src/drl_layout_3d.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// Layout +// +// This program implements a parallel force directed graph drawing +// algorithm. The algorithm used is based upon a random decomposition +// of the graph and simulated shared memory of node position and density. +// In this version, the simulated shared memory is spread among all processors +// +// The structure of the inputs and outputs of this code will be displayed +// if the program is called without parameters, or if an erroneous +// parameter is passed to the program. +// +// S. Martin +// 5/6/2005 + +// C++ library routines +#include +#include + +using namespace std; + +// layout routines and constants +#include "drl_layout_3d.h" +#include "drl_parse.h" +#include "drl_graph_3d.h" + +// MPI +#ifdef MUSE_MPI + #include +#endif + +using namespace drl3d; +#include "igraph_layout.h" +#include "igraph_random.h" +#include "igraph_interface.h" + +/** + * \function igraph_layout_drl_3d + * The DrL layout generator, 3d version. + * + * This function implements the force-directed DrL layout generator. + * Please see more in the technical report: Martin, S., Brown, W.M., + * Klavans, R., Boyack, K.W., DrL: Distributed Recursive (Graph) + * Layout. SAND Reports, 2008. 2936: p. 1-10. + * + * This function uses a modified DrL generator that does + * the layout in three dimensions. + * \param graph The input graph. + * \param use_seed Logical scalar, if true, then the coordinates + * supplied in the \p res argument are used as starting points. + * \param res Pointer to a matrix, the result layout is stored + * here. It will be resized as needed. + * \param options The parameters to pass to the layout generator. + * \param weights Edge weights, pointer to a vector. If this is a null + * pointer then every edge will have the same weight. + * \param fixed Pointer to a logical vector, or a null pointer. This + * can be used to fix the position of some vertices. Vertices for + * which it is true will not be moved, but stay at the coordinates + * given in the \p res matrix. This argument is ignored if it is a + * null pointer or if use_seed is false. + * \return Error code. + * + * Time complexity: ???. + * + * \sa \ref igraph_layout_drl() for the standard 2d version. + */ + +int igraph_layout_drl_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_layout_drl_options_t *options, + const igraph_vector_t *weights, + const igraph_vector_bool_t *fixed) { + + RNG_BEGIN(); + + drl3d::graph neighbors(graph, options, weights); + neighbors.init_parms(options); + if (use_seed) { + IGRAPH_CHECK(igraph_matrix_resize(res, igraph_vcount(graph), 3)); + neighbors.read_real(res, fixed); + } + neighbors.draw_graph(res); + + RNG_END(); + + return 0; +} diff --git a/src/drl_layout_3d.h b/src/drl_layout_3d.h new file mode 100644 index 0000000..d9b0095 --- /dev/null +++ b/src/drl_layout_3d.h @@ -0,0 +1,65 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains compile time parameters which affect the entire +// DrL program. + +#define DRL_VERSION "3.2 5/5/2006" + +// compile time parameters for MPI message passing +#define MAX_PROCS 256 // maximum number of processors +#define MAX_FILE_NAME 250 // max length of filename +#define MAX_INT_LENGTH 4 // max length of integer suffix of intermediate .coord file + +// Compile time adjustable parameters for the Density grid + +#define GRID_SIZE 100 // size of Density grid +#define VIEW_SIZE 250.0 // actual physical size of layout plane +// these values use more memory but have +// little effect on performance or layout + +#define RADIUS 10 // radius for density fall-off: +// larger values tends to slow down +// the program and clump the data + +#define HALF_VIEW 125.0 // 1/2 of VIEW_SIZE +#define VIEW_TO_GRID .4 // ratio of GRID_SIZE to VIEW_SIZE + +/* +// original values for VxOrd +#define GRID_SIZE 400 // size of VxOrd Density grid +#define VIEW_SIZE 1600.0 // actual physical size of VxOrd plane +#define RADIUS 10 // radius for density fall-off + +#define HALF_VIEW 800 // 1/2 of VIEW_SIZE +#define VIEW_TO_GRID .25 // ratio of GRID_SIZE to VIEW_SIZE +*/ diff --git a/src/drl_parse.cpp b/src/drl_parse.cpp new file mode 100644 index 0000000..c0e98cd --- /dev/null +++ b/src/drl_parse.cpp @@ -0,0 +1,197 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// This file contains the methods for the parse.h class + +#include "drl_layout.h" +#include "drl_parse.h" + +namespace drl { + +// void parse::print_syntax( const char *error_string ) +// { +// cout << endl << "Error: " << error_string << endl; +// cout << endl << "Layout" << endl +// << "------" << endl +// << "S. Martin" << endl +// << "Version " << DRL_VERSION << endl << endl +// << "This program provides a parallel adaptation of a force directed" << endl +// << "graph layout algorithm for use with large datasets." << endl << endl +// << "Usage: layout [options] root_file" << endl << endl +// << "root_file -- the root name of the file being processed." << endl << endl +// << "INPUT" << endl +// << "-----" << endl +// << "root_file.int -- the input file containing the graph to draw using layout." << endl +// << " The .int file must have the suffix \".int\" and each line of .int file" << endl +// << " should have the form" << endl +// << "\tnode_id node_id weight" << endl +// << " where node_id's are integers in sequence starting from 0, and" << endl +// << " weight is a float > 0." << endl << endl +// << "OUTPUT" << endl +// << "------" << endl +// << "root_file.icoord -- the resulting output file, containing an ordination" << endl +// << " of the graph. The .icoord file will have the suffix \".icoord\" and" << endl +// << " each line of the .icoord file will be of the form" << endl +// << "\tnode_id x-coord y-coord" << endl << endl +// << "Options:" << endl << endl +// << "\t-s {int>=0} random seed (default value is 0)" << endl +// << "\t-c {real[0,1]} edge cutting (default 32/40 = .8)" << endl +// << "\t (old max was 39/40 = .975)" << endl +// << "\t-p input parameters from .parms file" << endl +// << "\t-r {real[0,1]} input coordinates from .real file" << endl +// << "\t (hold fixed until fraction of optimization schedule reached)" << endl +// << "\t-i {int>=0} intermediate output interval (default 0: no output)" << endl +// << "\t-e output .iedges file (same prefix as .coord file)" << endl << endl; + +// #ifdef MUSE_MPI +// MPI_Abort ( MPI_COMM_WORLD, 1 ); +// #else +// exit (1); +// #endif +// } + +// parse::parse ( int argc, char** argv) +// { +// map m; + +// // make sure there is at least one argument +// if ( argc < 2) +// print_syntax ( "not enough arguments!" ); + +// // make sure coord_file ends in ".coord" +// parms_file = real_file = sim_file = coord_file = argv[argc-1]; +// parms_file = parms_file + ".parms"; +// real_file = real_file + ".real"; +// sim_file = sim_file + ".int"; +// coord_file = coord_file + ".icoord"; + +// char error_string[200]; +// sprintf ( error_string, "%s %d %s", "root file name cannot be longer than", MAX_FILE_NAME-7, +// "characters."); +// if ( coord_file.length() > MAX_FILE_NAME ) +// print_syntax ( error_string ); + +// // echo sim_file and coord_file +// cout << "Using " << sim_file << " for .int file, and " << coord_file << " for .icoord file." << endl; + +// // set defaults +// rand_seed = 0; +// //edge_cut = 32.0/39.0; // (old default) +// edge_cut = 32.0/40.0; +// int_out = 0; +// edges_out = 0; +// parms_in = 0; +// real_in = -1.0; + +// // now check for optional arguments +// string arg; +// for( int i = 1; i= (argc-1) ) +// print_syntax ( "-s flag has no argument." ); +// else +// { +// rand_seed = atoi ( argv[i] ); +// if ( rand_seed < 0 ) +// print_syntax ( "random seed must be >= 0." ); +// } +// } +// // check for edge cutting +// else if ( arg == "-c" ) +// { +// i++; +// if ( i >= (argc-1) ) +// print_syntax ( "-c flag has no argument." ); +// else +// { +// edge_cut = atof ( argv[i] ); +// if ( (edge_cut < 0) || (edge_cut > 1) ) +// print_syntax ( "edge cut must be between 0 and 1." ); +// } +// } +// // check for intermediate output +// else if ( arg == "-i" ) +// { +// i++; +// if ( i >= (argc-1) ) +// print_syntax ( "-i flag has no argument." ); +// else +// { +// int_out = atoi ( argv[i] ); +// if ( int_out < 0 ) +// print_syntax ( "intermediate output must be >= 0." ); +// } +// } +// // check for .real input +// else if ( arg == "-r" ) +// { +// i++; +// if ( i >= (argc-1) ) +// print_syntax ( "-r flag has no argument." ); +// else +// { +// real_in = atof ( argv[i] ); +// if ( (real_in < 0) || (real_in > 1) ) +// print_syntax ( "real iteration fraction must be from 0 to 1." ); +// } +// } +// else if ( arg == "-e" ) +// edges_out = 1; +// else if ( arg == "-p" ) +// parms_in = 1; +// else +// print_syntax ( "unrecongized option!" ); +// } + +// if ( parms_in ) +// cout << "Using " << parms_file << " for .parms file." << endl; + +// if ( real_in >= 0 ) +// cout << "Using " << real_file << " for .real file." << endl; + +// // echo arguments input or default +// cout << "Using random seed = " << rand_seed << endl +// << " edge_cutting = " << edge_cut << endl +// << " intermediate output = " << int_out << endl +// << " output .iedges file = " << edges_out << endl; +// if ( real_in >= 0 ) +// cout << " holding .real fixed until iterations = " << real_in << endl; + +// } + +} // namespace drl diff --git a/src/drl_parse.h b/src/drl_parse.h new file mode 100644 index 0000000..a772478 --- /dev/null +++ b/src/drl_parse.h @@ -0,0 +1,72 @@ +/* + * Copyright 2007 Sandia Corporation. Under the terms of Contract + * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + * certain rights in this software. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Sandia National Laboratories nor the names of + * its contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +// The parse class contains the methods necessary to parse +// the command line, print help, and do error checking + +#ifdef MUSE_MPI + #include +#endif + +#include + +namespace drl { + +class parse { + +public: + + // Methods + + parse ( int argc, char **argv ); + ~parse () {} + + // user parameters + std::string sim_file; // .sim file + std::string coord_file; // .coord file + std::string parms_file; // .parms file + std::string real_file; // .real file + + int rand_seed; // random seed int >= 0 + float edge_cut; // edge cutting real [0,1] + int int_out; // intermediate output, int >= 1 + int edges_out; // true if .edges file is requested + int parms_in; // true if .parms file is to be read + float real_in; // true if .real file is to be read + +private: + + void print_syntax ( const char *error_string ); + +}; + +} // namespace drl diff --git a/src/eigen.c b/src/eigen.c new file mode 100644 index 0000000..d561d28 --- /dev/null +++ b/src/eigen.c @@ -0,0 +1,1520 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_eigen.h" +#include "igraph_qsort.h" +#include "igraph_blas.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include +#include +#include + +static int igraph_i_eigen_arpackfun_to_mat(igraph_arpack_function_t *fun, + int n, void *extra, + igraph_matrix_t *res) { + + int i; + igraph_vector_t v; + + IGRAPH_CHECK(igraph_matrix_init(res, n, n)); + IGRAPH_FINALLY(igraph_matrix_destroy, res); + IGRAPH_VECTOR_INIT_FINALLY(&v, n); + VECTOR(v)[0] = 1; + IGRAPH_CHECK(fun(/*to=*/ &MATRIX(*res, 0, 0), /*from=*/ VECTOR(v), n, + extra)); + for (i = 1; i < n; i++) { + VECTOR(v)[i - 1] = 0; + VECTOR(v)[i ] = 1; + IGRAPH_CHECK(fun(/*to=*/ &MATRIX(*res, 0, i), /*from=*/ VECTOR(v), n, + extra)); + } + igraph_vector_destroy(&v); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +static int igraph_i_eigen_matrix_symmetric_lapack_lm(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + igraph_matrix_t vec1, vec2; + igraph_vector_t val1, val2; + int n = (int) igraph_matrix_nrow(A); + int p1 = 0, p2 = which->howmany - 1, pr = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&val1, 0); + IGRAPH_VECTOR_INIT_FINALLY(&val2, 0); + + if (vectors) { + IGRAPH_CHECK(igraph_matrix_init(&vec1, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &vec1); + IGRAPH_CHECK(igraph_matrix_init(&vec2, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &vec1); + } + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ 1, /*iu=*/ which->howmany, + /*abstol=*/ 1e-14, &val1, + vectors ? &vec1 : 0, + /*support=*/ 0)); + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ n - which->howmany + 1, /*iu=*/ n, + /*abstol=*/ 1e-14, &val2, + vectors ? &vec2 : 0, + /*support=*/ 0)); + + if (values) { + IGRAPH_CHECK(igraph_vector_resize(values, which->howmany)); + } + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, which->howmany)); + } + + while (pr < which->howmany) { + if (p2 < 0 || fabs(VECTOR(val1)[p1]) > fabs(VECTOR(val2)[p2])) { + if (values) { + VECTOR(*values)[pr] = VECTOR(val1)[p1]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec1, 0, p1), + sizeof(igraph_real_t) * (size_t) n); + } + p1++; + pr++; + } else { + if (values) { + VECTOR(*values)[pr] = VECTOR(val2)[p2]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec2, 0, p2), + sizeof(igraph_real_t) * (size_t) n); + } + p2--; + pr++; + } + } + + + if (vectors) { + igraph_matrix_destroy(&vec2); + igraph_matrix_destroy(&vec1); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_vector_destroy(&val2); + igraph_vector_destroy(&val1); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +static int igraph_i_eigen_matrix_symmetric_lapack_sm(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + igraph_vector_t val; + igraph_matrix_t vec; + int i, w = 0, n = (int) igraph_matrix_nrow(A); + igraph_real_t small; + int p1, p2, pr = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&val, 0); + + if (vectors) { + IGRAPH_MATRIX_INIT_FINALLY(&vec, 0, 0); + } + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_ALL, /*vl=*/ 0, + /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ 0, /*iu=*/ 0, + /*abstol=*/ 1e-14, &val, + vectors ? &vec : 0, + /*support=*/ 0)); + + /* Look for smallest value */ + small = fabs(VECTOR(val)[0]); + for (i = 1; i < n; i++) { + igraph_real_t v = fabs(VECTOR(val)[i]); + if (v < small) { + small = v; + w = i; + } + } + p1 = w - 1; p2 = w; + + if (values) { + IGRAPH_CHECK(igraph_vector_resize(values, which->howmany)); + } + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, which->howmany)); + } + + while (pr < which->howmany) { + if (p2 == n - 1 || fabs(VECTOR(val)[p1]) < fabs(VECTOR(val)[p2])) { + if (values) { + VECTOR(*values)[pr] = VECTOR(val)[p1]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec, 0, p1), + sizeof(igraph_real_t) * (size_t) n); + } + p1--; + pr++; + } else { + if (values) { + VECTOR(*values)[pr] = VECTOR(val)[p2]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec, 0, p2), + sizeof(igraph_real_t) * (size_t) n); + } + p2++; + pr++; + } + } + + if (vectors) { + igraph_matrix_destroy(&vec); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&val); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_eigen_matrix_symmetric_lapack_la(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + /* TODO: ordering? */ + + int n = (int) igraph_matrix_nrow(A); + int il = n - which->howmany + 1; + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ il, /*iu=*/ n, + /*abstol=*/ 1e-14, values, vectors, + /*support=*/ 0)); + return 0; +} + +static int igraph_i_eigen_matrix_symmetric_lapack_sa(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + /* TODO: ordering? */ + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ 1, /*iu=*/ which->howmany, + /*abstol=*/ 1e-14, values, vectors, + /*support=*/ 0)); + + return 0; +} + +static int igraph_i_eigen_matrix_symmetric_lapack_be(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + /* TODO: ordering? */ + + igraph_matrix_t vec1, vec2; + igraph_vector_t val1, val2; + int n = (int) igraph_matrix_nrow(A); + int p1 = 0, p2 = which->howmany / 2, pr = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&val1, 0); + IGRAPH_VECTOR_INIT_FINALLY(&val2, 0); + + if (vectors) { + IGRAPH_CHECK(igraph_matrix_init(&vec1, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &vec1); + IGRAPH_CHECK(igraph_matrix_init(&vec2, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &vec1); + } + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ 1, /*iu=*/ (which->howmany) / 2, + /*abstol=*/ 1e-14, &val1, + vectors ? &vec1 : 0, + /*support=*/ 0)); + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ n - (which->howmany) / 2, /*iu=*/ n, + /*abstol=*/ 1e-14, &val2, + vectors ? &vec2 : 0, + /*support=*/ 0)); + + if (values) { + IGRAPH_CHECK(igraph_vector_resize(values, which->howmany)); + } + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, which->howmany)); + } + + while (pr < which->howmany) { + if (pr % 2) { + if (values) { + VECTOR(*values)[pr] = VECTOR(val1)[p1]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec1, 0, p1), + sizeof(igraph_real_t) * (size_t) n); + } + p1++; + pr++; + } else { + if (values) { + VECTOR(*values)[pr] = VECTOR(val2)[p2]; + } + if (vectors) { + memcpy(&MATRIX(*vectors, 0, pr), &MATRIX(vec2, 0, p2), + sizeof(igraph_real_t) * (size_t) n); + } + p2--; + pr++; + } + } + + if (vectors) { + igraph_matrix_destroy(&vec2); + igraph_matrix_destroy(&vec1); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_vector_destroy(&val2); + igraph_vector_destroy(&val1); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +static int igraph_i_eigen_matrix_symmetric_lapack_all(const igraph_matrix_t *A, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_ALL, /*vl=*/ 0, + /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ 0, /*iu=*/ 0, + /*abstol=*/ 1e-14, values, vectors, + /*support=*/ 0)); + + return 0; +} + +static int igraph_i_eigen_matrix_symmetric_lapack_iv(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_INTERVAL, + /*vl=*/ which->vl, /*vu=*/ which->vu, + /*vestimate=*/ which->vestimate, + /*il=*/ 0, /*iu=*/ 0, + /*abstol=*/ 1e-14, values, vectors, + /*support=*/ 0)); + + return 0; +} + +static int igraph_i_eigen_matrix_symmetric_lapack_sel(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + IGRAPH_CHECK(igraph_lapack_dsyevr(A, IGRAPH_LAPACK_DSYEV_SELECT, + /*vl=*/ 0, /*vu=*/ 0, /*vestimate=*/ 0, + /*il=*/ which->il, /*iu=*/ which->iu, + /*abstol=*/ 1e-14, values, vectors, + /*support=*/ 0)); + + return 0; +} + +static int igraph_i_eigen_matrix_symmetric_lapack(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, + int n, void *extra, + const igraph_eigen_which_t *which, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + const igraph_matrix_t *myA = A; + igraph_matrix_t mA; + + /* First we need to create a dense square matrix */ + + if (A) { + n = (int) igraph_matrix_nrow(A); + } else if (sA) { + n = (int) igraph_sparsemat_nrow(sA); + IGRAPH_CHECK(igraph_matrix_init(&mA, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &mA); + IGRAPH_CHECK(igraph_sparsemat_as_matrix(&mA, sA)); + myA = &mA; + } else if (fun) { + IGRAPH_CHECK(igraph_i_eigen_arpackfun_to_mat(fun, n, extra, &mA)); + IGRAPH_FINALLY(igraph_matrix_destroy, &mA); + myA = &mA; + } + + switch (which->pos) { + case IGRAPH_EIGEN_LM: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_lm(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_SM: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_sm(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_LA: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_la(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_SA: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_sa(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_BE: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_be(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_ALL: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_all(myA, + values, + vectors)); + break; + case IGRAPH_EIGEN_INTERVAL: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_iv(myA, which, + values, + vectors)); + break; + case IGRAPH_EIGEN_SELECT: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack_sel(myA, which, + values, + vectors)); + break; + default: + /* This cannot happen */ + break; + } + + if (!A) { + igraph_matrix_destroy(&mA); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +typedef struct igraph_i_eigen_matrix_sym_arpack_data_t { + const igraph_matrix_t *A; + const igraph_sparsemat_t *sA; +} igraph_i_eigen_matrix_sym_arpack_data_t; + +static int igraph_i_eigen_matrix_sym_arpack_cb(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + + igraph_i_eigen_matrix_sym_arpack_data_t *data = + (igraph_i_eigen_matrix_sym_arpack_data_t *) extra; + + if (data->A) { + igraph_blas_dgemv_array(/*transpose=*/ 0, /*alpha=*/ 1.0, + data->A, from, /*beta=*/ 0.0, to); + } else { /* data->sA */ + igraph_vector_t vto, vfrom; + igraph_vector_view(&vto, to, n); + igraph_vector_view(&vfrom, to, n); + igraph_vector_null(&vto); + igraph_sparsemat_gaxpy(data->sA, &vfrom, &vto); + } + return 0; +} + +static int igraph_i_eigen_matrix_symmetric_arpack_be(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, + int n, void *extra, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + igraph_vector_t tmpvalues, tmpvalues2; + igraph_matrix_t tmpvectors, tmpvectors2; + igraph_i_eigen_matrix_sym_arpack_data_t myextra = { A, sA }; + int low = (int) floor(which->howmany / 2.0), high = (int) ceil(which->howmany / 2.0); + int l1, l2, w; + + if (low + high >= n) { + IGRAPH_ERROR("Requested too many eigenvalues/vectors", IGRAPH_EINVAL); + } + + if (!fun) { + fun = igraph_i_eigen_matrix_sym_arpack_cb; + extra = (void*) &myextra; + } + + IGRAPH_VECTOR_INIT_FINALLY(&tmpvalues, high); + IGRAPH_MATRIX_INIT_FINALLY(&tmpvectors, n, high); + IGRAPH_VECTOR_INIT_FINALLY(&tmpvalues2, low); + IGRAPH_MATRIX_INIT_FINALLY(&tmpvectors2, n, low); + + options->n = n; + options->nev = high; + options->ncv = 2 * options->nev < n ? 2 * options->nev : n; + options->which[0] = 'L'; options->which[1] = 'A'; + + IGRAPH_CHECK(igraph_arpack_rssolve(fun, extra, options, storage, + &tmpvalues, &tmpvectors)); + + options->nev = low; + options->ncv = 2 * options->nev < n ? 2 * options->nev : n; + options->which[0] = 'S'; options->which[1] = 'A'; + + IGRAPH_CHECK(igraph_arpack_rssolve(fun, extra, options, storage, + &tmpvalues2, &tmpvectors2)); + + IGRAPH_CHECK(igraph_vector_resize(values, low + high)); + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, low + high)); + + l1 = 0; l2 = 0; w = 0; + while (w < which->howmany) { + VECTOR(*values)[w] = VECTOR(tmpvalues)[l1]; + memcpy(&MATRIX(*vectors, 0, w), &MATRIX(tmpvectors, 0, l1), + (size_t) n * sizeof(igraph_real_t)); + w++; l1++; + if (w < which->howmany) { + VECTOR(*values)[w] = VECTOR(tmpvalues2)[l2]; + memcpy(&MATRIX(*vectors, 0, w), &MATRIX(tmpvectors2, 0, l2), + (size_t) n * sizeof(igraph_real_t)); + w++; l2++; + } + } + + igraph_matrix_destroy(&tmpvectors2); + igraph_vector_destroy(&tmpvalues2); + igraph_matrix_destroy(&tmpvectors); + igraph_vector_destroy(&tmpvalues); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +static int igraph_i_eigen_matrix_symmetric_arpack(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, + int n, void *extra, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + /* For ARPACK we need a matrix multiplication operation. + This can be done in any format, so everything is fine, + we don't have to convert. */ + + igraph_i_eigen_matrix_sym_arpack_data_t myextra = { A, sA }; + + if (!options) { + IGRAPH_ERROR("`options' must be given for ARPACK algorithm", + IGRAPH_EINVAL); + } + + if (which->pos == IGRAPH_EIGEN_BE) { + return igraph_i_eigen_matrix_symmetric_arpack_be(A, sA, fun, n, extra, + which, options, storage, + values, vectors); + } else { + + switch (which->pos) { + case IGRAPH_EIGEN_LM: + options->which[0] = 'L'; options->which[1] = 'M'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_SM: + options->which[0] = 'S'; options->which[1] = 'M'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_LA: + options->which[0] = 'L'; options->which[1] = 'A'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_SA: + options->which[0] = 'S'; options->which[1] = 'A'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_ALL: + options->which[0] = 'L'; options->which[1] = 'M'; + options->nev = n; + break; + case IGRAPH_EIGEN_INTERVAL: + IGRAPH_ERROR("Interval of eigenvectors with ARPACK", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_SELECT: + IGRAPH_ERROR("Selected eigenvalues with ARPACK", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + default: + /* This cannot happen */ + break; + } + + options->n = n; + options->ncv = 2 * options->nev < n ? 2 * options->nev : n; + + if (!fun) { + fun = igraph_i_eigen_matrix_sym_arpack_cb; + extra = (void*) &myextra; + } + + IGRAPH_CHECK(igraph_arpack_rssolve(fun, extra, options, storage, + values, vectors)); + return 0; + } +} + +/* Get the eigenvalues and the eigenvectors from the compressed + form. Order them according to the ordering criteria. + Comparison functions for the reordering first */ + +typedef int (*igraph_i_eigen_matrix_lapack_cmp_t)(void*, const void*, + const void *); + +typedef struct igraph_i_eml_cmp_t { + const igraph_vector_t *mag, *real, *imag; +} igraph_i_eml_cmp_t; + +/* TODO: these should be defined in some header */ + +#define EPS (DBL_EPSILON*100) +#define LESS(a,b) ((a) < (b)-EPS) +#define MORE(a,b) ((a) > (b)+EPS) +#define ZERO(a) ((a) > -EPS && (a) < EPS) +#define NONZERO(a) ((a) < -EPS || (a) > EPS) + +/* Largest magnitude. Ordering is according to + 1 Larger magnitude + 2 Real eigenvalues before complex ones + 3 Larger real part + 4 Larger imaginary part */ + +static int igraph_i_eigen_matrix_lapack_cmp_lm(void *extra, const void *a, + const void *b) { + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + int *aa = (int*) a, *bb = (int*) b; + igraph_real_t a_m = VECTOR(*myextra->mag)[*aa]; + igraph_real_t b_m = VECTOR(*myextra->mag)[*bb]; + + if (LESS(a_m, b_m)) { + return 1; + } else if (MORE(a_m, b_m)) { + return -1; + } else { + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + if (ZERO(a_i) && NONZERO(b_i)) { + return -1; + } + if (NONZERO(a_i) && ZERO(b_i)) { + return 1; + } + if (MORE(a_r, b_r)) { + return -1; + } + if (LESS(a_r, b_r)) { + return 1; + } + if (MORE(a_i, b_i)) { + return -1; + } + if (LESS(a_i, b_i)) { + return 1; + } + } + return 0; +} + +/* Smallest marginude. Ordering is according to + 1 Magnitude (smaller first) + 2 Complex eigenvalues before real ones + 3 Smaller real part + 4 Smaller imaginary part + This ensures that lm has exactly the opposite order to sm */ + +static int igraph_i_eigen_matrix_lapack_cmp_sm(void *extra, const void *a, + const void *b) { + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + int *aa = (int*) a, *bb = (int*) b; + igraph_real_t a_m = VECTOR(*myextra->mag)[*aa]; + igraph_real_t b_m = VECTOR(*myextra->mag)[*bb]; + + if (MORE(a_m, b_m)) { + return 1; + } else if (LESS(a_m, b_m)) { + return -1; + } else { + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + if (NONZERO(a_i) && ZERO(b_i)) { + return -1; + } + if (ZERO(a_i) && NONZERO(b_i)) { + return 1; + } + if (LESS(a_r, b_r)) { + return -1; + } + if (MORE(a_r, b_r)) { + return 1; + } + if (LESS(a_i, b_i)) { + return -1; + } + if (MORE(a_i, b_i)) { + return 1; + } + } + return 0; +} + +/* Largest real part. Ordering is according to + 1 Larger real part + 2 Real eigenvalues come before complex ones + 3 Larger complex part */ + +static int igraph_i_eigen_matrix_lapack_cmp_lr(void *extra, const void *a, + const void *b) { + + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + int *aa = (int*) a, *bb = (int*) b; + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + + if (MORE(a_r, b_r)) { + return -1; + } else if (LESS(a_r, b_r)) { + return 1; + } else { + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + if (ZERO(a_i) && NONZERO(b_i)) { + return -1; + } + if (NONZERO(a_i) && ZERO(b_i)) { + return 1; + } + if (MORE(a_i, b_i)) { + return -1; + } + if (LESS(a_i, b_i)) { + return 1; + } + } + + return 0; +} + +/* Largest real part. Ordering is according to + 1 Smaller real part + 2 Complex eigenvalues come before real ones + 3 Smaller complex part + This is opposite to LR +*/ + +static int igraph_i_eigen_matrix_lapack_cmp_sr(void *extra, const void *a, + const void *b) { + + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + int *aa = (int*) a, *bb = (int*) b; + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + + if (LESS(a_r, b_r)) { + return -1; + } else if (MORE(a_r, b_r)) { + return 1; + } else { + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + if (NONZERO(a_i) && ZERO(b_i)) { + return -1; + } + if (ZERO(a_i) && NONZERO(b_i)) { + return 1; + } + if (LESS(a_i, b_i)) { + return -1; + } + if (MORE(a_i, b_i)) { + return 1; + } + } + + return 0; +} + +/* Order: + 1 Larger imaginary part + 2 Real eigenvalues before complex ones + 3 Larger real part */ + +static int igraph_i_eigen_matrix_lapack_cmp_li(void *extra, const void *a, + const void *b) { + + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + int *aa = (int*) a, *bb = (int*) b; + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + + if (MORE(a_i, b_i)) { + return -1; + } else if (LESS(a_i, b_i)) { + return 1; + } else { + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + if (ZERO(a_i) && NONZERO(b_i)) { + return -1; + } + if (NONZERO(a_i) && ZERO(b_i)) { + return 1; + } + if (MORE(a_r, b_r)) { + return -1; + } + if (LESS(a_r, b_r)) { + return 1; + } + } + + return 0; +} + +/* Order: + 1 Smaller imaginary part + 2 Complex eigenvalues before real ones + 3 Smaller real part + Order is opposite to LI */ + +static int igraph_i_eigen_matrix_lapack_cmp_si(void *extra, const void *a, + const void *b) { + + igraph_i_eml_cmp_t *myextra = (igraph_i_eml_cmp_t *) extra; + int *aa = (int*) a, *bb = (int*) b; + igraph_real_t a_i = VECTOR(*myextra->imag)[*aa]; + igraph_real_t b_i = VECTOR(*myextra->imag)[*bb]; + + if (LESS(a_i, b_i)) { + return -1; + } else if (MORE(a_i, b_i)) { + return 1; + } else { + igraph_real_t a_r = VECTOR(*myextra->real)[*aa]; + igraph_real_t b_r = VECTOR(*myextra->real)[*bb]; + if (NONZERO(a_i) && ZERO(b_i)) { + return -1; + } + if (ZERO(a_i) && NONZERO(b_i)) { + return 1; + } + if (LESS(a_r, b_r)) { + return -1; + } + if (MORE(a_r, b_r)) { + return 1; + } + } + + return 0; +} + +#undef EPS +#undef LESS +#undef MORE +#undef ZERO +#undef NONZERO + +#define INITMAG() \ + do { \ + int i; \ + IGRAPH_VECTOR_INIT_FINALLY(&mag, nev); \ + hasmag=1; \ + for (i=0; ipos) { + case IGRAPH_EIGEN_LM: + INITMAG(); + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_lm; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_ALL: + INITMAG(); + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_sm; + howmany = nev; + break; + case IGRAPH_EIGEN_SM: + INITMAG(); + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_sm; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_LR: + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_lr; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_SR: + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_sr; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_SELECT: + INITMAG(); + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_sm; + start = which->il - 1; + howmany = which->iu - which->il + 1; + break; + case IGRAPH_EIGEN_LI: + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_li; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_SI: + cmpfunc = igraph_i_eigen_matrix_lapack_cmp_si; + howmany = which->howmany; + break; + case IGRAPH_EIGEN_INTERVAL: + case IGRAPH_EIGEN_BE: + default: + IGRAPH_ERROR("Unimplemented eigenvalue ordering", IGRAPH_UNIMPLEMENTED); + break; + } + + for (i = 0; i < nev; i++) { + VECTOR(idx)[i] = i; + } + + igraph_qsort_r(VECTOR(idx), (size_t) nev, sizeof(VECTOR(idx)[0]), extra, + cmpfunc); + + if (hasmag) { + igraph_vector_destroy(&mag); + IGRAPH_FINALLY_CLEAN(1); + } + + if (values) { + IGRAPH_CHECK(igraph_vector_complex_resize(values, howmany)); + for (i = 0; i < howmany; i++) { + int x = VECTOR(idx)[start + i]; + VECTOR(*values)[i] = igraph_complex(VECTOR(*real)[x], + VECTOR(*imag)[x]); + } + } + + if (vectors) { + int n = (int) igraph_matrix_nrow(compressed); + IGRAPH_CHECK(igraph_matrix_complex_resize(vectors, n, howmany)); + for (i = 0; i < howmany; i++) { + int j, x = VECTOR(idx)[start + i]; + if (VECTOR(*imag)[x] == 0) { + /* real eigenvalue */ + for (j = 0; j < n; j++) { + MATRIX(*vectors, j, i) = igraph_complex(MATRIX(*compressed, j, x), + 0.0); + } + } else { + /* complex eigenvalue */ + int neg = 1, co = 0; + if (VECTOR(*imag)[x] < 0) { + neg = -1; + co = 1; + } + for (j = 0; j < n; j++) { + MATRIX(*vectors, j, i) = + igraph_complex(MATRIX(*compressed, j, x - co), + neg * MATRIX(*compressed, j, x + 1 - co)); + } + } + } + } + + igraph_vector_int_destroy(&idx); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_eigen_matrix_lapack_common(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + + igraph_vector_t valuesreal, valuesimag; + igraph_matrix_t vectorsright, *myvectors = vectors ? &vectorsright : 0; + int n = (int) igraph_matrix_nrow(A); + int info = 1; + + IGRAPH_VECTOR_INIT_FINALLY(&valuesreal, n); + IGRAPH_VECTOR_INIT_FINALLY(&valuesimag, n); + if (vectors) { + IGRAPH_MATRIX_INIT_FINALLY(&vectorsright, n, n); + } + IGRAPH_CHECK(igraph_lapack_dgeev(A, &valuesreal, &valuesimag, + /*vectorsleft=*/ 0, myvectors, &info)); + + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack_reorder(&valuesreal, + &valuesimag, + myvectors, which, values, + vectors)); + + if (vectors) { + igraph_matrix_destroy(&vectorsright); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&valuesimag); + igraph_vector_destroy(&valuesreal); + IGRAPH_FINALLY_CLEAN(2); + + return 0; + +} + +static int igraph_i_eigen_matrix_lapack_lm(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + return igraph_i_eigen_matrix_lapack_common(A, which, values, vectors); +} + +static int igraph_i_eigen_matrix_lapack_sm(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + return igraph_i_eigen_matrix_lapack_common(A, which, values, vectors); +} + +static int igraph_i_eigen_matrix_lapack_lr(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + return igraph_i_eigen_matrix_lapack_common(A, which, values, vectors); +} + + +static int igraph_i_eigen_matrix_lapack_sr(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + return igraph_i_eigen_matrix_lapack_common(A, which, values, vectors); +} + +static int igraph_i_eigen_matrix_lapack_li(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + return igraph_i_eigen_matrix_lapack_common(A, which, values, vectors); +} + +static int igraph_i_eigen_matrix_lapack_si(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + return igraph_i_eigen_matrix_lapack_common(A, which, values, vectors); +} + +static int igraph_i_eigen_matrix_lapack_select(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + return igraph_i_eigen_matrix_lapack_common(A, which, values, vectors); +} + +static int igraph_i_eigen_matrix_lapack_all(const igraph_matrix_t *A, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + return igraph_i_eigen_matrix_lapack_common(A, which, values, vectors); +} + +static int igraph_i_eigen_matrix_lapack(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, + int n, void *extra, + const igraph_eigen_which_t *which, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + + const igraph_matrix_t *myA = A; + igraph_matrix_t mA; + + /* We need to create a dense square matrix first */ + + if (A) { + n = (int) igraph_matrix_nrow(A); + } else if (sA) { + n = (int) igraph_sparsemat_nrow(sA); + IGRAPH_CHECK(igraph_matrix_init(&mA, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &mA); + IGRAPH_CHECK(igraph_sparsemat_as_matrix(&mA, sA)); + myA = &mA; + } else if (fun) { + IGRAPH_CHECK(igraph_i_eigen_arpackfun_to_mat(fun, n, extra, &mA)); + IGRAPH_FINALLY(igraph_matrix_destroy, &mA); + } + + switch (which->pos) { + case IGRAPH_EIGEN_LM: + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack_lm(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_SM: + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack_sm(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_LR: + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack_lr(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_SR: + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack_sr(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_LI: + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack_li(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_SI: + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack_si(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_SELECT: + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack_select(myA, which, + values, vectors)); + break; + case IGRAPH_EIGEN_ALL: + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack_all(myA, which, + values, + vectors)); + break; + default: + /* This cannot happen */ + break; + } + + if (!A) { + igraph_matrix_destroy(&mA); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +static int igraph_i_eigen_checks(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, int n) { + + if ( (A ? 1 : 0) + (sA ? 1 : 0) + (fun ? 1 : 0) != 1) { + IGRAPH_ERROR("Exactly one of 'A', 'sA' and 'fun' must be given", + IGRAPH_EINVAL); + } + + if (A) { + if (n != igraph_matrix_ncol(A) || n != igraph_matrix_nrow(A)) { + IGRAPH_ERROR("Invalid matrix", IGRAPH_NONSQUARE); + } + } else if (sA) { + if (n != igraph_sparsemat_ncol(sA) || n != igraph_sparsemat_nrow(sA)) { + IGRAPH_ERROR("Invalid matrix", IGRAPH_NONSQUARE); + } + } + + return 0; +} + +/** + * \function igraph_eigen_matrix_symmetric + * + * \example examples/simple/igraph_eigen_matrix_symmetric.c + */ + +int igraph_eigen_matrix_symmetric(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, int n, + void *extra, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors) { + + IGRAPH_CHECK(igraph_i_eigen_checks(A, sA, fun, n)); + + if (which->pos != IGRAPH_EIGEN_LM && + which->pos != IGRAPH_EIGEN_SM && + which->pos != IGRAPH_EIGEN_LA && + which->pos != IGRAPH_EIGEN_SA && + which->pos != IGRAPH_EIGEN_BE && + which->pos != IGRAPH_EIGEN_ALL && + which->pos != IGRAPH_EIGEN_INTERVAL && + which->pos != IGRAPH_EIGEN_SELECT) { + IGRAPH_ERROR("Invalid 'pos' position in 'which'", IGRAPH_EINVAL); + } + + switch (algorithm) { + case IGRAPH_EIGEN_AUTO: + if (which->howmany == n || n < 100) { + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack(A, sA, fun, n, + extra, which, + values, vectors)); + } else { + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_arpack(A, sA, fun, n, + extra, which, + options, storage, + values, vectors)); + } + break; + case IGRAPH_EIGEN_LAPACK: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_lapack(A, sA, fun, n, extra, + which, values, + vectors)); + break; + case IGRAPH_EIGEN_ARPACK: + IGRAPH_CHECK(igraph_i_eigen_matrix_symmetric_arpack(A, sA, fun, n, extra, + which, options, + storage, + values, vectors)); + break; + default: + IGRAPH_ERROR("Unknown 'algorithm'", IGRAPH_EINVAL); + } + + return 0; +} + +/** + * \function igraph_eigen_matrix + * + */ + +int igraph_eigen_matrix(const igraph_matrix_t *A, + const igraph_sparsemat_t *sA, + igraph_arpack_function_t *fun, int n, + void *extra, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors) { + + IGRAPH_CHECK(igraph_i_eigen_checks(A, sA, fun, n)); + + if (which->pos != IGRAPH_EIGEN_LM && + which->pos != IGRAPH_EIGEN_SM && + which->pos != IGRAPH_EIGEN_LR && + which->pos != IGRAPH_EIGEN_SR && + which->pos != IGRAPH_EIGEN_LI && + which->pos != IGRAPH_EIGEN_SI && + which->pos != IGRAPH_EIGEN_SELECT && + which->pos != IGRAPH_EIGEN_ALL) { + IGRAPH_ERROR("Invalid 'pos' position in 'which'", IGRAPH_EINVAL); + } + + switch (algorithm) { + case IGRAPH_EIGEN_AUTO: + IGRAPH_ERROR("'AUTO' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_LAPACK: + IGRAPH_CHECK(igraph_i_eigen_matrix_lapack(A, sA, fun, n, extra, which, + values, vectors)); + /* TODO */ + break; + case IGRAPH_EIGEN_ARPACK: + IGRAPH_ERROR("'ARPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_COMP_AUTO: + IGRAPH_ERROR("'COMP_AUTO' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_COMP_LAPACK: + IGRAPH_ERROR("'COMP_LAPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_COMP_ARPACK: + IGRAPH_ERROR("'COMP_ARPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + default: + IGRAPH_ERROR("Unknown `algorithm'", IGRAPH_EINVAL); + } + + return 0; +} + +static int igraph_i_eigen_adjacency_arpack_sym_cb(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + igraph_adjlist_t *adjlist = (igraph_adjlist_t *) extra; + igraph_vector_int_t *neis; + int i, j, nlen; + + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(adjlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + int nei = VECTOR(*neis)[j]; + to[i] += from[nei]; + } + } + + return 0; +} + +static int igraph_i_eigen_adjacency_arpack(const igraph_t *graph, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t* storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_vector_complex_t *cmplxvalues, + igraph_matrix_complex_t *cmplxvectors) { + + igraph_adjlist_t adjlist; + void *extra = (void*) &adjlist; + int n = igraph_vcount(graph); + + if (!options) { + IGRAPH_ERROR("`options' must be given for ARPACK algorithm", + IGRAPH_EINVAL); + } + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("ARPACK adjacency eigensolver not implemented for " + "directed graphs", IGRAPH_UNIMPLEMENTED); + } + if (which->pos == IGRAPH_EIGEN_INTERVAL) { + IGRAPH_ERROR("ARPACK adjacency eigensolver does not implement " + "`INTERNAL' eigenvalues", IGRAPH_UNIMPLEMENTED); + } + if (which->pos == IGRAPH_EIGEN_SELECT) { + IGRAPH_ERROR("ARPACK adjacency eigensolver does not implement " + "`SELECT' eigenvalues", IGRAPH_UNIMPLEMENTED); + } + if (which->pos == IGRAPH_EIGEN_ALL) { + IGRAPH_ERROR("ARPACK adjacency eigensolver does not implement " + "`ALL' eigenvalues", IGRAPH_UNIMPLEMENTED); + } + + switch (which->pos) { + case IGRAPH_EIGEN_LM: + options->which[0] = 'L'; options->which[1] = 'M'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_SM: + options->which[0] = 'S'; options->which[1] = 'M'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_LA: + options->which[0] = 'L'; options->which[1] = 'A'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_SA: + options->which[0] = 'S'; options->which[1] = 'A'; + options->nev = which->howmany; + break; + case IGRAPH_EIGEN_ALL: + options->which[0] = 'L'; options->which[1] = 'M'; + options->nev = n; + break; + case IGRAPH_EIGEN_BE: + IGRAPH_ERROR("Eigenvectors from both ends with ARPACK", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_INTERVAL: + IGRAPH_ERROR("Interval of eigenvectors with ARPACK", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_SELECT: + IGRAPH_ERROR("Selected eigenvalues with ARPACK", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + default: + /* This cannot happen */ + break; + } + + options->n = n; + options->ncv = 2 * options->nev < n ? 2 * options->nev : n; + + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_eigen_adjacency_arpack_sym_cb, + extra, options, storage, values, vectors)); + + igraph_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_eigen_adjacency + * + */ + +int igraph_eigen_adjacency(const igraph_t *graph, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_vector_complex_t *cmplxvalues, + igraph_matrix_complex_t *cmplxvectors) { + + if (which->pos != IGRAPH_EIGEN_LM && + which->pos != IGRAPH_EIGEN_SM && + which->pos != IGRAPH_EIGEN_LA && + which->pos != IGRAPH_EIGEN_SA && + which->pos != IGRAPH_EIGEN_BE && + which->pos != IGRAPH_EIGEN_SELECT && + which->pos != IGRAPH_EIGEN_INTERVAL && + which->pos != IGRAPH_EIGEN_ALL) { + IGRAPH_ERROR("Invalid 'pos' position in 'which'", IGRAPH_EINVAL); + } + + switch (algorithm) { + case IGRAPH_EIGEN_AUTO: + IGRAPH_ERROR("'AUTO' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_LAPACK: + IGRAPH_ERROR("'LAPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_ARPACK: + IGRAPH_CHECK(igraph_i_eigen_adjacency_arpack(graph, which, options, + storage, values, vectors, + cmplxvalues, + cmplxvectors)); + break; + case IGRAPH_EIGEN_COMP_AUTO: + IGRAPH_ERROR("'COMP_AUTO' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_COMP_LAPACK: + IGRAPH_ERROR("'COMP_LAPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + case IGRAPH_EIGEN_COMP_ARPACK: + IGRAPH_ERROR("'COMP_ARPACK' algorithm not implemented yet", + IGRAPH_UNIMPLEMENTED); + /* TODO */ + break; + default: + IGRAPH_ERROR("Unknown `algorithm'", IGRAPH_EINVAL); + } + + + return 0; +} + +/** + * \function igraph_eigen_laplacian + * + */ + +int igraph_eigen_laplacian(const igraph_t *graph, + igraph_eigen_algorithm_t algorithm, + const igraph_eigen_which_t *which, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_vector_complex_t *cmplxvalues, + igraph_matrix_complex_t *cmplxvectors) { + + IGRAPH_ERROR("'igraph_eigen_laplacian'", IGRAPH_UNIMPLEMENTED); + /* TODO */ + return 0; +} diff --git a/src/embedding.c b/src/embedding.c new file mode 100644 index 0000000..9ed3722 --- /dev/null +++ b/src/embedding.c @@ -0,0 +1,1169 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_embedding.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include "igraph_centrality.h" +#include "igraph_blas.h" + +typedef struct { + const igraph_t *graph; + const igraph_vector_t *cvec; + const igraph_vector_t *cvec2; + igraph_adjlist_t *outlist, *inlist; + igraph_inclist_t *eoutlist, *einlist; + igraph_vector_t *tmp; + const igraph_vector_t *weights; +} igraph_i_asembedding_data_t; + +/* Adjacency matrix, unweighted, undirected. + Eigendecomposition is used */ +static int igraph_i_asembeddingu(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *outlist = data->outlist; + const igraph_vector_t *cvec = data->cvec; + igraph_vector_int_t *neis; + int i, j, nlen; + + /* to = (A+cD) from */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + to[i] += from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return 0; +} + +/* Adjacency matrix, weighted, undirected. + Eigendecomposition is used. */ +static int igraph_i_asembeddinguw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *outlist = data->eoutlist; + const igraph_vector_t *cvec = data->cvec; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_int_t *incs; + int i, j, nlen; + + /* to = (A+cD) from */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(incs); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int edge = VECTOR(*incs)[j]; + long int nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + to[i] += w * from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return 0; +} + +/* Adjacency matrix, unweighted, directed. SVD. */ +static int igraph_i_asembedding(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *outlist = data->outlist; + igraph_adjlist_t *inlist = data->inlist; + const igraph_vector_t *cvec = data->cvec; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + int i, j, nlen; + + /* tmp = (A+cD)' from */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + VECTOR(*tmp)[i] += from[nei]; + } + VECTOR(*tmp)[i] += VECTOR(*cvec)[i] * from[i]; + } + + /* to = (A+cD) tmp */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + to[i] += VECTOR(*tmp)[nei]; + } + to[i] += VECTOR(*cvec)[i] * VECTOR(*tmp)[i]; + } + + return 0; +} + +/* Adjacency matrix, unweighted, directed. SVD, right eigenvectors */ +static int igraph_i_asembedding_right(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *inlist = data->inlist; + const igraph_vector_t *cvec = data->cvec; + igraph_vector_int_t *neis; + int i, j, nlen; + + /* to = (A+cD)' from */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + to[i] += from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return 0; +} + +/* Adjacency matrix, weighted, directed. SVD. */ +static int igraph_i_asembeddingw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *outlist = data->eoutlist; + igraph_inclist_t *inlist = data->einlist; + const igraph_vector_t *cvec = data->cvec; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *incs; + int i, j, nlen; + + /* tmp = (A+cD)' from */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(inlist, i); + nlen = igraph_vector_int_size(incs); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int edge = VECTOR(*incs)[j]; + long int nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + VECTOR(*tmp)[i] += w * from[nei]; + } + VECTOR(*tmp)[i] += VECTOR(*cvec)[i] * from[i]; + } + + /* to = (A+cD) tmp */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(incs); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int edge = VECTOR(*incs)[j]; + long int nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + to[i] += w * VECTOR(*tmp)[nei]; + } + to[i] += VECTOR(*cvec)[i] * VECTOR(*tmp)[i]; + } + + return 0; +} + +/* Adjacency matrix, weighted, directed. SVD, right eigenvectors. */ +static int igraph_i_asembeddingw_right(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *inlist = data->einlist; + const igraph_vector_t *cvec = data->cvec; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_int_t *incs; + int i, j, nlen; + + /* to = (A+cD)' from */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(inlist, i); + nlen = igraph_vector_int_size(incs); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int edge = VECTOR(*incs)[j]; + long int nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + to[i] += w * from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return 0; +} + +/* Laplacian D-A, unweighted, undirected. Eigendecomposition. */ +static int igraph_i_lsembedding_da(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *outlist = data->outlist; + const igraph_vector_t *cvec = data->cvec; + igraph_vector_int_t *neis; + int i, j, nlen; + + /* to = (D-A) from */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + to[i] -= from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return 0; +} + +/* Laplacian D-A, weighted, undirected. Eigendecomposition. */ +static int igraph_i_lsembedding_daw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *outlist = data->eoutlist; + const igraph_vector_t *cvec = data->cvec; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_int_t *incs; + int i, j, nlen; + + /* to = (D-A) from */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(incs); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int edge = VECTOR(*incs)[j]; + long int nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + to[i] -= w * from[nei]; + } + to[i] += VECTOR(*cvec)[i] * from[i]; + } + + return 0; +} + +/* Laplacian DAD, unweighted, undirected. Eigendecomposition. */ +static int igraph_i_lsembedding_dad(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *outlist = data->outlist; + const igraph_vector_t *cvec = data->cvec; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + int i, j, nlen; + + /* to = D^1/2 from */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*cvec)[i] * from[i]; + } + + /* tmp = A to */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + VECTOR(*tmp)[i] += to[nei]; + } + } + + /* to = D tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*cvec)[i] * VECTOR(*tmp)[i]; + } + + return 0; +} + +static int igraph_i_lsembedding_dadw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *outlist = data->eoutlist; + const igraph_vector_t *cvec = data->cvec; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *incs; + int i, j, nlen; + + /* to = D^-1/2 from */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*cvec)[i] * from[i]; + } + + /* tmp = A' to */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(incs); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int edge = VECTOR(*incs)[j]; + long int nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + VECTOR(*tmp)[i] += w * to[nei]; + } + } + + /* to = D tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*cvec)[i] * VECTOR(*cvec)[i] * VECTOR(*tmp)[i]; + } + + /* tmp = A to */ + for (i = 0; i < n; i++) { + incs = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(incs); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + long int edge = VECTOR(*incs)[j]; + long int nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + VECTOR(*tmp)[i] += w * to[nei]; + } + } + + /* to = D^-1/2 tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*cvec)[i] * VECTOR(*tmp)[i]; + } + + return 0; +} + +/* Laplacian I-DAD, unweighted, undirected. Eigendecomposition. */ +static int igraph_i_lsembedding_idad(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + int i; + + igraph_i_lsembedding_dad(to, from, n, extra); + for (i = 0; i < n; i++) { + to[i] = from[i] - to[i]; + } + + return 0; +} + +static int igraph_i_lsembedding_idadw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + int i; + + igraph_i_lsembedding_dadw(to, from, n, extra); + for (i = 0; i < n; i++) { + to[i] = from[i] - to[i]; + } + + return 0; +} + +/* Laplacian OAP, unweighted, directed. SVD. */ +static int igraph_i_lseembedding_oap(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *outlist = data->outlist; + igraph_adjlist_t *inlist = data->inlist; + const igraph_vector_t *deg_in = data->cvec; + const igraph_vector_t *deg_out = data->cvec2; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + int i, j, nlen; + + /* tmp = O' from */ + for (i = 0; i < n; i++) { + VECTOR(*tmp)[i] = VECTOR(*deg_out)[i] * from[i]; + } + + /* to = A' tmp */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + int nei = VECTOR(*neis)[j]; + to[i] += VECTOR(*tmp)[nei]; + } + } + + /* tmp = P' to */ + for (i = 0; i < n; i++) { + VECTOR(*tmp)[i] = VECTOR(*deg_in)[i] * to[i]; + } + + /* to = P tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_in)[i] * VECTOR(*tmp)[i]; + } + + /* tmp = A to */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + int nei = VECTOR(*neis)[j]; + VECTOR(*tmp)[i] += to[nei]; + } + } + + /* to = O tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_out)[i] * VECTOR(*tmp)[i]; + } + + return 0; +} + +/* Laplacian OAP, unweighted, directed. SVD, right eigenvectors. */ +static int igraph_i_lseembedding_oap_right(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_adjlist_t *inlist = data->inlist; + const igraph_vector_t *deg_in = data->cvec; + const igraph_vector_t *deg_out = data->cvec2; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + int i, j, nlen; + + /* to = O' from */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_out)[i] * from[i]; + } + + /* tmp = A' to */ + for (i = 0; i < n; i++) { + neis = igraph_adjlist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + int nei = VECTOR(*neis)[j]; + VECTOR(*tmp)[i] += to[nei]; + } + } + + /* to = P' tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_in)[i] * VECTOR(*tmp)[i]; + } + + return 0; +} + +/* Laplacian OAP, weighted, directed. SVD. */ +static int igraph_i_lseembedding_oapw(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *outlist = data->eoutlist; + igraph_inclist_t *inlist = data->einlist; + const igraph_vector_t *deg_in = data->cvec; + const igraph_vector_t *deg_out = data->cvec2; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + int i, j, nlen; + + /* tmp = O' from */ + for (i = 0; i < n; i++) { + VECTOR(*tmp)[i] = VECTOR(*deg_out)[i] * from[i]; + } + + /* to = A' tmp */ + for (i = 0; i < n; i++) { + neis = igraph_inclist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + to[i] = 0.0; + for (j = 0; j < nlen; j++) { + int edge = VECTOR(*neis)[j]; + int nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + to[i] += w * VECTOR(*tmp)[nei]; + } + } + + /* tmp = P' to */ + for (i = 0; i < n; i++) { + VECTOR(*tmp)[i] = VECTOR(*deg_in)[i] * to[i]; + } + + /* to = P tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_in)[i] * VECTOR(*tmp)[i]; + } + + /* tmp = A to */ + for (i = 0; i < n; i++) { + neis = igraph_inclist_get(outlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + int edge = VECTOR(*neis)[j]; + int nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + VECTOR(*tmp)[i] += w * to[nei]; + } + } + + /* to = O tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_out)[i] * VECTOR(*tmp)[i]; + } + + return 0; +} + +/* Laplacian OAP, weighted, directed. SVD, right eigenvectors. */ +static int igraph_i_lseembedding_oapw_right(igraph_real_t *to, + const igraph_real_t *from, + int n, void *extra) { + igraph_i_asembedding_data_t *data = extra; + igraph_inclist_t *inlist = data->einlist; + const igraph_vector_t *deg_in = data->cvec; + const igraph_vector_t *deg_out = data->cvec2; + const igraph_vector_t *weights = data->weights; + const igraph_t *graph = data->graph; + igraph_vector_t *tmp = data->tmp; + igraph_vector_int_t *neis; + int i, j, nlen; + + /* to = O' from */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_out)[i] * from[i]; + } + + /* tmp = A' to */ + for (i = 0; i < n; i++) { + neis = igraph_inclist_get(inlist, i); + nlen = igraph_vector_int_size(neis); + VECTOR(*tmp)[i] = 0.0; + for (j = 0; j < nlen; j++) { + int edge = VECTOR(*neis)[j]; + int nei = IGRAPH_OTHER(graph, edge, i); + igraph_real_t w = VECTOR(*weights)[edge]; + VECTOR(*tmp)[i] += w * to[nei]; + } + } + + /* to = P' tmp */ + for (i = 0; i < n; i++) { + to[i] = VECTOR(*deg_in)[i] * VECTOR(*tmp)[i]; + } + + return 0; +} + +static int igraph_i_spectral_embedding(const igraph_t *graph, + igraph_integer_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + const igraph_vector_t *cvec, + const igraph_vector_t *cvec2, + igraph_arpack_options_t *options, + igraph_arpack_function_t *callback, + igraph_arpack_function_t *callback_right, + igraph_bool_t symmetric, + igraph_bool_t eigen, + igraph_bool_t zapsmall) { + + igraph_integer_t vc = igraph_vcount(graph); + igraph_vector_t tmp; + igraph_adjlist_t outlist, inlist; + igraph_inclist_t eoutlist, einlist; + int i, j, cveclen = igraph_vector_size(cvec); + igraph_i_asembedding_data_t data = { graph, cvec, cvec2, &outlist, &inlist, + &eoutlist, &einlist, &tmp, weights + }; + igraph_vector_t tmpD; + + if (weights && igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + if (which != IGRAPH_EIGEN_LM && + which != IGRAPH_EIGEN_LA && + which != IGRAPH_EIGEN_SA) { + IGRAPH_ERROR("Invalid eigenvalue chosen, must be one of " + "`largest magnitude', `largest algebraic' or " + "`smallest algebraic'", IGRAPH_EINVAL); + } + + if (no > vc) { + IGRAPH_ERROR("Too many singular values requested", IGRAPH_EINVAL); + } + if (no <= 0) { + IGRAPH_ERROR("No singular values requested", IGRAPH_EINVAL); + } + + if (cveclen != 1 && cveclen != vc) { + IGRAPH_ERROR("Augmentation vector size is invalid, it should be " + "the number of vertices or scalar", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(X, vc, no)); + if (Y) { + IGRAPH_CHECK(igraph_matrix_resize(Y, vc, no)); + } + + /* empty graph */ + if (igraph_ecount(graph) == 0) { + igraph_matrix_null(X); + if (Y) { + igraph_matrix_null(Y); + } + return 0; + } + + igraph_vector_init(&tmp, vc); + IGRAPH_FINALLY(igraph_vector_destroy, &tmp); + if (!weights) { + IGRAPH_CHECK(igraph_adjlist_init(graph, &outlist, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &outlist); + if (!symmetric) { + IGRAPH_CHECK(igraph_adjlist_init(graph, &inlist, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &inlist); + } + } else { + IGRAPH_CHECK(igraph_inclist_init(graph, &eoutlist, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_inclist_destroy, &eoutlist); + if (!symmetric) { + IGRAPH_CHECK(igraph_inclist_init(graph, &einlist, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_inclist_destroy, &einlist); + } + } + IGRAPH_VECTOR_INIT_FINALLY(&tmpD, no); + + options->n = vc; + options->start = 0; /* random start vector */ + options->nev = no; + switch (which) { + case IGRAPH_EIGEN_LM: + options->which[0] = 'L'; options->which[1] = 'M'; + break; + case IGRAPH_EIGEN_LA: + options->which[0] = 'L'; options->which[1] = 'A'; + break; + case IGRAPH_EIGEN_SA: + options->which[0] = 'S'; options->which[1] = 'A'; + break; + default: + break; + } + options->ncv = no + 3; + if (options->ncv > vc) { + options->ncv = vc; + } + + IGRAPH_CHECK(igraph_arpack_rssolve(callback, &data, options, 0, &tmpD, X)); + + if (!symmetric) { + /* calculate left eigenvalues */ + IGRAPH_CHECK(igraph_matrix_resize(Y, vc, no)); + for (i = 0; i < no; i++) { + igraph_real_t norm; + igraph_vector_t v; + callback_right(&MATRIX(*Y, 0, i), &MATRIX(*X, 0, i), vc, &data); + igraph_vector_view(&v, &MATRIX(*Y, 0, i), vc); + norm = 1.0 / igraph_blas_dnrm2(&v); + igraph_vector_scale(&v, norm); + } + } else if (Y) { + IGRAPH_CHECK(igraph_matrix_update(Y, X)); + } + + if (zapsmall) { + igraph_vector_zapsmall(&tmpD, 0); + igraph_matrix_zapsmall(X, 0); + if (Y) { + igraph_matrix_zapsmall(Y, 0); + } + } + + if (D) { + igraph_vector_update(D, &tmpD); + if (!eigen) { + for (i = 0; i < no; i++) { + VECTOR(*D)[i] = sqrt(VECTOR(*D)[i]); + } + } + } + + if (scaled) { + if (eigen) { + /* eigenvalues were calculated */ + for (i = 0; i < no; i++) { + VECTOR(tmpD)[i] = sqrt(fabs(VECTOR(tmpD)[i])); + } + } else { + /* singular values were calculated */ + for (i = 0; i < no; i++) { + VECTOR(tmpD)[i] = sqrt(sqrt(VECTOR(tmpD)[i])); + } + } + + for (j = 0; j < vc; j++) { + for (i = 0; i < no; i++) { + MATRIX(*X, j, i) *= VECTOR(tmpD)[i]; + } + } + + if (Y) { + for (j = 0; j < vc; j++) { + for (i = 0; i < no; i++) { + MATRIX(*Y, j, i) *= VECTOR(tmpD)[i]; + } + } + } + } + + igraph_vector_destroy(&tmpD); + if (!weights) { + if (!symmetric) { + igraph_adjlist_destroy(&inlist); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_adjlist_destroy(&outlist); + } else { + if (!symmetric) { + igraph_inclist_destroy(&einlist); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_inclist_destroy(&eoutlist); + } + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_adjacency_spectral_embedding + * Adjacency spectral embedding + * + * Spectral decomposition of the adjacency matrices of graphs. + * This function computes a \code{no}-dimensional Euclidean + * representation of the graph based on its adjacency + * matrix, A. This representation is computed via the singular value + * decomposition of the adjacency matrix, A=UDV^T. In the case, + * where the graph is a random dot product graph generated using latent + * position vectors in R^no for each vertex, the embedding will + * provide an estimate of these latent vectors. + * + * + * For undirected graphs the latent positions are calculated as + * X=U^no D^(1/2) where U^no equals to the first no columns of U, and + * D^(1/2) is a diagonal matrix containing the square root of the selected + * singular values on the diagonal. + * + * + * For directed graphs the embedding is defined as the pair + * X=U^no D^(1/2), Y=V^no D^(1/2). (For undirected graphs U=V, + * so it is enough to keep one of them.) + * + * \param graph The input graph, can be directed or undirected. + * \param no An integer scalar. This value is the embedding dimension of + * the spectral embedding. Should be smaller than the number of + * vertices. The largest no-dimensional non-zero + * singular values are used for the spectral embedding. + * \param weights Optional edge weights. Supply a null pointer for + * unweighted graphs. + * \param which Which eigenvalues (or singular values, for directed + * graphs) to use, possible values: + * \clist + * \cli IGRAPH_EIGEN_LM + * the ones with the largest magnitude + * \cli IGRAPH_EIGEN_LA + * the (algebraic) largest ones + * \cli IGRAPH_EIGEN_SA + * the (algebraic) smallest ones. + * \endclist + * For directed graphs, IGRAPH_EIGEN_LM and + * IGRAPH_EIGEN_LA are the same because singular + * values are used for the ordering instead of eigenvalues. + * \param scaled Whether to return X and Y (if scaled is non-zero), or + * U and V. + * \param X Initialized matrix, the estimated latent positions are + * stored here. + * \param Y Initialized matrix or a null pointer. If not a null + * pointer, then the second half of the latent positions are + * stored here. (For undirected graphs, this always equals X.) + * \param D Initialized vector or a null pointer. If not a null + * pointer, then the eigenvalues (for undirected graphs) or the + * singular values (for directed graphs) are stored here. + * \param cvec A numeric vector, its length is the number vertices in the + * graph. This vector is added to the diagonal of the adjacency + * matrix, before performing the SVD. + * \param options Options to ARPACK. See \ref igraph_arpack_options_t + * for details. Note that the function overwrites the + * n (number of vertices), nev and + * which parameters and it always starts the + * calculation from a random start vector. + * \return Error code. + * + */ + +int igraph_adjacency_spectral_embedding(const igraph_t *graph, + igraph_integer_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + const igraph_vector_t *cvec, + igraph_arpack_options_t *options) { + + igraph_arpack_function_t *callback, *callback_right; + igraph_bool_t directed = igraph_is_directed(graph); + + if (directed) { + callback = weights ? igraph_i_asembeddingw : igraph_i_asembedding; + callback_right = (weights ? igraph_i_asembeddingw_right : + igraph_i_asembedding_right); + } else { + callback = weights ? igraph_i_asembeddinguw : igraph_i_asembeddingu; + callback_right = 0; + } + + return igraph_i_spectral_embedding(graph, no, weights, which, scaled, + X, Y, D, cvec, /* deg2=*/ 0, + options, callback, callback_right, + /*symmetric=*/ !directed, + /*eigen=*/ !directed, /*zapsmall=*/ 1); +} + +static int igraph_i_lse_und(const igraph_t *graph, + igraph_integer_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_neimode_t degmode, + igraph_laplacian_spectral_embedding_type_t type, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + igraph_arpack_options_t *options) { + + igraph_arpack_function_t *callback; + igraph_vector_t deg; + + switch (type) { + case IGRAPH_EMBEDDING_D_A: + callback = weights ? igraph_i_lsembedding_daw : igraph_i_lsembedding_da; + break; + case IGRAPH_EMBEDDING_DAD: + callback = weights ? igraph_i_lsembedding_dadw : igraph_i_lsembedding_dad; + break; + case IGRAPH_EMBEDDING_I_DAD: + callback = weights ? igraph_i_lsembedding_idadw : igraph_i_lsembedding_idad; + break; + default: + IGRAPH_ERROR("Invalid Laplacian spectral embedding type", + IGRAPH_EINVAL); + break; + } + + IGRAPH_VECTOR_INIT_FINALLY(°, 0); + igraph_strength(graph, °, igraph_vss_all(), IGRAPH_ALL, /*loops=*/ 1, + weights); + + switch (type) { + case IGRAPH_EMBEDDING_D_A: + break; + case IGRAPH_EMBEDDING_DAD: + case IGRAPH_EMBEDDING_I_DAD: { + int i, n = igraph_vector_size(°); + for (i = 0; i < n; i++) { + VECTOR(deg)[i] = 1.0 / sqrt(VECTOR(deg)[i]); + } + } + break; + default: + break; + } + + IGRAPH_CHECK(igraph_i_spectral_embedding(graph, no, weights, which, + scaled, X, Y, D, /*cvec=*/ °, /*deg2=*/ 0, + options, callback, 0, /*symmetric=*/ 1, + /*eigen=*/ 1, /*zapsmall=*/ 1)); + + igraph_vector_destroy(°); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_lse_dir(const igraph_t *graph, + igraph_integer_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_neimode_t degmode, + igraph_laplacian_spectral_embedding_type_t type, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + igraph_arpack_options_t *options) { + + igraph_arpack_function_t *callback = + weights ? igraph_i_lseembedding_oapw : igraph_i_lseembedding_oap; + igraph_arpack_function_t *callback_right = + weights ? igraph_i_lseembedding_oapw_right : + igraph_i_lseembedding_oap_right; + igraph_vector_t deg_in, deg_out; + int i, n = igraph_vcount(graph); + + if (type != IGRAPH_EMBEDDING_OAP) { + IGRAPH_ERROR("Invalid Laplacian spectral embedding type", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(°_in, n); + IGRAPH_VECTOR_INIT_FINALLY(°_out, n); + igraph_strength(graph, °_in, igraph_vss_all(), IGRAPH_IN, /*loops=*/ 1, + weights); + igraph_strength(graph, °_out, igraph_vss_all(), IGRAPH_OUT, /*loops=*/ 1, + weights); + + for (i = 0; i < n; i++) { + VECTOR(deg_in)[i] = 1.0 / sqrt(VECTOR(deg_in)[i]); + VECTOR(deg_out)[i] = 1.0 / sqrt(VECTOR(deg_out)[i]); + } + + IGRAPH_CHECK(igraph_i_spectral_embedding(graph, no, weights, which, + scaled, X, Y, D, /*cvec=*/ °_in, + /*deg2=*/ °_out, options, callback, + callback_right, /*symmetric=*/ 0, /*eigen=*/ 0, + /*zapsmall=*/ 1)); + + igraph_vector_destroy(°_in); + igraph_vector_destroy(°_out); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_laplacian_spectral_embedding + * Spectral embedding of the Laplacian of a graph + * + * This function essentially does the same as + * \ref igraph_adjacency_spectral_embedding, but works on the Laplacian + * of the graph, instead of the adjacency matrix. + * \param graph The input graph. + * \param no The number of eigenvectors (or singular vectors if the graph + * is directed) to use for the embedding. + * \param weights Optional edge weights. Supply a null pointer for + * unweighted graphs. + * \param which Which eigenvalues (or singular values, for directed + * graphs) to use, possible values: + * \clist + * \cli IGRAPH_EIGEN_LM + * the ones with the largest magnitude + * \cli IGRAPH_EIGEN_LA + * the (algebraic) largest ones + * \cli IGRAPH_EIGEN_SA + * the (algebraic) smallest ones. + * \endclist + * For directed graphs, IGRAPH_EIGEN_LM and + * IGRAPH_EIGEN_LA are the same because singular + * values are used for the ordering instead of eigenvalues. + * \param type The type of the Laplacian to use. Various definitions + * exist for the Laplacian of a graph, and one can choose + * between them with this argument. Possible values: + * \clist + * \cli IGRAPH_EMBEDDING_D_A + * means D - A where D is the + * degree matrix and A is the adjacency matrix + * \cli IGRAPH_EMBEDDING_DAD + * means Di times A times Di, + * where Di is the inverse of the square root of the degree matrix; + * \cli IGRAPH_EMBEDDING_I_DAD + * means I - Di A Di, where I + * is the identity matrix. + * \endclist + * \param scaled Whether to return X and Y (if scaled is non-zero), or + * U and V. + * \param X Initialized matrix, the estimated latent positions are + * stored here. + * \param Y Initialized matrix or a null pointer. If not a null + * pointer, then the second half of the latent positions are + * stored here. (For undirected graphs, this always equals X.) + * \param D Initialized vector or a null pointer. If not a null + * pointer, then the eigenvalues (for undirected graphs) or the + * singular values (for directed graphs) are stored here. + * \param options Options to ARPACK. See \ref igraph_arpack_options_t + * for details. Note that the function overwrites the + * n (number of vertices), nev and + * which parameters and it always starts the + * calculation from a random start vector. + * \return Error code. + * + * \sa \ref igraph_adjacency_spectral_embedding to embed the adjacency + * matrix. + */ + +int igraph_laplacian_spectral_embedding(const igraph_t *graph, + igraph_integer_t no, + const igraph_vector_t *weights, + igraph_eigen_which_position_t which, + igraph_neimode_t degmode, + igraph_laplacian_spectral_embedding_type_t type, + igraph_bool_t scaled, + igraph_matrix_t *X, + igraph_matrix_t *Y, + igraph_vector_t *D, + igraph_arpack_options_t *options) { + + if (igraph_is_directed(graph)) { + return igraph_i_lse_dir(graph, no, weights, which, degmode, type, scaled, + X, Y, D, options); + } else { + return igraph_i_lse_und(graph, no, weights, which, degmode, type, scaled, + X, Y, D, options); + } +} + +/** + * \function igraph_dim_select + * Dimensionality selection + * + * Dimensionality selection for singular values using + * profile likelihood. + * + * + * The input of the function is a numeric vector which contains + * the measure of "importance" for each dimension. + * + * + * For spectral embedding, these are the singular values of the adjacency + * matrix. The singular values are assumed to be generated from a + * Gaussian mixture distribution with two components that have different + * means and same variance. The dimensionality d is chosen to + * maximize the likelihood when the d largest singular values are + * assigned to one component of the mixture and the rest of the singular + * values assigned to the other component. + * + * + * This function can also be used for the general separation problem, + * where we assume that the left and the right of the vector are coming + * from two Normal distributions, with different means, and we want + * to know their border. + * + * \param sv A numeric vector, the ordered singular values. + * \param dim The result is stored here. + * \return Error code. + * + * Time complexity: O(n), n is the number of values in sv. + * + * \sa \ref igraph_adjacency_spectral_embedding(). + */ + +int igraph_dim_select(const igraph_vector_t *sv, igraph_integer_t *dim) { + + int i, n = igraph_vector_size(sv); + igraph_real_t x, x2, sum1 = 0.0, sum2 = igraph_vector_sum(sv); + igraph_real_t sumsq1 = 0.0, sumsq2 = 0.0; /* to be set */ + igraph_real_t oldmean1, oldmean2, mean1 = 0.0, mean2 = sum2 / n; + igraph_real_t varsq1 = 0.0, varsq2 = 0.0; /* to be set */ + igraph_real_t var1, var2, sd, profile, max = IGRAPH_NEGINFINITY; + + if (n == 0) { + IGRAPH_ERROR("Need at least one singular value for dimensionality " + "selection", IGRAPH_EINVAL); + } + + if (n == 1) { + *dim = 1; + return 0; + } + + for (i = 0; i < n; i++) { + x = VECTOR(*sv)[i]; + sumsq2 += x * x; + varsq2 += (mean2 - x) * (mean2 - x); + } + + for (i = 0; i < n - 1; i++) { + int n1 = i + 1, n2 = n - i - 1, n1m1 = n1 - 1, n2m1 = n2 - 1; + x = VECTOR(*sv)[i]; x2 = x * x; + sum1 += x; sum2 -= x; + sumsq1 += x2; sumsq2 -= x2; + oldmean1 = mean1; oldmean2 = mean2; + mean1 = sum1 / n1; mean2 = sum2 / n2; + varsq1 += (x - oldmean1) * (x - mean1); + varsq2 -= (x - oldmean2) * (x - mean2); + var1 = i == 0 ? 0 : varsq1 / n1m1; + var2 = i == n - 2 ? 0 : varsq2 / n2m1; + sd = sqrt(( n1m1 * var1 + n2m1 * var2) / (n - 2)); + profile = /* - n * log(2.0*M_PI)/2.0 */ /* This is redundant */ + - n * log(sd) - + ((sumsq1 - 2 * mean1 * sum1 + n1 * mean1 * mean1) + + (sumsq2 - 2 * mean2 * sum2 + n2 * mean2 * mean2)) / 2.0 / sd / sd; + if (profile > max) { + max = profile; + *dim = n1; + } + } + + /* Plus the last case, all elements in one group */ + x = VECTOR(*sv)[n - 1]; + sum1 += x; + oldmean1 = mean1; + mean1 = sum1 / n; + sumsq1 += x * x; + varsq1 += (x - oldmean1) * (x - mean1); + var1 = varsq1 / (n - 1); + sd = sqrt(var1); + profile = /* - n * log(2.0*M_PI)/2.0 */ /* This is redundant */ + - n * log(sd) - + (sumsq1 - 2 * mean1 * sum1 + n * mean1 * mean1) / 2.0 / sd / sd; + if (profile > max) { + max = profile; + *dim = n; + } + + return 0; +} diff --git a/src/f2c.h b/src/f2c.h new file mode 100644 index 0000000..66b12cf --- /dev/null +++ b/src/f2c.h @@ -0,0 +1,234 @@ +/* f2c.h -- Standard Fortran to C header file */ + +/** barf [ba:rf] 2. "He suggested using FORTRAN, and everybody barfed." + + - From The Shogakukan DICTIONARY OF NEW ENGLISH (Second edition) */ + +#ifndef F2C_INCLUDE +#define F2C_INCLUDE + +#include "igraph_blas_internal.h" +#include "igraph_lapack_internal.h" +#include "igraph_arpack_internal.h" + +typedef int integer; +typedef unsigned int uinteger; +typedef char *address; +typedef short int shortint; +typedef float real; +typedef double doublereal; +typedef struct { + real r, i; +} f2c_complex; +typedef struct { + doublereal r, i; +} doublecomplex; +typedef int logical; +typedef short int shortlogical; +typedef char logical1; +typedef char integer1; +#ifdef INTEGER_STAR_8 /* Adjust for integer*8. */ + typedef long longint; /* system-dependent */ + typedef unsigned long ulongint; /* system-dependent */ + #define qbit_clear(a,b) ((a) & ~((ulongint)1 << (b))) + #define qbit_set(a,b) ((a) | ((ulongint)1 << (b))) +#endif + +#define TRUE_ (1) +#define FALSE_ (0) + +/* Extern is for use with -E */ +#ifndef Extern + #define Extern extern +#endif + +/* I/O stuff */ + +#ifdef f2c_i2 + /* for -i2 */ + typedef short flag; + typedef short ftnlen; + typedef short ftnint; +#else + typedef int flag; + typedef int ftnlen; + typedef int ftnint; +#endif + +/*external read, write*/ +typedef struct { + flag cierr; + ftnint ciunit; + flag ciend; + char *cifmt; + ftnint cirec; +} cilist; + +/*internal read, write*/ +typedef struct { + flag icierr; + char *iciunit; + flag iciend; + char *icifmt; + ftnint icirlen; + ftnint icirnum; +} icilist; + +/*open*/ +typedef struct { + flag oerr; + ftnint ounit; + char *ofnm; + ftnlen ofnmlen; + char *osta; + char *oacc; + char *ofm; + ftnint orl; + char *oblnk; +} olist; + +/*close*/ +typedef struct { + flag cerr; + ftnint cunit; + char *csta; +} cllist; + +/*rewind, backspace, endfile*/ +typedef struct { + flag aerr; + ftnint aunit; +} alist; + +/* inquire */ +typedef struct { + flag inerr; + ftnint inunit; + char *infile; + ftnlen infilen; + ftnint *inex; /*parameters in standard's order*/ + ftnint *inopen; + ftnint *innum; + ftnint *innamed; + char *inname; + ftnlen innamlen; + char *inacc; + ftnlen inacclen; + char *inseq; + ftnlen inseqlen; + char *indir; + ftnlen indirlen; + char *infmt; + ftnlen infmtlen; + char *inform; + ftnint informlen; + char *inunf; + ftnlen inunflen; + ftnint *inrecl; + ftnint *innrec; + char *inblank; + ftnlen inblanklen; +} inlist; + +#define VOID void + +union Multitype { /* for multiple entry points */ + integer1 g; + shortint h; + integer i; + /* longint j; */ + real r; + doublereal d; + f2c_complex c; + doublecomplex z; +}; + +typedef union Multitype Multitype; + +/*typedef long int Long;*/ /* No longer used; formerly in Namelist */ + +struct Vardesc { /* for Namelist */ + char *name; + char *addr; + ftnlen *dims; + int type; +}; +typedef struct Vardesc Vardesc; + +struct Namelist { + char *name; + Vardesc **vars; + int nvars; +}; +typedef struct Namelist Namelist; + +#define abs(x) ((x) >= 0 ? (x) : -(x)) +#define dabs(x) (doublereal)abs(x) +#define min(a,b) ((a) <= (b) ? (a) : (b)) +#define max(a,b) ((a) >= (b) ? (a) : (b)) +#define dmin(a,b) (doublereal)min(a,b) +#define dmax(a,b) (doublereal)max(a,b) +#define bit_test(a,b) ((a) >> (b) & 1) +#define bit_clear(a,b) ((a) & ~((uinteger)1 << (b))) +#define bit_set(a,b) ((a) | ((uinteger)1 << (b))) + +/* procedure parameter types for -A and -C++ */ + +#define F2C_proc_par_types 1 +#ifdef __cplusplus + typedef int /* Unknown procedure type */ (*U_fp)(...); + typedef shortint (*J_fp)(...); + typedef integer (*I_fp)(...); + typedef real (*R_fp)(...); + typedef doublereal (*D_fp)(...), (*E_fp)(...); + typedef /* Complex */ VOID (*C_fp)(...); + typedef /* Double Complex */ VOID (*Z_fp)(...); + typedef logical (*L_fp)(...); + typedef shortlogical (*K_fp)(...); + typedef /* Character */ VOID (*H_fp)(...); + typedef /* Subroutine */ int (*S_fp)(...); +#else + typedef int /* Unknown procedure type */ (*U_fp)(); + typedef shortint (*J_fp)(); + typedef integer (*I_fp)(); + typedef real (*R_fp)(); + typedef doublereal (*D_fp)(), (*E_fp)(); + typedef /* Complex */ VOID (*C_fp)(); + typedef /* Double Complex */ VOID (*Z_fp)(); + typedef logical (*L_fp)(); + typedef shortlogical (*K_fp)(); + typedef /* Character */ VOID (*H_fp)(); + typedef /* Subroutine */ int (*S_fp)(); +#endif +/* E_fp is for real functions when -R is not specified */ +typedef VOID C_f; /* complex function */ +typedef VOID H_f; /* character function */ +typedef VOID Z_f; /* double complex function */ +typedef doublereal E_f; /* real function with -R not specified */ + +/* undef any lower-case symbols that your C compiler predefines, e.g.: */ + +#ifndef Skip_f2c_Undefs + #undef cray + #undef gcos + #undef mc68010 + #undef mc68020 + #undef mips + #undef pdp11 + #undef sgi + #undef sparc + #undef sun + #undef sun2 + #undef sun3 + #undef sun4 + #undef u370 + #undef u3b + #undef u3b2 + #undef u3b5 + #undef unix + #undef vax +#endif + +#include "config.h" + +#endif diff --git a/src/f2c_dummy.c b/src/f2c_dummy.c new file mode 100644 index 0000000..de9b2c9 --- /dev/null +++ b/src/f2c_dummy.c @@ -0,0 +1,27 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +int MAIN__(void) { + return 0; +} + diff --git a/src/fast_community.c b/src/fast_community.c new file mode 100644 index 0000000..7483925 --- /dev/null +++ b/src/fast_community.c @@ -0,0 +1,1067 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_community.h" +#include "igraph_memory.h" +#include "igraph_iterators.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_interrupt_internal.h" +#include "igraph_structural.h" +#include "igraph_vector_ptr.h" +#include "config.h" + +/* #define IGRAPH_FASTCOMM_DEBUG */ + +#ifdef _MSC_VER +/* MSVC does not support variadic macros */ +#include +void debug(const char* fmt, ...) { + va_list args; + va_start(args, fmt); +#ifdef IGRAPH_FASTCOMM_DEBUG + vfprintf(stderr, fmt, args); +#endif + va_end(args); +} +#else +#ifdef IGRAPH_FASTCOMM_DEBUG + #define debug(...) fprintf(stderr, __VA_ARGS__) +#else + #define debug(...) +#endif +#endif + +/* + * Implementation of the community structure algorithm originally published + * by Clauset et al in: + * + * A. Clauset, M.E.J. Newman and C. Moore, "Finding community structure in + * very large networks.". Phys. Rev. E 70, 066111 (2004). + * + * The data structures being used are slightly different and they are described + * most closely in: + * + * K. Wakita, T. Tsurumi, "Finding community structure in mega-scale social + * networks.". arXiv:cs/0702048v1. + * + * We maintain a vector of communities, each of which containing a list of + * pointers to their neighboring communities along with the increase in the + * modularity score that could be achieved by joining the two communities. + * Each community has a pointer to one of its neighbors - the one which would + * result in the highest increase in modularity after a join. The local + * (community-level) maximums are also stored in an indexed max-heap. The + * max-heap itself stores its elements in an array which satisfies the heap + * property, but to allow us to access any of the elements in the array based + * on the community index (and not based on the array index - which depends on + * the element's actual position in the heap), we also maintain an index + * vector in the heap: the ith element of the index vector contains the + * position of community i in the array of the max-heap. When we perform + * sifting operations on the heap to restore the heap property, we also maintain + * the index vector. + */ + +/* Structure storing a pair of communities along with their dQ values */ +typedef struct s_igraph_i_fastgreedy_commpair { + long int first; /* first member of the community pair */ + long int second; /* second member of the community pair */ + igraph_real_t *dq; /* pointer to a member of the dq vector storing the */ + /* increase in modularity achieved when joining */ + struct s_igraph_i_fastgreedy_commpair *opposite; +} igraph_i_fastgreedy_commpair; + +/* Structure storing a community */ +typedef struct { + igraph_integer_t id; /* Identifier of the community (for merges matrix) */ + igraph_integer_t size; /* Size of the community */ + igraph_vector_ptr_t neis; /* references to neighboring communities */ + igraph_i_fastgreedy_commpair* maxdq; /* community pair with maximal dq */ +} igraph_i_fastgreedy_community; + +/* Global community list structure */ +typedef struct { + long int no_of_communities, n; /* number of communities, number of vertices */ + igraph_i_fastgreedy_community* e; /* list of communities */ + igraph_i_fastgreedy_community** heap; /* heap of communities */ + igraph_integer_t *heapindex; /* heap index to speed up lookup by community idx */ +} igraph_i_fastgreedy_community_list; + +/* Scans the community neighborhood list for the new maximal dq value. + * Returns 1 if the maximum is different from the previous one, + * 0 otherwise. */ +static int igraph_i_fastgreedy_community_rescan_max( + igraph_i_fastgreedy_community* comm) { + long int i, n; + igraph_i_fastgreedy_commpair *p, *best; + igraph_real_t bestdq, currdq; + + n = igraph_vector_ptr_size(&comm->neis); + if (n == 0) { + comm->maxdq = 0; + return 1; + } + + best = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[0]; + bestdq = *best->dq; + for (i = 1; i < n; i++) { + p = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[i]; + currdq = *p->dq; + if (currdq > bestdq) { + best = p; + bestdq = currdq; + } + } + + if (best != comm->maxdq) { + comm->maxdq = best; + return 1; + } else { + return 0; + } +} + +/* Destroys the global community list object */ +static void igraph_i_fastgreedy_community_list_destroy( + igraph_i_fastgreedy_community_list* list) { + long int i; + for (i = 0; i < list->n; i++) { + igraph_vector_ptr_destroy(&list->e[i].neis); + } + igraph_Free(list->e); + if (list->heapindex != 0) { + igraph_Free(list->heapindex); + } + if (list->heap != 0) { + igraph_Free(list->heap); + } +} + +/* Community list heap maintenance: sift down */ +static void igraph_i_fastgreedy_community_list_sift_down( + igraph_i_fastgreedy_community_list* list, long int idx) { + long int root, child, c1, c2; + igraph_i_fastgreedy_community* dummy; + igraph_integer_t dummy2; + igraph_i_fastgreedy_community** heap = list->heap; + igraph_integer_t* heapindex = list->heapindex; + + root = idx; + while (root * 2 + 1 < list->no_of_communities) { + child = root * 2 + 1; + if (child + 1 < list->no_of_communities && + *heap[child]->maxdq->dq < *heap[child + 1]->maxdq->dq) { + child++; + } + if (*heap[root]->maxdq->dq < *heap[child]->maxdq->dq) { + c1 = heap[root]->maxdq->first; + c2 = heap[child]->maxdq->first; + + dummy = heap[root]; + heap[root] = heap[child]; + heap[child] = dummy; + + dummy2 = heapindex[c1]; + heapindex[c1] = heapindex[c2]; + heapindex[c2] = dummy2; + + root = child; + } else { + break; + } + } +} + +/* Community list heap maintenance: sift up */ +static void igraph_i_fastgreedy_community_list_sift_up( + igraph_i_fastgreedy_community_list* list, long int idx) { + long int root, parent, c1, c2; + igraph_i_fastgreedy_community* dummy; + igraph_integer_t dummy2; + igraph_i_fastgreedy_community** heap = list->heap; + igraph_integer_t* heapindex = list->heapindex; + + root = idx; + while (root > 0) { + parent = (root - 1) / 2; + if (*heap[parent]->maxdq->dq < *heap[root]->maxdq->dq) { + c1 = heap[root]->maxdq->first; + c2 = heap[parent]->maxdq->first; + + dummy = heap[parent]; + heap[parent] = heap[root]; + heap[root] = dummy; + + dummy2 = heapindex[c1]; + heapindex[c1] = heapindex[c2]; + heapindex[c2] = dummy2; + + root = parent; + } else { + break; + } + } +} + +/* Builds the community heap for the first time */ +static void igraph_i_fastgreedy_community_list_build_heap( + igraph_i_fastgreedy_community_list* list) { + long int i; + for (i = list->no_of_communities / 2 - 1; i >= 0; i--) { + igraph_i_fastgreedy_community_list_sift_down(list, i); + } +} + +/* Finds the element belonging to a given community in the heap and return its + * index in the heap array */ +#define igraph_i_fastgreedy_community_list_find_in_heap(list, idx) (list)->heapindex[idx] + +/* Dumps the heap - for debugging purposes */ +static void igraph_i_fastgreedy_community_list_dump_heap( + igraph_i_fastgreedy_community_list* list) { + long int i; + debug("Heap:\n"); + for (i = 0; i < list->no_of_communities; i++) { + debug("(%ld, %p, %p)", i, list->heap[i], + list->heap[i]->maxdq); + if (list->heap[i]->maxdq) { + debug(" (%ld, %ld, %.7f)", list->heap[i]->maxdq->first, + list->heap[i]->maxdq->second, *list->heap[i]->maxdq->dq); + } + debug("\n"); + } + debug("Heap index:\n"); + for (i = 0; i < list->no_of_communities; i++) { + debug("%ld ", (long)list->heapindex[i]); + } + debug("\nEND\n"); +} + +/* Checks if the community heap satisfies the heap property. + * Only useful for debugging. */ +static void igraph_i_fastgreedy_community_list_check_heap( + igraph_i_fastgreedy_community_list* list) { + long int i; + for (i = 0; i < list->no_of_communities / 2; i++) { + if ((2 * i + 1 < list->no_of_communities && *list->heap[i]->maxdq->dq < *list->heap[2 * i + 1]->maxdq->dq) || + (2 * i + 2 < list->no_of_communities && *list->heap[i]->maxdq->dq < *list->heap[2 * i + 2]->maxdq->dq)) { + IGRAPH_WARNING("Heap property violated"); + debug("Position: %ld, %ld and %ld\n", i, 2 * i + 1, 2 * i + 2); + igraph_i_fastgreedy_community_list_dump_heap(list); + } + } +} + +/* Removes a given element from the heap */ +static void igraph_i_fastgreedy_community_list_remove( + igraph_i_fastgreedy_community_list* list, long int idx) { + igraph_real_t old; + long int commidx; + + /* First adjust the index */ + commidx = list->heap[list->no_of_communities - 1]->maxdq->first; + list->heapindex[commidx] = (igraph_integer_t) idx; + commidx = list->heap[idx]->maxdq->first; + list->heapindex[commidx] = -1; + + /* Now remove the element */ + old = *list->heap[idx]->maxdq->dq; + list->heap[idx] = list->heap[list->no_of_communities - 1]; + list->no_of_communities--; + + /* Recover heap property */ + if (old > *list->heap[idx]->maxdq->dq) { + igraph_i_fastgreedy_community_list_sift_down(list, idx); + } else { + igraph_i_fastgreedy_community_list_sift_up(list, idx); + } +} + +/* Removes a given element from the heap when there are no more neighbors + * for it (comm->maxdq is NULL) */ +static void igraph_i_fastgreedy_community_list_remove2( + igraph_i_fastgreedy_community_list* list, long int idx, long int comm) { + long int i; + + if (idx == list->no_of_communities - 1) { + /* We removed the rightmost element on the bottom level, no problem, + * there's nothing to be done */ + list->heapindex[comm] = -1; + list->no_of_communities--; + return; + } + + /* First adjust the index */ + i = list->heap[list->no_of_communities - 1]->maxdq->first; + list->heapindex[i] = (igraph_integer_t) idx; + list->heapindex[comm] = -1; + + /* Now remove the element */ + list->heap[idx] = list->heap[list->no_of_communities - 1]; + list->no_of_communities--; + + /* Recover heap property */ + for (i = list->no_of_communities / 2 - 1; i >= 0; i--) { + igraph_i_fastgreedy_community_list_sift_down(list, i); + } +} + +/* Removes the pair belonging to community k from the neighborhood list + * of community c (that is, clist[c]) and recalculates maxdq */ +static void igraph_i_fastgreedy_community_remove_nei( + igraph_i_fastgreedy_community_list* list, long int c, long int k) { + long int i, n; + igraph_bool_t rescan = 0; + igraph_i_fastgreedy_commpair *p; + igraph_i_fastgreedy_community *comm; + igraph_real_t olddq; + + comm = &list->e[c]; + n = igraph_vector_ptr_size(&comm->neis); + for (i = 0; i < n; i++) { + p = (igraph_i_fastgreedy_commpair*)VECTOR(comm->neis)[i]; + if (p->second == k) { + /* Check current maxdq */ + if (comm->maxdq == p) { + rescan = 1; + } + break; + } + } + if (i < n) { + olddq = *comm->maxdq->dq; + igraph_vector_ptr_remove(&comm->neis, i); + if (rescan) { + igraph_i_fastgreedy_community_rescan_max(comm); + i = igraph_i_fastgreedy_community_list_find_in_heap(list, c); + if (comm->maxdq) { + if (*comm->maxdq->dq > olddq) { + igraph_i_fastgreedy_community_list_sift_up(list, i); + } else { + igraph_i_fastgreedy_community_list_sift_down(list, i); + } + } else { + /* no more neighbors for this community. we should remove this + * community from the heap and restore the heap property */ + debug("REMOVING (NO MORE NEIS): %ld\n", i); + igraph_i_fastgreedy_community_list_remove2(list, i, c); + } + } + } +} + +/* Auxiliary function to sort a community pair list with respect to the + * `second` field */ +static int igraph_i_fastgreedy_commpair_cmp(const void* p1, const void* p2) { + igraph_i_fastgreedy_commpair *cp1, *cp2; + cp1 = *(igraph_i_fastgreedy_commpair**)p1; + cp2 = *(igraph_i_fastgreedy_commpair**)p2; + return (int) (cp1->second - cp2->second); +} + +/* Sorts the neighbor list of the community with the given index, optionally + * optimizing the process if we know that the list is nearly sorted and only + * a given pair is in the wrong place. */ +static void igraph_i_fastgreedy_community_sort_neighbors_of( + igraph_i_fastgreedy_community_list* list, long int index, + igraph_i_fastgreedy_commpair* changed_pair) { + igraph_vector_ptr_t* vec; + long int i, n; + igraph_bool_t can_skip_sort = 0; + igraph_i_fastgreedy_commpair *other_pair; + + vec = &list->e[index].neis; + if (changed_pair != 0) { + /* Optimized sorting */ + + /* First we look for changed_pair in vec */ + n = igraph_vector_ptr_size(vec); + for (i = 0; i < n; i++) { + if (VECTOR(*vec)[i] == changed_pair) { + break; + } + } + + /* Did we find it? We should have -- otherwise it's a bug */ + if (i >= n) { + IGRAPH_WARNING("changed_pair not found in neighbor vector while re-sorting " + "the neighbors of a community; this is probably a bug. Falling back to " + "full sort instead." + ); + } else { + /* Okay, the pair that changed is at index i. We need to figure out where + * its new place should be. We can simply try moving the item all the way + * to the left as long as the comparison function tells so (since the + * rest of the vector is sorted), and then move all the way to the right + * as long as the comparison function tells so, and we will be okay. */ + + /* Shifting to the left */ + while (i > 0) { + other_pair = VECTOR(*vec)[i - 1]; + if (other_pair->second > changed_pair->second) { + VECTOR(*vec)[i] = other_pair; + i--; + } else { + break; + } + } + VECTOR(*vec)[i] = changed_pair; + + /* Shifting to the right */ + while (i < n - 1) { + other_pair = VECTOR(*vec)[i + 1]; + if (other_pair->second < changed_pair->second) { + VECTOR(*vec)[i] = other_pair; + i++; + } else { + break; + } + } + VECTOR(*vec)[i] = changed_pair; + + /* Mark that we don't need a full sort */ + can_skip_sort = 1; + } + } + + if (!can_skip_sort) { + /* Fallback to full sorting */ + igraph_vector_ptr_sort(vec, igraph_i_fastgreedy_commpair_cmp); + } +} + +/* Updates the dq value of community pair p in the community with index p->first + * of the community list clist to newdq and restores the heap property + * in community c if necessary. Returns 1 if the maximum in the row had + * to be updated, zero otherwise */ +static int igraph_i_fastgreedy_community_update_dq( + igraph_i_fastgreedy_community_list* list, + igraph_i_fastgreedy_commpair* p, igraph_real_t newdq) { + long int i, j, to, from; + igraph_real_t olddq; + igraph_i_fastgreedy_community *comm_to, *comm_from; + to = p->first; from = p->second; + comm_to = &list->e[to]; + comm_from = &list->e[from]; + if (comm_to->maxdq == p && newdq >= *p->dq) { + /* If we are adjusting the current maximum and it is increased, we don't + * have to re-scan for the new maximum */ + *p->dq = newdq; + /* The maximum was increased, so perform a sift-up in the heap */ + i = igraph_i_fastgreedy_community_list_find_in_heap(list, to); + igraph_i_fastgreedy_community_list_sift_up(list, i); + /* Let's check the opposite side. If the pair was not the maximal in + * the opposite side (the other community list)... */ + if (comm_from->maxdq != p->opposite) { + if (*comm_from->maxdq->dq < newdq) { + /* ...and it will become the maximal, we need to adjust and sift up */ + comm_from->maxdq = p->opposite; + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_up(list, j); + } else { + /* The pair was not the maximal in the opposite side and it will + * NOT become the maximal, there's nothing to do there */ + } + } else { + /* The pair was maximal in the opposite side, so we need to sift it up + * with the new value */ + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_up(list, j); + } + return 1; + } else if (comm_to->maxdq != p && (newdq <= *comm_to->maxdq->dq)) { + /* If we are modifying an item which is not the current maximum, and the + * new value is less than the current maximum, we don't + * have to re-scan for the new maximum */ + olddq = *p->dq; + *p->dq = newdq; + /* However, if the item was the maximum on the opposite side, we'd better + * re-scan it */ + if (comm_from->maxdq == p->opposite) { + if (olddq > newdq) { + /* Decreased the maximum on the other side, we have to re-scan for the + * new maximum */ + igraph_i_fastgreedy_community_rescan_max(comm_from); + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_down(list, j); + } else { + /* Increased the maximum on the other side, we don't have to re-scan + * but we might have to sift up */ + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_up(list, j); + } + } + return 0; + } else { + /* We got here in two cases: + (1) the pair we are modifying right now is the maximum in the given + community and we are decreasing it + (2) the pair we are modifying right now is NOT the maximum in the + given community, but we increase it so much that it will become + the new maximum + */ + *p->dq = newdq; + if (comm_to->maxdq != p) { + /* case (2) */ + comm_to->maxdq = p; + /* The maximum was increased, so perform a sift-up in the heap */ + i = igraph_i_fastgreedy_community_list_find_in_heap(list, to); + igraph_i_fastgreedy_community_list_sift_up(list, i); + /* Opposite side. Chances are that the new value became the maximum + * in the opposite side, but check it first */ + if (comm_from->maxdq != p->opposite) { + if (*comm_from->maxdq->dq < newdq) { + /* Yes, it will become the new maximum */ + comm_from->maxdq = p->opposite; + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_up(list, j); + } else { + /* No, nothing to do there */ + } + } else { + /* Already increased the maximum on the opposite side, so sift it up */ + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_up(list, j); + } + } else { + /* case (1) */ + /* This is the worst, we have to re-scan the whole community to find + * the new maximum and update the global maximum as well if necessary */ + igraph_i_fastgreedy_community_rescan_max(comm_to); + /* The maximum was decreased, so perform a sift-down in the heap */ + i = igraph_i_fastgreedy_community_list_find_in_heap(list, to); + igraph_i_fastgreedy_community_list_sift_down(list, i); + if (comm_from->maxdq != p->opposite) { + /* The one that we decreased on the opposite side is not the + * maximal one. Nothing to do. */ + } else { + /* We decreased the maximal on the opposite side as well. Re-scan + * and sift down */ + igraph_i_fastgreedy_community_rescan_max(comm_from); + j = igraph_i_fastgreedy_community_list_find_in_heap(list, from); + igraph_i_fastgreedy_community_list_sift_down(list, j); + } + } + } + return 1; +} + +/** + * \function igraph_community_fastgreedy + * \brief Finding community structure by greedy optimization of modularity + * + * This function implements the fast greedy modularity optimization + * algorithm for finding community structure, see + * A Clauset, MEJ Newman, C Moore: Finding community structure in very + * large networks, http://www.arxiv.org/abs/cond-mat/0408187 for the + * details. + * + * + * Some improvements proposed in K Wakita, T Tsurumi: Finding community + * structure in mega-scale social networks, + * http://www.arxiv.org/abs/cs.CY/0702048v1 have also been implemented. + * + * \param graph The input graph. It must be a graph without multiple edges. + * This is checked and an error message is given for graphs with multiple + * edges. + * \param weights Potentially a numeric vector containing edge + * weights. Supply a null pointer here for unweighted graphs. The + * weights are expected to be non-negative. + * \param merges Pointer to an initialized matrix or NULL, the result of the + * computation is stored here. The matrix has two columns and each + * merge corresponds to one merge, the ids of the two merged + * components are stored. The component ids are numbered from zero and + * the first \c n components are the individual vertices, \c n is + * the number of vertices in the graph. Component \c n is created + * in the first merge, component \c n+1 in the second merge, etc. + * The matrix will be resized as needed. If this argument is NULL + * then it is ignored completely. + * \param modularity Pointer to an initialized vector or NULL pointer, + * in the former case the modularity scores along the stages of the + * computation are recorded here. The vector will be resized as + * needed. + * \param membership Pointer to a vector. If not a null pointer, then + * the membership vector corresponding to the best split (in terms + * of modularity) is stored here. + * \return Error code. + * + * \sa \ref igraph_community_walktrap(), \ref + * igraph_community_edge_betweenness() for other community detection + * algorithms, \ref igraph_community_to_membership() to convert the + * dendrogram to a membership vector. + * + * Time complexity: O(|E||V|log|V|) in the worst case, + * O(|E|+|V|log^2|V|) typically, |V| is the number of vertices, |E| is + * the number of edges. + * + * \example examples/simple/igraph_community_fastgreedy.c + */ +int igraph_community_fastgreedy(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_matrix_t *merges, + igraph_vector_t *modularity, + igraph_vector_t *membership) { + long int no_of_edges, no_of_nodes, no_of_joins, total_joins; + long int i, j, k, n, m, from, to, dummy, best_no_of_joins; + igraph_integer_t ffrom, fto; + igraph_eit_t edgeit; + igraph_i_fastgreedy_commpair *pairs, *p1, *p2; + igraph_i_fastgreedy_community_list communities; + igraph_vector_t a; + igraph_real_t q, *dq, bestq, weight_sum, loop_weight_sum; + igraph_bool_t has_multiple; + igraph_matrix_t merges_local; + + /*long int join_order[] = { 16,5, 5,6, 6,0, 4,0, 10,0, 26,29, 29,33, 23,33, 27,33, 25,24, 24,31, 12,3, 21,1, 30,8, 8,32, 9,2, 17,1, 11,0, 7,3, 3,2, 13,2, 1,2, 28,31, 31,33, 22,32, 18,32, 20,32, 32,33, 15,33, 14,33, 0,19, 19,2, -1,-1 };*/ + /*long int join_order[] = { 43,42, 42,41, 44,41, 41,36, 35,36, 37,36, 36,29, 38,29, 34,29, 39,29, 33,29, 40,29, 32,29, 14,29, 30,29, 31,29, 6,18, 18,4, 23,4, 21,4, 19,4, 27,4, 20,4, 22,4, 26,4, 25,4, 24,4, 17,4, 0,13, 13,2, 1,2, 11,2, 8,2, 5,2, 3,2, 10,2, 9,2, 7,2, 2,28, 28,15, 12,15, 29,16, 4,15, -1,-1 };*/ + + no_of_nodes = igraph_vcount(graph); + no_of_edges = igraph_ecount(graph); + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("fast greedy community detection works for undirected graphs only", IGRAPH_UNIMPLEMENTED); + } + + total_joins = no_of_nodes - 1; + + if (weights != 0) { + if (igraph_vector_size(weights) < igraph_ecount(graph)) { + IGRAPH_ERROR("fast greedy community detection: weight vector too short", IGRAPH_EINVAL); + } + if (igraph_vector_any_smaller(weights, 0)) { + IGRAPH_ERROR("weights must be positive", IGRAPH_EINVAL); + } + weight_sum = igraph_vector_sum(weights); + } else { + weight_sum = no_of_edges; + } + + IGRAPH_CHECK(igraph_has_multiple(graph, &has_multiple)); + if (has_multiple) { + IGRAPH_ERROR("fast-greedy community finding works only on graphs without multiple edges", IGRAPH_EINVAL); + } + + if (membership != 0 && merges == 0) { + /* We need the merge matrix because the user wants the membership + * vector, so we allocate one on our own */ + IGRAPH_CHECK(igraph_matrix_init(&merges_local, total_joins, 2)); + IGRAPH_FINALLY(igraph_matrix_destroy, &merges_local); + merges = &merges_local; + } + + if (merges != 0) { + IGRAPH_CHECK(igraph_matrix_resize(merges, total_joins, 2)); + igraph_matrix_null(merges); + } + + if (modularity != 0) { + IGRAPH_CHECK(igraph_vector_resize(modularity, total_joins + 1)); + } + + /* Create degree vector */ + IGRAPH_VECTOR_INIT_FINALLY(&a, no_of_nodes); + if (weights) { + debug("Calculating weighted degrees\n"); + for (i = 0; i < no_of_edges; i++) { + VECTOR(a)[(long int)IGRAPH_FROM(graph, i)] += VECTOR(*weights)[i]; + VECTOR(a)[(long int)IGRAPH_TO(graph, i)] += VECTOR(*weights)[i]; + } + } else { + debug("Calculating degrees\n"); + IGRAPH_CHECK(igraph_degree(graph, &a, igraph_vss_all(), IGRAPH_ALL, 1)); + } + + /* Create list of communities */ + debug("Creating community list\n"); + communities.n = no_of_nodes; + communities.no_of_communities = no_of_nodes; + communities.e = (igraph_i_fastgreedy_community*)calloc((size_t) no_of_nodes, sizeof(igraph_i_fastgreedy_community)); + if (communities.e == 0) { + IGRAPH_ERROR("can't run fast greedy community detection", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, communities.e); + communities.heap = (igraph_i_fastgreedy_community**)calloc((size_t) no_of_nodes, sizeof(igraph_i_fastgreedy_community*)); + if (communities.heap == 0) { + IGRAPH_ERROR("can't run fast greedy community detection", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, communities.heap); + communities.heapindex = (igraph_integer_t*)calloc((size_t)no_of_nodes, sizeof(igraph_integer_t)); + if (communities.heapindex == 0) { + IGRAPH_ERROR("can't run fast greedy community detection", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_FINALLY(igraph_i_fastgreedy_community_list_destroy, &communities); + for (i = 0; i < no_of_nodes; i++) { + igraph_vector_ptr_init(&communities.e[i].neis, 0); + communities.e[i].id = (igraph_integer_t) i; + communities.e[i].size = 1; + } + + /* Create list of community pairs from edges */ + debug("Allocating dq vector\n"); + dq = (igraph_real_t*)calloc((size_t) no_of_edges, sizeof(igraph_real_t)); + if (dq == 0) { + IGRAPH_ERROR("can't run fast greedy community detection", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, dq); + debug("Creating community pair list\n"); + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(0), &edgeit)); + IGRAPH_FINALLY(igraph_eit_destroy, &edgeit); + pairs = (igraph_i_fastgreedy_commpair*)calloc(2 * (size_t) no_of_edges, sizeof(igraph_i_fastgreedy_commpair)); + if (pairs == 0) { + IGRAPH_ERROR("can't run fast greedy community detection", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, pairs); + loop_weight_sum = 0; + for (i = 0, j = 0; !IGRAPH_EIT_END(edgeit); i += 2, j++, IGRAPH_EIT_NEXT(edgeit)) { + long int eidx = IGRAPH_EIT_GET(edgeit); + igraph_edge(graph, (igraph_integer_t) eidx, &ffrom, &fto); + + /* Create the pairs themselves */ + from = (long int)ffrom; to = (long int)fto; + if (from == to) { + loop_weight_sum += weights ? 2 * VECTOR(*weights)[eidx] : 2; + continue; + } + + if (from > to) { + dummy = from; from = to; to = dummy; + } + if (weights) { + dq[j] = 2 * (VECTOR(*weights)[eidx] / (weight_sum * 2.0) - VECTOR(a)[from] * VECTOR(a)[to] / (4.0 * weight_sum * weight_sum)); + } else { + dq[j] = 2 * (1.0 / (no_of_edges * 2.0) - VECTOR(a)[from] * VECTOR(a)[to] / (4.0 * no_of_edges * no_of_edges)); + } + pairs[i].first = from; + pairs[i].second = to; + pairs[i].dq = &dq[j]; + pairs[i].opposite = &pairs[i + 1]; + pairs[i + 1].first = to; + pairs[i + 1].second = from; + pairs[i + 1].dq = pairs[i].dq; + pairs[i + 1].opposite = &pairs[i]; + /* Link the pair to the communities */ + igraph_vector_ptr_push_back(&communities.e[from].neis, &pairs[i]); + igraph_vector_ptr_push_back(&communities.e[to].neis, &pairs[i + 1]); + /* Update maximums */ + if (communities.e[from].maxdq == 0 || *communities.e[from].maxdq->dq < *pairs[i].dq) { + communities.e[from].maxdq = &pairs[i]; + } + if (communities.e[to].maxdq == 0 || *communities.e[to].maxdq->dq < *pairs[i + 1].dq) { + communities.e[to].maxdq = &pairs[i + 1]; + } + } + igraph_eit_destroy(&edgeit); + IGRAPH_FINALLY_CLEAN(1); + + /* Sorting community neighbor lists by community IDs */ + debug("Sorting community neighbor lists\n"); + for (i = 0, j = 0; i < no_of_nodes; i++) { + igraph_i_fastgreedy_community_sort_neighbors_of(&communities, i, 0); + /* Isolated vertices and vertices with loop edges only won't be stored in + * the heap (to avoid maxdq == 0) */ + if (communities.e[i].maxdq != 0) { + communities.heap[j] = &communities.e[i]; + communities.heapindex[i] = (igraph_integer_t) j; + j++; + } else { + communities.heapindex[i] = -1; + } + } + communities.no_of_communities = j; + + /* Calculate proper vector a (see paper) and initial modularity */ + q = 2.0 * (weights ? weight_sum : no_of_edges); + if (q == 0) { + /* All the weights are zero */ + } else { + igraph_vector_scale(&a, 1.0 / q); + q = loop_weight_sum / q; + for (i = 0; i < no_of_nodes; i++) { + q -= VECTOR(a)[i] * VECTOR(a)[i]; + } + } + + /* Initialize "best modularity" value and best merge counter */ + bestq = q; + best_no_of_joins = 0; + + /* Initializing community heap */ + debug("Initializing community heap\n"); + igraph_i_fastgreedy_community_list_build_heap(&communities); + + debug("Initial modularity: %.4f\n", q); + + /* Let's rock ;) */ + no_of_joins = 0; + while (no_of_joins < total_joins) { + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_PROGRESS("fast greedy community detection", no_of_joins * 100.0 / total_joins, 0); + + /* Store the modularity */ + if (modularity) { + VECTOR(*modularity)[no_of_joins] = q; + } + + /* Update best modularity if needed */ + if (q >= bestq) { + bestq = q; + best_no_of_joins = no_of_joins; + } + + /* Some debug info if needed */ + /* igraph_i_fastgreedy_community_list_check_heap(&communities); */ +#ifdef DEBUG + debug("===========================================\n"); + for (i = 0; i < communities.n; i++) { + if (communities.e[i].maxdq == 0) { + debug("Community #%ld: PASSIVE\n", i); + continue; + } + debug("Community #%ld\n ", i); + for (j = 0; j < igraph_vector_ptr_size(&communities.e[i].neis); j++) { + p1 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[i].neis)[j]; + debug(" (%ld,%ld,%.4f)", p1->first, p1->second, *p1->dq); + } + p1 = communities.e[i].maxdq; + debug("\n Maxdq: (%ld,%ld,%.4f)\n", p1->first, p1->second, *p1->dq); + } + debug("Global maxdq is: (%ld,%ld,%.4f)\n", communities.heap[0]->maxdq->first, + communities.heap[0]->maxdq->second, *communities.heap[0]->maxdq->dq); + for (i = 0; i < communities.no_of_communities; i++) { + debug("(%ld,%ld,%.4f) ", communities.heap[i]->maxdq->first, communities.heap[i]->maxdq->second, *communities.heap[0]->maxdq->dq); + } + debug("\n"); +#endif + if (communities.heap[0] == 0) { + break; /* no more communities */ + } + if (communities.heap[0]->maxdq == 0) { + break; /* there are only isolated comms */ + } + to = communities.heap[0]->maxdq->second; + from = communities.heap[0]->maxdq->first; + + debug("Q[%ld] = %.7f\tdQ = %.7f\t |H| = %ld\n", + no_of_joins, q, *communities.heap[0]->maxdq->dq, no_of_nodes - no_of_joins - 1); + + /* DEBUG */ + /* from=join_order[no_of_joins*2]; to=join_order[no_of_joins*2+1]; + if (to == -1) break; + for (i=0; isecond == from) communities.maxdq = p1; + } */ + + n = igraph_vector_ptr_size(&communities.e[to].neis); + m = igraph_vector_ptr_size(&communities.e[from].neis); + /*if (n>m) { + dummy=n; n=m; m=dummy; + dummy=to; to=from; from=dummy; + }*/ + debug(" joining: %ld <- %ld\n", to, from); + q += *communities.heap[0]->maxdq->dq; + + /* Merge the second community into the first */ + i = j = 0; + while (i < n && j < m) { + p1 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[to].neis)[i]; + p2 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[from].neis)[j]; + debug("Pairs: %ld-%ld and %ld-%ld\n", p1->first, p1->second, + p2->first, p2->second); + if (p1->second < p2->second) { + /* Considering p1 from now on */ + debug(" Considering: %ld-%ld\n", p1->first, p1->second); + if (p1->second == from) { + debug(" WILL REMOVE: %ld-%ld\n", to, from); + } else { + /* chain, case 1 */ + debug(" CHAIN(1): %ld-%ld %ld, now=%.7f, adding=%.7f, newdq(%ld,%ld)=%.7f\n", + to, p1->second, from, *p1->dq, -2 * VECTOR(a)[from]*VECTOR(a)[p1->second], p1->first, p1->second, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]); + igraph_i_fastgreedy_community_update_dq(&communities, p1, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]); + } + i++; + } else if (p1->second == p2->second) { + /* p1->first, p1->second and p2->first form a triangle */ + debug(" Considering: %ld-%ld and %ld-%ld\n", p1->first, p1->second, + p2->first, p2->second); + /* Update dq value */ + debug(" TRIANGLE: %ld-%ld-%ld, now=%.7f, adding=%.7f, newdq(%ld,%ld)=%.7f\n", + to, p1->second, from, *p1->dq, *p2->dq, p1->first, p1->second, *p1->dq + *p2->dq); + igraph_i_fastgreedy_community_update_dq(&communities, p1, *p1->dq + *p2->dq); + igraph_i_fastgreedy_community_remove_nei(&communities, p1->second, from); + i++; + j++; + } else { + debug(" Considering: %ld-%ld\n", p2->first, p2->second); + if (p2->second == to) { + debug(" WILL REMOVE: %ld-%ld\n", p2->second, p2->first); + } else { + /* chain, case 2 */ + debug(" CHAIN(2): %ld %ld-%ld, newdq(%ld,%ld)=%.7f\n", + to, p2->second, from, to, p2->second, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]); + p2->opposite->second = to; + /* p2->opposite->second changed, so it means that + * communities.e[p2->second].neis (which contains p2->opposite) is + * not sorted any more. We have to find the index of p2->opposite in + * this vector and move it to the correct place. Moving should be an + * O(n) operation; re-sorting would be O(n*logn) or even worse, + * depending on the pivoting strategy used by qsort() since the + * vector is nearly sorted */ + igraph_i_fastgreedy_community_sort_neighbors_of( + &communities, p2->second, p2->opposite); + /* link from.neis[j] to the current place in to.neis if + * from.neis[j] != to */ + p2->first = to; + IGRAPH_CHECK(igraph_vector_ptr_insert(&communities.e[to].neis, i, p2)); + n++; i++; + if (*p2->dq > *communities.e[to].maxdq->dq) { + communities.e[to].maxdq = p2; + k = igraph_i_fastgreedy_community_list_find_in_heap(&communities, to); + igraph_i_fastgreedy_community_list_sift_up(&communities, k); + } + igraph_i_fastgreedy_community_update_dq(&communities, p2, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]); + } + j++; + } + } + + while (i < n) { + p1 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[to].neis)[i]; + if (p1->second == from) { + debug(" WILL REMOVE: %ld-%ld\n", p1->first, from); + } else { + /* chain, case 1 */ + debug(" CHAIN(1): %ld-%ld %ld, now=%.7f, adding=%.7f, newdq(%ld,%ld)=%.7f\n", + to, p1->second, from, *p1->dq, -2 * VECTOR(a)[from]*VECTOR(a)[p1->second], p1->first, p1->second, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]); + igraph_i_fastgreedy_community_update_dq(&communities, p1, *p1->dq - 2 * VECTOR(a)[from]*VECTOR(a)[p1->second]); + } + i++; + } + while (j < m) { + p2 = (igraph_i_fastgreedy_commpair*)VECTOR(communities.e[from].neis)[j]; + if (to == p2->second) { + j++; + continue; + } + /* chain, case 2 */ + debug(" CHAIN(2): %ld %ld-%ld, newdq(%ld,%ld)=%.7f\n", + to, p2->second, from, p1->first, p2->second, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]); + p2->opposite->second = to; + /* need to re-sort community nei list `p2->second` */ + igraph_i_fastgreedy_community_sort_neighbors_of(&communities, p2->second, p2->opposite); + /* link from.neis[j] to the current place in to.neis if + * from.neis[j] != to */ + p2->first = to; + IGRAPH_CHECK(igraph_vector_ptr_push_back(&communities.e[to].neis, p2)); + if (*p2->dq > *communities.e[to].maxdq->dq) { + communities.e[to].maxdq = p2; + k = igraph_i_fastgreedy_community_list_find_in_heap(&communities, to); + igraph_i_fastgreedy_community_list_sift_up(&communities, k); + } + igraph_i_fastgreedy_community_update_dq(&communities, p2, *p2->dq - 2 * VECTOR(a)[to]*VECTOR(a)[p2->second]); + j++; + } + + /* Now, remove community `from` from the neighbors of community `to` */ + if (communities.no_of_communities > 2) { + debug(" REMOVING: %ld-%ld\n", to, from); + igraph_i_fastgreedy_community_remove_nei(&communities, to, from); + i = igraph_i_fastgreedy_community_list_find_in_heap(&communities, from); + igraph_i_fastgreedy_community_list_remove(&communities, i); + } + communities.e[from].maxdq = 0; + + /* Update community sizes */ + communities.e[to].size += communities.e[from].size; + communities.e[from].size = 0; + + /* record what has been merged */ + /* igraph_vector_ptr_clear is not enough here as it won't free + * the memory consumed by communities.e[from].neis. Thanks + * to Tom Gregorovic for pointing that out. */ + igraph_vector_ptr_destroy(&communities.e[from].neis); + if (merges) { + MATRIX(*merges, no_of_joins, 0) = communities.e[to].id; + MATRIX(*merges, no_of_joins, 1) = communities.e[from].id; + communities.e[to].id = (igraph_integer_t) (no_of_nodes + no_of_joins); + } + + /* Update vector a */ + VECTOR(a)[to] += VECTOR(a)[from]; + VECTOR(a)[from] = 0.0; + + no_of_joins++; + } + /* TODO: continue merging when some isolated communities remained. Always + * joining the communities with the least number of nodes results in the + * smallest decrease in modularity every step. Now we're simply deleting + * the excess rows from the merge matrix */ + if (no_of_joins < total_joins) { + long int *ivec; + ivec = igraph_Calloc(igraph_matrix_nrow(merges), long int); + if (ivec == 0) { + IGRAPH_ERROR("can't run fast greedy community detection", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, ivec); + for (i = 0; i < no_of_joins; i++) { + ivec[i] = i + 1; + } + igraph_matrix_permdelete_rows(merges, ivec, total_joins - no_of_joins); + igraph_Free(ivec); + IGRAPH_FINALLY_CLEAN(1); + } + IGRAPH_PROGRESS("fast greedy community detection", 100.0, 0); + + if (modularity) { + VECTOR(*modularity)[no_of_joins] = q; + igraph_vector_resize(modularity, no_of_joins + 1); + } + + debug("Freeing memory\n"); + igraph_Free(pairs); + igraph_Free(dq); + igraph_i_fastgreedy_community_list_destroy(&communities); + igraph_vector_destroy(&a); + IGRAPH_FINALLY_CLEAN(4); + + if (membership) { + IGRAPH_CHECK(igraph_community_to_membership(merges, + (igraph_integer_t) no_of_nodes, + /*steps=*/ (igraph_integer_t) best_no_of_joins, + membership, + /*csize=*/ 0)); + } + + if (merges == &merges_local) { + igraph_matrix_destroy(&merges_local); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +#ifdef IGRAPH_FASTCOMM_DEBUG + #undef IGRAPH_FASTCOMM_DEBUG +#endif + + diff --git a/src/feedback_arc_set.c b/src/feedback_arc_set.c new file mode 100644 index 0000000..c3cff4a --- /dev/null +++ b/src/feedback_arc_set.c @@ -0,0 +1,665 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_centrality.h" +#include "igraph_components.h" +#include "igraph_constants.h" +#include "igraph_datatype.h" +#include "igraph_dqueue.h" +#include "igraph_error.h" +#include "igraph_glpk_support.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_structural.h" +#include "igraph_types.h" +#include "igraph_visitor.h" + +int igraph_i_feedback_arc_set_ip(const igraph_t *graph, igraph_vector_t *result, + const igraph_vector_t *weights); + + +/** + * \ingroup structural + * \function igraph_feedback_arc_set + * \brief Calculates a feedback arc set of the graph using different + * algorithms. + * + * + * A feedback arc set is a set of edges whose removal makes the graph acyclic. + * We are usually interested in \em minimum feedback arc sets, i.e. sets of edges + * whose total weight is minimal among all the feedback arc sets. + * + * + * For undirected graphs, the problem is simple: one has to find a maximum weight + * spanning tree and then remove all the edges not in the spanning tree. For directed + * graphs, this is an NP-hard problem, and various heuristics are usually used to + * find an approximate solution to the problem. This function implements a few of + * these heuristics. + * + * \param graph The graph object. + * \param result An initialized vector, the result will be returned here. + * \param weights Weight vector or NULL if no weights are specified. + * \param algo The algorithm to use to solve the problem if the graph is directed. + * Possible values: + * \clist + * \cli IGRAPH_FAS_EXACT_IP + * Finds a \em minimum feedback arc set using integer programming (IP). + * The complexity of this algorithm is exponential of course. + * \cli IGRAPH_FAS_APPROX_EADES + * Finds a feedback arc set using the heuristic of Eades, Lin and + * Smyth (1993). This is guaranteed to be smaller than |E|/2 - |V|/6, + * and it is linear in the number of edges (i.e. O(|E|)). + * For more details, see Eades P, Lin X and Smyth WF: A fast and effective + * heuristic for the feedback arc set problem. In: Proc Inf Process Lett + * 319-323, 1993. + * \endclist + * + * \return Error code: + * \c IGRAPH_EINVAL if an unknown method was specified or the weight vector + * is invalid. + * + * \example examples/simple/igraph_feedback_arc_set.c + * \example examples/simple/igraph_feedback_arc_set_ip.c + * + * Time complexity: depends on \p algo, see the time complexities there. + */ +int igraph_feedback_arc_set(const igraph_t *graph, igraph_vector_t *result, + const igraph_vector_t *weights, igraph_fas_algorithm_t algo) { + + if (weights && igraph_vector_size(weights) < igraph_ecount(graph)) + IGRAPH_ERROR("cannot calculate feedback arc set, weight vector too short", + IGRAPH_EINVAL); + + if (!igraph_is_directed(graph)) { + return igraph_i_feedback_arc_set_undirected(graph, result, weights, 0); + } + + switch (algo) { + case IGRAPH_FAS_EXACT_IP: + return igraph_i_feedback_arc_set_ip(graph, result, weights); + + case IGRAPH_FAS_APPROX_EADES: + return igraph_i_feedback_arc_set_eades(graph, result, weights, 0); + + default: + IGRAPH_ERROR("Invalid algorithm", IGRAPH_EINVAL); + } +} + +/** + * Solves the feedback arc set problem for undirected graphs. + */ +int igraph_i_feedback_arc_set_undirected(const igraph_t *graph, igraph_vector_t *result, + const igraph_vector_t *weights, igraph_vector_t *layering) { + igraph_vector_t edges; + long int i, j, n, no_of_nodes = igraph_vcount(graph); + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_nodes - 1); + if (weights) { + /* Find a maximum weight spanning tree. igraph has a routine for minimum + * spanning trees, so we negate the weights */ + igraph_vector_t vcopy; + IGRAPH_CHECK(igraph_vector_copy(&vcopy, weights)); + IGRAPH_FINALLY(igraph_vector_destroy, &vcopy); + igraph_vector_scale(&vcopy, -1); + IGRAPH_CHECK(igraph_minimum_spanning_tree(graph, &edges, &vcopy)); + igraph_vector_destroy(&vcopy); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Any spanning tree will do */ + IGRAPH_CHECK(igraph_minimum_spanning_tree(graph, &edges, 0)); + } + + /* Now we have a bunch of edges that constitute a spanning forest. We have + * to come up with a layering, and return those edges that are not in the + * spanning forest */ + igraph_vector_sort(&edges); + IGRAPH_CHECK(igraph_vector_push_back(&edges, -1)); /* guard element */ + + if (result != 0) { + igraph_vector_clear(result); + n = igraph_ecount(graph); + for (i = 0, j = 0; i < n; i++) { + if (i == VECTOR(edges)[j]) { + j++; + continue; + } + IGRAPH_CHECK(igraph_vector_push_back(result, i)); + } + } + + if (layering != 0) { + igraph_vector_t degrees; + igraph_vector_t roots; + + IGRAPH_VECTOR_INIT_FINALLY(°rees, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&roots, no_of_nodes); + + IGRAPH_CHECK(igraph_strength(graph, °rees, igraph_vss_all(), + IGRAPH_ALL, 0, weights)); + IGRAPH_CHECK((int) igraph_vector_qsort_ind(°rees, &roots, + /* descending = */ 1)); + IGRAPH_CHECK(igraph_bfs(graph, + /* root = */ 0, + /* roots = */ &roots, + /* mode = */ IGRAPH_OUT, + /* unreachable = */ 0, + /* restricted = */ 0, + /* order = */ 0, + /* rank = */ 0, + /* father = */ 0, + /* pred = */ 0, + /* succ = */ 0, + /* dist = */ layering, + /* callback = */ 0, + /* extra = */ 0)); + + igraph_vector_destroy(°rees); + igraph_vector_destroy(&roots); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * Solves the feedback arc set problem using the heuristics of Eades et al. + */ +int igraph_i_feedback_arc_set_eades(const igraph_t *graph, igraph_vector_t *result, + const igraph_vector_t *weights, igraph_vector_t *layers) { + long int i, j, k, v, eid, no_of_nodes = igraph_vcount(graph), nodes_left; + igraph_dqueue_t sources, sinks; + igraph_vector_t neis; + igraph_vector_t indegrees, outdegrees; + igraph_vector_t instrengths, outstrengths; + long int* ordering; + long int order_next_pos = 0, order_next_neg = -1; + igraph_real_t diff, maxdiff; + + ordering = igraph_Calloc(no_of_nodes, long int); + IGRAPH_FINALLY(igraph_free, ordering); + + IGRAPH_VECTOR_INIT_FINALLY(&indegrees, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&outdegrees, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&instrengths, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&outstrengths, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_dqueue_init(&sources, 0)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &sources); + IGRAPH_CHECK(igraph_dqueue_init(&sinks, 0)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &sinks); + + IGRAPH_CHECK(igraph_degree(graph, &indegrees, igraph_vss_all(), IGRAPH_IN, 0)); + IGRAPH_CHECK(igraph_degree(graph, &outdegrees, igraph_vss_all(), IGRAPH_OUT, 0)); + + if (weights) { + IGRAPH_CHECK(igraph_strength(graph, &instrengths, igraph_vss_all(), IGRAPH_IN, 0, weights)); + IGRAPH_CHECK(igraph_strength(graph, &outstrengths, igraph_vss_all(), IGRAPH_OUT, 0, weights)); + } else { + IGRAPH_CHECK(igraph_vector_update(&instrengths, &indegrees)); + IGRAPH_CHECK(igraph_vector_update(&outstrengths, &outdegrees)); + } + + /* Find initial sources and sinks */ + nodes_left = no_of_nodes; + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(indegrees)[i] == 0) { + if (VECTOR(outdegrees)[i] == 0) { + /* Isolated vertex, we simply ignore it */ + nodes_left--; + ordering[i] = order_next_pos++; + VECTOR(indegrees)[i] = VECTOR(outdegrees)[i] = -1; + } else { + /* This is a source */ + igraph_dqueue_push(&sources, i); + } + } else if (VECTOR(outdegrees)[i] == 0) { + /* This is a sink */ + igraph_dqueue_push(&sinks, i); + } + } + + /* While we have any nodes left... */ + while (nodes_left > 0) { + /* (1) Remove the sources one by one */ + while (!igraph_dqueue_empty(&sources)) { + i = (long)igraph_dqueue_pop(&sources); + /* Add the node to the ordering */ + ordering[i] = order_next_pos++; + /* Exclude the node from further searches */ + VECTOR(indegrees)[i] = VECTOR(outdegrees)[i] = -1; + /* Get the neighbors and decrease their degrees */ + IGRAPH_CHECK(igraph_incident(graph, &neis, (igraph_integer_t) i, + IGRAPH_OUT)); + j = igraph_vector_size(&neis); + for (i = 0; i < j; i++) { + eid = (long int) VECTOR(neis)[i]; + k = IGRAPH_TO(graph, eid); + if (VECTOR(indegrees)[k] <= 0) { + /* Already removed, continue */ + continue; + } + VECTOR(indegrees)[k]--; + VECTOR(instrengths)[k] -= (weights ? VECTOR(*weights)[eid] : 1.0); + if (VECTOR(indegrees)[k] == 0) { + IGRAPH_CHECK(igraph_dqueue_push(&sources, k)); + } + } + nodes_left--; + } + + /* (2) Remove the sinks one by one */ + while (!igraph_dqueue_empty(&sinks)) { + i = (long)igraph_dqueue_pop(&sinks); + /* Maybe the vertex became sink and source at the same time, hence it + * was already removed in the previous iteration. Check it. */ + if (VECTOR(indegrees)[i] < 0) { + continue; + } + /* Add the node to the ordering */ + ordering[i] = order_next_neg--; + /* Exclude the node from further searches */ + VECTOR(indegrees)[i] = VECTOR(outdegrees)[i] = -1; + /* Get the neighbors and decrease their degrees */ + IGRAPH_CHECK(igraph_incident(graph, &neis, (igraph_integer_t) i, + IGRAPH_IN)); + j = igraph_vector_size(&neis); + for (i = 0; i < j; i++) { + eid = (long int) VECTOR(neis)[i]; + k = IGRAPH_FROM(graph, eid); + if (VECTOR(outdegrees)[k] <= 0) { + /* Already removed, continue */ + continue; + } + VECTOR(outdegrees)[k]--; + VECTOR(outstrengths)[k] -= (weights ? VECTOR(*weights)[eid] : 1.0); + if (VECTOR(outdegrees)[k] == 0) { + IGRAPH_CHECK(igraph_dqueue_push(&sinks, k)); + } + } + nodes_left--; + } + + /* (3) No more sources or sinks. Find the node with the largest + * difference between its out-strength and in-strength */ + v = -1; maxdiff = -IGRAPH_INFINITY; + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(outdegrees)[i] < 0) { + continue; + } + diff = VECTOR(outstrengths)[i] - VECTOR(instrengths)[i]; + if (diff > maxdiff) { + maxdiff = diff; + v = i; + } + } + if (v >= 0) { + /* Remove vertex v */ + ordering[v] = order_next_pos++; + /* Remove outgoing edges */ + IGRAPH_CHECK(igraph_incident(graph, &neis, (igraph_integer_t) v, + IGRAPH_OUT)); + j = igraph_vector_size(&neis); + for (i = 0; i < j; i++) { + eid = (long int) VECTOR(neis)[i]; + k = IGRAPH_TO(graph, eid); + if (VECTOR(indegrees)[k] <= 0) { + /* Already removed, continue */ + continue; + } + VECTOR(indegrees)[k]--; + VECTOR(instrengths)[k] -= (weights ? VECTOR(*weights)[eid] : 1.0); + if (VECTOR(indegrees)[k] == 0) { + IGRAPH_CHECK(igraph_dqueue_push(&sources, k)); + } + } + /* Remove incoming edges */ + IGRAPH_CHECK(igraph_incident(graph, &neis, (igraph_integer_t) v, + IGRAPH_IN)); + j = igraph_vector_size(&neis); + for (i = 0; i < j; i++) { + eid = (long int) VECTOR(neis)[i]; + k = IGRAPH_FROM(graph, eid); + if (VECTOR(outdegrees)[k] <= 0) { + /* Already removed, continue */ + continue; + } + VECTOR(outdegrees)[k]--; + VECTOR(outstrengths)[k] -= (weights ? VECTOR(*weights)[eid] : 1.0); + if (VECTOR(outdegrees)[k] == 0 && VECTOR(indegrees)[k] > 0) { + IGRAPH_CHECK(igraph_dqueue_push(&sinks, k)); + } + } + + VECTOR(outdegrees)[v] = -1; + VECTOR(indegrees)[v] = -1; + nodes_left--; + } + } + + igraph_dqueue_destroy(&sinks); + igraph_dqueue_destroy(&sources); + igraph_vector_destroy(&neis); + igraph_vector_destroy(&outstrengths); + igraph_vector_destroy(&instrengths); + igraph_vector_destroy(&outdegrees); + igraph_vector_destroy(&indegrees); + IGRAPH_FINALLY_CLEAN(7); + + /* Tidy up the ordering */ + for (i = 0; i < no_of_nodes; i++) { + if (ordering[i] < 0) { + ordering[i] += no_of_nodes; + } + } + + /* Find the feedback edges based on the ordering */ + if (result != 0) { + igraph_vector_clear(result); + j = igraph_ecount(graph); + for (i = 0; i < j; i++) { + long int from = IGRAPH_FROM(graph, i), to = IGRAPH_TO(graph, i); + if (from == to || ordering[from] > ordering[to]) { + IGRAPH_CHECK(igraph_vector_push_back(result, i)); + } + } + } + + /* If we have also requested a layering, return that as well */ + if (layers != 0) { + igraph_vector_t ranks; + igraph_vector_long_t order_vec; + + IGRAPH_CHECK(igraph_vector_resize(layers, no_of_nodes)); + igraph_vector_null(layers); + + igraph_vector_long_view(&order_vec, ordering, no_of_nodes); + + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INIT_FINALLY(&ranks, 0); + + IGRAPH_CHECK((int) igraph_vector_long_qsort_ind(&order_vec, &ranks, 0)); + + for (i = 0; i < no_of_nodes; i++) { + long int from = (long int) VECTOR(ranks)[i]; + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) from, + IGRAPH_OUT)); + k = igraph_vector_size(&neis); + for (j = 0; j < k; j++) { + long int to = (long int) VECTOR(neis)[j]; + if (from == to) { + continue; + } + if (ordering[from] > ordering[to]) { + continue; + } + if (VECTOR(*layers)[to] < VECTOR(*layers)[from] + 1) { + VECTOR(*layers)[to] = VECTOR(*layers)[from] + 1; + } + } + } + + igraph_vector_destroy(&neis); + igraph_vector_destroy(&ranks); + IGRAPH_FINALLY_CLEAN(2); + } + + /* Free the ordering vector */ + igraph_free(ordering); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * Solves the feedback arc set problem using integer programming. + */ +int igraph_i_feedback_arc_set_ip(const igraph_t *graph, igraph_vector_t *result, + const igraph_vector_t *weights) { +#ifndef HAVE_GLPK + IGRAPH_ERROR("GLPK is not available", IGRAPH_UNIMPLEMENTED); +#else + + igraph_integer_t no_of_components; + igraph_integer_t no_of_vertices = igraph_vcount(graph); + igraph_integer_t no_of_edges = igraph_ecount(graph); + igraph_vector_t membership, ordering, vertex_remapping; + igraph_vector_ptr_t vertices_by_components, edges_by_components; + long int i, j, k, l, m, n, from, to; + igraph_real_t weight; + glp_prob *ip; + glp_iocp parm; + + IGRAPH_VECTOR_INIT_FINALLY(&membership, 0); + IGRAPH_VECTOR_INIT_FINALLY(&ordering, 0); + IGRAPH_VECTOR_INIT_FINALLY(&vertex_remapping, no_of_vertices); + + igraph_vector_clear(result); + + /* Decompose the graph into connected components */ + IGRAPH_CHECK(igraph_clusters(graph, &membership, 0, &no_of_components, + IGRAPH_WEAK)); + + /* Construct vertex and edge lists for each of the components */ + IGRAPH_CHECK(igraph_vector_ptr_init(&vertices_by_components, no_of_components)); + IGRAPH_CHECK(igraph_vector_ptr_init(&edges_by_components, no_of_components)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &vertices_by_components); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &edges_by_components); + for (i = 0; i < no_of_components; i++) { + igraph_vector_t* vptr; + vptr = igraph_Calloc(1, igraph_vector_t); + if (vptr == 0) { + IGRAPH_ERROR("cannot calculate feedback arc set using IP", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, vptr); + IGRAPH_CHECK(igraph_vector_init(vptr, 0)); + IGRAPH_FINALLY_CLEAN(1); + VECTOR(vertices_by_components)[i] = vptr; + } + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&vertices_by_components, igraph_vector_destroy); + for (i = 0; i < no_of_components; i++) { + igraph_vector_t* vptr; + vptr = igraph_Calloc(1, igraph_vector_t); + if (vptr == 0) { + IGRAPH_ERROR("cannot calculate feedback arc set using IP", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, vptr); + IGRAPH_CHECK(igraph_vector_init(vptr, 0)); + IGRAPH_FINALLY_CLEAN(1); + VECTOR(edges_by_components)[i] = vptr; + } + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&edges_by_components, igraph_vector_destroy); + for (i = 0; i < no_of_vertices; i++) { + j = (long int) VECTOR(membership)[i]; + IGRAPH_CHECK(igraph_vector_push_back(VECTOR(vertices_by_components)[j], i)); + } + for (i = 0; i < no_of_edges; i++) { + j = (long int) VECTOR(membership)[(long)IGRAPH_FROM(graph, i)]; + IGRAPH_CHECK(igraph_vector_push_back(VECTOR(edges_by_components)[j], i)); + } + +#define VAR2IDX(i, j) (i*(n-1)+j-(i+1)*i/2) + + /* Configure GLPK */ + glp_term_out(GLP_OFF); + glp_init_iocp(&parm); + parm.br_tech = GLP_BR_DTH; + parm.bt_tech = GLP_BT_BLB; + parm.pp_tech = GLP_PP_ALL; + parm.presolve = GLP_ON; + parm.binarize = GLP_OFF; + parm.cb_func = igraph_i_glpk_interruption_hook; + + /* Solve an IP for feedback arc sets in each of the components */ + for (i = 0; i < no_of_components; i++) { + igraph_vector_t* vertices_in_comp = (igraph_vector_t*)VECTOR(vertices_by_components)[i]; + igraph_vector_t* edges_in_comp = (igraph_vector_t*)VECTOR(edges_by_components)[i]; + + /* + * Let x_ij denote whether layer(i) < layer(j). + * + * The standard formulation of the problem is as follows: + * + * max sum_{i,j} w_ij x_ij + * + * subject to + * + * (1) x_ij + x_ji = 1 (i.e. either layer(i) < layer(j) or layer(i) > layer(j)) + * for all i < j + * (2) x_ij + x_jk + x_ki <= 2 for all i < j, i < k, j != k + * + * Note that x_ij = 1 implies that x_ji = 0 and vice versa; in other words, + * x_ij = 1 - x_ji. Thus, we can get rid of the (1) constraints and half of the + * x_ij variables (where j < i) if we rewrite constraints of type (2) as follows: + * + * (2a) x_ij + x_jk - x_ik <= 1 for all i < j, i < k, j < k + * (2b) x_ij - x_kj - x_ik <= 0 for all i < j, i < k, j > k + * + * The goal function then becomes: + * + * max sum_{i 0) { + glp_add_cols(ip, (int) k); + for (j = 1; j <= k; j++) { + glp_set_col_kind(ip, (int) j, GLP_BV); + } + } + + /* Set up coefficients in the goal function */ + k = igraph_vector_size(edges_in_comp); + for (j = 0; j < k; j++) { + l = (long int) VECTOR(*edges_in_comp)[j]; + from = (long int) VECTOR(vertex_remapping)[(long)IGRAPH_FROM(graph, l)]; + to = (long int) VECTOR(vertex_remapping)[(long)IGRAPH_TO(graph, l)]; + if (from == to) { + continue; + } + + weight = weights ? VECTOR(*weights)[l] : 1; + + if (from < to) { + l = VAR2IDX(from, to); + glp_set_obj_coef(ip, (int) l, glp_get_obj_coef(ip, (int) l) + weight); + } else { + l = VAR2IDX(to, from); + glp_set_obj_coef(ip, (int) l, glp_get_obj_coef(ip, (int) l) - weight); + } + } + + /* Add constraints */ + if (n > 1) { + glp_add_rows(ip, (int)(n * (n - 1) / 2 + n * (n - 1) * (n - 2) / 3)); + m = 1; + for (j = 0; j < n; j++) { + int ind[4]; + double val[4] = {0, 1, 1, -1}; + for (k = j + 1; k < n; k++) { + ind[1] = (int) VAR2IDX(j, k); + /* Type (2a) */ + val[2] = 1; + for (l = k + 1; l < n; l++, m++) { + ind[2] = (int) VAR2IDX(k, l); + ind[3] = (int) VAR2IDX(j, l); + glp_set_row_bnds(ip, (int) m, GLP_UP, 1, 1); + glp_set_mat_row(ip, (int) m, 3, ind, val); + } + /* Type (2b) */ + val[2] = -1; + for (l = j + 1; l < k; l++, m++) { + ind[2] = (int) VAR2IDX(l, k); + ind[3] = (int) VAR2IDX(j, l); + glp_set_row_bnds(ip, (int) m, GLP_UP, 0, 0); + glp_set_mat_row(ip, (int) m, 3, ind, val); + } + } + } + } + + /* Solve the problem */ + IGRAPH_GLPK_CHECK(glp_intopt(ip, &parm), "Feedback arc set using IP failed"); + + /* Find the ordering of the vertices */ + IGRAPH_CHECK(igraph_vector_resize(&ordering, n)); + igraph_vector_null(&ordering); + m = n * (n - 1) / 2; + j = 0; k = 1; + for (l = 1; l <= m; l++) { + /* variable l always corresponds to the (j, k) vertex pair */ + /* printf("(%ld, %ld) = %g\n", i, j, glp_mip_col_val(ip, l)); */ + if (glp_mip_col_val(ip, (int) l) > 0) { + /* j comes earlier in the ordering than k */ + VECTOR(ordering)[j]++; + } else { + /* k comes earlier in the ordering than j */ + VECTOR(ordering)[k]++; + } + k++; + if (k == n) { + j++; k = j + 1; + } + } + + /* Find the feedback edges */ + k = igraph_vector_size(edges_in_comp); + for (j = 0; j < k; j++) { + l = (long int) VECTOR(*edges_in_comp)[j]; + from = (long int) VECTOR(vertex_remapping)[(long)IGRAPH_FROM(graph, l)]; + to = (long int) VECTOR(vertex_remapping)[(long)IGRAPH_TO(graph, l)]; + if (from == to || VECTOR(ordering)[from] < VECTOR(ordering)[to]) { + IGRAPH_CHECK(igraph_vector_push_back(result, l)); + } + } + + /* Clean up */ + glp_delete_prob(ip); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_ptr_destroy_all(&vertices_by_components); + igraph_vector_ptr_destroy_all(&edges_by_components); + igraph_vector_destroy(&vertex_remapping); + igraph_vector_destroy(&ordering); + igraph_vector_destroy(&membership); + IGRAPH_FINALLY_CLEAN(5); + + return IGRAPH_SUCCESS; +#endif +} + diff --git a/src/flow.c b/src/flow.c new file mode 100644 index 0000000..05849b3 --- /dev/null +++ b/src/flow.c @@ -0,0 +1,2529 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_flow.h" +#include "igraph_error.h" +#include "igraph_memory.h" +#include "igraph_constants.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include "igraph_conversion.h" +#include "igraph_constructors.h" +#include "igraph_progress.h" +#include "igraph_structural.h" +#include "igraph_components.h" +#include "igraph_types_internal.h" +#include "igraph_math.h" +#include "igraph_dqueue.h" +#include "igraph_interrupt_internal.h" +#include "igraph_topology.h" +#include "config.h" + + +/* + * Some general remarks about the functions in this file. + * + * The following measures can be calculated: + * ( 1) s-t maximum flow value, directed graph + * ( 2) s-t maximum flow value, undirected graph + * ( 3) s-t maximum flow, directed graph + * ( 4) s-t maximum flow, undirected graph + * ( 5) s-t minimum cut value, directed graph + * ( 6) s-t minimum cut value, undirected graph + * ( 7) minimum cut value, directed graph + * ( 8) minimum cut value, undirected graph + * ( 9) s-t minimum cut, directed graph + * (10) s-t minimum cut, undirected graph + * (11) minimum cut, directed graph + * (12) minimum cut, undirected graph + * (13) s-t edge connectivity, directed graph + * (14) s-t edge connectivity, undirected graph + * (15) edge connectivity, directed graph + * (16) edge connectivity, undirected graph + * (17) s-t vertex connectivity, directed graph + * (18) s-t vertex connectivity, undirected graph + * (19) vertex connectivity, directed graph + * (20) vertex connectivity, undirected graph + * (21) s-t number of edge disjoint paths, directed graph + * (22) s-t number of edge disjoint paths, undirected graph + * (23) s-t number of vertex disjoint paths, directed graph + * (24) s-t number of vertex disjoint paths, undirected graph + * (25) graph adhesion, directed graph + * (26) graph adhesion, undirected graph + * (27) graph cohesion, directed graph + * (28) graph cohesion, undirected graph + * + * This is how they are calculated: + * ( 1) igraph_maxflow_value, calls igraph_maxflow. + * ( 2) igraph_maxflow_value, calls igraph_maxflow, this calls + * igraph_i_maxflow_undirected. This transforms the graph into a + * directed graph, including two mutual edges instead of every + * undirected edge, then igraph_maxflow is called again with the + * directed graph. + * ( 3) igraph_maxflow, does the push-relabel algorithm, optionally + * calculates the cut, the partitions and the flow itself. + * ( 4) igraph_maxflow calls igraph_i_maxflow_undirected, this converts + * the undirected graph into a directed one, adding two mutual edges + * for each undirected edge, then igraph_maxflow is called again, + * with the directed graph. After igraph_maxflow returns, we need + * to edit the flow (and the cut) to make it sense for the + * original graph. + * ( 5) igraph_st_mincut_value, we just call igraph_maxflow_value + * ( 6) igraph_st_mincut_value, we just call igraph_maxflow_value + * ( 7) igraph_mincut_value, we call igraph_maxflow_value (|V|-1)*2 + * times, from vertex 0 to all other vertices and from all other + * vertices to vertex 0 + * ( 8) We call igraph_i_mincut_value_undirected, that calls + * igraph_i_mincut_undirected with partition=partition2=cut=NULL + * The Stoer-Wagner algorithm is used. + * ( 9) igraph_st_mincut, just calls igraph_maxflow. + * (10) igraph_st_mincut, just calls igraph_maxflow. + * (11) igraph_mincut, calls igraph_i_mincut_directed, which runs + * the maximum flow algorithm 2(|V|-1) times, from vertex zero to + * and from all other vertices and stores the smallest cut. + * (12) igraph_mincut, igraph_i_mincut_undirected is called, + * this is the Stoer-Wagner algorithm + * (13) We just call igraph_maxflow_value, back to (1) + * (14) We just call igraph_maxflow_value, back to (2) + * (15) We just call igraph_mincut_value (possibly after some basic + * checks). Back to (7) + * (16) We just call igraph_mincut_value (possibly after some basic + * checks). Back to (8). + * (17) We call igraph_i_st_vertex_connectivity_directed. + * That creates a new graph with 2*|V| vertices and smartly chosen + * edges, so that the s-t edge connectivity of this graph is the + * same as the s-t vertex connectivity of the original graph. + * So finally it calls igraph_maxflow_value, go to (1) + * (18) We call igraph_i_st_vertex_connectivity_undirected. + * We convert the graph to a directed one, + * IGRAPH_TO_DIRECTED_MUTUAL method. Then we call + * igraph_i_st_vertex_connectivity_directed, see (17). + * (19) We call igraph_i_vertex_connectivity_directed. + * That calls igraph_st_vertex_connectivity for all pairs of + * vertices. Back to (17). + * (20) We call igraph_i_vertex_connectivity_undirected. + * That converts the graph into a directed one + * (IGRAPH_TO_DIRECTED_MUTUAL) and calls the directed version, + * igraph_i_vertex_connectivity_directed, see (19). + * (21) igraph_edge_disjoint_paths, we just call igraph_maxflow_value, (1). + * (22) igraph_edge_disjoint_paths, we just call igraph_maxflow_value, (2). + * (23) igraph_vertex_disjoint_paths, if there is a connection between + * the two vertices, then we remove that (or all of them if there + * are many), as this could mess up vertex connectivity + * calculation. The we call + * igraph_i_st_vertex_connectivity_directed, see (19). + * (24) igraph_vertex_disjoint_paths, if there is a connection between + * the two vertices, then we remove that (or all of them if there + * are many), as this could mess up vertex connectivity + * calculation. The we call + * igraph_i_st_vertex_connectivity_undirected, see (20). + * (25) We just call igraph_edge_connectivity, see (15). + * (26) We just call igraph_edge_connectivity, see (16). + * (27) We just call igraph_vertex_connectivity, see (19). + * (28) We just call igraph_vertex_connectivity, see (20). + */ + +/* + * This is an internal function that calculates the maximum flow value + * on undirected graphs, either for an s-t vertex pair or for the + * graph (i.e. all vertex pairs). + * + * It does it by converting the undirected graph to a corresponding + * directed graph, including reciprocal directed edges instead of each + * undirected edge. + */ + +static int igraph_i_maxflow_undirected(const igraph_t *graph, + igraph_real_t *value, + igraph_vector_t *flow, + igraph_vector_t *cut, + igraph_vector_t *partition, + igraph_vector_t *partition2, + igraph_integer_t source, + igraph_integer_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats) { + igraph_integer_t no_of_edges = (igraph_integer_t) igraph_ecount(graph); + igraph_integer_t no_of_nodes = (igraph_integer_t) igraph_vcount(graph); + igraph_vector_t edges; + igraph_vector_t newcapacity; + igraph_t newgraph; + long int i; + + /* We need to convert this to directed by hand, since we need to be + sure that the edge ids will be handled properly to build the new + capacity vector. */ + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&newcapacity, no_of_edges * 2); + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges * 4)); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + IGRAPH_CHECK(igraph_vector_resize(&edges, no_of_edges * 4)); + for (i = 0; i < no_of_edges; i++) { + VECTOR(edges)[no_of_edges * 2 + i * 2] = VECTOR(edges)[i * 2 + 1]; + VECTOR(edges)[no_of_edges * 2 + i * 2 + 1] = VECTOR(edges)[i * 2]; + VECTOR(newcapacity)[i] = VECTOR(newcapacity)[no_of_edges + i] = + capacity ? VECTOR(*capacity)[i] : 1.0; + } + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, no_of_nodes, IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + + IGRAPH_CHECK(igraph_maxflow(&newgraph, value, flow, cut, partition, + partition2, source, target, &newcapacity, stats)); + + if (cut) { + long int i, cs = igraph_vector_size(cut); + for (i = 0; i < cs; i++) { + if (VECTOR(*cut)[i] >= no_of_edges) { + VECTOR(*cut)[i] -= no_of_edges; + } + } + } + + /* The flow has one non-zero value for each real-nonreal edge pair, + by definition, we convert it to a positive-negative vector. If + for an edge the flow is negative that means that it is going + from the bigger vertex id to the smaller one. For positive + values the direction is the opposite. */ + if (flow) { + long int i; + for (i = 0; i < no_of_edges; i++) { + VECTOR(*flow)[i] -= VECTOR(*flow)[i + no_of_edges]; + } + IGRAPH_CHECK(igraph_vector_resize(flow, no_of_edges)); + } + + igraph_destroy(&newgraph); + igraph_vector_destroy(&edges); + igraph_vector_destroy(&newcapacity); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +#define FIRST(i) (VECTOR(*first)[(i)]) +#define LAST(i) (VECTOR(*first)[(i)+1]) +#define CURRENT(i) (VECTOR(*current)[(i)]) +#define RESCAP(i) (VECTOR(*rescap)[(i)]) +#define REV(i) (VECTOR(*rev)[(i)]) +#define HEAD(i) (VECTOR(*to)[(i)]) +#define EXCESS(i) (VECTOR(*excess)[(i)]) +#define DIST(i) (VECTOR(*distance)[(i)]) +#define DISCHARGE(v) (igraph_i_mf_discharge((v), ¤t, &first, &rescap, \ + &to, &distance, &excess, \ + no_of_nodes, source, target, \ + &buckets, &ibuckets, \ + &rev, stats, &npushsince, \ + &nrelabelsince)) +#define PUSH(v,e,n) (igraph_i_mf_push((v), (e), (n), current, rescap, \ + excess, target, source, buckets, \ + ibuckets, distance, rev, stats, \ + npushsince)) +#define RELABEL(v) (igraph_i_mf_relabel((v), no_of_nodes, distance, \ + first, rescap, to, current, \ + stats, nrelabelsince)) +#define GAP(b) (igraph_i_mf_gap((b), stats, buckets, ibuckets, \ + no_of_nodes, distance)) +#define BFS() (igraph_i_mf_bfs(&bfsq, source, target, no_of_nodes, \ + &buckets, &ibuckets, &distance, \ + &first, ¤t, &to, &excess, \ + &rescap, &rev)) + +static void igraph_i_mf_gap(long int b, igraph_maxflow_stats_t *stats, + igraph_buckets_t *buckets, igraph_dbuckets_t *ibuckets, + long int no_of_nodes, + igraph_vector_long_t *distance) { + + long int bo; + (stats->nogap)++; + for (bo = b + 1; bo <= no_of_nodes; bo++) { + while (!igraph_dbuckets_empty_bucket(ibuckets, bo)) { + long int n = igraph_dbuckets_pop(ibuckets, bo); + (stats->nogapnodes)++; + DIST(n) = no_of_nodes; + } + } +} + +static void igraph_i_mf_relabel(long int v, long int no_of_nodes, + igraph_vector_long_t *distance, + igraph_vector_long_t *first, + igraph_vector_t *rescap, igraph_vector_long_t *to, + igraph_vector_long_t *current, + igraph_maxflow_stats_t *stats, int *nrelabelsince) { + + long int min = no_of_nodes; + long int k, l, min_edge = 0; + (stats->norelabel)++; (*nrelabelsince)++; + DIST(v) = no_of_nodes; + for (k = FIRST(v), l = LAST(v); k < l; k++) { + if (RESCAP(k) > 0 && DIST(HEAD(k)) < min) { + min = DIST(HEAD(k)); + min_edge = k; + } + } + min++; + if (min < no_of_nodes) { + DIST(v) = min; + CURRENT(v) = min_edge; + } +} + +static void igraph_i_mf_push(long int v, long int e, long int n, + igraph_vector_long_t *current, + igraph_vector_t *rescap, igraph_vector_t *excess, + long int target, long int source, + igraph_buckets_t *buckets, igraph_dbuckets_t *ibuckets, + igraph_vector_long_t *distance, + igraph_vector_long_t *rev, igraph_maxflow_stats_t *stats, + int *npushsince) { + igraph_real_t delta = + RESCAP(e) < EXCESS(v) ? RESCAP(e) : EXCESS(v); + (stats->nopush)++; (*npushsince)++; + if (EXCESS(n) == 0 && n != target) { + igraph_dbuckets_delete(ibuckets, DIST(n), n); + igraph_buckets_add(buckets, (long int) DIST(n), n); + } + RESCAP(e) -= delta; + RESCAP(REV(e)) += delta; + EXCESS(n) += delta; + EXCESS(v) -= delta; +} + +static void igraph_i_mf_discharge(long int v, + igraph_vector_long_t *current, + igraph_vector_long_t *first, + igraph_vector_t *rescap, + igraph_vector_long_t *to, + igraph_vector_long_t *distance, + igraph_vector_t *excess, + long int no_of_nodes, long int source, + long int target, igraph_buckets_t *buckets, + igraph_dbuckets_t *ibuckets, + igraph_vector_long_t *rev, + igraph_maxflow_stats_t *stats, + int *npushsince, int *nrelabelsince) { + do { + long int i; + long int start = (long int) CURRENT(v); + long int stop = (long int) LAST(v); + for (i = start; i < stop; i++) { + if (RESCAP(i) > 0) { + long int nei = HEAD(i); + if (DIST(v) == DIST(nei) + 1) { + PUSH((v), i, nei); + if (EXCESS(v) == 0) { + break; + } + } + } + } + if (i == stop) { + long int origdist = DIST(v); + RELABEL(v); + if (igraph_buckets_empty_bucket(buckets, origdist) && + igraph_dbuckets_empty_bucket(ibuckets, origdist)) { + GAP(origdist); + } + if (DIST(v) == no_of_nodes) { + break; + } + } else { + CURRENT(v) = i; + igraph_dbuckets_add(ibuckets, DIST(v), v); + break; + } + } while (1); +} + +static void igraph_i_mf_bfs(igraph_dqueue_long_t *bfsq, + long int source, long int target, + long int no_of_nodes, igraph_buckets_t *buckets, + igraph_dbuckets_t *ibuckets, + igraph_vector_long_t *distance, + igraph_vector_long_t *first, igraph_vector_long_t *current, + igraph_vector_long_t *to, igraph_vector_t *excess, + igraph_vector_t *rescap, igraph_vector_long_t *rev) { + + long int k, l; + + igraph_buckets_clear(buckets); + igraph_dbuckets_clear(ibuckets); + igraph_vector_long_fill(distance, no_of_nodes); + DIST(target) = 0; + + igraph_dqueue_long_push(bfsq, target); + while (!igraph_dqueue_long_empty(bfsq)) { + long int node = igraph_dqueue_long_pop(bfsq); + long int ndist = DIST(node) + 1; + for (k = FIRST(node), l = LAST(node); k < l; k++) { + if (RESCAP(REV(k)) > 0) { + long int nei = HEAD(k); + if (DIST(nei) == no_of_nodes) { + DIST(nei) = ndist; + CURRENT(nei) = FIRST(nei); + if (EXCESS(nei) > 0) { + igraph_buckets_add(buckets, ndist, nei); + } else { + igraph_dbuckets_add(ibuckets, ndist, nei); + } + igraph_dqueue_long_push(bfsq, nei); + } + } + } + } +} + +/** + * \function igraph_maxflow + * Maximum network flow between a pair of vertices + * + * This function implements the Goldberg-Tarjan algorithm for + * calculating value of the maximum flow in a directed or undirected + * graph. The algorithm was given in Andrew V. Goldberg, Robert + * E. Tarjan: A New Approach to the Maximum-Flow Problem, Journal of + * the ACM, 35(4), 921-940, 1988. + * + * The input of the function is a graph, a vector + * of real numbers giving the capacity of the edges and two vertices + * of the graph, the source and the target. A flow is a function + * assigning positive real numbers to the edges and satisfying two + * requirements: (1) the flow value is less than the capacity of the + * edge and (2) at each vertex except the source and the target, the + * incoming flow (ie. the sum of the flow on the incoming edges) is + * the same as the outgoing flow (ie. the sum of the flow on the + * outgoing edges). The value of the flow is the incoming flow at the + * target vertex. The maximum flow is the flow with the maximum + * value. + * + * \param graph The input graph, either directed or undirected. + * \param value Pointer to a real number, the value of the maximum + * will be placed here, unless it is a null pointer. + * \param flow If not a null pointer, then it must be a pointer to an + * initialized vector. The vector will be resized, and the flow + * on each edge will be placed in it, in the order of the edge + * ids. For undirected graphs this argument is bit trickier, + * since for these the flow direction is not predetermined by + * the edge direction. For these graphs the elements of the + * \p flow vector can be negative, this means that the flow + * goes from the bigger vertex id to the smaller one. Positive + * values mean that the flow goes from the smaller vertex id to + * the bigger one. + * \param cut A null pointer or a pointer to an initialized vector. + * If not a null pointer, then the minimum cut corresponding to + * the maximum flow is stored here, i.e. all edge ids that are + * part of the minimum cut are stored in the vector. + * \param partition A null pointer or a pointer to an initialized + * vector. If not a null pointer, then the first partition of + * the minimum cut that corresponds to the maximum flow will be + * placed here. The first partition is always the one that + * contains the source vertex. + * \param partition2 A null pointer or a pointer to an initialized + * vector. If not a null pointer, then the second partition of + * the minimum cut that corresponds to the maximum flow will be + * placed here. The second partition is always the one that + * contains the target vertex. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \param capacity Vector containing the capacity of the edges. If NULL, then + * every edge is considered to have capacity 1.0. + * \param stats Counts of the number of different operations + * preformed by the algorithm are stored here. + * \return Error code. + * + * Time complexity: O(|V|^3). In practice it is much faster, but i + * cannot prove a better lower bound for the data structure i've + * used. In fact, this implementation runs much faster than the + * \c hi_pr implementation discussed in + * B. V. Cherkassky and A. V. Goldberg: On implementing the + * push-relabel method for the maximum flow problem, (Algorithmica, + * 19:390--410, 1997) on all the graph classes i've tried. + * + * \sa \ref igraph_mincut_value(), \ref igraph_edge_connectivity(), + * \ref igraph_vertex_connectivity() for + * properties based on the maximum flow. + * + * \example examples/simple/flow.c + * \example examples/simple/flow2.c + */ + +int igraph_maxflow(const igraph_t *graph, igraph_real_t *value, + igraph_vector_t *flow, igraph_vector_t *cut, + igraph_vector_t *partition, igraph_vector_t *partition2, + igraph_integer_t source, igraph_integer_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats) { + + igraph_integer_t no_of_nodes = (igraph_integer_t) igraph_vcount(graph); + igraph_integer_t no_of_orig_edges = (igraph_integer_t) igraph_ecount(graph); + igraph_integer_t no_of_edges = 2 * no_of_orig_edges; + + igraph_vector_t rescap, excess; + igraph_vector_long_t from, to, rev, distance; + igraph_vector_t edges, rank; + igraph_vector_long_t current, first; + igraph_buckets_t buckets; + igraph_dbuckets_t ibuckets; + + igraph_dqueue_long_t bfsq; + + long int i, j, idx; + int npushsince = 0, nrelabelsince = 0; + + igraph_maxflow_stats_t local_stats; /* used if the user passed a null pointer for stats */ + + if (stats == 0) { + stats = &local_stats; + } + + if (!igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_maxflow_undirected(graph, value, flow, cut, + partition, partition2, source, + target, capacity, stats)); + return 0; + } + + if (capacity && igraph_vector_size(capacity) != no_of_orig_edges) { + IGRAPH_ERROR("Invalid capacity vector", IGRAPH_EINVAL); + } + if (source < 0 || source >= no_of_nodes || target < 0 || target >= no_of_nodes) { + IGRAPH_ERROR("Invalid source or target vertex", IGRAPH_EINVAL); + } + + stats->nopush = stats->norelabel = stats->nogap = stats->nogapnodes = + stats->nobfs = 0; + + /* + * The data structure: + * - First of all, we consider every edge twice, first the edge + * itself, but also its opposite. + * - (from, to) contain all edges (original + opposite), ordered by + * the id of the source vertex. During the algorithm we just need + * 'to', so from is destroyed soon. We only need it in the + * beginning, to create the 'first' pointers. + * - 'first' is a pointer vector for 'to', first[i] points to the + * first neighbor of vertex i and first[i+1]-1 is the last + * neighbor of vertex i. (Unless vertex i is isolate, in which + * case first[i]==first[i+1]). + * - 'rev' contains a mapping from an edge to its opposite pair + * - 'rescap' contains the residual capacities of the edges, this is + * initially equal to the capacity of the edges for the original + * edges and it is zero for the opposite edges. + * - 'excess' contains the excess flow for the vertices. I.e. the flow + * that is coming in, but it is not going out. + * - 'current' stores the next neighboring vertex to check, for every + * vertex, when excess flow is being pushed to neighbors. + * - 'distance' stores the distance of the vertices from the source. + * - 'rank' and 'edges' are only needed temporarily, for ordering and + * storing the edges. + * - we use an igraph_buckets_t data structure ('buckets') to find + * the vertices with the highest 'distance' values quickly. + * This always contains the vertices that have a positive excess + * flow. + */ +#undef FIRST +#undef LAST +#undef CURRENT +#undef RESCAP +#undef REV +#undef HEAD +#undef EXCESS +#undef DIST +#define FIRST(i) (VECTOR(first)[(i)]) +#define LAST(i) (VECTOR(first)[(i)+1]) +#define CURRENT(i) (VECTOR(current)[(i)]) +#define RESCAP(i) (VECTOR(rescap)[(i)]) +#define REV(i) (VECTOR(rev)[(i)]) +#define HEAD(i) (VECTOR(to)[(i)]) +#define EXCESS(i) (VECTOR(excess)[(i)]) +#define DIST(i) (VECTOR(distance)[(i)]) + + igraph_dqueue_long_init(&bfsq, no_of_nodes); + IGRAPH_FINALLY(igraph_dqueue_long_destroy, &bfsq); + IGRAPH_VECTOR_LONG_INIT_FINALLY(&to, no_of_edges); + IGRAPH_VECTOR_LONG_INIT_FINALLY(&rev, no_of_edges); + IGRAPH_VECTOR_INIT_FINALLY(&rescap, no_of_edges); + IGRAPH_VECTOR_INIT_FINALLY(&excess, no_of_nodes); + IGRAPH_VECTOR_LONG_INIT_FINALLY(&distance, no_of_nodes); + IGRAPH_VECTOR_LONG_INIT_FINALLY(&first, no_of_nodes + 1); + + IGRAPH_VECTOR_INIT_FINALLY(&rank, no_of_edges); + IGRAPH_VECTOR_LONG_INIT_FINALLY(&from, no_of_edges); + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges); + + /* Create the basic data structure */ + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + IGRAPH_CHECK(igraph_vector_rank(&edges, &rank, no_of_nodes)); + + for (i = 0; i < no_of_edges; i += 2) { + long int pos = (long int) VECTOR(rank)[i]; + long int pos2 = (long int) VECTOR(rank)[i + 1]; + VECTOR(from)[pos] = VECTOR(edges)[i]; + VECTOR(to)[pos] = VECTOR(edges)[i + 1]; + VECTOR(from)[pos2] = VECTOR(edges)[i + 1]; + VECTOR(to)[pos2] = VECTOR(edges)[i]; + VECTOR(rev)[pos] = pos2; + VECTOR(rev)[pos2] = pos; + VECTOR(rescap)[pos] = capacity ? VECTOR(*capacity)[i / 2] : 1.0; + VECTOR(rescap)[pos2] = 0.0; + } + + /* The first pointers. This is a but trickier, than one would + think, because of the possible isolate vertices. */ + + idx = -1; + for (i = 0; i <= VECTOR(from)[0]; i++) { + idx++; VECTOR(first)[idx] = 0; + } + for (i = 1; i < no_of_edges; i++) { + long int n = (long int) (VECTOR(from)[i] - + VECTOR(from)[ (long int) VECTOR(first)[idx] ]); + for (j = 0; j < n; j++) { + idx++; VECTOR(first)[idx] = i; + } + } + idx++; + while (idx < no_of_nodes + 1) { + VECTOR(first)[idx++] = no_of_edges; + } + + igraph_vector_long_destroy(&from); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(2); + + if (!flow) { + igraph_vector_destroy(&rank); + IGRAPH_FINALLY_CLEAN(1); + } + + /* And the current pointers, initially the same as the first */ + IGRAPH_VECTOR_LONG_INIT_FINALLY(¤t, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(current)[i] = VECTOR(first)[i]; + } + + /* OK, the graph is set up, initialization */ + + IGRAPH_CHECK(igraph_buckets_init(&buckets, no_of_nodes + 1, no_of_nodes)); + IGRAPH_FINALLY(igraph_buckets_destroy, &buckets); + IGRAPH_CHECK(igraph_dbuckets_init(&ibuckets, no_of_nodes + 1, no_of_nodes)); + IGRAPH_FINALLY(igraph_dbuckets_destroy, &ibuckets); + + /* Send as much flow as possible from the source to its neighbors */ + for (i = FIRST(source), j = LAST(source); i < j; i++) { + if (HEAD(i) != source) { + igraph_real_t delta = RESCAP(i); + RESCAP(i) = 0; + RESCAP(REV(i)) += delta; + EXCESS(HEAD(i)) += delta; + } + } + + BFS(); + (stats->nobfs)++; + + while (!igraph_buckets_empty(&buckets)) { + long int vertex = igraph_buckets_popmax(&buckets); + DISCHARGE(vertex); + if (npushsince > no_of_nodes / 2 && nrelabelsince > no_of_nodes) { + (stats->nobfs)++; + BFS(); + npushsince = nrelabelsince = 0; + } + } + + /* Store the result */ + if (value) { + *value = EXCESS(target); + } + + /* If we also need the minimum cut */ + if (cut || partition || partition2) { + /* We need to find all vertices from which the target is reachable + in the residual graph. We do a breadth-first search, going + backwards. */ + igraph_dqueue_t Q; + igraph_vector_bool_t added; + long int marked = 0; + + IGRAPH_CHECK(igraph_vector_bool_init(&added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &added); + + IGRAPH_CHECK(igraph_dqueue_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &Q); + + igraph_dqueue_push(&Q, target); + VECTOR(added)[(long int)target] = 1; + marked++; + while (!igraph_dqueue_empty(&Q)) { + long int actnode = (long int) igraph_dqueue_pop(&Q); + for (i = FIRST(actnode), j = LAST(actnode); i < j; i++) { + long int nei = HEAD(i); + if (!VECTOR(added)[nei] && RESCAP(REV(i)) > 0.0) { + VECTOR(added)[nei] = 1; + marked++; + IGRAPH_CHECK(igraph_dqueue_push(&Q, nei)); + } + } + } + igraph_dqueue_destroy(&Q); + IGRAPH_FINALLY_CLEAN(1); + + /* Now we marked each vertex that is on one side of the cut, + check the crossing edges */ + + if (cut) { + igraph_vector_clear(cut); + for (i = 0; i < no_of_orig_edges; i++) { + long int f = IGRAPH_FROM(graph, i); + long int t = IGRAPH_TO(graph, i); + if (!VECTOR(added)[f] && VECTOR(added)[t]) { + IGRAPH_CHECK(igraph_vector_push_back(cut, i)); + } + } + } + + if (partition2) { + long int x = 0; + IGRAPH_CHECK(igraph_vector_resize(partition2, marked)); + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(added)[i]) { + VECTOR(*partition2)[x++] = i; + } + } + } + + if (partition) { + long int x = 0; + IGRAPH_CHECK(igraph_vector_resize(partition, + no_of_nodes - marked)); + for (i = 0; i < no_of_nodes; i++) { + if (!VECTOR(added)[i]) { + VECTOR(*partition)[x++] = i; + } + } + } + + igraph_vector_bool_destroy(&added); + IGRAPH_FINALLY_CLEAN(1); + } + + if (flow) { + /* Initialize the backward distances, with a breadth-first search + from the source */ + igraph_dqueue_t Q; + igraph_vector_int_t added; + long int j, k, l; + igraph_t flow_graph; + igraph_vector_t flow_edges; + igraph_bool_t dag; + + IGRAPH_CHECK(igraph_vector_int_init(&added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &added); + IGRAPH_CHECK(igraph_dqueue_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &added); + + igraph_dqueue_push(&Q, source); + igraph_dqueue_push(&Q, 0); + VECTOR(added)[(long int)source] = 1; + while (!igraph_dqueue_empty(&Q)) { + long int actnode = (long int) igraph_dqueue_pop(&Q); + long int actdist = (long int) igraph_dqueue_pop(&Q); + DIST(actnode) = actdist; + + for (i = FIRST(actnode), j = LAST(actnode); i < j; i++) { + long int nei = HEAD(i); + if (!VECTOR(added)[nei] && RESCAP(REV(i)) > 0.0) { + VECTOR(added)[nei] = 1; + IGRAPH_CHECK(igraph_dqueue_push(&Q, nei)); + IGRAPH_CHECK(igraph_dqueue_push(&Q, actdist + 1)); + } + } + } /* !igraph_dqueue_empty(&Q) */ + + igraph_vector_int_destroy(&added); + igraph_dqueue_destroy(&Q); + IGRAPH_FINALLY_CLEAN(2); + + /* Reinitialize the buckets */ + igraph_buckets_clear(&buckets); + for (i = 0; i < no_of_nodes; i++) { + if (EXCESS(i) > 0.0 && i != source && i != target) { + igraph_buckets_add(&buckets, (long int) DIST(i), i); + } + } + + /* Now we return the flow to the source */ + while (!igraph_buckets_empty(&buckets)) { + long int vertex = igraph_buckets_popmax(&buckets); + + /* DISCHARGE(vertex) comes here */ + do { + for (i = (long int) CURRENT(vertex), j = LAST(vertex); i < j; i++) { + if (RESCAP(i) > 0) { + long int nei = HEAD(i); + + if (DIST(vertex) == DIST(nei) + 1) { + igraph_real_t delta = + RESCAP(i) < EXCESS(vertex) ? RESCAP(i) : EXCESS(vertex); + RESCAP(i) -= delta; + RESCAP(REV(i)) += delta; + + if (nei != source && EXCESS(nei) == 0.0 && + DIST(nei) != no_of_nodes) { + igraph_buckets_add(&buckets, (long int) DIST(nei), nei); + } + + EXCESS(nei) += delta; + EXCESS(vertex) -= delta; + + if (EXCESS(vertex) == 0) { + break; + } + + } + } + } + + if (i == j) { + + /* RELABEL(vertex) comes here */ + igraph_real_t min; + long int min_edge = 0; + DIST(vertex) = min = no_of_nodes; + for (k = FIRST(vertex), l = LAST(vertex); k < l; k++) { + if (RESCAP(k) > 0) { + if (DIST(HEAD(k)) < min) { + min = DIST(HEAD(k)); + min_edge = k; + } + } + } + + min++; + + if (min < no_of_nodes) { + DIST(vertex) = min; + CURRENT(vertex) = min_edge; + /* Vertex is still active */ + igraph_buckets_add(&buckets, (long int) DIST(vertex), vertex); + } + + /* TODO: gap heuristics here ??? */ + + } else { + CURRENT(vertex) = FIRST(vertex); + } + + break; + + } while (1); + } + + /* We need to eliminate flow cycles now. Before that we check that + there is a cycle in the flow graph. + + First we do a couple of DFSes from the source vertex to the + target and factor out the paths we find. If there is no more + path to the target, then all remaining flow must be in flow + cycles, so we don't need it at all. + + Some details. 'stack' contains the whole path of the DFS, both + the vertices and the edges, they are alternating in the stack. + 'current' helps finding the next outgoing edge of a vertex + quickly, the next edge of 'v' is FIRST(v)+CURRENT(v). If this + is LAST(v), then there are no more edges to try. + + The 'added' vector contains 0 if the vertex was not visited + before, 1 if it is currently in 'stack', and 2 if it is not in + 'stack', but it was visited before. */ + + IGRAPH_VECTOR_INIT_FINALLY(&flow_edges, 0); + for (i = 0, j = 0; i < no_of_edges; i += 2, j++) { + long int pos = (long int) VECTOR(rank)[i]; + if ((capacity ? VECTOR(*capacity)[j] : 1.0) > RESCAP(pos)) { + IGRAPH_CHECK(igraph_vector_push_back(&flow_edges, + IGRAPH_FROM(graph, j))); + IGRAPH_CHECK(igraph_vector_push_back(&flow_edges, + IGRAPH_TO(graph, j))); + } + } + IGRAPH_CHECK(igraph_create(&flow_graph, &flow_edges, no_of_nodes, + IGRAPH_DIRECTED)); + igraph_vector_destroy(&flow_edges); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_destroy, &flow_graph); + IGRAPH_CHECK(igraph_is_dag(&flow_graph, &dag)); + igraph_destroy(&flow_graph); + IGRAPH_FINALLY_CLEAN(1); + + if (!dag) { + igraph_vector_long_t stack; + igraph_vector_t mycap; + + IGRAPH_CHECK(igraph_vector_long_init(&stack, 0)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &stack); + IGRAPH_CHECK(igraph_vector_int_init(&added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &added); + IGRAPH_VECTOR_INIT_FINALLY(&mycap, no_of_edges); + +#define MYCAP(i) (VECTOR(mycap)[(i)]) + + for (i = 0; i < no_of_edges; i += 2) { + long int pos = (long int) VECTOR(rank)[i]; + long int pos2 = (long int) VECTOR(rank)[i + 1]; + MYCAP(pos) = (capacity ? VECTOR(*capacity)[i / 2] : 1.0) - RESCAP(pos); + MYCAP(pos2) = 0.0; + } + + do { + igraph_vector_long_null(¤t); + igraph_vector_long_clear(&stack); + igraph_vector_int_null(&added); + + IGRAPH_CHECK(igraph_vector_long_push_back(&stack, -1)); + IGRAPH_CHECK(igraph_vector_long_push_back(&stack, source)); + VECTOR(added)[(long int)source] = 1; + while (!igraph_vector_long_empty(&stack) && + igraph_vector_long_tail(&stack) != target) { + long int actnode = igraph_vector_long_tail(&stack); + long int edge = FIRST(actnode) + (long int) CURRENT(actnode); + long int nei; + while (edge < LAST(actnode) && MYCAP(edge) == 0.0) { + edge++; + } + nei = edge < LAST(actnode) ? HEAD(edge) : -1; + + if (edge < LAST(actnode) && !VECTOR(added)[nei]) { + /* Go forward along next edge, if the vertex was not + visited before */ + IGRAPH_CHECK(igraph_vector_long_push_back(&stack, edge)); + IGRAPH_CHECK(igraph_vector_long_push_back(&stack, nei)); + VECTOR(added)[nei] = 1; + CURRENT(actnode) += 1; + } else if (edge < LAST(actnode) && VECTOR(added)[nei] == 1) { + /* We found a flow cycle, factor it out. Go back in stack + until we find 'nei' again, determine the flow along the + cycle. */ + igraph_real_t thisflow = MYCAP(edge); + long int idx; + for (idx = igraph_vector_long_size(&stack) - 2; + idx >= 0 && VECTOR(stack)[idx + 1] != nei; idx -= 2) { + long int e = VECTOR(stack)[idx]; + igraph_real_t rcap = e >= 0 ? MYCAP(e) : MYCAP(edge); + if (rcap < thisflow) { + thisflow = rcap; + } + } + MYCAP(edge) -= thisflow; RESCAP(edge) += thisflow; + for (idx = igraph_vector_long_size(&stack) - 2; + idx >= 0 && VECTOR(stack)[idx + 1] != nei; idx -= 2) { + long int e = VECTOR(stack)[idx]; + if (e >= 0) { + MYCAP(e) -= thisflow; + RESCAP(e) += thisflow; + } + } + CURRENT(actnode) += 1; + } else if (edge < LAST(actnode)) { /* && VECTOR(added)[nei]==2 */ + /* The next edge leads to a vertex that was visited before, + but it is currently not in 'stack' */ + CURRENT(actnode) += 1; + } else { + /* Go backward, take out the node and the edge that leads to it */ + igraph_vector_long_pop_back(&stack); + igraph_vector_long_pop_back(&stack); + VECTOR(added)[actnode] = 2; + } + } + + /* If non-empty, then it contains a path from source to target + in the residual graph. We factor out this path from the flow. */ + if (!igraph_vector_long_empty(&stack)) { + long int pl = igraph_vector_long_size(&stack); + igraph_real_t thisflow = EXCESS(target); + for (i = 2; i < pl; i += 2) { + long int edge = VECTOR(stack)[i]; + igraph_real_t rcap = MYCAP(edge); + if (rcap < thisflow) { + thisflow = rcap; + } + } + for (i = 2; i < pl; i += 2) { + long int edge = VECTOR(stack)[i]; + MYCAP(edge) -= thisflow; + } + } + + } while (!igraph_vector_long_empty(&stack)); + + igraph_vector_destroy(&mycap); + igraph_vector_int_destroy(&added); + igraph_vector_long_destroy(&stack); + IGRAPH_FINALLY_CLEAN(3); + } + + /* ----------------------------------------------------------- */ + + IGRAPH_CHECK(igraph_vector_resize(flow, no_of_orig_edges)); + for (i = 0, j = 0; i < no_of_edges; i += 2, j++) { + long int pos = (long int) VECTOR(rank)[i]; + VECTOR(*flow)[j] = (capacity ? VECTOR(*capacity)[j] : 1.0) - + RESCAP(pos); + } + + igraph_vector_destroy(&rank); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_dbuckets_destroy(&ibuckets); + igraph_buckets_destroy(&buckets); + igraph_vector_long_destroy(¤t); + igraph_vector_long_destroy(&first); + igraph_vector_long_destroy(&distance); + igraph_vector_destroy(&excess); + igraph_vector_destroy(&rescap); + igraph_vector_long_destroy(&rev); + igraph_vector_long_destroy(&to); + igraph_dqueue_long_destroy(&bfsq); + IGRAPH_FINALLY_CLEAN(10); + + return 0; +} + +/** + * \function igraph_maxflow_value + * \brief Maximum flow in a network with the push/relabel algorithm + * + * This function implements the Goldberg-Tarjan algorithm for + * calculating value of the maximum flow in a directed or undirected + * graph. The algorithm was given in Andrew V. Goldberg, Robert + * E. Tarjan: A New Approach to the Maximum-Flow Problem, Journal of + * the ACM, 35(4), 921-940, 1988. + * + * The input of the function is a graph, a vector + * of real numbers giving the capacity of the edges and two vertices + * of the graph, the source and the target. A flow is a function + * assigning positive real numbers to the edges and satisfying two + * requirements: (1) the flow value is less than the capacity of the + * edge and (2) at each vertex except the source and the target, the + * incoming flow (ie. the sum of the flow on the incoming edges) is + * the same as the outgoing flow (ie. the sum of the flow on the + * outgoing edges). The value of the flow is the incoming flow at the + * target vertex. The maximum flow is the flow with the maximum + * value. + * + * According to a theorem by Ford and Fulkerson + * (L. R. Ford Jr. and D. R. Fulkerson. Maximal flow through a + * network. Canadian J. Math., 8:399-404, 1956.) the maximum flow + * between two vertices is the same as the + * minimum cut between them (also called the minimum s-t cut). So \ref + * igraph_st_mincut_value() gives the same result in all cases as \c + * igraph_maxflow_value(). + * + * Note that the value of the maximum flow is the same as the + * minimum cut in the graph. + * \param graph The input graph, either directed or undirected. + * \param value Pointer to a real number, the result will be placed here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \param capacity Vector containing the capacity of the edges. If NULL, then + * every edge is considered to have capacity 1.0. + * \param stats Counts of the number of different operations + * preformed by the algorithm are stored here. + * \return Error code. + * + * Time complexity: O(|V|^3). + * + * \sa \ref igraph_maxflow() to calculate the actual flow. + * \ref igraph_mincut_value(), \ref igraph_edge_connectivity(), + * \ref igraph_vertex_connectivity() for + * properties based on the maximum flow. + */ + +int igraph_maxflow_value(const igraph_t *graph, igraph_real_t *value, + igraph_integer_t source, igraph_integer_t target, + const igraph_vector_t *capacity, + igraph_maxflow_stats_t *stats) { + + return igraph_maxflow(graph, value, /*flow=*/ 0, /*cut=*/ 0, + /*partition=*/ 0, /*partition1=*/ 0, + source, target, capacity, stats); +} + +/** + * \function igraph_st_mincut_value + * \brief The minimum s-t cut in a graph + * + * The minimum s-t cut in a weighted (=valued) graph is the + * total minimum edge weight needed to remove from the graph to + * eliminate all paths from a given vertex (\c source) to + * another vertex (\c target). Directed paths are considered in + * directed graphs, and undirected paths in undirected graphs. + * + * The minimum s-t cut between two vertices is known to be same + * as the maximum flow between these two vertices. So this function + * calls \ref igraph_maxflow_value() to do the calculation. + * \param graph The input graph. + * \param value Pointer to a real variable, the result will be stored + * here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \param capacity Pointer to the capacity vector, it should contain + * non-negative numbers and its length should be the same the + * the number of edges in the graph. It can be a null pointer, then + * every edge has unit capacity. + * \return Error code. + * + * Time complexity: O(|V|^3), see also the discussion for \ref + * igraph_maxflow_value(), |V| is the number of vertices. + */ + +int igraph_st_mincut_value(const igraph_t *graph, igraph_real_t *value, + igraph_integer_t source, igraph_integer_t target, + const igraph_vector_t *capacity) { + + if (source == target) { + IGRAPH_ERROR("source and target vertices are the same", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_maxflow_value(graph, value, source, target, capacity, 0)); + + return 0; +} + +/** + * \function igraph_st_mincut + * Minimum cut between a source and a target vertex + * + * Finds the edge set that has the smallest total capacity among all + * edge sets that disconnect the source and target vertices. + * + * The calculation is performed using maximum flow + * techniques, by calling \ref igraph_maxflow(). + * \param graph The input graph. + * \param value Pointer to a real variable, the value of the cut is + * stored here. + * \param cut Pointer to a real vector, the edge ids that are included + * in the cut are stored here. This argument is ignored if it + * is a null pointer. + * \param partition Pointer to a real vector, the vertex ids of the + * vertices in the first partition of the cut are stored + * here. The first partition is always the one that contains the + * source vertex. This argument is ignored if it is a null pointer. + * \param partition2 Pointer to a real vector, the vertex ids of the + * vertices in the second partition of the cut are stored here. + * The second partition is always the one that contains the + * target vertex. This argument is ignored if it is a null pointer. + * \param source Integer, the id of the source vertex. + * \param target Integer, the id of the target vertex. + * \param capacity Vector containing the capacity of the edges. If a + * null pointer, then every edge is considered to have capacity + * 1.0. + * \return Error code. + * + * \sa \ref igraph_maxflow(). + * + * Time complexity: see \ref igraph_maxflow(). + */ + +int igraph_st_mincut(const igraph_t *graph, igraph_real_t *value, + igraph_vector_t *cut, igraph_vector_t *partition, + igraph_vector_t *partition2, + igraph_integer_t source, igraph_integer_t target, + const igraph_vector_t *capacity) { + + return igraph_maxflow(graph, value, /*flow=*/ 0, + cut, partition, partition2, + source, target, capacity, 0); +} + +/* This is a flow-based version, but there is a better one + for undirected graphs */ + +/* int igraph_i_mincut_value_undirected(const igraph_t *graph, */ +/* igraph_real_t *res, */ +/* const igraph_vector_t *capacity) { */ + +/* long int no_of_edges=igraph_ecount(graph); */ +/* long int no_of_nodes=igraph_vcount(graph); */ +/* igraph_vector_t edges; */ +/* igraph_vector_t newcapacity; */ +/* igraph_t newgraph; */ +/* long int i; */ + +/* /\* We need to convert this to directed by hand, since we need to be */ +/* sure that the edge ids will be handled properly to build the new */ +/* capacity vector. *\/ */ + +/* IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); */ +/* IGRAPH_VECTOR_INIT_FINALLY(&newcapacity, no_of_edges*2); */ +/* IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges*4)); */ +/* IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); */ +/* IGRAPH_CHECK(igraph_vector_resize(&edges, no_of_edges*4)); */ +/* for (i=0; i= 2) { + + long int last; + igraph_real_t acut; + long int a, n; + + igraph_vector_int_t *edges, *edges2; + igraph_vector_int_t *neis, *neis2; + + do { + a = igraph_i_cutheap_popmax(&heap); + + /* update the weights of the active vertices connected to a */ + edges = igraph_inclist_get(&inclist, a); + neis = igraph_adjlist_get(&adjlist, a); + n = igraph_vector_int_size(edges); + for (i = 0; i < n; i++) { + igraph_integer_t edge = (igraph_integer_t) VECTOR(*edges)[i]; + igraph_integer_t to = (igraph_integer_t) VECTOR(*neis)[i]; + igraph_real_t weight = capacity ? VECTOR(*capacity)[(long int)edge] : 1.0; + igraph_i_cutheap_update(&heap, to, weight); + } + + } while (igraph_i_cutheap_active_size(&heap) > 1); + + /* Now, there is only one active vertex left, + calculate the cut of the phase */ + acut = igraph_i_cutheap_maxvalue(&heap); + last = igraph_i_cutheap_popmax(&heap); + + if (acut < mincut) { + mincut = acut; + mincut_step = act_step; + } + + if (mincut == 0) { + break; + } + + /* And contract the last and the remaining vertex (a and last) */ + /* Before actually doing that, make some notes */ + act_step++; + if (calc_cut) { + IGRAPH_CHECK(igraph_vector_push_back(&mergehist, a)); + IGRAPH_CHECK(igraph_vector_push_back(&mergehist, last)); + } + /* First remove the a--last edge if there is one, a is still the + last deactivated vertex */ + edges = igraph_inclist_get(&inclist, a); + neis = igraph_adjlist_get(&adjlist, a); + n = igraph_vector_int_size(edges); + for (i = 0; i < n; ) { + if (VECTOR(*neis)[i] == last) { + VECTOR(*neis)[i] = VECTOR(*neis)[n - 1]; + VECTOR(*edges)[i] = VECTOR(*edges)[n - 1]; + igraph_vector_int_pop_back(neis); + igraph_vector_int_pop_back(edges); + n--; + } else { + i++; + } + } + + edges = igraph_inclist_get(&inclist, last); + neis = igraph_adjlist_get(&adjlist, last); + n = igraph_vector_int_size(edges); + for (i = 0; i < n; ) { + if (VECTOR(*neis)[i] == a) { + VECTOR(*neis)[i] = VECTOR(*neis)[n - 1]; + VECTOR(*edges)[i] = VECTOR(*edges)[n - 1]; + igraph_vector_int_pop_back(neis); + igraph_vector_int_pop_back(edges); + n--; + } else { + i++; + } + } + + /* Now rewrite the edge lists of last's neighbors */ + neis = igraph_adjlist_get(&adjlist, last); + n = igraph_vector_int_size(neis); + for (i = 0; i < n; i++) { + igraph_integer_t nei = (igraph_integer_t) VECTOR(*neis)[i]; + long int n2, j; + neis2 = igraph_adjlist_get(&adjlist, nei); + n2 = igraph_vector_int_size(neis2); + for (j = 0; j < n2; j++) { + if (VECTOR(*neis2)[j] == last) { + VECTOR(*neis2)[j] = a; + } + } + } + + /* And append the lists of last to the lists of a */ + edges = igraph_inclist_get(&inclist, a); + neis = igraph_adjlist_get(&adjlist, a); + edges2 = igraph_inclist_get(&inclist, last); + neis2 = igraph_adjlist_get(&adjlist, last); + IGRAPH_CHECK(igraph_vector_int_append(edges, edges2)); + IGRAPH_CHECK(igraph_vector_int_append(neis, neis2)); + igraph_vector_int_clear(edges2); /* TODO: free it */ + igraph_vector_int_clear(neis2); /* TODO: free it */ + + /* Remove the deleted vertex from the heap entirely */ + igraph_i_cutheap_reset_undefine(&heap, last); + } + + *res = mincut; + + igraph_inclist_destroy(&inclist); + igraph_adjlist_destroy(&adjlist); + igraph_i_cutheap_destroy(&heap); + IGRAPH_FINALLY_CLEAN(3); + + if (calc_cut) { + long int bignode = (long int) VECTOR(mergehist)[2 * mincut_step + 1]; + long int i, idx; + long int size = 1; + char *mark; + mark = igraph_Calloc(no_of_nodes, char); + if (!mark) { + IGRAPH_ERROR("Not enough memory for minimum cut", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, mark); + + /* first count the vertices in the partition */ + mark[bignode] = 1; + for (i = mincut_step - 1; i >= 0; i--) { + if ( mark[ (long int) VECTOR(mergehist)[2 * i] ] ) { + size++; + mark [ (long int) VECTOR(mergehist)[2 * i + 1] ] = 1; + } + } + + /* now store them, if requested */ + if (partition) { + IGRAPH_CHECK(igraph_vector_resize(partition, size)); + idx = 0; + VECTOR(*partition)[idx++] = bignode; + for (i = mincut_step - 1; i >= 0; i--) { + if (mark[ (long int) VECTOR(mergehist)[2 * i] ]) { + VECTOR(*partition)[idx++] = VECTOR(mergehist)[2 * i + 1]; + } + } + } + + /* The other partition too? */ + if (partition2) { + IGRAPH_CHECK(igraph_vector_resize(partition2, no_of_nodes - size)); + idx = 0; + for (i = 0; i < no_of_nodes; i++) { + if (!mark[i]) { + VECTOR(*partition2)[idx++] = i; + } + } + } + + /* The edges in the cut are also requested? */ + /* We want as few memory allocated for 'cut' as possible, + so we first collect the edges in mergehist, we don't + need that anymore. Then we copy it to 'cut'; */ + if (cut) { + igraph_integer_t from, to; + igraph_vector_clear(&mergehist); + for (i = 0; i < no_of_edges; i++) { + igraph_edge(graph, (igraph_integer_t) i, &from, &to); + if ((mark[(long int)from] && !mark[(long int)to]) || + (mark[(long int)to] && !mark[(long int)from])) { + IGRAPH_CHECK(igraph_vector_push_back(&mergehist, i)); + } + } + igraph_vector_clear(cut); + IGRAPH_CHECK(igraph_vector_append(cut, &mergehist)); + } + + igraph_free(mark); + igraph_vector_destroy(&mergehist); + IGRAPH_FINALLY_CLEAN(2); + } + + return 0; +} + +static int igraph_i_mincut_directed(const igraph_t *graph, + igraph_real_t *value, + igraph_vector_t *partition, + igraph_vector_t *partition2, + igraph_vector_t *cut, + const igraph_vector_t *capacity) { + long int i; + long int no_of_nodes = igraph_vcount(graph); + igraph_real_t flow; + igraph_real_t minmaxflow = IGRAPH_INFINITY; + igraph_vector_t mypartition, mypartition2, mycut; + igraph_vector_t *ppartition = 0, *ppartition2 = 0, *pcut = 0; + igraph_vector_t bestpartition, bestpartition2, bestcut; + + if (partition) { + IGRAPH_VECTOR_INIT_FINALLY(&bestpartition, 0); + } + if (partition2) { + IGRAPH_VECTOR_INIT_FINALLY(&bestpartition2, 0); + } + if (cut) { + IGRAPH_VECTOR_INIT_FINALLY(&bestcut, 0); + } + + if (partition) { + IGRAPH_VECTOR_INIT_FINALLY(&mypartition, 0); + ppartition = &mypartition; + } + if (partition2) { + IGRAPH_VECTOR_INIT_FINALLY(&mypartition2, 0); + ppartition2 = &mypartition2; + } + if (cut) { + IGRAPH_VECTOR_INIT_FINALLY(&mycut, 0); + pcut = &mycut; + } + + for (i = 1; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_maxflow(graph, /*value=*/ &flow, /*flow=*/ 0, + pcut, ppartition, ppartition2, /*source=*/ 0, + /*target=*/ (igraph_integer_t) i, capacity, 0)); + if (flow < minmaxflow) { + minmaxflow = flow; + if (cut) { + IGRAPH_CHECK(igraph_vector_update(&bestcut, &mycut)); + } + if (partition) { + IGRAPH_CHECK(igraph_vector_update(&bestpartition, &mypartition)); + } + if (partition2) { + IGRAPH_CHECK(igraph_vector_update(&bestpartition2, &mypartition2)); + } + + if (minmaxflow == 0) { + break; + } + } + IGRAPH_CHECK(igraph_maxflow(graph, /*value=*/ &flow, /*flow=*/ 0, + pcut, ppartition, ppartition2, + /*source=*/ (igraph_integer_t) i, + /*target=*/ 0, capacity, 0)); + if (flow < minmaxflow) { + minmaxflow = flow; + if (cut) { + IGRAPH_CHECK(igraph_vector_update(&bestcut, &mycut)); + } + if (partition) { + IGRAPH_CHECK(igraph_vector_update(&bestpartition, &mypartition)); + } + if (partition2) { + IGRAPH_CHECK(igraph_vector_update(&bestpartition2, &mypartition2)); + } + + if (minmaxflow == 0) { + break; + } + } + } + + if (value) { + *value = minmaxflow; + } + + if (cut) { + igraph_vector_destroy(&mycut); + IGRAPH_FINALLY_CLEAN(1); + } + if (partition) { + igraph_vector_destroy(&mypartition); + IGRAPH_FINALLY_CLEAN(1); + } + if (partition2) { + igraph_vector_destroy(&mypartition2); + IGRAPH_FINALLY_CLEAN(1); + } + if (cut) { + IGRAPH_CHECK(igraph_vector_update(cut, &bestcut)); + igraph_vector_destroy(&bestcut); + IGRAPH_FINALLY_CLEAN(1); + } + if (partition2) { + IGRAPH_CHECK(igraph_vector_update(partition2, &bestpartition2)); + igraph_vector_destroy(&bestpartition2); + IGRAPH_FINALLY_CLEAN(1); + } + if (partition) { + IGRAPH_CHECK(igraph_vector_update(partition, &bestpartition)); + igraph_vector_destroy(&bestpartition); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_mincut + * \brief Calculates the minimum cut in a graph. + * + * This function calculates the minimum cut in a graph. + * The minimum cut is the minimum set of edges which needs to be + * removed to disconnect the graph. The minimum is calculated using + * the weights (\p capacity) of the edges, so the cut with the minimum + * total capacity is calculated. + * + * For directed graphs an implementation based on + * calculating 2|V|-2 maximum flows is used. + * For undirected graphs we use the Stoer-Wagner + * algorithm, as described in M. Stoer and F. Wagner: A simple min-cut + * algorithm, Journal of the ACM, 44 585-591, 1997. + * + * + * The first implementation of the actual cut calculation for + * undirected graphs was made by Gregory Benison, thanks Greg. + * \param graph The input graph. + * \param value Pointer to a float, the value of the cut will be + * stored here. + * \param partition Pointer to an initialized vector, the ids + * of the vertices in the first partition after separating the + * graph will be stored here. The vector will be resized as + * needed. This argument is ignored if it is a NULL pointer. + * \param partition2 Pointer to an initialized vector the ids + * of the vertices in the second partition will be stored here. + * The vector will be resized as needed. This argument is ignored + * if it is a NULL pointer. + * \param cut Pointer to an initialized vector, the ids of the edges + * in the cut will be stored here. This argument is ignored if it + * is a NULL pointer. + * \param capacity A numeric vector giving the capacities of the + * edges. If a null pointer then all edges have unit capacity. + * \return Error code. + * + * \sa \ref igraph_mincut_value(), a simpler interface for calculating + * the value of the cut only. + * + * Time complexity: for directed graphs it is O(|V|^4), but see the + * remarks at \ref igraph_maxflow(). For undirected graphs it is + * O(|V||E|+|V|^2 log|V|). |V| and |E| are the number of vertices and + * edges respectively. + * + * \example examples/simple/igraph_mincut.c + */ + +int igraph_mincut(const igraph_t *graph, + igraph_real_t *value, + igraph_vector_t *partition, + igraph_vector_t *partition2, + igraph_vector_t *cut, + const igraph_vector_t *capacity) { + + if (igraph_is_directed(graph)) { + if (partition || partition2 || cut) { + igraph_i_mincut_directed(graph, value, partition, partition2, cut, + capacity); + } else { + return igraph_mincut_value(graph, value, capacity); + } + } else { + IGRAPH_CHECK(igraph_i_mincut_undirected(graph, value, partition, + partition2, cut, capacity)); + return IGRAPH_SUCCESS; + } + + return 0; +} + + +static int igraph_i_mincut_value_undirected(const igraph_t *graph, + igraph_real_t *res, + const igraph_vector_t *capacity) { + return igraph_i_mincut_undirected(graph, res, 0, 0, 0, capacity); +} + +/** + * \function igraph_mincut_value + * \brief The minimum edge cut in a graph + * + * The minimum edge cut in a graph is the total minimum + * weight of the edges needed to remove from the graph to make the + * graph \em not strongly connected. (If the original graph is not + * strongly connected then this is zero.) Note that in undirected + * graphs strong connectedness is the same as weak connectedness. + * + * The minimum cut can be calculated with maximum flow + * techniques, although the current implementation does this only for + * directed graphs and a separate non-flow based implementation is + * used for undirected graphs. See Mechthild Stoer and Frank Wagner: A + * simple min-cut algorithm, Journal of the ACM 44 585--591, 1997. + * For directed graphs + * the maximum flow is calculated between a fixed vertex and all the + * other vertices in the graph and this is done in both + * directions. Then the minimum is taken to get the minimum cut. + * + * \param graph The input graph. + * \param res Pointer to a real variable, the result will be stored + * here. + * \param capacity Pointer to the capacity vector, it should contain + * the same number of non-negative numbers as the number of edges in + * the graph. If a null pointer then all edges will have unit capacity. + * \return Error code. + * + * \sa \ref igraph_mincut(), \ref igraph_maxflow_value(), \ref + * igraph_st_mincut_value(). + * + * Time complexity: O(log(|V|)*|V|^2) for undirected graphs and + * O(|V|^4) for directed graphs, but see also the discussion at the + * documentation of \ref igraph_maxflow_value(). + */ + +int igraph_mincut_value(const igraph_t *graph, igraph_real_t *res, + const igraph_vector_t *capacity) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_real_t minmaxflow, flow; + long int i; + + minmaxflow = IGRAPH_INFINITY; + + if (!igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_mincut_value_undirected(graph, res, capacity)); + return 0; + } + + for (i = 1; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_maxflow_value(graph, &flow, 0, (igraph_integer_t) i, + capacity, 0)); + if (flow < minmaxflow) { + minmaxflow = flow; + if (flow == 0) { + break; + } + } + IGRAPH_CHECK(igraph_maxflow_value(graph, &flow, (igraph_integer_t) i, 0, + capacity, 0)); + if (flow < minmaxflow) { + minmaxflow = flow; + if (flow == 0) { + break; + } + } + } + + if (res) { + *res = minmaxflow; + } + + return 0; +} + +static int igraph_i_st_vertex_connectivity_directed(const igraph_t *graph, + igraph_integer_t *res, + igraph_integer_t source, + igraph_integer_t target, + igraph_vconn_nei_t neighbors) { + + igraph_integer_t no_of_nodes = (igraph_integer_t) igraph_vcount(graph); + igraph_integer_t no_of_edges = (igraph_integer_t) igraph_ecount(graph); + igraph_vector_t edges; + igraph_real_t real_res; + igraph_t newgraph; + long int i; + igraph_bool_t conn1; + + if (source < 0 || source >= no_of_nodes || target < 0 || target >= no_of_nodes) { + IGRAPH_ERROR("Invalid source or target vertex", IGRAPH_EINVAL); + } + + switch (neighbors) { + case IGRAPH_VCONN_NEI_ERROR: + IGRAPH_CHECK(igraph_are_connected(graph, source, target, &conn1)); + if (conn1) { + IGRAPH_ERROR("vertices connected", IGRAPH_EINVAL); + return 0; + } + break; + case IGRAPH_VCONN_NEI_NEGATIVE: + IGRAPH_CHECK(igraph_are_connected(graph, source, target, &conn1)); + if (conn1) { + *res = -1; + return 0; + } + break; + case IGRAPH_VCONN_NEI_NUMBER_OF_NODES: + IGRAPH_CHECK(igraph_are_connected(graph, source, target, &conn1)); + if (conn1) { + *res = no_of_nodes; + return 0; + } + break; + case IGRAPH_VCONN_NEI_IGNORE: + break; + default: + IGRAPH_ERROR("Unknown `igraph_vconn_nei_t'", IGRAPH_EINVAL); + break; + } + + /* Create the new graph */ + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, 2 * (no_of_edges + no_of_nodes))); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + IGRAPH_CHECK(igraph_vector_resize(&edges, 2 * (no_of_edges + no_of_nodes))); + + for (i = 0; i < 2 * no_of_edges; i += 2) { + igraph_integer_t to = (igraph_integer_t) VECTOR(edges)[i + 1]; + if (to != source && to != target) { + VECTOR(edges)[i + 1] = no_of_nodes + to; + } + } + + for (i = 0; i < no_of_nodes; i++) { + VECTOR(edges)[ 2 * (no_of_edges + i) ] = no_of_nodes + i; + VECTOR(edges)[ 2 * (no_of_edges + i) + 1 ] = i; + } + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, 2 * no_of_nodes, + igraph_is_directed(graph))); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + + /* Do the maximum flow */ + + no_of_nodes = igraph_vcount(&newgraph); + no_of_edges = igraph_ecount(&newgraph); + + IGRAPH_CHECK(igraph_maxflow_value(&newgraph, &real_res, + source, target, 0, 0)); + *res = (igraph_integer_t)real_res; + + igraph_destroy(&newgraph); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_st_vertex_connectivity_undirected(const igraph_t *graph, + igraph_integer_t *res, + igraph_integer_t source, + igraph_integer_t target, + igraph_vconn_nei_t neighbors) { + + igraph_integer_t no_of_nodes = (igraph_integer_t) igraph_vcount(graph); + igraph_t newgraph; + igraph_bool_t conn; + + if (source < 0 || source >= no_of_nodes || target < 0 || target >= no_of_nodes) { + IGRAPH_ERROR("Invalid source or target vertex", IGRAPH_EINVAL); + } + + switch (neighbors) { + case IGRAPH_VCONN_NEI_ERROR: + IGRAPH_CHECK(igraph_are_connected(graph, source, target, &conn)); + if (conn) { + IGRAPH_ERROR("vertices connected", IGRAPH_EINVAL); + return 0; + } + break; + case IGRAPH_VCONN_NEI_NEGATIVE: + IGRAPH_CHECK(igraph_are_connected(graph, source, target, &conn)); + if (conn) { + *res = -1; + return 0; + } + break; + case IGRAPH_VCONN_NEI_NUMBER_OF_NODES: + IGRAPH_CHECK(igraph_are_connected(graph, source, target, &conn)); + if (conn) { + *res = no_of_nodes; + return 0; + } + break; + case IGRAPH_VCONN_NEI_IGNORE: + break; + default: + IGRAPH_ERROR("Unknown `igraph_vconn_nei_t'", IGRAPH_EINVAL); + break; + } + + IGRAPH_CHECK(igraph_copy(&newgraph, graph)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_CHECK(igraph_to_directed(&newgraph, IGRAPH_TO_DIRECTED_MUTUAL)); + + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_directed(&newgraph, res, + source, target, + IGRAPH_VCONN_NEI_IGNORE)); + + igraph_destroy(&newgraph); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_st_vertex_connectivity + * \brief The vertex connectivity of a pair of vertices + * + * The vertex connectivity of two vertices (\c source and + * \c target) is the minimum number of vertices that have to be + * deleted to eliminate all paths from \c source to \c + * target. Directed paths are considered in directed graphs. + * + * The vertex connectivity of a pair is the same as the number + * of different (ie. node-independent) paths from source to + * target. + * + * The current implementation uses maximum flow calculations to + * obtain the result. + * \param graph The input graph. + * \param res Pointer to an integer, the result will be stored here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \param neighbors A constant giving what to do if the two vertices + * are connected. Possible values: + * \c IGRAPH_VCONN_NEI_ERROR, stop with an error message, + * \c IGRAPH_VCONN_NEGATIVE, return -1. + * \c IGRAPH_VCONN_NUMBER_OF_NODES, return the number of nodes. + * \c IGRAPH_VCONN_IGNORE, ignore the fact that the two vertices + * are connected and calculated the number of vertices needed + * to eliminate all paths except for the trivial (direct) paths + * between \c source and \c vertex. TOOD: what about neighbors? + * \return Error code. + * + * Time complexity: O(|V|^3), but see the discussion at \ref + * igraph_maxflow_value(). + * + * \sa \ref igraph_vertex_connectivity(), + * \ref igraph_edge_connectivity(), + * \ref igraph_maxflow_value(). + */ + +int igraph_st_vertex_connectivity(const igraph_t *graph, + igraph_integer_t *res, + igraph_integer_t source, + igraph_integer_t target, + igraph_vconn_nei_t neighbors) { + + if (source == target) { + IGRAPH_ERROR("source and target vertices are the same", IGRAPH_EINVAL); + } + + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_directed(graph, res, + source, target, + neighbors)); + } else { + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_undirected(graph, res, + source, target, + neighbors)); + } + + return 0; +} + +static int igraph_i_vertex_connectivity_directed(const igraph_t *graph, + igraph_integer_t *res) { + + igraph_integer_t no_of_nodes = (igraph_integer_t) igraph_vcount(graph); + long int i, j; + igraph_integer_t minconn = no_of_nodes - 1, conn; + + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < no_of_nodes; j++) { + if (i == j) { + continue; + } + + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_st_vertex_connectivity(graph, &conn, + (igraph_integer_t) i, + (igraph_integer_t) j, + IGRAPH_VCONN_NEI_NUMBER_OF_NODES)); + if (conn < minconn) { + minconn = conn; + if (conn == 0) { + break; + } + } + } + if (conn == 0) { + break; + } + } + + if (res) { + *res = minconn; + } + + return 0; +} + +static int igraph_i_vertex_connectivity_undirected(const igraph_t *graph, + igraph_integer_t *res) { + igraph_t newgraph; + + IGRAPH_CHECK(igraph_copy(&newgraph, graph)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_CHECK(igraph_to_directed(&newgraph, IGRAPH_TO_DIRECTED_MUTUAL)); + + IGRAPH_CHECK(igraph_i_vertex_connectivity_directed(&newgraph, res)); + + igraph_destroy(&newgraph); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/* Use that vertex.connectivity(G) <= edge.connectivity(G) <= min(degree(G)) */ +static int igraph_i_connectivity_checks(const igraph_t *graph, + igraph_integer_t *res, + igraph_bool_t *found) { + igraph_bool_t conn; + *found = 0; + + if (igraph_vcount(graph) == 0) { + *res = 0; + *found = 1; + return 0; + } + + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_STRONG)); + if (!conn) { + *res = 0; + *found = 1; + } else { + igraph_vector_t degree; + IGRAPH_VECTOR_INIT_FINALLY(°ree, 0); + if (!igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + if (igraph_vector_min(°ree) == 1) { + *res = 1; + *found = 1; + } + } else { + /* directed, check both in- & out-degree */ + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + if (igraph_vector_min(°ree) == 1) { + *res = 1; + *found = 1; + } else { + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS)); + if (igraph_vector_min(°ree) == 1) { + *res = 1; + *found = 1; + } + } + } + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + } + return 0; +} + +/** + * \function igraph_vertex_connectivity + * The vertex connectivity of a graph + * + * The vertex connectivity of a graph is the minimum + * vertex connectivity along each pairs of vertices in the graph. + * + * The vertex connectivity of a graph is the same as group + * cohesion as defined in Douglas R. White and Frank Harary: The + * cohesiveness of blocks in social networks: node connectivity and + * conditional density, Sociological Methodology 31:305--359, 2001. + * \param graph The input graph. + * \param res Pointer to an integer, the result will be stored here. + * \param checks Logical constant. Whether to check that the graph is + * connected and also the degree of the vertices. If the graph is + * not (strongly) connected then the connectivity is obviously zero. Otherwise + * if the minimum degree is one then the vertex connectivity is also + * one. It is a good idea to perform these checks, as they can be + * done quickly compared to the connectivity calculation itself. + * They were suggested by Peter McMahan, thanks Peter. + * \return Error code. + * + * Time complexity: O(|V|^5). + * + * \sa \ref igraph_st_vertex_connectivity(), \ref igraph_maxflow_value(), + * and \ref igraph_edge_connectivity(). + */ + +int igraph_vertex_connectivity(const igraph_t *graph, igraph_integer_t *res, + igraph_bool_t checks) { + + igraph_bool_t ret = 0; + + if (checks) { + IGRAPH_CHECK(igraph_i_connectivity_checks(graph, res, &ret)); + } + + /* Are we done yet? */ + if (!ret) { + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_vertex_connectivity_directed(graph, res)); + } else { + IGRAPH_CHECK(igraph_i_vertex_connectivity_undirected(graph, res)); + } + } + + return 0; +} + +/** + * \function igraph_st_edge_connectivity + * \brief Edge connectivity of a pair of vertices + * + * The edge connectivity of two vertices (\c source and + * \c target) in a graph is the minimum number of edges that + * have to be deleted from the graph to eliminate all paths from \c + * source to \c target. + * + * This function uses the maximum flow algorithm to calculate + * the edge connectivity. + * \param graph The input graph, it has to be directed. + * \param res Pointer to an integer, the result will be stored here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \return Error code. + * + * Time complexity: O(|V|^3). + * + * \sa \ref igraph_maxflow_value(), \ref igraph_edge_connectivity(), + * \ref igraph_st_vertex_connectivity(), \ref + * igraph_vertex_connectivity(). + */ + +int igraph_st_edge_connectivity(const igraph_t *graph, igraph_integer_t *res, + igraph_integer_t source, + igraph_integer_t target) { + igraph_real_t flow; + + if (source == target) { + IGRAPH_ERROR("source and target vertices are the same", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_maxflow_value(graph, &flow, source, target, 0, 0)); + *res = (igraph_integer_t) flow; + + return 0; +} + + +/** + * \function igraph_edge_connectivity + * \brief The minimum edge connectivity in a graph. + * + * This is the minimum of the edge connectivity over all + * pairs of vertices in the graph. + * + * + * The edge connectivity of a graph is the same as group adhesion as + * defined in Douglas R. White and Frank Harary: The cohesiveness of + * blocks in social networks: node connectivity and conditional + * density, Sociological Methodology 31:305--359, 2001. + * \param graph The input graph. + * \param res Pointer to an integer, the result will be stored here. + * \param checks Logical constant. Whether to check that the graph is + * connected and also the degree of the vertices. If the graph is + * not (strongly) connected then the connectivity is obviously zero. Otherwise + * if the minimum degree is one then the edge connectivity is also + * one. It is a good idea to perform these checks, as they can be + * done quickly compared to the connectivity calculation itself. + * They were suggested by Peter McMahan, thanks Peter. + * \return Error code. + * + * Time complexity: O(log(|V|)*|V|^2) for undirected graphs and + * O(|V|^4) for directed graphs, but see also the discussion at the + * documentation of \ref igraph_maxflow_value(). + * + * \sa \ref igraph_st_edge_connectivity(), \ref igraph_maxflow_value(), + * \ref igraph_vertex_connectivity(). + */ + +int igraph_edge_connectivity(const igraph_t *graph, igraph_integer_t *res, + igraph_bool_t checks) { + igraph_bool_t ret = 0; + igraph_integer_t number_of_nodes = igraph_vcount(graph); + + /* igraph_mincut_value returns infinity for the singleton graph, + * which cannot be cast to an integer. We catch this case early + * and postulate the edge-connectivity of this graph to be 0. + * This is consistent with what other software packages return. */ + if (number_of_nodes <= 1) { + *res = 0; + return 0; + } + + /* Use that vertex.connectivity(G) <= edge.connectivity(G) <= min(degree(G)) */ + if (checks) { + IGRAPH_CHECK(igraph_i_connectivity_checks(graph, res, &ret)); + } + + if (!ret) { + igraph_real_t real_res; + IGRAPH_CHECK(igraph_mincut_value(graph, &real_res, 0)); + *res = (igraph_integer_t)real_res; + } + + return 0; +} + +/** + * \function igraph_edge_disjoint_paths + * \brief The maximum number of edge-disjoint paths between two vertices. + * + * A set of paths between two vertices is called + * edge-disjoint if they do not share any edges. The maximum number of + * edge-disjoint paths are calculated by this function using maximum + * flow techniques. Directed paths are considered in directed + * graphs. + * + * Note that the number of disjoint paths is the same as the + * edge connectivity of the two vertices using uniform edge weights. + * \param graph The input graph, can be directed or undirected. + * \param res Pointer to an integer variable, the result will be + * stored here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \return Error code. + * + * Time complexity: O(|V|^3), but see the discussion at \ref + * igraph_maxflow_value(). + * + * \sa \ref igraph_vertex_disjoint_paths(), \ref + * igraph_st_edge_connectivity(), \ref igraph_maxflow_value(). + */ + +int igraph_edge_disjoint_paths(const igraph_t *graph, igraph_integer_t *res, + igraph_integer_t source, + igraph_integer_t target) { + + igraph_real_t flow; + + if (source == target) { + IGRAPH_ERROR("Not implemented for source=target", IGRAPH_UNIMPLEMENTED); + } + + IGRAPH_CHECK(igraph_maxflow_value(graph, &flow, source, target, 0, 0)); + + *res = (igraph_integer_t) flow; + + return 0; +} + +/** + * \function igraph_vertex_disjoint_paths + * \brief Maximum number of vertex-disjoint paths between two vertices. + * + * A set of paths between two vertices is called + * vertex-disjoint if they share no vertices. The calculation is + * performed by using maximum flow techniques. + * + * Note that the number of vertex-disjoint paths is the same as + * the vertex connectivity of the two vertices in most cases (if the + * two vertices are not connected by an edge). + * \param graph The input graph. + * \param res Pointer to an integer variable, the result will be + * stored here. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \return Error code. + * + * Time complexity: O(|V|^3). + * + * \sa \ref igraph_edge_disjoint_paths(), \ref + * igraph_vertex_connectivity(), \ref igraph_maxflow_value(). + */ + +int igraph_vertex_disjoint_paths(const igraph_t *graph, igraph_integer_t *res, + igraph_integer_t source, + igraph_integer_t target) { + + igraph_bool_t conn; + + if (source == target) { + IGRAPH_ERROR("The source==target case is not implemented", + IGRAPH_UNIMPLEMENTED); + } + + igraph_are_connected(graph, source, target, &conn); + if (conn) { + /* We need to remove every (possibly directed) edge between source + and target and calculate the disjoint paths on the new + graph. Finally we add 1 for the removed connection(s). */ + igraph_es_t es; + igraph_vector_t v; + igraph_t newgraph; + IGRAPH_VECTOR_INIT_FINALLY(&v, 2); + VECTOR(v)[0] = source; + VECTOR(v)[1] = target; + IGRAPH_CHECK(igraph_es_multipairs(&es, &v, IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + + IGRAPH_CHECK(igraph_copy(&newgraph, graph)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_CHECK(igraph_delete_edges(&newgraph, es)); + + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_directed(&newgraph, res, + source, target, + IGRAPH_VCONN_NEI_IGNORE)); + } else { + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_undirected(&newgraph, res, + source, target, + IGRAPH_VCONN_NEI_IGNORE)); + } + + if (res) { + *res += 1; + } + + IGRAPH_FINALLY_CLEAN(3); + igraph_destroy(&newgraph); + igraph_es_destroy(&es); + igraph_vector_destroy(&v); + } + + /* These do nothing if the two vertices are connected, + so it is safe to call them. */ + + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_directed(graph, res, + source, target, + IGRAPH_VCONN_NEI_IGNORE)); + } else { + IGRAPH_CHECK(igraph_i_st_vertex_connectivity_undirected(graph, res, + source, target, + IGRAPH_VCONN_NEI_IGNORE)); + } + + return 0; +} + +/** + * \function igraph_adhesion + * \brief Graph adhesion, this is (almost) the same as edge connectivity. + * + * This quantity is defined by White and Harary in + * The cohesiveness of blocks in social networks: node connectivity and + * conditional density, (Sociological Methodology 31:305--359, 2001) + * and basically it is the edge connectivity of the graph + * with uniform edge weights. + * \param graph The input graph, either directed or undirected. + * \param res Pointer to an integer, the result will be stored here. + * \param checks Logical constant. Whether to check that the graph is + * connected and also the degree of the vertices. If the graph is + * not (strongly) connected then the adhesion is obviously zero. Otherwise + * if the minimum degree is one then the adhesion is also + * one. It is a good idea to perform these checks, as they can be + * done quickly compared to the edge connectivity calculation itself. + * They were suggested by Peter McMahan, thanks Peter. +* \return Error code. + * + * Time complexity: O(log(|V|)*|V|^2) for undirected graphs and + * O(|V|^4) for directed graphs, but see also the discussion at the + * documentation of \ref igraph_maxflow_value(). + * + * \sa \ref igraph_cohesion(), \ref igraph_maxflow_value(), \ref + * igraph_edge_connectivity(), \ref igraph_mincut_value(). + */ + +int igraph_adhesion(const igraph_t *graph, igraph_integer_t *res, + igraph_bool_t checks) { + return igraph_edge_connectivity(graph, res, checks); +} + +/** + * \function igraph_cohesion + * \brief Graph cohesion, this is the same as vertex connectivity. + * + * This quantity was defined by White and Harary in The + * cohesiveness of blocks in social networks: node connectivity and + * conditional density, (Sociological Methodology 31:305--359, 2001) + * and it is the same as the vertex connectivity of a + * graph. + * \param graph The input graph. + * \param res Pointer to an integer variable, the result will be + * stored here. + * \param checks Logical constant. Whether to check that the graph is + * connected and also the degree of the vertices. If the graph is + * not (strongly) connected then the cohesion is obviously zero. Otherwise + * if the minimum degree is one then the cohesion is also + * one. It is a good idea to perform these checks, as they can be + * done quickly compared to the vertex connectivity calculation itself. + * They were suggested by Peter McMahan, thanks Peter. + * \return Error code. + * + * Time complexity: O(|V|^4), |V| is the number of vertices. In + * practice it is more like O(|V|^2), see \ref igraph_maxflow_value(). + * + * \sa \ref igraph_vertex_connectivity(), \ref igraph_adhesion(), + * \ref igraph_maxflow_value(). + */ + +int igraph_cohesion(const igraph_t *graph, igraph_integer_t *res, + igraph_bool_t checks) { + + IGRAPH_CHECK(igraph_vertex_connectivity(graph, res, checks)); + return 0; +} + +/** + * \function igraph_gomory_hu_tree + * \brief Gomory-Hu tree of a graph. + * + * + * The Gomory-Hu tree is a concise representation of the value of all the + * maximum flows (or minimum cuts) in a graph. The vertices of the tree + * correspond exactly to the vertices of the original graph in the same order. + * Edges of the Gomory-Hu tree are annotated by flow values. The value of + * the maximum flow (or minimum cut) between an arbitrary (u,v) vertex + * pair in the original graph is then given by the minimum flow value (i.e. + * edge annotation) along the shortest path between u and v in the + * Gomory-Hu tree. + * + * This implementation uses Gusfield's algorithm to construct the + * Gomory-Hu tree. See the following paper for more details: + * + * + * Gusfield D: Very simple methods for all pairs network flow analysis. SIAM J + * Comput 19(1):143-155, 1990. + * + * \param graph The input graph. + * \param tree Pointer to an uninitialized graph; the result will be + * stored here. + * \param flows Pointer to an uninitialized vector; the flow values + * corresponding to each edge in the Gomory-Hu tree will + * be returned here. You may pass a NULL pointer here if you are + * not interested in the flow values. + * \param capacity Vector containing the capacity of the edges. If NULL, then + * every edge is considered to have capacity 1.0. + * \return Error code. + * + * Time complexity: O(|V|^4) since it performs a max-flow calculation + * between vertex zero and every other vertex and max-flow is + * O(|V|^3). + * + * \sa \ref igraph_maxflow() + */ +int igraph_gomory_hu_tree(const igraph_t *graph, igraph_t *tree, + igraph_vector_t *flows, const igraph_vector_t *capacity) { + + igraph_integer_t no_of_nodes = igraph_vcount(graph); + igraph_integer_t source, target, mid, i, n; + igraph_vector_t neighbors; + igraph_vector_t flow_values; + igraph_vector_t partition; + igraph_vector_t partition2; + igraph_real_t flow_value; + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Gomory-Hu tree can only be calculated for undirected graphs", + IGRAPH_EINVAL); + } + + /* Allocate memory */ + IGRAPH_VECTOR_INIT_FINALLY(&neighbors, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&flow_values, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&partition, 0); + IGRAPH_VECTOR_INIT_FINALLY(&partition2, 0); + + /* Initialize the tree: every edge points to node 0 */ + /* Actually, this is done implicitly since both 'neighbors' and 'flow_values' are + * initialized to zero already */ + + /* For each source vertex except vertex zero... */ + for (source = 1; source < no_of_nodes; source++) { + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_PROGRESS("Gomory-Hu tree", (100.0 * (source - 1)) / (no_of_nodes - 1), 0); + + /* Find its current neighbor in the tree */ + target = VECTOR(neighbors)[(long int)source]; + + /* Find the maximum flow between source and target */ + IGRAPH_CHECK(igraph_maxflow(graph, &flow_value, 0, 0, &partition, &partition2, + source, target, capacity, 0)); + + /* Store the maximum flow and determine which side each node is on */ + VECTOR(flow_values)[(long int)source] = flow_value; + + /* Update the tree */ + /* igraph_maxflow() guarantees that the source vertex will be in &partition + * and not in &partition2 */ + n = igraph_vector_size(&partition); + for (i = 0; i < n; i++) { + mid = VECTOR(partition)[i]; + if (mid > source && VECTOR(neighbors)[(long int)mid] == target) { + VECTOR(neighbors)[(long int)mid] = source; + } + } + } + + IGRAPH_PROGRESS("Gomory-Hu tree", 100.0, 0); + + /* Re-use the 'partition' vector as an edge list now */ + IGRAPH_CHECK(igraph_vector_resize(&partition, 2 * (no_of_nodes - 1))); + for (i = 1, mid = 0; i < no_of_nodes; i++, mid += 2) { + VECTOR(partition)[(long int)mid] = i; + VECTOR(partition)[(long int)mid + 1] = VECTOR(neighbors)[(long int)i]; + } + + /* Create the tree graph; we use igraph_subgraph_edges here to keep the + * graph and vertex attributes */ + IGRAPH_CHECK(igraph_subgraph_edges(graph, tree, igraph_ess_none(), 0)); + IGRAPH_CHECK(igraph_add_edges(tree, &partition, 0)); + + /* Free the allocated memory */ + igraph_vector_destroy(&partition2); + igraph_vector_destroy(&partition); + igraph_vector_destroy(&neighbors); + IGRAPH_FINALLY_CLEAN(3); + + /* Return the flow values to the caller */ + if (flows != 0) { + IGRAPH_CHECK(igraph_vector_update(flows, &flow_values)); + if (no_of_nodes > 0) { + igraph_vector_remove(flows, 0); + } + } + + /* Free the remaining allocated memory */ + igraph_vector_destroy(&flow_values); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + diff --git a/src/foreign-dl-header.h b/src/foreign-dl-header.h new file mode 100644 index 0000000..6fdd692 --- /dev/null +++ b/src/foreign-dl-header.h @@ -0,0 +1,42 @@ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_types_internal.h" + +typedef enum { IGRAPH_DL_MATRIX, + IGRAPH_DL_EDGELIST1, IGRAPH_DL_NODELIST1 + } igraph_i_dl_type_t; + +typedef struct { + void *scanner; + int eof; + int mode; + long int n; + long int from, to; + igraph_vector_t edges; + igraph_vector_t weights; + igraph_strvector_t labels; + igraph_trie_t trie; + igraph_i_dl_type_t type; + char errmsg[300]; +} igraph_i_dl_parsedata_t; diff --git a/src/foreign-dl-lexer.l b/src/foreign-dl-lexer.l new file mode 100644 index 0000000..4307323 --- /dev/null +++ b/src/foreign-dl-lexer.l @@ -0,0 +1,140 @@ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +%{ + +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "config.h" +#include +#include +#include "foreign-dl-header.h" +#include "foreign-dl-parser.h" +#define YY_EXTRA_TYPE igraph_i_dl_parsedata_t* +#define YY_USER_ACTION yylloc->first_line = yylineno; +/* We assume that 'file' is 'stderr' here. */ +#ifdef USING_R +#define fprintf(file, msg, ...) (1) +#endif +#ifdef stdout +# undef stdout +#endif +#define stdout 0 +#define exit(code) igraph_error("Fatal error in DL parser", __FILE__, \ + __LINE__, IGRAPH_PARSEERROR); +%} + +%option noyywrap +%option prefix="igraph_dl_yy" +%option outfile="lex.yy.c" +%option nounput +%option noinput +%option nodefault +%option reentrant +%option bison-bridge +%option bison-locations + +digit [0-9] +whitespace [ \t\v\f] + +%x LABELM FULLMATRIX EDGELIST NODELIST + +%% + +<*>\n\r|\r\n|\r|\n { return NEWLINE; } + +[dD][lL]{whitespace}+ { return DL; } +[nN]{whitespace}*[=]{whitespace}* { + return NEQ; } +{digit}+ { return NUM; } + +[dD][aA][tT][aA][:] { + switch (yyextra->mode) { + case 0: BEGIN(FULLMATRIX); + break; + case 1: BEGIN(EDGELIST); + break; + case 2: BEGIN(NODELIST); + break; + } + return DATA; } + +[lL][aA][bB][eE][lL][sS]: { BEGIN(LABELM); return LABELS; } +[lL][aA][bB][eE][lL][sS]{whitespace}+[eE][mM][bB][eE][dD][dD][eE][dD]:?{whitespace}* { + return LABELSEMBEDDED; } +[fF][oO][rR][mM][aA][tT]{whitespace}*[=]{whitespace}*[fF][uU][lL][lL][mM][aA][tT][rR][iI][xX]{whitespace}* { + yyextra->mode=0; return FORMATFULLMATRIX; } +[fF][oO][rR][mM][aA][tT]{whitespace}*[=]{whitespace}*[eE][dD][gG][eE][lL][iI][sS][tT][1]{whitespace}* { + yyextra->mode=1; return FORMATEDGELIST1; } +[fF][oO][rR][mM][aA][tT]{whitespace}*[=]{whitespace}*[nN][oO][dD][eE][lL][iI][sS][tT][1]{whitespace}* { + yyextra->mode=2; return FORMATNODELIST1; } + +[, ] { /* eaten up */ } +[^, \t\n\r\f\v]+{whitespace}* { return LABEL; } + +{digit}{whitespace}* { return DIGIT; } +[^ \t\n\r\v\f,]+ { return LABEL; } +{whitespace} { } + +\-?{digit}+(\.{digit}+)?([eE](\+|\-)?{digit}+)? { return NUM; } +[^ \t\n\r\v\f,]+ { return LABEL; } +{whitespace}* { } + +{digit}+ { return NUM; } +[^ \t\r\n\v\f,]+ { return LABEL; } +{whitespace}* { } + +{whitespace}+ { /* eaten up */ } + +<> { + if (yyextra->eof) { + yyterminate(); + } else { + yyextra->eof=1; + BEGIN(INITIAL); + return EOFF; + } + } + +<*>. { return 0; } + +. { return ERROR; } diff --git a/src/foreign-dl-parser.y b/src/foreign-dl-parser.y new file mode 100644 index 0000000..3282b6b --- /dev/null +++ b/src/foreign-dl-parser.y @@ -0,0 +1,309 @@ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +%{ + +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "config.h" +#include "igraph_hacks_internal.h" +#include "igraph_math.h" +#include "igraph_types_internal.h" +#include "foreign-dl-header.h" +#include "foreign-dl-parser.h" +#include + +#define yyscan_t void* + +int igraph_dl_yylex(YYSTYPE* lvalp, YYLTYPE* llocp, void* scanner); +int igraph_dl_yyerror(YYLTYPE* locp, igraph_i_dl_parsedata_t* context, + const char *s); +char *igraph_dl_yyget_text (yyscan_t yyscanner ); +int igraph_dl_yyget_leng (yyscan_t yyscanner ); + +int igraph_i_dl_add_str(char *newstr, int length, + igraph_i_dl_parsedata_t *context); +int igraph_i_dl_add_edge(long int from, long int to, + igraph_i_dl_parsedata_t *context); +int igraph_i_dl_add_edge_w(long int from, long int to, + igraph_real_t weight, + igraph_i_dl_parsedata_t *context); + +extern igraph_real_t igraph_pajek_get_number(const char *str, long int len); + +#define scanner context->scanner + +%} + +%pure-parser +%output="y.tab.c" +%name-prefix="igraph_dl_yy" +%defines +%locations +%error-verbose +%parse-param { igraph_i_dl_parsedata_t* context } +%lex-param { void* scanner } + +%union { + long int integer; + igraph_real_t real; +}; + +%type integer elabel; +%type weight; + +%token NUM +%token NEWLINE +%token DL +%token NEQ +%token DATA +%token LABELS +%token LABELSEMBEDDED +%token FORMATFULLMATRIX +%token FORMATEDGELIST1 +%token FORMATNODELIST1 +%token DIGIT +%token LABEL +%token EOFF +%token ERROR + +%% + +input: DL NEQ integer NEWLINE rest trail eof { context->n=$3; }; + +trail: | trail newline; + +eof: | EOFF; + +rest: formfullmatrix { context->type=IGRAPH_DL_MATRIX; } + | edgelist1 { context->type=IGRAPH_DL_EDGELIST1; } + | nodelist1 { context->type=IGRAPH_DL_NODELIST1; } +; + +formfullmatrix: FORMATFULLMATRIX newline fullmatrix {} | fullmatrix {} ; + +newline: | NEWLINE ; + +fullmatrix: DATA newline fullmatrixdata { } + | LABELS newline labels newline DATA newline fullmatrixdata { } + | LABELSEMBEDDED newline DATA newline labeledfullmatrixdata { } +; + +labels: {} /* nothing, empty matrix */ + | labels newline LABEL { + igraph_i_dl_add_str(igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner), + context); } +; + +fullmatrixdata: {} | fullmatrixdata zerooneseq NEWLINE { + context->from += 1; + context->to = 0; + } ; + +zerooneseq: | zerooneseq zeroone { } ; + +zeroone: DIGIT { + if (igraph_dl_yyget_text(scanner)[0]=='1') { + IGRAPH_CHECK(igraph_vector_push_back(&context->edges, + context->from)); + IGRAPH_CHECK(igraph_vector_push_back(&context->edges, + context->to)); + } + context->to += 1; +} ; + +labeledfullmatrixdata: reallabeledfullmatrixdata {} ; + +reallabeledfullmatrixdata: labelseq NEWLINE labeledmatrixlines {} ; + +labelseq: | labelseq newline label ; + +label: LABEL { igraph_i_dl_add_str(igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner), + context); }; + +labeledmatrixlines: labeledmatrixline { + context->from += 1; + context->to = 0; + } + | labeledmatrixlines labeledmatrixline { + context->from += 1; + context->to = 0; + }; + +labeledmatrixline: LABEL zerooneseq NEWLINE { } ; + +/*-----------------------------------------------------------*/ + +edgelist1: FORMATEDGELIST1 newline edgelist1rest {} ; + +edgelist1rest: DATA newline edgelist1data {} + | LABELS newline labels newline DATA newline edgelist1data {} + | LABELSEMBEDDED newline DATA newline labelededgelist1data {} + | LABELS newline labels newline LABELSEMBEDDED newline DATA newline labelededgelist1data {} + | LABELSEMBEDDED newline LABELS newline labels newline DATA newline labelededgelist1data {} +; + +edgelist1data: {} /* nothing, empty graph */ + | edgelist1data edgelist1dataline {} +; + +edgelist1dataline: integer integer weight NEWLINE { + igraph_i_dl_add_edge_w($1-1, $2-1, $3, context); } + | integer integer NEWLINE { + igraph_i_dl_add_edge($1-1, $2-1, context); +} ; + +integer: NUM { $$=igraph_pajek_get_number(igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner)); }; + +labelededgelist1data: {} /* nothing, empty graph */ + | labelededgelist1data labelededgelist1dataline {} +; + +labelededgelist1dataline: elabel elabel weight NEWLINE { + igraph_i_dl_add_edge_w($1, $2, $3, context); } + | elabel elabel NEWLINE { + igraph_i_dl_add_edge($1, $2, context); + }; + +weight: NUM { $$=igraph_pajek_get_number(igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner)); }; + +elabel: LABEL { + /* Copy label list to trie, if needed */ + if (igraph_strvector_size(&context->labels) != 0) { + long int i, id, n=igraph_strvector_size(&context->labels); + for (i=0; itrie, + STR(context->labels, i), &id); + } + igraph_strvector_clear(&context->labels); + } + igraph_trie_get2(&context->trie, igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner), &$$); + }; + +/*-----------------------------------------------------------*/ + +nodelist1: FORMATNODELIST1 newline nodelist1rest {} ; + +nodelist1rest: DATA nodelist1data {} + | LABELS newline labels newline DATA newline nodelist1data {} + | LABELSEMBEDDED newline DATA newline labelednodelist1data {} + | LABELS newline labels newline LABELSEMBEDDED newline DATA newline labelednodelist1data {} + | LABELSEMBEDDED newline LABELS newline labels newline DATA newline labelednodelist1data {} +; + +nodelist1data: {} /* nothing, empty graph */ + | nodelist1data nodelist1dataline {} +; + +nodelist1dataline: from tolist NEWLINE {} ; + +from: NUM { context->from=igraph_pajek_get_number(igraph_dl_yyget_text(scanner), + igraph_dl_yyget_leng(scanner)); } ; + +tolist: {} | tolist integer { + IGRAPH_CHECK(igraph_vector_push_back(&context->edges, + context->from-1)); + IGRAPH_CHECK(igraph_vector_push_back(&context->edges, $2-1)); + } ; + +labelednodelist1data: {} /* nothing, empty graph */ + | labelednodelist1data labelednodelist1dataline {} +; + +labelednodelist1dataline: fromelabel labeltolist NEWLINE { } ; + +fromelabel: elabel { + context->from=$1; + }; + +labeltolist: | labeltolist elabel { + IGRAPH_CHECK(igraph_vector_push_back(&context->edges, + context->from)); + IGRAPH_CHECK(igraph_vector_push_back(&context->edges, $2)); + } ; + +%% + +int igraph_dl_yyerror(YYLTYPE* locp, igraph_i_dl_parsedata_t* context, + const char *s) { + snprintf(context->errmsg, + sizeof(context->errmsg)/sizeof(char)-1, + "%s in line %i", s, locp->first_line); + return 0; +} + +int igraph_i_dl_add_str(char *newstr, int length, + igraph_i_dl_parsedata_t *context) { + int tmp=newstr[length]; + newstr[length]='\0'; + IGRAPH_CHECK(igraph_strvector_add(&context->labels, newstr)); + newstr[length]=tmp; + return 0; +} + +int igraph_i_dl_add_edge(long int from, long int to, + igraph_i_dl_parsedata_t *context) { + IGRAPH_CHECK(igraph_vector_push_back(&context->edges, from)); + IGRAPH_CHECK(igraph_vector_push_back(&context->edges, to)); + return 0; +} + +int igraph_i_dl_add_edge_w(long int from, long int to, + igraph_real_t weight, + igraph_i_dl_parsedata_t *context) { + long int n=igraph_vector_size(&context->weights); + long int n2=igraph_vector_size(&context->edges)/2; + if (n != n2) { + igraph_vector_resize(&context->weights, n2); + for (; nweights)[n]=IGRAPH_NAN; + } + } + IGRAPH_CHECK(igraph_i_dl_add_edge(from, to, context)); + IGRAPH_CHECK(igraph_vector_push_back(&context->weights, weight)); + return 0; +} diff --git a/src/foreign-gml-header.h b/src/foreign-gml-header.h new file mode 100644 index 0000000..2a5df36 --- /dev/null +++ b/src/foreign-gml-header.h @@ -0,0 +1,30 @@ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_gml_tree.h" + +typedef struct { + void *scanner; + int eof; + char errmsg[300]; + igraph_gml_tree_t *tree; +} igraph_i_gml_parsedata_t; diff --git a/src/foreign-gml-lexer.l b/src/foreign-gml-lexer.l new file mode 100644 index 0000000..b2de09d --- /dev/null +++ b/src/foreign-gml-lexer.l @@ -0,0 +1,99 @@ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +%{ + +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "config.h" +#include +#include "foreign-gml-header.h" +#include "foreign-gml-parser.h" +#define YY_EXTRA_TYPE igraph_i_gml_parsedata_t* +#define YY_USER_ACTION yylloc->first_line = yylineno; +/* We assume that 'file' is 'stderr' here. */ +#ifdef USING_R +#define fprintf(file, msg, ...) (1) +#endif +#ifdef stdout +# undef stdout +#endif +#define stdout 0 +#define exit(code) igraph_error("Fatal error in DL parser", __FILE__, \ + __LINE__, IGRAPH_PARSEERROR); +%} + +%option noyywrap +%option prefix="igraph_gml_yy" +%option outfile="lex.yy.c" +%option nounput +%option noinput +%option nodefault +%option reentrant +%option bison-bridge +%option bison-locations + +digit [0-9] +whitespace [ \r\n\t] + +%% + +^#[^\n\r]*[\n]|[\r] { /* comments ignored */ } + +\"[^\"]*\" { return STRING; } +\-?{digit}+(\.{digit}+)?([eE](\+|\-)?{digit}+)? { return NUM; } +[a-zA-Z_][a-zA-Z_0-9]* { return KEYWORD; } +\[ { return LISTOPEN; } +\] { return LISTCLOSE; } +\n\r|\r\n|\r|\n { } +{whitespace} { /* other whitespace ignored */ } + +<> { + if (yyextra->eof) { + yyterminate(); + } else { + yyextra->eof=1; + return EOFF; + } + } +. { return ERROR; } +%% diff --git a/src/foreign-gml-parser.y b/src/foreign-gml-parser.y new file mode 100644 index 0000000..ddaba88 --- /dev/null +++ b/src/foreign-gml-parser.y @@ -0,0 +1,258 @@ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +%{ + +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include + +#include "igraph_error.h" +#include "igraph_memory.h" +#include "config.h" +#include "igraph_hacks_internal.h" +#include "igraph_math.h" +#include "igraph_gml_tree.h" +#include "foreign-gml-header.h" +#include "foreign-gml-parser.h" + +#define yyscan_t void* + +int igraph_gml_yylex(YYSTYPE* lvalp, YYLTYPE* llocp, void *scanner); +int igraph_gml_yyerror(YYLTYPE* locp, igraph_i_gml_parsedata_t *context, + const char *s); +char *igraph_gml_yyget_text (yyscan_t yyscanner ); +int igraph_gml_yyget_leng (yyscan_t yyscanner ); +void igraph_i_gml_get_keyword(char *s, int len, void *res); +void igraph_i_gml_get_string(char *s, int len, void *res); +double igraph_i_gml_get_real(char *s, int len); +igraph_gml_tree_t *igraph_i_gml_make_numeric(char* s, int len, double value); +igraph_gml_tree_t *igraph_i_gml_make_numeric2(char* s, int len, + char *v, int vlen); +igraph_gml_tree_t *igraph_i_gml_make_string(char* s, int len, + char *value, int valuelen); +igraph_gml_tree_t *igraph_i_gml_make_list(char* s, int len, + igraph_gml_tree_t *list); +igraph_gml_tree_t *igraph_i_gml_merge(igraph_gml_tree_t *t1, igraph_gml_tree_t* t2); + +#define scanner context->scanner +#define USE(x) /*(x)*/ + +%} + +%pure-parser +%output="y.tab.c" +%name-prefix="igraph_gml_yy" +%defines +%locations +%error-verbose +%parse-param { igraph_i_gml_parsedata_t* context } +%lex-param { void *scanner } + +%union { + struct { + char *s; + int len; + } str; + void *tree; + double real; +} + +%type list; +%type keyvalue; +%type key; +%type num; +%type string; + +%token STRING +%token NUM +%token KEYWORD +%token LISTOPEN +%token LISTCLOSE +%token EOFF +%token ERROR + +%destructor { igraph_Free($$.s); } string key KEYWORD; +%destructor { igraph_gml_tree_destroy($$); } list keyvalue; + +%% + +input: list { context->tree=$1; } + | list EOFF { context->tree=$1; } +; + +list: keyvalue { $$=$1; } + | list keyvalue { $$=igraph_i_gml_merge($1, $2); }; + +keyvalue: key num + { $$=igraph_i_gml_make_numeric($1.s, $1.len, $2); } + | key string + { $$=igraph_i_gml_make_string($1.s, $1.len, $2.s, $2.len); } + | key LISTOPEN list LISTCLOSE + { $$=igraph_i_gml_make_list($1.s, $1.len, $3); } + | key key + { $$=igraph_i_gml_make_numeric2($1.s, $1.len, $2.s, $2.len); } +; + +key: KEYWORD { igraph_i_gml_get_keyword(igraph_gml_yyget_text(scanner), + igraph_gml_yyget_leng(scanner), + &$$); USE($1) }; +num : NUM { $$=igraph_i_gml_get_real(igraph_gml_yyget_text(scanner), + igraph_gml_yyget_leng(scanner)); }; + +string: STRING { igraph_i_gml_get_string(igraph_gml_yyget_text(scanner), + igraph_gml_yyget_leng(scanner), + &$$); }; + +%% + +int igraph_gml_yyerror(YYLTYPE* locp, igraph_i_gml_parsedata_t *context, + const char *s) { + snprintf(context->errmsg, sizeof(context->errmsg)/sizeof(char)-1, + "Parse error in GML file, line %i (%s)", + locp->first_line, s); + return 0; +} + +void igraph_i_gml_get_keyword(char *s, int len, void *res) { + struct { char *s; int len; } *p=res; + p->s=igraph_Calloc(len+1, char); + if (!p->s) { + igraph_error("Cannot read GML file", __FILE__, __LINE__, IGRAPH_PARSEERROR); + } + memcpy(p->s, s, sizeof(char)*len); + p->s[len]='\0'; + p->len=len; +} + +void igraph_i_gml_get_string(char *s, int len, void *res) { + struct { char *s; int len; } *p=res; + p->s=igraph_Calloc(len-1, char); + if (!p->s) { + igraph_error("Cannot read GML file", __FILE__, __LINE__, IGRAPH_PARSEERROR); + } + memcpy(p->s, s+1, sizeof(char)*(len-2)); + p->s[len-2]='\0'; + p->len=len-2; +} + +double igraph_i_gml_get_real(char *s, int len) { + igraph_real_t num; + char tmp=s[len]; + s[len]='\0'; + sscanf(s, "%lf", &num); + s[len]=tmp; + return num; +} + +igraph_gml_tree_t *igraph_i_gml_make_numeric(char* s, int len, double value) { + igraph_gml_tree_t *t=igraph_Calloc(1, igraph_gml_tree_t); + if (!t) { + igraph_error("Cannot build GML tree", __FILE__, __LINE__, IGRAPH_ENOMEM); + return 0; + } + if (floor(value)==value) { + igraph_gml_tree_init_integer(t, s, len, value); + } else { + igraph_gml_tree_init_real(t, s, len, value); + } + + return t; +} + +igraph_gml_tree_t *igraph_i_gml_make_numeric2(char* s, int len, + char *v, int vlen) { + igraph_gml_tree_t *t=igraph_Calloc(1, igraph_gml_tree_t); + char tmp=v[vlen]; + igraph_real_t value=0; + if (!t) { + igraph_error("Cannot build GML tree", __FILE__, __LINE__, IGRAPH_ENOMEM); + return 0; + } + v[vlen]='\0'; + if (strcasecmp(v, "inf")) { + value=IGRAPH_INFINITY; + } else if (strcasecmp(v, "nan")) { + value=IGRAPH_NAN; + } else { + igraph_error("Parse error", __FILE__, __LINE__, IGRAPH_PARSEERROR); + } + v[vlen]=tmp; + igraph_gml_tree_init_real(t, s, len, value); + + return t; +} + +igraph_gml_tree_t *igraph_i_gml_make_string(char* s, int len, + char *value, int valuelen) { + igraph_gml_tree_t *t=igraph_Calloc(1, igraph_gml_tree_t); + if (!t) { + igraph_error("Cannot build GML tree", __FILE__, __LINE__, IGRAPH_ENOMEM); + return 0; + } + igraph_gml_tree_init_string(t, s, len, value, valuelen); + + return t; +} + +igraph_gml_tree_t *igraph_i_gml_make_list(char* s, int len, + igraph_gml_tree_t *list) { + + igraph_gml_tree_t *t=igraph_Calloc(1, igraph_gml_tree_t); + if (!t) { + igraph_error("Cannot build GML tree", __FILE__, __LINE__, IGRAPH_ENOMEM); + return 0; + } + igraph_gml_tree_init_tree(t, s, len, list); + + return t; +} + +igraph_gml_tree_t *igraph_i_gml_merge(igraph_gml_tree_t *t1, igraph_gml_tree_t* t2) { + + igraph_gml_tree_mergedest(t1, t2); + igraph_Free(t2); + + return t1; +} diff --git a/src/foreign-graphml.c b/src/foreign-graphml.c new file mode 100644 index 0000000..7581d22 --- /dev/null +++ b/src/foreign-graphml.c @@ -0,0 +1,1847 @@ +/* -*- mode: C -*- */ +/* + IGraph R package. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include "igraph_foreign.h" +#include "config.h" +#include /* isnan */ +#include "igraph_math.h" +#include "igraph_attributes.h" +#include "igraph_interface.h" +#include "igraph_types_internal.h" + +#include /* isspace */ +#include +#include "igraph_memory.h" +#include /* va_start & co */ + +#define GRAPHML_NAMESPACE_URI "http://graphml.graphdrawing.org/xmlns" + +#if HAVE_LIBXML == 1 +#include +#include + +xmlEntity blankEntityStruct = { +#ifndef XML_WITHOUT_CORBA + 0, +#endif + XML_ENTITY_DECL, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + XML_EXTERNAL_GENERAL_PARSED_ENTITY, + 0, + 0, + 0, + 0, + 0, + 1 +}; + +xmlEntityPtr blankEntity = &blankEntityStruct; + +#define GRAPHML_PARSE_ERROR_WITH_CODE(state, msg, code) do { \ + if (state->successful) { \ + igraph_error(msg, __FILE__, __LINE__, code); \ + igraph_i_graphml_sax_handler_error(state, msg); \ + } \ + } while (0) +#define GRAPHML_PARSE_ERROR(state, msg) \ + GRAPHML_PARSE_ERROR_WITH_CODE(state, msg, IGRAPH_PARSEERROR) +#define RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, msg, code) do { \ + GRAPHML_PARSE_ERROR_WITH_CODE(state, msg, code); \ + return; \ + } while (1) +#define RETURN_GRAPHML_PARSE_ERROR(state, msg) do { \ + GRAPHML_PARSE_ERROR(state, msg); \ + return; \ + } while (1) + +/* TODO: proper error handling */ + +typedef struct igraph_i_graphml_attribute_record_t { + const char *id; /* GraphML id */ + enum { I_GRAPHML_BOOLEAN, I_GRAPHML_INTEGER, I_GRAPHML_LONG, + I_GRAPHML_FLOAT, I_GRAPHML_DOUBLE, I_GRAPHML_STRING, + I_GRAPHML_UNKNOWN_TYPE + } type; /* GraphML type */ + union { + igraph_real_t as_numeric; + igraph_bool_t as_boolean; + char* as_string; + } default_value; /* Default value of the attribute, if any */ + igraph_attribute_record_t record; +} igraph_i_graphml_attribute_record_t; + +struct igraph_i_graphml_parser_state { + enum { START, INSIDE_GRAPHML, INSIDE_GRAPH, INSIDE_NODE, INSIDE_EDGE, + INSIDE_KEY, INSIDE_DEFAULT, INSIDE_DATA, FINISH, UNKNOWN, ERROR + } st; + igraph_t *g; + igraph_trie_t node_trie; + igraph_strvector_t edgeids; + igraph_vector_t edgelist; + igraph_vector_int_t prev_state_stack; + unsigned int unknown_depth; + int index; + igraph_bool_t successful, edges_directed, destroyed; + igraph_trie_t v_names; + igraph_vector_ptr_t v_attrs; + igraph_trie_t e_names; + igraph_vector_ptr_t e_attrs; + igraph_trie_t g_names; + igraph_vector_ptr_t g_attrs; + igraph_i_graphml_attribute_record_t* current_attr_record; + xmlChar *data_key; + igraph_attribute_elemtype_t data_type; + char *error_message; + char *data_char; + long int act_node; + igraph_bool_t ignore_namespaces; +}; + +static void igraph_i_report_unhandled_attribute_target(const char* target, + const char* file, int line) { + igraph_warningf("Attribute target '%s' is not handled; ignoring corresponding " + "attribute specifications", file, line, 0, target); +} + +static igraph_real_t igraph_i_graphml_parse_numeric(const char* char_data, + igraph_real_t default_value) { + double result; + + if (char_data == 0) { + return default_value; + } + + if (sscanf(char_data, "%lf", &result) == 0) { + return default_value; + } + + return result; +} + +static igraph_bool_t igraph_i_graphml_parse_boolean(const char* char_data, + igraph_bool_t default_value) { + int value; + if (char_data == 0) { + return default_value; + } + if (!strcasecmp("true", char_data)) { + return 1; + } + if (!strcasecmp("yes", char_data)) { + return 1; + } + if (!strcasecmp("false", char_data)) { + return 0; + } + if (!strcasecmp("no", char_data)) { + return 0; + } + if (sscanf(char_data, "%d", &value) == 0) { + return default_value; + } + return value != 0; +} + +static void igraph_i_graphml_attribute_record_destroy(igraph_i_graphml_attribute_record_t* rec) { + if (rec->record.type == IGRAPH_ATTRIBUTE_NUMERIC) { + if (rec->record.value != 0) { + igraph_vector_destroy((igraph_vector_t*)rec->record.value); + igraph_Free(rec->record.value); + } + } else if (rec->record.type == IGRAPH_ATTRIBUTE_STRING) { + if (rec->record.value != 0) { + igraph_strvector_destroy((igraph_strvector_t*)rec->record.value); + if (rec->default_value.as_string != 0) { + igraph_Free(rec->default_value.as_string); + } + igraph_Free(rec->record.value); + } + } else if (rec->record.type == IGRAPH_ATTRIBUTE_BOOLEAN) { + if (rec->record.value != 0) { + igraph_vector_bool_destroy((igraph_vector_bool_t*)rec->record.value); + igraph_Free(rec->record.value); + } + } + if (rec->id != 0) { + igraph_Free(rec->id); + } + if (rec->record.name != 0) { + igraph_Free(rec->record.name); + } +} + +static void igraph_i_graphml_destroy_state(struct igraph_i_graphml_parser_state* state) { + if (state->destroyed) { + return; + } + state->destroyed = 1; + + igraph_trie_destroy(&state->node_trie); + igraph_strvector_destroy(&state->edgeids); + igraph_trie_destroy(&state->v_names); + igraph_trie_destroy(&state->e_names); + igraph_trie_destroy(&state->g_names); + igraph_vector_destroy(&state->edgelist); + igraph_vector_int_destroy(&state->prev_state_stack); + + if (state->error_message) { + free(state->error_message); + } + if (state->data_key) { + free(state->data_key); + } + if (state->data_char) { + free(state->data_char); + } + + igraph_vector_ptr_destroy_all(&state->v_attrs); + igraph_vector_ptr_destroy_all(&state->e_attrs); + igraph_vector_ptr_destroy_all(&state->g_attrs); + + IGRAPH_FINALLY_CLEAN(1); +} + +static void igraph_i_graphml_sax_handler_error(void *state0, const char* msg, ...) { + struct igraph_i_graphml_parser_state *state = + (struct igraph_i_graphml_parser_state*)state0; + va_list ap; + + va_start(ap, msg); + + if (state->error_message == 0) { + state->error_message = igraph_Calloc(4096, char); + } + + state->successful = 0; + state->st = ERROR; + vsnprintf(state->error_message, 4096, msg, ap); + + va_end(ap); +} + +static xmlEntityPtr igraph_i_graphml_sax_handler_get_entity(void *state0, + const xmlChar* name) { + xmlEntityPtr predef = xmlGetPredefinedEntity(name); + IGRAPH_UNUSED(state0); + if (predef != NULL) { + return predef; + } + IGRAPH_WARNING("unknown XML entity found\n"); + return blankEntity; +} + +static void igraph_i_graphml_handle_unknown_start_tag(struct igraph_i_graphml_parser_state *state) { + if (state->st != UNKNOWN) { + igraph_vector_int_push_back(&state->prev_state_stack, state->st); + state->st = UNKNOWN; + state->unknown_depth = 1; + } else { + state->unknown_depth++; + } +} + +static void igraph_i_graphml_sax_handler_start_document(void *state0) { + struct igraph_i_graphml_parser_state *state = + (struct igraph_i_graphml_parser_state*)state0; + int ret; + + state->st = START; + state->successful = 1; + state->edges_directed = 0; + state->destroyed = 0; + state->data_key = 0; + state->error_message = 0; + state->data_char = 0; + state->unknown_depth = 0; + state->ignore_namespaces = 0; + + ret = igraph_vector_int_init(&state->prev_state_stack, 0); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + ret = igraph_vector_int_reserve(&state->prev_state_stack, 32); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + IGRAPH_FINALLY(igraph_vector_int_destroy, &state->prev_state_stack); + + ret = igraph_vector_ptr_init(&state->v_attrs, 0); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&state->v_attrs, + igraph_i_graphml_attribute_record_destroy); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &state->v_attrs); + + ret = igraph_vector_ptr_init(&state->e_attrs, 0); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&state->e_attrs, + igraph_i_graphml_attribute_record_destroy); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &state->e_attrs); + + ret = igraph_vector_ptr_init(&state->g_attrs, 0); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&state->g_attrs, + igraph_i_graphml_attribute_record_destroy); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &state->g_attrs); + + ret = igraph_vector_init(&state->edgelist, 0); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + IGRAPH_FINALLY(igraph_vector_destroy, &state->edgelist); + + ret = igraph_trie_init(&state->node_trie, 1); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + IGRAPH_FINALLY(igraph_trie_destroy, &state->node_trie); + + ret = igraph_strvector_init(&state->edgeids, 0); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + IGRAPH_FINALLY(igraph_strvector_destroy, &state->edgeids); + + ret = igraph_trie_init(&state->v_names, 0); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + IGRAPH_FINALLY(igraph_trie_destroy, &state->v_names); + + ret = igraph_trie_init(&state->e_names, 0); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + IGRAPH_FINALLY(igraph_trie_destroy, &state->e_names); + + ret = igraph_trie_init(&state->g_names, 0); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + IGRAPH_FINALLY(igraph_trie_destroy, &state->g_names); + + IGRAPH_FINALLY_CLEAN(10); + IGRAPH_FINALLY(igraph_i_graphml_destroy_state, state); +} + +static void igraph_i_graphml_sax_handler_end_document(void *state0) { + struct igraph_i_graphml_parser_state *state = + (struct igraph_i_graphml_parser_state*)state0; + long i, l; + int r; + igraph_attribute_record_t idrec, eidrec; + const char *idstr = "id"; + igraph_bool_t already_has_vertex_id = 0, already_has_edge_id = 0; + + if (!state->successful) { + return; + } + + if (state->index < 0) { + + igraph_vector_ptr_t vattr, eattr, gattr; + long int esize = igraph_vector_ptr_size(&state->e_attrs); + const void **tmp; + r = igraph_vector_ptr_init(&vattr, + igraph_vector_ptr_size(&state->v_attrs) + 1); + if (r) { + igraph_error("Cannot parse GraphML file", __FILE__, __LINE__, r); + igraph_i_graphml_sax_handler_error(state, "Cannot parse GraphML file"); + return; + } + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &vattr); + if (igraph_strvector_size(&state->edgeids) != 0) { + esize++; + } + r = igraph_vector_ptr_init(&eattr, esize); + if (r) { + igraph_error("Cannot parse GraphML file", __FILE__, __LINE__, r); + igraph_i_graphml_sax_handler_error(state, "Cannot parse GraphML file"); + return; + } + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &eattr); + r = igraph_vector_ptr_init(&gattr, igraph_vector_ptr_size(&state->g_attrs)); + if (r) { + igraph_error("Cannot parse GraphML file", __FILE__, __LINE__, r); + igraph_i_graphml_sax_handler_error(state, "Cannot parse GraphML file"); + return; + } + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &gattr); + + for (i = 0; i < igraph_vector_ptr_size(&state->v_attrs); i++) { + igraph_i_graphml_attribute_record_t *graphmlrec = + VECTOR(state->v_attrs)[i]; + igraph_attribute_record_t *rec = &graphmlrec->record; + + /* Check that the name of the vertex attribute is not 'id'. + If it is then we cannot the complimentary 'id' attribute. */ + if (! strcmp(rec->name, idstr)) { + already_has_vertex_id = 1; + } + + if (rec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *vec = (igraph_vector_t*)rec->value; + long int origsize = igraph_vector_size(vec); + long int nodes = igraph_trie_size(&state->node_trie); + igraph_vector_resize(vec, nodes); + for (l = origsize; l < nodes; l++) { + VECTOR(*vec)[l] = graphmlrec->default_value.as_numeric; + } + } else if (rec->type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *strvec = (igraph_strvector_t*)rec->value; + long int origsize = igraph_strvector_size(strvec); + long int nodes = igraph_trie_size(&state->node_trie); + igraph_strvector_resize(strvec, nodes); + for (l = origsize; l < nodes; l++) { + igraph_strvector_set(strvec, l, graphmlrec->default_value.as_string); + } + } else if (rec->type == IGRAPH_ATTRIBUTE_BOOLEAN) { + igraph_vector_bool_t *boolvec = (igraph_vector_bool_t*)rec->value; + long int origsize = igraph_vector_bool_size(boolvec); + long int nodes = igraph_trie_size(&state->node_trie); + igraph_vector_bool_resize(boolvec, nodes); + for (l = origsize; l < nodes; l++) { + VECTOR(*boolvec)[l] = graphmlrec->default_value.as_boolean; + } + } + VECTOR(vattr)[i] = rec; + } + if (!already_has_vertex_id) { + idrec.name = idstr; + idrec.type = IGRAPH_ATTRIBUTE_STRING; + tmp = &idrec.value; + igraph_trie_getkeys(&state->node_trie, (const igraph_strvector_t **)tmp); + VECTOR(vattr)[i] = &idrec; + } else { + igraph_vector_ptr_pop_back(&vattr); + } + + for (i = 0; i < igraph_vector_ptr_size(&state->e_attrs); i++) { + igraph_i_graphml_attribute_record_t *graphmlrec = + VECTOR(state->e_attrs)[i]; + igraph_attribute_record_t *rec = &graphmlrec->record; + + if (! strcmp(rec->name, idstr)) { + already_has_edge_id = 1; + } + + if (rec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *vec = (igraph_vector_t*)rec->value; + long int origsize = igraph_vector_size(vec); + long int edges = igraph_vector_size(&state->edgelist) / 2; + igraph_vector_resize(vec, edges); + for (l = origsize; l < edges; l++) { + VECTOR(*vec)[l] = graphmlrec->default_value.as_numeric; + } + } else if (rec->type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *strvec = (igraph_strvector_t*)rec->value; + long int origsize = igraph_strvector_size(strvec); + long int edges = igraph_vector_size(&state->edgelist) / 2; + igraph_strvector_resize(strvec, edges); + for (l = origsize; l < edges; l++) { + igraph_strvector_set(strvec, l, graphmlrec->default_value.as_string); + } + } else if (rec->type == IGRAPH_ATTRIBUTE_BOOLEAN) { + igraph_vector_bool_t *boolvec = (igraph_vector_bool_t*)rec->value; + long int origsize = igraph_vector_bool_size(boolvec); + long int edges = igraph_vector_size(&state->edgelist) / 2; + igraph_vector_bool_resize(boolvec, edges); + for (l = origsize; l < edges; l++) { + VECTOR(*boolvec)[l] = graphmlrec->default_value.as_boolean; + } + } + VECTOR(eattr)[i] = rec; + } + if (igraph_strvector_size(&state->edgeids) != 0) { + if (!already_has_edge_id) { + long int origsize = igraph_strvector_size(&state->edgeids); + eidrec.name = idstr; + eidrec.type = IGRAPH_ATTRIBUTE_STRING; + igraph_strvector_resize(&state->edgeids, + igraph_vector_size(&state->edgelist) / 2); + for (; origsize < igraph_strvector_size(&state->edgeids); origsize++) { + igraph_strvector_set(&state->edgeids, origsize, ""); + } + eidrec.value = &state->edgeids; + VECTOR(eattr)[(long int)igraph_vector_ptr_size(&eattr) - 1] = &eidrec; + } else { + igraph_vector_ptr_pop_back(&eattr); + IGRAPH_WARNING("Could not add edge ids, " + "there is already an 'id' edge attribute"); + } + } + + for (i = 0; i < igraph_vector_ptr_size(&state->g_attrs); i++) { + igraph_i_graphml_attribute_record_t *graphmlrec = + VECTOR(state->g_attrs)[i]; + igraph_attribute_record_t *rec = &graphmlrec->record; + if (rec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *vec = (igraph_vector_t*)rec->value; + long int origsize = igraph_vector_size(vec); + igraph_vector_resize(vec, 1); + for (l = origsize; l < 1; l++) { + VECTOR(*vec)[l] = graphmlrec->default_value.as_numeric; + } + } else if (rec->type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *strvec = (igraph_strvector_t*)rec->value; + long int origsize = igraph_strvector_size(strvec); + igraph_strvector_resize(strvec, 1); + for (l = origsize; l < 1; l++) { + igraph_strvector_set(strvec, l, graphmlrec->default_value.as_string); + } + } else if (rec->type == IGRAPH_ATTRIBUTE_BOOLEAN) { + igraph_vector_bool_t *boolvec = (igraph_vector_bool_t*)rec->value; + long int origsize = igraph_vector_bool_size(boolvec); + igraph_vector_bool_resize(boolvec, 1); + for (l = origsize; l < 1; l++) { + VECTOR(*boolvec)[l] = graphmlrec->default_value.as_boolean; + } + } + VECTOR(gattr)[i] = rec; + } + + igraph_empty_attrs(state->g, 0, state->edges_directed, &gattr); + igraph_add_vertices(state->g, (igraph_integer_t) + igraph_trie_size(&state->node_trie), &vattr); + igraph_add_edges(state->g, &state->edgelist, &eattr); + + igraph_vector_ptr_destroy(&vattr); + igraph_vector_ptr_destroy(&eattr); + igraph_vector_ptr_destroy(&gattr); + IGRAPH_FINALLY_CLEAN(3); + } + + igraph_i_graphml_destroy_state(state); +} + +#define toXmlChar(a) (BAD_CAST(a)) +#define fromXmlChar(a) ((char *)(a)) /* not the most elegant way... */ + +#define XML_ATTR_LOCALNAME(it) (*(it)) +#define XML_ATTR_PREFIX(it) (*(it+1)) +#define XML_ATTR_URI(it) (*(it+2)) +#define XML_ATTR_VALUE_START(it) (*(it+3)) +#define XML_ATTR_VALUE_END(it) (*(it+4)) +#define XML_ATTR_VALUE(it) *(it+3), (*(it+4))-(*(it+3)) + +static igraph_i_graphml_attribute_record_t* igraph_i_graphml_add_attribute_key( + const xmlChar** attrs, int nb_attrs, + struct igraph_i_graphml_parser_state *state) { + xmlChar **it; + xmlChar *localname; + igraph_trie_t *trie = 0; + igraph_vector_ptr_t *ptrvector = 0; + long int id; + unsigned short int skip = 0; + int i, ret; + igraph_i_graphml_attribute_record_t *rec; + + if (!state->successful) { + return 0; + } + + rec = igraph_Calloc(1, igraph_i_graphml_attribute_record_t); + if (rec == 0) { + GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", IGRAPH_ENOMEM); + return 0; + } + IGRAPH_FINALLY(igraph_free, rec); + + rec->type = I_GRAPHML_UNKNOWN_TYPE; + + for (i = 0, it = (xmlChar**)attrs; i < nb_attrs; i++, it += 5) { + if (XML_ATTR_URI(it) != 0 && + !xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), XML_ATTR_URI(it))) { + continue; + } + + localname = XML_ATTR_LOCALNAME(it); + + if (xmlStrEqual(localname, toXmlChar("id"))) { + rec->id = fromXmlChar(xmlStrndup(XML_ATTR_VALUE(it))); + } else if (xmlStrEqual(localname, toXmlChar("attr.name"))) { + rec->record.name = fromXmlChar(xmlStrndup(XML_ATTR_VALUE(it))); + } else if (xmlStrEqual(localname, toXmlChar("attr.type"))) { + if (!xmlStrncmp(toXmlChar("boolean"), XML_ATTR_VALUE(it))) { + rec->type = I_GRAPHML_BOOLEAN; + rec->record.type = IGRAPH_ATTRIBUTE_BOOLEAN; + rec->default_value.as_boolean = 0; + } else if (!xmlStrncmp(toXmlChar("string"), XML_ATTR_VALUE(it))) { + rec->type = I_GRAPHML_STRING; + rec->record.type = IGRAPH_ATTRIBUTE_STRING; + rec->default_value.as_string = strdup(""); + } else if (!xmlStrncmp(toXmlChar("float"), XML_ATTR_VALUE(it))) { + rec->type = I_GRAPHML_FLOAT; + rec->record.type = IGRAPH_ATTRIBUTE_NUMERIC; + rec->default_value.as_numeric = IGRAPH_NAN; + } else if (!xmlStrncmp(toXmlChar("double"), XML_ATTR_VALUE(it))) { + rec->type = I_GRAPHML_DOUBLE; + rec->record.type = IGRAPH_ATTRIBUTE_NUMERIC; + rec->default_value.as_numeric = IGRAPH_NAN; + } else if (!xmlStrncmp(toXmlChar("int"), XML_ATTR_VALUE(it))) { + rec->type = I_GRAPHML_INTEGER; + rec->record.type = IGRAPH_ATTRIBUTE_NUMERIC; + rec->default_value.as_numeric = IGRAPH_NAN; + } else if (!xmlStrncmp(toXmlChar("long"), XML_ATTR_VALUE(it))) { + rec->type = I_GRAPHML_LONG; + rec->record.type = IGRAPH_ATTRIBUTE_NUMERIC; + rec->default_value.as_numeric = IGRAPH_NAN; + } else { + GRAPHML_PARSE_ERROR(state, + "Cannot parse GraphML file, unknown attribute type"); + return 0; + } + } else if (xmlStrEqual(*it, toXmlChar("for"))) { + /* graph, vertex or edge attribute? */ + if (!xmlStrncmp(toXmlChar("graph"), XML_ATTR_VALUE(it))) { + trie = &state->g_names; + ptrvector = &state->g_attrs; + } else if (!xmlStrncmp(toXmlChar("node"), XML_ATTR_VALUE(it))) { + trie = &state->v_names; + ptrvector = &state->v_attrs; + } else if (!xmlStrncmp(toXmlChar("edge"), XML_ATTR_VALUE(it))) { + trie = &state->e_names; + ptrvector = &state->e_attrs; + } else if (!xmlStrncmp(toXmlChar("graphml"), XML_ATTR_VALUE(it))) { + igraph_i_report_unhandled_attribute_target("graphml", __FILE__, __LINE__); + skip = 1; + } else if (!xmlStrncmp(toXmlChar("hyperedge"), XML_ATTR_VALUE(it))) { + igraph_i_report_unhandled_attribute_target("hyperedge", __FILE__, __LINE__); + skip = 1; + } else if (!xmlStrncmp(toXmlChar("port"), XML_ATTR_VALUE(it))) { + igraph_i_report_unhandled_attribute_target("port", __FILE__, __LINE__); + skip = 1; + } else if (!xmlStrncmp(toXmlChar("endpoint"), XML_ATTR_VALUE(it))) { + igraph_i_report_unhandled_attribute_target("endpoint", __FILE__, __LINE__); + skip = 1; + } else if (!xmlStrncmp(toXmlChar("all"), XML_ATTR_VALUE(it))) { + /* TODO: we should handle this */ + igraph_i_report_unhandled_attribute_target("all", __FILE__, __LINE__); + skip = 1; + } else { + GRAPHML_PARSE_ERROR(state, + "Cannot parse GraphML file, unknown value in the 'for' attribute of a tag"); + return 0; + } + } + } + + /* throw an error if there is no ID; this is a clear violation of the GraphML + * DTD */ + if (rec->id == 0) { + GRAPHML_PARSE_ERROR(state, "Found tag with no 'id' attribute"); + return 0; + } + + /* in case of a missing attr.name attribute, use the id as the attribute name */ + if (rec->record.name == 0) { + rec->record.name = strdup(rec->id); + } + + /* if the attribute type is missing, throw an error */ + if (!skip && rec->type == I_GRAPHML_UNKNOWN_TYPE) { + igraph_warningf("Ignoring because of a missing or unknown 'attr.type' attribute", __FILE__, __LINE__, 0, rec->id); + skip = 1; + } + + /* if the value of the 'for' attribute was unknown, throw an error */ + if (!skip && trie == 0) { + GRAPHML_PARSE_ERROR(state, + "Cannot parse GraphML file, missing 'for' attribute in a tag"); + return 0; + } + + /* if the code above requested skipping the attribute, free everything and + * return */ + if (skip) { + igraph_free(rec); + IGRAPH_FINALLY_CLEAN(1); + return 0; + } + + /* add to trie, attribues */ + igraph_trie_get(trie, rec->id, &id); + if (id != igraph_trie_size(trie) - 1) { + GRAPHML_PARSE_ERROR(state, "Cannot parse GraphML file, duplicate attribute"); + return 0; + } + + ret = igraph_vector_ptr_push_back(ptrvector, rec); + if (ret) { + GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot read GraphML file", ret); + return 0; + } + + /* Ownership of 'rec' is now taken by ptrvector so we can clean the + * finally stack */ + IGRAPH_FINALLY_CLEAN(1); /* rec */ + + /* create the attribute values */ + switch (rec->record.type) { + igraph_vector_t *vec; + igraph_vector_bool_t *boolvec; + igraph_strvector_t *strvec; + case IGRAPH_ATTRIBUTE_BOOLEAN: + boolvec = igraph_Calloc(1, igraph_vector_bool_t); + if (boolvec == 0) { + GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", IGRAPH_ENOMEM); + return 0; + } + rec->record.value = boolvec; + igraph_vector_bool_init(boolvec, 0); + break; + case IGRAPH_ATTRIBUTE_NUMERIC: + vec = igraph_Calloc(1, igraph_vector_t); + if (vec == 0) { + GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", IGRAPH_ENOMEM); + return 0; + } + rec->record.value = vec; + igraph_vector_init(vec, 0); + break; + case IGRAPH_ATTRIBUTE_STRING: + strvec = igraph_Calloc(1, igraph_strvector_t); + if (strvec == 0) { + GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", IGRAPH_ENOMEM); + return 0; + } + rec->record.value = strvec; + igraph_strvector_init(strvec, 0); + break; + default: break; + } + + return rec; +} + +static void igraph_i_graphml_attribute_data_setup(struct igraph_i_graphml_parser_state *state, + const xmlChar **attrs, + int nb_attrs, + igraph_attribute_elemtype_t type) { + xmlChar **it; + int i; + + if (!state->successful) { + return; + } + + for (i = 0, it = (xmlChar**)attrs; i < nb_attrs; i++, it += 5) { + if (XML_ATTR_URI(it) != 0 && + !xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), XML_ATTR_URI(it))) { + continue; + } + + if (xmlStrEqual(*it, toXmlChar("key"))) { + if (state->data_key) { + free(state->data_key); + } + state->data_key = xmlStrndup(XML_ATTR_VALUE(it)); + if (state->data_char) { + free(state->data_char); + } + state->data_char = 0; + state->data_type = type; + } else { + /* ignore */ + } + } +} + +static void igraph_i_graphml_append_to_data_char(struct igraph_i_graphml_parser_state *state, + const xmlChar *data, int len) { + long int data_char_new_start = 0; + + if (!state->successful) { + return; + } + + if (state->data_char) { + data_char_new_start = (long int) strlen(state->data_char); + state->data_char = igraph_Realloc(state->data_char, + (size_t)(data_char_new_start + len + 1), char); + } else { + state->data_char = igraph_Calloc((size_t) len + 1, char); + } + if (state->data_char == 0) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", IGRAPH_ENOMEM); + } + memcpy(state->data_char + data_char_new_start, data, + (size_t) len * sizeof(xmlChar)); + state->data_char[data_char_new_start + len] = '\0'; +} + +static void igraph_i_graphml_attribute_data_finish(struct igraph_i_graphml_parser_state *state) { + const char *key = fromXmlChar(state->data_key); + igraph_attribute_elemtype_t type = state->data_type; + igraph_trie_t *trie = 0; + igraph_vector_ptr_t *ptrvector = 0; + igraph_i_graphml_attribute_record_t *graphmlrec; + igraph_attribute_record_t *rec; + long int recid, id = 0; + int ret; + + switch (type) { + case IGRAPH_ATTRIBUTE_GRAPH: + trie = &state->g_names; + ptrvector = &state->g_attrs; + id = 0; + break; + case IGRAPH_ATTRIBUTE_VERTEX: + trie = &state->v_names; + ptrvector = &state->v_attrs; + id = state->act_node; + break; + case IGRAPH_ATTRIBUTE_EDGE: + trie = &state->e_names; + ptrvector = &state->e_attrs; + id = igraph_vector_size(&state->edgelist) / 2 - 1; /* hack */ + break; + default: + /* impossible */ + break; + } + + if (key == 0) { + /* no key specified, issue a warning */ + igraph_warningf( + "missing attribute key in a tag, ignoring attribute", + __FILE__, __LINE__, 0, + key + ); + igraph_Free(state->data_char); + return; + } + + igraph_trie_check(trie, key, &recid); + if (recid < 0) { + /* no such attribute key, issue a warning */ + igraph_warningf( + "unknown attribute key '%s' in a tag, ignoring attribute", + __FILE__, __LINE__, 0, + key + ); + igraph_Free(state->data_char); + return; + } + + graphmlrec = VECTOR(*ptrvector)[recid]; + rec = &graphmlrec->record; + + switch (rec->type) { + igraph_vector_bool_t *boolvec; + igraph_vector_t *vec; + igraph_strvector_t *strvec; + long int s, i; + const char* strvalue; + case IGRAPH_ATTRIBUTE_BOOLEAN: + boolvec = (igraph_vector_bool_t *)rec->value; + s = igraph_vector_bool_size(boolvec); + if (id >= s) { + ret = igraph_vector_bool_resize(boolvec, id + 1); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + for (i = s; i < id; i++) { + VECTOR(*boolvec)[i] = graphmlrec->default_value.as_boolean; + } + } + VECTOR(*boolvec)[id] = igraph_i_graphml_parse_boolean(state->data_char, + graphmlrec->default_value.as_boolean); + break; + case IGRAPH_ATTRIBUTE_NUMERIC: + vec = (igraph_vector_t *)rec->value; + s = igraph_vector_size(vec); + if (id >= s) { + ret = igraph_vector_resize(vec, id + 1); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + for (i = s; i < id; i++) { + VECTOR(*vec)[i] = graphmlrec->default_value.as_numeric; + } + } + VECTOR(*vec)[id] = igraph_i_graphml_parse_numeric(state->data_char, + graphmlrec->default_value.as_numeric); + break; + case IGRAPH_ATTRIBUTE_STRING: + strvec = (igraph_strvector_t *)rec->value; + s = igraph_strvector_size(strvec); + if (id >= s) { + ret = igraph_strvector_resize(strvec, id + 1); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + strvalue = graphmlrec->default_value.as_string; + for (i = s; i < id; i++) { + igraph_strvector_set(strvec, i, strvalue); + } + } + if (state->data_char) { + strvalue = state->data_char; + } else { + strvalue = graphmlrec->default_value.as_string; + } + ret = igraph_strvector_set(strvec, id, strvalue); + if (ret) { + RETURN_GRAPHML_PARSE_ERROR_WITH_CODE(state, "Cannot parse GraphML file", ret); + } + break; + default: + break; + } + + if (state->data_char) { + igraph_Free(state->data_char); + } +} + +static void igraph_i_graphml_attribute_default_value_finish( + struct igraph_i_graphml_parser_state *state) { + igraph_i_graphml_attribute_record_t *graphmlrec = state->current_attr_record; + + if (graphmlrec == 0) { + igraph_warning("state->current_attr_record was null where it should have been " + "non-null; this is probably a bug. Please notify the developers!", + __FILE__, __LINE__, 0); + return; + } + + if (state->data_char == 0) { + return; + } + + switch (graphmlrec->record.type) { + case IGRAPH_ATTRIBUTE_BOOLEAN: + graphmlrec->default_value.as_boolean = igraph_i_graphml_parse_boolean( + state->data_char, 0); + break; + case IGRAPH_ATTRIBUTE_NUMERIC: + graphmlrec->default_value.as_numeric = igraph_i_graphml_parse_numeric( + state->data_char, IGRAPH_NAN); + break; + case IGRAPH_ATTRIBUTE_STRING: + if (state->data_char) { + if (graphmlrec->default_value.as_string != 0) { + free(graphmlrec->default_value.as_string); + } + graphmlrec->default_value.as_string = strdup(state->data_char); + } + break; + default: + break; + } + + if (state->data_char) { + igraph_Free(state->data_char); + } +} + +static void igraph_i_graphml_sax_handler_start_element_ns( + void *state0, const xmlChar* localname, const xmlChar* prefix, + const xmlChar* uri, int nb_namespaces, const xmlChar** namespaces, + int nb_attributes, int nb_defaulted, const xmlChar** attributes) { + struct igraph_i_graphml_parser_state *state = + (struct igraph_i_graphml_parser_state*)state0; + xmlChar** it; + char* attr_value; + long int id1, id2; + int i; + igraph_bool_t tag_is_unknown = 0; + + if (!state->successful) { + return; + } + + if (uri) { + if (!xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), uri)) { + /* Tag is in a different namespace, so treat it as an unknown start + * tag irrespectively of our state */ + tag_is_unknown = 1; + } + } else { + /* No namespace URI. If we are in lenient mode, accept it and proceed + * as if we are in the GraphML namespace to handle lots of naive + * non-namespace-aware GraphML files floating out there. If we are not + * in lenient mode _but_ we are in the START state, accept it as well + * and see whether the root tag is (in which case we will + * enter lenient mode). Otherwise, reject the tag */ + if (!state->ignore_namespaces && state->st != START) { + tag_is_unknown = 1; + } + } + + if (tag_is_unknown) { + igraph_i_graphml_handle_unknown_start_tag(state); + return; + } + + switch (state->st) { + case START: + /* If we are in the START state and received a graphml tag, + * change to INSIDE_GRAPHML state. Otherwise, change to UNKNOWN. */ + if (xmlStrEqual(localname, toXmlChar("graphml"))) { + if (uri == 0) { + state->ignore_namespaces = 1; + } + state->st = INSIDE_GRAPHML; + } else { + igraph_i_graphml_handle_unknown_start_tag(state); + } + break; + + case INSIDE_GRAPHML: + /* If we are in the INSIDE_GRAPHML state and received a graph tag, + * change to INSIDE_GRAPH state if the state->index counter reached + * zero (this is to handle multiple graphs in the same file). + * Otherwise, change to UNKNOWN. */ + if (xmlStrEqual(localname, toXmlChar("graph"))) { + if (state->index == 0) { + state->st = INSIDE_GRAPH; + for (i = 0, it = (xmlChar**)attributes; i < nb_attributes; i++, it += 5) { + if (XML_ATTR_URI(it) != 0 && + !xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), XML_ATTR_URI(it))) { + /* Attribute is from a different namespace, so skip it */ + continue; + } + if (xmlStrEqual(*it, toXmlChar("edgedefault"))) { + if (!xmlStrncmp(toXmlChar("directed"), XML_ATTR_VALUE(it))) { + state->edges_directed = 1; + } else if (!xmlStrncmp(toXmlChar("undirected"), XML_ATTR_VALUE(it))) { + state->edges_directed = 0; + } + } + } + } + state->index--; + } else if (xmlStrEqual(localname, toXmlChar("key"))) { + state->current_attr_record = + igraph_i_graphml_add_attribute_key(attributes, nb_attributes, state); + state->st = INSIDE_KEY; + } else { + igraph_i_graphml_handle_unknown_start_tag(state); + } + break; + + case INSIDE_KEY: + /* If we are in the INSIDE_KEY state, check for default tag */ + if (xmlStrEqual(localname, toXmlChar("default"))) { + state->st = INSIDE_DEFAULT; + } else { + igraph_i_graphml_handle_unknown_start_tag(state); + } + break; + + case INSIDE_DEFAULT: + /* If we are in the INSIDE_DEFAULT state, every further tag will be unknown */ + igraph_i_graphml_handle_unknown_start_tag(state); + break; + + case INSIDE_GRAPH: + /* If we are in the INSIDE_GRAPH state, check for node and edge tags */ + if (xmlStrEqual(localname, toXmlChar("edge"))) { + id1 = -1; id2 = -1; + for (i = 0, it = (xmlChar**)attributes; i < nb_attributes; i++, it += 5) { + if (XML_ATTR_URI(it) != 0 && + !xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), XML_ATTR_URI(it))) { + /* Attribute is from a different namespace, so skip it */ + continue; + } + if (xmlStrEqual(*it, toXmlChar("source"))) { + attr_value = fromXmlChar(xmlStrndup(XML_ATTR_VALUE(it))); + igraph_trie_get(&state->node_trie, attr_value, &id1); + free(attr_value); + } else if (xmlStrEqual(*it, toXmlChar("target"))) { + attr_value = fromXmlChar(xmlStrndup(XML_ATTR_VALUE(it))); + igraph_trie_get(&state->node_trie, attr_value, &id2); + free(attr_value); + } else if (xmlStrEqual(*it, toXmlChar("id"))) { + long int edges = igraph_vector_size(&state->edgelist) / 2 + 1; + long int origsize = igraph_strvector_size(&state->edgeids); + attr_value = fromXmlChar(xmlStrndup(XML_ATTR_VALUE(it))); + igraph_strvector_resize(&state->edgeids, edges); + for (; origsize < edges - 1; origsize++) { + igraph_strvector_set(&state->edgeids, origsize, ""); + } + igraph_strvector_set(&state->edgeids, edges - 1, attr_value); + free(attr_value); + } + } + if (id1 >= 0 && id2 >= 0) { + igraph_vector_push_back(&state->edgelist, id1); + igraph_vector_push_back(&state->edgelist, id2); + } else { + igraph_i_graphml_sax_handler_error(state, "Edge with missing source or target encountered"); + return; + } + state->st = INSIDE_EDGE; + } else if (xmlStrEqual(localname, toXmlChar("node"))) { + id1 = -1; + for (i = 0, it = (xmlChar**)attributes; i < nb_attributes; i++, it += 5) { + if (XML_ATTR_URI(it) != 0 && + !xmlStrEqual(toXmlChar(GRAPHML_NAMESPACE_URI), XML_ATTR_URI(it))) { + /* Attribute is from a different namespace, so skip it */ + continue; + } + if (xmlStrEqual(XML_ATTR_LOCALNAME(it), toXmlChar("id"))) { + attr_value = fromXmlChar(xmlStrndup(XML_ATTR_VALUE(it))); + igraph_trie_get(&state->node_trie, attr_value, &id1); + free(attr_value); + break; + } + } + if (id1 >= 0) { + state->act_node = id1; + } else { + state->act_node = -1; + igraph_i_graphml_sax_handler_error(state, "Node with missing id encountered"); + return; + } + state->st = INSIDE_NODE; + } else if (xmlStrEqual(localname, toXmlChar("data"))) { + igraph_i_graphml_attribute_data_setup(state, attributes, nb_attributes, + IGRAPH_ATTRIBUTE_GRAPH); + igraph_vector_int_push_back(&state->prev_state_stack, state->st); + state->st = INSIDE_DATA; + } else { + igraph_i_graphml_handle_unknown_start_tag(state); + } + break; + + case INSIDE_NODE: + if (xmlStrEqual(localname, toXmlChar("data"))) { + igraph_i_graphml_attribute_data_setup(state, attributes, nb_attributes, + IGRAPH_ATTRIBUTE_VERTEX); + igraph_vector_int_push_back(&state->prev_state_stack, state->st); + state->st = INSIDE_DATA; + } + break; + + case INSIDE_EDGE: + if (xmlStrEqual(localname, toXmlChar("data"))) { + igraph_i_graphml_attribute_data_setup(state, attributes, nb_attributes, + IGRAPH_ATTRIBUTE_EDGE); + igraph_vector_int_push_back(&state->prev_state_stack, state->st); + state->st = INSIDE_DATA; + } + break; + + case INSIDE_DATA: + /* We do not expect any new tags within a tag */ + igraph_i_graphml_handle_unknown_start_tag(state); + break; + + case UNKNOWN: + igraph_i_graphml_handle_unknown_start_tag(state); + break; + + default: + break; + } +} + +static void igraph_i_graphml_sax_handler_end_element_ns( + void *state0, + const xmlChar* localname, const xmlChar* prefix, + const xmlChar* uri) { + struct igraph_i_graphml_parser_state *state = + (struct igraph_i_graphml_parser_state*)state0; + + if (!state->successful) { + return; + } + + IGRAPH_UNUSED(localname); + IGRAPH_UNUSED(prefix); + IGRAPH_UNUSED(uri); + + switch (state->st) { + case INSIDE_GRAPHML: + state->st = FINISH; + break; + + case INSIDE_GRAPH: + state->st = INSIDE_GRAPHML; + break; + + case INSIDE_KEY: + state->current_attr_record = 0; + state->st = INSIDE_GRAPHML; + break; + + case INSIDE_DEFAULT: + igraph_i_graphml_attribute_default_value_finish(state); + state->st = INSIDE_KEY; + break; + + case INSIDE_NODE: + state->st = INSIDE_GRAPH; + break; + + case INSIDE_EDGE: + state->st = INSIDE_GRAPH; + break; + + case INSIDE_DATA: + igraph_i_graphml_attribute_data_finish(state); + state->st = igraph_vector_int_pop_back(&state->prev_state_stack); + break; + + case UNKNOWN: + state->unknown_depth--; + if (!state->unknown_depth) { + state->st = igraph_vector_int_pop_back(&state->prev_state_stack); + } + break; + + default: + break; + } +} + +static void igraph_i_graphml_sax_handler_chars(void* state0, const xmlChar* ch, int len) { + struct igraph_i_graphml_parser_state *state = + (struct igraph_i_graphml_parser_state*)state0; + + if (!state->successful) { + return; + } + + switch (state->st) { + case INSIDE_KEY: + break; + + case INSIDE_DATA: + case INSIDE_DEFAULT: + igraph_i_graphml_append_to_data_char(state, ch, len); + break; + + default: + /* just ignore it */ + break; + } +} + +static xmlSAXHandler igraph_i_graphml_sax_handler = { + /* internalSubset = */ 0, + /* isStandalone = */ 0, + /* hasInternalSubset = */ 0, + /* hasExternalSubset = */ 0, + /* resolveEntity = */ 0, + /* getEntity = */ igraph_i_graphml_sax_handler_get_entity, + /* entityDecl = */ 0, + /* notationDecl = */ 0, + /* attributeDecl = */ 0, + /* elementDecl = */ 0, + /* unparsedEntityDecl = */ 0, + /* setDocumentLocator = */ 0, + /* startDocument = */ igraph_i_graphml_sax_handler_start_document, + /* endDocument = */ igraph_i_graphml_sax_handler_end_document, + /* startElement = */ 0, + /* endElement = */ 0, + /* reference = */ 0, + /* characters = */ igraph_i_graphml_sax_handler_chars, + /* ignorableWhitespaceFunc = */ 0, + /* processingInstruction = */ 0, + /* comment = */ 0, + /* warning = */ igraph_i_graphml_sax_handler_error, + /* error = */ igraph_i_graphml_sax_handler_error, + /* fatalError = */ igraph_i_graphml_sax_handler_error, + /* getParameterEntity = */ 0, + /* cdataBlock = */ 0, + /* externalSubset = */ 0, + /* initialized = */ XML_SAX2_MAGIC, + /* _private = */ 0, + /* startElementNs = */ igraph_i_graphml_sax_handler_start_element_ns, + /* endElementNs = */ igraph_i_graphml_sax_handler_end_element_ns, + /* serror = */ 0 +}; + +#endif + +#define IS_FORBIDDEN_CONTROL_CHAR(x) ((x) < ' ' && (x) != '\t' && (x) != '\r' && (x) != '\n') + +static int igraph_i_xml_escape(char* src, char** dest) { + long int destlen = 0; + char *s, *d; + unsigned char ch; + + for (s = src; *s; s++, destlen++) { + ch = (unsigned char)(*s); + if (ch == '&') { + destlen += 4; + } else if (ch == '<') { + destlen += 3; + } else if (ch == '>') { + destlen += 3; + } else if (ch == '"') { + destlen += 5; + } else if (ch == '\'') { + destlen += 5; + } else if (IS_FORBIDDEN_CONTROL_CHAR(ch)) { + char msg[4096]; + snprintf(msg, 4096, "Forbidden control character 0x%02X found in igraph_i_xml_escape", + ch); + IGRAPH_ERROR(msg, IGRAPH_EINVAL); + } + } + *dest = igraph_Calloc(destlen + 1, char); + if (!*dest) { + IGRAPH_ERROR("Not enough memory", IGRAPH_ENOMEM); + } + for (s = src, d = *dest; *s; s++, d++) { + ch = (unsigned char)(*s); + switch (ch) { + case '&': + strcpy(d, "&"); d += 4; break; + case '<': + strcpy(d, "<"); d += 3; break; + case '>': + strcpy(d, ">"); d += 3; break; + case '"': + strcpy(d, """); d += 5; break; + case '\'': + strcpy(d, "'"); d += 5; break; + default: + *d = ch; + } + } + *d = 0; + return 0; +} + +/** + * \ingroup loadsave + * \function igraph_read_graph_graphml + * \brief Reads a graph from a GraphML file. + * + * + * GraphML is an XML-based file format for representing various types of + * graphs. Currently only the most basic import functionality is implemented + * in igraph: it can read GraphML files without nested graphs and hyperedges. + * Attributes of the graph are loaded only if an attribute interface + * is attached, ie. if you use igraph from R or Python. + * + * + * Graph attribute names are taken from the \c attr.name attributes of the + * \c key tags in the GraphML file. Since \c attr.name is not mandatory, + * igraph will fall back to the \c id attribute of the \c key tag if + * \c attr.name is missing. + * + * \param graph Pointer to an uninitialized graph object. + * \param instream A stream, it should be readable. + * \param index If the GraphML file contains more than one graph, the one + * specified by this index will be loaded. Indices start from + * zero, so supply zero here if your GraphML file contains only + * a single graph. + * + * \return Error code: + * \c IGRAPH_PARSEERROR: if there is a + * problem reading the file, or the file is syntactically + * incorrect. + * \c IGRAPH_UNIMPLEMENTED: the GraphML functionality was disabled + * at compile-time + * + * \example examples/simple/graphml.c + */ +int igraph_read_graph_graphml(igraph_t *graph, FILE *instream, + int index) { + +#if HAVE_LIBXML == 1 + xmlParserCtxtPtr ctxt; + struct igraph_i_graphml_parser_state state; + int res; + char buffer[4096]; + + if (index < 0) { + IGRAPH_ERROR("Graph index must be non-negative", IGRAPH_EINVAL); + } + + xmlInitParser(); + + /* Create a progressive parser context */ + state.g = graph; + state.index = index < 0 ? 0 : index; + res = (int) fread(buffer, 1, 4096, instream); + ctxt = xmlCreatePushParserCtxt(&igraph_i_graphml_sax_handler, + &state, + buffer, + res, + NULL); + /* ctxt=xmlCreateIOParserCtxt(&igraph_i_graphml_sax_handler, &state, */ + /* igraph_i_libxml2_read_callback, */ + /* igraph_i_libxml2_close_callback, */ + /* instream, XML_CHAR_ENCODING_NONE); */ + if (ctxt == NULL) { + IGRAPH_ERROR("Can't create progressive parser context", IGRAPH_PARSEERROR); + } + + /* Set parsing options */ + if (xmlCtxtUseOptions(ctxt, + XML_PARSE_NOENT | XML_PARSE_NOBLANKS | + XML_PARSE_NONET | XML_PARSE_NSCLEAN | + XML_PARSE_NOCDATA | XML_PARSE_HUGE + )) { + IGRAPH_ERROR("Cannot set options for the parser context", IGRAPH_EINVAL); + } + + /* Parse the file */ + while ((res = (int) fread(buffer, 1, 4096, instream)) > 0) { + xmlParseChunk(ctxt, buffer, res, 0); + if (!state.successful) { + break; + } + } + xmlParseChunk(ctxt, buffer, res, 1); + + /* Free the context */ + xmlFreeParserCtxt(ctxt); + if (!state.successful) { + if (state.error_message != 0) { + IGRAPH_ERROR(state.error_message, IGRAPH_PARSEERROR); + } else { + IGRAPH_ERROR("Malformed GraphML file", IGRAPH_PARSEERROR); + } + } + if (state.index >= 0) { + IGRAPH_ERROR("Graph index was too large", IGRAPH_EINVAL); + } + + return 0; +#else + IGRAPH_ERROR("GraphML support is disabled", IGRAPH_UNIMPLEMENTED); +#endif +} + +/** + * \ingroup loadsave + * \function igraph_write_graph_graphml + * \brief Writes the graph to a file in GraphML format + * + * + * GraphML is an XML-based file format for representing various types of + * graphs. See the GraphML Primer (http://graphml.graphdrawing.org/primer/graphml-primer.html) + * for detailed format description. + * + * \param graph The graph to write. + * \param outstream The stream object to write to, it should be + * writable. + * \param prefixattr Logical value, whether to put a prefix in front of the + * attribute names to ensure uniqueness if the graph has vertex and + * edge (or graph) attributes with the same name. + * \return Error code: + * \c IGRAPH_EFILE if there is an error + * writing the file. + * + * Time complexity: O(|V|+|E|) otherwise. All + * file operations are expected to have time complexity + * O(1). + * + * \example examples/simple/graphml.c + */ +int igraph_write_graph_graphml(const igraph_t *graph, FILE *outstream, + igraph_bool_t prefixattr) { + int ret; + igraph_integer_t l, vc; + igraph_eit_t it; + igraph_strvector_t gnames, vnames, enames; + igraph_vector_t gtypes, vtypes, etypes; + long int i; + igraph_vector_t numv; + igraph_strvector_t strv; + igraph_vector_bool_t boolv; + const char *gprefix = prefixattr ? "g_" : ""; + const char *vprefix = prefixattr ? "v_" : ""; + const char *eprefix = prefixattr ? "e_" : ""; + + /* set standard C locale lest we sometimes get commas instead of dots */ + char *saved_locale = strdup(setlocale(LC_NUMERIC, NULL)); + if (saved_locale == NULL) { + IGRAPH_ERROR("Not enough memory", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, saved_locale); + setlocale(LC_NUMERIC, "C"); + + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n", GRAPHML_NAMESPACE_URI); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + + /* dump the elements if any */ + + IGRAPH_VECTOR_INIT_FINALLY(&numv, 1); + IGRAPH_STRVECTOR_INIT_FINALLY(&strv, 1); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&boolv, 1); + + IGRAPH_STRVECTOR_INIT_FINALLY(&gnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&vnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&enames, 0); + IGRAPH_VECTOR_INIT_FINALLY(>ypes, 0); + IGRAPH_VECTOR_INIT_FINALLY(&vtypes, 0); + IGRAPH_VECTOR_INIT_FINALLY(&etypes, 0); + igraph_i_attribute_get_info(graph, + &gnames, >ypes, + &vnames, &vtypes, + &enames, &etypes); + + /* graph attributes */ + for (i = 0; i < igraph_vector_size(>ypes); i++) { + char *name, *name_escaped; + igraph_strvector_get(&gnames, i, &name); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + ret = fprintf(outstream, " \n", gprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + ret = fprintf(outstream, " \n", gprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + ret = fprintf(outstream, " \n", gprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } + igraph_Free(name_escaped); + } + + /* vertex attributes */ + for (i = 0; i < igraph_vector_size(&vtypes); i++) { + char *name, *name_escaped; + igraph_strvector_get(&vnames, i, &name); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + ret = fprintf(outstream, " \n", vprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } else if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + ret = fprintf(outstream, " \n", vprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } else if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + ret = fprintf(outstream, " \n", vprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } + igraph_Free(name_escaped); + } + + /* edge attributes */ + for (i = 0; i < igraph_vector_size(&etypes); i++) { + char *name, *name_escaped; + igraph_strvector_get(&enames, i, &name); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + ret = fprintf(outstream, " \n", eprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } else if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + ret = fprintf(outstream, " \n", eprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } else if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + ret = fprintf(outstream, " \n", eprefix, name_escaped, name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } + igraph_Free(name_escaped); + } + + ret = fprintf(outstream, " \n", (igraph_is_directed(graph) ? "directed" : "undirected")); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + + /* Write the graph atributes before anything else */ + + for (i = 0; i < igraph_vector_size(>ypes); i++) { + char *name, *name_escaped; + if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_strvector_get(&gnames, i, &name); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_graph_attr(graph, name, &numv)); + if (!isnan(VECTOR(numv)[0])) { + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + ret = fprintf(outstream, " ", gprefix, name_escaped); + igraph_Free(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + char *s, *s_escaped; + igraph_strvector_get(&gnames, i, &name); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + ret = fprintf(outstream, " ", gprefix, + name_escaped); + igraph_Free(name_escaped); + IGRAPH_CHECK(igraph_i_attribute_get_string_graph_attr(graph, name, &strv)); + igraph_strvector_get(&strv, 0, &s); + IGRAPH_CHECK(igraph_i_xml_escape(s, &s_escaped)); + ret = fprintf(outstream, "%s", s_escaped); + igraph_Free(s_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + igraph_strvector_get(&gnames, i, &name); + IGRAPH_CHECK(igraph_i_attribute_get_bool_graph_attr(graph, name, &boolv)); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + ret = fprintf(outstream, " %s\n", + gprefix, name_escaped, VECTOR(boolv)[0] ? "true" : "false"); + igraph_Free(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } + } + + /* Let's dump the nodes first */ + vc = igraph_vcount(graph); + for (l = 0; l < vc; l++) { + char *name, *name_escaped; + ret = fprintf(outstream, " \n", (long)l); + + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + + for (i = 0; i < igraph_vector_size(&vtypes); i++) { + if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_strvector_get(&vnames, i, &name); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, name, + igraph_vss_1(l), &numv)); + if (!isnan(VECTOR(numv)[0])) { + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + ret = fprintf(outstream, " ", vprefix, name_escaped); + igraph_Free(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } + } else if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + char *s, *s_escaped; + igraph_strvector_get(&vnames, i, &name); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + ret = fprintf(outstream, " ", vprefix, + name_escaped); + igraph_Free(name_escaped); + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, name, + igraph_vss_1(l), &strv)); + igraph_strvector_get(&strv, 0, &s); + IGRAPH_CHECK(igraph_i_xml_escape(s, &s_escaped)); + ret = fprintf(outstream, "%s", s_escaped); + igraph_Free(s_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } else if (VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + igraph_strvector_get(&vnames, i, &name); + IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr(graph, name, + igraph_vss_1(l), &boolv)); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + ret = fprintf(outstream, " %s\n", + vprefix, name_escaped, VECTOR(boolv)[0] ? "true" : "false"); + igraph_Free(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } + } + + ret = fprintf(outstream, " \n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } + + /* Now the edges */ + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(0), &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + while (!IGRAPH_EIT_END(it)) { + igraph_integer_t from, to; + char *name, *name_escaped; + long int edge = IGRAPH_EIT_GET(it); + igraph_edge(graph, (igraph_integer_t) edge, &from, &to); + ret = fprintf(outstream, " \n", + (long int)from, (long int)to); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + + for (i = 0; i < igraph_vector_size(&etypes); i++) { + if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_strvector_get(&enames, i, &name); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, name, + igraph_ess_1((igraph_integer_t) edge), &numv)); + if (!isnan(VECTOR(numv)[0])) { + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + ret = fprintf(outstream, " ", eprefix, name_escaped); + igraph_Free(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } + } else if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + char *s, *s_escaped; + igraph_strvector_get(&enames, i, &name); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + ret = fprintf(outstream, " ", eprefix, + name_escaped); + igraph_Free(name_escaped); + IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr(graph, name, + igraph_ess_1((igraph_integer_t) edge), &strv)); + igraph_strvector_get(&strv, 0, &s); + IGRAPH_CHECK(igraph_i_xml_escape(s, &s_escaped)); + ret = fprintf(outstream, "%s", s_escaped); + igraph_Free(s_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } else if (VECTOR(etypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + igraph_strvector_get(&enames, i, &name); + IGRAPH_CHECK(igraph_i_attribute_get_bool_edge_attr(graph, name, + igraph_ess_1((igraph_integer_t) edge), &boolv)); + IGRAPH_CHECK(igraph_i_xml_escape(name, &name_escaped)); + ret = fprintf(outstream, " %s\n", + eprefix, name_escaped, VECTOR(boolv)[0] ? "true" : "false"); + igraph_Free(name_escaped); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } + } + + ret = fprintf(outstream, " \n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + + ret = fprintf(outstream, " \n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + fprintf(outstream, "\n"); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + + /* reset locale to whatever was before this function */ + setlocale(LC_NUMERIC, saved_locale); + + igraph_free(saved_locale); + igraph_strvector_destroy(&gnames); + igraph_strvector_destroy(&vnames); + igraph_strvector_destroy(&enames); + igraph_vector_destroy(>ypes); + igraph_vector_destroy(&vtypes); + igraph_vector_destroy(&etypes); + igraph_vector_destroy(&numv); + igraph_strvector_destroy(&strv); + igraph_vector_bool_destroy(&boolv); + IGRAPH_FINALLY_CLEAN(10); + + return 0; +} diff --git a/src/foreign-lgl-header.h b/src/foreign-lgl-header.h new file mode 100644 index 0000000..a15f122 --- /dev/null +++ b/src/foreign-lgl-header.h @@ -0,0 +1,35 @@ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_vector.h" +#include "igraph_types_internal.h" + +typedef struct { + void *scanner; + int eof; + char errmsg[300]; + int has_weights; + igraph_vector_t *vector; + igraph_vector_t *weights; + igraph_trie_t *trie; + int actvertex; +} igraph_i_lgl_parsedata_t; diff --git a/src/foreign-lgl-lexer.l b/src/foreign-lgl-lexer.l new file mode 100644 index 0000000..4b56da0 --- /dev/null +++ b/src/foreign-lgl-lexer.l @@ -0,0 +1,101 @@ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +%{ + +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "config.h" +#include +#include "foreign-lgl-header.h" +#include "foreign-lgl-parser.h" +#define YY_EXTRA_TYPE igraph_i_lgl_parsedata_t* +#define YY_USER_ACTION yylloc->first_line = yylineno; +/* We assume that 'file' is 'stderr' here. */ +#ifdef USING_R +#define fprintf(file, msg, ...) (1) +#endif +#ifdef stdout +# undef stdout +#endif +#define stdout 0 +#define exit(code) igraph_error("Fatal error in DL parser", __FILE__, \ + __LINE__, IGRAPH_PARSEERROR); +%} + +%option noyywrap +%option prefix="igraph_lgl_yy" +%option outfile="lex.yy.c" +%option nounput +%option noinput +%option nodefault +%option reentrant +%option bison-bridge +%option bison-locations + +alnum [^ \t\r\n#] + +%% + + /* --------------------------------------------------hashmark------*/ +# { return HASH; } + + /* ------------------------------------------------whitespace------*/ +[ \t]* { } + + /* ---------------------------------------------------newline------*/ +\n\r|\r\n|\n|\r { return NEWLINE; } + + /* ----------------------------------------------alphanumeric------*/ +{alnum}+ { return ALNUM; } + +<> { if (yyextra->eof) { + yyterminate(); + } else { + yyextra->eof=1; + return NEWLINE; + } + } + +. { return ERROR; } + +%% diff --git a/src/foreign-lgl-parser.y b/src/foreign-lgl-parser.y new file mode 100644 index 0000000..0b997b5 --- /dev/null +++ b/src/foreign-lgl-parser.y @@ -0,0 +1,148 @@ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +%{ + +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include "igraph_hacks_internal.h" +#include "igraph_types.h" +#include "igraph_types_internal.h" +#include "igraph_math.h" +#include "igraph_memory.h" +#include "igraph_error.h" +#include "config.h" +#include "foreign-lgl-header.h" +#include "foreign-lgl-parser.h" + +#define yyscan_t void* + +int igraph_lgl_yylex(YYSTYPE* lvalp, YYLTYPE* llocp, + void* scanner); +int igraph_lgl_yyerror(YYLTYPE* locp, igraph_i_lgl_parsedata_t *context, + const char *s); +char *igraph_lgl_yyget_text (yyscan_t yyscanner ); +int igraph_lgl_yyget_leng (yyscan_t yyscanner ); +igraph_real_t igraph_lgl_get_number(const char *str, long int len); + +#define scanner context->scanner +%} + +%pure-parser +%output="y.tab.c" +%name-prefix="igraph_lgl_yy" +%defines +%locations +%error-verbose +%parse-param { igraph_i_lgl_parsedata_t* context } +%lex-param { void *scanner } + +%union { + long int edgenum; + double weightnum; +} + +%type edgeid +%type weight + +%token ALNUM +%token NEWLINE +%token HASH +%token ERROR + +%% + +input : /* empty */ + | input NEWLINE + | input vertex +; + +vertex : vertexdef edges ; + +vertexdef : HASH edgeid NEWLINE { context->actvertex=$2; } ; + +edges : /* empty */ | edges edge ; + +edge : edgeid NEWLINE { + igraph_vector_push_back(context->vector, context->actvertex); + igraph_vector_push_back(context->vector, $1); + igraph_vector_push_back(context->weights, 0); + } + | edgeid weight NEWLINE { + igraph_vector_push_back(context->vector, context->actvertex); + igraph_vector_push_back(context->vector, $1); + igraph_vector_push_back(context->weights, $2); + context->has_weights = 1; + } +; + + +edgeid : ALNUM { igraph_trie_get2(context->trie, + igraph_lgl_yyget_text(scanner), + igraph_lgl_yyget_leng(scanner), + &$$); }; + +weight : ALNUM { $$=igraph_lgl_get_number(igraph_lgl_yyget_text(scanner), + igraph_lgl_yyget_leng(scanner)); } ; + +%% + +int igraph_lgl_yyerror(YYLTYPE* locp, igraph_i_lgl_parsedata_t *context, + const char *s) { + snprintf(context->errmsg, sizeof(context->errmsg)/sizeof(char), + "Parse error in LGL file, line %i (%s)", + locp->first_line, s); + return 0; +} + +igraph_real_t igraph_lgl_get_number(const char *str, long int length) { + igraph_real_t num; + char *tmp=igraph_Calloc(length+1, char); + + strncpy(tmp, str, length); + tmp[length]='\0'; + sscanf(tmp, "%lf", &num); + igraph_Free(tmp); + return num; +} diff --git a/src/foreign-ncol-header.h b/src/foreign-ncol-header.h new file mode 100644 index 0000000..4a23d4e --- /dev/null +++ b/src/foreign-ncol-header.h @@ -0,0 +1,34 @@ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_vector.h" +#include "igraph_types_internal.h" + +typedef struct { + void *scanner; + int eof; + char errmsg[300]; + int has_weights; + igraph_vector_t *vector; + igraph_vector_t *weights; + igraph_trie_t *trie; +} igraph_i_ncol_parsedata_t; diff --git a/src/foreign-ncol-lexer.l b/src/foreign-ncol-lexer.l new file mode 100644 index 0000000..826d3db --- /dev/null +++ b/src/foreign-ncol-lexer.l @@ -0,0 +1,100 @@ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +%{ + +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "config.h" +#include +#include "foreign-ncol-header.h" +#include "foreign-ncol-parser.h" +#define YY_EXTRA_TYPE igraph_i_ncol_parsedata_t* +#define YY_USER_ACTION yylloc->first_line = yylineno; +/* We assume that 'file' is 'stderr' here. */ +#ifdef USING_R +#define fprintf(file, msg, ...) (1) +#endif +#ifdef stdout +# undef stdout +#endif +#define stdout 0 +#define exit(code) igraph_error("Fatal error in DL parser", __FILE__, \ + __LINE__, IGRAPH_PARSEERROR); +%} + +%option noyywrap +%option prefix="igraph_ncol_yy" +%option outfile="lex.yy.c" +%option nounput +%option noinput +%option nodefault +%option reentrant +%option bison-bridge +%option bison-locations + +alnum [^ \t\n\r] + +%% + + /* ------------------------------------------------whitespace------*/ +[ \t]* { } + + /* ---------------------------------------------------newline------*/ +\n\r|\r\n|\n|\r { return NEWLINE; } + + /* ----------------------------------------------alphanumeric------*/ +{alnum}+ { return ALNUM; } + +<> { if (yyextra->eof) { + yyterminate(); + } else { + yyextra->eof=1; + return NEWLINE; + } + } + + /* ---------------------------------------------anything else------*/ +. { return ERROR; } + +%% + diff --git a/src/foreign-ncol-parser.y b/src/foreign-ncol-parser.y new file mode 100644 index 0000000..7ce3b43 --- /dev/null +++ b/src/foreign-ncol-parser.y @@ -0,0 +1,142 @@ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +%{ + +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include "igraph_hacks_internal.h" +#include "igraph_types.h" +#include "igraph_types_internal.h" +#include "igraph_math.h" +#include "igraph_memory.h" +#include "igraph_error.h" +#include "config.h" +#include "foreign-ncol-header.h" +#include "foreign-ncol-parser.h" + +#define yyscan_t void* + +int igraph_ncol_yylex(YYSTYPE* lvalp, YYLTYPE* llocp, + void* scanner); +int igraph_ncol_yyerror(YYLTYPE* locp, + igraph_i_ncol_parsedata_t *context, + const char *s); +char *igraph_ncol_yyget_text (yyscan_t yyscanner ); +int igraph_ncol_yyget_leng (yyscan_t yyscanner ); +igraph_real_t igraph_ncol_get_number(const char *str, long int len); + +#define scanner context->scanner +%} + +%pure-parser +%output="y.tab.c" +%name-prefix="igraph_ncol_yy" +%defines +%locations +%error-verbose +%parse-param { igraph_i_ncol_parsedata_t* context } +%lex-param { void *scanner } + +%union { + long int edgenum; + double weightnum; +} + +%type edgeid +%type weight + +%token ALNUM +%token NEWLINE +%token ERROR + +%% + +input : /* empty */ + | input NEWLINE + | input edge +; + +edge : edgeid edgeid NEWLINE { + igraph_vector_push_back(context->vector, $1); + igraph_vector_push_back(context->vector, $2); + igraph_vector_push_back(context->weights, 0); + } + | edgeid edgeid weight NEWLINE { + igraph_vector_push_back(context->vector, $1); + igraph_vector_push_back(context->vector, $2); + igraph_vector_push_back(context->weights, $3); + context->has_weights = 1; + } +; + +edgeid : ALNUM { igraph_trie_get2(context->trie, + igraph_ncol_yyget_text(scanner), + igraph_ncol_yyget_leng(scanner), + &$$); }; + +weight : ALNUM { $$=igraph_ncol_get_number(igraph_ncol_yyget_text(scanner), + igraph_ncol_yyget_leng(scanner)); } ; + +%% + +int igraph_ncol_yyerror(YYLTYPE* locp, + igraph_i_ncol_parsedata_t *context, + const char *s) { + snprintf(context->errmsg, sizeof(context->errmsg)/sizeof(char)-1, + "Parse error in NCOL file, line %i (%s)", + locp->first_line, s); + return 0; +} + +igraph_real_t igraph_ncol_get_number(const char *str, long int length) { + igraph_real_t num; + char *tmp=igraph_Calloc(length+1, char); + + strncpy(tmp, str, length); + tmp[length]='\0'; + sscanf(tmp, "%lf", &num); + igraph_Free(tmp); + return num; +} diff --git a/src/foreign-pajek-header.h b/src/foreign-pajek-header.h new file mode 100644 index 0000000..f077ef3 --- /dev/null +++ b/src/foreign-pajek-header.h @@ -0,0 +1,43 @@ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_vector.h" +#include "igraph_types_internal.h" + +typedef struct { + void *scanner; + int eof; + char errmsg[300]; + igraph_vector_t *vector; + igraph_bool_t directed; + int vcount, vcount2; + int actfrom; + int actto; + int mode; /* 0: general, 1: vertex, 2: edge */ + igraph_trie_t *vertex_attribute_names; + igraph_vector_ptr_t *vertex_attributes; + igraph_trie_t *edge_attribute_names; + igraph_vector_ptr_t *edge_attributes; + int vertexid; + int actvertex; + int actedge; +} igraph_i_pajek_parsedata_t; diff --git a/src/foreign-pajek-lexer.l b/src/foreign-pajek-lexer.l new file mode 100644 index 0000000..59b7fb6 --- /dev/null +++ b/src/foreign-pajek-lexer.l @@ -0,0 +1,148 @@ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +%{ + +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "config.h" +#include +#include "foreign-pajek-header.h" +#include "foreign-pajek-parser.h" +#define YY_EXTRA_TYPE igraph_i_pajek_parsedata_t* +#define YY_USER_ACTION yylloc->first_line = yylineno; +/* We assume that 'file' is 'stderr' here. */ +#ifdef USING_R +#define fprintf(file, msg, ...) (1) +#endif +#ifdef stdout +# undef stdout +#endif +#define stdout 0 +#define exit(code) igraph_error("Fatal error in DL parser", __FILE__, \ + __LINE__, IGRAPH_PARSEERROR); +%} + +%option noyywrap +%option prefix="igraph_pajek_yy" +%option outfile="lex.yy.c" +%option nounput +%option noinput +%option nodefault +%option reentrant +%option bison-bridge +%option bison-locations + +digit [0-9] +word [^ \t\r\n] + +%% + +[ \t]* { } +%[^\n]*\n[\r]* { } +%[^\n]*\r[\n]* { } +\*[Nn][eE][Tt] { return NETWORKLINE; } +\*[Nn][Ee][Tt][Ww][Oo][Rr][Kk] { return NETWORKLINE; } +\*[Vv][Ee][Rr][Tt][Ii][Cc][Ee][Ss] { return VERTICESLINE; } +\*[Aa][Rr][Cc][Ss] { return ARCSLINE; } +\*[Ee][Dd][Gg][Ee][Ss] { return EDGESLINE; } +\*[Aa][Rr][Cc][Ss][Ll][Ii][Ss][Tt] { return ARCSLISTLINE; } +\*[Ee][Dd][Gg][Ee][Ss][Ll][Ii][Ss][Tt] { return EDGESLISTLINE; } +\*[Mm][Aa][Tt][Rr][Ii][Xx] { return MATRIXLINE; } +\n\r|\r\n|\n|\r { yyextra->mode=0; return NEWLINE; } +\"[^\"]*\" { return QSTR; } +\([^\)]*\) { return PSTR; } +\-?{digit}+(\.{digit}+)?([eE](\+|\-)?{digit}+)? { + return NUM; } + +[Xx]_[Ff][Aa][Cc][Tt]/[ \t\n\r] { if (yyextra->mode==1) { return VP_X_FACT; } else { return ALNUM; } } +[Yy]_[Ff][Aa][Cc][Tt]/[ \t\n\r] { if (yyextra->mode==1) { return VP_Y_FACT; } else { return ALNUM; } } +[Ii][Cc]/[ \t\n\r] { if (yyextra->mode==1) { return VP_IC; } else { return ALNUM; } } +[Bb][Cc]/[ \t\n\r] { if (yyextra->mode==1) { return VP_BC; } else { return ALNUM; } } +[Bb][Ww]/[ \t\n\r] { if (yyextra->mode==1) { return VP_BW; } else { return ALNUM; } } +[Pp][Hh][Ii]/[ \t\n\r] { if (yyextra->mode==1) { return VP_PHI; } else { return ALNUM; } } +[Rr]/[ \t\n\r] { if (yyextra->mode==1) { return VP_R; } else { return ALNUM; } } +[Qq]/[ \t\n\r] { if (yyextra->mode==1) { return VP_Q; } else { return ALNUM; } } +[Ff][Oo][Nn][Tt]/[ \t\n\r] { if (yyextra->mode==1) { return VP_FONT; } else { return ALNUM; } } +[Uu][Rr][Ll]/[ \t\n\r] { if (yyextra->mode==1) { return VP_URL; } else { return ALNUM; } } + +[Cc]/[ \t\n\r] { if (yyextra->mode==2) { return EP_C; } else { return ALNUM; } } +[Pp]/[ \t\n\r] { if (yyextra->mode==2) { return EP_P; } else { return ALNUM; } } +[Ss]/[ \t\n\r] { if (yyextra->mode==2) { return EP_S; } else { return ALNUM; } } +[Aa]/[ \t\n\r] { if (yyextra->mode==2) { return EP_A; } else { return ALNUM; } } +[Ww]/[ \t\n\r] { if (yyextra->mode==2) { return EP_W; } else { return ALNUM; } } +[Hh]1/[ \t\n\r] { if (yyextra->mode==2) { return EP_H1; } else { return ALNUM; } } +[Hh]2/[ \t\n\r] { if (yyextra->mode==2) { return EP_H2; } else { return ALNUM; } } +[Aa]1/[ \t\n\r] { if (yyextra->mode==2) { return EP_A1; } else { return ALNUM; } } +[Aa]2/[ \t\n\r] { if (yyextra->mode==2) { return EP_A2; } else { return ALNUM; } } +[Kk]1/[ \t\n\r] { if (yyextra->mode==2) { return EP_K1; } else { return ALNUM; } } +[Kk]2/[ \t\n\r] { if (yyextra->mode==2) { return EP_K2; } else { return ALNUM; } } +[Aa][Pp]/[ \t\n\r] { if (yyextra->mode==2) { return EP_AP; } else { return ALNUM; } } +[Ll]/[ \t\n\r] { if (yyextra->mode==2) { return EP_L; } else { return ALNUM; } } +[Ll][Pp]/[ \t\n\r] { if (yyextra->mode==2) { return EP_LP; } else { return ALNUM; } } + +[Ll][Pp][Hh][Ii]/[ \t\n\r] { if (yyextra->mode==1) { return VP_LPHI; } else + if (yyextra->mode==2) { return EP_LPHI; } else { return ALNUM; } } +[Ll][Cc]/[ \t\n\r] { if (yyextra->mode==1) { return VP_LC; } else + if (yyextra->mode==2) { return EP_LC; } else { return ALNUM; } } +[Ll][Rr]/[ \t\n\r] { if (yyextra->mode==1) { return VP_LR; } else + if (yyextra->mode==2) { return EP_LR; } else { return ALNUM; } } +[Ll][Aa]/[ \t\n\r] { if (yyextra->mode==1) { return VP_LA; } else + if (yyextra->mode==2) { return EP_LA; } else { return ALNUM; } } +[Ss][Ii][Zz][Ee]/[ \t\n\r] { if (yyextra->mode==1) { return VP_SIZE; } else + if (yyextra->mode==2) { return EP_SIZE; } else { return ALNUM; } } +[Ff][Oo][Ss]/[ \t\n\r] { if (yyextra->mode==1) { return VP_FOS; } else + if (yyextra->mode==2) { return EP_FOS; } else { return ALNUM; } } + +{word}+ { return ALNUM; } + +<> { if (yyextra->eof) { + yyterminate(); + } else { + yyextra->eof=1; + return NEWLINE; + } + } + +. { return ERROR; } + +%% diff --git a/src/foreign-pajek-parser.y b/src/foreign-pajek-parser.y new file mode 100644 index 0000000..4643b56 --- /dev/null +++ b/src/foreign-pajek-parser.y @@ -0,0 +1,755 @@ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +%{ + +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include "igraph_hacks_internal.h" +#include "igraph_types.h" +#include "igraph_types_internal.h" +#include "igraph_memory.h" +#include "igraph_error.h" +#include "igraph_attributes.h" +#include "config.h" +#include "igraph_math.h" +#include +#include "foreign-pajek-header.h" +#include "foreign-pajek-parser.h" + +#define yyscan_t void* + +int igraph_pajek_yylex(YYSTYPE* lvalp, YYLTYPE* llocp, + void* scanner); +int igraph_pajek_yyerror(YYLTYPE* locp, + igraph_i_pajek_parsedata_t *context, + const char *s); +char *igraph_pajek_yyget_text (yyscan_t yyscanner ); +int igraph_pajek_yyget_leng (yyscan_t yyscanner ); + +int igraph_i_pajek_add_string_vertex_attribute(const char *name, + const char *value, + int len, + igraph_i_pajek_parsedata_t *context); +int igraph_i_pajek_add_string_edge_attribute(const char *name, + const char *value, + int len, + igraph_i_pajek_parsedata_t *context); +int igraph_i_pajek_add_numeric_vertex_attribute(const char *name, + igraph_real_t value, + igraph_i_pajek_parsedata_t *context); +int igraph_i_pajek_add_numeric_edge_attribute(const char *name, + igraph_real_t value, + igraph_i_pajek_parsedata_t *context); +int igraph_i_pajek_add_numeric_attribute(igraph_trie_t *names, + igraph_vector_ptr_t *attrs, + long int count, + const char *attrname, + igraph_integer_t vid, + igraph_real_t number); +int igraph_i_pajek_add_string_attribute(igraph_trie_t *names, + igraph_vector_ptr_t *attrs, + long int count, + const char *attrname, + igraph_integer_t vid, + const char *str); + +int igraph_i_pajek_add_bipartite_type(igraph_i_pajek_parsedata_t *context); +int igraph_i_pajek_check_bipartite(igraph_i_pajek_parsedata_t *context); + +extern igraph_real_t igraph_pajek_get_number(const char *str, long int len); +extern long int igraph_i_pajek_actvertex; +extern long int igraph_i_pajek_actedge; + +#define scanner context->scanner + +%} + +%pure-parser +%output="y.tab.c" +%name-prefix="igraph_pajek_yy" +%defines +%locations +%error-verbose +%parse-param { igraph_i_pajek_parsedata_t* context } +%lex-param { void *scanner } + +%union { + long int intnum; + double realnum; + struct { + char *str; + int len; + } string; +} + +%type longint; +%type arcfrom; +%type arcto; +%type edgefrom; +%type edgeto; +%type number; +%type word; +%type vpwordpar; +%type epwordpar; +%type vertex; + +%token NEWLINE +%token NUM +%token ALNUM +%token QSTR +%token PSTR +%token NETWORKLINE +%token VERTICESLINE +%token ARCSLINE +%token EDGESLINE +%token ARCSLISTLINE +%token EDGESLISTLINE +%token MATRIXLINE +%token ERROR + +%token VP_X_FACT +%token VP_Y_FACT +%token VP_IC +%token VP_BC +%token VP_LC +%token VP_LR +%token VP_LPHI +%token VP_BW +%token VP_FOS +%token VP_PHI +%token VP_R +%token VP_Q +%token VP_LA +%token VP_FONT +%token VP_URL +%token VP_SIZE + +%token EP_C +%token EP_S +%token EP_A +%token EP_W +%token EP_H1 +%token EP_H2 +%token EP_A1 +%token EP_A2 +%token EP_K1 +%token EP_K2 +%token EP_AP +%token EP_P +%token EP_L +%token EP_LP +%token EP_LR +%token EP_LPHI +%token EP_LC +%token EP_LA +%token EP_SIZE +%token EP_FOS + +%% + +input: nethead vertices edgeblock { + if (context->vcount2 > 0) { igraph_i_pajek_check_bipartite(context); } + }; + +nethead: /* empty */ | NETWORKLINE words NEWLINE; + +vertices: verticeshead NEWLINE vertdefs; + +verticeshead: VERTICESLINE longint { + context->vcount=$2; + context->vcount2=0; + } + | VERTICESLINE longint longint { + context->vcount=$2; + context->vcount2=$3; + igraph_i_pajek_add_bipartite_type(context); +}; + +vertdefs: /* empty */ | vertdefs vertexline; + +vertexline: NEWLINE | + vertex NEWLINE | + vertex { context->actvertex=$1; } vertexid vertexcoords shape params NEWLINE { } +; + +vertex: longint { $$=$1; context->mode=1; }; + +vertexid: word { + igraph_i_pajek_add_string_vertex_attribute("id", $1.str, $1.len, context); + igraph_i_pajek_add_string_vertex_attribute("name", $1.str, $1.len, context); +}; + +vertexcoords: /* empty */ + | number number { + igraph_i_pajek_add_numeric_vertex_attribute("x", $1, context); + igraph_i_pajek_add_numeric_vertex_attribute("y", $2, context); + } + | number number number { + igraph_i_pajek_add_numeric_vertex_attribute("x", $1, context); + igraph_i_pajek_add_numeric_vertex_attribute("y", $2, context); + igraph_i_pajek_add_numeric_vertex_attribute("z", $3, context); + }; + +shape: /* empty */ | word { + igraph_i_pajek_add_string_vertex_attribute("shape", $1.str, $1.len, context); +}; + +params: /* empty */ | params param; + +param: + vpword + | VP_X_FACT number { + igraph_i_pajek_add_numeric_vertex_attribute("xfact", $2, context); + } + | VP_Y_FACT number { + igraph_i_pajek_add_numeric_vertex_attribute("yfact", $2, context); + } + | VP_IC number number number { /* RGB color */ + igraph_i_pajek_add_numeric_vertex_attribute("color-red", $2, context); + igraph_i_pajek_add_numeric_vertex_attribute("color-green", $3, context); + igraph_i_pajek_add_numeric_vertex_attribute("color-blue", $4, context); + } + | VP_BC number number number { + igraph_i_pajek_add_numeric_vertex_attribute("framecolor-red", $2, context); + igraph_i_pajek_add_numeric_vertex_attribute("framecolor-green", $3, context); + igraph_i_pajek_add_numeric_vertex_attribute("framecolor-blue", $4, context); + } + | VP_LC number number number { + igraph_i_pajek_add_numeric_vertex_attribute("labelcolor-red", $2, context); + igraph_i_pajek_add_numeric_vertex_attribute("labelcolor-green", $3, context); + igraph_i_pajek_add_numeric_vertex_attribute("labelcolor-blue", $4, context); + } + | VP_LR number { + igraph_i_pajek_add_numeric_vertex_attribute("labeldist", $2, context); + } + | VP_LPHI number { + igraph_i_pajek_add_numeric_vertex_attribute("labeldegree2", $2, context); + } + | VP_BW number { + igraph_i_pajek_add_numeric_vertex_attribute("framewidth", $2, context); + } + | VP_FOS number { + igraph_i_pajek_add_numeric_vertex_attribute("fontsize", $2, context); + } + | VP_PHI number { + igraph_i_pajek_add_numeric_vertex_attribute("rotation", $2, context); + } + | VP_R number { + igraph_i_pajek_add_numeric_vertex_attribute("radius", $2, context); + } + | VP_Q number { + igraph_i_pajek_add_numeric_vertex_attribute("diamondratio", $2, context); + } + | VP_LA number { + igraph_i_pajek_add_numeric_vertex_attribute("labeldegree", $2, context); + } + | VP_SIZE number { + igraph_i_pajek_add_numeric_vertex_attribute("vertexsize", $2, context); + } +; + +vpword: VP_FONT { context->mode=3; } vpwordpar { + context->mode=1; + igraph_i_pajek_add_string_vertex_attribute("font", $3.str, $3.len, context); + } + | VP_URL { context->mode=3; } vpwordpar { + context->mode=1; + igraph_i_pajek_add_string_vertex_attribute("url", $3.str, $3.len, context); + } + | VP_IC { context->mode=3; } vpwordpar { + context->mode=1; + igraph_i_pajek_add_string_vertex_attribute("color", $3.str, $3.len, context); + } + | VP_BC { context->mode=3; } vpwordpar { + context->mode=1; + igraph_i_pajek_add_string_vertex_attribute("framecolor", + $3.str, $3.len, context); + } + | VP_LC { context->mode=3; } vpwordpar { + context->mode=1; + igraph_i_pajek_add_string_vertex_attribute("labelcolor", + $3.str, $3.len, context); + } +; + +vpwordpar: word { $$=$1; }; + +edgeblock: /* empty */ | edgeblock arcs | edgeblock edges | edgeblock arcslist | edgeblock edgeslist | edgeblock adjmatrix; + +arcs: ARCSLINE NEWLINE arcsdefs { context->directed=1; } + | ARCSLINE number NEWLINE arcsdefs { context->directed=1; }; + +arcsdefs: /* empty */ | arcsdefs arcsline; + +arcsline: NEWLINE | + arcfrom arcto { context->actedge++; + context->mode=2; } weight edgeparams NEWLINE { + igraph_vector_push_back(context->vector, $1-1); + igraph_vector_push_back(context->vector, $2-1); } +; + +arcfrom: longint; + +arcto: longint; + +edges: EDGESLINE NEWLINE edgesdefs { context->directed=0; } + | EDGESLINE number NEWLINE edgesdefs { context->directed=0; } + +edgesdefs: /* empty */ | edgesdefs edgesline; + +edgesline: NEWLINE | + edgefrom edgeto { context->actedge++; + context->mode=2; } weight edgeparams NEWLINE { + igraph_vector_push_back(context->vector, $1-1); + igraph_vector_push_back(context->vector, $2-1); } +; + +edgefrom: longint; + +edgeto: longint; + +weight: /* empty */ | number { + igraph_i_pajek_add_numeric_edge_attribute("weight", $1, context); +}; + +edgeparams: /* empty */ | edgeparams edgeparam; + +edgeparam: + epword + | EP_C number number number { + igraph_i_pajek_add_numeric_edge_attribute("color-red", $2, context); + igraph_i_pajek_add_numeric_edge_attribute("color-green", $3, context); + igraph_i_pajek_add_numeric_edge_attribute("color-blue", $4, context); + } + | EP_S number { + igraph_i_pajek_add_numeric_edge_attribute("arrowsize", $2, context); + } + | EP_W number { + igraph_i_pajek_add_numeric_edge_attribute("edgewidth", $2, context); + } + | EP_H1 number { + igraph_i_pajek_add_numeric_edge_attribute("hook1", $2, context); + } + | EP_H2 number { + igraph_i_pajek_add_numeric_edge_attribute("hook2", $2, context); + } + | EP_A1 number { + igraph_i_pajek_add_numeric_edge_attribute("angle1", $2, context); + } + | EP_A2 number { + igraph_i_pajek_add_numeric_edge_attribute("angle2", $2, context); + } + | EP_K1 number { + igraph_i_pajek_add_numeric_edge_attribute("velocity1", $2, context); + } + | EP_K2 number { + igraph_i_pajek_add_numeric_edge_attribute("velocity2", $2, context); + } + | EP_AP number { + igraph_i_pajek_add_numeric_edge_attribute("arrowpos", $2, context); + } + | EP_LP number { + igraph_i_pajek_add_numeric_edge_attribute("labelpos", $2, context); + } + | EP_LR number { + igraph_i_pajek_add_numeric_edge_attribute("labelangle", $2, context); + } + | EP_LPHI number { + igraph_i_pajek_add_numeric_edge_attribute("labelangle2", $2, context); + } + | EP_LA number { + igraph_i_pajek_add_numeric_edge_attribute("labeldegree", $2, context); + } + | EP_SIZE number { /* what is this??? */ + igraph_i_pajek_add_numeric_edge_attribute("arrowsize", $2, context); + } + | EP_FOS number { + igraph_i_pajek_add_numeric_edge_attribute("fontsize", $2, context); + } +; + +epword: EP_A { context->mode=4; } epwordpar { + context->mode=2; + igraph_i_pajek_add_string_edge_attribute("arrowtype", $3.str, $3.len, context); + } + | EP_P { context->mode=4; } epwordpar { + context->mode=2; + igraph_i_pajek_add_string_edge_attribute("linepattern", $3.str, $3.len, context); + } + | EP_L { context->mode=4; } epwordpar { + context->mode=2; + igraph_i_pajek_add_string_edge_attribute("label", $3.str, $3.len, context); + } + | EP_LC { context->mode=4; } epwordpar { + context->mode=2; + igraph_i_pajek_add_string_edge_attribute("labelcolor", $3.str, $3.len, context); + } + | EP_C { context->mode=4; } epwordpar { + context->mode=2; + igraph_i_pajek_add_string_edge_attribute("color", $3.str, $3.len, context); + } +; + +epwordpar: word { context->mode=2; $$=$1; }; + +arcslist: ARCSLISTLINE NEWLINE arcslistlines { context->directed=1; }; + +arcslistlines: /* empty */ | arcslistlines arclistline; + +arclistline: NEWLINE | arclistfrom arctolist NEWLINE; + +arctolist: /* empty */ | arctolist arclistto; + +arclistfrom: longint { context->mode=0; context->actfrom=labs($1)-1; }; + +arclistto: longint { + igraph_vector_push_back(context->vector, context->actfrom); + igraph_vector_push_back(context->vector, labs($1)-1); +}; + +edgeslist: EDGESLISTLINE NEWLINE edgelistlines { context->directed=0; }; + +edgelistlines: /* empty */ | edgelistlines edgelistline; + +edgelistline: NEWLINE | edgelistfrom edgetolist NEWLINE; + +edgetolist: /* empty */ | edgetolist edgelistto; + +edgelistfrom: longint { context->mode=0; context->actfrom=labs($1)-1; }; + +edgelistto: longint { + igraph_vector_push_back(context->vector, context->actfrom); + igraph_vector_push_back(context->vector, labs($1)-1); +}; + +/* -----------------------------------------------------*/ + +adjmatrix: matrixline NEWLINE adjmatrixlines; + +matrixline: MATRIXLINE { context->actfrom=0; + context->actto=0; + context->directed=(context->vcount2==0); + }; + +adjmatrixlines: /* empty */ | adjmatrixlines adjmatrixline; + +adjmatrixline: adjmatrixnumbers NEWLINE { context->actfrom++; context->actto=0; }; + +adjmatrixnumbers: /* empty */ | adjmatrixentry adjmatrixnumbers; + +adjmatrixentry: number { + if ($1 != 0) { + if (context->vcount2==0) { + context->actedge++; + igraph_i_pajek_add_numeric_edge_attribute("weight", $1, context); + igraph_vector_push_back(context->vector, context->actfrom); + igraph_vector_push_back(context->vector, context->actto); + } else if (context->vcount2 + context->actto < context->vcount) { + context->actedge++; + igraph_i_pajek_add_numeric_edge_attribute("weight", $1, context); + igraph_vector_push_back(context->vector, context->actfrom); + igraph_vector_push_back(context->vector, + context->vcount2+context->actto); + } + } + context->actto++; +}; + +/* -----------------------------------------------------*/ + +longint: NUM { $$=igraph_pajek_get_number(igraph_pajek_yyget_text(scanner), + igraph_pajek_yyget_leng(scanner)); }; + +number: NUM { $$=igraph_pajek_get_number(igraph_pajek_yyget_text(scanner), + igraph_pajek_yyget_leng(scanner)); }; + +words: /* empty */ | words word; + +word: ALNUM { $$.str=igraph_pajek_yyget_text(scanner); + $$.len=igraph_pajek_yyget_leng(scanner); } + | NUM { $$.str=igraph_pajek_yyget_text(scanner); + $$.len=igraph_pajek_yyget_leng(scanner); } + | QSTR { $$.str=igraph_pajek_yyget_text(scanner)+1; + $$.len=igraph_pajek_yyget_leng(scanner)-2; }; + +%% + +int igraph_pajek_yyerror(YYLTYPE* locp, + igraph_i_pajek_parsedata_t *context, + const char *s) { + snprintf(context->errmsg, sizeof(context->errmsg)/sizeof(char)-1, + "Parse error in Pajek file, line %i (%s)", + locp->first_line, s); + return 0; +} + +igraph_real_t igraph_pajek_get_number(const char *str, long int length) { + igraph_real_t num; + char *tmp=igraph_Calloc(length+1, char); + + strncpy(tmp, str, length); + tmp[length]='\0'; + sscanf(tmp, "%lf", &num); + igraph_Free(tmp); + return num; +} + +/* TODO: NA's */ + +int igraph_i_pajek_add_numeric_attribute(igraph_trie_t *names, + igraph_vector_ptr_t *attrs, + long int count, + const char *attrname, + igraph_integer_t vid, + igraph_real_t number) { + long int attrsize=igraph_trie_size(names); + long int id; + igraph_vector_t *na; + igraph_attribute_record_t *rec; + + igraph_trie_get(names, attrname, &id); + if (id == attrsize) { + /* add a new attribute */ + rec=igraph_Calloc(1, igraph_attribute_record_t); + na=igraph_Calloc(1, igraph_vector_t); + igraph_vector_init(na, count); + rec->name=strdup(attrname); + rec->type=IGRAPH_ATTRIBUTE_NUMERIC; + rec->value=na; + igraph_vector_ptr_push_back(attrs, rec); + } + rec=VECTOR(*attrs)[id]; + na=(igraph_vector_t*)rec->value; + if (igraph_vector_size(na) == vid) { + IGRAPH_CHECK(igraph_vector_push_back(na, number)); + } else if (igraph_vector_size(na) < vid) { + long int origsize=igraph_vector_size(na); + IGRAPH_CHECK(igraph_vector_resize(na, (long int)vid+1)); + for (;origsizename=strdup(attrname); + rec->type=IGRAPH_ATTRIBUTE_STRING; + rec->value=na; + igraph_vector_ptr_push_back(attrs, rec); + } + rec=VECTOR(*attrs)[id]; + na=(igraph_strvector_t*)rec->value; + if (igraph_strvector_size(na) <= vid) { + long int origsize=igraph_strvector_size(na); + IGRAPH_CHECK(igraph_strvector_resize(na, vid+1)); + for (;origsizevertex_attribute_names, + context->vertex_attributes, + context->vcount, + name, context->actvertex-1, + tmp); + + igraph_Free(tmp); + IGRAPH_FINALLY_CLEAN(1); + + return ret; +} + +int igraph_i_pajek_add_string_edge_attribute(const char *name, + const char *value, + int len, + igraph_i_pajek_parsedata_t *context) { + char *tmp; + int ret; + + tmp=igraph_Calloc(len+1, char); + if (tmp==0) { + IGRAPH_ERROR("cannot add element to hash table", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmp); + strncpy(tmp, value, len); + tmp[len]='\0'; + + ret=igraph_i_pajek_add_string_attribute(context->edge_attribute_names, + context->edge_attributes, + context->actedge, + name, context->actedge-1, + tmp); + + igraph_Free(tmp); + IGRAPH_FINALLY_CLEAN(1); + + return ret; +} + +int igraph_i_pajek_add_numeric_vertex_attribute(const char *name, + igraph_real_t value, + igraph_i_pajek_parsedata_t *context) { + + return + igraph_i_pajek_add_numeric_attribute(context->vertex_attribute_names, + context->vertex_attributes, + context->vcount, + name, context->actvertex-1, + value); +} + +int igraph_i_pajek_add_numeric_edge_attribute(const char *name, + igraph_real_t value, + igraph_i_pajek_parsedata_t *context) { + + return + igraph_i_pajek_add_numeric_attribute(context->edge_attribute_names, + context->edge_attributes, + context->actedge, + name, context->actedge-1, + value); +} + +int igraph_i_pajek_add_bipartite_type(igraph_i_pajek_parsedata_t *context) { + + const char *attrname="type"; + igraph_trie_t *names=context->vertex_attribute_names; + igraph_vector_ptr_t *attrs=context->vertex_attributes; + int i, n=context->vcount, n1=context->vcount2; + long int attrid, attrsize=igraph_trie_size(names); + igraph_attribute_record_t *rec; + igraph_vector_t *na; + + if (n1 > n) { + IGRAPH_ERROR("Invalid number of vertices in bipartite Pajek file", + IGRAPH_PARSEERROR); + } + + igraph_trie_get(names, attrname, &attrid); + if (attrid != attrsize) { + IGRAPH_ERROR("Duplicate 'type' attribute in Pajek file, " + "this should not happen", IGRAPH_EINTERNAL); + } + + /* add a new attribute */ + rec=igraph_Calloc(1, igraph_attribute_record_t); + na=igraph_Calloc(1, igraph_vector_t); + igraph_vector_init(na, n); + rec->name=strdup(attrname); + rec->type=IGRAPH_ATTRIBUTE_NUMERIC; + rec->value=na; + igraph_vector_ptr_push_back(attrs, rec); + + for (i=0; ivector; + int i, n1=context->vcount2; + int ne=igraph_vector_size(edges); + + for (i=0; i n1 && v2 > n1) ) { + IGRAPH_WARNING("Invalid edge in bipartite graph"); + } + } + + return 0; +} diff --git a/src/foreign.c b/src/foreign.c new file mode 100644 index 0000000..38dbd0b --- /dev/null +++ b/src/foreign.c @@ -0,0 +1,3390 @@ +/* -*- mode: C -*- */ +/* + IGraph R package. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_foreign.h" +#include "igraph_math.h" +#include "igraph_gml_tree.h" +#include "igraph_memory.h" +#include "igraph_attributes.h" +#include "igraph_interface.h" +#include "igraph_interrupt_internal.h" +#include "igraph_constructors.h" +#include "igraph_types_internal.h" +#include "config.h" + +#include /* isspace */ +#include +#include + +/** + * \section about_loadsave + * + * These functions can write a graph to a file, or read a graph + * from a file. + * + * Note that as \a igraph uses the traditional C streams, it is + * possible to read/write files from/to memory, at least on GNU + * operating systems supporting \quote non-standard\endquote streams. + */ + +/** + * \ingroup loadsave + * \function igraph_read_graph_edgelist + * \brief Reads an edge list from a file and creates a graph. + * + * + * This format is simply a series of even number integers separated by + * whitespace. The one edge (ie. two integers) per line format is thus + * not required (but recommended for readability). Edges of directed + * graphs are assumed to be in from, to order. + * \param graph Pointer to an uninitialized graph object. + * \param instream Pointer to a stream, it should be readable. + * \param n The number of vertices in the graph. If smaller than the + * largest integer in the file it will be ignored. It is thus + * safe to supply zero here. + * \param directed Logical, if true the graph is directed, if false it + * will be undirected. + * \return Error code: + * \c IGRAPH_PARSEERROR: if there is a + * problem reading the file, or the file is syntactically + * incorrect. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges. It is assumed that + * reading an integer requires O(1) + * time. + */ + +int igraph_read_graph_edgelist(igraph_t *graph, FILE *instream, + igraph_integer_t n, igraph_bool_t directed) { + + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + long int from, to; + int c; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, 100)); + + /* skip all whitespace */ + do { + c = getc (instream); + } while (isspace (c)); + ungetc (c, instream); + + while (!feof(instream)) { + int read; + + IGRAPH_ALLOW_INTERRUPTION(); + + read = fscanf(instream, "%li", &from); + if (read != 1) { + IGRAPH_ERROR("parsing edgelist file failed", IGRAPH_PARSEERROR); + } + read = fscanf(instream, "%li", &to); + if (read != 1) { + IGRAPH_ERROR("parsing edgelist file failed", IGRAPH_PARSEERROR); + } + IGRAPH_CHECK(igraph_vector_push_back(&edges, from)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to)); + + /* skip all whitespace */ + do { + c = getc (instream); + } while (isspace (c)); + ungetc (c, instream); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +#include "foreign-ncol-header.h" + +int igraph_ncol_yylex_init_extra (igraph_i_ncol_parsedata_t* user_defined, + void* scanner); +int igraph_ncol_yylex_destroy (void *scanner ); +int igraph_ncol_yyparse (igraph_i_ncol_parsedata_t* context); +void igraph_ncol_yyset_in (FILE * in_str, void* yyscanner ); + +/** + * \ingroup loadsave + * \function igraph_read_graph_ncol + * \brief Reads a .ncol file used by LGL. + * + * Also useful for creating graphs from \quote named\endquote (and + * optionally weighted) edge lists. + * + * + * This format is used by the Large Graph Layout program + * (http://lgl.sourceforge.net), and it is simply a + * symbolic weighted edge list. It is a simple text file with one edge + * per line. An edge is defined by two symbolic vertex names separated + * by whitespace. (The symbolic vertex names themselves cannot contain + * whitespace. They might follow by an optional number, this will be + * the weight of the edge; the number can be negative and can be in + * scientific notation. If there is no weight specified to an edge it + * is assumed to be zero. + * + * + * The resulting graph is always undirected. + * LGL cannot deal with files which contain multiple or loop edges, + * this is however not checked here, as \a igraph is happy with + * these. + * \param graph Pointer to an uninitialized graph object. + * \param instream Pointer to a stream, it should be readable. + * \param predefnames Pointer to the symbolic names of the vertices in + * the file. If \c NULL is given here then vertex ids will be + * assigned to vertex names in the order of their appearance in + * the \c .ncol file. If it is not \c NULL and some unknown + * vertex names are found in the \c .ncol file then new vertex + * ids will be assigned to them. + * \param names Logical value, if TRUE the symbolic names of the + * vertices will be added to the graph as a vertex attribute + * called \quote name\endquote. + * \param weights Whether to add the weights of the edges to the + * graph as an edge attribute called \quote weight\endquote. + * \c IGRAPH_ADD_WEIGHTS_YES adds the weights (even if they + * are not present in the file, in this case they are assumed + * to be zero). \c IGRAPH_ADD_WEIGHTS_NO does not add any + * edge attribute. \c IGRAPH_ADD_WEIGHTS_IF_PRESENT adds the + * attribute if and only if there is at least one explicit + * edge weight in the input file. + * \param directed Whether to create a directed graph. As this format + * was originally used only for undirected graphs there is no + * information in the file about the directedness of the graph. + * Set this parameter to \c IGRAPH_DIRECTED or \c + * IGRAPH_UNDIRECTED to create a directed or undirected graph. + * \return Error code: + * \c IGRAPH_PARSEERROR: if there is a + * problem reading + * the file, or the file is syntactically incorrect. + * + * Time complexity: + * O(|V|+|E|log(|V|)) if we neglect + * the time required by the parsing. As usual + * |V| is the number of vertices, + * while |E| is the number of edges. + * + * \sa \ref igraph_read_graph_lgl(), \ref igraph_write_graph_ncol() + */ + +int igraph_read_graph_ncol(igraph_t *graph, FILE *instream, + igraph_strvector_t *predefnames, + igraph_bool_t names, + igraph_add_weights_t weights, + igraph_bool_t directed) { + + igraph_vector_t edges, ws; + igraph_trie_t trie = IGRAPH_TRIE_NULL; + igraph_integer_t no_of_nodes; + long int no_predefined = 0; + igraph_vector_ptr_t name, weight; + igraph_vector_ptr_t *pname = 0, *pweight = 0; + igraph_attribute_record_t namerec, weightrec; + const char *namestr = "name", *weightstr = "weight"; + igraph_i_ncol_parsedata_t context; + + IGRAPH_CHECK(igraph_empty(graph, 0, directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + IGRAPH_TRIE_INIT_FINALLY(&trie, names); + IGRAPH_VECTOR_INIT_FINALLY(&ws, 0); + + /* Add the predefined names, if any */ + if (predefnames != 0) { + long int i, id, n; + char *key; + n = no_predefined = igraph_strvector_size(predefnames); + for (i = 0; i < n; i++) { + igraph_strvector_get(predefnames, i, &key); + igraph_trie_get(&trie, key, &id); + if (id != i) { + IGRAPH_WARNING("reading NCOL file, duplicate entry in predefnames"); + no_predefined--; + } + } + } + + context.has_weights = 0; + context.vector = &edges; + context.weights = &ws; + context.trie = ≜ + context.eof = 0; + + igraph_ncol_yylex_init_extra(&context, &context.scanner); + IGRAPH_FINALLY(igraph_ncol_yylex_destroy, context.scanner); + + igraph_ncol_yyset_in(instream, context.scanner); + + if (igraph_ncol_yyparse(&context)) { + if (context.errmsg[0] != 0) { + IGRAPH_ERROR(context.errmsg, IGRAPH_PARSEERROR); + } else { + IGRAPH_ERROR("Cannot read NCOL file", IGRAPH_PARSEERROR); + } + } + + if (predefnames != 0 && + igraph_trie_size(&trie) != no_predefined) { + IGRAPH_WARNING("unknown vertex/vertices found, predefnames extended"); + } + + if (names) { + const igraph_strvector_t *namevec; + IGRAPH_CHECK(igraph_vector_ptr_init(&name, 1)); + pname = &name; + igraph_trie_getkeys(&trie, &namevec); /* dirty */ + namerec.name = namestr; + namerec.type = IGRAPH_ATTRIBUTE_STRING; + namerec.value = namevec; + VECTOR(name)[0] = &namerec; + } + + if (weights == IGRAPH_ADD_WEIGHTS_YES || + (weights == IGRAPH_ADD_WEIGHTS_IF_PRESENT && context.has_weights)) { + IGRAPH_CHECK(igraph_vector_ptr_init(&weight, 1)); + pweight = &weight; + weightrec.name = weightstr; + weightrec.type = IGRAPH_ATTRIBUTE_NUMERIC; + weightrec.value = &ws; + VECTOR(weight)[0] = &weightrec; + } + + if (igraph_vector_empty(&edges)) { + no_of_nodes = 0; + } else { + no_of_nodes = igraph_vector_max(&edges) + 1; + } + + IGRAPH_CHECK(igraph_add_vertices(graph, no_of_nodes, pname)); + IGRAPH_CHECK(igraph_add_edges(graph, &edges, pweight)); + + if (pname) { + igraph_vector_ptr_destroy(pname); + } + if (pweight) { + igraph_vector_ptr_destroy(pweight); + } + igraph_vector_destroy(&ws); + igraph_trie_destroy(&trie); + igraph_vector_destroy(&edges); + igraph_ncol_yylex_destroy(context.scanner); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +#include "foreign-lgl-header.h" + +int igraph_lgl_yylex_init_extra (igraph_i_lgl_parsedata_t* user_defined, + void* scanner); +int igraph_lgl_yylex_destroy (void *scanner ); +int igraph_lgl_yyparse (igraph_i_lgl_parsedata_t* context); +void igraph_lgl_yyset_in (FILE * in_str, void* yyscanner ); + +/** + * \ingroup loadsave + * \function igraph_read_graph_lgl + * \brief Reads a graph from an .lgl file + * + * + * The .lgl format is used by the Large Graph + * Layout visualization software + * (http://lgl.sourceforge.net), it can + * describe undirected optionally weighted graphs. From the LGL + * manual: + * + * \blockquote The second format is the LGL file format + * (.lgl file + * suffix). This is yet another graph file format that tries to be as + * stingy as possible with space, yet keeping the edge file in a human + * readable (not binary) format. The format itself is like the + * following: + * \verbatim # vertex1name +vertex2name [optionalWeight] +vertex3name [optionalWeight] \endverbatim + * Here, the first vertex of an edge is preceded with a pound sign + * '#'. Then each vertex that shares an edge with that vertex is + * listed one per line on subsequent lines. \endblockquote + * + * + * LGL cannot handle loop and multiple edges or directed graphs, but + * in \a igraph it is not an error to have multiple and loop edges. + * \param graph Pointer to an uninitialized graph object. + * \param instream A stream, it should be readable. + * \param names Logical value, if TRUE the symbolic names of the + * vertices will be added to the graph as a vertex attribute + * called \quote name\endquote. + * \param weights Whether to add the weights of the edges to the + * graph as an edge attribute called \quote weight\endquote. + * \c IGRAPH_ADD_WEIGHTS_YES adds the weights (even if they + * are not present in the file, in this case they are assumed + * to be zero). \c IGRAPH_ADD_WEIGHTS_NO does not add any + * edge attribute. \c IGRAPH_ADD_WEIGHTS_IF_PRESENT adds the + * attribute if and only if there is at least one explicit + * edge weight in the input file. + * \param directed Whether to create a directed graph. As this format + * was originally used only for undirected graphs there is no + * information in the file about the directedness of the graph. + * Set this parameter to \c IGRAPH_DIRECTED or \c + * IGRAPH_UNDIRECTED to create a directed or undirected graph. + * \return Error code: + * \c IGRAPH_PARSEERROR: if there is a + * problem reading the file, or the file is syntactically + * incorrect. + * + * Time complexity: + * O(|V|+|E|log(|V|)) if we neglect + * the time required by the parsing. As usual + * |V| is the number of vertices, + * while |E| is the number of edges. + * + * \sa \ref igraph_read_graph_ncol(), \ref igraph_write_graph_lgl() + * + * \example examples/simple/igraph_read_graph_lgl.c + */ + +int igraph_read_graph_lgl(igraph_t *graph, FILE *instream, + igraph_bool_t names, + igraph_add_weights_t weights, + igraph_bool_t directed) { + + igraph_vector_t edges = IGRAPH_VECTOR_NULL, ws = IGRAPH_VECTOR_NULL; + igraph_trie_t trie = IGRAPH_TRIE_NULL; + igraph_vector_ptr_t name, weight; + igraph_vector_ptr_t *pname = 0, *pweight = 0; + igraph_attribute_record_t namerec, weightrec; + const char *namestr = "name", *weightstr = "weight"; + igraph_i_lgl_parsedata_t context; + + IGRAPH_VECTOR_INIT_FINALLY(&ws, 0); + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_TRIE_INIT_FINALLY(&trie, names); + + context.has_weights = 0; + context.vector = &edges; + context.weights = &ws; + context.trie = ≜ + context.eof = 0; + + igraph_lgl_yylex_init_extra(&context, &context.scanner); + IGRAPH_FINALLY(igraph_lgl_yylex_destroy, context.scanner); + + igraph_lgl_yyset_in(instream, context.scanner); + + if (igraph_lgl_yyparse(&context)) { + if (context.errmsg[0] != 0) { + IGRAPH_ERROR(context.errmsg, IGRAPH_PARSEERROR); + } else { + IGRAPH_ERROR("Cannot read LGL file", IGRAPH_PARSEERROR); + } + } + + IGRAPH_CHECK(igraph_empty(graph, 0, directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + + if (names) { + const igraph_strvector_t *namevec; + IGRAPH_CHECK(igraph_vector_ptr_init(&name, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &name); + pname = &name; + igraph_trie_getkeys(&trie, &namevec); /* dirty */ + namerec.name = namestr; + namerec.type = IGRAPH_ATTRIBUTE_STRING; + namerec.value = namevec; + VECTOR(name)[0] = &namerec; + } + + if (weights == IGRAPH_ADD_WEIGHTS_YES || + (weights == IGRAPH_ADD_WEIGHTS_IF_PRESENT && context.has_weights)) { + IGRAPH_CHECK(igraph_vector_ptr_init(&weight, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &weight); + pweight = &weight; + weightrec.name = weightstr; + weightrec.type = IGRAPH_ATTRIBUTE_NUMERIC; + weightrec.value = &ws; + VECTOR(weight)[0] = &weightrec; + } + + IGRAPH_CHECK(igraph_add_vertices(graph, (igraph_integer_t) + igraph_trie_size(&trie), pname)); + IGRAPH_CHECK(igraph_add_edges(graph, &edges, pweight)); + + if (pweight) { + igraph_vector_ptr_destroy(pweight); + IGRAPH_FINALLY_CLEAN(1); + } + if (pname) { + igraph_vector_ptr_destroy(pname); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_trie_destroy(&trie); + igraph_vector_destroy(&edges); + igraph_vector_destroy(&ws); + igraph_lgl_yylex_destroy(context.scanner); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +#include "foreign-pajek-header.h" + +int igraph_pajek_yylex_init_extra(igraph_i_pajek_parsedata_t* user_defined, + void* scanner); +int igraph_pajek_yylex_destroy (void *scanner ); +int igraph_pajek_yyparse (igraph_i_pajek_parsedata_t* context); +void igraph_pajek_yyset_in (FILE * in_str, void* yyscanner ); + +/** + * \function igraph_read_graph_pajek + * \brief Reads a file in Pajek format + * + * \param graph Pointer to an uninitialized graph object. + * \param file An already opened file handler. + * \return Error code. + * + * + * Only a subset of the Pajek format is implemented. This is partially + * because this format is not very well documented, but also because + * igraph does not support some Pajek features, like + * multigraphs. + * + * + * Starting from version 0.6.1 igraph reads bipartite (two-mode) + * graphs from Pajek files and add the \c type vertex attribute for them. + * Warnings are given for invalid edges, i.e. edges connecting + * vertices of the same type. + * + * + * The list of the current limitations: + * \olist + * \oli Only .net files are supported, Pajek + * project files (.paj) are not. These might be + * supported in the future if there is need for it. + * \oli Time events networks are not supported. + * \oli Hypergraphs (ie. graphs with non-binary edges) are not + * supported. + * \oli Graphs with both directed and non-directed edges are not + * supported, are they cannot be represented in + * igraph. + * \oli Only Pajek networks are supported, permutations, hierarchies, + * clusters and vectors are not. + * \oli Graphs with multiple edge sets are not supported. + * \endolist + * + * + * If there are attribute handlers installed, + * igraph also reads the vertex and edge attributes + * from the file. Most attributes are renamed to be more informative: + * `\c color' instead of `\c c', `\c xfact' instead of `\c x_fact', + * `\c yfact' instead of `y_fact', `\c labeldist' instead of `\c lr', + * `\c labeldegree2' instead of `\c lphi', `\c framewidth' instead of `\c bw', + * `\c fontsize' + * instead of `\c fos', `\c rotation' instead of `\c phi', `\c radius' instead + * of `\c r', + * `\c diamondratio' instead of `\c q', `\c labeldegree' instead of `\c la', + * `\c vertexsize' + * instead of `\c size', `\c color' instead of `\c ic', `\c framecolor' instead of + * `\c bc', `\c labelcolor' instead of `\c lc', these belong to vertices. + * + * + * Edge attributes are also renamed, `\c s' to `\c arrowsize', `\c w' + * to `\c edgewidth', `\c h1' to `\c hook1', `\c h2' to `\c hook2', + * `\c a1' to `\c angle1', `\c a2' to `\c angle2', `\c k1' to + * `\c velocity1', `\c k2' to `\c velocity2', `\c ap' to `\c + * arrowpos', `\c lp' to `\c labelpos', `\c lr' to + * `\c labelangle', `\c lphi' to `\c labelangle2', `\c la' to `\c + * labeldegree', `\c fos' to + * `\c fontsize', `\c a' to `\c arrowtype', `\c p' to `\c + * linepattern', `\c l' to `\c label', `\c lc' to + * `\c labelcolor', `\c c' to `\c color'. + * + * + * In addition the following vertex attributes might be added: `\c id' + * if there are vertex ids in the file, `\c x' and `\c y' or `\c x' + * and `\c y' and `\c z' if there are vertex coordinates in the file. + * + * The `\c weight' edge attribute might be + * added if there are edge weights present. + * + * + * See the pajek homepage: + * http://vlado.fmf.uni-lj.si/pub/networks/pajek/ for more info on + * Pajek and the Pajek manual: + * http://vlado.fmf.uni-lj.si/pub/networks/pajek/doc/pajekman.pdf for + * information on the Pajek file format. + * + * + * Time complexity: O(|V|+|E|+|A|), |V| is the number of vertices, |E| + * the number of edges, |A| the number of attributes (vertex + edge) + * in the graph if there are attribute handlers installed. + * + * \sa \ref igraph_write_graph_pajek() for writing Pajek files, \ref + * igraph_read_graph_graphml() for reading GraphML files. + * + * \example examples/simple/foreign.c + */ + +int igraph_read_graph_pajek(igraph_t *graph, FILE *instream) { + + igraph_vector_t edges; + igraph_trie_t vattrnames; + igraph_vector_ptr_t vattrs; + igraph_trie_t eattrnames; + igraph_vector_ptr_t eattrs; + long int i, j; + igraph_i_pajek_parsedata_t context; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + IGRAPH_TRIE_INIT_FINALLY(&vattrnames, 1); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&vattrs, 0); + IGRAPH_TRIE_INIT_FINALLY(&eattrnames, 1); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&eattrs, 0); + + context.vector = &edges; + context.mode = 0; + context.vcount = -1; + context.vertexid = 0; + context.vertex_attribute_names = &vattrnames; + context.vertex_attributes = &vattrs; + context.edge_attribute_names = &eattrnames; + context.edge_attributes = &eattrs; + context.actedge = 0; + context.eof = 0; + + igraph_pajek_yylex_init_extra(&context, &context.scanner); + IGRAPH_FINALLY(igraph_pajek_yylex_destroy, context.scanner); + + igraph_pajek_yyset_in(instream, context.scanner); + + if (igraph_pajek_yyparse(&context)) { + if (context.errmsg[0] != 0) { + IGRAPH_ERROR(context.errmsg, IGRAPH_PARSEERROR); + } else { + IGRAPH_ERROR("Cannot read Pajek file", IGRAPH_PARSEERROR); + } + } + + if (context.vcount < 0) { + IGRAPH_ERROR("invalid vertex count in Pajek file", IGRAPH_EINVAL); + } + if (context.vcount2 < 0) { + IGRAPH_ERROR("invalid 2-mode vertex count in Pajek file", IGRAPH_EINVAL); + } + + for (i = 0; i < igraph_vector_ptr_size(&eattrs); i++) { + igraph_attribute_record_t *rec = VECTOR(eattrs)[i]; + if (rec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *vec = (igraph_vector_t*)rec->value; + long int origsize = igraph_vector_size(vec); + igraph_vector_resize(vec, context.actedge); + for (j = origsize; j < context.actedge; j++) { + VECTOR(*vec)[j] = IGRAPH_NAN; + } + } else if (rec->type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *strvec = (igraph_strvector_t*)rec->value; + long int origsize = igraph_strvector_size(strvec); + igraph_strvector_resize(strvec, context.actedge); + for (j = origsize; j < context.actedge; j++) { + igraph_strvector_set(strvec, j, ""); + } + } + } + + IGRAPH_CHECK(igraph_empty(graph, 0, context.directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_add_vertices(graph, context.vcount, &vattrs)); + IGRAPH_CHECK(igraph_add_edges(graph, &edges, &eattrs)); + + for (i = 0; i < igraph_vector_ptr_size(&vattrs); i++) { + igraph_attribute_record_t *rec = VECTOR(vattrs)[i]; + if (rec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *vec = (igraph_vector_t*) rec->value; + igraph_vector_destroy(vec); + igraph_Free(vec); + } else if (rec->type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *strvec = (igraph_strvector_t *)rec->value; + igraph_strvector_destroy(strvec); + igraph_Free(strvec); + } + igraph_free( (char*)(rec->name)); + igraph_Free(rec); + } + + for (i = 0; i < igraph_vector_ptr_size(&eattrs); i++) { + igraph_attribute_record_t *rec = VECTOR(eattrs)[i]; + if (rec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *vec = (igraph_vector_t*) rec->value; + igraph_vector_destroy(vec); + igraph_Free(vec); + } else if (rec->type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *strvec = (igraph_strvector_t *)rec->value; + igraph_strvector_destroy(strvec); + igraph_Free(strvec); + } + igraph_free( (char*)(rec->name)); + igraph_Free(rec); + } + + igraph_vector_destroy(&edges); + igraph_vector_ptr_destroy(&eattrs); + igraph_trie_destroy(&eattrnames); + igraph_vector_ptr_destroy(&vattrs); + igraph_trie_destroy(&vattrnames); + igraph_pajek_yylex_destroy(context.scanner); + + IGRAPH_FINALLY_CLEAN(7); + return 0; +} + +/** + * \function igraph_read_graph_dimacs + * \brief Read a graph in DIMACS format. + * + * This function reads the DIMACS file format, more specifically the + * version for network flow problems, see the files at + * ftp://dimacs.rutgers.edu/pub/netflow/general-info/ + * + * + * This is a line-oriented text file (ASCII) format. The first + * character of each line defines the type of the line. If the first + * character is c the line is a comment line and it is + * ignored. There is one problem line (p in the file, it + * must appear before any node and arc descriptor lines. The problem + * line has three fields separated by spaces: the problem type + * (min, max or asn), the + * number of vertices and number of edges in the graph. + * Exactly two node identification lines are expected + * (n), one for the source, one for the target vertex. + * These have two fields: the id of the vertex and the type of the + * vertex, either s (=source) or t + * (=target). Arc lines start with a and have three + * fields: the source vertex, the target vertex and the edge capacity. + * + * + * Vertex ids are numbered from 1. + * \param graph Pointer to an uninitialized graph object. + * \param instream The file to read from. + * \param source Pointer to an integer, the id of the source node will + * be stored here. (The igraph vertex id, which is one less than + * the actual number in the file.) It is ignored if + * NULL. + * \param target Pointer to an integer, the (igraph) id of the target + * node will be stored here. It is ignored if NULL. + * \param capacity Pointer to an initialized vector, the capacity of + * the edges will be stored here if not NULL. + * \param directed Boolean, whether to create a directed graph. + * \return Error code. + * + * Time complexity: O(|V|+|E|+c), the number of vertices plus the + * number of edges, plus the size of the file in characters. + * + * \sa \ref igraph_write_graph_dimacs() + */ + +int igraph_read_graph_dimacs(igraph_t *graph, FILE *instream, + igraph_strvector_t *problem, + igraph_vector_t *label, + igraph_integer_t *source, + igraph_integer_t *target, + igraph_vector_t *capacity, + igraph_bool_t directed) { + + igraph_vector_t edges; + long int no_of_nodes = -1; + long int no_of_edges = -1; + long int tsource = -1; + long int ttarget = -1; + char prob[21]; + char c; + int problem_type = 0; + +#define PROBLEM_EDGE 1 +#define PROBLEM_MAX 2 + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + if (capacity) { + igraph_vector_clear(capacity); + } + + while (!feof(instream)) { + int read; + char str[3]; + + IGRAPH_ALLOW_INTERRUPTION(); + + read = fscanf(instream, "%2c", str); + if (feof(instream)) { + break; + } + if (read != 1) { + IGRAPH_ERROR("parsing dimacs file failed", IGRAPH_PARSEERROR); + } + switch (str[0]) { + long int tmp, tmp2; + long int from, to; + igraph_real_t cap; + + case 'c': + /* comment */ + break; + + case 'p': + if (no_of_nodes != -1) { + IGRAPH_ERROR("reading dimacs file failed, double 'p' line", + IGRAPH_PARSEERROR); + } + read = fscanf(instream, "%20s %li %li", prob, + &no_of_nodes, &no_of_edges); + if (read != 3) { + IGRAPH_ERROR("reading dimacs file failed", IGRAPH_PARSEERROR); + } + if (!strcmp(prob, "edge")) { + /* edge list */ + problem_type = PROBLEM_EDGE; + if (label) { + long int i; + IGRAPH_CHECK(igraph_vector_resize(label, no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*label)[i] = i + 1; + } + } + } else if (!strcmp(prob, "max")) { + /* maximum flow problem */ + problem_type = PROBLEM_MAX; + if (capacity) { + IGRAPH_CHECK(igraph_vector_reserve(capacity, no_of_edges)); + } + } else { + IGRAPH_ERROR("Unknown problem type, should be 'edge' or 'max'", + IGRAPH_PARSEERROR); + } + if (problem) { + igraph_strvector_clear(problem); + IGRAPH_CHECK(igraph_strvector_add(problem, prob)); + } + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges * 2)); + break; + + case 'n': + /* for MAX this is either the source or target vertex, + for EDGE this is a vertex label */ + if (problem_type == PROBLEM_MAX) { + str[0] = 'x'; + read = fscanf(instream, "%li %1s", &tmp, str); + if (str[0] == 's') { + if (tsource != -1) { + IGRAPH_ERROR("reading dimacsfile: multiple source vertex line", + IGRAPH_PARSEERROR); + } else { + tsource = tmp; + } + } else if (str[0] == 't') { + if (ttarget != -1) { + IGRAPH_ERROR("reading dimacsfile: multiple target vertex line", + IGRAPH_PARSEERROR); + } else { + ttarget = tmp; + } + } else { + IGRAPH_ERROR("invalid node descriptor line in dimacs file", + IGRAPH_PARSEERROR); + } + } else { + read = fscanf(instream, "%li %li", &tmp, &tmp2); + if (label) { + VECTOR(*label)[tmp] = tmp2; + } + } + + break; + + case 'a': + /* This is valid only for MAX, a weighted edge */ + if (problem_type != PROBLEM_MAX) { + IGRAPH_ERROR("'a' lines are allowed only in MAX problem files", + IGRAPH_PARSEERROR); + } + read = fscanf(instream, "%li %li %lf", &from, &to, &cap); + if (read != 3) { + IGRAPH_ERROR("reading dimacs file", IGRAPH_PARSEERROR); + } + IGRAPH_CHECK(igraph_vector_push_back(&edges, from - 1)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to - 1)); + if (capacity) { + IGRAPH_CHECK(igraph_vector_push_back(capacity, cap)); + } + break; + + case 'e': + /* Edge line, only in EDGE */ + if (problem_type != PROBLEM_EDGE) { + IGRAPH_ERROR("'e' lines are allowed only in EDGE problem files", + IGRAPH_PARSEERROR); + } + read = fscanf(instream, "%li %li", &from, &to); + if (read != 2) { + IGRAPH_ERROR("reading dimacs file", IGRAPH_PARSEERROR); + } + IGRAPH_CHECK(igraph_vector_push_back(&edges, from - 1)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to - 1)); + break; + + default: + IGRAPH_ERROR("unknown line type in dimacs file", IGRAPH_PARSEERROR); + } + + /* Go to next line */ + while (!feof(instream) && (c = (char) getc(instream)) != '\n') ; + } + + if (source) { + *source = (igraph_integer_t) tsource - 1; + } + if (target) { + *target = (igraph_integer_t) ttarget - 1; + } + + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) no_of_nodes, + directed)); + igraph_vector_destroy(&edges); + + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_read_graph_graphdb_getword(FILE *instream) { + int b1, b2; + unsigned char c1, c2; + b1 = fgetc(instream); + b2 = fgetc(instream); + if (b1 != EOF) { + c1 = (unsigned char) b1; c2 = (unsigned char) b2; + return c1 | (c2 << 8); + } else { + return -1; + } +} + +/** + * \function igraph_read_graph_graphdb + * \brief Read a graph in the binary graph database format. + * + * This is a binary format, used in the graph database + * for isomorphism testing. From the (now defunct) graph database + * homepage: + * + * + * \blockquote + * The graphs are stored in a compact binary format, one graph per + * file. The file is composed of 16 bit words, which are represented + * using the so-called little-endian convention, i.e. the least + * significant byte of the word is stored first. + * + * + * Then, for each node, the file contains the list of edges coming + * out of the node itself. The list is represented by a word encoding + * its length, followed by a word for each edge, representing the + * destination node of the edge. Node numeration is 0-based, so the + * first node of the graph has index 0. \endblockquote + * + * + * Only unlabelled graphs are implemented. + * \param graph Pointer to an uninitialized graph object. + * \param instream The stream to read from. + * \param directed Logical scalar, whether to create a directed graph. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the + * number of edges. + * + * \example examples/simple/igraph_read_graph_graphdb.c + */ + +int igraph_read_graph_graphdb(igraph_t *graph, FILE *instream, + igraph_bool_t directed) { + + igraph_vector_t edges; + long int nodes; + long int i, j; + igraph_bool_t end = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + nodes = igraph_i_read_graph_graphdb_getword(instream); + if (nodes < 0) { + IGRAPH_ERROR("Can't read from file", IGRAPH_EFILE); + } + for (i = 0; !end && i < nodes; i++) { + long int len = igraph_i_read_graph_graphdb_getword(instream); + if (len < 0) { + end = 1; + break; + } + for (j = 0; ! end && j < len; j++) { + long int to = igraph_i_read_graph_graphdb_getword(instream); + if (to < 0) { + end = 1; + break; + } + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to)); + } + } + + if (end) { + IGRAPH_ERROR("Truncated graphdb file", IGRAPH_EFILE); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) nodes, + directed)); + igraph_vector_destroy(&edges); + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +#include "foreign-gml-header.h" + +int igraph_gml_yylex_init_extra (igraph_i_gml_parsedata_t* user_defined, + void* scanner); +int igraph_gml_yylex_destroy (void *scanner ); +int igraph_gml_yyparse (igraph_i_gml_parsedata_t* context); +void igraph_gml_yyset_in (FILE * in_str, void* yyscanner ); + +static void igraph_i_gml_destroy_attrs(igraph_vector_ptr_t **ptr) { + long int i; + igraph_vector_ptr_t *vec; + for (i = 0; i < 3; i++) { + long int j; + vec = ptr[i]; + for (j = 0; j < igraph_vector_ptr_size(vec); j++) { + igraph_attribute_record_t *atrec = VECTOR(*vec)[j]; + if (atrec->type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *value = (igraph_vector_t*)atrec->value; + if (value != 0) { + igraph_vector_destroy(value); + igraph_Free(value); + } + } else { + igraph_strvector_t *value = (igraph_strvector_t*)atrec->value; + if (value != 0) { + igraph_strvector_destroy(value); + igraph_Free(value); + } + } + igraph_Free(atrec->name); + igraph_Free(atrec); + } + igraph_vector_ptr_destroy(vec); + } +} + +static igraph_real_t igraph_i_gml_toreal(igraph_gml_tree_t *node, long int pos) { + + igraph_real_t value = 0.0; + int type = igraph_gml_tree_type(node, pos); + + switch (type) { + case IGRAPH_I_GML_TREE_INTEGER: + value = igraph_gml_tree_get_integer(node, pos); + break; + case IGRAPH_I_GML_TREE_REAL: + value = igraph_gml_tree_get_real(node, pos); + break; + default: + IGRAPH_ERROR("Internal error while parsing GML file", IGRAPH_FAILURE); + break; + } + + return value; +} + +static const char *igraph_i_gml_tostring(igraph_gml_tree_t *node, long int pos) { + + int type = igraph_gml_tree_type(node, pos); + char tmp[256]; + const char *p = tmp; + long int i; + igraph_real_t d; + + switch (type) { + case IGRAPH_I_GML_TREE_INTEGER: + i = igraph_gml_tree_get_integer(node, pos); + snprintf(tmp, sizeof(tmp) / sizeof(char), "%li", i); + break; + case IGRAPH_I_GML_TREE_REAL: + d = igraph_gml_tree_get_real(node, pos); + igraph_real_snprintf_precise(tmp, sizeof(tmp) / sizeof(char), d); + break; + case IGRAPH_I_GML_TREE_STRING: + p = igraph_gml_tree_get_string(node, pos); + break; + default: + break; + } + + return p; +} + +/** + * \function igraph_read_graph_gml + * \brief Read a graph in GML format. + * + * GML is a simple textual format, see + * http://www.fim.uni-passau.de/en/fim/faculty/chairs/theoretische-informatik/projects.html for details. + * + * + * Although all syntactically correct GML can be parsed, + * we implement only a subset of this format, some attributes might be + * ignored. Here is a list of all the differences: + * \olist + * \oli Only node and edge attributes are + * used, and only if they have a simple type: integer, real or + * string. So if an attribute is an array or a record, then it is + * ignored. This is also true if only some values of the + * attribute are complex. + * \oli Top level attributes except for Version and the + * first graph attribute are completely ignored. + * \oli Graph attributes except for node and + * edge are completely ignored. + * \oli There is no maximum line length. + * \oli There is no maximum keyword length. + * \oli Character entities in strings are not interpreted. + * \oli We allow inf (infinity) and nan + * (not a number) as a real number. This is case insensitive, so + * nan, NaN and NAN are equal. + * \endolist + * + * Please contact us if you cannot live with these + * limitations of the GML parser. + * \param graph Pointer to an uninitialized graph object. + * \param instream The stream to read the GML file from. + * \return Error code. + * + * Time complexity: should be proportional to the length of the file. + * + * \sa \ref igraph_read_graph_graphml() for a more modern format, + * \ref igraph_write_graph_gml() for writing GML files. + * + * \example examples/simple/gml.c + */ + +int igraph_read_graph_gml(igraph_t *graph, FILE *instream) { + + long int i, p; + long int no_of_nodes = 0, no_of_edges = 0; + igraph_trie_t trie; + igraph_vector_t edges; + igraph_bool_t directed = IGRAPH_UNDIRECTED; + igraph_gml_tree_t *gtree; + long int gidx; + igraph_trie_t vattrnames; + igraph_trie_t eattrnames; + igraph_trie_t gattrnames; + igraph_vector_ptr_t gattrs = IGRAPH_VECTOR_PTR_NULL, + vattrs = IGRAPH_VECTOR_PTR_NULL, eattrs = IGRAPH_VECTOR_PTR_NULL; + igraph_vector_ptr_t *attrs[3]; + long int edgeptr = 0; + igraph_i_gml_parsedata_t context; + + attrs[0] = &gattrs; attrs[1] = &vattrs; attrs[2] = &eattrs; + + context.eof = 0; + context.tree = 0; + + igraph_gml_yylex_init_extra(&context, &context.scanner); + IGRAPH_FINALLY(igraph_gml_yylex_destroy, context.scanner); + + igraph_gml_yyset_in(instream, context.scanner); + + i = igraph_gml_yyparse(&context); + if (i != 0) { + if (context.errmsg[0] != 0) { + IGRAPH_ERROR(context.errmsg, IGRAPH_PARSEERROR); + } else { + IGRAPH_ERROR("Cannot read GML file", IGRAPH_PARSEERROR); + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + /* Check version, if present, integer and not '1' then ignored */ + i = igraph_gml_tree_find(context.tree, "Version", 0); + if (i >= 0 && + igraph_gml_tree_type(context.tree, i) == IGRAPH_I_GML_TREE_INTEGER && + igraph_gml_tree_get_integer(context.tree, i) != 1) { + igraph_gml_tree_destroy(context.tree); + IGRAPH_ERROR("Unknown GML version", IGRAPH_UNIMPLEMENTED); + /* RETURN HERE!!!! */ + } + + /* get the graph */ + gidx = igraph_gml_tree_find(context.tree, "graph", 0); + if (gidx == -1) { + IGRAPH_ERROR("No 'graph' object in GML file", IGRAPH_PARSEERROR); + } + if (igraph_gml_tree_type(context.tree, gidx) != + IGRAPH_I_GML_TREE_TREE) { + IGRAPH_ERROR("Invalid type for 'graph' object in GML file", IGRAPH_PARSEERROR); + } + gtree = igraph_gml_tree_get_tree(context.tree, gidx); + + IGRAPH_FINALLY(igraph_i_gml_destroy_attrs, &attrs); + igraph_vector_ptr_init(&gattrs, 0); + igraph_vector_ptr_init(&vattrs, 0); + igraph_vector_ptr_init(&eattrs, 0); + + IGRAPH_TRIE_INIT_FINALLY(&trie, 0); + IGRAPH_TRIE_INIT_FINALLY(&vattrnames, 0); + IGRAPH_TRIE_INIT_FINALLY(&eattrnames, 0); + IGRAPH_TRIE_INIT_FINALLY(&gattrnames, 0); + + /* Is is directed? */ + i = igraph_gml_tree_find(gtree, "directed", 0); + if (i >= 0 && igraph_gml_tree_type(gtree, i) == IGRAPH_I_GML_TREE_INTEGER) { + if (igraph_gml_tree_get_integer(gtree, i) == 1) { + directed = IGRAPH_DIRECTED; + } + } + + /* Now we go over all objects in the graph and collect the attribute names and + types. Plus we collect node ids. We also do some checks. */ + for (i = 0; i < igraph_gml_tree_length(gtree); i++) { + long int j; + char cname[100]; + const char *name = igraph_gml_tree_name(gtree, i); + if (!strcmp(name, "node")) { + igraph_gml_tree_t *node; + igraph_bool_t hasid; + no_of_nodes++; + if (igraph_gml_tree_type(gtree, i) != IGRAPH_I_GML_TREE_TREE) { + IGRAPH_ERROR("'node' is not a list", IGRAPH_PARSEERROR); + } + node = igraph_gml_tree_get_tree(gtree, i); + hasid = 0; + for (j = 0; j < igraph_gml_tree_length(node); j++) { + const char *name = igraph_gml_tree_name(node, j); + long int trieid, triesize = igraph_trie_size(&vattrnames); + IGRAPH_CHECK(igraph_trie_get(&vattrnames, name, &trieid)); + if (trieid == triesize) { + /* new attribute */ + igraph_attribute_record_t *atrec = igraph_Calloc(1, igraph_attribute_record_t); + int type = igraph_gml_tree_type(node, j); + if (!atrec) { + IGRAPH_ERROR("Cannot read GML file", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_ptr_push_back(&vattrs, atrec)); + atrec->name = strdup(name); + if (type == IGRAPH_I_GML_TREE_INTEGER || type == IGRAPH_I_GML_TREE_REAL) { + atrec->type = IGRAPH_ATTRIBUTE_NUMERIC; + } else { + atrec->type = IGRAPH_ATTRIBUTE_STRING; + } + } else { + /* already seen, should we update type? */ + igraph_attribute_record_t *atrec = VECTOR(vattrs)[trieid]; + int type1 = atrec->type; + int type2 = igraph_gml_tree_type(node, j); + if (type1 == IGRAPH_ATTRIBUTE_NUMERIC && type2 == IGRAPH_I_GML_TREE_STRING) { + atrec->type = IGRAPH_ATTRIBUTE_STRING; + } + } + /* check id */ + if (!hasid && !strcmp(name, "id")) { + long int id; + if (igraph_gml_tree_type(node, j) != IGRAPH_I_GML_TREE_INTEGER) { + IGRAPH_ERROR("Non-integer node id in GML file", IGRAPH_PARSEERROR); + } + id = igraph_gml_tree_get_integer(node, j); + snprintf(cname, sizeof(cname) / sizeof(char) -1, "%li", id); + IGRAPH_CHECK(igraph_trie_get(&trie, cname, &id)); + hasid = 1; + } + } + if (!hasid) { + IGRAPH_ERROR("Node without 'id' while parsing GML file", IGRAPH_PARSEERROR); + } + } else if (!strcmp(name, "edge")) { + igraph_gml_tree_t *edge; + igraph_bool_t has_source = 0, has_target = 0; + no_of_edges++; + if (igraph_gml_tree_type(gtree, i) != IGRAPH_I_GML_TREE_TREE) { + IGRAPH_ERROR("'edge' is not a list", IGRAPH_PARSEERROR); + } + edge = igraph_gml_tree_get_tree(gtree, i); + has_source = has_target = 0; + for (j = 0; j < igraph_gml_tree_length(edge); j++) { + const char *name = igraph_gml_tree_name(edge, j); + if (!strcmp(name, "source")) { + has_source = 1; + if (igraph_gml_tree_type(edge, j) != IGRAPH_I_GML_TREE_INTEGER) { + IGRAPH_ERROR("Non-integer 'source' for an edge in GML file", + IGRAPH_PARSEERROR); + } + } else if (!strcmp(name, "target")) { + has_target = 1; + if (igraph_gml_tree_type(edge, j) != IGRAPH_I_GML_TREE_INTEGER) { + IGRAPH_ERROR("Non-integer 'source' for an edge in GML file", + IGRAPH_PARSEERROR); + } + } else { + long int trieid, triesize = igraph_trie_size(&eattrnames); + IGRAPH_CHECK(igraph_trie_get(&eattrnames, name, &trieid)); + if (trieid == triesize) { + /* new attribute */ + igraph_attribute_record_t *atrec = igraph_Calloc(1, igraph_attribute_record_t); + int type = igraph_gml_tree_type(edge, j); + if (!atrec) { + IGRAPH_ERROR("Cannot read GML file", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_ptr_push_back(&eattrs, atrec)); + atrec->name = strdup(name); + if (type == IGRAPH_I_GML_TREE_INTEGER || type == IGRAPH_I_GML_TREE_REAL) { + atrec->type = IGRAPH_ATTRIBUTE_NUMERIC; + } else { + atrec->type = IGRAPH_ATTRIBUTE_STRING; + } + } else { + /* already seen, should we update type? */ + igraph_attribute_record_t *atrec = VECTOR(eattrs)[trieid]; + int type1 = atrec->type; + int type2 = igraph_gml_tree_type(edge, j); + if (type1 == IGRAPH_ATTRIBUTE_NUMERIC && type2 == IGRAPH_I_GML_TREE_STRING) { + atrec->type = IGRAPH_ATTRIBUTE_STRING; + } + } + } + } /* for */ + if (!has_source) { + IGRAPH_ERROR("No 'source' for edge in GML file", IGRAPH_PARSEERROR); + } + if (!has_target) { + IGRAPH_ERROR("No 'target' for edge in GML file", IGRAPH_PARSEERROR); + } + } else { + /* anything to do? Maybe add as graph attribute.... */ + } + } + + /* check vertex id uniqueness */ + if (igraph_trie_size(&trie) != no_of_nodes) { + IGRAPH_ERROR("Node 'id' not unique", IGRAPH_PARSEERROR); + } + + /* now we allocate the vectors and strvectors for the attributes */ + for (i = 0; i < igraph_vector_ptr_size(&vattrs); i++) { + igraph_attribute_record_t *atrec = VECTOR(vattrs)[i]; + int type = atrec->type; + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *p = igraph_Calloc(1, igraph_vector_t); + atrec->value = p; + IGRAPH_CHECK(igraph_vector_init(p, no_of_nodes)); + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *p = igraph_Calloc(1, igraph_strvector_t); + atrec->value = p; + IGRAPH_CHECK(igraph_strvector_init(p, no_of_nodes)); + } else { + IGRAPH_WARNING("A composite attribute ignored"); + } + } + + for (i = 0; i < igraph_vector_ptr_size(&eattrs); i++) { + igraph_attribute_record_t *atrec = VECTOR(eattrs)[i]; + int type = atrec->type; + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *p = igraph_Calloc(1, igraph_vector_t); + atrec->value = p; + IGRAPH_CHECK(igraph_vector_init(p, no_of_edges)); + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *p = igraph_Calloc(1, igraph_strvector_t); + atrec->value = p; + IGRAPH_CHECK(igraph_strvector_init(p, no_of_edges)); + } else { + IGRAPH_WARNING("A composite attribute ignored"); + } + } + + /* Ok, now the edges, attributes too */ + IGRAPH_CHECK(igraph_vector_resize(&edges, no_of_edges * 2)); + p = -1; + while ( (p = igraph_gml_tree_find(gtree, "edge", p + 1)) != -1) { + igraph_gml_tree_t *edge; + long int from, to, fromidx = 0, toidx = 0; + char name[100]; + long int j; + edge = igraph_gml_tree_get_tree(gtree, p); + for (j = 0; j < igraph_gml_tree_length(edge); j++) { + const char *n = igraph_gml_tree_name(edge, j); + if (!strcmp(n, "source")) { + fromidx = igraph_gml_tree_find(edge, "source", 0); + } else if (!strcmp(n, "target")) { + toidx = igraph_gml_tree_find(edge, "target", 0); + } else { + long int edgeid = edgeptr / 2; + long int trieidx; + igraph_attribute_record_t *atrec; + int type; + igraph_trie_get(&eattrnames, n, &trieidx); + atrec = VECTOR(eattrs)[trieidx]; + type = atrec->type; + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *v = (igraph_vector_t *)atrec->value; + VECTOR(*v)[edgeid] = igraph_i_gml_toreal(edge, j); + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *v = (igraph_strvector_t *)atrec->value; + const char *value = igraph_i_gml_tostring(edge, j); + IGRAPH_CHECK(igraph_strvector_set(v, edgeid, value)); + } + } + } + from = igraph_gml_tree_get_integer(edge, fromidx); + to = igraph_gml_tree_get_integer(edge, toidx); + snprintf(name, sizeof(name) / sizeof(char) -1, "%li", from); + IGRAPH_CHECK(igraph_trie_get(&trie, name, &from)); + snprintf(name, sizeof(name) / sizeof(char) -1, "%li", to); + IGRAPH_CHECK(igraph_trie_get(&trie, name, &to)); + if (igraph_trie_size(&trie) != no_of_nodes) { + IGRAPH_ERROR("Unknown node id found at an edge", IGRAPH_PARSEERROR); + } + VECTOR(edges)[edgeptr++] = from; + VECTOR(edges)[edgeptr++] = to; + } + + /* and add vertex attributes */ + for (i = 0; i < igraph_gml_tree_length(gtree); i++) { + const char *n; + char name[100]; + long int j, k; + n = igraph_gml_tree_name(gtree, i); + if (!strcmp(n, "node")) { + igraph_gml_tree_t *node = igraph_gml_tree_get_tree(gtree, i); + long int iidx = igraph_gml_tree_find(node, "id", 0); + long int id = igraph_gml_tree_get_integer(node, iidx); + snprintf(name, sizeof(name) / sizeof(char) -1, "%li", id); + igraph_trie_get(&trie, name, &id); + for (j = 0; j < igraph_gml_tree_length(node); j++) { + const char *aname = igraph_gml_tree_name(node, j); + igraph_attribute_record_t *atrec; + int type; + igraph_trie_get(&vattrnames, aname, &k); + atrec = VECTOR(vattrs)[k]; + type = atrec->type; + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_vector_t *v = (igraph_vector_t *)atrec->value; + VECTOR(*v)[id] = igraph_i_gml_toreal(node, j); + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + igraph_strvector_t *v = (igraph_strvector_t *)atrec->value; + const char *value = igraph_i_gml_tostring(node, j); + IGRAPH_CHECK(igraph_strvector_set(v, id, value)); + } + } + } + } + + igraph_gml_tree_destroy(context.tree); + + igraph_trie_destroy(&trie); + igraph_trie_destroy(&gattrnames); + igraph_trie_destroy(&vattrnames); + igraph_trie_destroy(&eattrnames); + IGRAPH_FINALLY_CLEAN(4); + + IGRAPH_CHECK(igraph_empty_attrs(graph, 0, directed, 0)); /* TODO */ + IGRAPH_CHECK(igraph_add_vertices(graph, (igraph_integer_t) no_of_nodes, + &vattrs)); + IGRAPH_CHECK(igraph_add_edges(graph, &edges, &eattrs)); + + igraph_i_gml_destroy_attrs(attrs); + igraph_vector_destroy(&edges); + igraph_gml_yylex_destroy(context.scanner); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \ingroup loadsave + * \function igraph_write_graph_edgelist + * \brief Writes the edge list of a graph to a file. + * + * + * One edge is written per line, separated by a single space. + * For directed graphs edges are written in from, to order. + * \param graph The graph object to write. + * \param outstream Pointer to a stream, it should be writable. + * \return Error code: + * \c IGRAPH_EFILE if there is an error writing the + * file. + * + * Time complexity: O(|E|), the + * number of edges in the graph. It is assumed that writing an + * integer to the file requires O(1) + * time. + */ + +int igraph_write_graph_edgelist(const igraph_t *graph, FILE *outstream) { + + igraph_eit_t it; + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_FROM), + &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + + while (!IGRAPH_EIT_END(it)) { + igraph_integer_t from, to; + int ret; + igraph_edge(graph, IGRAPH_EIT_GET(it), &from, &to); + ret = fprintf(outstream, "%li %li\n", + (long int) from, + (long int) to); + if (ret < 0) { + IGRAPH_ERROR("Write error", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \ingroup loadsave + * \function igraph_write_graph_ncol + * \brief Writes the graph to a file in .ncol format + * + * + * .ncol is a format used by LGL, see \ref + * igraph_read_graph_ncol() for details. + * + * + * Note that having multiple or loop edges in an + * .ncol file breaks the LGL software but + * \a igraph does not check for this condition. + * \param graph The graph to write. + * \param outstream The stream object to write to, it should be + * writable. + * \param names The name of the vertex attribute, if symbolic names + * are written to the file. If not, supply 0 here. + * \param weights The name of the edge attribute, if they are also + * written to the file. If you don't want weights, supply 0 + * here. + * \return Error code: + * \c IGRAPH_EFILE if there is an error writing the + * file. + * + * Time complexity: O(|E|), the + * number of edges. All file operations are expected to have time + * complexity O(1). + * + * \sa \ref igraph_read_graph_ncol(), \ref igraph_write_graph_lgl() + */ + +int igraph_write_graph_ncol(const igraph_t *graph, FILE *outstream, + const char *names, const char *weights) { + igraph_eit_t it; + igraph_attribute_type_t nametype, weighttype; + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_FROM), + &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + + /* Check if we have the names attribute */ + if (names && !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, + names)) { + names = 0; + IGRAPH_WARNING("names attribute does not exists"); + } + if (names) { + IGRAPH_CHECK(igraph_i_attribute_gettype(graph, &nametype, + IGRAPH_ATTRIBUTE_VERTEX, names)); + } + if (names && nametype != IGRAPH_ATTRIBUTE_NUMERIC && + nametype != IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_WARNING("ignoring names attribute, unknown attribute type"); + names = 0; + } + + /* Check the weights as well */ + if (weights && !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, + weights)) { + weights = 0; + IGRAPH_WARNING("weights attribute does not exists"); + } + if (weights) { + IGRAPH_CHECK(igraph_i_attribute_gettype(graph, &weighttype, + IGRAPH_ATTRIBUTE_EDGE, weights)); + } + if (weights && weighttype != IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_WARNING("ignoring weights attribute, unknown attribute type"); + weights = 0; + } + + if (names == 0 && weights == 0) { + /* No names, no weights */ + while (!IGRAPH_EIT_END(it)) { + igraph_integer_t from, to; + int ret; + igraph_edge(graph, IGRAPH_EIT_GET(it), &from, &to); + ret = fprintf(outstream, "%li %li\n", + (long int) from, + (long int) to); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + } else if (weights == 0) { + /* No weights, but use names */ + igraph_strvector_t nvec; + IGRAPH_CHECK(igraph_strvector_init(&nvec, igraph_vcount(graph))); + IGRAPH_FINALLY(igraph_strvector_destroy, &nvec); + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, names, + igraph_vss_all(), + &nvec)); + while (!IGRAPH_EIT_END(it)) { + igraph_integer_t edge = IGRAPH_EIT_GET(it); + igraph_integer_t from, to; + int ret = 0; + char *str1, *str2; + igraph_edge(graph, edge, &from, &to); + igraph_strvector_get(&nvec, from, &str1); + igraph_strvector_get(&nvec, to, &str2); + ret = fprintf(outstream, "%s %s\n", str1, str2); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_strvector_destroy(&nvec); + IGRAPH_FINALLY_CLEAN(1); + } else if (names == 0) { + /* No names but weights */ + igraph_vector_t wvec; + IGRAPH_VECTOR_INIT_FINALLY(&wvec, igraph_ecount(graph)); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, weights, + igraph_ess_all(IGRAPH_EDGEORDER_ID), + &wvec)); + while (!IGRAPH_EIT_END(it)) { + igraph_integer_t edge = IGRAPH_EIT_GET(it); + igraph_integer_t from, to; + int ret1, ret2, ret3; + igraph_edge(graph, edge, &from, &to); + ret1 = fprintf(outstream, "%li %li ", + (long int)from, (long int)to); + ret2 = igraph_real_fprintf_precise(outstream, VECTOR(wvec)[(long int)edge]); + ret3 = fputc('\n', outstream); + if (ret1 < 0 || ret2 < 0 || ret3 == EOF) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_vector_destroy(&wvec); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Both names and weights */ + igraph_strvector_t nvec; + igraph_vector_t wvec; + IGRAPH_VECTOR_INIT_FINALLY(&wvec, igraph_ecount(graph)); + IGRAPH_CHECK(igraph_strvector_init(&nvec, igraph_vcount(graph))); + IGRAPH_FINALLY(igraph_strvector_destroy, &nvec); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, weights, + igraph_ess_all(IGRAPH_EDGEORDER_ID), + &wvec)); + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, names, + igraph_vss_all(), + &nvec)); + while (!IGRAPH_EIT_END(it)) { + igraph_integer_t edge = IGRAPH_EIT_GET(it); + igraph_integer_t from, to; + int ret = 0, ret2 = 0; + char *str1, *str2; + igraph_edge(graph, edge, &from, &to); + igraph_strvector_get(&nvec, from, &str1); + igraph_strvector_get(&nvec, to, &str2); + ret = fprintf(outstream, "%s %s ", str1, str2); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = igraph_real_fprintf_precise(outstream, VECTOR(wvec)[(long int)edge]); + ret2 = fputc('\n', outstream); + if (ret < 0 || ret2 == EOF) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_strvector_destroy(&nvec); + igraph_vector_destroy(&wvec); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \ingroup loadsave + * \function igraph_write_graph_lgl + * \brief Writes the graph to a file in .lgl format + * + * + * .lgl is a format used by LGL, see \ref + * igraph_read_graph_lgl() for details. + * + * + * Note that having multiple or loop edges in an + * .lgl file breaks the LGL software but \a igraph + * does not check for this condition. + * \param graph The graph to write. + * \param outstream The stream object to write to, it should be + * writable. + * \param names The name of the vertex attribute, if symbolic names + * are written to the file. If not supply 0 here. + * \param weights The name of the edge attribute, if they are also + * written to the file. If you don't want weights supply 0 + * here. + * \param isolates Logical, if TRUE isolated vertices are also written + * to the file. If FALSE they will be omitted. + * \return Error code: + * \c IGRAPH_EFILE if there is an error + * writing the file. + * + * Time complexity: O(|E|), the + * number of edges if \p isolates is + * FALSE, O(|V|+|E|) otherwise. All + * file operations are expected to have time complexity + * O(1). + * + * \sa \ref igraph_read_graph_lgl(), \ref igraph_write_graph_ncol() + * + * \example examples/simple/igraph_write_graph_lgl.c + */ + +int igraph_write_graph_lgl(const igraph_t *graph, FILE *outstream, + const char *names, const char *weights, + igraph_bool_t isolates) { + igraph_eit_t it; + long int actvertex = -1; + igraph_attribute_type_t nametype, weighttype; + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_FROM), + &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + + /* Check if we have the names attribute */ + if (names && !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, + names)) { + names = 0; + IGRAPH_WARNING("names attribute does not exists"); + } + if (names) { + IGRAPH_CHECK(igraph_i_attribute_gettype(graph, &nametype, + IGRAPH_ATTRIBUTE_VERTEX, names)); + } + if (names && nametype != IGRAPH_ATTRIBUTE_NUMERIC && + nametype != IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_WARNING("ignoring names attribute, unknown attribute type"); + names = 0; + } + + /* Check the weights as well */ + if (weights && !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, + weights)) { + weights = 0; + IGRAPH_WARNING("weights attribute does not exists"); + } + if (weights) { + IGRAPH_CHECK(igraph_i_attribute_gettype(graph, &weighttype, + IGRAPH_ATTRIBUTE_EDGE, weights)); + } + if (weights && weighttype != IGRAPH_ATTRIBUTE_NUMERIC && + weighttype != IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_WARNING("ignoring weights attribute, unknown attribute type"); + weights = 0; + } + + if (names == 0 && weights == 0) { + /* No names, no weights */ + while (!IGRAPH_EIT_END(it)) { + igraph_integer_t from, to; + int ret; + igraph_edge(graph, IGRAPH_EIT_GET(it), &from, &to); + if (from == actvertex) { + ret = fprintf(outstream, "%li\n", (long int)to); + } else { + actvertex = from; + ret = fprintf(outstream, "# %li\n%li\n", (long int)from, (long int)to); + } + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + } else if (weights == 0) { + /* No weights but use names */ + igraph_strvector_t nvec; + IGRAPH_CHECK(igraph_strvector_init(&nvec, igraph_vcount(graph))); + IGRAPH_FINALLY(igraph_strvector_destroy, &nvec); + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, names, + igraph_vss_all(), + &nvec)); + while (!IGRAPH_EIT_END(it)) { + igraph_integer_t edge = IGRAPH_EIT_GET(it); + igraph_integer_t from, to; + int ret = 0; + char *str1, *str2; + igraph_edge(graph, edge, &from, &to); + igraph_strvector_get(&nvec, to, &str2); + + if (from == actvertex) { + ret = fprintf(outstream, "%s\n", str2); + } else { + actvertex = from; + igraph_strvector_get(&nvec, from, &str1); + ret = fprintf(outstream, "# %s\n%s\n", str1, str2); + } + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + IGRAPH_FINALLY_CLEAN(1); + } else if (names == 0) { + igraph_strvector_t wvec; + IGRAPH_CHECK(igraph_strvector_init(&wvec, igraph_ecount(graph))); + IGRAPH_FINALLY(igraph_strvector_destroy, &wvec); + IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr(graph, weights, + igraph_ess_all(IGRAPH_EDGEORDER_ID), + &wvec)); + /* No names but weights */ + while (!IGRAPH_EIT_END(it)) { + igraph_integer_t edge = IGRAPH_EIT_GET(it); + igraph_integer_t from, to; + int ret = 0; + char *str1; + igraph_edge(graph, edge, &from, &to); + igraph_strvector_get(&wvec, edge, &str1); + if (from == actvertex) { + ret = fprintf(outstream, "%li %s\n", (long)to, str1); + } else { + actvertex = from; + ret = fprintf(outstream, "# %li\n%li %s\n", (long)from, (long)to, str1); + } + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_strvector_destroy(&wvec); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Both names and weights */ + igraph_strvector_t nvec, wvec; + IGRAPH_CHECK(igraph_strvector_init(&wvec, igraph_ecount(graph))); + IGRAPH_FINALLY(igraph_strvector_destroy, &wvec); + IGRAPH_CHECK(igraph_strvector_init(&nvec, igraph_vcount(graph))); + IGRAPH_FINALLY(igraph_strvector_destroy, &nvec); + IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr(graph, weights, + igraph_ess_all(IGRAPH_EDGEORDER_ID), + &wvec)); + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, names, + igraph_vss_all(), + &nvec)); + while (!IGRAPH_EIT_END(it)) { + igraph_integer_t edge = IGRAPH_EIT_GET(it); + igraph_integer_t from, to; + int ret = 0; + char *str1, *str2, *str3; + igraph_edge(graph, edge, &from, &to); + igraph_strvector_get(&nvec, to, &str2); + igraph_strvector_get(&wvec, edge, &str3); + if (from == actvertex) { + ret = fprintf(outstream, "%s ", str2); + } else { + actvertex = from; + igraph_strvector_get(&nvec, from, &str1); + ret = fprintf(outstream, "# %s\n%s ", str1, str2); + } + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + ret = fprintf(outstream, "%s\n", str3); + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + igraph_strvector_destroy(&nvec); + igraph_strvector_destroy(&wvec); + IGRAPH_FINALLY_CLEAN(2); + } + + if (isolates) { + long int nov = igraph_vcount(graph); + long int i; + int ret = 0; + igraph_vector_t deg; + igraph_strvector_t nvec; + char *str; + + IGRAPH_VECTOR_INIT_FINALLY(°, 1); + IGRAPH_CHECK(igraph_strvector_init(&nvec, 1)); + IGRAPH_FINALLY(igraph_strvector_destroy, &nvec); + for (i = 0; i < nov; i++) { + igraph_degree(graph, °, igraph_vss_1((igraph_integer_t) i), + IGRAPH_ALL, IGRAPH_LOOPS); + if (VECTOR(deg)[0] == 0) { + if (names == 0) { + ret = fprintf(outstream, "# %li\n", i); + } else { + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, names, + igraph_vss_1((igraph_integer_t) i), &nvec)); + igraph_strvector_get(&nvec, 0, &str); + ret = fprintf(outstream, "# %s\n", str); + } + } + if (ret < 0) { + IGRAPH_ERROR("Write failed", IGRAPH_EFILE); + } + } + igraph_strvector_destroy(&nvec); + igraph_vector_destroy(°); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/* Order matters here! */ +#define V_ID 0 +#define V_X 1 +#define V_Y 2 +#define V_Z 3 +#define V_SHAPE 4 +#define V_XFACT 5 +#define V_YFACT 6 +#define V_COLOR_RED 7 +#define V_COLOR_GREEN 8 +#define V_COLOR_BLUE 9 +#define V_FRAMECOLOR_RED 10 +#define V_FRAMECOLOR_GREEN 11 +#define V_FRAMECOLOR_BLUE 12 +#define V_LABELCOLOR_RED 13 +#define V_LABELCOLOR_GREEN 14 +#define V_LABELCOLOR_BLUE 15 +#define V_LABELDIST 16 +#define V_LABELDEGREE2 17 +#define V_FRAMEWIDTH 18 +#define V_FONTSIZE 19 +#define V_ROTATION 20 +#define V_RADIUS 21 +#define V_DIAMONDRATIO 22 +#define V_LABELDEGREE 23 +#define V_VERTEXSIZE 24 +#define V_FONT 25 +#define V_URL 26 +#define V_COLOR 27 +#define V_FRAMECOLOR 28 +#define V_LABELCOLOR 29 +#define V_LAST 30 + +#define E_WEIGHT 0 +#define E_COLOR_RED 1 +#define E_COLOR_GREEN 2 +#define E_COLOR_BLUE 3 +#define E_ARROWSIZE 4 +#define E_EDGEWIDTH 5 +#define E_HOOK1 6 +#define E_HOOK2 7 +#define E_ANGLE1 8 +#define E_ANGLE2 9 +#define E_VELOCITY1 10 +#define E_VELOCITY2 11 +#define E_ARROWPOS 12 +#define E_LABELPOS 13 +#define E_LABELANGLE 14 +#define E_LABELANGLE2 15 +#define E_LABELDEGREE 16 +#define E_FONTSIZE 17 +#define E_ARROWTYPE 18 +#define E_LINEPATTERN 19 +#define E_LABEL 20 +#define E_LABELCOLOR 21 +#define E_COLOR 22 +#define E_LAST 23 + +static int igraph_i_pajek_escape(char* src, char** dest) { + long int destlen = 0; + igraph_bool_t need_escape = 0; + + /* Determine whether the string contains characters to be escaped */ + char *s, *d; + for (s = src; *s; s++, destlen++) { + if (*s == '\\') { + need_escape = 1; + destlen++; + } else if (*s == '"') { + need_escape = 1; + destlen++; + } else if (!isalnum(*s)) { + need_escape = 1; + } + } + + if (!need_escape) { + /* At this point, we know that the string does not contain any chars + * that would warrant escaping. Therefore, we simply quote it and + * return the quoted string. This is necessary because Pajek uses some + * reserved words in its format (like 'c' standing for color) and they + * have to be quoted as well. + */ + *dest = igraph_Calloc(destlen + 3, char); + if (!*dest) { + IGRAPH_ERROR("Not enough memory", IGRAPH_ENOMEM); + } + + d = *dest; + strcpy(d + 1, src); + d[0] = d[destlen + 1] = '"'; + d[destlen + 2] = 0; + return IGRAPH_SUCCESS; + } + + *dest = igraph_Calloc(destlen + 3, char); + if (!*dest) { + IGRAPH_ERROR("Not enough memory", IGRAPH_ENOMEM); + } + + d = *dest; + *d = '"'; d++; + + for (s = src; *s; s++, d++) { + switch (*s) { + case '\\': + case '"': + *d = '\\'; d++; + default: + *d = *s; + } + } + *d = '"'; d++; *d = 0; + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_write_graph_pajek + * \brief Writes a graph to a file in Pajek format. + * + * + * The Pajek vertex and edge parameters (like color) are determined by + * the attributes of the vertices and edges, of course this requires + * an attribute handler to be installed. The names of the + * corresponding vertex and edge attributes are listed at \ref + * igraph_read_graph_pajek(), eg. the `\c color' vertex attributes + * determines the color (`\c c' in Pajek) parameter. + * + * + * As of version 0.6.1 igraph writes bipartite graphs into Pajek files + * correctly, i.e. they will be also bipartite when read into Pajek. + * As Pajek is less flexible for bipartite graphs (the numeric ids of + * the vertices must be sorted according to vertex type), igraph might + * need to reorder the vertices when writing a bipartite Pajek file. + * This effectively means that numeric vertex ids usually change when + * a bipartite graph is written to a Pajek file, and then read back + * into igraph. + * \param graph The graph object to write. + * \param outstream The file to write to. It should be opened and + * writable. Make sure that you open the file in binary format if you use MS Windows, + * otherwise end of line characters will be messed up. (igraph will be able + * to read back these messed up files, but Pajek won't.) + * \return Error code. + * + * Time complexity: O(|V|+|E|+|A|), |V| is the number of vertices, |E| + * is the number of edges, |A| the number of attributes (vertex + + * edge) in the graph if there are attribute handlers installed. + * + * \sa \ref igraph_read_graph_pajek() for reading Pajek graphs, \ref + * igraph_write_graph_graphml() for writing a graph in GraphML format, + * this suites igraph graphs better. + * + * \example examples/simple/igraph_write_graph_pajek.c + */ + +int igraph_write_graph_pajek(const igraph_t *graph, FILE *outstream) { + long int no_of_nodes = igraph_vcount(graph); + long int i, j; + + igraph_attribute_type_t vtypes[V_LAST], etypes[E_LAST]; + igraph_bool_t write_vertex_attrs = 0; + + /* Same order as the #define's */ + const char *vnames[] = { "id", "x", "y", "z", "shape", "xfact", "yfact", + "", "", "", "", "", "", "", "", "", + "labeldist", "labeldegree2", "framewidth", + "fontsize", "rotation", "radius", + "diamondratio", "labeldegree", "vertexsize", + "font", "url", "color", "framecolor", + "labelcolor" + }; + + const char *vnumnames[] = { "xfact", "yfact", "labeldist", + "labeldegree2", "framewidth", "fontsize", + "rotation", "radius", "diamondratio", + "labeldegree", "vertexsize" + }; + const char *vnumnames2[] = { "x_fact", "y_fact", "lr", "lphi", "bw", + "fos", "phi", "r", "q", "la", "size" + }; + const char *vstrnames[] = { "font", "url", "color", "framecolor", + "labelcolor" + }; + const char *vstrnames2[] = { "font", "url", "ic", "bc", "lc" }; + + const char *enames[] = { "weight", "", "", "", + "arrowsize", "edgewidth", "hook1", "hook2", + "angle1", "angle2", "velocity1", "velocity2", + "arrowpos", "labelpos", "labelangle", + "labelangle2", "labeldegree", "fontsize", + "arrowtype", "linepattern", "label", "labelcolor", + "color" + }; + const char *enumnames[] = { "arrowsize", "edgewidth", "hook1", "hook2", + "angle1", "angle2", "velocity1", "velocity2", + "arrowpos", "labelpos", "labelangle", + "labelangle2", "labeldegree", "fontsize" + }; + const char *enumnames2[] = { "s", "w", "h1", "h2", "a1", "a2", "k1", "k2", + "ap", "lp", "lr", "lphi", "la", "fos" + }; + const char *estrnames[] = { "arrowtype", "linepattern", "label", + "labelcolor", "color" + }; + const char *estrnames2[] = { "a", "p", "l", "lc", "c" }; + + const char *newline = "\x0d\x0a"; + + igraph_es_t es; + igraph_eit_t eit; + + igraph_vector_t numv; + igraph_strvector_t strv; + + igraph_vector_t ex_numa; + igraph_vector_t ex_stra; + igraph_vector_t vx_numa; + igraph_vector_t vx_stra; + + char *s, *escaped; + + igraph_bool_t bipartite = 0; + igraph_vector_int_t bip_index, bip_index2; + igraph_vector_bool_t bvec; + long int notop = 0, nobottom = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&numv, 1); + IGRAPH_STRVECTOR_INIT_FINALLY(&strv, 1); + + IGRAPH_VECTOR_INIT_FINALLY(&ex_numa, 0); + IGRAPH_VECTOR_INIT_FINALLY(&ex_stra, 0); + IGRAPH_VECTOR_INIT_FINALLY(&vx_numa, 0); + IGRAPH_VECTOR_INIT_FINALLY(&vx_stra, 0); + + /* Check if graph is bipartite */ + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, "type")) { + igraph_attribute_type_t type_type; + igraph_i_attribute_gettype(graph, &type_type, IGRAPH_ATTRIBUTE_VERTEX, + "type"); + if (type_type == IGRAPH_ATTRIBUTE_BOOLEAN) { + int bptr = 0, tptr = 0; + bipartite = 1; write_vertex_attrs = 1; + /* Count top and bottom vertices, we go over them twice, + because we want to keep their original order */ + IGRAPH_CHECK(igraph_vector_int_init(&bip_index, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &bip_index); + IGRAPH_CHECK(igraph_vector_int_init(&bip_index2, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &bip_index2); + IGRAPH_CHECK(igraph_vector_bool_init(&bvec, 1)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &bvec); + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr(graph, + "type", igraph_vss_1((igraph_integer_t) i), &bvec)); + if (VECTOR(bvec)[0]) { + notop++; + } else { + nobottom++; + } + } + for (i = 0, bptr = 0, tptr = (int) nobottom; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr(graph, + "type", igraph_vss_1((igraph_integer_t) i), &bvec)); + if (VECTOR(bvec)[0]) { + VECTOR(bip_index)[tptr] = (int) i; + VECTOR(bip_index2)[i] = tptr; + tptr++; + } else { + VECTOR(bip_index)[bptr] = (int) i; + VECTOR(bip_index2)[i] = bptr; + bptr++; + } + } + igraph_vector_bool_destroy(&bvec); + IGRAPH_FINALLY_CLEAN(1); + } + } + + /* Write header */ + if (bipartite) { + if (fprintf(outstream, "*Vertices %li %li%s", no_of_nodes, nobottom, + newline) < 0) { + IGRAPH_ERROR("Cannot write pajek file", IGRAPH_EFILE); + } + } else { + if (fprintf(outstream, "*Vertices %li%s", no_of_nodes, newline) < 0) { + IGRAPH_ERROR("Cannot write pajek file", IGRAPH_EFILE); + } + } + + /* Check the vertex attributes */ + memset(vtypes, 0, sizeof(vtypes[0])*V_LAST); + for (i = 0; i < V_LAST; i++) { + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, + vnames[i])) { + igraph_i_attribute_gettype(graph, &vtypes[i], IGRAPH_ATTRIBUTE_VERTEX, + vnames[i]); + write_vertex_attrs = 1; + } else { + vtypes[i] = (igraph_attribute_type_t) -1; + } + } + for (i = 0; i < (long int) (sizeof(vnumnames) / sizeof(const char*)); i++) { + igraph_attribute_type_t type; + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, + vnumnames[i])) { + igraph_i_attribute_gettype(graph, &type, IGRAPH_ATTRIBUTE_VERTEX, + vnumnames[i]); + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_vector_push_back(&vx_numa, i)); + } + } + } + for (i = 0; i < (long int) (sizeof(vstrnames) / sizeof(const char*)); i++) { + igraph_attribute_type_t type; + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, + vstrnames[i])) { + igraph_i_attribute_gettype(graph, &type, IGRAPH_ATTRIBUTE_VERTEX, + vstrnames[i]); + if (type == IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_CHECK(igraph_vector_push_back(&vx_stra, i)); + } + } + } + + /* Write vertices */ + if (write_vertex_attrs) { + for (i = 0; i < no_of_nodes; i++) { + long int id = bipartite ? VECTOR(bip_index)[i] : i; + + /* vertex id */ + fprintf(outstream, "%li", i + 1); + if (vtypes[V_ID] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_i_attribute_get_numeric_vertex_attr(graph, vnames[V_ID], + igraph_vss_1((igraph_integer_t) id), &numv); + fputs(" \"", outstream); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + fputc('"', outstream); + } else if (vtypes[V_ID] == IGRAPH_ATTRIBUTE_STRING) { + igraph_i_attribute_get_string_vertex_attr(graph, vnames[V_ID], + igraph_vss_1((igraph_integer_t) id), &strv); + igraph_strvector_get(&strv, 0, &s); + IGRAPH_CHECK(igraph_i_pajek_escape(s, &escaped)); + fprintf(outstream, " %s", escaped); + igraph_Free(escaped); + } else { + fprintf(outstream, " \"%li\"", id + 1); + } + + /* coordinates */ + if (vtypes[V_X] == IGRAPH_ATTRIBUTE_NUMERIC && + vtypes[V_Y] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_i_attribute_get_numeric_vertex_attr(graph, vnames[V_X], + igraph_vss_1((igraph_integer_t) id), &numv); + fputc(' ', outstream); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + igraph_i_attribute_get_numeric_vertex_attr(graph, vnames[V_Y], + igraph_vss_1((igraph_integer_t) id), &numv); + fputc(' ', outstream); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + if (vtypes[V_Z] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_i_attribute_get_numeric_vertex_attr(graph, vnames[V_Z], + igraph_vss_1((igraph_integer_t) id), &numv); + fputc(' ', outstream); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + } + } + + /* shape */ + if (vtypes[V_SHAPE] == IGRAPH_ATTRIBUTE_STRING) { + igraph_i_attribute_get_string_vertex_attr(graph, vnames[V_SHAPE], + igraph_vss_1((igraph_integer_t) id), &strv); + igraph_strvector_get(&strv, 0, &s); + IGRAPH_CHECK(igraph_i_pajek_escape(s, &escaped)); + fprintf(outstream, " %s", escaped); + igraph_Free(escaped); + } + + /* numeric parameters */ + for (j = 0; j < igraph_vector_size(&vx_numa); j++) { + int idx = (int) VECTOR(vx_numa)[j]; + igraph_i_attribute_get_numeric_vertex_attr(graph, vnumnames[idx], + igraph_vss_1((igraph_integer_t) id), &numv); + fprintf(outstream, " %s ", vnumnames2[idx]); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + } + + /* string parameters */ + for (j = 0; j < igraph_vector_size(&vx_stra); j++) { + int idx = (int) VECTOR(vx_stra)[j]; + igraph_i_attribute_get_string_vertex_attr(graph, vstrnames[idx], + igraph_vss_1((igraph_integer_t) id), &strv); + igraph_strvector_get(&strv, 0, &s); + IGRAPH_CHECK(igraph_i_pajek_escape(s, &escaped)); + fprintf(outstream, " %s %s", vstrnames2[idx], escaped); + igraph_Free(escaped); + } + + /* trailing newline */ + fprintf(outstream, "%s", newline); + } + } + + /* edges header */ + if (igraph_is_directed(graph)) { + fprintf(outstream, "*Arcs%s", newline); + } else { + fprintf(outstream, "*Edges%s", newline); + } + + IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_ID)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + /* Check edge attributes */ + for (i = 0; i < E_LAST; i++) { + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, + enames[i])) { + igraph_i_attribute_gettype(graph, &etypes[i], IGRAPH_ATTRIBUTE_EDGE, + enames[i]); + } else { + etypes[i] = (igraph_attribute_type_t) -1; + } + } + for (i = 0; i < (long int) (sizeof(enumnames) / sizeof(const char*)); i++) { + igraph_attribute_type_t type; + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, + enumnames[i])) { + igraph_i_attribute_gettype(graph, &type, IGRAPH_ATTRIBUTE_EDGE, + enumnames[i]); + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_vector_push_back(&ex_numa, i)); + } + } + } + for (i = 0; i < (long int) (sizeof(estrnames) / sizeof(const char*)); i++) { + igraph_attribute_type_t type; + if (igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, + estrnames[i])) { + igraph_i_attribute_gettype(graph, &type, IGRAPH_ATTRIBUTE_EDGE, + estrnames[i]); + if (type == IGRAPH_ATTRIBUTE_STRING) { + IGRAPH_CHECK(igraph_vector_push_back(&ex_stra, i)); + } + } + } + + for (i = 0; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit), i++) { + long int edge = IGRAPH_EIT_GET(eit); + igraph_integer_t from, to; + igraph_edge(graph, (igraph_integer_t) edge, &from, &to); + if (bipartite) { + from = VECTOR(bip_index2)[from]; + to = VECTOR(bip_index2)[to]; + } + fprintf(outstream, "%li %li", (long int) from + 1, (long int) to + 1); + + /* Weights */ + if (etypes[E_WEIGHT] == IGRAPH_ATTRIBUTE_NUMERIC) { + igraph_i_attribute_get_numeric_edge_attr(graph, enames[E_WEIGHT], + igraph_ess_1((igraph_integer_t) edge), &numv); + fputc(' ', outstream); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + } + + /* numeric parameters */ + for (j = 0; j < igraph_vector_size(&ex_numa); j++) { + int idx = (int) VECTOR(ex_numa)[j]; + igraph_i_attribute_get_numeric_edge_attr(graph, enumnames[idx], + igraph_ess_1((igraph_integer_t) edge), &numv); + fprintf(outstream, " %s ", enumnames2[idx]); + igraph_real_fprintf_precise(outstream, VECTOR(numv)[0]); + } + + /* string parameters */ + for (j = 0; j < igraph_vector_size(&ex_stra); j++) { + int idx = (int) VECTOR(ex_stra)[j]; + igraph_i_attribute_get_string_edge_attr(graph, estrnames[idx], + igraph_ess_1((igraph_integer_t) edge), &strv); + igraph_strvector_get(&strv, 0, &s); + IGRAPH_CHECK(igraph_i_pajek_escape(s, &escaped)); + fprintf(outstream, " %s %s", estrnames2[idx], escaped); + igraph_Free(escaped); + } + + /* trailing newline */ + fprintf(outstream, "%s", newline); + } + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(2); + + if (bipartite) { + igraph_vector_int_destroy(&bip_index2); + igraph_vector_int_destroy(&bip_index); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_vector_destroy(&ex_numa); + igraph_vector_destroy(&ex_stra); + igraph_vector_destroy(&vx_numa); + igraph_vector_destroy(&vx_stra); + igraph_strvector_destroy(&strv); + igraph_vector_destroy(&numv); + IGRAPH_FINALLY_CLEAN(6); + return 0; +} + +/** + * \function igraph_write_graph_dimacs + * \brief Write a graph in DIMACS format. + * + * This function writes a graph to an output stream in DIMACS format, + * describing a maximum flow problem. + * See ftp://dimacs.rutgers.edu/pub/netflow/general-info/ + * + * + * This file format is discussed in the documentation of \ref + * igraph_read_graph_dimacs(), see that for more information. + * + * \param graph The graph to write to the stream. + * \param outstream The stream. + * \param source Integer, the id of the source vertex for the maximum + * flow. + * \param target Integer, the id of the target vertex. + * \param capacity Pointer to an initialized vector containing the + * edge capacity values. + * \return Error code. + * + * Time complexity: O(|E|), the number of edges in the graph. + * + * \sa igraph_read_graph_dimacs() + */ + +int igraph_write_graph_dimacs(const igraph_t *graph, FILE *outstream, + long int source, long int target, + const igraph_vector_t *capacity) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_eit_t it; + long int i = 0; + int ret, ret1, ret2, ret3; + + if (igraph_vector_size(capacity) != no_of_edges) { + IGRAPH_ERROR("invalid capacity vector length", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), + &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + + ret = fprintf(outstream, + "c created by igraph\np max %li %li\nn %li s\nn %li t\n", + no_of_nodes, no_of_edges, source + 1, target + 1); + if (ret < 0) { + IGRAPH_ERROR("Write error", IGRAPH_EFILE); + } + + + while (!IGRAPH_EIT_END(it)) { + igraph_integer_t from, to; + igraph_real_t cap; + igraph_edge(graph, IGRAPH_EIT_GET(it), &from, &to); + cap = VECTOR(*capacity)[i++]; + ret1 = fprintf(outstream, "a %li %li ", + (long int) from + 1, (long int) to + 1); + ret2 = igraph_real_fprintf_precise(outstream, cap); + ret3 = fputc('\n', outstream); + if (ret1 < 0 || ret2 < 0 || ret3 == EOF) { + IGRAPH_ERROR("Write error", IGRAPH_EFILE); + } + IGRAPH_EIT_NEXT(it); + } + + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +static int igraph_i_gml_convert_to_key(const char *orig, char **key) { + int no = 1; + char strno[50]; + size_t i, len = strlen(orig), newlen = 0, plen = 0; + + /* do we need a prefix? */ + if (len == 0 || !isalpha(orig[0])) { + no++; + snprintf(strno, sizeof(strno) - 1, "igraph"); + plen = newlen = strlen(strno); + } + for (i = 0; i < len; i++) { + if (isalnum(orig[i])) { + newlen++; + } + } + *key = igraph_Calloc(newlen + 1, char); + if (! *key) { + IGRAPH_ERROR("Writing GML file failed", IGRAPH_ENOMEM); + } + memcpy(*key, strno, plen * sizeof(char)); + for (i = 0; i < len; i++) { + if (isalnum(orig[i])) { + (*key)[plen++] = orig[i]; + } + } + (*key)[newlen] = '\0'; + + return 0; +} + +#define CHECK(cmd) do { ret=cmd; if (ret<0) IGRAPH_ERROR("Write failed", IGRAPH_EFILE); } while (0) + +/** + * \function igraph_write_graph_gml + * \brief Write the graph to a stream in GML format + * + * GML is a quite general textual format, see + * http://www.fim.uni-passau.de/en/fim/faculty/chairs/theoretische-informatik/projects.html for details. + * + * The graph, vertex and edges attributes are written to the + * file as well, if they are numeric or string. + * + * As igraph is more forgiving about attribute names, it might + * be necessary to simplify the them before writing to the GML file. + * This way we'll have a syntactically correct GML file. The following + * simple procedure is performed on each attribute name: first the alphanumeric + * characters are extracted, the others are ignored. Then if the first character + * is not a letter then the attribute name is prefixed with igraph. + * Note that this might result identical names for two attributes, igraph + * does not check this. + * + * The id vertex attribute is treated specially. + * If the id argument is not 0 then it should be a numeric + * vector with the vertex ids and the id vertex attribute is + * ignored (if there is one). If id is 0 and there is a + * numeric id vertex attribute that is used instead. If ids + * are not specified in either way then the regular igraph vertex ids are used. + * + * Note that whichever way vertex ids are specified, their + * uniqueness is not checked. + * + * If the graph has edge attributes named source + * or target they're silently ignored. GML uses these attributes + * to specify the edges, so we cannot write them to the file. Rename them + * before calling this function if you want to preserve them. + * \param graph The graph to write to the stream. + * \param outstream The stream to write the file to. + * \param id Either NULL or a numeric vector with the vertex ids. + * See details above. + * \param creator An optional string to write to the stream in the creator line. + * If this is 0 then the current date and time is added. + * \return Error code. + * + * Time complexity: should be proportional to the number of characters written + * to the file. + * + * \sa \ref igraph_read_graph_gml() for reading GML files, + * \ref igraph_read_graph_graphml() for a more modern format. + * + * \example examples/simple/gml.c + */ + +int igraph_write_graph_gml(const igraph_t *graph, FILE *outstream, + const igraph_vector_t *id, const char *creator) { + int ret; + igraph_strvector_t gnames, vnames, enames; + igraph_vector_t gtypes, vtypes, etypes; + igraph_vector_t numv; + igraph_strvector_t strv; + igraph_vector_bool_t boolv; + long int i; + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + + igraph_vector_t v_myid; + const igraph_vector_t *myid = id; + + time_t curtime = time(0); + char *timestr = ctime(&curtime); + timestr[strlen(timestr) - 1] = '\0'; /* nicely remove \n */ + + CHECK(fprintf(outstream, + "Creator \"igraph version %s %s\"\nVersion 1\ngraph\n[\n", + PACKAGE_VERSION, creator ? creator : timestr)); + + IGRAPH_STRVECTOR_INIT_FINALLY(&gnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&vnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&enames, 0); + IGRAPH_VECTOR_INIT_FINALLY(>ypes, 0); + IGRAPH_VECTOR_INIT_FINALLY(&vtypes, 0); + IGRAPH_VECTOR_INIT_FINALLY(&etypes, 0); + IGRAPH_CHECK(igraph_i_attribute_get_info(graph, + &gnames, >ypes, + &vnames, &vtypes, + &enames, &etypes)); + + IGRAPH_VECTOR_INIT_FINALLY(&numv, 1); + IGRAPH_STRVECTOR_INIT_FINALLY(&strv, 1); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&boolv, 1); + + /* Check whether there is an 'id' node attribute if the supplied is 0 */ + if (!id) { + igraph_bool_t found = 0; + for (i = 0; i < igraph_vector_size(&vtypes); i++) { + char *n; + igraph_strvector_get(&vnames, i, &n); + if (!strcmp(n, "id") && VECTOR(vtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + found = 1; break; + } + } + if (found) { + IGRAPH_VECTOR_INIT_FINALLY(&v_myid, no_of_nodes); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, "id", + igraph_vss_all(), + &v_myid)); + myid = &v_myid; + } + } + + /* directedness */ + CHECK(fprintf(outstream, " directed %i\n", igraph_is_directed(graph) ? 1 : 0)); + + /* Graph attributes first */ + for (i = 0; i < igraph_vector_size(>ypes); i++) { + char *name, *newname; + igraph_strvector_get(&gnames, i, &name); + IGRAPH_CHECK(igraph_i_gml_convert_to_key(name, &newname)); + if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_graph_attr(graph, name, &numv)); + CHECK(fprintf(outstream, " %s ", newname)); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(numv)[0])); + CHECK(fputc('\n', outstream)); + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + char *s; + IGRAPH_CHECK(igraph_i_attribute_get_string_graph_attr(graph, name, &strv)); + igraph_strvector_get(&strv, 0, &s); + CHECK(fprintf(outstream, " %s \"%s\"\n", newname, s)); + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_graph_attr(graph, name, &boolv)); + CHECK(fprintf(outstream, " %s %d\n", newname, VECTOR(boolv)[0] ? 1 : 0)); + IGRAPH_WARNING("A boolean graph attribute was converted to numeric"); + } else { + IGRAPH_WARNING("A non-numeric, non-string, non-boolean graph attribute ignored"); + } + igraph_Free(newname); + } + + /* Now come the vertices */ + for (i = 0; i < no_of_nodes; i++) { + long int j; + CHECK(fprintf(outstream, " node\n [\n")); + /* id */ + CHECK(fprintf(outstream, " id %li\n", myid ? (long int)VECTOR(*myid)[i] : i)); + /* other attributes */ + for (j = 0; j < igraph_vector_size(&vtypes); j++) { + int type = (int) VECTOR(vtypes)[j]; + char *name, *newname; + igraph_strvector_get(&vnames, j, &name); + if (!strcmp(name, "id")) { + continue; + } + IGRAPH_CHECK(igraph_i_gml_convert_to_key(name, &newname)); + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, name, + igraph_vss_1((igraph_integer_t) i), &numv)); + CHECK(fprintf(outstream, " %s ", newname)); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(numv)[0])); + CHECK(fputc('\n', outstream)); + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + char *s; + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, name, + igraph_vss_1((igraph_integer_t) i), &strv)); + igraph_strvector_get(&strv, 0, &s); + CHECK(fprintf(outstream, " %s \"%s\"\n", newname, s)); + } else if (type == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr(graph, name, + igraph_vss_1((igraph_integer_t) i), &boolv)); + CHECK(fprintf(outstream, " %s %d\n", newname, VECTOR(boolv)[0] ? 1 : 0)); + IGRAPH_WARNING("A boolean vertex attribute was converted to numeric"); + } else { + IGRAPH_WARNING("A non-numeric, non-string, non-boolean edge attribute was ignored"); + } + igraph_Free(newname); + } + CHECK(fprintf(outstream, " ]\n")); + } + + /* The edges too */ + for (i = 0; i < no_of_edges; i++) { + long int from = IGRAPH_FROM(graph, i); + long int to = IGRAPH_TO(graph, i); + long int j; + CHECK(fprintf(outstream, " edge\n [\n")); + /* source and target */ + CHECK(fprintf(outstream, " source %li\n", + myid ? (long int)VECTOR(*myid)[from] : from)); + CHECK(fprintf(outstream, " target %li\n", + myid ? (long int)VECTOR(*myid)[to] : to)); + + /* other attributes */ + for (j = 0; j < igraph_vector_size(&etypes); j++) { + int type = (int) VECTOR(etypes)[j]; + char *name, *newname; + igraph_strvector_get(&enames, j, &name); + if (!strcmp(name, "source") || !strcmp(name, "target")) { + continue; + } + IGRAPH_CHECK(igraph_i_gml_convert_to_key(name, &newname)); + if (type == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, name, + igraph_ess_1((igraph_integer_t) i), &numv)); + CHECK(fprintf(outstream, " %s ", newname)); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(numv)[0])); + CHECK(fputc('\n', outstream)); + } else if (type == IGRAPH_ATTRIBUTE_STRING) { + char *s; + IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr(graph, name, + igraph_ess_1((igraph_integer_t) i), &strv)); + igraph_strvector_get(&strv, 0, &s); + CHECK(fprintf(outstream, " %s \"%s\"\n", newname, s)); + } else if (type == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_edge_attr(graph, name, + igraph_ess_1((igraph_integer_t) i), &boolv)); + CHECK(fprintf(outstream, " %s %d\n", newname, VECTOR(boolv)[0] ? 1 : 0)); + IGRAPH_WARNING("A boolean edge attribute was converted to numeric"); + } else { + IGRAPH_WARNING("A non-numeric, non-string, non-boolean edge attribute was ignored"); + } + igraph_Free(newname); + } + CHECK(fprintf(outstream, " ]\n")); + } + + CHECK(fprintf(outstream, "]\n")); + + if (&v_myid == myid) { + igraph_vector_destroy(&v_myid); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_bool_destroy(&boolv); + igraph_strvector_destroy(&strv); + igraph_vector_destroy(&numv); + igraph_vector_destroy(&etypes); + igraph_vector_destroy(&vtypes); + igraph_vector_destroy(>ypes); + igraph_strvector_destroy(&enames); + igraph_strvector_destroy(&vnames); + igraph_strvector_destroy(&gnames); + IGRAPH_FINALLY_CLEAN(9); + + return 0; +} + +static int igraph_i_dot_escape(const char *orig, char **result) { + /* do we have to escape the string at all? */ + long int i, j, len = (long int) strlen(orig), newlen = 0; + igraph_bool_t need_quote = 0, is_number = 1; + + /* first, check whether the string is equal to some reserved word */ + if (!strcasecmp(orig, "graph") || !strcasecmp(orig, "digraph") || + !strcasecmp(orig, "node") || !strcasecmp(orig, "edge") || + !strcasecmp(orig, "strict") || !strcasecmp(orig, "subgraph")) { + need_quote = 1; + is_number = 0; + } + + /* next, check whether we need to escape the string for any other reason. + * Also update is_number and newlen */ + for (i = 0; i < len; i++) { + if (isdigit(orig[i])) { + newlen++; + } else if (orig[i] == '-' && i == 0) { + newlen++; + } else if (orig[i] == '.') { + if (is_number) { + newlen++; + } else { + need_quote = 1; + newlen++; + } + } else if (orig[i] == '_') { + is_number = 0; newlen++; + } else if (orig[i] == '\\' || orig[i] == '"' || orig[i] == '\n') { + need_quote = 1; is_number = 0; newlen += 2; /* will be escaped */ + } else if (isalpha(orig[i])) { + is_number = 0; newlen++; + } else { + is_number = 0; need_quote = 1; newlen++; + } + } + if (is_number && orig[len - 1] == '.') { + is_number = 0; + } + if (!is_number && isdigit(orig[0])) { + need_quote = 1; + } + + if (is_number || !need_quote) { + *result = strdup(orig); + if (!*result) { + IGRAPH_ERROR("Writing DOT file failed", IGRAPH_ENOMEM); + } + } else { + *result = igraph_Calloc(newlen + 3, char); + (*result)[0] = '"'; + (*result)[newlen + 1] = '"'; + (*result)[newlen + 2] = '\0'; + for (i = 0, j = 1; i < len; i++) { + if (orig[i] == '\n') { + (*result)[j++] = '\\'; + (*result)[j++] = 'n'; + continue; + } + if (orig[i] == '\\' || orig[i] == '"') { + (*result)[j++] = '\\'; + } + (*result)[j++] = orig[i]; + } + } + + return 0; +} + +/** + * \function igraph_write_graph_dot + * \brief Write the graph to a stream in DOT format + * + * DOT is the format used by the widely known GraphViz software, see + * http://www.graphviz.org for details. The grammar of the DOT format + * can be found here: http://www.graphviz.org/doc/info/lang.html + * + * This is only a preliminary implementation, only the vertices + * and the edges are written but not the attributes or any visualization + * information. + * + * \param graph The graph to write to the stream. + * \param outstream The stream to write the file to. + * + * Time complexity: should be proportional to the number of characters written + * to the file. + * + * \sa \ref igraph_write_graph_graphml() for a more modern format. + * + * \example examples/simple/dot.c + */ +int igraph_write_graph_dot(const igraph_t *graph, FILE* outstream) { + int ret; + long int i, j; + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + char edgeop[3]; + igraph_strvector_t gnames, vnames, enames; + igraph_vector_t gtypes, vtypes, etypes; + igraph_vector_t numv; + igraph_strvector_t strv; + igraph_vector_bool_t boolv; + + IGRAPH_STRVECTOR_INIT_FINALLY(&gnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&vnames, 0); + IGRAPH_STRVECTOR_INIT_FINALLY(&enames, 0); + IGRAPH_VECTOR_INIT_FINALLY(>ypes, 0); + IGRAPH_VECTOR_INIT_FINALLY(&vtypes, 0); + IGRAPH_VECTOR_INIT_FINALLY(&etypes, 0); + IGRAPH_CHECK(igraph_i_attribute_get_info(graph, + &gnames, >ypes, + &vnames, &vtypes, + &enames, &etypes)); + + IGRAPH_VECTOR_INIT_FINALLY(&numv, 1); + IGRAPH_STRVECTOR_INIT_FINALLY(&strv, 1); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&boolv, 1); + + CHECK(fprintf(outstream, "/* Created by igraph %s */\n", + PACKAGE_VERSION)); + + if (igraph_is_directed(graph)) { + CHECK(fprintf(outstream, "digraph {\n")); + strcpy(edgeop, "->"); + } else { + CHECK(fprintf(outstream, "graph {\n")); + strcpy(edgeop, "--"); + } + + /* Write the graph attributes */ + if (igraph_vector_size(>ypes) > 0) { + CHECK(fprintf(outstream, " graph [\n")); + for (i = 0; i < igraph_vector_size(>ypes); i++) { + char *name, *newname; + igraph_strvector_get(&gnames, i, &name); + IGRAPH_CHECK(igraph_i_dot_escape(name, &newname)); + if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_graph_attr(graph, name, &numv)); + if (VECTOR(numv)[0] == (long)VECTOR(numv)[0]) { + CHECK(fprintf(outstream, " %s=%ld\n", newname, (long)VECTOR(numv)[0])); + } else { + CHECK(fprintf(outstream, " %s=", newname)); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(numv)[0])); + CHECK(fputc('\n', outstream)); + } + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_STRING) { + char *s, *news; + IGRAPH_CHECK(igraph_i_attribute_get_string_graph_attr(graph, name, &strv)); + igraph_strvector_get(&strv, 0, &s); + IGRAPH_CHECK(igraph_i_dot_escape(s, &news)); + CHECK(fprintf(outstream, " %s=%s\n", newname, news)); + igraph_Free(news); + } else if (VECTOR(gtypes)[i] == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_graph_attr(graph, name, &boolv)); + CHECK(fprintf(outstream, " %s=%d\n", newname, VECTOR(boolv)[0] ? 1 : 0)); + IGRAPH_WARNING("A boolean graph attribute was converted to numeric"); + } else { + IGRAPH_WARNING("A non-numeric, non-string, non-boolean graph attribute ignored"); + } + igraph_Free(newname); + } + CHECK(fprintf(outstream, " ];\n")); + } + + /* Write the vertices */ + if (igraph_vector_size(&vtypes) > 0) { + for (i = 0; i < no_of_nodes; i++) { + CHECK(fprintf(outstream, " %ld [\n", i)); + for (j = 0; j < igraph_vector_size(&vtypes); j++) { + char *name, *newname; + igraph_strvector_get(&vnames, j, &name); + IGRAPH_CHECK(igraph_i_dot_escape(name, &newname)); + if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr(graph, name, igraph_vss_1((igraph_integer_t) i), &numv)); + if (VECTOR(numv)[0] == (long)VECTOR(numv)[0]) { + CHECK(fprintf(outstream, " %s=%ld\n", newname, (long)VECTOR(numv)[0])); + } else { + CHECK(fprintf(outstream, " %s=", newname)); + CHECK(igraph_real_fprintf_precise(outstream, + VECTOR(numv)[0])); + CHECK(fputc('\n', outstream)); + } + } else if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_STRING) { + char *s, *news; + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr(graph, name, igraph_vss_1((igraph_integer_t) i), &strv)); + igraph_strvector_get(&strv, 0, &s); + IGRAPH_CHECK(igraph_i_dot_escape(s, &news)); + CHECK(fprintf(outstream, " %s=%s\n", newname, news)); + igraph_Free(news); + } else if (VECTOR(vtypes)[j] == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_vertex_attr(graph, name, igraph_vss_1((igraph_integer_t) i), &boolv)); + CHECK(fprintf(outstream, " %s=%d\n", newname, VECTOR(boolv)[0] ? 1 : 0)); + IGRAPH_WARNING("A boolean vertex attribute was converted to numeric"); + } else { + IGRAPH_WARNING("A non-numeric, non-string, non-boolean vertex attribute was ignored"); + } + igraph_Free(newname); + } + CHECK(fprintf(outstream, " ];\n")); + } + } else { + for (i = 0; i < no_of_nodes; i++) { + CHECK(fprintf(outstream, " %ld;\n", i)); + } + } + CHECK(fprintf(outstream, "\n")); + + /* Write the edges */ + if (igraph_vector_size(&etypes) > 0) { + for (i = 0; i < no_of_edges; i++) { + long int from = IGRAPH_FROM(graph, i); + long int to = IGRAPH_TO(graph, i); + CHECK(fprintf(outstream, " %ld %s %ld [\n", from, edgeop, to)); + for (j = 0; j < igraph_vector_size(&etypes); j++) { + char *name, *newname; + igraph_strvector_get(&enames, j, &name); + IGRAPH_CHECK(igraph_i_dot_escape(name, &newname)); + if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_NUMERIC) { + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr(graph, + name, igraph_ess_1((igraph_integer_t) i), &numv)); + if (VECTOR(numv)[0] == (long)VECTOR(numv)[0]) { + CHECK(fprintf(outstream, " %s=%ld\n", newname, (long)VECTOR(numv)[0])); + } else { + CHECK(fprintf(outstream, " %s=", newname)); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(numv)[0])); + CHECK(fputc('\n', outstream)); + } + igraph_Free(newname); + } else if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_STRING) { + char *s, *news; + IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr(graph, + name, igraph_ess_1((igraph_integer_t) i), &strv)); + igraph_strvector_get(&strv, 0, &s); + IGRAPH_CHECK(igraph_i_dot_escape(s, &news)); + CHECK(fprintf(outstream, " %s=%s\n", newname, news)); + igraph_Free(newname); + igraph_Free(news); + } else if (VECTOR(etypes)[j] == IGRAPH_ATTRIBUTE_BOOLEAN) { + IGRAPH_CHECK(igraph_i_attribute_get_bool_edge_attr(graph, + name, igraph_ess_1((igraph_integer_t) i), &boolv)); + CHECK(fprintf(outstream, " %s=%d\n", newname, VECTOR(boolv)[0] ? 1 : 0)); + IGRAPH_WARNING("A boolean edge attribute was converted to numeric"); + } else { + IGRAPH_WARNING("A non-numeric, non-string graph attribute ignored"); + } + } + CHECK(fprintf(outstream, " ];\n")); + } + } else { + for (i = 0; i < no_of_edges; i++) { + long int from = IGRAPH_FROM(graph, i); + long int to = IGRAPH_TO(graph, i); + CHECK(fprintf(outstream, " %ld %s %ld;\n", from, edgeop, to)); + } + } + CHECK(fprintf(outstream, "}\n")); + + igraph_vector_bool_destroy(&boolv); + igraph_strvector_destroy(&strv); + igraph_vector_destroy(&numv); + igraph_vector_destroy(&etypes); + igraph_vector_destroy(&vtypes); + igraph_vector_destroy(>ypes); + igraph_strvector_destroy(&enames); + igraph_strvector_destroy(&vnames); + igraph_strvector_destroy(&gnames); + IGRAPH_FINALLY_CLEAN(9); + + return 0; +} + +#include "foreign-dl-header.h" + +int igraph_dl_yylex_init_extra (igraph_i_dl_parsedata_t* user_defined, + void* scanner); +int igraph_dl_yylex_destroy (void *scanner ); +int igraph_dl_yyparse (igraph_i_dl_parsedata_t* context); +void igraph_dl_yyset_in (FILE * in_str, void* yyscanner ); + +/** + * \function igraph_read_graph_dl + * \brief Read a file in the DL format of UCINET + * + * This is a simple textual file format used by UCINET. See + * http://www.analytictech.com/networks/dataentry.htm for + * examples. All the forms described here are supported by + * igraph. Vertex names and edge weights are also supported and they + * are added as attributes. (If an attribute handler is attached.) + * + * Note the specification does not mention whether the + * format is case sensitive or not. For igraph DL files are case + * sensitive, i.e. \c Larry and \c larry are not the same. + * \param graph Pointer to an uninitialized graph object. + * \param instream The stream to read the DL file from. + * \param directed Logical scalar, whether to create a directed file. + * \return Error code. + * + * Time complexity: linear in terms of the number of edges and + * vertices, except for the matrix format, which is quadratic in the + * number of vertices. + * + * \example examples/simple/igraph_read_graph_dl.c + */ + +int igraph_read_graph_dl(igraph_t *graph, FILE *instream, + igraph_bool_t directed) { + + int i; + long int n, n2; + const igraph_strvector_t *namevec = 0; + igraph_vector_ptr_t name, weight; + igraph_vector_ptr_t *pname = 0, *pweight = 0; + igraph_attribute_record_t namerec, weightrec; + const char *namestr = "name", *weightstr = "weight"; + igraph_i_dl_parsedata_t context; + + context.eof = 0; + context.mode = 0; + context.n = -1; + context.from = 0; + context.to = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&context.edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&context.weights, 0); + IGRAPH_CHECK(igraph_strvector_init(&context.labels, 0)); + IGRAPH_FINALLY(igraph_strvector_destroy, &context.labels); + IGRAPH_TRIE_INIT_FINALLY(&context.trie, /*names=*/ 1); + + igraph_dl_yylex_init_extra(&context, &context.scanner); + IGRAPH_FINALLY(igraph_dl_yylex_destroy, context.scanner); + + igraph_dl_yyset_in(instream, context.scanner); + + i = igraph_dl_yyparse(&context); + if (i != 0) { + if (context.errmsg[0] != 0) { + IGRAPH_ERROR(context.errmsg, IGRAPH_PARSEERROR); + } else { + IGRAPH_ERROR("Cannot read DL file", IGRAPH_PARSEERROR); + } + } + + /* Extend the weight vector, if needed */ + n = igraph_vector_size(&context.weights); + n2 = igraph_vector_size(&context.edges) / 2; + if (n != 0) { + igraph_vector_resize(&context.weights, n2); + for (; n < n2; n++) { + VECTOR(context.weights)[n] = IGRAPH_NAN; + } + } + + /* Check number of vertices */ + if (n2 > 0) { + n = (long int) igraph_vector_max(&context.edges); + } else { + n = 0; + } + if (n >= context.n) { + IGRAPH_WARNING("More vertices than specified in `DL' file"); + context.n = n; + } + + /* OK, everything is ready, create the graph */ + IGRAPH_CHECK(igraph_empty(graph, 0, directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + + /* Labels */ + if (igraph_strvector_size(&context.labels) != 0) { + namevec = (const igraph_strvector_t*) &context.labels; + } else if (igraph_trie_size(&context.trie) != 0) { + igraph_trie_getkeys(&context.trie, &namevec); + } + if (namevec) { + IGRAPH_CHECK(igraph_vector_ptr_init(&name, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &name); + pname = &name; + namerec.name = namestr; + namerec.type = IGRAPH_ATTRIBUTE_STRING; + namerec.value = namevec; + VECTOR(name)[0] = &namerec; + } + + /* Weights */ + if (igraph_vector_size(&context.weights) != 0) { + IGRAPH_CHECK(igraph_vector_ptr_init(&weight, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &weight); + pweight = &weight; + weightrec.name = weightstr; + weightrec.type = IGRAPH_ATTRIBUTE_NUMERIC; + weightrec.value = &context.weights; + VECTOR(weight)[0] = &weightrec; + } + + IGRAPH_CHECK(igraph_add_vertices(graph, (igraph_integer_t) context.n, pname)); + IGRAPH_CHECK(igraph_add_edges(graph, &context.edges, pweight)); + + if (pweight) { + igraph_vector_ptr_destroy(pweight); + IGRAPH_FINALLY_CLEAN(1); + } + + if (pname) { + igraph_vector_ptr_destroy(pname); + IGRAPH_FINALLY_CLEAN(1); + } + + /* don't destroy the graph itself but pop it from the finally stack */ + IGRAPH_FINALLY_CLEAN(1); + + igraph_trie_destroy(&context.trie); + igraph_strvector_destroy(&context.labels); + igraph_vector_destroy(&context.edges); + igraph_vector_destroy(&context.weights); + igraph_dl_yylex_destroy(context.scanner); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +/** + * \function igraph_write_graph_leda + * \brief Write a graph in LEDA native graph format. + * + * This function writes a graph to an output stream in LEDA format. + * See http://www.algorithmic-solutions.info/leda_guide/graphs/leda_native_graph_fileformat.html + * + * + * The support for the LEDA format is very basic at the moment; igraph + * writes only the LEDA graph section which supports one selected vertex + * and edge attribute and no layout information or visual attributes. + * + * \param graph The graph to write to the stream. + * \param outstream The stream. + * \param vertex_attr_name The name of the vertex attribute whose values + * are to be stored in the output or \c NULL if no + * vertex attribute has to be stored. + * \param edge_attr_name The name of the edge attribute whose values + * are to be stored in the output or \c NULL if no + * edge attribute has to be stored. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of vertices and edges in the + * graph. + * + * \example examples/simple/igraph_write_graph_leda.c + */ + +int igraph_write_graph_leda(const igraph_t *graph, FILE *outstream, + const char* vertex_attr_name, + const char* edge_attr_name) { + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_eit_t it; + long int i = 0; + int ret; + igraph_attribute_type_t vertex_attr_type = IGRAPH_ATTRIBUTE_DEFAULT; + igraph_attribute_type_t edge_attr_type = IGRAPH_ATTRIBUTE_DEFAULT; + igraph_integer_t from, to, rev; + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_FROM), + &it)); + IGRAPH_FINALLY(igraph_eit_destroy, &it); + + /* Check if we have the vertex attribute */ + if (vertex_attr_name && + !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_VERTEX, vertex_attr_name)) { + vertex_attr_name = 0; + IGRAPH_WARNING("specified vertex attribute does not exist"); + } + if (vertex_attr_name) { + IGRAPH_CHECK(igraph_i_attribute_gettype(graph, &vertex_attr_type, + IGRAPH_ATTRIBUTE_VERTEX, vertex_attr_name)); + if (vertex_attr_type != IGRAPH_ATTRIBUTE_NUMERIC && + vertex_attr_type != IGRAPH_ATTRIBUTE_STRING) { + vertex_attr_name = 0; vertex_attr_type = IGRAPH_ATTRIBUTE_DEFAULT; + IGRAPH_WARNING("specified vertex attribute must be numeric or string"); + } + } + + /* Check if we have the edge attribute */ + if (edge_attr_name && + !igraph_i_attribute_has_attr(graph, IGRAPH_ATTRIBUTE_EDGE, edge_attr_name)) { + edge_attr_name = 0; + IGRAPH_WARNING("specified edge attribute does not exist"); + } + if (edge_attr_name) { + IGRAPH_CHECK(igraph_i_attribute_gettype(graph, &edge_attr_type, + IGRAPH_ATTRIBUTE_EDGE, edge_attr_name)); + if (edge_attr_type != IGRAPH_ATTRIBUTE_NUMERIC && + edge_attr_type != IGRAPH_ATTRIBUTE_STRING) { + edge_attr_name = 0; edge_attr_type = IGRAPH_ATTRIBUTE_DEFAULT; + IGRAPH_WARNING("specified edge attribute must be numeric or string"); + } + } + + /* Start writing header */ + CHECK(fprintf(outstream, "LEDA.GRAPH\n")); + + switch (vertex_attr_type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + CHECK(fprintf(outstream, "float\n")); + break; + case IGRAPH_ATTRIBUTE_STRING: + CHECK(fprintf(outstream, "string\n")); + break; + default: + CHECK(fprintf(outstream, "void\n")); + } + + switch (edge_attr_type) { + case IGRAPH_ATTRIBUTE_NUMERIC: + CHECK(fprintf(outstream, "float\n")); + break; + case IGRAPH_ATTRIBUTE_STRING: + CHECK(fprintf(outstream, "string\n")); + break; + default: + CHECK(fprintf(outstream, "void\n")); + } + + CHECK(fprintf(outstream, "%d\n", (igraph_is_directed(graph) ? -1 : -2))); + + /* Start writing vertices */ + CHECK(fprintf(outstream, "# Vertices\n")); + CHECK(fprintf(outstream, "%ld\n", no_of_nodes)); + + if (vertex_attr_type == IGRAPH_ATTRIBUTE_NUMERIC) { + /* Vertices with numeric attributes */ + igraph_vector_t values; + + IGRAPH_VECTOR_INIT_FINALLY(&values, no_of_nodes); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_vertex_attr( + graph, vertex_attr_name, igraph_vss_all(), &values)); + + for (i = 0; i < no_of_nodes; i++) { + CHECK(fprintf(outstream, "|{")); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(values)[i])); + CHECK(fprintf(outstream, "}|\n")); + } + + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + } else if (vertex_attr_type == IGRAPH_ATTRIBUTE_STRING) { + /* Vertices with string attributes */ + igraph_strvector_t values; + + IGRAPH_CHECK(igraph_strvector_init(&values, no_of_nodes)); + IGRAPH_FINALLY(igraph_strvector_destroy, &values); + + IGRAPH_CHECK(igraph_i_attribute_get_string_vertex_attr( + graph, vertex_attr_name, igraph_vss_all(), &values)); + + for (i = 0; i < no_of_nodes; i++) { + const char* str = STR(values, i); + if (strchr(str, '\n') != 0) { + IGRAPH_ERROR("edge attribute values cannot contain newline characters", + IGRAPH_EINVAL); + } + CHECK(fprintf(outstream, "|{%s}|\n", str)); + } + + igraph_strvector_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Vertices with no attributes */ + for (i = 0; i < no_of_nodes; i++) { + CHECK(fprintf(outstream, "|{}|\n")); + } + } + + CHECK(fprintf(outstream, "# Edges\n")); + CHECK(fprintf(outstream, "%ld\n", no_of_edges)); + + if (edge_attr_type == IGRAPH_ATTRIBUTE_NUMERIC) { + /* Edges with numeric attributes */ + igraph_vector_t values; + IGRAPH_VECTOR_INIT_FINALLY(&values, no_of_nodes); + IGRAPH_CHECK(igraph_i_attribute_get_numeric_edge_attr( + graph, edge_attr_name, igraph_ess_all(IGRAPH_EDGEORDER_ID), &values)); + while (!IGRAPH_EIT_END(it)) { + long int eid = IGRAPH_EIT_GET(it); + igraph_edge(graph, (igraph_integer_t) eid, &from, &to); + igraph_get_eid(graph, &rev, to, from, 1, 0); + if (rev == IGRAPH_EIT_GET(it)) { + rev = -1; + } + CHECK(fprintf(outstream, "%ld %ld %ld |{", + (long int) from + 1, (long int) to + 1, + (long int) rev + 1)); + CHECK(igraph_real_fprintf_precise(outstream, VECTOR(values)[eid])); + CHECK(fprintf(outstream, "}|\n")); + IGRAPH_EIT_NEXT(it); + } + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + } else if (edge_attr_type == IGRAPH_ATTRIBUTE_STRING) { + /* Edges with string attributes */ + igraph_strvector_t values; + IGRAPH_CHECK(igraph_strvector_init(&values, no_of_nodes)); + IGRAPH_FINALLY(igraph_strvector_destroy, &values); + IGRAPH_CHECK(igraph_i_attribute_get_string_edge_attr( + graph, edge_attr_name, igraph_ess_all(IGRAPH_EDGEORDER_ID), &values)); + while (!IGRAPH_EIT_END(it)) { + long int eid = IGRAPH_EIT_GET(it); + const char* str = STR(values, eid); + igraph_edge(graph, (igraph_integer_t) eid, &from, &to); + igraph_get_eid(graph, &rev, to, from, 1, 0); + if (rev == IGRAPH_EIT_GET(it)) { + rev = -1; + } + if (strchr(str, '\n') != 0) { + IGRAPH_ERROR("edge attribute values cannot contain newline characters", + IGRAPH_EINVAL); + } + CHECK(fprintf(outstream, "%ld %ld %ld |{%s}|\n", + (long int) from + 1, (long int) to + 1, + (long int) rev + 1, str)); + IGRAPH_EIT_NEXT(it); + } + igraph_strvector_destroy(&values); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Edges with no attributes */ + while (!IGRAPH_EIT_END(it)) { + igraph_edge(graph, IGRAPH_EIT_GET(it), &from, &to); + igraph_get_eid(graph, &rev, to, from, 1, 0); + if (rev == IGRAPH_EIT_GET(it)) { + rev = -1; + } + CHECK(fprintf(outstream, "%ld %ld %ld |{}|\n", + (long int) from + 1, (long int) to + 1, + (long int) rev + 1)); + IGRAPH_EIT_NEXT(it); + } + } + + igraph_eit_destroy(&it); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +#undef CHECK + + diff --git a/src/forestfire.c b/src/forestfire.c new file mode 100644 index 0000000..db5d9c3 --- /dev/null +++ b/src/forestfire.c @@ -0,0 +1,263 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_games.h" +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_progress.h" +#include "igraph_interrupt_internal.h" +#include "igraph_interface.h" +#include "igraph_constructors.h" +#include "igraph_dqueue.h" +#include "config.h" + +typedef struct igraph_i_forest_fire_data_t { + igraph_vector_t *inneis; + igraph_vector_t *outneis; + long int no_of_nodes; +} igraph_i_forest_fire_data_t; + + +static void igraph_i_forest_fire_free(igraph_i_forest_fire_data_t *data) { + long int i; + for (i = 0; i < data->no_of_nodes; i++) { + igraph_vector_destroy(data->inneis + i); + igraph_vector_destroy(data->outneis + i); + } +} + +/** + * \function igraph_forest_fire_game + * \brief Generates a network according to the \quote forest fire game \endquote + * + * The forest fire model intends to reproduce the following network + * characteristics, observed in real networks: + * \ilist + * \ili Heavy-tailed in-degree distribution. + * \ili Heavy-tailed out-degree distribution. + * \ili Communities. + * \ili Densification power-law. The network is densifying in time, + * according to a power-law rule. + * \ili Shrinking diameter. The diameter of the network decreases in + * time. + * \endilist + * + * + * The network is generated in the following way. One vertex is added at + * a time. This vertex connects to (cites) ambs vertices already + * present in the network, chosen uniformly random. Now, for each cited + * vertex v we do the following procedure: + * \olist + * \oli We generate two random number, x and y, that are + * geometrically distributed with means p/(1-p) and + * rp(1-rp). (p is fw_prob, r is + * bw_factor.) The new vertex cites x outgoing neighbors + * and y incoming neighbors of v, from those which are + * not yet cited by the new vertex. If there are less than x or + * y such vertices available then we cite all of them. + * \oli The same procedure is applied to all the newly cited + * vertices. + * \endolist + * + * See also: + * Jure Leskovec, Jon Kleinberg and Christos Faloutsos. Graphs over time: + * densification laws, shrinking diameters and possible explanations. + * \emb KDD '05: Proceeding of the eleventh ACM SIGKDD international + * conference on Knowledge discovery in data mining \eme, 177--187, 2005. + * + * Note however, that the version of the model in the published paper is incorrect + * in the sense that it cannot generate the kind of graphs the authors + * claim. A corrected version is available from + * http://cs.stanford.edu/people/jure/pubs/powergrowth-tkdd.pdf , our + * implementation is based on this. + * + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the graph. + * \param fw_prob The forward burning probability. + * \param bw_factor The backward burning ratio. The backward burning + probability is calculated as bw.factor*fw.prob. + * \param pambs The number of ambassador vertices. + * \param directed Whether to create a directed graph. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_forest_fire_game(igraph_t *graph, igraph_integer_t nodes, + igraph_real_t fw_prob, igraph_real_t bw_factor, + igraph_integer_t pambs, igraph_bool_t directed) { + + igraph_vector_long_t visited; + long int no_of_nodes = nodes, actnode, i; + igraph_vector_t edges; + igraph_vector_t *inneis, *outneis; + igraph_i_forest_fire_data_t data; + igraph_dqueue_t neiq; + long int ambs = pambs; + igraph_real_t param_geom_out = 1 - fw_prob; + igraph_real_t param_geom_in = 1 - fw_prob * bw_factor; + + if (fw_prob < 0 || fw_prob >= 1) { + IGRAPH_ERROR("Forest fire model: 'fw_prob' must satisfy 0 <= fw_prob < 1.", + IGRAPH_EINVAL); + } + if (bw_factor * fw_prob < 0 || bw_factor * fw_prob >= 1) { + IGRAPH_ERROR("Forest fire model: 'bw_factor' must satisfy 0 <= bw_factor * fw_prob < 1.", + IGRAPH_EINVAL); + } + if (ambs < 0) { + IGRAPH_ERROR("Forest fire model: Number of ambassadors must not be negative.", + IGRAPH_EINVAL); + } + + if (ambs == 0) { + IGRAPH_CHECK(igraph_empty(graph, nodes, directed)); + return 0; + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + inneis = igraph_Calloc(no_of_nodes, igraph_vector_t); + if (!inneis) { + IGRAPH_ERROR("Cannot run forest fire model", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, inneis); + outneis = igraph_Calloc(no_of_nodes, igraph_vector_t); + if (!outneis) { + IGRAPH_ERROR("Cannot run forest fire model", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, outneis); + data.inneis = inneis; + data.outneis = outneis; + data.no_of_nodes = no_of_nodes; + IGRAPH_FINALLY(igraph_i_forest_fire_free, &data); + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_CHECK(igraph_vector_init(inneis + i, 0)); + IGRAPH_CHECK(igraph_vector_init(outneis + i, 0)); + } + + IGRAPH_CHECK(igraph_vector_long_init(&visited, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &visited); + IGRAPH_DQUEUE_INIT_FINALLY(&neiq, 10); + + RNG_BEGIN(); + +#define ADD_EDGE_TO(nei) \ + if (VECTOR(visited)[(nei)] != actnode+1) { \ + VECTOR(visited)[(nei)] = actnode+1; \ + IGRAPH_CHECK(igraph_dqueue_push(&neiq, nei)); \ + IGRAPH_CHECK(igraph_vector_push_back(&edges, actnode)); \ + IGRAPH_CHECK(igraph_vector_push_back(&edges, nei)); \ + IGRAPH_CHECK(igraph_vector_push_back(outneis+actnode, nei)); \ + IGRAPH_CHECK(igraph_vector_push_back(inneis+nei, actnode)); \ + } + + IGRAPH_PROGRESS("Forest fire: ", 0.0, NULL); + + for (actnode = 1; actnode < no_of_nodes; actnode++) { + + IGRAPH_PROGRESS("Forest fire: ", 100.0 * actnode / no_of_nodes, NULL); + + IGRAPH_ALLOW_INTERRUPTION(); + + /* We don't want to visit the current vertex */ + VECTOR(visited)[actnode] = actnode + 1; + + /* Choose ambassador(s) */ + for (i = 0; i < ambs; i++) { + long int a = RNG_INTEGER(0, actnode - 1); + ADD_EDGE_TO(a); + } + + while (!igraph_dqueue_empty(&neiq)) { + long int actamb = (long int) igraph_dqueue_pop(&neiq); + igraph_vector_t *outv = outneis + actamb; + igraph_vector_t *inv = inneis + actamb; + long int no_in = igraph_vector_size(inv); + long int no_out = igraph_vector_size(outv); + long int neis_out = (long int) RNG_GEOM(param_geom_out); + long int neis_in = (long int) RNG_GEOM(param_geom_in); + /* outgoing neighbors */ + if (neis_out >= no_out) { + for (i = 0; i < no_out; i++) { + long int nei = (long int) VECTOR(*outv)[i]; + ADD_EDGE_TO(nei); + } + } else { + long int oleft = no_out; + for (i = 0; i < neis_out && oleft > 0; ) { + long int which = RNG_INTEGER(0, oleft - 1); + long int nei = (long int) VECTOR(*outv)[which]; + VECTOR(*outv)[which] = VECTOR(*outv)[oleft - 1]; + VECTOR(*outv)[oleft - 1] = nei; + if (VECTOR(visited)[nei] != actnode + 1) { + ADD_EDGE_TO(nei); + i++; + } + oleft--; + } + } + /* incoming neighbors */ + if (neis_in >= no_in) { + for (i = 0; i < no_in; i++) { + long int nei = (long int) VECTOR(*inv)[i]; + ADD_EDGE_TO(nei); + } + } else { + long int ileft = no_in; + for (i = 0; i < neis_in && ileft > 0; ) { + long int which = RNG_INTEGER(0, ileft - 1); + long int nei = (long int) VECTOR(*inv)[which]; + VECTOR(*inv)[which] = VECTOR(*inv)[ileft - 1]; + VECTOR(*inv)[ileft - 1] = nei; + if (VECTOR(visited)[nei] != actnode + 1) { + ADD_EDGE_TO(nei); + i++; + } + ileft--; + } + } + + } /* while neiq not empty */ + + } /* actnode < no_of_nodes */ + +#undef ADD_EDGE_TO + + RNG_END(); + + IGRAPH_PROGRESS("Forest fire: ", 100.0, NULL); + + igraph_dqueue_destroy(&neiq); + igraph_vector_long_destroy(&visited); + igraph_i_forest_fire_free(&data); + igraph_free(outneis); + igraph_free(inneis); + IGRAPH_FINALLY_CLEAN(5); + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} diff --git a/src/fortran_intrinsics.c b/src/fortran_intrinsics.c new file mode 100644 index 0000000..5bc314c --- /dev/null +++ b/src/fortran_intrinsics.c @@ -0,0 +1,53 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2011-12 Gabor Csardi + 334 Harvard street, Cambridge MA, 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include + +double digitsdbl_(double x) { + return (double) DBL_MANT_DIG; +} + +double epsilondbl_(double x) { + return DBL_EPSILON; +} + +double hugedbl_(double x) { + return DBL_MAX; +} + +double tinydbl_(double x) { + return DBL_MIN; +} + +int maxexponentdbl_(double x) { + return DBL_MAX_EXP; +} + +int minexponentdbl_(double x) { + return DBL_MIN_EXP; +} + +double radixdbl_(double x) { + return (double) FLT_RADIX; +} + diff --git a/src/games.c b/src/games.c new file mode 100644 index 0000000..6fb6700 --- /dev/null +++ b/src/games.c @@ -0,0 +1,4806 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph R library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_interface.h" +#include "igraph_games.h" +#include "igraph_random.h" +#include "igraph_memory.h" +#include "igraph_interrupt_internal.h" +#include "igraph_attributes.h" +#include "igraph_constructors.h" +#include "igraph_nongraph.h" +#include "igraph_conversion.h" +#include "igraph_psumtree.h" +#include "igraph_dqueue.h" +#include "igraph_adjlist.h" +#include "igraph_iterators.h" +#include "igraph_progress.h" +#include "igraph_topology.h" +#include "igraph_types_internal.h" +#include "config.h" + +#include + +typedef struct { + long int no; + igraph_psumtree_t *sumtrees; +} igraph_i_citing_cited_type_game_struct_t; + +static void igraph_i_citing_cited_type_game_free ( + igraph_i_citing_cited_type_game_struct_t *s); +/** + * \section about_games + * + * Games are randomized graph generators. Randomization means that + * they generate a different graph every time you call them. + */ + +static int igraph_i_barabasi_game_bag(igraph_t *graph, igraph_integer_t n, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_bool_t directed, + const igraph_t *start_from); + +static int igraph_i_barabasi_game_psumtree_multiple(igraph_t *graph, + igraph_integer_t n, + igraph_real_t power, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + const igraph_t *start_from); + +static int igraph_i_barabasi_game_psumtree(igraph_t *graph, + igraph_integer_t n, + igraph_real_t power, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + const igraph_t *start_from); + +static int igraph_i_barabasi_game_bag(igraph_t *graph, igraph_integer_t n, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_bool_t directed, + const igraph_t *start_from) { + + long int no_of_nodes = n; + long int no_of_neighbors = m; + long int *bag; + long int bagp = 0; + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + long int resp; + long int i, j, k; + long int bagsize, start_nodes, start_edges, new_edges, no_of_edges; + + if (!directed) { + outpref = 1; + } + + start_nodes = start_from ? igraph_vcount(start_from) : 1; + start_edges = start_from ? igraph_ecount(start_from) : 0; + if (outseq) { + if (igraph_vector_size(outseq) > 1) { + new_edges = (long int) (igraph_vector_sum(outseq) - VECTOR(*outseq)[0]); + } else { + new_edges = 0; + } + } else { + new_edges = (no_of_nodes - start_nodes) * no_of_neighbors; + } + no_of_edges = start_edges + new_edges; + resp = start_edges * 2; + bagsize = no_of_nodes + no_of_edges + (outpref ? no_of_edges : 0); + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + + bag = igraph_Calloc(bagsize, long int); + if (bag == 0) { + IGRAPH_ERROR("barabasi_game failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, bag); + + /* The first node(s) in the bag */ + if (start_from) { + igraph_vector_t deg; + long int ii, jj, sn = igraph_vcount(start_from); + igraph_neimode_t mm = outpref ? IGRAPH_ALL : IGRAPH_IN; + + IGRAPH_VECTOR_INIT_FINALLY(°, sn); + IGRAPH_CHECK(igraph_degree(start_from, °, igraph_vss_all(), mm, + IGRAPH_LOOPS)); + for (ii = 0; ii < sn; ii++) { + long int d = (long int) VECTOR(deg)[ii]; + for (jj = 0; jj <= d; jj++) { + bag[bagp++] = ii; + } + } + + igraph_vector_destroy(°); + IGRAPH_FINALLY_CLEAN(1); + } else { + bag[bagp++] = 0; + } + + /* Initialize the edges vector */ + if (start_from) { + IGRAPH_CHECK(igraph_get_edgelist(start_from, &edges, /* bycol= */ 0)); + igraph_vector_resize(&edges, no_of_edges * 2); + } + + RNG_BEGIN(); + + /* and the others */ + + for (i = (start_from ? start_nodes : 1), k = (start_from ? 0 : 1); + i < no_of_nodes; i++, k++) { + /* draw edges */ + if (outseq) { + no_of_neighbors = (long int) VECTOR(*outseq)[k]; + } + for (j = 0; j < no_of_neighbors; j++) { + long int to = bag[RNG_INTEGER(0, bagp - 1)]; + VECTOR(edges)[resp++] = i; + VECTOR(edges)[resp++] = to; + } + /* update bag */ + bag[bagp++] = i; + for (j = 0; j < no_of_neighbors; j++) { + bag[bagp++] = (long int) VECTOR(edges)[resp - 2 * j - 1]; + if (outpref) { + bag[bagp++] = i; + } + } + } + + RNG_END(); + + igraph_Free(bag); + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) no_of_nodes, + directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +static int igraph_i_barabasi_game_psumtree_multiple(igraph_t *graph, + igraph_integer_t n, + igraph_real_t power, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + const igraph_t *start_from) { + + long int no_of_nodes = n; + long int no_of_neighbors = m; + igraph_vector_t edges; + long int i, j, k; + igraph_psumtree_t sumtree; + long int edgeptr = 0; + igraph_vector_t degree; + long int start_nodes, start_edges, new_edges, no_of_edges; + + if (!directed) { + outpref = 1; + } + + start_nodes = start_from ? igraph_vcount(start_from) : 1; + start_edges = start_from ? igraph_ecount(start_from) : 0; + if (outseq) { + if (igraph_vector_size(outseq) > 1) { + new_edges = (long int) (igraph_vector_sum(outseq) - VECTOR(*outseq)[0]); + } else { + new_edges = 0; + } + } else { + new_edges = (no_of_nodes - start_nodes) * no_of_neighbors; + } + no_of_edges = start_edges + new_edges; + edgeptr = start_edges * 2; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + + /* first node(s) */ + if (start_from) { + long int ii, sn = igraph_vcount(start_from); + igraph_neimode_t mm = outpref ? IGRAPH_ALL : IGRAPH_IN; + IGRAPH_CHECK(igraph_degree(start_from, °ree, igraph_vss_all(), mm, + IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_vector_resize(°ree, no_of_nodes)); + for (ii = 0; ii < sn; ii++) { + igraph_psumtree_update(&sumtree, ii, pow(VECTOR(degree)[ii], power) + A); + } + } else { + igraph_psumtree_update(&sumtree, 0, A); + } + + /* Initialize the edges vector */ + if (start_from) { + IGRAPH_CHECK(igraph_get_edgelist(start_from, &edges, /* bycol= */ 0)); + igraph_vector_resize(&edges, no_of_edges * 2); + } + + RNG_BEGIN(); + + /* and the rest */ + for (i = (start_from ? start_nodes : 1), k = (start_from ? 0 : 1); + i < no_of_nodes; i++, k++) { + igraph_real_t sum = igraph_psumtree_sum(&sumtree); + long int to; + if (outseq) { + no_of_neighbors = (long int) VECTOR(*outseq)[k]; + } + for (j = 0; j < no_of_neighbors; j++) { + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + VECTOR(degree)[to]++; + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = to; + } + /* update probabilities */ + for (j = 0; j < no_of_neighbors; j++) { + long int nn = (long int) VECTOR(edges)[edgeptr - 2 * j - 1]; + igraph_psumtree_update(&sumtree, nn, + pow(VECTOR(degree)[nn], power) + A); + } + if (outpref) { + VECTOR(degree)[i] += no_of_neighbors; + igraph_psumtree_update(&sumtree, i, + pow(VECTOR(degree)[i], power) + A); + } else { + igraph_psumtree_update(&sumtree, i, A); + } + } + + RNG_END(); + + igraph_psumtree_destroy(&sumtree); + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_barabasi_game_psumtree(igraph_t *graph, + igraph_integer_t n, + igraph_real_t power, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + const igraph_t *start_from) { + + long int no_of_nodes = n; + long int no_of_neighbors = m; + igraph_vector_t edges; + long int i, j, k; + igraph_psumtree_t sumtree; + long int edgeptr = 0; + igraph_vector_t degree; + long int start_nodes, start_edges, new_edges, no_of_edges; + + if (!directed) { + outpref = 1; + } + + start_nodes = start_from ? igraph_vcount(start_from) : 1; + start_edges = start_from ? igraph_ecount(start_from) : 0; + if (outseq) { + if (igraph_vector_size(outseq) > 1) { + new_edges = (long int) (igraph_vector_sum(outseq) - VECTOR(*outseq)[0]); + } else { + new_edges = 0; + } + } else { + new_edges = (no_of_nodes - start_nodes) * no_of_neighbors; + } + no_of_edges = start_edges + new_edges; + edgeptr = start_edges * 2; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges * 2)); + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + + RNG_BEGIN(); + + /* first node(s) */ + if (start_from) { + long int ii, sn = igraph_vcount(start_from); + igraph_neimode_t mm = outpref ? IGRAPH_ALL : IGRAPH_IN; + IGRAPH_CHECK(igraph_degree(start_from, °ree, igraph_vss_all(), mm, + IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_vector_resize(°ree, no_of_nodes)); + for (ii = 0; ii < sn; ii++) { + igraph_psumtree_update(&sumtree, ii, pow(VECTOR(degree)[ii], power) + A); + } + } else { + igraph_psumtree_update(&sumtree, 0, A); + } + + /* Initialize the edges vector */ + if (start_from) { + IGRAPH_CHECK(igraph_get_edgelist(start_from, &edges, /* bycol= */ 0)); + } + + /* and the rest */ + for (i = (start_from ? start_nodes : 1), k = (start_from ? 0 : 1); + i < no_of_nodes; i++, k++) { + igraph_real_t sum; + long int to; + if (outseq) { + no_of_neighbors = (long int) VECTOR(*outseq)[k]; + } + if (no_of_neighbors >= i) { + /* All existing vertices are cited */ + for (to = 0; to < i; to++) { + VECTOR(degree)[to]++; + igraph_vector_push_back(&edges, i); + igraph_vector_push_back(&edges, to); + edgeptr += 2; + igraph_psumtree_update(&sumtree, to, pow(VECTOR(degree)[to], power) + A); + } + } else { + for (j = 0; j < no_of_neighbors; j++) { + sum = igraph_psumtree_sum(&sumtree); + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + VECTOR(degree)[to]++; + igraph_vector_push_back(&edges, i); + igraph_vector_push_back(&edges, to); + edgeptr += 2; + igraph_psumtree_update(&sumtree, to, 0.0); + } + /* update probabilities */ + for (j = 0; j < no_of_neighbors; j++) { + long int nn = (long int) VECTOR(edges)[edgeptr - 2 * j - 1]; + igraph_psumtree_update(&sumtree, nn, + pow(VECTOR(degree)[nn], power) + A); + } + } + if (outpref) { + VECTOR(degree)[i] += no_of_neighbors > i ? i : no_of_neighbors; + igraph_psumtree_update(&sumtree, i, + pow(VECTOR(degree)[i], power) + A); + } else { + igraph_psumtree_update(&sumtree, i, A); + } + } + + RNG_END(); + + igraph_psumtree_destroy(&sumtree); + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \ingroup generators + * \function igraph_barabasi_game + * \brief Generates a graph based on the Barabási-Albert model. + * + * \param graph An uninitialized graph object. + * \param n The number of vertices in the graph. + * \param power Power of the preferential attachment. The probability + * that a vertex is cited is proportional to d^power+A, where + * d is its degree (see also the \p outpref argument), power + * and A are given by arguments. In the classic preferential + * attachment model power=1. + * \param m The number of outgoing edges generated for each + * vertex. (Only if \p outseq is \c NULL.) + * \param outseq Gives the (out-)degrees of the vertices. If this is + * constant, this can be a NULL pointer or an empty (but + * initialized!) vector, in this case \p m contains + * the constant out-degree. The very first vertex has by definition + * no outgoing edges, so the first number in this vector is + * ignored. + * \param outpref Boolean, if true not only the in- but also the out-degree + * of a vertex increases its citation probability. Ie. the + * citation probability is determined by the total degree of + * the vertices. Ignored and assumed to be true if the graph + * being generated is undirected. + * \param A The probability that a vertex is cited is proportional to + * d^power+A, where d is its degree (see also the \p outpref + * argument), power and A are given by arguments. In the + * previous versions of the function this parameter was + * implicitly set to one. + * \param directed Boolean, whether to generate a directed graph. + * \param algo The algorithm to use to generate the network. Possible + * values: + * \clist + * \cli IGRAPH_BARABASI_BAG + * This is the algorithm that was previously (before version + * 0.6) solely implemented in igraph. It works by putting the + * ids of the vertices into a bag (multiset, really), exactly + * as many times as their (in-)degree, plus once more. Then + * the required number of cited vertices are drawn from the + * bag, with replacement. This method might generate multiple + * edges. It only works if power=1 and A=1. + * \cli IGRAPH_BARABASI_PSUMTREE + * This algorithm uses a partial prefix-sum tree to generate + * the graph. It does not generate multiple edges and + * works for any power and A values. + * \cli IGRAPH_BARABASI_PSUMTREE_MULTIPLE + * This algorithm also uses a partial prefix-sum tree to + * generate the graph. The difference is, that now multiple + * edges are allowed. This method was implemented under the + * name \c igraph_nonlinear_barabasi_game before version 0.6. + * \endclist + * \param start_from Either a null pointer, or a graph. In the former + * case, the starting configuration is a clique of size \p m. + * In the latter case, the graph is a starting configuration. + * The graph must be non-empty, i.e. it must have at least one + * vertex. If a graph is supplied here and the \p outseq + * argument is also given, then \p outseq should only contain + * information on the vertices that are not in the \p + * start_from graph. + * \return Error code: + * \c IGRAPH_EINVAL: invalid \p n, + * \p m or \p outseq parameter. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges. + * + * \example examples/simple/igraph_barabasi_game.c + * \example examples/simple/igraph_barabasi_game2.c + */ + +int igraph_barabasi_game(igraph_t *graph, igraph_integer_t n, + igraph_real_t power, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t A, + igraph_bool_t directed, + igraph_barabasi_algorithm_t algo, + const igraph_t *start_from) { + + long int start_nodes = start_from ? igraph_vcount(start_from) : 0; + long int newn = start_from ? n - start_nodes : n; + + /* Fix obscure parameterizations */ + if (outseq && igraph_vector_size(outseq) == 0) { + outseq = 0; + } + if (!directed) { + outpref = 1; + } + + /* Check arguments */ + + if (algo != IGRAPH_BARABASI_BAG && + algo != IGRAPH_BARABASI_PSUMTREE && + algo != IGRAPH_BARABASI_PSUMTREE_MULTIPLE) { + IGRAPH_ERROR("Invalid algorithm", IGRAPH_EINVAL); + } + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices", IGRAPH_EINVAL); + } else if (newn < 0) { + IGRAPH_ERROR("Starting graph has too many vertices", IGRAPH_EINVAL); + } + if (start_from && start_nodes == 0) { + IGRAPH_ERROR("Cannot start from an empty graph", IGRAPH_EINVAL); + } + if (outseq != 0 && igraph_vector_size(outseq) != 0 && + igraph_vector_size(outseq) != newn) { + IGRAPH_ERROR("Invalid out degree sequence length", IGRAPH_EINVAL); + } + if ( (outseq == 0 || igraph_vector_size(outseq) == 0) && m < 0) { + IGRAPH_ERROR("Invalid out degree", IGRAPH_EINVAL); + } + if (outseq && igraph_vector_min(outseq) < 0) { + IGRAPH_ERROR("Negative out degree in sequence", IGRAPH_EINVAL); + } + if (!outpref && A <= 0) { + IGRAPH_ERROR("Constant attractiveness (A) must be positive", + IGRAPH_EINVAL); + } + if (outpref && A < 0) { + IGRAPH_ERROR("Constant attractiveness (A) must be non-negative", + IGRAPH_EINVAL); + } + if (algo == IGRAPH_BARABASI_BAG) { + if (power != 1) { + IGRAPH_ERROR("Power must be one for 'bag' algorithm", IGRAPH_EINVAL); + } + if (A != 1) { + IGRAPH_ERROR("Constant attractiveness (A) must be one for bag algorithm", + IGRAPH_EINVAL); + } + } + if (start_from && directed != igraph_is_directed(start_from)) { + IGRAPH_WARNING("Directedness of the start graph and the output graph" + " mismatch"); + } + if (start_from && !igraph_is_directed(start_from) && !outpref) { + IGRAPH_ERROR("`outpref' must be true if starting from an undirected " + "graph", IGRAPH_EINVAL); + } + + if (n == 0) { + return igraph_empty(graph, 0, directed); + } + + if (algo == IGRAPH_BARABASI_BAG) { + return igraph_i_barabasi_game_bag(graph, n, m, outseq, outpref, directed, + start_from); + } else if (algo == IGRAPH_BARABASI_PSUMTREE) { + return igraph_i_barabasi_game_psumtree(graph, n, power, m, outseq, + outpref, A, directed, start_from); + } else if (algo == IGRAPH_BARABASI_PSUMTREE_MULTIPLE) { + return igraph_i_barabasi_game_psumtree_multiple(graph, n, power, m, + outseq, outpref, A, + directed, start_from); + } + + return 0; +} + +/** + * \ingroup internal + */ + +int igraph_erdos_renyi_game_gnp(igraph_t *graph, igraph_integer_t n, igraph_real_t p, + igraph_bool_t directed, igraph_bool_t loops) { + + long int no_of_nodes = n; + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + igraph_vector_t s = IGRAPH_VECTOR_NULL; + int retval = 0; + + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices", IGRAPH_EINVAL); + } + if (p < 0.0 || p > 1.0) { + IGRAPH_ERROR("Invalid probability given", IGRAPH_EINVAL); + } + + if (p == 0.0 || no_of_nodes <= 1) { + IGRAPH_CHECK(retval = igraph_empty(graph, n, directed)); + } else if (p == 1.0) { + IGRAPH_CHECK(retval = igraph_full(graph, n, directed, loops)); + } else { + + long int i; + double maxedges = n, last; + if (directed && loops) { + maxedges *= n; + } else if (directed && !loops) { + maxedges *= (n - 1); + } else if (!directed && loops) { + maxedges *= (n + 1) / 2.0; + } else { + maxedges *= (n - 1) / 2.0; + } + + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_CHECK(igraph_vector_reserve(&s, (long int) (maxedges * p * 1.1))); + + RNG_BEGIN(); + + last = RNG_GEOM(p); + while (last < maxedges) { + IGRAPH_CHECK(igraph_vector_push_back(&s, last)); + last += RNG_GEOM(p); + last += 1; + } + + RNG_END(); + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, igraph_vector_size(&s) * 2)); + + if (directed && loops) { + for (i = 0; i < igraph_vector_size(&s); i++) { + long int to = (long int) floor(VECTOR(s)[i] / no_of_nodes); + long int from = (long int) (VECTOR(s)[i] - ((igraph_real_t)to) * no_of_nodes); + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } + } else if (directed && !loops) { + for (i = 0; i < igraph_vector_size(&s); i++) { + long int to = (long int) floor(VECTOR(s)[i] / no_of_nodes); + long int from = (long int) (VECTOR(s)[i] - ((igraph_real_t)to) * no_of_nodes); + if (from == to) { + to = no_of_nodes - 1; + } + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } + } else if (!directed && loops) { + for (i = 0; i < igraph_vector_size(&s); i++) { + long int to = (long int) floor((sqrt(8 * VECTOR(s)[i] + 1) - 1) / 2); + long int from = (long int) (VECTOR(s)[i] - (((igraph_real_t)to) * (to + 1)) / 2); + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } + } else { /* !directed && !loops */ + for (i = 0; i < igraph_vector_size(&s); i++) { + long int to = (long int) floor((sqrt(8 * VECTOR(s)[i] + 1) + 1) / 2); + long int from = (long int) (VECTOR(s)[i] - (((igraph_real_t)to) * (to - 1)) / 2); + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } + } + + igraph_vector_destroy(&s); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_CHECK(retval = igraph_create(graph, &edges, n, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } + + return retval; +} + +int igraph_erdos_renyi_game_gnm(igraph_t *graph, igraph_integer_t n, igraph_real_t m, + igraph_bool_t directed, igraph_bool_t loops) { + + igraph_integer_t no_of_nodes = n; + igraph_integer_t no_of_edges = (igraph_integer_t) m; + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + igraph_vector_t s = IGRAPH_VECTOR_NULL; + int retval = 0; + + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices", IGRAPH_EINVAL); + } + if (m < 0) { + IGRAPH_ERROR("Invalid number of edges", IGRAPH_EINVAL); + } + + if (m == 0.0 || no_of_nodes <= 1) { + IGRAPH_CHECK(retval = igraph_empty(graph, n, directed)); + } else { + + long int i; + double maxedges = n; + if (directed && loops) { + maxedges *= n; + } else if (directed && !loops) { + maxedges *= (n - 1); + } else if (!directed && loops) { + maxedges *= (n + 1) / 2.0; + } else { + maxedges *= (n - 1) / 2.0; + } + + if (no_of_edges > maxedges) { + IGRAPH_ERROR("Invalid number (too large) of edges", IGRAPH_EINVAL); + } + + if (maxedges == no_of_edges) { + retval = igraph_full(graph, n, directed, loops); + } else { + + long int slen; + + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_CHECK(igraph_random_sample(&s, 0, maxedges - 1, + (igraph_integer_t) no_of_edges)); + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, igraph_vector_size(&s) * 2)); + + slen = igraph_vector_size(&s); + if (directed && loops) { + for (i = 0; i < slen; i++) { + long int to = (long int) floor(VECTOR(s)[i] / no_of_nodes); + long int from = (long int) (VECTOR(s)[i] - ((igraph_real_t)to) * no_of_nodes); + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } + } else if (directed && !loops) { + for (i = 0; i < slen; i++) { + long int from = (long int) floor(VECTOR(s)[i] / (no_of_nodes - 1)); + long int to = (long int) (VECTOR(s)[i] - ((igraph_real_t)from) * (no_of_nodes - 1)); + if (from == to) { + to = no_of_nodes - 1; + } + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } + } else if (!directed && loops) { + for (i = 0; i < slen; i++) { + long int to = (long int) floor((sqrt(8 * VECTOR(s)[i] + 1) - 1) / 2); + long int from = (long int) (VECTOR(s)[i] - (((igraph_real_t)to) * (to + 1)) / 2); + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } + } else { /* !directed && !loops */ + for (i = 0; i < slen; i++) { + long int to = (long int) floor((sqrt(8 * VECTOR(s)[i] + 1) + 1) / 2); + long int from = (long int) (VECTOR(s)[i] - (((igraph_real_t)to) * (to - 1)) / 2); + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } + } + + igraph_vector_destroy(&s); + IGRAPH_FINALLY_CLEAN(1); + retval = igraph_create(graph, &edges, n, directed); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } + } + + return retval; +} + +/** + * \ingroup generators + * \function igraph_erdos_renyi_game + * \brief Generates a random (Erdos-Renyi) graph. + * + * \param graph Pointer to an uninitialized graph object. + * \param type The type of the random graph, possible values: + * \clist + * \cli IGRAPH_ERDOS_RENYI_GNM + * G(n,m) graph, + * m edges are + * selected uniformly randomly in a graph with + * n vertices. + * \cli IGRAPH_ERDOS_RENYI_GNP + * G(n,p) graph, + * every possible edge is included in the graph with + * probability p. + * \endclist + * \param n The number of vertices in the graph. + * \param p_or_m This is the p parameter for + * G(n,p) graphs and the + * m + * parameter for G(n,m) graphs. + * \param directed Logical, whether to generate a directed graph. + * \param loops Logical, whether to generate loops (self) edges. + * \return Error code: + * \c IGRAPH_EINVAL: invalid + * \p type, \p n, + * \p p or \p m + * parameter. + * \c IGRAPH_ENOMEM: there is not enough + * memory for the operation. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_barabasi_game(), \ref igraph_growing_random_game() + * + * \example examples/simple/igraph_erdos_renyi_game.c + */ + +int igraph_erdos_renyi_game(igraph_t *graph, igraph_erdos_renyi_t type, + igraph_integer_t n, igraph_real_t p_or_m, + igraph_bool_t directed, igraph_bool_t loops) { + int retval = 0; + if (type == IGRAPH_ERDOS_RENYI_GNP) { + retval = igraph_erdos_renyi_game_gnp(graph, n, p_or_m, directed, loops); + } else if (type == IGRAPH_ERDOS_RENYI_GNM) { + retval = igraph_erdos_renyi_game_gnm(graph, n, p_or_m, directed, loops); + } else { + IGRAPH_ERROR("Invalid type", IGRAPH_EINVAL); + } + + return retval; +} + +int igraph_degree_sequence_game_simple(igraph_t *graph, + const igraph_vector_t *out_seq, + const igraph_vector_t *in_seq); + +int igraph_degree_sequence_game_simple(igraph_t *graph, + const igraph_vector_t *out_seq, + const igraph_vector_t *in_seq) { + + long int outsum = 0, insum = 0; + igraph_bool_t directed = (in_seq != 0 && igraph_vector_size(in_seq) != 0); + igraph_bool_t degseq_ok; + long int no_of_nodes, no_of_edges; + long int *bag1 = 0, *bag2 = 0; + long int bagp1 = 0, bagp2 = 0; + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + long int i, j; + + IGRAPH_CHECK(igraph_is_degree_sequence(out_seq, in_seq, °seq_ok)); + if (!degseq_ok) { + IGRAPH_ERROR(in_seq ? "No directed graph can realize the given degree sequences" : + "No undirected graph can realize the given degree sequence", IGRAPH_EINVAL); + } + + outsum = (long int) igraph_vector_sum(out_seq); + if (directed) { + insum = (long int) igraph_vector_sum(in_seq); + } + + no_of_nodes = igraph_vector_size(out_seq); + no_of_edges = directed ? outsum : outsum / 2; + + bag1 = igraph_Calloc(outsum, long int); + if (bag1 == 0) { + IGRAPH_ERROR("degree sequence game (simple)", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, bag1); + + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < VECTOR(*out_seq)[i]; j++) { + bag1[bagp1++] = i; + } + } + if (directed) { + bag2 = igraph_Calloc(insum, long int); + if (bag2 == 0) { + IGRAPH_ERROR("degree sequence game (simple)", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, bag2); + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < VECTOR(*in_seq)[i]; j++) { + bag2[bagp2++] = i; + } + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges * 2)); + + RNG_BEGIN(); + + if (directed) { + for (i = 0; i < no_of_edges; i++) { + long int from = RNG_INTEGER(0, bagp1 - 1); + long int to = RNG_INTEGER(0, bagp2 - 1); + igraph_vector_push_back(&edges, bag1[from]); /* safe, already reserved */ + igraph_vector_push_back(&edges, bag2[to]); /* ditto */ + bag1[from] = bag1[bagp1 - 1]; + bag2[to] = bag2[bagp2 - 1]; + bagp1--; bagp2--; + } + } else { + for (i = 0; i < no_of_edges; i++) { + long int from = RNG_INTEGER(0, bagp1 - 1); + long int to; + igraph_vector_push_back(&edges, bag1[from]); /* safe, already reserved */ + bag1[from] = bag1[bagp1 - 1]; + bagp1--; + to = RNG_INTEGER(0, bagp1 - 1); + igraph_vector_push_back(&edges, bag1[to]); /* ditto */ + bag1[to] = bag1[bagp1 - 1]; + bagp1--; + } + } + + RNG_END(); + + igraph_Free(bag1); + IGRAPH_FINALLY_CLEAN(1); + if (directed) { + igraph_Free(bag2); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) no_of_nodes, + directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +int igraph_degree_sequence_game_no_multiple_undirected( + igraph_t *graph, const igraph_vector_t *seq) { + + igraph_vector_t stubs = IGRAPH_VECTOR_NULL; + igraph_vector_int_t *neis; + igraph_vector_t residual_degrees = IGRAPH_VECTOR_NULL; + igraph_set_t incomplete_vertices; + igraph_adjlist_t al; + igraph_bool_t finished, failed; + igraph_integer_t from, to, dummy; + long int i, j, k; + long int no_of_nodes, outsum = 0; + igraph_bool_t degseq_ok; + + IGRAPH_CHECK(igraph_is_graphical_degree_sequence(seq, 0, °seq_ok)); + if (!degseq_ok) { + IGRAPH_ERROR("No simple undirected graph can realize the given degree sequence", + IGRAPH_EINVAL); + } + + outsum = (long int) igraph_vector_sum(seq); + no_of_nodes = igraph_vector_size(seq); + + /* Allocate required data structures */ + IGRAPH_CHECK(igraph_adjlist_init_empty(&al, (igraph_integer_t) no_of_nodes)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + IGRAPH_VECTOR_INIT_FINALLY(&stubs, 0); + IGRAPH_CHECK(igraph_vector_reserve(&stubs, outsum)); + IGRAPH_VECTOR_INIT_FINALLY(&residual_degrees, no_of_nodes); + IGRAPH_CHECK(igraph_set_init(&incomplete_vertices, 0)); + IGRAPH_FINALLY(igraph_set_destroy, &incomplete_vertices); + + /* Start the RNG */ + RNG_BEGIN(); + + /* Outer loop; this will try to construct a graph several times from scratch + * until it finally succeeds. */ + finished = 0; + while (!finished) { + IGRAPH_ALLOW_INTERRUPTION(); + + /* Be optimistic :) */ + failed = 0; + + /* Clear the adjacency list to get rid of the previous attempt (if any) */ + igraph_adjlist_clear(&al); + + /* Initialize the residual degrees from the degree sequence */ + IGRAPH_CHECK(igraph_vector_update(&residual_degrees, seq)); + + /* While there are some unconnected stubs left... */ + while (!finished && !failed) { + /* Construct the initial stub vector */ + igraph_vector_clear(&stubs); + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < VECTOR(residual_degrees)[i]; j++) { + igraph_vector_push_back(&stubs, i); + } + } + + /* Clear the skipped stub counters and the set of incomplete vertices */ + igraph_vector_null(&residual_degrees); + igraph_set_clear(&incomplete_vertices); + + /* Shuffle the stubs in-place */ + igraph_vector_shuffle(&stubs); + + /* Connect the stubs where possible */ + k = igraph_vector_size(&stubs); + for (i = 0; i < k; ) { + from = (igraph_integer_t) VECTOR(stubs)[i++]; + to = (igraph_integer_t) VECTOR(stubs)[i++]; + + if (from > to) { + dummy = from; from = to; to = dummy; + } + + neis = igraph_adjlist_get(&al, from); + if (from == to || igraph_vector_int_binsearch(neis, to, &j)) { + /* Edge exists already */ + VECTOR(residual_degrees)[from]++; + VECTOR(residual_degrees)[to]++; + IGRAPH_CHECK(igraph_set_add(&incomplete_vertices, from)); + IGRAPH_CHECK(igraph_set_add(&incomplete_vertices, to)); + } else { + /* Insert the edge */ + IGRAPH_CHECK(igraph_vector_int_insert(neis, j, to)); + } + } + + finished = igraph_set_empty(&incomplete_vertices); + + if (!finished) { + /* We are not done yet; check if the remaining stubs are feasible. This + * is done by enumerating all possible pairs and checking whether at + * least one feasible pair is found. */ + i = 0; + failed = 1; + while (failed && igraph_set_iterate(&incomplete_vertices, &i, &from)) { + j = 0; + while (igraph_set_iterate(&incomplete_vertices, &j, &to)) { + if (from == to) { + /* This is used to ensure that each pair is checked once only */ + break; + } + if (from > to) { + dummy = from; from = to; to = dummy; + } + neis = igraph_adjlist_get(&al, from); + if (!igraph_vector_int_binsearch(neis, to, 0)) { + /* Found a suitable pair, so we can continue */ + failed = 0; + break; + } + } + } + } + } + } + + /* Finish the RNG */ + RNG_END(); + + /* Clean up */ + igraph_set_destroy(&incomplete_vertices); + igraph_vector_destroy(&residual_degrees); + igraph_vector_destroy(&stubs); + IGRAPH_FINALLY_CLEAN(3); + + /* Create the graph. We cannot use IGRAPH_ALL here for undirected graphs + * because we did not add edges in both directions in the adjacency list. + * We will use igraph_to_undirected in an extra step. */ + IGRAPH_CHECK(igraph_adjlist(graph, &al, IGRAPH_OUT, 1)); + IGRAPH_CHECK(igraph_to_undirected(graph, IGRAPH_TO_UNDIRECTED_EACH, 0)); + + /* Clear the adjacency list */ + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +int igraph_degree_sequence_game_no_multiple_directed(igraph_t *graph, + const igraph_vector_t *out_seq, const igraph_vector_t *in_seq) { + igraph_adjlist_t al; + igraph_bool_t deg_seq_ok, failed, finished; + igraph_vector_t in_stubs = IGRAPH_VECTOR_NULL; + igraph_vector_t out_stubs = IGRAPH_VECTOR_NULL; + igraph_vector_int_t *neis; + igraph_vector_t residual_in_degrees = IGRAPH_VECTOR_NULL; + igraph_vector_t residual_out_degrees = IGRAPH_VECTOR_NULL; + igraph_set_t incomplete_in_vertices; + igraph_set_t incomplete_out_vertices; + igraph_integer_t from, to; + long int i, j, k; + long int no_of_nodes, outsum; + + IGRAPH_CHECK(igraph_is_graphical_degree_sequence(out_seq, in_seq, °_seq_ok)); + if (!deg_seq_ok) { + IGRAPH_ERROR("No simple directed graph can realize the given degree sequence", + IGRAPH_EINVAL); + } + + outsum = (long int) igraph_vector_sum(out_seq); + no_of_nodes = igraph_vector_size(out_seq); + + /* Allocate required data structures */ + IGRAPH_CHECK(igraph_adjlist_init_empty(&al, (igraph_integer_t) no_of_nodes)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + IGRAPH_VECTOR_INIT_FINALLY(&out_stubs, 0); + IGRAPH_CHECK(igraph_vector_reserve(&out_stubs, outsum)); + IGRAPH_VECTOR_INIT_FINALLY(&in_stubs, 0); + IGRAPH_CHECK(igraph_vector_reserve(&in_stubs, outsum)); + IGRAPH_VECTOR_INIT_FINALLY(&residual_out_degrees, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&residual_in_degrees, no_of_nodes); + IGRAPH_CHECK(igraph_set_init(&incomplete_out_vertices, 0)); + IGRAPH_FINALLY(igraph_set_destroy, &incomplete_out_vertices); + IGRAPH_CHECK(igraph_set_init(&incomplete_in_vertices, 0)); + IGRAPH_FINALLY(igraph_set_destroy, &incomplete_in_vertices); + + /* Start the RNG */ + RNG_BEGIN(); + + /* Outer loop; this will try to construct a graph several times from scratch + * until it finally succeeds. */ + finished = 0; + while (!finished) { + IGRAPH_ALLOW_INTERRUPTION(); + + /* Be optimistic :) */ + failed = 0; + + /* Clear the adjacency list to get rid of the previous attempt (if any) */ + igraph_adjlist_clear(&al); + + /* Initialize the residual degrees from the degree sequences */ + IGRAPH_CHECK(igraph_vector_update(&residual_out_degrees, out_seq)); + IGRAPH_CHECK(igraph_vector_update(&residual_in_degrees, in_seq)); + + /* While there are some unconnected stubs left... */ + while (!finished && !failed) { + /* Construct the initial stub vectors */ + igraph_vector_clear(&out_stubs); + igraph_vector_clear(&in_stubs); + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < VECTOR(residual_out_degrees)[i]; j++) { + igraph_vector_push_back(&out_stubs, i); + } + for (j = 0; j < VECTOR(residual_in_degrees)[i]; j++) { + igraph_vector_push_back(&in_stubs, i); + } + } + + /* Clear the skipped stub counters and the set of incomplete vertices */ + igraph_vector_null(&residual_out_degrees); + igraph_vector_null(&residual_in_degrees); + igraph_set_clear(&incomplete_out_vertices); + igraph_set_clear(&incomplete_in_vertices); + outsum = 0; + + /* Shuffle the out-stubs in-place */ + igraph_vector_shuffle(&out_stubs); + + /* Connect the stubs where possible */ + k = igraph_vector_size(&out_stubs); + for (i = 0; i < k; i++) { + from = (igraph_integer_t) VECTOR(out_stubs)[i]; + to = (igraph_integer_t) VECTOR(in_stubs)[i]; + + neis = igraph_adjlist_get(&al, from); + if (from == to || igraph_vector_int_binsearch(neis, to, &j)) { + /* Edge exists already */ + VECTOR(residual_out_degrees)[from]++; + VECTOR(residual_in_degrees)[to]++; + IGRAPH_CHECK(igraph_set_add(&incomplete_out_vertices, from)); + IGRAPH_CHECK(igraph_set_add(&incomplete_in_vertices, to)); + } else { + /* Insert the edge */ + IGRAPH_CHECK(igraph_vector_int_insert(neis, j, to)); + } + } + + /* Are we finished? */ + finished = igraph_set_empty(&incomplete_out_vertices); + + if (!finished) { + /* We are not done yet; check if the remaining stubs are feasible. This + * is done by enumerating all possible pairs and checking whether at + * least one feasible pair is found. */ + i = 0; + failed = 1; + while (failed && igraph_set_iterate(&incomplete_out_vertices, &i, &from)) { + j = 0; + while (igraph_set_iterate(&incomplete_in_vertices, &j, &to)) { + neis = igraph_adjlist_get(&al, from); + if (from != to && !igraph_vector_int_binsearch(neis, to, 0)) { + /* Found a suitable pair, so we can continue */ + failed = 0; + break; + } + } + } + } + } + } + + /* Finish the RNG */ + RNG_END(); + + /* Clean up */ + igraph_set_destroy(&incomplete_in_vertices); + igraph_set_destroy(&incomplete_out_vertices); + igraph_vector_destroy(&residual_in_degrees); + igraph_vector_destroy(&residual_out_degrees); + igraph_vector_destroy(&in_stubs); + igraph_vector_destroy(&out_stubs); + IGRAPH_FINALLY_CLEAN(6); + + /* Create the graph */ + IGRAPH_CHECK(igraph_adjlist(graph, &al, IGRAPH_OUT, 1)); + + /* Clear the adjacency list */ + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +int igraph_degree_sequence_game_no_multiple_undirected_uniform(igraph_t *graph, const igraph_vector_t *degseq) { + igraph_vector_int_t stubs; + igraph_vector_t edges; + igraph_bool_t degseq_ok; + igraph_vector_ptr_t adjlist; + long i, j, k; + long vcount, ecount, stub_count; + + IGRAPH_CHECK(igraph_is_graphical_degree_sequence(degseq, 0, °seq_ok)); + if (!degseq_ok) { + IGRAPH_ERROR("No simple undirected graph can realize the given degree sequence", IGRAPH_EINVAL); + } + + stub_count = (long) igraph_vector_sum(degseq); + ecount = stub_count / 2; + vcount = igraph_vector_size(degseq); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&stubs, stub_count); + IGRAPH_VECTOR_INIT_FINALLY(&edges, stub_count); + + k = 0; + for (i = 0; i < vcount; ++i) { + long deg = (long) VECTOR(*degseq)[i]; + for (j = 0; j < deg; ++j) { + VECTOR(stubs)[k++] = i; + } + } + + IGRAPH_CHECK(igraph_vector_ptr_init(&adjlist, vcount)); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&adjlist, igraph_set_destroy); + for (i = 0; i < vcount; ++i) { + igraph_set_t *set = igraph_malloc(sizeof(igraph_set_t)); + if (! set) { + IGRAPH_ERROR("Out of memory", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_set_init(set, 0)); + VECTOR(adjlist)[i] = set; + IGRAPH_CHECK(igraph_set_reserve(set, (long) VECTOR(*degseq)[i])); + } + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &adjlist); + + RNG_BEGIN(); + + for (;;) { + igraph_bool_t success = 1; + IGRAPH_CHECK(igraph_vector_int_shuffle(&stubs)); + + /* optimization: we do an initial pass looking for self-loops */ + for (i = 0; i < ecount; ++i) { + igraph_integer_t from = VECTOR(stubs)[2 * i]; + igraph_integer_t to = VECTOR(stubs)[2 * i + 1]; + + /* loop edge, fail */ + if (to == from) { + success = 0; + break; + } + } + + IGRAPH_ALLOW_INTERRUPTION(); + + if (! success) + continue; + + for (i = 0; i < ecount; ++i) { + igraph_integer_t from = VECTOR(stubs)[2 * i]; + igraph_integer_t to = VECTOR(stubs)[2 * i + 1]; + + /* multi-edge, fail */ + if (igraph_set_contains((igraph_set_t *) VECTOR(adjlist)[to], from)) { + success = 0; + break; + } + + /* sets are already reserved */ + igraph_set_add((igraph_set_t *) VECTOR(adjlist)[to], from); + igraph_set_add((igraph_set_t *) VECTOR(adjlist)[from], to); + + /* register edge */ + VECTOR(edges)[2 * i] = from; + VECTOR(edges)[2 * i + 1] = to; + } + + if (success) { + break; + } + + IGRAPH_ALLOW_INTERRUPTION(); + + for (j = 0; j < vcount; ++j) { + igraph_set_clear((igraph_set_t *) VECTOR(adjlist)[j]); + } + } + + RNG_END(); + + igraph_vector_ptr_destroy_all(&adjlist); + igraph_vector_int_destroy(&stubs); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(graph, &edges, vcount, /* directed = */ 0)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +int igraph_degree_sequence_game_no_multiple_directed_uniform( + igraph_t *graph, const igraph_vector_t *out_deg, const igraph_vector_t *in_deg) { + igraph_vector_int_t out_stubs, in_stubs; + igraph_vector_t edges; + igraph_bool_t degseq_ok; + igraph_vector_ptr_t adjlist; + long i, j, k, l; + long vcount, ecount; + + IGRAPH_CHECK(igraph_is_graphical_degree_sequence(out_deg, in_deg, °seq_ok)); + if (!degseq_ok) { + IGRAPH_ERROR("No simple directed graph can realize the given degree sequence", IGRAPH_EINVAL); + } + + ecount = (long) igraph_vector_sum(out_deg); + vcount = igraph_vector_size(out_deg); + + IGRAPH_VECTOR_INT_INIT_FINALLY(&out_stubs, ecount); + IGRAPH_VECTOR_INT_INIT_FINALLY(&in_stubs, ecount); + IGRAPH_VECTOR_INIT_FINALLY(&edges, 2 * ecount); + + k = 0; l = 0; + for (i = 0; i < vcount; ++i) { + long dout, din; + + dout = (long) VECTOR(*out_deg)[i]; + for (j = 0; j < dout; ++j) { + VECTOR(out_stubs)[k++] = i; + } + + din = (long) VECTOR(*in_deg)[i]; + for (j = 0; j < din; ++j) { + VECTOR(in_stubs)[l++] = i; + } + } + + IGRAPH_CHECK(igraph_vector_ptr_init(&adjlist, vcount)); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&adjlist, igraph_set_destroy); + for (i = 0; i < vcount; ++i) { + igraph_set_t *set = igraph_malloc(sizeof(igraph_set_t)); + if (! set) { + IGRAPH_ERROR("Out of memory", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_set_init(set, 0)); + VECTOR(adjlist)[i] = set; + IGRAPH_CHECK(igraph_set_reserve(set, (long) VECTOR(*out_deg)[i])); + } + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &adjlist); + + RNG_BEGIN(); + + for (;;) { + igraph_bool_t success = 1; + IGRAPH_CHECK(igraph_vector_int_shuffle(&out_stubs)); + + /* optimization: we do an initial pass looking for self-loops */ + for (i = 0; i < ecount; ++i) { + igraph_integer_t from = VECTOR(out_stubs)[i]; + igraph_integer_t to = VECTOR(in_stubs)[i]; + + /* loop edge, fail */ + if (to == from) { + success = 0; + break; + } + } + + IGRAPH_ALLOW_INTERRUPTION(); + + if (! success) + continue; + + for (i = 0; i < ecount; ++i) { + igraph_integer_t from = VECTOR(out_stubs)[i]; + igraph_integer_t to = VECTOR(in_stubs)[i]; + igraph_set_t *set; + + /* multi-edge, fail */ + set = (igraph_set_t *) VECTOR(adjlist)[from]; + if (igraph_set_contains(set, to)) { + success = 0; + break; + } + + /* sets are already reserved */ + igraph_set_add(set, to); + + /* register edge */ + VECTOR(edges)[2 * i] = from; + VECTOR(edges)[2 * i + 1] = to; + } + + if (success) { + break; + } + + IGRAPH_ALLOW_INTERRUPTION(); + + for (j = 0; j < vcount; ++j) { + igraph_set_clear((igraph_set_t *) VECTOR(adjlist)[j]); + } + } + + RNG_END(); + + igraph_vector_ptr_destroy_all(&adjlist); + igraph_vector_int_destroy(&out_stubs); + igraph_vector_int_destroy(&in_stubs); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_create(graph, &edges, vcount, /* directed = */ 1)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* This is in gengraph_mr-connected.cpp */ + +int igraph_degree_sequence_game_vl(igraph_t *graph, + const igraph_vector_t *out_seq, + const igraph_vector_t *in_seq); +/** + * \ingroup generators + * \function igraph_degree_sequence_game + * \brief Generates a random graph with a given degree sequence + * + * \param graph Pointer to an uninitialized graph object. + * \param out_deg The degree sequence for an undirected graph (if + * \p in_seq is \c NULL or of length zero), or the out-degree + * sequence of a directed graph (if \p in_deq is not + * of length zero). + * \param in_deg It is either a zero-length vector or + * \c NULL (if an undirected + * graph is generated), or the in-degree sequence. + * \param method The method to generate the graph. Possible values: + * \clist + * \cli IGRAPH_DEGSEQ_SIMPLE + * This method implements the configuration model. + * For undirected graphs, it puts all vertex IDs in a bag + * such that the multiplicity of a vertex in the bag is the same as + * its degree. Then it draws pairs from the bag until the bag becomes + * empty. This method may generate both loop (self) edges and multiple + * edges. For directed graphs, the algorithm is basically the same, + * but two separate bags are used for the in- and out-degrees. + * Undirected graphs are generated with probability proportional to + * (\prod_{i<j} A_{ij} ! \prod_i A_{ii} !!)^{-1}, + * where \c A denotes the adjacency matrix and !! denotes + * the double factorial. Here \c A is assumed to have twice the number of + * self-loops on its diagonal. + * The corresponding expression for directed graphs is + * (\prod_{i,j} A_{ij}!)^{-1}. + * Thus the probability of all simple graphs (which only have 0s and 1s + * in the adjacency matrix) is the same, while that of + * non-simple ones depends on their edge and self-loop multiplicities. + * \cli IGRAPH_DEGSEQ_SIMPLE_NO_MULTIPLE + * This method generates simple graphs. + * It is similar to \c IGRAPH_DEGSEQ_SIMPLE + * but tries to avoid multiple and loop edges and restarts the + * generation from scratch if it gets stuck. It can generate all simple + * realizations of a degree sequence, but it is not guaranteed + * to sample them uniformly. This method is relatively fast and it will + * eventually succeed if the provided degree sequence is graphical, + * but there is no upper bound on the number of iterations. + * \cli IGRAPH_DEGSEQ_SIMPLE_NO_MULTIPLE_UNIFORM + * This method is identical to \c IGRAPH_DEGSEQ_SIMPLE, but if the + * generated graph is not simple, it rejects it and re-starts the + * generation. It generates all simple graphs with the same probability. + * \cli IGRAPH_DEGSEQ_VL + * This method samples undirected connected graphs approximately + * uniformly. It is a Monte Carlo method based on degree-preserving + * edge swaps. + * This generator should be favoured if undirected and connected + * graphs are to be generated and execution time is not a concern. + * igraph uses the original implementation of Fabien Viger; for the algorithm, + * see https://www-complexnetworks.lip6.fr/~latapy/FV/generation.html + * and the paper https://arxiv.org/abs/cs/0502085 + * \endclist + * \return Error code: + * \c IGRAPH_ENOMEM: there is not enough + * memory to perform the operation. + * \c IGRAPH_EINVAL: invalid method parameter, or + * invalid in- and/or out-degree vectors. The degree vectors + * should be non-negative, \p out_deg should sum + * up to an even integer for undirected graphs; the length + * and sum of \p out_deg and + * \p in_deg + * should match for directed graphs. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number of edges + * for \c IGRAPH_DEGSEQ_SIMPLE. The time complexity of the + * other modes is not known. + * + * \sa \ref igraph_barabasi_game(), \ref igraph_erdos_renyi_game(), + * \ref igraph_is_degree_sequence(), + * \ref igraph_is_graphical_degree_sequence() + * + * \example examples/simple/igraph_degree_sequence_game.c + */ + +int igraph_degree_sequence_game(igraph_t *graph, const igraph_vector_t *out_deg, + const igraph_vector_t *in_deg, + igraph_degseq_t method) { + if (in_deg && igraph_vector_empty(in_deg) && !igraph_vector_empty(out_deg)) { + in_deg = 0; + } + + switch (method) { + case IGRAPH_DEGSEQ_SIMPLE: + return igraph_degree_sequence_game_simple(graph, out_deg, in_deg); + + case IGRAPH_DEGSEQ_VL: + return igraph_degree_sequence_game_vl(graph, out_deg, in_deg); + + case IGRAPH_DEGSEQ_SIMPLE_NO_MULTIPLE: + if (in_deg == 0) { + return igraph_degree_sequence_game_no_multiple_undirected(graph, out_deg); + } else { + return igraph_degree_sequence_game_no_multiple_directed(graph, out_deg, in_deg); + } + + case IGRAPH_DEGSEQ_SIMPLE_NO_MULTIPLE_UNIFORM: + if (in_deg == 0) { + return igraph_degree_sequence_game_no_multiple_undirected_uniform(graph, out_deg); + } else { + return igraph_degree_sequence_game_no_multiple_directed_uniform(graph, out_deg, in_deg); + } + + default: + IGRAPH_ERROR("Invalid degree sequence game method", IGRAPH_EINVAL); + } +} + +/** + * \ingroup generators + * \function igraph_growing_random_game + * \brief Generates a growing random graph. + * + * + * This function simulates a growing random graph. In each discrete + * time step a new vertex is added and a number of new edges are also + * added. These graphs are known to be different from standard (not + * growing) random graphs. + * \param graph Uninitialized graph object. + * \param n The number of vertices in the graph. + * \param m The number of edges to add in a time step (ie. after + * adding a vertex). + * \param directed Boolean, whether to generate a directed graph. + * \param citation Boolean, if \c TRUE, the edges always + * originate from the most recently added vertex. + * \return Error code: + * \c IGRAPH_EINVAL: invalid + * \p n or \p m + * parameter. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges. + * + * \example examples/simple/igraph_growing_random_game.c + */ +int igraph_growing_random_game(igraph_t *graph, igraph_integer_t n, + igraph_integer_t m, igraph_bool_t directed, + igraph_bool_t citation) { + + long int no_of_nodes = n; + long int no_of_neighbors = m; + long int no_of_edges; + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + + long int resp = 0; + + long int i, j; + + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices", IGRAPH_EINVAL); + } + if (m < 0) { + IGRAPH_ERROR("Invalid number of edges per step (m)", IGRAPH_EINVAL); + } + + no_of_edges = (no_of_nodes - 1) * no_of_neighbors; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + + RNG_BEGIN(); + + for (i = 1; i < no_of_nodes; i++) { + for (j = 0; j < no_of_neighbors; j++) { + if (citation) { + long int to = RNG_INTEGER(0, i - 1); + VECTOR(edges)[resp++] = i; + VECTOR(edges)[resp++] = to; + } else { + long int from = RNG_INTEGER(0, i); + long int to = RNG_INTEGER(1, i); + VECTOR(edges)[resp++] = from; + VECTOR(edges)[resp++] = to; + } + } + } + + RNG_END(); + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_callaway_traits_game + * \brief Simulate a growing network with vertex types. + * + * + * The different types of vertices prefer to connect other types of + * vertices with a given probability. + * + * + * The simulation goes like this: in each discrete time step a new + * vertex is added to the graph. The type of this vertex is generated + * based on \p type_dist. Then two vertices are selected uniformly + * randomly from the graph. The probability that they will be + * connected depends on the types of these vertices and is taken from + * \p pref_matrix. Then another two vertices are selected and this is + * repeated \p edges_per_step times in each time step. + * \param graph Pointer to an uninitialized graph. + * \param nodes The number of nodes in the graph. + * \param types Number of node types. + * \param edges_per_step The number of edges to be add per time step. + * \param type_dist Vector giving the distribution of the vertex + * types. + * \param pref_matrix Matrix giving the connection probabilities for + * the vertex types. + * \param directed Logical, whether to generate a directed graph. + * \return Error code. + * + * Added in version 0.2. + * + * Time complexity: O(|V|e*log(|V|)), |V| is the number of vertices, e + * is \p edges_per_step. + */ + +int igraph_callaway_traits_game (igraph_t *graph, igraph_integer_t nodes, + igraph_integer_t types, igraph_integer_t edges_per_step, + igraph_vector_t *type_dist, + igraph_matrix_t *pref_matrix, + igraph_bool_t directed) { + long int i, j; + igraph_vector_t edges; + igraph_vector_t cumdist; + igraph_real_t maxcum; + igraph_vector_t nodetypes; + + /* TODO: parameter checks */ + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&cumdist, types + 1); + IGRAPH_VECTOR_INIT_FINALLY(&nodetypes, nodes); + + VECTOR(cumdist)[0] = 0; + for (i = 0; i < types; i++) { + VECTOR(cumdist)[i + 1] = VECTOR(cumdist)[i] + VECTOR(*type_dist)[i]; + } + maxcum = igraph_vector_tail(&cumdist); + + RNG_BEGIN(); + + for (i = 0; i < nodes; i++) { + igraph_real_t uni = RNG_UNIF(0, maxcum); + long int type; + igraph_vector_binsearch(&cumdist, uni, &type); + VECTOR(nodetypes)[i] = type - 1; + } + + for (i = 1; i < nodes; i++) { + for (j = 0; j < edges_per_step; j++) { + long int node1 = RNG_INTEGER(0, i); + long int node2 = RNG_INTEGER(0, i); + long int type1 = (long int) VECTOR(nodetypes)[node1]; + long int type2 = (long int) VECTOR(nodetypes)[node2]; + /* printf("unif: %f, %f, types: %li, %li\n", uni1, uni2, type1, type2); */ + if (RNG_UNIF01() < MATRIX(*pref_matrix, type1, type2)) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, node1)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, node2)); + } + } + } + + RNG_END(); + + igraph_vector_destroy(&nodetypes); + igraph_vector_destroy(&cumdist); + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_establishment_game + * \brief Generates a graph with a simple growing model with vertex types. + * + * + * The simulation goes like this: a single vertex is added at each + * time step. This new vertex tries to connect to \p k vertices in the + * graph. The probability that such a connection is realized depends + * on the types of the vertices involved. + * + * \param graph Pointer to an uninitialized graph. + * \param nodes The number of vertices in the graph. + * \param types The number of vertex types. + * \param k The number of connections tried in each time step. + * \param type_dist Vector giving the distribution of vertex types. + * \param pref_matrix Matrix giving the connection probabilities for + * different vertex types. + * \param directed Logical, whether to generate a directed graph. + * \return Error code. + * + * Added in version 0.2. + * + * Time complexity: O(|V|*k*log(|V|)), |V| is the number of vertices + * and k is the \p k parameter. + */ + +int igraph_establishment_game(igraph_t *graph, igraph_integer_t nodes, + igraph_integer_t types, igraph_integer_t k, + igraph_vector_t *type_dist, + igraph_matrix_t *pref_matrix, + igraph_bool_t directed) { + + long int i, j; + igraph_vector_t edges; + igraph_vector_t cumdist; + igraph_vector_t potneis; + igraph_real_t maxcum; + igraph_vector_t nodetypes; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&cumdist, types + 1); + IGRAPH_VECTOR_INIT_FINALLY(&potneis, k); + IGRAPH_VECTOR_INIT_FINALLY(&nodetypes, nodes); + + VECTOR(cumdist)[0] = 0; + for (i = 0; i < types; i++) { + VECTOR(cumdist)[i + 1] = VECTOR(cumdist)[i] + VECTOR(*type_dist)[i]; + } + maxcum = igraph_vector_tail(&cumdist); + + RNG_BEGIN(); + + for (i = 0; i < nodes; i++) { + igraph_real_t uni = RNG_UNIF(0, maxcum); + long int type; + igraph_vector_binsearch(&cumdist, uni, &type); + VECTOR(nodetypes)[i] = type - 1; + } + + for (i = k; i < nodes; i++) { + long int type1 = (long int) VECTOR(nodetypes)[i]; + igraph_random_sample(&potneis, 0, i - 1, k); + for (j = 0; j < k; j++) { + long int type2 = (long int) VECTOR(nodetypes)[(long int)VECTOR(potneis)[j]]; + if (RNG_UNIF01() < MATRIX(*pref_matrix, type1, type2)) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, VECTOR(potneis)[j])); + } + } + } + + RNG_END(); + + igraph_vector_destroy(&nodetypes); + igraph_vector_destroy(&potneis); + igraph_vector_destroy(&cumdist); + IGRAPH_FINALLY_CLEAN(3); + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_recent_degree_game + * \brief Stochastic graph generator based on the number of incident edges a node has gained recently + * + * \param graph Pointer to an uninitialized graph object. + * \param n The number of vertices in the graph, this is the same as + * the number of time steps. + * \param power The exponent, the probability that a node gains a + * new edge is proportional to the number of edges it has + * gained recently (in the last \p window time steps) to \p + * power. + * \param window Integer constant, the size of the time window to use + * to count the number of recent edges. + * \param m Integer constant, the number of edges to add per time + * step if the \p outseq parameter is a null pointer or a + * zero-length vector. + * \param outseq The number of edges to add in each time step. This + * argument is ignored if it is a null pointer or a zero length + * vector, is this case the constant \p m parameter is used. + * \param outpref Logical constant, if true the edges originated by a + * vertex also count as recent incident edges. It is false in + * most cases. + * \param zero_appeal Constant giving the attractiveness of the + * vertices which haven't gained any edge recently. + * \param directed Logical constant, whether to generate a directed + * graph. + * \return Error code. + * + * Time complexity: O(|V|*log(|V|)+|E|), |V| is the number of + * vertices, |E| is the number of edges in the graph. + * + */ + +int igraph_recent_degree_game(igraph_t *graph, igraph_integer_t n, + igraph_real_t power, + igraph_integer_t window, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t zero_appeal, + igraph_bool_t directed) { + + long int no_of_nodes = n; + long int no_of_neighbors = m; + long int no_of_edges; + igraph_vector_t edges; + long int i, j; + igraph_psumtree_t sumtree; + long int edgeptr = 0; + igraph_vector_t degree; + long int time_window = window; + igraph_dqueue_t history; + + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices", IGRAPH_EINVAL); + } + if (outseq != 0 && igraph_vector_size(outseq) != 0 && igraph_vector_size(outseq) != n) { + IGRAPH_ERROR("Invalid out degree sequence length", IGRAPH_EINVAL); + } + if ( (outseq == 0 || igraph_vector_size(outseq) == 0) && m < 0) { + IGRAPH_ERROR("Invalid out degree", IGRAPH_EINVAL); + } + + if (outseq == 0 || igraph_vector_size(outseq) == 0) { + no_of_neighbors = m; + no_of_edges = (no_of_nodes - 1) * no_of_neighbors; + } else { + no_of_edges = 0; + for (i = 1; i < igraph_vector_size(outseq); i++) { + no_of_edges += VECTOR(*outseq)[i]; + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_CHECK(igraph_dqueue_init(&history, + time_window * (no_of_neighbors + 1) + 10)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &history); + + RNG_BEGIN(); + + /* first node */ + igraph_psumtree_update(&sumtree, 0, zero_appeal); + igraph_dqueue_push(&history, -1); + + /* and the rest */ + for (i = 1; i < no_of_nodes; i++) { + igraph_real_t sum; + long int to; + if (outseq != 0 && igraph_vector_size(outseq) != 0) { + no_of_neighbors = (long int) VECTOR(*outseq)[i]; + } + + if (i >= time_window) { + while ((j = (long int) igraph_dqueue_pop(&history)) != -1) { + VECTOR(degree)[j] -= 1; + igraph_psumtree_update(&sumtree, j, + pow(VECTOR(degree)[j], power) + zero_appeal); + } + } + + sum = igraph_psumtree_sum(&sumtree); + for (j = 0; j < no_of_neighbors; j++) { + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + VECTOR(degree)[to]++; + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = to; + igraph_dqueue_push(&history, to); + } + igraph_dqueue_push(&history, -1); + + /* update probabilities */ + for (j = 0; j < no_of_neighbors; j++) { + long int nn = (long int) VECTOR(edges)[edgeptr - 2 * j - 1]; + igraph_psumtree_update(&sumtree, nn, + pow(VECTOR(degree)[nn], power) + zero_appeal); + } + if (outpref) { + VECTOR(degree)[i] += no_of_neighbors; + igraph_psumtree_update(&sumtree, i, + pow(VECTOR(degree)[i], power) + zero_appeal); + } else { + igraph_psumtree_update(&sumtree, i, zero_appeal); + } + } + + RNG_END(); + + igraph_dqueue_destroy(&history); + igraph_psumtree_destroy(&sumtree); + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_barabasi_aging_game + * \brief Preferential attachment with aging of vertices + * + * + * In this game, the probability that a node gains a new edge is + * given by its (in-)degree (k) and age (l). This probability has a + * degree dependent component multiplied by an age dependent + * component. The degree dependent part is: \p deg_coef times k to the + * power of \p pa_exp plus \p zero_deg_appeal; and the age dependent + * part is \p age_coef times l to the power of \p aging_exp plus \p + * zero_age_appeal. + * + * + * The age is based on the number of vertices in the + * network and the \p aging_bin argument: vertices grew one unit older + * after each \p aging_bin vertices added to the network. + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the graph. + * \param m The number of edges to add in each time step. If the \p + * outseq argument is not a null vector and not a zero-length + * vector. + * \param outseq The number of edges to add in each time step. If it + * is a null pointer or a zero-length vector then it is ignored + * and the \p m argument is used instead. + * \param outpref Logical constant, whether the edges + * initiated by a vertex contribute to the probability to gain + * a new edge. + * \param pa_exp The exponent of the preferential attachment, a small + * positive number usually, the value 1 yields the classic + * linear preferential attachment. + * \param aging_exp The exponent of the aging, this is a negative + * number usually. + * \param aging_bin Integer constant, the number of vertices to add + * before vertices in the network grew one unit older. + * \param zero_deg_appeal The degree dependent part of the + * attractiveness of the zero degree vertices. + * \param zero_age_appeal The age dependent part of the attractiveness + * of the vertices of age zero. This parameter is usually zero. + * \param deg_coef The coefficient for the degree. + * \param age_coef The coefficient for the age. + * \param directed Logical constant, whether to generate a directed + * graph. + * \return Error code. + * + * Time complexity: O((|V|+|V|/aging_bin)*log(|V|)+|E|). |V| is the number + * of vertices, |E| the number of edges. + */ + +int igraph_barabasi_aging_game(igraph_t *graph, + igraph_integer_t nodes, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t pa_exp, + igraph_real_t aging_exp, + igraph_integer_t aging_bin, + igraph_real_t zero_deg_appeal, + igraph_real_t zero_age_appeal, + igraph_real_t deg_coef, + igraph_real_t age_coef, + igraph_bool_t directed) { + long int no_of_nodes = nodes; + long int no_of_neighbors = m; + long int binwidth = nodes / aging_bin + 1; + long int no_of_edges; + igraph_vector_t edges; + long int i, j, k; + igraph_psumtree_t sumtree; + long int edgeptr = 0; + igraph_vector_t degree; + + if (no_of_nodes < 0) { + IGRAPH_ERROR("Invalid number of vertices", IGRAPH_EINVAL); + } + if (outseq != 0 && igraph_vector_size(outseq) != 0 && igraph_vector_size(outseq) != no_of_nodes) { + IGRAPH_ERROR("Invalid out degree sequence length", IGRAPH_EINVAL); + } + if ( (outseq == 0 || igraph_vector_size(outseq) == 0) && m < 0) { + IGRAPH_ERROR("Invalid out degree", IGRAPH_EINVAL); + } + if (aging_bin <= 0) { + IGRAPH_ERROR("Invalid aging bin", IGRAPH_EINVAL); + } + + if (outseq == 0 || igraph_vector_size(outseq) == 0) { + no_of_neighbors = m; + no_of_edges = (no_of_nodes - 1) * no_of_neighbors; + } else { + no_of_edges = 0; + for (i = 1; i < igraph_vector_size(outseq); i++) { + no_of_edges += VECTOR(*outseq)[i]; + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + + RNG_BEGIN(); + + /* first node */ + igraph_psumtree_update(&sumtree, 0, zero_deg_appeal * (1 + zero_age_appeal)); + + /* and the rest */ + for (i = 1; i < no_of_nodes; i++) { + igraph_real_t sum; + long int to; + if (outseq != 0 && igraph_vector_size(outseq) != 0) { + no_of_neighbors = (long int) VECTOR(*outseq)[i]; + } + sum = igraph_psumtree_sum(&sumtree); + for (j = 0; j < no_of_neighbors; j++) { + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + VECTOR(degree)[to]++; + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = to; + } + /* update probabilities */ + for (j = 0; j < no_of_neighbors; j++) { + long int n = (long int) VECTOR(edges)[edgeptr - 2 * j - 1]; + long int age = (i - n) / binwidth; + igraph_psumtree_update(&sumtree, n, + (deg_coef * pow(VECTOR(degree)[n], pa_exp) + + zero_deg_appeal)* + (age_coef * pow(age + 1, aging_exp) + zero_age_appeal)); + } + if (outpref) { + VECTOR(degree)[i] += no_of_neighbors; + igraph_psumtree_update(&sumtree, i, (zero_age_appeal + 1)* + (deg_coef * pow(VECTOR(degree)[i], pa_exp) + + zero_deg_appeal)); + } else { + igraph_psumtree_update(&sumtree, i, (1 + zero_age_appeal)*zero_deg_appeal); + } + + /* aging */ + for (k = 1; i - binwidth * k + 1 >= 1; k++) { + long int shnode = i - binwidth * k; + long int deg = (long int) VECTOR(degree)[shnode]; + long int age = (i - shnode) / binwidth; + /* igraph_real_t old=igraph_psumtree_get(&sumtree, shnode); */ + igraph_psumtree_update(&sumtree, shnode, + (deg_coef * pow(deg, pa_exp) + zero_deg_appeal) * + (age_coef * pow(age + 2, aging_exp) + zero_age_appeal)); + } + } + + RNG_END(); + + igraph_vector_destroy(°ree); + igraph_psumtree_destroy(&sumtree); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_recent_degree_aging_game + * \brief Preferential attachment based on the number of edges gained recently, with aging of vertices + * + * + * This game is very similar to \ref igraph_barabasi_aging_game(), + * except that instead of the total number of incident edges the + * number of edges gained in the last \p time_window time steps are + * counted. + * + * The degree dependent part of the attractiveness is + * given by k to the power of \p pa_exp plus \p zero_appeal; the age + * dependent part is l to the power to \p aging_exp. + * k is the number of edges gained in the last \p time_window time + * steps, l is the age of the vertex. + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the graph. + * \param m The number of edges to add in each time step. If the \p + * outseq argument is not a null vector or a zero-length vector + * then it is ignored. + * \param outseq Vector giving the number of edges to add in each time + * step. If it is a null pointer or a zero-length vector then + * it is ignored and the \p m argument is used. + * \param outpref Logical constant, if true the edges initiated by a + * vertex are also counted. Normally it is false. + * \param pa_exp The exponent for the preferential attachment. + * \param aging_exp The exponent for the aging, normally it is + * negative: old vertices gain edges with less probability. + * \param aging_bin Integer constant, gives the scale of the aging. + * The age of the vertices is incremented by one after every \p + * aging_bin vertex added. + * \param time_window The time window to use to count the number of + * incident edges for the vertices. + * \param zero_appeal The degree dependent part of the attractiveness + * for zero degree vertices. + * \param directed Logical constant, whether to create a directed + * graph. + * \return Error code. + * + * Time complexity: O((|V|+|V|/aging_bin)*log(|V|)+|E|). |V| is the number + * of vertices, |E| the number of edges. + */ + +int igraph_recent_degree_aging_game(igraph_t *graph, + igraph_integer_t nodes, + igraph_integer_t m, + const igraph_vector_t *outseq, + igraph_bool_t outpref, + igraph_real_t pa_exp, + igraph_real_t aging_exp, + igraph_integer_t aging_bin, + igraph_integer_t time_window, + igraph_real_t zero_appeal, + igraph_bool_t directed) { + + long int no_of_nodes = nodes; + long int no_of_neighbors = m; + long int binwidth = nodes / aging_bin + 1; + long int no_of_edges; + igraph_vector_t edges; + long int i, j, k; + igraph_psumtree_t sumtree; + long int edgeptr = 0; + igraph_vector_t degree; + igraph_dqueue_t history; + + if (no_of_nodes < 0) { + IGRAPH_ERROR("Invalid number of vertices", IGRAPH_EINVAL); + } + if (outseq != 0 && igraph_vector_size(outseq) != 0 && igraph_vector_size(outseq) != no_of_nodes) { + IGRAPH_ERROR("Invalid out degree sequence length", IGRAPH_EINVAL); + } + if ( (outseq == 0 || igraph_vector_size(outseq) == 0) && m < 0) { + IGRAPH_ERROR("Invalid out degree", IGRAPH_EINVAL); + } + if (aging_bin <= 0) { + IGRAPH_ERROR("Invalid aging bin", IGRAPH_EINVAL); + } + + if (outseq == 0 || igraph_vector_size(outseq) == 0) { + no_of_neighbors = m; + no_of_edges = (no_of_nodes - 1) * no_of_neighbors; + } else { + no_of_edges = 0; + for (i = 1; i < igraph_vector_size(outseq); i++) { + no_of_edges += VECTOR(*outseq)[i]; + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_CHECK(igraph_dqueue_init(&history, + time_window * (no_of_neighbors + 1) + 10)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &history); + + RNG_BEGIN(); + + /* first node */ + igraph_psumtree_update(&sumtree, 0, zero_appeal); + igraph_dqueue_push(&history, -1); + + /* and the rest */ + for (i = 1; i < no_of_nodes; i++) { + igraph_real_t sum; + long int to; + if (outseq != 0 && igraph_vector_size(outseq) != 0) { + no_of_neighbors = (long int) VECTOR(*outseq)[i]; + } + + if (i >= time_window) { + while ((j = (long int) igraph_dqueue_pop(&history)) != -1) { + long int age = (i - j) / binwidth; + VECTOR(degree)[j] -= 1; + igraph_psumtree_update(&sumtree, j, + (pow(VECTOR(degree)[j], pa_exp) + zero_appeal)* + pow(age + 1, aging_exp)); + } + } + + sum = igraph_psumtree_sum(&sumtree); + for (j = 0; j < no_of_neighbors; j++) { + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + VECTOR(degree)[to]++; + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = to; + igraph_dqueue_push(&history, to); + } + igraph_dqueue_push(&history, -1); + + /* update probabilities */ + for (j = 0; j < no_of_neighbors; j++) { + long int n = (long int) VECTOR(edges)[edgeptr - 2 * j - 1]; + long int age = (i - n) / binwidth; + igraph_psumtree_update(&sumtree, n, + (pow(VECTOR(degree)[n], pa_exp) + zero_appeal)* + pow(age + 1, aging_exp)); + } + if (outpref) { + VECTOR(degree)[i] += no_of_neighbors; + igraph_psumtree_update(&sumtree, i, + pow(VECTOR(degree)[i], pa_exp) + zero_appeal); + } else { + igraph_psumtree_update(&sumtree, i, zero_appeal); + } + + /* aging */ + for (k = 1; i - binwidth * k + 1 >= 1; k++) { + long int shnode = i - binwidth * k; + long int deg = (long int) VECTOR(degree)[shnode]; + long int age = (i - shnode) / binwidth; + igraph_psumtree_update(&sumtree, shnode, + (pow(deg, pa_exp) + zero_appeal) * + pow(age + 2, aging_exp)); + } + } + + RNG_END(); + + igraph_dqueue_destroy(&history); + igraph_vector_destroy(°ree); + igraph_psumtree_destroy(&sumtree); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_grg_game + * \brief Generating geometric random graphs. + * + * A geometric random graph is created by dropping points (=vertices) + * randomly to the unit square and then connecting all those pairs + * which are less than \c radius apart in Euclidean norm. + * + * + * Original code contributed by Keith Briggs, thanks Keith. + * \param graph Pointer to an uninitialized graph object, + * \param nodes The number of vertices in the graph. + * \param radius The radius within which the vertices will be connected. + * \param torus Logical constant, if true periodic boundary conditions + * will be used, ie. the vertices are assumed to be on a torus + * instead of a square. + * \return Error code. + * + * Time complexity: TODO, less than O(|V|^2+|E|). + * + * \example examples/simple/igraph_grg_game.c + */ + +int igraph_grg_game(igraph_t *graph, igraph_integer_t nodes, + igraph_real_t radius, igraph_bool_t torus, + igraph_vector_t *x, igraph_vector_t *y) { + + long int i; + igraph_vector_t myx, myy, *xx = &myx, *yy = &myy, edges; + igraph_real_t r2 = radius * radius; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, nodes)); + + if (x) { + xx = x; + IGRAPH_CHECK(igraph_vector_resize(xx, nodes)); + } else { + IGRAPH_VECTOR_INIT_FINALLY(xx, nodes); + } + if (y) { + yy = y; + IGRAPH_CHECK(igraph_vector_resize(yy, nodes)); + } else { + IGRAPH_VECTOR_INIT_FINALLY(yy, nodes); + } + + RNG_BEGIN(); + + for (i = 0; i < nodes; i++) { + VECTOR(*xx)[i] = RNG_UNIF01(); + VECTOR(*yy)[i] = RNG_UNIF01(); + } + + RNG_END(); + + igraph_vector_sort(xx); + + if (!torus) { + for (i = 0; i < nodes; i++) { + igraph_real_t xx1 = VECTOR(*xx)[i]; + igraph_real_t yy1 = VECTOR(*yy)[i]; + long int j = i + 1; + igraph_real_t dx, dy; + while ( j < nodes && (dx = VECTOR(*xx)[j] - xx1) < radius) { + dy = VECTOR(*yy)[j] - yy1; + if (dx * dx + dy * dy < r2) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, j)); + } + j++; + } + } + } else { + for (i = 0; i < nodes; i++) { + igraph_real_t xx1 = VECTOR(*xx)[i]; + igraph_real_t yy1 = VECTOR(*yy)[i]; + long int j = i + 1; + igraph_real_t dx, dy; + while ( j < nodes && (dx = VECTOR(*xx)[j] - xx1) < radius) { + dy = fabs(VECTOR(*yy)[j] - yy1); + if (dx > 0.5) { + dx = 1 - dx; + } + if (dy > 0.5) { + dy = 1 - dy; + } + if (dx * dx + dy * dy < r2) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, j)); + } + j++; + } + if (j == nodes) { + j = 0; + while (j < i && (dx = 1 - xx1 + VECTOR(*xx)[j]) < radius && + xx1 - VECTOR(*xx)[j] >= radius) { + dy = fabs(VECTOR(*yy)[j] - yy1); + if (dy > 0.5) { + dy = 1 - dy; + } + if (dx * dx + dy * dy < r2) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, j)); + } + j++; + } + } + } + } + + if (!y) { + igraph_vector_destroy(yy); + IGRAPH_FINALLY_CLEAN(1); + } + if (!x) { + igraph_vector_destroy(xx); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, IGRAPH_UNDIRECTED)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + + +static void igraph_i_preference_game_free_vids_by_type(igraph_vector_ptr_t *vecs) { + int i = 0, n; + igraph_vector_t *v; + + n = (int) igraph_vector_ptr_size(vecs); + for (i = 0; i < n; i++) { + v = (igraph_vector_t*)VECTOR(*vecs)[i]; + if (v) { + igraph_vector_destroy(v); + } + } + igraph_vector_ptr_destroy_all(vecs); +} + +/** + * \function igraph_preference_game + * \brief Generates a graph with vertex types and connection preferences + * + * + * This is practically the nongrowing variant of \ref + * igraph_establishment_game. A given number of vertices are + * generated. Every vertex is assigned to a vertex type according to + * the given type probabilities. Finally, every + * vertex pair is evaluated and an edge is created between them with a + * probability depending on the types of the vertices involved. + * + * + * In other words, this function generates a graph according to a + * block-model. Vertices are divided into groups (or blocks), and + * the probability the two vertices are connected depends on their + * groups only. + * + * \param graph Pointer to an uninitialized graph. + * \param nodes The number of vertices in the graph. + * \param types The number of vertex types. + * \param type_dist Vector giving the distribution of vertex types. If + * \c NULL, all vertex types will have equal probability. See also the + * \c fixed_sizes argument. + * \param fixed_sizes Boolean. If true, then the number of vertices with a + * given vertex type is fixed and the \c type_dist argument gives these + * numbers for each vertex type. If true, and \c type_dist is \c NULL, + * then the function tries to make vertex groups of the same size. If this + * is not possible, then some groups will have an extra vertex. + * \param pref_matrix Matrix giving the connection probabilities for + * different vertex types. This should be symmetric if the requested + * graph is undirected. + * \param node_type_vec A vector where the individual generated vertex types + * will be stored. If \c NULL , the vertex types won't be saved. + * \param directed Logical, whether to generate a directed graph. If undirected + * graphs are requested, only the lower left triangle of the preference + * matrix is considered. + * \param loops Logical, whether loop edges are allowed. + * \return Error code. + * + * Added in version 0.3. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa igraph_establishment_game() + * + * \example examples/simple/igraph_preference_game.c + */ + +int igraph_preference_game(igraph_t *graph, igraph_integer_t nodes, + igraph_integer_t types, + const igraph_vector_t *type_dist, + igraph_bool_t fixed_sizes, + const igraph_matrix_t *pref_matrix, + igraph_vector_t *node_type_vec, + igraph_bool_t directed, + igraph_bool_t loops) { + + long int i, j; + igraph_vector_t edges, s; + igraph_vector_t* nodetypes; + igraph_vector_ptr_t vids_by_type; + igraph_real_t maxcum, maxedges; + + if (types < 1) { + IGRAPH_ERROR("types must be >= 1", IGRAPH_EINVAL); + } + if (nodes < 0) { + IGRAPH_ERROR("nodes must be >= 0", IGRAPH_EINVAL); + } + if (type_dist && igraph_vector_size(type_dist) != types) { + if (igraph_vector_size(type_dist) > types) { + IGRAPH_WARNING("length of type_dist > types, type_dist will be trimmed"); + } else { + IGRAPH_ERROR("type_dist vector too short", IGRAPH_EINVAL); + } + } + if (igraph_matrix_nrow(pref_matrix) < types || + igraph_matrix_ncol(pref_matrix) < types) { + IGRAPH_ERROR("pref_matrix too small", IGRAPH_EINVAL); + } + + if (fixed_sizes && type_dist) { + if (igraph_vector_sum(type_dist) != nodes) { + IGRAPH_ERROR("Invalid group sizes, their sum must match the number" + " of vertices", IGRAPH_EINVAL); + } + } + + if (node_type_vec) { + IGRAPH_CHECK(igraph_vector_resize(node_type_vec, nodes)); + nodetypes = node_type_vec; + } else { + nodetypes = igraph_Calloc(1, igraph_vector_t); + if (nodetypes == 0) { + IGRAPH_ERROR("preference_game failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, nodetypes); + IGRAPH_VECTOR_INIT_FINALLY(nodetypes, nodes); + } + + IGRAPH_CHECK(igraph_vector_ptr_init(&vids_by_type, types)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &vids_by_type); + for (i = 0; i < types; i++) { + VECTOR(vids_by_type)[i] = igraph_Calloc(1, igraph_vector_t); + if (VECTOR(vids_by_type)[i] == 0) { + IGRAPH_ERROR("preference_game failed", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(VECTOR(vids_by_type)[i], 0)); + } + IGRAPH_FINALLY_CLEAN(1); /* removing igraph_vector_ptr_destroy_all */ + IGRAPH_FINALLY(igraph_i_preference_game_free_vids_by_type, &vids_by_type); + + RNG_BEGIN(); + + if (!fixed_sizes) { + + igraph_vector_t cumdist; + IGRAPH_VECTOR_INIT_FINALLY(&cumdist, types + 1); + + VECTOR(cumdist)[0] = 0; + if (type_dist) { + for (i = 0; i < types; i++) { + VECTOR(cumdist)[i + 1] = VECTOR(cumdist)[i] + VECTOR(*type_dist)[i]; + } + } else { + for (i = 0; i < types; i++) { + VECTOR(cumdist)[i + 1] = i + 1; + } + } + maxcum = igraph_vector_tail(&cumdist); + + for (i = 0; i < nodes; i++) { + long int type1; + igraph_real_t uni1 = RNG_UNIF(0, maxcum); + igraph_vector_binsearch(&cumdist, uni1, &type1); + VECTOR(*nodetypes)[i] = type1 - 1; + IGRAPH_CHECK(igraph_vector_push_back( + (igraph_vector_t*)VECTOR(vids_by_type)[type1 - 1], i)); + } + + igraph_vector_destroy(&cumdist); + IGRAPH_FINALLY_CLEAN(1); + + } else { + + int an = 0; + if (type_dist) { + for (i = 0; i < types; i++) { + int no = (int) VECTOR(*type_dist)[i]; + igraph_vector_t *v = VECTOR(vids_by_type)[i]; + for (j = 0; j < no && an < nodes; j++) { + VECTOR(*nodetypes)[an] = i; + IGRAPH_CHECK(igraph_vector_push_back(v, an)); + an++; + } + } + } else { + int fixno = (int) ceil( (double)nodes / types); + for (i = 0; i < types; i++) { + igraph_vector_t *v = VECTOR(vids_by_type)[i]; + for (j = 0; j < fixno && an < nodes; j++) { + VECTOR(*nodetypes)[an++] = i; + IGRAPH_CHECK(igraph_vector_push_back(v, an)); + an++; + } + } + } + + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + + for (i = 0; i < types; i++) { + for (j = 0; j < types; j++) { + /* Generating the random subgraph between vertices of type i and j */ + long int k, l; + igraph_real_t p, last; + igraph_vector_t *v1, *v2; + long int v1_size, v2_size; + + IGRAPH_ALLOW_INTERRUPTION(); + + v1 = (igraph_vector_t*)VECTOR(vids_by_type)[i]; + v2 = (igraph_vector_t*)VECTOR(vids_by_type)[j]; + v1_size = igraph_vector_size(v1); + v2_size = igraph_vector_size(v2); + + p = MATRIX(*pref_matrix, i, j); + igraph_vector_clear(&s); + if (i != j) { + /* The two vertex sets are disjoint, this is the easier case */ + if (i > j && !directed) { + continue; + } + maxedges = v1_size * v2_size; + } else { + if (directed && loops) { + maxedges = v1_size * v1_size; + } else if (directed && !loops) { + maxedges = v1_size * (v1_size - 1); + } else if (!directed && loops) { + maxedges = v1_size * (v1_size + 1) / 2; + } else { + maxedges = v1_size * (v1_size - 1) / 2; + } + } + + IGRAPH_CHECK(igraph_vector_reserve(&s, (long int) (maxedges * p * 1.1))); + + last = RNG_GEOM(p); + while (last < maxedges) { + IGRAPH_CHECK(igraph_vector_push_back(&s, last)); + last += RNG_GEOM(p); + last += 1; + } + l = igraph_vector_size(&s); + + IGRAPH_CHECK(igraph_vector_reserve(&edges, igraph_vector_size(&edges) + l * 2)); + + if (i != j) { + /* Generating the subgraph between vertices of type i and j */ + for (k = 0; k < l; k++) { + long int to = (long int) floor(VECTOR(s)[k] / v1_size); + long int from = (long int) (VECTOR(s)[k] - ((igraph_real_t)to) * v1_size); + igraph_vector_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_push_back(&edges, VECTOR(*v2)[to]); + } + } else { + /* Generating the subgraph among vertices of type i */ + if (directed && loops) { + for (k = 0; k < l; k++) { + long int to = (long int) floor(VECTOR(s)[k] / v1_size); + long int from = (long int) (VECTOR(s)[k] - ((igraph_real_t)to) * v1_size); + igraph_vector_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_push_back(&edges, VECTOR(*v1)[to]); + } + } else if (directed && !loops) { + for (k = 0; k < l; k++) { + long int to = (long int) floor(VECTOR(s)[k] / v1_size); + long int from = (long int) (VECTOR(s)[k] - ((igraph_real_t)to) * v1_size); + if (from == to) { + to = v1_size - 1; + } + igraph_vector_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_push_back(&edges, VECTOR(*v1)[to]); + } + } else if (!directed && loops) { + for (k = 0; k < l; k++) { + long int to = (long int) floor((sqrt(8 * VECTOR(s)[k] + 1) - 1) / 2); + long int from = (long int) (VECTOR(s)[k] - (((igraph_real_t)to) * (to + 1)) / 2); + igraph_vector_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_push_back(&edges, VECTOR(*v1)[to]); + } + } else { + for (k = 0; k < l; k++) { + long int to = (long int) floor((sqrt(8 * VECTOR(s)[k] + 1) + 1) / 2); + long int from = (long int) (VECTOR(s)[k] - (((igraph_real_t)to) * (to - 1)) / 2); + igraph_vector_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_push_back(&edges, VECTOR(*v1)[to]); + } + } + } + } + } + + RNG_END(); + + igraph_vector_destroy(&s); + igraph_i_preference_game_free_vids_by_type(&vids_by_type); + IGRAPH_FINALLY_CLEAN(2); + + if (node_type_vec == 0) { + igraph_vector_destroy(nodetypes); + igraph_Free(nodetypes); + IGRAPH_FINALLY_CLEAN(2); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_asymmetric_preference_game + * \brief Generates a graph with asymmetric vertex types and connection preferences + * + * + * This is the asymmetric variant of \ref igraph_preference_game() . + * A given number of vertices are generated. Every vertex is assigned to an + * "incoming" and an "outgoing" vertex type according to the given joint + * type probabilities. Finally, every vertex pair is evaluated and a + * directed edge is created between them with a probability depending on the + * "outgoing" type of the source vertex and the "incoming" type of the target + * vertex. + * + * \param graph Pointer to an uninitialized graph. + * \param nodes The number of vertices in the graph. + * \param types The number of vertex types. + * \param type_dist_matrix Matrix giving the joint distribution of vertex types. + * If null, incoming and outgoing vertex types are independent and uniformly + * distributed. + * \param pref_matrix Matrix giving the connection probabilities for + * different vertex types. + * \param node_type_in_vec A vector where the individual generated "incoming" + * vertex types will be stored. If NULL, the vertex types won't be saved. + * \param node_type_out_vec A vector where the individual generated "outgoing" + * vertex types will be stored. If NULL, the vertex types won't be saved. + * \param loops Logical, whether loop edges are allowed. + * \return Error code. + * + * Added in version 0.3. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_preference_game() + */ + +int igraph_asymmetric_preference_game(igraph_t *graph, igraph_integer_t nodes, + igraph_integer_t types, + igraph_matrix_t *type_dist_matrix, + igraph_matrix_t *pref_matrix, + igraph_vector_t *node_type_in_vec, + igraph_vector_t *node_type_out_vec, + igraph_bool_t loops) { + + long int i, j, k; + igraph_vector_t edges, cumdist, s, intersect; + igraph_vector_t *nodetypes_in; + igraph_vector_t *nodetypes_out; + igraph_vector_ptr_t vids_by_intype, vids_by_outtype; + igraph_real_t maxcum, maxedges; + + if (types < 1) { + IGRAPH_ERROR("types must be >= 1", IGRAPH_EINVAL); + } + if (nodes < 0) { + IGRAPH_ERROR("nodes must be >= 0", IGRAPH_EINVAL); + } + if (type_dist_matrix) { + if (igraph_matrix_nrow(type_dist_matrix) < types || + igraph_matrix_ncol(type_dist_matrix) < types) { + IGRAPH_ERROR("type_dist_matrix too small", IGRAPH_EINVAL); + } else if (igraph_matrix_nrow(type_dist_matrix) > types || + igraph_matrix_ncol(type_dist_matrix) > types) { + IGRAPH_WARNING("type_dist_matrix will be trimmed"); + } + } + if (igraph_matrix_nrow(pref_matrix) < types || + igraph_matrix_ncol(pref_matrix) < types) { + IGRAPH_ERROR("pref_matrix too small", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&cumdist, types * types + 1); + + if (node_type_in_vec) { + nodetypes_in = node_type_in_vec; + IGRAPH_CHECK(igraph_vector_resize(nodetypes_in, nodes)); + } else { + nodetypes_in = igraph_Calloc(1, igraph_vector_t); + if (nodetypes_in == 0) { + IGRAPH_ERROR("asymmetric_preference_game failed", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(nodetypes_in, nodes); + } + + if (node_type_out_vec) { + nodetypes_out = node_type_out_vec; + IGRAPH_CHECK(igraph_vector_resize(nodetypes_out, nodes)); + } else { + nodetypes_out = igraph_Calloc(1, igraph_vector_t); + if (nodetypes_out == 0) { + IGRAPH_ERROR("asymmetric_preference_game failed", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(nodetypes_out, nodes); + } + + IGRAPH_CHECK(igraph_vector_ptr_init(&vids_by_intype, types)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &vids_by_intype); + IGRAPH_CHECK(igraph_vector_ptr_init(&vids_by_outtype, types)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &vids_by_outtype); + for (i = 0; i < types; i++) { + VECTOR(vids_by_intype)[i] = igraph_Calloc(1, igraph_vector_t); + VECTOR(vids_by_outtype)[i] = igraph_Calloc(1, igraph_vector_t); + if (VECTOR(vids_by_intype)[i] == 0 || VECTOR(vids_by_outtype)[i] == 0) { + IGRAPH_ERROR("asymmetric_preference_game failed", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(VECTOR(vids_by_intype)[i], 0)); + IGRAPH_CHECK(igraph_vector_init(VECTOR(vids_by_outtype)[i], 0)); + } + IGRAPH_FINALLY_CLEAN(2); /* removing igraph_vector_ptr_destroy_all */ + IGRAPH_FINALLY(igraph_i_preference_game_free_vids_by_type, &vids_by_intype); + IGRAPH_FINALLY(igraph_i_preference_game_free_vids_by_type, &vids_by_outtype); + + VECTOR(cumdist)[0] = 0; + if (type_dist_matrix) { + for (i = 0, k = 0; i < types; i++) { + for (j = 0; j < types; j++, k++) { + VECTOR(cumdist)[k + 1] = VECTOR(cumdist)[k] + MATRIX(*type_dist_matrix, i, j); + } + } + } else { + for (i = 0; i < types * types; i++) { + VECTOR(cumdist)[i + 1] = i + 1; + } + } + maxcum = igraph_vector_tail(&cumdist); + + RNG_BEGIN(); + + for (i = 0; i < nodes; i++) { + long int type1, type2; + igraph_real_t uni1 = RNG_UNIF(0, maxcum); + igraph_vector_binsearch(&cumdist, uni1, &type1); + type2 = (type1 - 1) % (int)types; + type1 = (type1 - 1) / (int)types; + VECTOR(*nodetypes_in)[i] = type1; + VECTOR(*nodetypes_out)[i] = type2; + IGRAPH_CHECK(igraph_vector_push_back( + (igraph_vector_t*)VECTOR(vids_by_intype)[type1], i)); + IGRAPH_CHECK(igraph_vector_push_back( + (igraph_vector_t*)VECTOR(vids_by_outtype)[type2], i)); + } + + igraph_vector_destroy(&cumdist); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_VECTOR_INIT_FINALLY(&intersect, 0); + for (i = 0; i < types; i++) { + for (j = 0; j < types; j++) { + long int kk, l, c; + igraph_real_t p, last; + igraph_vector_t *v1, *v2; + long int v1_size, v2_size; + + IGRAPH_ALLOW_INTERRUPTION(); + + v1 = (igraph_vector_t*)VECTOR(vids_by_outtype)[i]; + v2 = (igraph_vector_t*)VECTOR(vids_by_intype)[j]; + v1_size = igraph_vector_size(v1); + v2_size = igraph_vector_size(v2); + + maxedges = v1_size * v2_size; + if (!loops) { + IGRAPH_CHECK(igraph_vector_intersect_sorted(v1, v2, &intersect)); + c = igraph_vector_size(&intersect); + maxedges -= c; + } + + p = MATRIX(*pref_matrix, i, j); + igraph_vector_clear(&s); + IGRAPH_CHECK(igraph_vector_reserve(&s, (long int) (maxedges * p * 1.1))); + + last = RNG_GEOM(p); + while (last < maxedges) { + IGRAPH_CHECK(igraph_vector_push_back(&s, last)); + last += RNG_GEOM(p); + last += 1; + } + l = igraph_vector_size(&s); + + IGRAPH_CHECK(igraph_vector_reserve(&edges, igraph_vector_size(&edges) + l * 2)); + + if (!loops && c > 0) { + for (kk = 0; kk < l; kk++) { + long int to = (long int) floor(VECTOR(s)[kk] / v1_size); + long int from = (long int) (VECTOR(s)[kk] - ((igraph_real_t)to) * v1_size); + if (VECTOR(*v1)[from] == VECTOR(*v2)[to]) { + /* remap loop edges */ + to = v2_size - 1; + igraph_vector_binsearch(&intersect, VECTOR(*v1)[from], &c); + from = v1_size - 1; + if (VECTOR(*v1)[from] == VECTOR(*v2)[to]) { + from--; + } + while (c > 0) { + c--; from--; + if (VECTOR(*v1)[from] == VECTOR(*v2)[to]) { + from--; + } + } + } + igraph_vector_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_push_back(&edges, VECTOR(*v2)[to]); + } + } else { + for (kk = 0; kk < l; kk++) { + long int to = (long int) floor(VECTOR(s)[kk] / v1_size); + long int from = (long int) (VECTOR(s)[kk] - ((igraph_real_t)to) * v1_size); + igraph_vector_push_back(&edges, VECTOR(*v1)[from]); + igraph_vector_push_back(&edges, VECTOR(*v2)[to]); + } + } + } + } + + RNG_END(); + + igraph_vector_destroy(&s); + igraph_vector_destroy(&intersect); + igraph_i_preference_game_free_vids_by_type(&vids_by_intype); + igraph_i_preference_game_free_vids_by_type(&vids_by_outtype); + IGRAPH_FINALLY_CLEAN(4); + + if (node_type_out_vec == 0) { + igraph_vector_destroy(nodetypes_out); + igraph_Free(nodetypes_out); + IGRAPH_FINALLY_CLEAN(1); + } + + if (node_type_in_vec == 0) { + igraph_vector_destroy(nodetypes_in); + igraph_Free(nodetypes_in); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, 1)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +static int igraph_i_rewire_edges_no_multiple(igraph_t *graph, igraph_real_t prob, + igraph_bool_t loops, + igraph_vector_t *edges) { + + int no_verts = igraph_vcount(graph); + int no_edges = igraph_ecount(graph); + igraph_vector_t eorder, tmp; + igraph_vector_int_t first, next, prev, marked; + int i, to_rewire, last_other = -1; + + /* Create our special graph representation */ + +# define ADD_STUB(vertex, stub) do { \ + if (VECTOR(first)[(vertex)]) { \ + VECTOR(prev)[(int) VECTOR(first)[(vertex)]-1]=(stub)+1; \ + } \ + VECTOR(next)[(stub)]=VECTOR(first)[(vertex)]; \ + VECTOR(prev)[(stub)]=0; \ + VECTOR(first)[(vertex)]=(stub)+1; \ + } while (0) + +# define DEL_STUB(vertex, stub) do { \ + if (VECTOR(next)[(stub)]) { \ + VECTOR(prev)[VECTOR(next)[(stub)]-1]=VECTOR(prev)[(stub)]; \ + } \ + if (VECTOR(prev)[(stub)]) { \ + VECTOR(next)[VECTOR(prev)[(stub)]-1]=VECTOR(next)[(stub)]; \ + } else { \ + VECTOR(first)[(vertex)]=VECTOR(next)[(stub)]; \ + } \ + } while (0) + +# define MARK_NEIGHBORS(vertex) do { \ + int xxx_ =VECTOR(first)[(vertex)]; \ + while (xxx_) { \ + int o= (int) VECTOR(*edges)[xxx_ % 2 ? xxx_ : xxx_-2]; \ + VECTOR(marked)[o]=other+1; \ + xxx_=VECTOR(next)[xxx_-1]; \ + } \ + } while (0) + + IGRAPH_CHECK(igraph_vector_int_init(&first, no_verts)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &first); + IGRAPH_CHECK(igraph_vector_int_init(&next, no_edges * 2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &next); + IGRAPH_CHECK(igraph_vector_int_init(&prev, no_edges * 2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &prev); + IGRAPH_CHECK(igraph_get_edgelist(graph, edges, /*bycol=*/ 0)); + IGRAPH_VECTOR_INIT_FINALLY(&eorder, no_edges); + IGRAPH_VECTOR_INIT_FINALLY(&tmp, no_edges); + for (i = 0; i < no_edges; i++) { + int idx1 = 2 * i, idx2 = idx1 + 1, + from = (int) VECTOR(*edges)[idx1], to = (int) VECTOR(*edges)[idx2]; + VECTOR(tmp)[i] = from; + ADD_STUB(from, idx1); + ADD_STUB(to, idx2); + } + IGRAPH_CHECK(igraph_vector_order1(&tmp, &eorder, no_verts)); + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_vector_int_init(&marked, no_verts)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &marked); + + /* Rewire the stubs, part I */ + + to_rewire = (int) RNG_GEOM(prob); + while (to_rewire < no_edges) { + int stub = (int) (2 * VECTOR(eorder)[to_rewire] + 1); + int v = (int) VECTOR(*edges)[stub]; + int ostub = stub - 1; + int other = (int) VECTOR(*edges)[ostub]; + int pot; + if (last_other != other) { + MARK_NEIGHBORS(other); + } + /* Do the rewiring */ + do { + if (loops) { + pot = (int) RNG_INTEGER(0, no_verts - 1); + } else { + pot = (int) RNG_INTEGER(0, no_verts - 2); + pot = pot != other ? pot : no_verts - 1; + } + } while (VECTOR(marked)[pot] == other + 1 && pot != v); + + if (pot != v) { + DEL_STUB(v, stub); + ADD_STUB(pot, stub); + VECTOR(marked)[v] = 0; + VECTOR(marked)[pot] = other + 1; + VECTOR(*edges)[stub] = pot; + } + + to_rewire += RNG_GEOM(prob) + 1; + last_other = other; + } + + /* Create the new index, from the potentially rewired stubs */ + + IGRAPH_VECTOR_INIT_FINALLY(&tmp, no_edges); + for (i = 0; i < no_edges; i++) { + VECTOR(tmp)[i] = VECTOR(*edges)[2 * i + 1]; + } + IGRAPH_CHECK(igraph_vector_order1(&tmp, &eorder, no_verts)); + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + /* Rewire the stubs, part II */ + + igraph_vector_int_null(&marked); + last_other = -1; + + to_rewire = (int) RNG_GEOM(prob); + while (to_rewire < no_edges) { + int stub = (int) (2 * VECTOR(eorder)[to_rewire]); + int v = (int) VECTOR(*edges)[stub]; + int ostub = stub + 1; + int other = (int) VECTOR(*edges)[ostub]; + int pot; + if (last_other != other) { + MARK_NEIGHBORS(other); + } + /* Do the rewiring */ + do { + if (loops) { + pot = (int) RNG_INTEGER(0, no_verts - 1); + } else { + pot = (int) RNG_INTEGER(0, no_verts - 2); + pot = pot != other ? pot : no_verts - 1; + } + } while (VECTOR(marked)[pot] == other + 1 && pot != v); + if (pot != v) { + DEL_STUB(v, stub); + ADD_STUB(pot, stub); + VECTOR(marked)[v] = 0; + VECTOR(marked)[pot] = other + 1; + VECTOR(*edges)[stub] = pot; + } + + to_rewire += RNG_GEOM(prob) + 1; + last_other = other; + } + + igraph_vector_int_destroy(&marked); + igraph_vector_int_destroy(&prev); + igraph_vector_int_destroy(&next); + igraph_vector_int_destroy(&first); + igraph_vector_destroy(&eorder); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +#undef ADD_STUB +#undef DEL_STUB +#undef MARK_NEIGHBORS + +/** + * \function igraph_rewire_edges + * \brief Rewire the edges of a graph with constant probability + * + * This function rewires the edges of a graph with a constant + * probability. More precisely each end point of each edge is rewired + * to a uniformly randomly chosen vertex with constant probability \p + * prob. + * + * Note that this function modifies the input \p graph, + * call \ref igraph_copy() if you want to keep it. + * + * \param graph The input graph, this will be rewired, it can be + * directed or undirected. + * \param prob The rewiring probability a constant between zero and + * one (inclusive). + * \param loops Boolean, whether loop edges are allowed in the new + * graph, or not. + * \param multiple Boolean, whether multiple edges are allowed in the + * new graph. + * \return Error code. + * + * \sa \ref igraph_watts_strogatz_game() uses this function for the + * rewiring. + * + * Time complexity: O(|V|+|E|). + */ + +int igraph_rewire_edges(igraph_t *graph, igraph_real_t prob, + igraph_bool_t loops, igraph_bool_t multiple) { + + igraph_t newgraph; + long int no_of_edges = igraph_ecount(graph); + long int no_of_nodes = igraph_vcount(graph); + long int endpoints = no_of_edges * 2; + long int to_rewire; + igraph_vector_t edges; + + if (prob < 0 || prob > 1) { + IGRAPH_ERROR("Rewiring probability should be between zero and one", + IGRAPH_EINVAL); + } + + if (prob == 0) { + /* This is easy, just leave things as they are */ + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, endpoints); + + RNG_BEGIN(); + + if (prob != 0 && no_of_edges > 0) { + if (multiple) { + /* If multiple edges are allowed, then there is an easy and fast + method. Each endpoint of an edge is rewired with probability p, + so the "skips" between the really rewired endpoints follow a + geometric distribution. */ + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + to_rewire = (long int) RNG_GEOM(prob); + while (to_rewire < endpoints) { + if (loops) { + VECTOR(edges)[to_rewire] = RNG_INTEGER(0, no_of_nodes - 1); + } else { + long int opos = to_rewire % 2 ? to_rewire - 1 : to_rewire + 1; + long int nei = (long int) VECTOR(edges)[opos]; + long int r = RNG_INTEGER(0, no_of_nodes - 2); + VECTOR(edges)[ to_rewire ] = (r != nei ? r : no_of_nodes - 1); + } + to_rewire += RNG_GEOM(prob) + 1; + } + + } else { + IGRAPH_CHECK(igraph_i_rewire_edges_no_multiple(graph, prob, loops, + &edges)); + } + } + + RNG_END(); + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, (igraph_integer_t) no_of_nodes, + igraph_is_directed(graph))); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_I_ATTRIBUTE_DESTROY(&newgraph); + IGRAPH_I_ATTRIBUTE_COPY(&newgraph, graph, 1, 1, 1); + IGRAPH_FINALLY_CLEAN(1); + igraph_destroy(graph); + *graph = newgraph; + + return 0; +} + +/** + * \function igraph_rewire_directed_edges + * \brief Rewire the chosen endpoint of directed edges + * + * This function rewires either the start or end of directed edges in a graph + * with a constant probability. Correspondingly, either the in-degree sequence + * or the out-degree sequence of the graph will be preserved. + * + * Note that this function modifies the input \p graph, + * call \ref igraph_copy() if you want to keep it. + * + * \param graph The input graph, this will be rewired, it can be + * directed or undirected. If it is directed, \ref igraph_rewire_edges() + * will be called. + * \param prob The rewiring probability, a constant between zero and + * one (inclusive). + * \param loops Boolean, whether loop edges are allowed in the new + * graph, or not. + * \param mode The endpoints of directed edges to rewire. It is ignored for + * undirected graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * rewire the end of each directed edge + * \cli IGRAPH_IN + * rewire the start of each directed edge + * \cli IGRAPH_ALL + * rewire both endpoints of each edge + * \endclist + * \return Error code. + * + * \sa \ref igraph_rewire_edges(), \ref igraph_rewire() + * + * Time complexity: O(|E|). + */ + +int igraph_rewire_directed_edges(igraph_t *graph, igraph_real_t prob, + igraph_bool_t loops, igraph_neimode_t mode) { + + if (prob < 0 || prob > 1) { + IGRAPH_ERROR("Rewiring probability should be between zero and one", + IGRAPH_EINVAL); + } + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument", IGRAPH_EINVMODE); + } + + if (prob == 0) { + return IGRAPH_SUCCESS; + } + + if (igraph_is_directed(graph) && mode != IGRAPH_ALL) { + igraph_t newgraph; + long int no_of_edges = igraph_ecount(graph); + long int no_of_nodes = igraph_vcount(graph); + long int to_rewire; + long int offset; + igraph_vector_t edges; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 2 * no_of_edges); + + switch (mode) { + case IGRAPH_IN: + offset = 0; + break; + case IGRAPH_OUT: + offset = 1; + break; + case IGRAPH_ALL: + break; /* suppress compiler warning */ + } + + IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, 0)); + + RNG_BEGIN(); + + to_rewire = RNG_GEOM(prob); + while (to_rewire < no_of_edges) { + if (loops) { + VECTOR(edges)[2 * to_rewire + offset] = RNG_INTEGER(0, no_of_nodes - 1); + } else { + long int nei = (long int) VECTOR(edges)[2 * to_rewire + (1 - offset)]; + long int r = RNG_INTEGER(0, no_of_nodes - 2); + VECTOR(edges)[2 * to_rewire + offset] = (r != nei ? r : no_of_nodes - 1); + } + to_rewire += RNG_GEOM(prob) + 1; + } + + RNG_END(); + + IGRAPH_CHECK(igraph_create(&newgraph, &edges, (igraph_integer_t) no_of_nodes, + igraph_is_directed(graph))); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_I_ATTRIBUTE_DESTROY(&newgraph); + IGRAPH_I_ATTRIBUTE_COPY(&newgraph, graph, 1, 1, 1); + IGRAPH_FINALLY_CLEAN(1); + igraph_destroy(graph); + *graph = newgraph; + + } else { + IGRAPH_CHECK(igraph_rewire_edges(graph, prob, loops, /* multiple = */ 0)); + } + + return 0; +} + +/** + * \function igraph_watts_strogatz_game + * \brief The Watts-Strogatz small-world model + * + * This function generates a graph according to the Watts-Strogatz + * model of small-world networks. The graph is obtained by creating a + * circular undirected lattice and then rewire the edges randomly with + * a constant probability. + * + * See also: Duncan J Watts and Steven H Strogatz: + * Collective dynamics of small world networks, Nature + * 393, 440-442, 1998. + * \param graph The graph to initialize. + * \param dim The dimension of the lattice. + * \param size The size of the lattice along each dimension. + * \param nei The size of the neighborhood for each vertex. This is + * the same as the \p nei argument of \ref + * igraph_connect_neighborhood(). + * \param p The rewiring probability. A real number between zero and + * one (inclusive). + * \param loops Logical, whether to generate loop edges. + * \param multiple Logical, whether to allow multiple edges in the + * generated graph. + * \return Error code. + * + * \sa \ref igraph_lattice(), \ref igraph_connect_neighborhood() and + * \ref igraph_rewire_edges() can be used if more flexibility is + * needed, eg. a different type of lattice. + * + * Time complexity: O(|V|*d^o+|E|), |V| and |E| are the number of + * vertices and edges, d is the average degree, o is the \p nei + * argument. + */ + +int igraph_watts_strogatz_game(igraph_t *graph, igraph_integer_t dim, + igraph_integer_t size, igraph_integer_t nei, + igraph_real_t p, igraph_bool_t loops, + igraph_bool_t multiple) { + + igraph_vector_t dimvector; + long int i; + + if (dim < 1) { + IGRAPH_ERROR("WS game: dimension should be at least one", IGRAPH_EINVAL); + } + if (size < 1) { + IGRAPH_ERROR("WS game: lattice size should be at least one", + IGRAPH_EINVAL); + } + if (p < 0 || p > 1) { + IGRAPH_ERROR("WS game: rewiring probability should be between 0 and 1", + IGRAPH_EINVAL); + } + + /* Create the lattice first */ + + IGRAPH_VECTOR_INIT_FINALLY(&dimvector, dim); + for (i = 0; i < dim; i++) { + VECTOR(dimvector)[i] = size; + } + + IGRAPH_CHECK(igraph_lattice(graph, &dimvector, nei, IGRAPH_UNDIRECTED, + 0 /* mutual */, 1 /* circular */)); + igraph_vector_destroy(&dimvector); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_destroy, graph); + + /* Rewire the edges then */ + + IGRAPH_CHECK(igraph_rewire_edges(graph, p, loops, multiple)); + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_lastcit_game + * \brief Simulate citation network, based on time passed since the last citation. + * + * This is a quite special stochastic graph generator, it models an + * evolving graph. In each time step a single vertex is added to the + * network and it cites a number of other vertices (as specified by + * the \p edges_per_step argument). The cited vertices are selected + * based on the last time they were cited. Time is measured by the + * addition of vertices and it is binned into \p pagebins bins. + * So if the current time step is \c t and the last citation to a + * given \c i vertex was made in time step \c t0, then \c + * (t-t0)/binwidth is calculated where binwidth is \c nodes/pagebins+1, + * in the last expression '/' denotes integer division, so the + * fraction part is omitted. + * + * + * The \p preference argument specifies the preferences for the + * citation lags, ie. its first elements contains the attractivity + * of the very recently cited vertices, etc. The last element is + * special, it contains the attractivity of the vertices which were + * never cited. This element should be bigger than zero. + * + * + * Note that this function generates networks with multiple edges if + * \p edges_per_step is bigger than one, call \ref igraph_simplify() + * on the result to get rid of these edges. + * \param graph Pointer to an uninitialized graph object, the result + * will be stored here. + * \param node The number of vertices in the network. + * \param edges_per_node The number of edges to add in each time + * step. + * \param pagebins The number of age bins to use. + * \param preference Pointer to an initialized vector of length + * \c pagebins+1. This contains the `attractivity' of the various + * age bins, the last element is the attractivity of the vertices + * which were never cited, and it should be greater than zero. + * It is a good idea to have all positive values in this vector. + * \param directed Logical constant, whether to create directed + * networks. + * \return Error code. + * + * \sa \ref igraph_barabasi_aging_game(). + * + * Time complexity: O(|V|*a+|E|*log|V|), |V| is the number of vertices, + * |E| is the total number of edges, a is the \p pagebins parameter. + */ + +int igraph_lastcit_game(igraph_t *graph, + igraph_integer_t nodes, igraph_integer_t edges_per_node, + igraph_integer_t pagebins, + const igraph_vector_t *preference, + igraph_bool_t directed) { + + long int no_of_nodes = nodes; + igraph_psumtree_t sumtree; + igraph_vector_t edges; + long int i, j, k; + long int *lastcit; + long int *index; + long int agebins = pagebins; + long int binwidth = no_of_nodes / agebins + 1; + + if (agebins != igraph_vector_size(preference) - 1) { + IGRAPH_ERROR("`preference' vector should be of length `agebins' plus one", + IGRAPH_EINVAL); + } + if (agebins <= 1 ) { + IGRAPH_ERROR("at least two age bins are need for lastcit game", + IGRAPH_EINVAL); + } + if (VECTOR(*preference)[agebins] <= 0) { + IGRAPH_ERROR("the last element of the `preference' vector needs to be positive", + IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + lastcit = igraph_Calloc(no_of_nodes, long int); + if (!lastcit) { + IGRAPH_ERROR("lastcit game failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, lastcit); + + index = igraph_Calloc(no_of_nodes + 1, long int); + if (!index) { + IGRAPH_ERROR("lastcit game failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, index); + + IGRAPH_CHECK(igraph_psumtree_init(&sumtree, nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &sumtree); + IGRAPH_CHECK(igraph_vector_reserve(&edges, nodes * edges_per_node)); + + /* The first node */ + igraph_psumtree_update(&sumtree, 0, VECTOR(*preference)[agebins]); + index[0] = 0; + index[1] = 0; + + RNG_BEGIN(); + + for (i = 1; i < no_of_nodes; i++) { + + /* Add new edges */ + for (j = 0; j < edges_per_node; j++) { + long int to; + igraph_real_t sum = igraph_psumtree_sum(&sumtree); + igraph_psumtree_search(&sumtree, &to, RNG_UNIF(0, sum)); + igraph_vector_push_back(&edges, i); + igraph_vector_push_back(&edges, to); + lastcit[to] = i + 1; + igraph_psumtree_update(&sumtree, to, VECTOR(*preference)[0]); + } + + /* Add the node itself */ + igraph_psumtree_update(&sumtree, i, VECTOR(*preference)[agebins]); + index[i + 1] = index[i] + edges_per_node; + + /* Update the preference of some vertices if they got to another bin. + We need to know the citations of some older vertices, this is in the index. */ + for (k = 1; i - binwidth * k >= 1; k++) { + long int shnode = i - binwidth * k; + long int m = index[shnode], n = index[shnode + 1]; + for (j = 2 * m; j < 2 * n; j += 2) { + long int cnode = (long int) VECTOR(edges)[j + 1]; + if (lastcit[cnode] == shnode + 1) { + igraph_psumtree_update(&sumtree, cnode, VECTOR(*preference)[k]); + } + } + } + + } + + RNG_END(); + + igraph_psumtree_destroy(&sumtree); + igraph_free(index); + igraph_free(lastcit); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_cited_type_game + * \brief Simulate a citation based on vertex types. + * + * Function to create a network based on some vertex categories. This + * function creates a citation network, in each step a single vertex + * and \p edges_per_step citating edges are added, nodes with + * different categories (may) have different probabilities to get + * cited, as given by the \p pref vector. + * + * + * Note that this function might generate networks with multiple edges + * if \p edges_per_step is greater than one. You might want to call + * \ref igraph_simplify() on the result to remove multiple edges. + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the network. + * \param types Numeric vector giving the categories of the vertices, + * so it should contain \p nodes non-negative integer + * numbers. Types are numbered from zero. + * \param pref The attractivity of the different vertex categories in + * a vector. Its length should be the maximum element in \p types + * plus one (types are numbered from zero). + * \param edges_per_step Integer constant, the number of edges to add + * in each time step. + * \param directed Logical constant, whether to create a directed + * network. + * \return Error code. + * + * \sa \ref igraph_citing_cited_type_game() for a bit more general + * game. + * + * Time complexity: O((|V|+|E|)log|V|), |V| and |E| are number of + * vertices and edges, respectively. + */ + +int igraph_cited_type_game(igraph_t *graph, igraph_integer_t nodes, + const igraph_vector_t *types, + const igraph_vector_t *pref, + igraph_integer_t edges_per_step, + igraph_bool_t directed) { + + igraph_vector_t edges; + igraph_vector_t cumsum; + igraph_real_t sum; + long int i, j, nnval, type; + + if (igraph_vector_size(types) != nodes) { + IGRAPH_ERROR("Invalid size of types", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + /* return an empty graph is nodes is zero */ + if (nodes == 0) { + igraph_create(graph, &edges, nodes, directed); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; + } + + IGRAPH_VECTOR_INIT_FINALLY(&cumsum, 2); + IGRAPH_CHECK(igraph_vector_reserve(&cumsum, nodes + 1)); + IGRAPH_CHECK(igraph_vector_reserve(&edges, nodes * edges_per_step)); + + /* first node */ + VECTOR(cumsum)[0] = 0; + type = (long int) VECTOR(*types)[0]; + if (type >= igraph_vector_size(pref)) { + IGRAPH_ERROR("pref is too short for the given types", IGRAPH_EINVAL); + } + nnval = VECTOR(*pref)[type]; + if (nnval < 0) { + IGRAPH_ERROR("pref contains negative entries", IGRAPH_EINVAL); + } + sum = VECTOR(cumsum)[1] = nnval; + + RNG_BEGIN(); + + for (i = 1; i < nodes; i++) { + for (j = 0; j < edges_per_step; j++) { + long int to; + if (sum > 0) { + igraph_vector_binsearch(&cumsum, RNG_UNIF(0, sum), &to); + } else { + to = i + 1; + } + igraph_vector_push_back(&edges, i); + igraph_vector_push_back(&edges, to - 1); + } + type = (long int) VECTOR(*types)[i]; + if (type >= igraph_vector_size(pref)) { + IGRAPH_ERROR("pref is too short for the given types", IGRAPH_EINVAL); + } + nnval = VECTOR(*pref)[type]; + if (nnval < 0) { + IGRAPH_ERROR("pref contains negative entries", IGRAPH_EINVAL); + } + sum += nnval; + igraph_vector_push_back(&cumsum, sum); + } + + RNG_END(); + + igraph_vector_destroy(&cumsum); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static void igraph_i_citing_cited_type_game_free(igraph_i_citing_cited_type_game_struct_t *s) { + long int i; + if (!s->sumtrees) { + return; + } + for (i = 0; i < s->no; i++) { + igraph_psumtree_destroy(&s->sumtrees[i]); + } +} + +/** + * \function igraph_citing_cited_type_game + * \brief Simulate a citation network based on vertex types. + * + * This game is similar to \ref igraph_cited_type_game() but here the + * category of the citing vertex is also considered. + * + * + * An evolving citation network is modeled here, a single vertex and + * its \p edges_per_step citation are added in each time step. The + * odds the a given vertex is cited by the new vertex depends on the + * category of both the citing and the cited vertex and is given in + * the \p pref matrix. The categories of the citing vertex correspond + * to the rows, the categories of the cited vertex to the columns of + * this matrix. Ie. the element in row \c i and column \c j gives the + * probability that a \c j vertex is cited, if the category of the + * citing vertex is \c i. + * + * + * Note that this function might generate networks with multiple edges + * if \p edges_per_step is greater than one. You might want to call + * \ref igraph_simplify() on the result to remove multiple edges. + * \param graph Pointer to an uninitialized graph object. + * \param nodes The number of vertices in the network. + * \param types A numeric matrix of length \p nodes, containing the + * categories of the vertices. The categories are numbered from + * zero. + * \param pref The preference matrix, a square matrix is required, + * both the number of rows and columns should be the maximum + * element in \p types plus one (types are numbered from zero). + * \param directed Logical constant, whether to create a directed + * network. + * \return Error code. + * + * Time complexity: O((|V|+|E|)log|V|), |V| and |E| are number of + * vertices and edges, respectively. + */ + +int igraph_citing_cited_type_game(igraph_t *graph, igraph_integer_t nodes, + const igraph_vector_t *types, + const igraph_matrix_t *pref, + igraph_integer_t edges_per_step, + igraph_bool_t directed) { + + igraph_vector_t edges; + igraph_i_citing_cited_type_game_struct_t str = { 0, 0 }; + igraph_psumtree_t *sumtrees; + igraph_vector_t sums; + long int nocats; + long int i, j; + + if (igraph_vector_size(types) != nodes) { + IGRAPH_ERROR("Invalid size of types", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + /* return an empty graph is nodes is zero */ + if (nodes == 0) { + igraph_create(graph, &edges, nodes, directed); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(2); /* str and edges */ + return 0; + } + + nocats = igraph_matrix_ncol(pref); + str.sumtrees = sumtrees = igraph_Calloc(nocats, igraph_psumtree_t); + if (!sumtrees) { + IGRAPH_ERROR("Citing-cited type game failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_i_citing_cited_type_game_free, &str); + + for (i = 0; i < nocats; i++) { + IGRAPH_CHECK(igraph_psumtree_init(&sumtrees[i], nodes)); + str.no++; + } + IGRAPH_VECTOR_INIT_FINALLY(&sums, nocats); + + IGRAPH_CHECK(igraph_vector_reserve(&edges, nodes * edges_per_step)); + + /* First node */ + for (i = 0; i < nocats; i++) { + long int type = (long int) VECTOR(*types)[0]; + if ( MATRIX(*pref, i, type) < 0) { + IGRAPH_ERROR("pref contains negative entries", IGRAPH_EINVAL); + } + igraph_psumtree_update(&sumtrees[i], 0, MATRIX(*pref, i, type)); + VECTOR(sums)[i] = MATRIX(*pref, i, type); + } + + RNG_BEGIN(); + + for (i = 1; i < nodes; i++) { + long int type = (long int) VECTOR(*types)[i]; + igraph_real_t sum = VECTOR(sums)[type]; + for (j = 0; j < edges_per_step; j++) { + long int to; + igraph_psumtree_search(&sumtrees[type], &to, RNG_UNIF(0, sum)); + igraph_vector_push_back(&edges, i); + igraph_vector_push_back(&edges, to); + } + + /* add i */ + for (j = 0; j < nocats; j++) { + if ( MATRIX(*pref, j, type) < 0) { + IGRAPH_ERROR("pref contains negative entries", IGRAPH_EINVAL); + } + igraph_psumtree_update(&sumtrees[j], i, MATRIX(*pref, j, type)); + VECTOR(sums)[j] += MATRIX(*pref, j, type); + } + } + + RNG_END(); + + igraph_i_citing_cited_type_game_free(&str); + IGRAPH_FINALLY_CLEAN(1); + + igraph_create(graph, &edges, nodes, directed); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + + + +/** + * \ingroup generators + * \function igraph_simple_interconnected_islands_game + * \brief Generates a random graph made of several interconnected islands, each island being a random graph. + * + * \param graph Pointer to an uninitialized graph object. + * \param islands_n The number of islands in the graph. + * \param islands_size The size of islands in the graph. + * \param islands_pin The probability to create each possible edge into each island . + * \param n_inter The number of edges to create between two islands . + + * \return Error code: + * \c IGRAPH_EINVAL: invalid parameter + * \c IGRAPH_ENOMEM: there is not enough + * memory for the operation. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + */ +int igraph_simple_interconnected_islands_game( + igraph_t *graph, + igraph_integer_t islands_n, + igraph_integer_t islands_size, + igraph_real_t islands_pin, + igraph_integer_t n_inter) { + + + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + igraph_vector_t s = IGRAPH_VECTOR_NULL; + int retval = 0; + int nbNodes; + double maxpossibleedgesPerIsland; + double maxedgesPerIsland; + int nbEdgesInterIslands; + double maxedges; + int startIsland = 0; + int endIsland = 0; + int i, j, is; + double myrand, last; + + if (islands_n < 0) { + IGRAPH_ERROR("Invalid number of islands", IGRAPH_EINVAL); + } + if (islands_size < 0) { + IGRAPH_ERROR("Invalid size for islands", IGRAPH_EINVAL); + } + if (islands_pin < 0 || islands_pin > 1) { + IGRAPH_ERROR("Invalid probability for islands", IGRAPH_EINVAL); + } + if ( (n_inter < 0) || (n_inter > islands_size) ) { + IGRAPH_ERROR("Invalid number of inter-islands links", IGRAPH_EINVAL); + } + + // how much memory ? + nbNodes = islands_n * islands_size; + maxpossibleedgesPerIsland = ((double)islands_size * ((double)islands_size - (double)1)) / (double)2; + maxedgesPerIsland = islands_pin * maxpossibleedgesPerIsland; + nbEdgesInterIslands = n_inter * (islands_n * (islands_n - 1)) / 2; + maxedges = maxedgesPerIsland * islands_n + nbEdgesInterIslands; + + // debug&tests : printf("total nodes %d, maxedgesperisland %f, maxedgesinterislands %d, maxedges %f\n", nbNodes, maxedgesPerIsland, nbEdgesInterIslands, maxedges); + + // reserve enough place for all the edges, thanks ! + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, (long int) maxedges)); + + RNG_BEGIN(); + + // first create all the islands + for (is = 1; is <= islands_n; is++) { // for each island + + // index for start and end of nodes in this island + startIsland = islands_size * (is - 1); + endIsland = startIsland + islands_size - 1; + + + // debug&tests : printf("start %d,end %d\n", startIsland, endIsland); + + // create the random numbers to be used (into s) + IGRAPH_VECTOR_INIT_FINALLY(&s, 0); + IGRAPH_CHECK(igraph_vector_reserve(&s, (long int) maxedgesPerIsland)); + + last = RNG_GEOM(islands_pin); + // debug&tests : printf("last=%f \n", last); + while (last < maxpossibleedgesPerIsland) { // maxedgesPerIsland + IGRAPH_CHECK(igraph_vector_push_back(&s, last)); + myrand = RNG_GEOM(islands_pin); + last += myrand; //RNG_GEOM(islands_pin); + //printf("myrand=%f , last=%f \n", myrand, last); + last += 1; + } + + + + // change this to edges ! + for (i = 0; i < igraph_vector_size(&s); i++) { + + long int to = (long int) floor((sqrt(8 * VECTOR(s)[i] + 1) + 1) / 2); + long int from = (long int) (VECTOR(s)[i] - (((igraph_real_t)to) * (to - 1)) / 2); + to += startIsland; + from += startIsland; + // debug&tests : printf("from %d to %d\n", from, to); + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } + + // clear the memory used for random number for this island + igraph_vector_destroy(&s); + IGRAPH_FINALLY_CLEAN(1); + + + // create the links with other islands + for (i = is + 1; i <= islands_n; i++) { // for each other island (not the previous ones) + + // debug&tests : printf("link islands %d and %d\n", is, i); + for (j = 0; j < n_inter; j++) { // for each link between islands + + long int from = (long int) RNG_UNIF(startIsland, endIsland); + long int to = (long int) RNG_UNIF((i - 1) * islands_size, i * islands_size); + //printf("from %d to %d\n", from, to); + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } + + } + } + + RNG_END(); + + // actually fill the graph object + IGRAPH_CHECK(retval = igraph_create(graph, &edges, nbNodes, 0)); + + // an clear remaining things + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return retval; +} + + +/** + * \ingroup generators + * \function igraph_static_fitness_game + * \brief Generates a non-growing random graph with edge probabilities + * proportional to node fitness scores. + * + * This game generates a directed or undirected random graph where the + * probability of an edge between vertices i and j depends on the fitness + * scores of the two vertices involved. For undirected graphs, each vertex + * has a single fitness score. For directed graphs, each vertex has an out- + * and an in-fitness, and the probability of an edge from i to j depends on + * the out-fitness of vertex i and the in-fitness of vertex j. + * + * + * The generation process goes as follows. We start from N disconnected nodes + * (where N is given by the length of the fitness vector). Then we randomly + * select two vertices i and j, with probabilities proportional to their + * fitnesses. (When the generated graph is directed, i is selected according to + * the out-fitnesses and j is selected according to the in-fitnesses). If the + * vertices are not connected yet (or if multiple edges are allowed), we + * connect them; otherwise we select a new pair. This is repeated until the + * desired number of links are created. + * + * + * It can be shown that the \em expected degree of each vertex will be + * proportional to its fitness, although the actual, observed degree will not + * be. If you need to generate a graph with an exact degree sequence, consider + * \ref igraph_degree_sequence_game instead. + * + * + * This model is commonly used to generate static scale-free networks. To + * achieve this, you have to draw the fitness scores from the desired power-law + * distribution. Alternatively, you may use \ref igraph_static_power_law_game + * which generates the fitnesses for you with a given exponent. + * + * + * Reference: Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution + * in scale-free networks. Phys Rev Lett 87(27):278701, 2001. + * + * \param graph Pointer to an uninitialized graph object. + * \param fitness_out A numeric vector containing the fitness of each vertex. + * For directed graphs, this specifies the out-fitness + * of each vertex. + * \param fitness_in If \c NULL, the generated graph will be undirected. + * If not \c NULL, this argument specifies the in-fitness + * of each vertex. + * \param no_of_edges The number of edges in the generated graph. + * \param loops Whether to allow loop edges in the generated graph. + * \param multiple Whether to allow multiple edges in the generated graph. + * + * \return Error code: + * \c IGRAPH_EINVAL: invalid parameter + * \c IGRAPH_ENOMEM: there is not enough + * memory for the operation. + * + * Time complexity: O(|V| + |E| log |E|). + */ +int igraph_static_fitness_game(igraph_t *graph, igraph_integer_t no_of_edges, + igraph_vector_t* fitness_out, igraph_vector_t* fitness_in, + igraph_bool_t loops, igraph_bool_t multiple) { + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + igraph_integer_t no_of_nodes; + igraph_integer_t outnodes, innodes, nodes; + igraph_vector_t cum_fitness_in, cum_fitness_out; + igraph_vector_t *p_cum_fitness_in, *p_cum_fitness_out; + igraph_real_t x, max_in, max_out; + igraph_real_t max_no_of_edges; + igraph_bool_t is_directed = (fitness_in != 0); + float num_steps; + igraph_integer_t step_counter = 0; + long int i, from, to, pos; + + if (fitness_out == 0) { + IGRAPH_ERROR("fitness_out must not be null", IGRAPH_EINVAL); + } + + if (no_of_edges < 0) { + IGRAPH_ERROR("Invalid number of edges", IGRAPH_EINVAL); + } + + no_of_nodes = (int) igraph_vector_size(fitness_out); + if (no_of_nodes == 0) { + IGRAPH_CHECK(igraph_empty(graph, 0, is_directed)); + return IGRAPH_SUCCESS; + } + + if (is_directed && igraph_vector_size(fitness_in) != no_of_nodes) { + IGRAPH_ERROR("fitness_in must have the same size as fitness_out", IGRAPH_EINVAL); + } + + /* Sanity checks for the fitnesses */ + if (igraph_vector_min(fitness_out) < 0) { + IGRAPH_ERROR("Fitness scores must be non-negative", IGRAPH_EINVAL); + } + if (fitness_in != 0 && igraph_vector_min(fitness_in) < 0) { + IGRAPH_ERROR("Fitness scores must be non-negative", IGRAPH_EINVAL); + } + + /* Avoid getting into an infinite loop when too many edges are requested */ + if (!multiple) { + if (is_directed) { + outnodes = innodes = nodes = 0; + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*fitness_out)[i] != 0) { + outnodes++; + } + if (VECTOR(*fitness_in)[i] != 0) { + innodes++; + } + if (VECTOR(*fitness_out)[i] != 0 && VECTOR(*fitness_in)[i] != 0) { + nodes++; + } + } + max_no_of_edges = ((igraph_real_t) outnodes) * innodes - (loops ? 0 : nodes); + } else { + nodes = 0; + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*fitness_out)[i] != 0) { + nodes++; + } + } + max_no_of_edges = loops + ? nodes * ((igraph_real_t)nodes + 1) / 2 + : nodes * ((igraph_real_t)nodes - 1) / 2; + } + if (no_of_edges > max_no_of_edges) { + IGRAPH_ERROR("Too many edges requested", IGRAPH_EINVAL); + } + } + + /* Calculate the cumulative fitness scores */ + IGRAPH_VECTOR_INIT_FINALLY(&cum_fitness_out, no_of_nodes); + IGRAPH_CHECK(igraph_vector_cumsum(&cum_fitness_out, fitness_out)); + max_out = igraph_vector_tail(&cum_fitness_out); + p_cum_fitness_out = &cum_fitness_out; + if (is_directed) { + IGRAPH_VECTOR_INIT_FINALLY(&cum_fitness_in, no_of_nodes); + IGRAPH_CHECK(igraph_vector_cumsum(&cum_fitness_in, fitness_in)); + max_in = igraph_vector_tail(&cum_fitness_in); + p_cum_fitness_in = &cum_fitness_in; + } else { + max_in = max_out; + p_cum_fitness_in = &cum_fitness_out; + } + + RNG_BEGIN(); + num_steps = no_of_edges; + if (multiple) { + /* Generating when multiple edges are allowed */ + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, 2 * no_of_edges)); + + while (no_of_edges > 0) { + /* Report progress after every 10000 edges */ + if ((step_counter++) % 10000 == 0) { + IGRAPH_PROGRESS("Static fitness game", 100.0 * (1 - no_of_edges / num_steps), NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + + x = RNG_UNIF(0, max_out); + igraph_vector_binsearch(p_cum_fitness_out, x, &from); + x = RNG_UNIF(0, max_in); + igraph_vector_binsearch(p_cum_fitness_in, x, &to); + + /* Skip if loop edge and loops = false */ + if (!loops && from == to) { + continue; + } + + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + + no_of_edges--; + } + + /* Create the graph */ + IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, is_directed)); + + /* Clear the edge list */ + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + } else { + /* Multiple edges are disallowed */ + igraph_adjlist_t al; + igraph_vector_int_t* neis; + + IGRAPH_CHECK(igraph_adjlist_init_empty(&al, no_of_nodes)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + while (no_of_edges > 0) { + /* Report progress after every 10000 edges */ + if ((step_counter++) % 10000 == 0) { + IGRAPH_PROGRESS("Static fitness game", 100.0 * (1 - no_of_edges / num_steps), NULL); + IGRAPH_ALLOW_INTERRUPTION(); + } + + x = RNG_UNIF(0, max_out); + igraph_vector_binsearch(p_cum_fitness_out, x, &from); + x = RNG_UNIF(0, max_in); + igraph_vector_binsearch(p_cum_fitness_in, x, &to); + + /* Skip if loop edge and loops = false */ + if (!loops && from == to) { + continue; + } + + /* For undirected graphs, ensure that from < to */ + if (!is_directed && from > to) { + pos = from; from = to; to = pos; + } + + /* Is there already an edge? If so, try again */ + neis = igraph_adjlist_get(&al, from); + if (igraph_vector_int_binsearch(neis, to, &pos)) { + continue; + } + + /* Insert the edge */ + IGRAPH_CHECK(igraph_vector_int_insert(neis, pos, to)); + + no_of_edges--; + } + + /* Create the graph. We cannot use IGRAPH_ALL here for undirected graphs + * because we did not add edges in both directions in the adjacency list. + * We will use igraph_to_undirected in an extra step. */ + IGRAPH_CHECK(igraph_adjlist(graph, &al, IGRAPH_OUT, 1)); + if (!is_directed) { + IGRAPH_CHECK(igraph_to_undirected(graph, IGRAPH_TO_UNDIRECTED_EACH, 0)); + } + + /* Clear the adjacency list */ + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(1); + } + RNG_END(); + + IGRAPH_PROGRESS("Static fitness game", 100.0, NULL); + + /* Cleanup before we create the graph */ + if (is_directed) { + igraph_vector_destroy(&cum_fitness_in); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&cum_fitness_out); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup generators + * \function igraph_static_power_law_game + * \brief Generates a non-growing random graph with expected power-law degree distributions. + * + * This game generates a directed or undirected random graph where the + * degrees of vertices follow power-law distributions with prescribed + * exponents. For directed graphs, the exponents of the in- and out-degree + * distributions may be specified separately. + * + * + * The game simply uses \ref igraph_static_fitness_game with appropriately + * constructed fitness vectors. In particular, the fitness of vertex i + * is i-alpha, where alpha = 1/(gamma-1) + * and gamma is the exponent given in the arguments. + * + * + * To remove correlations between in- and out-degrees in case of directed + * graphs, the in-fitness vector will be shuffled after it has been set up + * and before \ref igraph_static_fitness_game is called. + * + * + * Note that significant finite size effects may be observed for exponents + * smaller than 3 in the original formulation of the game. This function + * provides an argument that lets you remove the finite size effects by + * assuming that the fitness of vertex i is + * (i+i0-1)-alpha, + * where i0 is a constant chosen appropriately to ensure that the maximum + * degree is less than the square root of the number of edges times the + * average degree; see the paper of Chung and Lu, and Cho et al for more + * details. + * + * + * References: + * + * + * Goh K-I, Kahng B, Kim D: Universal behaviour of load distribution + * in scale-free networks. Phys Rev Lett 87(27):278701, 2001. + * + * + * Chung F and Lu L: Connected components in a random graph with given + * degree sequences. Annals of Combinatorics 6, 125-145, 2002. + * + * + * Cho YS, Kim JS, Park J, Kahng B, Kim D: Percolation transitions in + * scale-free networks under the Achlioptas process. Phys Rev Lett + * 103:135702, 2009. + * + * \param graph Pointer to an uninitialized graph object. + * \param no_of_nodes The number of nodes in the generated graph. + * \param no_of_edges The number of edges in the generated graph. + * \param exponent_out The power law exponent of the degree distribution. + * For directed graphs, this specifies the exponent of the + * out-degree distribution. It must be greater than or + * equal to 2. If you pass \c IGRAPH_INFINITY here, you + * will get back an Erdos-Renyi random network. + * \param exponent_in If negative, the generated graph will be undirected. + * If greater than or equal to 2, this argument specifies + * the exponent of the in-degree distribution. If + * non-negative but less than 2, an error will be + * generated. + * \param loops Whether to allow loop edges in the generated graph. + * \param multiple Whether to allow multiple edges in the generated graph. + * \param finite_size_correction Whether to use the proposed finite size + * correction of Cho et al. + * + * \return Error code: + * \c IGRAPH_EINVAL: invalid parameter + * \c IGRAPH_ENOMEM: there is not enough + * memory for the operation. + * + * Time complexity: O(|V| + |E| log |E|). + */ +int igraph_static_power_law_game(igraph_t *graph, + igraph_integer_t no_of_nodes, igraph_integer_t no_of_edges, + igraph_real_t exponent_out, igraph_real_t exponent_in, + igraph_bool_t loops, igraph_bool_t multiple, + igraph_bool_t finite_size_correction) { + + igraph_vector_t fitness_out, fitness_in; + igraph_real_t alpha_out = 0.0, alpha_in = 0.0; + long int i; + igraph_real_t j; + + if (no_of_nodes < 0) { + IGRAPH_ERROR("Invalid number of nodes", IGRAPH_EINVAL); + } + + /* Calculate alpha_out */ + if (exponent_out < 2) { + IGRAPH_ERROR("out-degree exponent must be >= 2", IGRAPH_EINVAL); + } else if (igraph_finite(exponent_out)) { + alpha_out = -1.0 / (exponent_out - 1); + } else { + alpha_out = 0.0; + } + + /* Construct the out-fitnesses */ + IGRAPH_VECTOR_INIT_FINALLY(&fitness_out, no_of_nodes); + j = no_of_nodes; + if (finite_size_correction && alpha_out < -0.5) { + /* See the Cho et al paper, first page first column + footnote 7 */ + j += pow(no_of_nodes, 1 + 0.5 / alpha_out) * + pow(10 * sqrt(2) * (1 + alpha_out), -1.0 / alpha_out) - 1; + } + if (j < no_of_nodes) { + j = no_of_nodes; + } + for (i = 0; i < no_of_nodes; i++, j--) { + VECTOR(fitness_out)[i] = pow(j, alpha_out); + } + + if (exponent_in >= 0) { + if (exponent_in < 2) { + IGRAPH_ERROR("in-degree exponent must be >= 2; use negative numbers " + "for undirected graphs", IGRAPH_EINVAL); + } else if (igraph_finite(exponent_in)) { + alpha_in = -1.0 / (exponent_in - 1); + } else { + alpha_in = 0.0; + } + + IGRAPH_VECTOR_INIT_FINALLY(&fitness_in, no_of_nodes); + j = no_of_nodes; + if (finite_size_correction && alpha_in < -0.5) { + /* See the Cho et al paper, first page first column + footnote 7 */ + j += pow(no_of_nodes, 1 + 0.5 / alpha_in) * + pow(10 * sqrt(2) * (1 + alpha_in), -1.0 / alpha_in) - 1; + } + if (j < no_of_nodes) { + j = no_of_nodes; + } + for (i = 0; i < no_of_nodes; i++, j--) { + VECTOR(fitness_in)[i] = pow(j, alpha_in); + } + IGRAPH_CHECK(igraph_vector_shuffle(&fitness_in)); + + IGRAPH_CHECK(igraph_static_fitness_game(graph, no_of_edges, + &fitness_out, &fitness_in, loops, multiple)); + + igraph_vector_destroy(&fitness_in); + IGRAPH_FINALLY_CLEAN(1); + } else { + IGRAPH_CHECK(igraph_static_fitness_game(graph, no_of_edges, + &fitness_out, 0, loops, multiple)); + } + + igraph_vector_destroy(&fitness_out); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup generators + * \function igraph_k_regular_game + * \brief Generates a random graph where each vertex has the same degree. + * + * This game generates a directed or undirected random graph where the + * degrees of vertices are equal to a predefined constant k. For undirected + * graphs, at least one of k and the number of vertices must be even. + * + * + * The game simply uses \ref igraph_degree_sequence_game with appropriately + * constructed degree sequences. + * + * \param graph Pointer to an uninitialized graph object. + * \param no_of_nodes The number of nodes in the generated graph. + * \param k The degree of each vertex in an undirected graph, or + * the out-degree and in-degree of each vertex in a + * directed graph. + * \param directed Whether the generated graph will be directed. + * \param multiple Whether to allow multiple edges in the generated graph. + * + * \return Error code: + * \c IGRAPH_EINVAL: invalid parameter; e.g., negative number of nodes, + * or odd number of nodes and odd k for undirected + * graphs. + * \c IGRAPH_ENOMEM: there is not enough memory for the operation. + * + * Time complexity: O(|V|+|E|) if \c multiple is true, otherwise not known. + */ +int igraph_k_regular_game(igraph_t *graph, + igraph_integer_t no_of_nodes, igraph_integer_t k, + igraph_bool_t directed, igraph_bool_t multiple) { + igraph_vector_t degseq; + igraph_degseq_t mode = multiple ? IGRAPH_DEGSEQ_SIMPLE : IGRAPH_DEGSEQ_SIMPLE_NO_MULTIPLE; + + /* Note to self: we are not using IGRAPH_DEGSEQ_VL when multiple = false + * because the VL method is not really good at generating k-regular graphs. + * Actually, that's why we have added SIMPLE_NO_MULTIPLE. */ + + if (no_of_nodes < 0) { + IGRAPH_ERROR("number of nodes must be non-negative", IGRAPH_EINVAL); + } + if (k < 0) { + IGRAPH_ERROR("degree must be non-negative", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(°seq, no_of_nodes); + igraph_vector_fill(°seq, k); + IGRAPH_CHECK(igraph_degree_sequence_game(graph, °seq, directed ? °seq : 0, mode)); + + igraph_vector_destroy(°seq); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_correlated_game + * Generate pairs of correlated random graphs + * + * Sample a new graph by perturbing the adjacency matrix of a + * given graph and shuffling its vertices. + * + * \param old_graph The original graph. + * \param new_graph The new graph will be stored here. + * \param corr A scalar in the unit interval, the target Pearson + * correlation between the adjacency matrices of the original the + * generated graph (the adjacency matrix being used as a vector). + * \param p A numeric scalar, the probability of an edge between two + * vertices, it must in the open (0,1) interval. + * \param permutation A permutation to apply to the vertices of the + * generated graph. It can also be a null pointer, in which case + * the vertices will not be permuted. + * \return Error code + * + * \sa \ref igraph_correlated_pair_game() for generating a pair + * of correlated random graphs in one go. + */ + +int igraph_correlated_game(const igraph_t *old_graph, igraph_t *new_graph, + igraph_real_t corr, igraph_real_t p, + const igraph_vector_t *permutation) { + + int no_of_nodes = igraph_vcount(old_graph); + int no_of_edges = igraph_ecount(old_graph); + igraph_bool_t directed = igraph_is_directed(old_graph); + igraph_real_t no_of_all = directed ? no_of_nodes * (no_of_nodes - 1) : + no_of_nodes * (no_of_nodes - 1) / 2; + igraph_real_t no_of_missing = no_of_all - no_of_edges; + igraph_real_t q = p + corr * (1 - p); + igraph_real_t p_del = 1 - q; + igraph_real_t p_add = ((1 - q) * (p / (1 - p))); + igraph_vector_t add, delete, edges, newedges; + igraph_real_t last; + int p_e = 0, p_a = 0, p_d = 0, no_add, no_del; + igraph_real_t inf = IGRAPH_INFINITY; + igraph_real_t next_e, next_a, next_d; + int i; + + if (corr < -1 || corr > 1) { + IGRAPH_ERROR("Correlation must be in [-1,1] in correlated " + "Erdos-Renyi game", IGRAPH_EINVAL); + } + if (p <= 0 || p >= 1) { + IGRAPH_ERROR("Edge probability must be in (0,1) in correlated " + "Erdos-Renyi game", IGRAPH_EINVAL); + } + if (permutation) { + if (igraph_vector_size(permutation) != no_of_nodes) { + IGRAPH_ERROR("Invalid permutation length in correlated Erdos-Renyi game", + IGRAPH_EINVAL); + } + } + + /* Special cases */ + + if (corr == 0) { + return igraph_erdos_renyi_game(new_graph, IGRAPH_ERDOS_RENYI_GNP, + no_of_nodes, p, directed, + IGRAPH_NO_LOOPS); + } + if (corr == 1) { + /* We don't copy, because we don't need the attributes.... */ + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_get_edgelist(old_graph, &edges, /* bycol= */ 0)); + if (permutation) { + int newec = igraph_vector_size(&edges); + for (i = 0; i < newec; i++) { + int tmp = VECTOR(edges)[i]; + VECTOR(edges)[i] = VECTOR(*permutation)[tmp]; + } + } + IGRAPH_CHECK(igraph_create(new_graph, &edges, no_of_nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; + } + + IGRAPH_VECTOR_INIT_FINALLY(&newedges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&add, 0); + IGRAPH_VECTOR_INIT_FINALLY(&delete, 0); + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + + IGRAPH_CHECK(igraph_get_edgelist(old_graph, &edges, /* bycol= */ 0)); + + RNG_BEGIN(); + + if (p_del > 0) { + last = RNG_GEOM(p_del); + while (last < no_of_edges) { + IGRAPH_CHECK(igraph_vector_push_back(&delete, last)); + last += RNG_GEOM(p_del); + last += 1; + } + } + no_del = igraph_vector_size(&delete); + + if (p_add > 0) { + last = RNG_GEOM(p_add); + while (last < no_of_missing) { + IGRAPH_CHECK(igraph_vector_push_back(&add, last)); + last += RNG_GEOM(p_add); + last += 1; + } + } + no_add = igraph_vector_size(&add); + + RNG_END(); + + IGRAPH_CHECK(igraph_get_edgelist(old_graph, &edges, /* bycol= */ 0)); + + /* Now we are merging the original edges, the edges that are removed, + and the new edges. We have the following pointers: + - p_a: the next edge to add + - p_d: the next edge to delete + - p_e: the next original edge + - next_e: the code of the next edge in 'edges' + - next_a: the code of the next edge to add + - next_d: the code of the next edge to delete */ + +#define D_CODE(f,t) (((t)==no_of_nodes-1 ? f : t) * no_of_nodes + (f)) +#define U_CODE(f,t) ((t) * ((t)-1) / 2 + (f)) +#define CODE(f,t) (directed ? D_CODE(f,t) : U_CODE(f,t)) +#define CODEE() (CODE(VECTOR(edges)[2*p_e], VECTOR(edges)[2*p_e+1])) + + /* First we (re)code the edges to delete */ + + for (i = 0; i < no_del; i++) { + int td = VECTOR(delete)[i]; + int from = VECTOR(edges)[2 * td]; + int to = VECTOR(edges)[2 * td + 1]; + VECTOR(delete)[i] = CODE(from, to); + } + + IGRAPH_CHECK(igraph_vector_reserve(&newedges, + (no_of_edges - no_del + no_add) * 2)); + + /* Now we can do the merge. Additional edges are tricky, because + the code must be shifted by the edges in the original graph. */ + +#define UPD_E() \ + { if (p_e < no_of_edges) { next_e=CODEE(); } else { next_e = inf; } } +#define UPD_A() \ +{ if (p_a < no_add) { \ + next_a = VECTOR(add)[p_a] + p_e; } else { next_a = inf; } } +#define UPD_D() \ +{ if (p_d < no_del) { \ + next_d = VECTOR(delete)[p_d]; } else { next_d = inf; } } + + UPD_E(); UPD_A(); UPD_D(); + + while (next_e != inf || next_a != inf || next_d != inf) { + if (next_e <= next_a && next_e < next_d) { + + /* keep an edge */ + IGRAPH_CHECK(igraph_vector_push_back(&newedges, VECTOR(edges)[2 * p_e])); + IGRAPH_CHECK(igraph_vector_push_back(&newedges, VECTOR(edges)[2 * p_e + 1])); + p_e ++; UPD_E(); UPD_A() + + } else if (next_e <= next_a && next_e == next_d) { + + /* delete an edge */ + p_e ++; UPD_E(); UPD_A(); + p_d++; UPD_D(); + + } else { + + /* add an edge */ + int to, from; + if (directed) { + to = (int) floor(next_a / no_of_nodes); + from = (int) (next_a - ((igraph_real_t)to) * no_of_nodes); + if (from == to) { + to = no_of_nodes - 1; + } + } else { + to = (int) floor((sqrt(8 * next_a + 1) + 1) / 2); + from = (int) (next_a - (((igraph_real_t)to) * (to - 1)) / 2); + } + IGRAPH_CHECK(igraph_vector_push_back(&newedges, from)); + IGRAPH_CHECK(igraph_vector_push_back(&newedges, to)); + p_a++; UPD_A(); + + } + } + + igraph_vector_destroy(&edges); + igraph_vector_destroy(&add); + igraph_vector_destroy(&delete); + IGRAPH_FINALLY_CLEAN(3); + + if (permutation) { + int newec = igraph_vector_size(&newedges); + for (i = 0; i < newec; i++) { + int tmp = VECTOR(newedges)[i]; + VECTOR(newedges)[i] = VECTOR(*permutation)[tmp]; + } + } + + IGRAPH_CHECK(igraph_create(new_graph, &newedges, no_of_nodes, directed)); + + igraph_vector_destroy(&newedges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +#undef D_CODE +#undef U_CODE +#undef CODE +#undef CODEE +#undef UPD_E +#undef UPD_A +#undef UPD_D + +/** + * \function igraph_correlated_pair_game + * Generate pairs of correlated random graphs + * + * Sample two random graphs, with given correlation. + * + * \param graph1 The first graph will be stored here. + * \param graph2 The second graph will be stored here. + * \param n The number of vertices in both graphs. + * \param corr A scalar in the unit interval, the target Pearson + * correlation between the adjacency matrices of the original the + * generated graph (the adjacency matrix being used as a vector). + * \param p A numeric scalar, the probability of an edge between two + * vertices, it must in the open (0,1) interval. + * \param directed Whether to generate directed graphs. + * \param permutation A permutation to apply to the vertices of the + * second graph. It can also be a null pointer, in which case + * the vertices will not be permuted. + * \return Error code + * + * \sa \ref igraph_correlated_game() for generating a correlated pair + * to a given graph. + */ + +int igraph_correlated_pair_game(igraph_t *graph1, igraph_t *graph2, + int n, igraph_real_t corr, igraph_real_t p, + igraph_bool_t directed, + const igraph_vector_t *permutation) { + + IGRAPH_CHECK(igraph_erdos_renyi_game(graph1, IGRAPH_ERDOS_RENYI_GNP, n, p, + directed, IGRAPH_NO_LOOPS)); + IGRAPH_CHECK(igraph_correlated_game(graph1, graph2, corr, p, permutation)); + return 0; +} + + +/* Uniform sampling of labelled trees (igraph_tree_game) */ + +/* The following implementation uniformly samples Prufer trees and converts + * them to trees. + */ + +static int igraph_i_tree_game_prufer(igraph_t *graph, igraph_integer_t n, igraph_bool_t directed) { + igraph_vector_int_t prufer; + long i; + + if (directed) { + IGRAPH_ERROR("The Prufer method for random tree generation does not support directed trees", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_int_init(&prufer, n - 2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &prufer); + + RNG_BEGIN(); + + for (i = 0; i < n - 2; ++i) { + VECTOR(prufer)[i] = RNG_INTEGER(0, n - 1); + } + + RNG_END(); + + IGRAPH_CHECK(igraph_from_prufer(graph, &prufer)); + + igraph_vector_int_destroy(&prufer); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/* The following implementation is based on loop-erased random walks and Wilson's algorithm + * for uniformly sampling spanning trees. We effectively sample spanning trees of the complete + * graph. + */ + +/* swap two elements of a vector_int */ +#define SWAP_INT_ELEM(vec, i, j) \ + { \ + igraph_integer_t temp; \ + temp = VECTOR(vec)[i]; \ + VECTOR(vec)[i] = VECTOR(vec)[j]; \ + VECTOR(vec)[j] = temp; \ + } + +static int igraph_i_tree_game_loop_erased_random_walk(igraph_t *graph, igraph_integer_t n, igraph_bool_t directed) { + igraph_vector_t edges; + igraph_vector_int_t vertices; + igraph_vector_bool_t visited; + long i, j, k; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 2 * (n - 1)); + + IGRAPH_CHECK(igraph_vector_bool_init(&visited, n)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &visited); + + /* The vertices vector contains visited vertices between 0..k-1, unvisited ones between k..n-1. */ + IGRAPH_CHECK(igraph_vector_int_init_seq(&vertices, 0, n - 1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &vertices); + + RNG_BEGIN(); + + /* A simple implementation could be as below. This is for illustration only. + * The actually implemented algorithm avoids unnecessary walking on the already visited + * portion of the vertex set. + */ + /* + // pick starting point for the walk + i = RNG_INTEGER(0, n-1); + VECTOR(visited)[i] = 1; + + k=1; + while (k < n) { + // pick next vertex in the walk + j = RNG_INTEGER(0, n-1); + // if it has not been visited before, connect to the previous vertex in the sequence + if (! VECTOR(visited)[j]) { + VECTOR(edges)[2*k - 2] = i; + VECTOR(edges)[2*k - 1] = j; + VECTOR(visited)[j] = 1; + k++; + } + i=j; + } + */ + + i = RNG_INTEGER(0, n - 1); + VECTOR(visited)[i] = 1; + SWAP_INT_ELEM(vertices, 0, i); + + for (k = 1; k < n; ++k) { + j = RNG_INTEGER(0, n - 1); + if (VECTOR(visited)[VECTOR(vertices)[j]]) { + i = VECTOR(vertices)[j]; + j = RNG_INTEGER(k, n - 1); + } + VECTOR(visited)[VECTOR(vertices)[j]] = 1; + SWAP_INT_ELEM(vertices, k, j); + VECTOR(edges)[2 * k - 2] = i; + i = VECTOR(vertices)[k]; + VECTOR(edges)[2 * k - 1] = i; + } + + RNG_END(); + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + + igraph_vector_int_destroy(&vertices); + igraph_vector_bool_destroy(&visited); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +#undef SWAP_INT_ELEM + +/** + * \ingroup generators + * \function igraph_tree_game + * \brief Generates a random tree with the given number of nodes + * + * This function samples uniformly from the set of labelled trees, + * i.e. it can generate each labelled tree with the same probability. + * + * \param graph Pointer to an uninitialized graph object. + * \param n The number of nodes in the tree. + * \param directed Whether to create a directed tree. The edges are oriented away from the root. + * \param method The algorithm to use to generate the tree. Possible values: + * \clist + * \cli IGRAPH_RANDOM_TREE_PRUFER + * This algorithm samples Prüfer sequences unformly, then converts them to trees. + * Directed trees are not currently supported. + * \cli IGRAPH_RANDOM_LERW + * This algorithm effectively performs a loop-erased random walk on the complete graph + * to uniformly sample its spanning trees (Wilson's algorithm). + * \endclist + * \return Error code: + * \c IGRAPH_ENOMEM: there is not enough + * memory to perform the operation. + * \c IGRAPH_EINVAL: invalid tree size + * + * \sa \ref igraph_from_prufer() + * + */ + +int igraph_tree_game(igraph_t *graph, igraph_integer_t n, igraph_bool_t directed, igraph_random_tree_t method) { + if (n < 2) { + IGRAPH_CHECK(igraph_empty(graph, n, directed)); + return IGRAPH_SUCCESS; + } + + switch (method) { + case IGRAPH_RANDOM_TREE_PRUFER: + return igraph_i_tree_game_prufer(graph, n, directed); + case IGRAPH_RANDOM_TREE_LERW: + return igraph_i_tree_game_loop_erased_random_walk(graph, n, directed); + default: + IGRAPH_ERROR("Invalid method for random tree construction", IGRAPH_EINVAL); + } +} diff --git a/src/gengraph_box_list.cpp b/src/gengraph_box_list.cpp new file mode 100644 index 0000000..bcbe18e --- /dev/null +++ b/src/gengraph_box_list.cpp @@ -0,0 +1,108 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gengraph_box_list.h" +#include + +namespace gengraph { + +void box_list::insert(int v) { + register int d = deg[v]; + if (d < 1) { + return; + } + if (d > dmax) { + dmax = d; + } + int yo = list[d - 1]; + list[d - 1] = v; + prev[v] = -1; + next[v] = yo; + if (yo >= 0) { + prev[yo] = v; + } +} + +void box_list::pop(int v) { + register int p = prev[v]; + register int n = next[v]; + if (p < 0) { + register int d = deg[v]; + assert(list[d - 1] == v); + list[d - 1] = n; + if (d == dmax && n < 0) do { + dmax--; + } while (dmax > 0 && list[dmax - 1] < 0); + } else { + next[p] = n; + } + if (n >= 0) { + prev[n] = p; + } +} + +box_list::box_list(int n0, int *deg0) : n(n0), deg(deg0) { + next = new int[n]; + prev = new int[n]; + dmax = -1; + int i; + for (i = 0; i < n; i++) if (deg[i] > dmax) { + dmax = deg[i]; + } + list = new int[dmax]; + for (i = 0; i < dmax; i++) { + list[i] = -1; + } + for (i = 0; i < n; i++) { + insert(i); + } +} + +box_list::~box_list() { + delete[] prev; + delete[] next; + delete[] list; +} + +void box_list::pop_vertex(int v, int **neigh) { + int k = deg[v]; + if (k < 1) { + return; + } + pop(v); + int *w = neigh[v]; + while (k--) { + int v2 = *(w++); + register int *w2 = neigh[v2]; + while (*w2 != v) { + w2++; + } + register int *w3 = neigh[v2] + (deg[v2] - 1); + assert(w2 <= w3); + register int tmp = *w3; + *w3 = *w2; + *w2 = tmp; + pop(v2); + deg[v2]--; + insert(v2); + } +} + +} // namespace gengraph diff --git a/src/gengraph_box_list.h b/src/gengraph_box_list.h new file mode 100644 index 0000000..073d21e --- /dev/null +++ b/src/gengraph_box_list.h @@ -0,0 +1,89 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +// This class allows to maintain a list of vertices, +// sorted by degree (largest degrees first) +// Operations allowed : +// - get the vertex having max degree -> Cost = O(1) +// - remove any vertex from the graph -> Cost = Sum(degrees of neighbours) +// [ could be O(degree) if optimized ] + +#ifndef _BOX_LIST_H +#define _BOX_LIST_H + +#ifndef _MSC_VER + #ifndef register + #define register + #endif +#endif + +namespace gengraph { + +class box_list { + +private: + int n; // INITIAL number of vertices + int dmax; // CURRENT Maximum degree + int *deg; // CURRENT Degrees (points directly to the deg[] of the graph + + // Vertices are grouped by degree: one double-chained lists for each degree + int *list; // list[d-1] is the head of list of vertices of degree d + int *next; // next[v]/prev[v] are the vertices next/previous to v + int *prev; // in the list where v belongs + void pop(int); // pop(v) just removes v from its list + void insert(int); // insert(v) insert v at the head of its list + +public: + + // Ctor. Takes O(n) time. + box_list(int n0, int *deg0); + + // Dtor + ~box_list(); + + // Self-explaining inline routines + inline bool is_empty() { + return dmax < 1; + }; + inline int get_max() { + return list[dmax - 1]; + }; + inline int get_one() { + return list[0]; + }; + inline int get_min() { + int i = 0; + while (list[i] < 0) { + i++; + } + return list[i]; + }; + + // Remove v from box_list + // Also, semi-remove vertex v from graph: all neighbours of v will swap + // their last neighbour wit hv, and then decrease their degree, so + // that any arc w->v virtually disappear + // Actually, adjacency lists are just permuted, and deg[] is changed + void pop_vertex(int v, int **neigh); +}; + +} // namespace gengraph + +#endif //_BOX_LIST_H diff --git a/src/gengraph_definitions.h b/src/gengraph_definitions.h new file mode 100644 index 0000000..ac32572 --- /dev/null +++ b/src/gengraph_definitions.h @@ -0,0 +1,216 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef DEFINITIONS_H +#define DEFINITIONS_H + +#ifndef _MSC_VER + #ifndef register + #define register + #endif +#endif + +#include +#include +#include + +namespace gengraph { + +// Max line size in files +#define FBUFF_SIZE 1000000 + +// disable lousy VC++ warnings +#ifdef _ATL_VER_ + #pragma warning(disable : 4127) +#endif //_ATL_VER_ + +// Verbose +#define VERBOSE_NONE 0 +#define VERBOSE_SOME 1 +#define VERBOSE_LOTS 2 +int VERBOSE(); +void SET_VERBOSE(int v); + +// Random number generator +void my_srandom(int); +int my_random(); +int my_binomial(double pp, int n); +double my_random01(); // (0,1] + +#define MY_RAND_MAX 0x7FFFFFFF + +// IPv4 address direct translation into 32-bit uint + special IP defs +typedef unsigned int ip_addr; +#define IP_NONE 0x7FFFFFFF +#define IP_STAR 0x00000000 +#define IP_MYSELF 0x7F000001 + +// Compatibility +#ifdef _WIN32 + #define strcasecmp _stricmp +#endif +//inline double round(double x) throw () { return (floor(0.5+x)); } + +// No assert +#ifndef _DEBUG + #ifndef NDEBUG + #define NDEBUG + #endif //NDEBUG +#endif //_DEBUG + +// Min & Max +#ifndef min + #define defmin(type) inline type min(type a, type b) { return ab ? a : b; } + defmax(int) + defmax(double) + defmax(unsigned long) +#endif //max + +// Traceroute Sampling +#define MODE_USP 0 +#define MODE_ASP 1 +#define MODE_RSP 2 + +// Debug definitions +//#define PERFORMANCE_MONITOR +//#define OPT_ISOLATED + +// Max Int +#ifndef MAX_INT + #define MAX_INT 0x7FFFFFFF +#endif //MAX_INT + +//Edge type +typedef struct { + int from; + int to; +} edge; + +// Tag Int +#define TAG_INT 0x40000000 + +// Oldies .... +#define S_VECTOR_RAW + +//********************* +// Routine definitions +//********************* + +/* log(1+x) +inline double logp(double x) { + if(fabs(x)<1e-6) return x+0.5*x*x+0.333333333333333*x*x*x; + else return log(1.0+x); +} +//*/ + + +//Fast search or replace +inline int* fast_rpl(int *m, const int a, const int b) { + while (*m != a) { + m++; + } + *m = b; + return m; +} +inline int* fast_search(int *m, const int size, const int a) { + int *p = m + size; + while (m != p--) if (*p == a) { + return p; + } + return NULL; +} + +// Lovely percentage print +// inline void print_percent(double yo, FILE *f = stderr) { +// int arf = int(100.0*yo); +// if(double(arf)>100.0*yo) arf--; +// if(arf<100) fprintf(f," "); +// if(arf<10) fprintf(f," "); +// fprintf(f,"%d.%d%%",arf,int(1000.0*yo-double(10*arf))); +// } + +// Skips non-numerical chars, then numerical chars, then non-numerical chars. +inline char skip_int(char* &c) { + while (*c < '0' || *c > '9') { + c++; + } + while (*c >= '0' && *c <= '9') { + c++; + } + while (*c != 0 && (*c < '0' || *c > '9')) { + c++; + } + return *c; +} + +// distance+1 modulo 255 for breadth-first search +inline unsigned char next_dist(const unsigned char c) { + return c == 255 ? 1 : c + 1; +} +inline unsigned char prev_dist(const unsigned char c) { + return c == 1 ? 255 : c - 1; +} + +// 1/(RANDMAX+1) +#define inv_RANDMAX (1.0/(1.0+double(MY_RAND_MAX))) + +// random number in ]0,1[, _very_ accurate around 0 +inline double random_float() { + int r = my_random(); + double mul = inv_RANDMAX; + while (r <= 0x7FFFFF) { + r <<= 8; + r += (my_random() & 0xFF); + mul *= (1.0 / 256.0); + } + return double(r) * mul; +} + +// Return true with probability p. Very accurate when p is small. +#define test_proba(p) (random_float()<(p)) + +// Random bit generator, sparwise. +static int _random_bits_stored = 0; +static int _random_bits = 0; + +inline int random_bit() { + register int a = _random_bits; + _random_bits = a >> 1; + if (_random_bits_stored--) { + return a & 0x1; + } + a = my_random(); + _random_bits = a >> 1; + _random_bits_stored = 30; + return a & 0x1; +} + +// Hash Profiling (see hash.h) +void _hash_prof(); + +} // namespace gengraph + +#endif //DEFINITIONS_H diff --git a/src/gengraph_degree_sequence.cpp b/src/gengraph_degree_sequence.cpp new file mode 100644 index 0000000..44bb7ba --- /dev/null +++ b/src/gengraph_degree_sequence.cpp @@ -0,0 +1,420 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gengraph_definitions.h" +#include "gengraph_random.h" +#include "gengraph_powerlaw.h" +#include "gengraph_degree_sequence.h" +#include "gengraph_hash.h" + +#include "igraph_statusbar.h" + +#include +#include +#include +#include +#include + +// using namespace __gnu_cxx; +using namespace std; + +namespace gengraph { + +// shuffle an int[] randomly +void random_permute(int *a, int n); + +// sort an array of positive integers in time & place O(n + max) +void cumul_sort(int *q, int n); + + +void degree_sequence::detach() { + deg = NULL; +} + +degree_sequence::~degree_sequence() { + if (deg != NULL) { + delete[] deg; + } + deg = NULL; +} + +void degree_sequence::make_even(int mini, int maxi) { + if (total % 2 == 0) { + return; + } + if (maxi < 0) { + maxi = 0x7FFFFFFF; + } + int i; + for (i = 0; i < n; i++) { + if (deg[i] > mini) { + deg[i]--; + total--; + break; + } else if (deg[i] < maxi) { + deg[i]++; + total++; + break; + } + } + if (i == n) { + IGRAPH_WARNING("Warning: degree_sequence::make_even() forced one " + "degree to go over degmax"); + deg[0]++; + total++; + } +} + +void degree_sequence::shuffle() { + random_permute(deg, n); +} + +void degree_sequence::sort() { + cumul_sort(deg, n); +} + +void degree_sequence::compute_total() { + total = 0; + for (int i = 0; i < n; i++) { + total += deg[i]; + } +} + +degree_sequence:: +degree_sequence(int n0, int *degs) { + deg = degs; + n = n0; + compute_total(); +} + +degree_sequence:: +degree_sequence(const igraph_vector_t *out_seq) { + n = igraph_vector_size(out_seq); + deg = new int[n]; + for (long int i = 0; i < n; i++) { + deg[i] = VECTOR(*out_seq)[i]; + } + compute_total(); +} + +#ifndef FBUFF_SIZE + #define FBUFF_SIZE 999 +#endif //FBUFF_SIZE + +// degree_sequence::degree_sequence(FILE *f, bool DISTRIB) { +// n = 0; +// total = 0; +// char *buff = new char[FBUFF_SIZE]; +// char *c; +// vector degree; +// if(!DISTRIB) { +// // Input is a 'raw' degree sequence d0 d1 d2 d3 ... +// while(fgets(buff, FBUFF_SIZE, f)) { +// int d = strtol(buff, &c, 10); +// if(c == buff) continue; +// degree.push_back(d); +// total += d; +// } +// n = int(degree.size()); +// deg = new int[n]; +// int *yo = deg; +// vector::iterator end = degree.end(); +// for(vector::iterator it=degree.begin(); it!=end; *(yo++) = *(it++)); +// } +// else { +// // Input is a degree distribution : d0 #(degree=d0), d1 #(degree=d1), ... +// vector n_with_degree; +// int line = 0; +// int syntax = 0; +// int ignored = 0; +// int first_syntax = 0; +// int first_ignored = 0; +// while(fgets(buff, FBUFF_SIZE, f)) { +// line++; +// int d = strtol(buff, &c, 10); +// if(c == buff) { ignored++; first_ignored = line; continue; } +// char *cc; +// int i = strtol(c, &cc, 10); +// if(cc == c) { syntax++; first_syntax = line; continue; } +// n += i; +// total += i*d; +// degree.push_back(d); +// n_with_degree.push_back(i); +// if( cc != c) { syntax++; first_syntax = line; } +// } +// if(VERBOSE()) { +// if(ignored > 0) fprintf(stderr,"Ignored %d lines (first was line #%d)\n", ignored, first_ignored); +// if(syntax > 0) fprintf(stderr,"Found %d probable syntax errors (first was line #%d)\n", syntax, first_syntax); +// } +// deg = new int[n]; +// int *yo = deg; +// vector::iterator it_n = n_with_degree.begin(); +// for(vector::iterator it = degree.begin(); it != degree.end(); it++) +// for(int k = *(it_n++); k--; *yo++ = *it); +// } +// if(VERBOSE()) { +// if(total % 2 != 0) fprintf(stderr,"Warning: degree sequence is odd\n"); +// fprintf(stderr,"Degree sequence created. N=%d, 2M=%d\n", n, total); +// } +// } + +// n vertices, exponent, min degree, max degree, average degree (optional, default is -1) +degree_sequence:: +degree_sequence(int _n, double exp, int degmin, int degmax, double z) { + + n = _n; + if (exp == 0.0) { + // Binomial distribution + if (z < 0) { + igraph_error("Fatal error in degree_sequence Ctor: " + "positive average degree must be specified", __FILE__, + __LINE__, IGRAPH_EINVAL); + } + if (degmax < 0) { + degmax = n - 1; + } + total = int(floor(double(n) * z + 0.5)); + deg = new int[n]; + KW_RNG::RNG myrand; + double p = (z - double(degmin)) / double(n); + total = 0; + for (int i = 0; i < n; i++) { + do { + deg[i] = 1 + myrand.binomial(p, n); + } while (deg[i] > degmax); + total += deg[i]; + } + } else { + // Power-law distribution + igraph_status("Creating powerlaw sampler...", 0); + powerlaw pw(exp, degmin, degmax); + if (z == -1.0) { + pw.init(); + igraph_statusf("done. Mean=%f\n", 0, pw.mean()); + } else { + double offset = pw.init_to_mean(z); + igraph_statusf("done. Offset=%f, Mean=%f\n", 0, offset, pw.mean()); + } + + deg = new int[n]; + total = 0; + int i; + + igraph_statusf("Sampling %d random numbers...", 0, n); + for (i = 0; i < n; i++) { + deg[i] = pw.sample(); + total += deg[i]; + } + + igraph_status("done\nSimple statistics on degrees...", 0); + int wanted_total = int(floor(z * n + 0.5)); + sort(); + igraph_statusf("done : Max=%d, Total=%d.\n", 0, deg[0], total); + if (z != -1.0) { + igraph_statusf("Adjusting total to %d...", 0, wanted_total); + int iterations = 0; + + while (total != wanted_total) { + sort(); + for (i = 0; i < n && total > wanted_total; i++) { + total -= deg[i]; + if (total + degmin <= wanted_total) { + deg[i] = wanted_total - total; + } else { + deg[i] = pw.sample(); + } + total += deg[i]; + } + iterations += i; + for (i = n - 1; i > 0 && total < wanted_total; i--) { + total -= deg[i]; + if (total + (deg[0] >> 1) >= wanted_total) { + deg[i] = wanted_total - total; + } else { + deg[i] = pw.sample(); + } + total += deg[i]; + } + iterations += n - 1 - i; + } + igraph_statusf("done(%d iterations).", 0, iterations); + igraph_statusf(" Now, degmax = %d\n", 0, dmax()); + } + + shuffle(); + } +} + +// void degree_sequence::print() { +// for(int i=0; ideg[i]) dmin=deg[i]; +// int *dd = new int[dmax-dmin+1]; +// for(i=dmin; i<=dmax; i++) dd[i-dmin]=0; +// if(VERBOSE()) fprintf(stderr,"Computing cumulative distribution..."); +// for(i=0; i0) printf("%d %d\n",i,dd[i-dmin]); +// delete[] dd; +// } + +bool degree_sequence::havelhakimi() { + + int i; + int dm = dmax() + 1; + // Sort vertices using basket-sort, in descending degrees + int *nb = new int[dm]; + int *sorted = new int[n]; + // init basket + for (i = 0; i < dm; i++) { + nb[i] = 0; + } + // count basket + for (i = 0; i < n; i++) { + nb[deg[i]]++; + } + // cumul + int c = 0; + for (i = dm - 1; i >= 0; i--) { + int t = nb[i]; + nb[i] = c; + c += t; + } + // sort + for (i = 0; i < n; i++) { + sorted[nb[deg[i]]++] = i; + } + +// Binding process starts + int first = 0; // vertex with biggest residual degree + int d = dm - 1; // maximum residual degree available + + for (c = total / 2; c > 0; ) { + // We design by 'v' the vertex of highest degree (indexed by first) + // look for current degree of v + while (nb[d] <= first) { + d--; + } + // store it in dv + int dv = d; + // bind it ! + c -= dv; + int dc = d; // residual degree of vertices we bind to + int fc = ++first; // position of the first vertex with degree dc + + while (dv > 0 && dc > 0) { + int lc = nb[dc]; + if (lc != fc) { + while (dv > 0 && lc > fc) { + // binds v with sorted[--lc] + dv--; + lc--; + } + fc = nb[dc]; + nb[dc] = lc; + } + dc--; + } + if (dv != 0) { // We couldn't bind entirely v + delete[] nb; + delete[] sorted; + return false; + } + } + delete[] nb; + delete[] sorted; + return true; +} + +//************************* +// Subroutines definitions +//************************* + +inline int int_adjust(double x) { + return (int(floor(x + random_float()))); +} + +void random_permute(int *a, int n) { + int j, tmp; + for (int i = 0; i < n - 1; i++) { + j = i + my_random() % (n - i); + tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } +} + +void cumul_sort(int *q, int n) { + // looks for the maximum q[i] and minimum + if (n == 0) { + return; + } + int qmax = q[0]; + int qmin = q[0]; + int i; + for (i = 0; i < n; i++) if (q[i] > qmax) { + qmax = q[i]; + } + for (i = 0; i < n; i++) if (q[i] < qmin) { + qmin = q[i]; + } + + // counts #q[i] with given q + int *nb = new int[qmax - qmin + 1]; + for (int *onk = nb + (qmax - qmin + 1); onk != nb; * (--onk) = 0) { } + for (i = 0; i < n; i++) { + nb[q[i] - qmin]++; + } + + // counts cumulative distribution + for (i = qmax - qmin; i > 0; i--) { + nb[i - 1] += nb[i]; + } + + // sort by q[i] + int last_q; + int tmp; + int modifier = qmax - qmin + 1; + for (int current = 0; current < n; current++) { + tmp = q[current]; + if (tmp >= qmin && tmp <= qmax) { + last_q = qmin; + do { + q[current] = last_q + modifier; + last_q = tmp; + current = --nb[last_q - qmin]; + } while ((tmp = q[current]) >= qmin && tmp <= qmax); + q[current] = last_q + modifier; + } + } + delete[] nb; + for (i = 0; i < n; i++) { + q[i] = q[i] - modifier; + } +} + +} // namespace gengraph diff --git a/src/gengraph_degree_sequence.h b/src/gengraph_degree_sequence.h new file mode 100644 index 0000000..841ea6d --- /dev/null +++ b/src/gengraph_degree_sequence.h @@ -0,0 +1,101 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef DEGREE_SEQUENCE_H +#define DEGREE_SEQUENCE_H + +#include "igraph_types.h" +#include "igraph_datatype.h" + +namespace gengraph { + +class degree_sequence { + +private: + int n; + int * deg; + int total; + +public : + // #vertices + inline int size() { + return n; + }; + inline int sum() { + return total; + }; + inline int operator[](int i) { + return deg[i]; + }; + inline int *seq() { + return deg; + }; + inline void assign(int n0, int* d0) { + n = n0; + deg = d0; + }; + inline int dmax() { + int dm = deg[0]; + for (int i = 1; i < n; i++) if (deg[i] > dm) { + dm = deg[i]; + } + return dm; + } + + void make_even(int mini = -1, int maxi = -1); + void sort(); + void shuffle(); + + // raw constructor + degree_sequence(int n, int *degs); + + // read-from-file constrictor + degree_sequence(FILE *f, bool DISTRIB = true); + + // simple power-law constructor : Pk = int((x+k0)^(-exp),x=k..k+1), with k0 so that avg(X)=z + degree_sequence(int n, double exp, int degmin, int degmax, double avg_degree = -1.0); + + // igraph constructor + degree_sequence(const igraph_vector_t *out_seq); + + // destructor + ~degree_sequence(); + + // unbind the deg[] vector (so that it doesn't get deleted when the class is destroyed) + void detach(); + + // compute total number of arcs + void compute_total(); + + // raw print (vertex by vertex) + void print(); + + // distribution print (degree frequency) + void print_cumul(); + + // is degree sequence realizable ? + bool havelhakimi(); + +}; + +} // namespace gengraph + +#endif //DEGREE_SEQUENCE_H + diff --git a/src/gengraph_graph_molloy_hash.cpp b/src/gengraph_graph_molloy_hash.cpp new file mode 100644 index 0000000..2a190e8 --- /dev/null +++ b/src/gengraph_graph_molloy_hash.cpp @@ -0,0 +1,1173 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gengraph_definitions.h" +#include +#include +#include +#include + +#include "gengraph_qsort.h" +#include "gengraph_hash.h" +#include "gengraph_degree_sequence.h" +#include "gengraph_graph_molloy_hash.h" + +#include "config.h" +#include "igraph_math.h" +#include "igraph_constructors.h" +#include "igraph_error.h" +#include "igraph_statusbar.h" +#include "igraph_progress.h" + +namespace gengraph { + +//_________________________________________________________________________ +void graph_molloy_hash::compute_neigh() { + int *p = links; + for (int i = 0; i < n; i++) { + neigh[i] = p; + p += HASH_SIZE(deg[i]); + } +} + +//_________________________________________________________________________ +void graph_molloy_hash::compute_size() { + size = 0; + for (int i = 0; i < n; i++) { + size += HASH_SIZE(deg[i]); + } +} + +//_________________________________________________________________________ +void graph_molloy_hash::init() { + for (int i = 0; i < size; i++) { + links[i] = HASH_NONE; + } +} + +//_________________________________________________________________________ +graph_molloy_hash::graph_molloy_hash(degree_sequence °s) { + igraph_status("Allocating memory for graph...", 0); + int s = alloc(degs); + igraph_statusf("%d bytes allocated successfully\n", 0, s); +} + +//_________________________________________________________________________ +int graph_molloy_hash::alloc(degree_sequence °s) { + n = degs.size(); + a = degs.sum(); + assert(a % 2 == 0); + + deg = degs.seq(); + compute_size(); + deg = new int[n + size]; + if (deg == NULL) { + return 0; + } + int i; + for (i = 0; i < n; i++) { + deg[i] = degs[i]; + } + links = deg + n; + init(); + neigh = new int*[n]; + if (neigh == NULL) { + return 0; + } + compute_neigh(); + return sizeof(int *)*n + sizeof(int) * (n + size); +} + +//_________________________________________________________________________ +graph_molloy_hash::~graph_molloy_hash() { + if (deg != NULL) { + delete[] deg; + } + if (neigh != NULL) { + delete[] neigh; + } + deg = NULL; + neigh = NULL; +} + +//_________________________________________________________________________ +graph_molloy_hash::graph_molloy_hash(int *svg) { + // Read n + n = *(svg++); + // Read a + a = *(svg++); + assert(a % 2 == 0); + // Read degree sequence + degree_sequence dd(n, svg); + // Build neigh[] and alloc links[] + alloc(dd); + dd.detach(); + // Read links[] + restore(svg + n); +} + +//_________________________________________________________________________ +int *graph_molloy_hash::hard_copy() { + int *hc = new int[2 + n + a / 2]; // to store n,a,deg[] and links[] + hc[0] = n; + hc[1] = a; + memcpy(hc + 2, deg, sizeof(int)*n); + int *p = hc + 2 + n; + int *l = links; + for (int i = 0; i < n; i++) for (int j = HASH_SIZE(deg[i]); j--; l++) { + register int d; + if ((d = *l) != HASH_NONE && d >= i) { + *(p++) = d; + } + } + assert(p == hc + 2 + n + a / 2); + return hc; +} + +//_________________________________________________________________________ +bool graph_molloy_hash::is_connected() { + bool *visited = new bool[n]; + int *buff = new int[n]; + int comp_size = depth_search(visited, buff); + delete[] visited; + delete[] buff; + return (comp_size == n); +} + +//_________________________________________________________________________ +int* graph_molloy_hash::backup() { + int *b = new int[a / 2]; + int *c = b; + int *p = links; + for (int i = 0; i < n; i++) + for (int d = HASH_SIZE(deg[i]); d--; p++) if (*p != HASH_NONE && *p > i) { + *(c++) = *p; + } + assert(c == b + (a / 2)); + return b; +} + +//_________________________________________________________________________ +void graph_molloy_hash::restore(int* b) { + init(); + int i; + int *dd = new int[n]; + memcpy(dd, deg, sizeof(int)*n); + for (i = 0; i < n; i++) { + deg[i] = 0; + } + for (i = 0; i < n - 1; i++) { + while (deg[i] < dd[i]) { + add_edge(i, *b, dd); + b++; + } + } + delete[] dd; +} + +//_________________________________________________________________________ +bool graph_molloy_hash::isolated(int v, int K, int *Kbuff, bool *visited) { + if (K < 2) { + return false; + } +#ifdef OPT_ISOLATED + if (K <= deg[v] + 1) { + return false; + } +#endif //OPT_ISOLATED + int *seen = Kbuff; + int *known = Kbuff; + int *max = Kbuff + K; + *(known++) = v; + visited[v] = true; + bool is_isolated = true; + + while (known != seen) { + v = *(seen++); + int *ww = neigh[v]; + int w; + for (int d = HASH_SIZE(deg[v]); d--; ww++) if ((w = *ww) != HASH_NONE && !visited[w]) { +#ifdef OPT_ISOLATED + if (K <= deg[w] + 1 || known == max) { +#else //OPT_ISOLATED + if (known == max) { +#endif //OPT_ISOLATED + is_isolated = false; + goto end_isolated; + } + visited[w] = true; + *(known++) = w; + } + } +end_isolated: + // Undo the changes to visited[]... + while (known != Kbuff) { + visited[*(--known)] = false; + } + return is_isolated; +} + +//_________________________________________________________________________ +int graph_molloy_hash::random_edge_swap(int K, int *Kbuff, bool *visited) { + // Pick two random vertices a and c + int f1 = pick_random_vertex(); + int f2 = pick_random_vertex(); + // Check that f1 != f2 + if (f1 == f2) { + return 0; + } + // Get two random edges (f1,*f1t1) and (f2,*f2t2) + int *f1t1 = random_neighbour(f1); + int t1 = *f1t1; + int *f2t2 = random_neighbour(f2); + int t2 = *f2t2; + // Check simplicity + if (t1 == t2 || f1 == t2 || f2 == t1) { + return 0; + } + if (is_edge(f1, t2) || is_edge(f2, t1)) { + return 0; + } + // Swap + int *f1t2 = H_rpl(neigh[f1], deg[f1], f1t1, t2); + int *f2t1 = H_rpl(neigh[f2], deg[f2], f2t2, t1); + int *t1f2 = H_rpl(neigh[t1], deg[t1], f1, f2); + int *t2f1 = H_rpl(neigh[t2], deg[t2], f2, f1); + // isolation test + if (K <= 2) { + return 1; + } + if ( !isolated(f1, K, Kbuff, visited) && !isolated(f2, K, Kbuff, visited) ) { + return 1; + } + // undo swap + H_rpl(neigh[f1], deg[f1], f1t2, t1); + H_rpl(neigh[f2], deg[f2], f2t1, t2); + H_rpl(neigh[t1], deg[t1], t1f2, f1); + H_rpl(neigh[t2], deg[t2], t2f1, f2); + return 0; +} + +//_________________________________________________________________________ +unsigned long graph_molloy_hash::shuffle(unsigned long times, + unsigned long maxtimes, int type) { + igraph_progress("Shuffle", 0, 0); + // assert(verify()); + // counters + unsigned long nb_swaps = 0; + unsigned long all_swaps = 0; + unsigned long cost = 0; + // window + double T = double(min((unsigned long)(a), times) / 10); + if (type == OPTIMAL_HEURISTICS) { + T = double(optimal_window()); + } + if (type == BRUTE_FORCE_HEURISTICS) { + T = double(times * 2); + } + // isolation test parameter, and buffers + double K = 2.4; + int *Kbuff = new int[int(K) + 1]; + bool *visited = new bool[n]; + for (int i = 0; i < n; i++) { + visited[i] = false; + } + // Used for monitoring , active only if VERBOSE() + int failures = 0; + int successes = 0; + double avg_K = 0; + double avg_T = 0; + unsigned long next = times; + next = 0; + + // Shuffle: while #edge swap attempts validated by connectivity < times ... + while (times > nb_swaps && maxtimes > all_swaps) { + // Backup graph + int *save = backup(); + // Prepare counters, K, T + unsigned long swaps = 0; + int K_int = 0; + if (type == FINAL_HEURISTICS || type == BRUTE_FORCE_HEURISTICS) { + K_int = int(K); + } + unsigned long T_int = (unsigned long)(floor(T)); + if (T_int < 1) { + T_int = 1; + } + // compute cost + cost += T_int; + if (K_int > 2) { + cost += (unsigned long)(K_int) * (unsigned long)(T_int); + } + // Perform T edge swap attempts + for (int i = T_int; i > 0; i--) { + // try one swap + swaps += (unsigned long)(random_edge_swap(K_int, Kbuff, visited)); + all_swaps++; + // Verbose + if (nb_swaps + swaps > next) { + next = (nb_swaps + swaps) + max((unsigned long)(100), (unsigned long)(times / 1000)); + int progress = int(double(nb_swaps + swaps) / double(times)); + igraph_progress("Shuffle", progress, 0); + } + } + // test connectivity + cost += (unsigned long)(a / 2); + bool ok = is_connected(); + // performance monitor + { + avg_T += double(T_int); avg_K += double(K_int); + if (ok) { + successes++; + } else { + failures++; + } + } + // restore graph if needed, and count validated swaps + if (ok) { + nb_swaps += swaps; + } else { + restore(save); + next = nb_swaps; + } + delete[] save; + // Adjust K and T following the heuristics. + switch (type) { + int steps; + case GKAN_HEURISTICS: + if (ok) { + T += 1.0; + } else { + T *= 0.5; + } + break; + case FAB_HEURISTICS: + steps = 50 / (8 + failures + successes); + if (steps < 1) { + steps = 1; + } + while (steps--) if (ok) { + T *= 1.17182818; + } else { + T *= 0.9; + } + if (T > double(5 * a)) { + T = double(5 * a); + } + break; + case FINAL_HEURISTICS: + if (ok) { + if ((K + 10.0)*T > 5.0 * double(a)) { + K /= 1.03; + } else { + T *= 2; + } + } else { + K *= 1.35; + delete[] Kbuff; + Kbuff = new int[int(K) + 1]; + } + break; + case OPTIMAL_HEURISTICS: + if (ok) { + T = double(optimal_window()); + } + break; + case BRUTE_FORCE_HEURISTICS: + K *= 2; delete[] Kbuff; Kbuff = new int[int(K) + 1]; + break; + default: + IGRAPH_ERROR("Error in graph_molloy_hash::shuffle(): " + "Unknown heuristics type", IGRAPH_EINVAL); + return 0; + } + } + + delete[] Kbuff; + delete[] visited; + + if (maxtimes <= all_swaps) { + IGRAPH_WARNING("Cannot shuffle graph, maybe there is only a single one?"); + } + + // Status report + { + igraph_status("*** Shuffle Monitor ***\n", 0); + igraph_statusf(" - Average cost : %f / validated edge swap\n", 0, + double(cost) / double(nb_swaps)); + igraph_statusf(" - Connectivity tests : %d (%d successes, %d failures)\n", + 0, successes + failures, successes, failures); + igraph_statusf(" - Average window : %d\n", 0, + int(avg_T / double(successes + failures))); + if (type == FINAL_HEURISTICS || type == BRUTE_FORCE_HEURISTICS) + igraph_statusf(" - Average isolation test width : %f\n", 0, + avg_K / double(successes + failures)); + } + return nb_swaps; +} + +//_________________________________________________________________________ +void graph_molloy_hash::print(FILE *f) { + int i, j; + for (i = 0; i < n; i++) { + fprintf(f, "%d", i); + for (j = 0; j < HASH_SIZE(deg[i]); j++) if (neigh[i][j] != HASH_NONE) { + fprintf(f, " %d", neigh[i][j]); + } + fprintf(f, "\n"); + } +} + +int graph_molloy_hash::print(igraph_t *graph) { + int i, j; + long int ptr = 0; + igraph_vector_t edges; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, a); // every edge is counted twice.... + + for (i = 0; i < n; i++) { + for (j = 0; j < HASH_SIZE(deg[i]); j++) { + if (neigh[i][j] != HASH_NONE) { + if (neigh[i][j] > i) { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = neigh[i][j]; + } + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, /*undirected=*/ 0)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +//_________________________________________________________________________ +bool graph_molloy_hash::try_shuffle(int T, int K, int *backup_graph) { + // init all + int *Kbuff = NULL; + bool *visited = NULL; + if (K > 2) { + Kbuff = new int[K]; + visited = new bool[n]; + for (int i = 0; i < n; i++) { + visited[i] = false; + } + } + int *back = backup_graph; + if (back == NULL) { + back = backup(); + } + // perform T edge swap attempts + while (T--) { + random_edge_swap(K, Kbuff, visited); + } + // clean + if (visited != NULL) { + delete[] visited; + } + if (Kbuff != NULL) { + delete[] Kbuff; + } + // check & restore + bool yo = is_connected(); + restore(back); + if (backup_graph == NULL) { + delete[] back; + } + return yo; +} + +//_________________________________________________________________________ +#define _TRUST_BERNOULLI_LOWER 0.01 + +bool bernoulli_param_is_lower(int success, int trials, double param) { + if (double(success) >= double(trials)*param) { + return false; + } + double comb = 1.0; + double fact = 1.0; + for (int i = 0; i < success; i++) { + comb *= double(trials - i); + fact *= double(i + 1); + } + comb /= fact; + comb *= pow(param, double(success)) * exp(double(trials - success) * log1p(-param)); + double sum = comb; + while (success && sum < _TRUST_BERNOULLI_LOWER) { + comb *= double(success) * (1.0 - param) / (double(trials - success) * param); + sum += comb; + success--; + } + // fprintf(stderr,"bernoulli test : %d/%d success against p=%f -> %s\n",success, trials, param, (sum < _TRUST_BERNOULLI_LOWER) ? "lower" : "can't say"); + return (sum < _TRUST_BERNOULLI_LOWER); +} + +//_________________________________________________________________________ +#define _MIN_SUCCESS_FOR_BERNOULLI_TRUST 100 +double graph_molloy_hash::average_cost(int T, int *backup, double min_cost) { + if (T < 1) { + return 1e+99; + } + int successes = 0; + int trials = 0; + while (successes < _MIN_SUCCESS_FOR_BERNOULLI_TRUST && + !bernoulli_param_is_lower(successes, trials, 1.0 / min_cost)) { + if (try_shuffle(T, 0, backup)) { + successes++; + } + trials++; + } + if (successes >= _MIN_SUCCESS_FOR_BERNOULLI_TRUST) { + return double(trials) / double(successes) * (1.0 + double(a / 2) / double(T)); + } else { + return 2.0 * min_cost; + } +} + +//_________________________________________________________________________ +int graph_molloy_hash::optimal_window() { + int Tmax; + int optimal_T = 1; + double min_cost = 1e+99; + int *back = backup(); + // on cherche une borne sup pour Tmax + int been_greater = 0; + for (Tmax = 1; Tmax <= 5 * a ; Tmax *= 2) { + double c = average_cost(Tmax, back, min_cost); + if (c > 1.5 * min_cost) { + break; + } + if (c > 1.2 * min_cost && ++been_greater >= 3) { + break; + } + if (c < min_cost) { + min_cost = c; + optimal_T = Tmax; + } + igraph_statusf("Tmax = %d [%f]", 0, Tmax, min_cost); + } + // on cree Tmin + int Tmin = int(0.5 * double(a) / (min_cost - 1.0)); + igraph_statusf("Optimal T is in [%d, %d]\n", 0, Tmin, Tmax); + // on cherche autour + double span = 2.0; + int try_again = 4; + while (span > 1.05 && optimal_T <= 5 * a) { + igraph_statusf("Best T [cost]: %d [%f]", 0, optimal_T, min_cost); + int T_low = int(double(optimal_T) / span); + int T_high = int(double(optimal_T) * span); + double c_low = average_cost(T_low, back, min_cost); + double c_high = average_cost(T_high, back, min_cost); + if (c_low < min_cost && c_high < min_cost) { + if (try_again--) { + continue; + } + { + igraph_status("Warning: when looking for optimal T,\n", 0); + igraph_statusf("Low: %d [%f] Middle: %d [%f] High: %d [%f]\n", 0, + T_low, c_low, optimal_T, min_cost, T_high, c_high); + } + delete[] back; + return optimal_T; + } + if (c_low < min_cost) { + optimal_T = T_low; + min_cost = c_low; + } else if (c_high < min_cost) { + optimal_T = T_high; + min_cost = c_high; + }; + span = pow(span, 0.618); + } + delete[] back; + return optimal_T; +} + +//_________________________________________________________________________ +double graph_molloy_hash::eval_K(int quality) { + double K = 5.0; + double avg_K = 1.0; + for (int i = quality; i--; ) { + int int_K = int(floor(K + 0.5)); + if (try_shuffle(a / (int_K + 1), int_K)) { + K *= 0.8; /*fprintf(stderr,"+");*/ + } else { + K *= 1.25; /*fprintf(stderr,"-");*/ + } + if (i < quality / 2) { + avg_K *= K; + } + } + return pow(avg_K, 1.0 / double(quality / 2)); +} + +//_________________________________________________________________________ +double graph_molloy_hash::effective_K(int K, int quality) { + if (K < 3) { + return 0.0; + } + long sum_K = 0; + int *Kbuff = new int[K]; + bool *visited = new bool[n]; + int i; + for (i = 0; i < n; i++) { + visited[i] = false; + } + for (int i = 0; i < quality; i++) { + // assert(verify()); + int f1, f2, t1, t2; + int *f1t1, *f2t2; + do { + // Pick two random vertices + do { + f1 = pick_random_vertex(); + f2 = pick_random_vertex(); + } while (f1 == f2); + // Pick two random neighbours + f1t1 = random_neighbour(f1); + t1 = *f1t1; + f2t2 = random_neighbour(f2); + t2 = *f2t2; + // test simplicity + } while (t1 == t2 || f1 == t2 || f2 == t1 || is_edge(f1, t2) || is_edge(f2, t1)); + // swap + swap_edges(f1, t2, f2, t1); + // assert(verify()); + sum_K += effective_isolated(deg[f1] > deg[t2] ? f1 : t2, K, Kbuff, visited); + // assert(verify()); + sum_K += effective_isolated(deg[f2] > deg[t1] ? f2 : t1, K, Kbuff, visited); + // assert(verify()); + // undo swap + swap_edges(f1, t2, f2, t1); + // assert(verify()); + } + delete[] Kbuff; + delete[] visited; + return double(sum_K) / double(2 * quality); +} + +//_________________________________________________________________________ +long graph_molloy_hash::effective_isolated(int v, int K, int *Kbuff, bool *visited) { + int i; + for (i = 0; i < K; i++) { + Kbuff[i] = -1; + } + long count = 0; + int left = K; + int *KB = Kbuff; + //yapido = (my_random()%1000 == 0); + depth_isolated(v, count, left, K, KB, visited); + while (KB-- != Kbuff) { + visited[*KB] = false; + } + //if(yapido) fprintf(stderr,"\n"); + return count; +} + +//_________________________________________________________________________ +void graph_molloy_hash::depth_isolated(int v, long &calls, int &left_to_explore, int dmax, int * &Kbuff, bool *visited) { + if (left_to_explore == 0) { + return; + } +// if(yapido) fprintf(stderr,"%d ",deg[v]); + if (--left_to_explore == 0) { + return; + } + if (deg[v] + 1 >= dmax) { + left_to_explore = 0; + return; + } + *(Kbuff++) = v; + visited[v] = true; +// print(); +// fflush(stdout); + calls++; + int *copy = NULL; + int *w = neigh[v]; + if (IS_HASH(deg[v])) { + copy = new int[deg[v]]; + H_copy(copy, w, deg[v]); + w = copy; + } + qsort(deg, w, deg[v]); + w += deg[v]; + for (int i = deg[v]; i--; ) { + if (visited[*--w]) { + calls++; + } else { + depth_isolated(*w, calls, left_to_explore, dmax, Kbuff, visited); + } + if (left_to_explore == 0) { + break; + } + } + if (copy != NULL) { + delete[] copy; + } +} + +//_________________________________________________________________________ +int graph_molloy_hash::depth_search(bool *visited, int *buff, int v0) { + for (int i = 0; i < n; i++) { + visited[i] = false; + } + int *to_visit = buff; + int nb_visited = 1; + visited[v0] = true; + *(to_visit++) = v0; + while (to_visit != buff && nb_visited < n) { + int v = *(--to_visit); + int *ww = neigh[v]; + int w; + for (int k = HASH_SIZE(deg[v]); k--; ww++) { + if (HASH_NONE != (w = *ww) && !visited[w]) { + visited[w] = true; + nb_visited++; + *(to_visit++) = w; + } + } + } + return nb_visited; +} + +//_________________________________________________________________________ +// bool graph_molloy_hash::verify() { +// fprintf(stderr,"Warning: graph_molloy_hash::verify() called..\n"); +// fprintf(stderr," try to convert graph into graph_molloy_opt() instead\n"); +// return true; +// } + + +/*____________________________________________________________________________ + Not to use anymore : use graph_molloy_opt class instead + +bool graph_molloy_hash::verify() { +int i; + assert(neigh[0]==links); + // verify edges count + int sum = 0; + for(i=0; in) n=i; + n++; + // degrees ? + if(VERBOSE()) fprintf(stderr,"%d, #edges=",n); + int *degs = new int[n]; + rewind(f); + while(fgets(buff,FBUFF_SIZE,f)) { + int d = 0; + if(sscanf(buff,"%d",&i)==1) { + char *b = buff; + while(skip_int(b)) d++; + degs[i]=d; + } + } + // allocate memory + degree_sequence dd(n,degs); + if(VERBOSE()) fprintf(stderr,"%d\nAllocating memory...",dd.sum()); + alloc(dd); + // add edges + if(VERBOSE()) fprintf(stderr,"done\nCreating edges..."); + rewind(f); + for(i=0; im) m=deg[k]; + return m; +} + + +bool graph_molloy_hash::havelhakimi() { + + int i; + int dmax = max_degree()+1; + // Sort vertices using basket-sort, in descending degrees + int *nb = new int[dmax]; + int *sorted = new int[n]; + // init basket + for(i=0; i=0; i--) { + int t=nb[i]; + nb[i]=c; + c+=t; + } + // sort + for(i=0; i0; ) { + // pick a vertex. we could pick any, but here we pick the one with biggest degree + int v = sorted[first]; + // look for current degree of v + while(nb[d]<=first) d--; + // store it in dv + int dv = d; + // bind it ! + c -= dv; + int dc = d; // residual degree of vertices we bind to + int fc = ++first; // position of the first vertex with degree dc + + while(dv>0 && dc>0) { + int lc = nb[dc]; + if(lc!=fc) { + while(dv>0 && lc>fc) { + // binds v with sorted[--lc] + dv--; + int w = sorted[--lc]; + add_edge(v,w); + } + fc = nb[dc]; + nb[dc] = lc; + } + dc--; + } + if(dv != 0) { // We couldn't bind entirely v + if(VERBOSE()) { + fprintf(stderr,"Error in graph_molloy_hash::havelhakimi() :\n"); + fprintf(stderr,"Couldn't bind vertex %d entirely (%d edges remaining)\n",v,dv); + } + delete[] nb; + delete[] sorted; + return false; + } + } + assert(c==0); + delete[] nb; + delete[] sorted; + return true; +} + + +bool graph_molloy_hash::make_connected() { + assert(verify()); + if(a/2 < n-1) { + // fprintf(stderr,"\ngraph::make_connected() failed : #edges < #vertices-1\n"); + return false; + } + int i; + +// Data struct for the visit : +// - buff[] contains vertices to visit +// - dist[V] is V's distance modulo 4 to the root of its comp, or -1 if it hasn't been visited yet +#define MC_BUFF_SIZE (n+2) + int *buff = new int[MC_BUFF_SIZE]; + unsigned char * dist = new unsigned char[n]; +#define NOT_VISITED 255 +#define FORBIDDEN 254 + for(i=n; i>0; dist[--i]=NOT_VISITED); + +// Data struct to store components : either surplus trees or surplus edges are stored at buff[]'s end +// - A Tree is coded by one of its vertices +// - An edge (a,b) is coded by the TWO ints a and b + int *ffub = buff+MC_BUFF_SIZE; + edge *edges = (edge *) ffub; + int *trees = ffub; + int *min_ffub = buff+1+(MC_BUFF_SIZE%2 ? 0 : 1); + +// There will be only one "fatty" component, and trees. + edge fatty_edge; + fatty_edge.from = -1; + bool enough_edges = false; + + // start main loop + for(int v0=0; v0min_ffub) min_ffub+=2; // update limit of ffub's storage + //assert(verify()); + } + else if(dist[w]==next_dist || (w!=HASH_NONE && w>v && dist[w]==current_dist)) { + // we found a removable edge + if(is_a_tree) { + // we must first merge with the fatty component + is_a_tree = false; + if(fatty_edge.from < 0) { + // we ARE the first component! fatty is us + fatty_edge.from = v; + fatty_edge.to = w; + } + else { + // we connect to fatty + swap_edges(fatty_edge.from, fatty_edge.to, v, w); + //assert(verify()); + } + } + else { + // we have removable edges to give! + if(trees!=ffub) { + // some trees still.. Let's merge with them! + assert(trees>=min_ffub); + assert(edges==(edge *)ffub); + swap_edges(v,w,*trees,neigh[*trees][0]); + trees++; + //assert(verify()); + } + else if(!enough_edges) { + // Store the removable edge for future use + if(edges<=(edge *)min_ffub+1) + enough_edges = true; + else { + edges--; + edges->from = v; + edges->to = w; + } + } + } + } + } + } + // Mark component + while(to_visit!=buff) dist[*(--to_visit)] = FORBIDDEN; + // Check if it is a tree + if(is_a_tree ) { + assert(deg[v0]!=0); + if(edges!=(edge *)ffub) { + // let's bind the tree we found with a removable edge in stock + assert(trees == ffub); + if(edges<(edge *)min_ffub) edges=(edge *)min_ffub; + swap_edges(v0,neigh[v0][0],edges->from,edges->to); + edges++; + assert(verify()); + } + else { + // add the tree to the list of trees + assert(trees>min_ffub); + *(--trees) = v0; + assert(verify()); + } + } + } + delete[] buff; + delete[] dist; + return(trees == ffub); +} + +int64_t graph_molloy_hash::slow_connected_shuffle(int64_t times) { + assert(verify()); + int64_t nb_swaps = 0; + int T = 1; + + while(times>nb_swaps) { + // Backup graph + int *save = backup(); + // Swaps + int swaps = 0; + for(int i=T; i>0; i--) { + // Pick two random vertices a and c + int f1 = pick_random_vertex(); + int f2 = pick_random_vertex(); + // Check that f1 != f2 + if(f1==f2) continue; + // Get two random edges (f1,*f1t1) and (f2,*f2t2) + int *f1t1 = random_neighbour(f1); + int t1 = *f1t1; + int *f2t2 = random_neighbour(f2); + int t2 = *f2t2; + // Check simplicity + if(t1==t2 || f1==t2 || f2==t1) continue; + if(is_edge(f1,t2) || is_edge(f2,t1)) continue; + // Swap + H_rpl(neigh[f1],deg[f1],f1t1,t2); + H_rpl(neigh[f2],deg[f2],f2t2,t1); + H_rpl(neigh[t1],deg[t1],f1,f2); + H_rpl(neigh[t2],deg[t2],f2,f1); + swaps++; + } + // test connectivity + bool ok = is_connected(); + if(ok) { + nb_swaps += swaps; + } + else { + restore(save); + } + delete[] save; + } + return nb_swaps; +} + + +int graph_molloy_hash::width_search(unsigned char *dist, int *buff, int v0) { + for(int i=0; i. + */ +#ifndef GRAPH_MOLLOY_HASH_H +#define GRAPH_MOLLOY_HASH_H + +#include "gengraph_definitions.h" +#include "gengraph_hash.h" +#include "gengraph_degree_sequence.h" + +#include +#include +// This class handles graphs with a constant degree sequence. + +#define FINAL_HEURISTICS 0 +#define GKAN_HEURISTICS 1 +#define FAB_HEURISTICS 2 +#define OPTIMAL_HEURISTICS 3 +#define BRUTE_FORCE_HEURISTICS 4 + +namespace gengraph { + +//**************************** +// class graph_molloy_hash +//**************************** + +class graph_molloy_hash { + +private: + // Number of vertices + int n; + //Number of arcs ( = #edges * 2 ) + int a; + //Total size of links[] + int size; + // The degree sequence of the graph + int *deg; + // The array containing all links + int *links; + // The array containing pointers to adjacency list of every vertices + int **neigh; + // Counts total size + void compute_size(); + // Build neigh with deg and links + void compute_neigh(); + // Allocate memory according to degree_sequence (for constructor use only!!) + int alloc(degree_sequence &); + // Add edge (a,b). Return FALSE if vertex a is already full. + // WARNING : only to be used by havelhakimi(), restore() or constructors + inline bool add_edge(int a, int b, int *realdeg) { + int deg_a = realdeg[a]; + if (deg_a == deg[a]) { + return false; + } + // Check that edge was not already inserted + assert(fast_search(neigh[a], int((a == n - 1 ? links + size : neigh[a + 1]) - neigh[a]), b) == NULL); + assert(fast_search(neigh[b], int((b == n - 1 ? links + size : neigh[b + 1]) - neigh[b]), a) == NULL); + assert(deg[a] < deg_a); + int deg_b = realdeg[b]; + if (IS_HASH(deg_a)) { + *H_add(neigh[a], HASH_EXPAND(deg_a), b) = b; + } else { + neigh[a][deg[a]] = b; + } + if (IS_HASH(deg_b)) { + *H_add(neigh[b], HASH_EXPAND(deg_b), a) = a; + } else { + neigh[b][deg[b]] = a; + } + deg[a]++; + deg[b]++; + // Check that edge was actually inserted + assert(fast_search(neigh[a], int((a == n - 1 ? links + size : neigh[a + 1]) - neigh[a]), b) != NULL); + assert(fast_search(neigh[b], int((b == n - 1 ? links + size : neigh[b + 1]) - neigh[b]), a) != NULL); + return true; + } + // Swap edges + inline void swap_edges(int from1, int to1, int from2, int to2) { + H_rpl(neigh[from1], deg[from1], to1, to2); + H_rpl(neigh[from2], deg[from2], to2, to1); + H_rpl(neigh[to1], deg[to1], from1, from2); + H_rpl(neigh[to2], deg[to2], from2, from1); + } + // Backup graph [sizeof(int) bytes per edge] + int* backup(); + // Test if vertex is in an isolated component of size dmax. + void depth_isolated(int v, long &calls, int &left_to_explore, int dmax, int * &Kbuff, bool *visited); + + +public: + //degree of v + inline int degree(const int v) { + return deg[v]; + }; + // For debug purposes : verify validity of the graph (symetry, simplicity) + bool verify(); + // Destroy deg[], neigh[] and links[] + ~graph_molloy_hash(); + // Allocate memory for the graph. Create deg and links. No edge is created. + graph_molloy_hash(degree_sequence &); + // Create graph from hard copy + graph_molloy_hash(int *); + // Create hard copy of graph + int *hard_copy(); + // Restore from backup + void restore(int* back); + //Clear hash tables + void init(); + // nb arcs + inline int nbarcs() { + return a; + }; + // nb vertices + inline int nbvertices() { + return n; + }; + // print graph in SUCC_LIST mode, in stdout + void print(FILE *f = stdout); + int print(igraph_t *graph); + // Test if graph is connected + bool is_connected(); + // is edge ? + inline bool is_edge(int a, int b) { + assert(H_is(neigh[a], deg[a], b) == (fast_search(neigh[a], HASH_SIZE(deg[a]), b) != NULL)); + assert(H_is(neigh[b], deg[b], a) == (fast_search(neigh[b], HASH_SIZE(deg[b]), a) != NULL)); + assert(H_is(neigh[a], deg[a], b) == H_is(neigh[b], deg[b], a)); + if (deg[a] < deg[b]) { + return H_is(neigh[a], deg[a], b); + } else { + return H_is(neigh[b], deg[b], a); + } + } + // Random edge swap ATTEMPT. Return 1 if attempt was a succes, 0 otherwise + int random_edge_swap(int K = 0, int *Kbuff = NULL, bool *visited = NULL); + // Connected Shuffle + unsigned long shuffle(unsigned long, unsigned long, int type); + // Optimal window for the gkantsidis heuristics + int optimal_window(); + // Average unitary cost per post-validated edge swap, for some window + double average_cost(int T, int *back, double min_cost); + // Get caracteristic K + double eval_K(int quality = 100); + // Get effective K + double effective_K(int K, int quality = 10000); + // Try to shuffle T times. Return true if at the end, the graph was still connected. + bool try_shuffle(int T, int K, int *back = NULL); + + + /*_____________________________________________________________________________ + Not to use anymore : use graph_molloy_opt class instead + + private: + // breadth-first search. Store the distance (modulo 3) in dist[]. Returns eplorated component size. + int width_search(unsigned char *dist, int *buff, int v0=0); + + public: + // Create graph + graph_molloy_hash(FILE *f); + // Bind the graph avoiding multiple edges or self-edges (return false if fail) + bool havelhakimi(); + // Get the graph connected (return false if fail) + bool make_connected(); + // "Fab" Shuffle (Optimized heuristic of Gkantsidis algo.) + long long fab_connected_shuffle(long long); + // Naive Shuffle + long long slow_connected_shuffle(long long); + // Maximum degree + int max_degree(); + // compute vertex betweenness : for each vertex, a unique random shortest path is chosen. + // this choice is consistent (if shortest path from a to c goes through b and then d, + // then shortest path from a to d goes through b). If(trivial path), also count all the + // shortest paths where vertex is an extremity + int *vertex_betweenness_rsp(bool trivial_path); + // same, but when multiple shortest path are possible, average the weights. + double *vertex_betweenness_asp(bool trivial_path); + //___________________________________________________________________________________ + //*/ + +}; + +} // namespace gengraph + +#endif //GRAPH_MOLLOY_HASH_H + diff --git a/src/gengraph_graph_molloy_optimized.cpp b/src/gengraph_graph_molloy_optimized.cpp new file mode 100644 index 0000000..052345b --- /dev/null +++ b/src/gengraph_graph_molloy_optimized.cpp @@ -0,0 +1,2221 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gengraph_definitions.h" +#include +#include +#include +#include + +#include "gengraph_qsort.h" +#include "gengraph_box_list.h" +#include "gengraph_vertex_cover.h" +#include "gengraph_degree_sequence.h" +#include "gengraph_graph_molloy_optimized.h" + +#include "igraph_error.h" +#include "igraph_statusbar.h" +#include "igraph_progress.h" + +#ifndef register + #define register +#endif + +using namespace std; + +namespace gengraph { + +void graph_molloy_opt::breadth_search(int *dist, int v0, int *buff) { + bool tmpbuff = (buff == NULL); + if (tmpbuff) { + buff = new int[n]; + } + for (int i = 0; i < n; i++) { + dist[i] = -1; + } + dist[v0] = 0; + int *visited = buff; + int *to_visit = buff; + *to_visit++ = v0; + while (visited != to_visit) { + int v = *visited++; + int *w = neigh[v]; + int dd = dist[v] + 1; + for (int d = deg[v]; d--; w++) if (dist[*w] < 0) { + dist[*w] = dd; + *to_visit++ = *w; + } + } + if (tmpbuff) { + delete[] buff; + } +} + + +int graph_molloy_opt::max_degree() { + int m = 0; + for (int k = 0; k < n; k++) if (deg[k] > m) { + m = deg[k]; + } + return m; +} + +void graph_molloy_opt::compute_neigh() { + int *p = links; + for (int i = 0; i < n; i++) { + neigh[i] = p; + p += deg[i]; + } +} + +void graph_molloy_opt::alloc(degree_sequence °s) { + n = degs.size(); + a = degs.sum(); + assert(a % 2 == 0); + deg = new int[n + a]; + for (int i = 0; i < n; i++) { + deg[i] = degs[i]; + } + links = deg + n; + neigh = new int*[n]; + compute_neigh(); +} + +graph_molloy_opt::graph_molloy_opt(degree_sequence °s) { + alloc(degs); +} + +// graph_molloy_opt::graph_molloy_opt(FILE *f) { +// char *buff = new char[FBUFF_SIZE]; +// // How many vertices ? +// if(VERBOSE()) fprintf(stderr,"Read file: #vertices="); +// int i; +// int n=0; +// while(fgets(buff,FBUFF_SIZE,f)) if(sscanf(buff,"%d",&i)==1 && i>n) n=i; +// n++; +// // degrees ? +// if(VERBOSE()) fprintf(stderr,"%d, #edges=",n); +// int *degs = new int[n]; +// for(i=0; i= i) { + *(c++) = *p; + } + } + } + assert(c == b + (a / 2)); + return b; +} + +int *graph_molloy_opt::hard_copy() { + int *hc = new int[2 + n + a / 2]; // to store n,a,deg[] and links[] + hc[0] = n; + hc[1] = a; + memcpy(hc + 2, deg, sizeof(int)*n); + int *c = hc + 2 + n; + for (int i = 0; i < n; i++) { + int *p = neigh[i]; + for (int d = deg[i]; d--; p++) { + assert(*p != i); + if (*p >= i) { + *(c++) = *p; + } + } + } + assert(c == hc + 2 + n + a / 2); + return hc; +} + +void graph_molloy_opt::restore(int* b) { + int i; + for (i = 0; i < n; i++) { + deg[i] = 0; + } + int *p = links; + for (i = 0; i < n - 1; i++) { + p += deg[i]; + deg[i] = int(neigh[i + 1] - neigh[i]); + assert((neigh[i] + deg[i]) == neigh[i + 1]); + while (p != neigh[i + 1]) { + // b points to the current 'j' + neigh[*b][deg[*b]++] = i; + *(p++) = *(b++); + } + } +} + +int* graph_molloy_opt::backup_degs(int *b) { + if (b == NULL) { + b = new int[n]; + } + memcpy(b, deg, sizeof(int)*n); + return b; +} + +void graph_molloy_opt::restore_degs_only(int *b) { + memcpy(deg, b, sizeof(int)*n); + refresh_nbarcs(); +} + +void graph_molloy_opt::restore_degs_and_neigh(int *b) { + restore_degs_only(b); + compute_neigh(); +} + +void graph_molloy_opt::restore_degs(int last_degree) { + a = last_degree; + deg[n - 1] = last_degree; + for (int i = n - 2; i >= 0; i--) { + a += (deg[i] = int(neigh[i + 1] - neigh[i])); + } + refresh_nbarcs(); +} + +void graph_molloy_opt::clean() { + int *b = hard_copy(); + replace(b); + delete[] b; +} + +void graph_molloy_opt::replace(int *_hardcopy) { + delete[] deg; + n = *(_hardcopy++); + a = *(_hardcopy++); + deg = new int[a + n]; + memcpy(deg, _hardcopy, sizeof(int)*n); + links = deg + n; + compute_neigh(); + restore(_hardcopy + n); +} + +int* graph_molloy_opt::components(int *comp) { + int i; + // breadth-first search buffer + int *buff = new int[n]; + // comp[i] will contain the index of the component that contains vertex i + if (comp == NULL) { + comp = new int[n]; + } + memset(comp, 0, sizeof(int)*n); + // current component index + int curr_comp = 0; + // loop over all non-visited vertices... + for (int v0 = 0; v0 < n; v0++) if (comp[v0] == 0) { + curr_comp++; + // initiate breadth-first search + int *to_visit = buff; + int *visited = buff; + *(to_visit++) = v0; + comp[v0] = curr_comp; + // breadth-first search + while (visited != to_visit) { + int v = *(visited++); + int d = deg[v]; + for (int *w = neigh[v]; d--; w++) if (comp[*w] == 0) { + comp[*w] = curr_comp; + *(to_visit++) = *w; + } + } + } + // compute component sizes and store them in buff[] + int nb_comp = 0; + memset(buff, 0, sizeof(int)*n); + for (i = 0; i < n; i++) + if (buff[comp[i] - 1]++ == 0 && comp[i] > nb_comp) { + nb_comp = comp[i]; + } + // box-sort sizes + int offset = 0; + int *box = pre_boxsort(buff, nb_comp, offset); + for (i = nb_comp - 1; i >= 0; i--) { + buff[i] = --box[buff[i] - offset]; + } + delete[] box; + // reassign component indexes + for (int *c = comp + n; comp != c--; *c = buff[*c - 1]) { } + // clean.. at last! + delete[] buff; + return comp; +} + +void graph_molloy_opt::giant_comp() { + int *comp = components(); + // Clear edges of all vertices that do not belong to comp 0 + for (int i = 0; i < n; i++) if (comp[i] != 0) { + deg[i] = 0; + } + // Clean comp[] + delete[] comp; +} + +int graph_molloy_opt::nbvertices_comp() { + int *comp = components(); + // Count all vertices that belong to comp 0 + int nb = 0; + for (int i = 0; i < n; i++) if (comp[i] == 0) { + nb++; + } + // Clean comp[] + delete[] comp; + return nb; +} + +int graph_molloy_opt::nbarcs_comp() { + int *comp = components(); + // Count all vertices that belong to comp 0 + int nb = 0; + for (int i = 0; i < n; i++) if (comp[i] == 0) { + nb += deg[i]; + } + // Clean comp[] + delete[] comp; + return nb; +} + +bool graph_molloy_opt::havelhakimi() { + + int i; + int dmax = max_degree() + 1; + // Sort vertices using basket-sort, in descending degrees + int *nb = new int[dmax]; + int *sorted = new int[n]; + // init basket + for (i = 0; i < dmax; i++) { + nb[i] = 0; + } + // count basket + for (i = 0; i < n; i++) { + nb[deg[i]]++; + } + // cumul + int c = 0; + for (i = dmax - 1; i >= 0; i--) { + c += nb[i]; + nb[i] = -nb[i] + c; + } + // sort + for (i = 0; i < n; i++) { + sorted[nb[deg[i]]++] = i; + } + +// Binding process starts + int first = 0; // vertex with biggest residual degree + int d = dmax - 1; // maximum residual degree available + + for (c = a / 2; c > 0; ) { + // pick a vertex. we could pick any, but here we pick the one with biggest degree + int v = sorted[first]; + // look for current degree of v + while (nb[d] <= first) { + d--; + } + // store it in dv + int dv = d; + // bind it ! + c -= dv; + int dc = d; // residual degree of vertices we bind to + int fc = ++first; // position of the first vertex with degree dc + + while (dv > 0 && dc > 0) { + int lc = nb[dc]; + if (lc != fc) { + while (dv > 0 && lc > fc) { + // binds v with sorted[--lc] + dv--; + int w = sorted[--lc]; + *(neigh[v]++) = w; + *(neigh[w]++) = v; + } + fc = nb[dc]; + nb[dc] = lc; + } + dc--; + } + if (dv != 0) { // We couldn't bind entirely v + delete[] nb; + delete[] sorted; + compute_neigh(); + igraph_errorf("Error in graph_molloy_opt::havelhakimi():" + " Couldn't bind vertex %d entirely " + "(%d edges remaining)", __FILE__, __LINE__, + IGRAPH_EINTERNAL, v, dv); + return false; + } + } + assert(c == 0); + compute_neigh(); + delete[] nb; + delete[] sorted; + return true; +} + +bool graph_molloy_opt::is_connected() { + bool *visited = new bool[n]; + for (int i = n; i > 0; visited[--i] = false) { } + int *to_visit = new int[n]; + int *stop = to_visit; + int left = n - 1; + *(to_visit++) = 0; + visited[0] = true; + while (left > 0 && to_visit != stop) { + int v = *(--to_visit); + int *w = neigh[v]; + for (int k = deg[v]; k--; w++) if (!visited[*w]) { + visited[*w] = true; + left--; + *(to_visit++) = *w; + } + } + delete[] visited; + delete[] stop; + assert(left >= 0); + return (left == 0); +} + + +bool graph_molloy_opt::make_connected() { + //assert(verify()); + if (a / 2 < n - 1) { + // fprintf(stderr,"\ngraph::make_connected() failed : #edges < #vertices-1\n"); + return false; + } + int i; + +// Data struct for the visit : +// - buff[] contains vertices to visit +// - dist[V] is V's distance modulo 4 to the root of its comp, or -1 if it hasn't been visited yet +#define MC_BUFF_SIZE (n+2) + int *buff = new int[MC_BUFF_SIZE]; + unsigned char * dist = new unsigned char[n]; +#define NOT_VISITED 255 +#define FORBIDDEN 254 + for (i = n; i > 0; dist[--i] = NOT_VISITED) { } + +// Data struct to store components : either surplus trees or surplus edges are stored at buff[]'s end +// - A Tree is coded by one of its vertices +// - An edge (a,b) is coded by the TWO ints a and b + int *ffub = buff + MC_BUFF_SIZE; + edge *edges = (edge *) ffub; + int *trees = ffub; + int *min_ffub = buff + 1 + (MC_BUFF_SIZE % 2 ? 0 : 1); + +// There will be only one "fatty" component, and trees. + edge fatty_edge = { -1, -1 }; + bool enough_edges = false; + + // start main loop + for (int v0 = 0; v0 < n; v0++) if (dist[v0] == NOT_VISITED) { + // is v0 an isolated vertex? + if (deg[v0] == 0) { + delete[] dist; + delete[] buff; + igraph_errorf("graph_molloy_opt::make_connected() returned FALSE : " + "vertex %d has degree 0", __FILE__, __LINE__, + IGRAPH_EINTERNAL, v0); + return false; + } + dist[v0] = 0; // root + int *to_visit = buff; + int *current = buff; + *(to_visit++) = v0; + + // explore component connected to v0 + bool is_a_tree = true; + while (current != to_visit) { + int v = *(current++); + unsigned char current_dist = dist[v]; + unsigned char next_dist = (current_dist + 1) & 0x03; + //unsigned char prev_dist = (current_dist-1) & 0x03; + int* ww = neigh[v]; + int w; + for (int k = deg[v]; k--; ww++) { + if (dist[w = *ww] == NOT_VISITED) { + // we didn't visit *w yet + dist[w] = next_dist; + *(to_visit++) = w; + if (to_visit > min_ffub) { + min_ffub += 2; // update limit of ffub's storage + } + //assert(verify()); + } else if (dist[w] == next_dist || (w >= v && dist[w] == current_dist)) { + // we found a removable edge + if (trees != ffub) { + // some trees still.. Let's merge with them! + assert(trees >= min_ffub); + assert(edges == (edge *)ffub); + swap_edges(v, w, *trees, neigh[*trees][0]); + trees++; + //assert(verify()); + } else if (is_a_tree) { + // we must merge with the fatty component + is_a_tree = false; + if (fatty_edge.from < 0) { + // we ARE the first component! fatty is us + fatty_edge.from = v; + fatty_edge.to = w; + } else { + // we connect to fatty + swap_edges(fatty_edge.from, fatty_edge.to, v, w); + fatty_edge.to = w; + //assert(verify()); + } + } else if (!enough_edges) { + // Store the removable edge for future use + if (edges <= (edge *)min_ffub + 1) { + enough_edges = true; + } else { + edges--; + edges->from = v; + edges->to = w; + } + } + } + } + } + // Mark component + while (to_visit != buff) { + dist[*(--to_visit)] = FORBIDDEN; + } + // Check if it is a tree + if (is_a_tree ) { + assert(deg[v0] != 0); + if (edges != (edge *)ffub) { + // let's bind the tree we found with a removable edge in stock + assert(trees == ffub); + if (edges < (edge *)min_ffub) { + edges = (edge *)min_ffub; + } + swap_edges(v0, neigh[v0][0], edges->from, edges->to); + edges++; + assert(verify()); + } else if (fatty_edge.from >= 0) { + // if there is a fatty component, let's merge with it ! and discard fatty :-/ + assert(trees == ffub); + swap_edges(v0, neigh[v0][0], fatty_edge.from, fatty_edge.to); + fatty_edge.from = -1; + fatty_edge.to = -1; + assert(verify()); + } else { + // add the tree to the list of trees + assert(trees > min_ffub); + *(--trees) = v0; + assert(verify()); + } + } + } + delete[] buff; + delete[] dist; + // Should ALWAYS return true : either we have no tree left, or we are a unique, big tree + return (trees == ffub || ((trees + 1) == ffub && fatty_edge.from < 0)); +} + +bool graph_molloy_opt::swap_edges_simple(int from1, int to1, int from2, int to2) { + if (from1 == to1 || from1 == from2 || from1 == to2 || to1 == from2 || to1 == to2 || from2 == to2) { + return false; + } + if (is_edge(from1, to2) || is_edge(from2, to1)) { + return false; + } + swap_edges(from1, to1, from2, to2); + return true; +} + +long graph_molloy_opt::fab_connected_shuffle(long times) { + //assert(verify()); + long nb_swaps = 0; + double T = double(min(a, times)) / 10.0; + double q1 = 1.131; + double q2 = 0.9237; + + while (times > 0) { + long iperiod = max(1, long(T)); + // Backup graph + int *save = backup(); + //assert(verify()); + // Swaps + long swaps = 0; + for (long i = iperiod; i > 0; i--) { + // Pick two random vertices + int f1 = links[my_random() % a]; + int f2 = links[my_random() % a]; + if (f1 == f2) { + continue; + } + // Pick two random neighbours + int *f1t1 = neigh[f1] + my_random() % deg[f1]; + int *f2t2 = neigh[f2] + my_random() % deg[f2]; + int t1 = *f1t1; + int t2 = *f2t2; + // test simplicity + if (t1 != t2 && f1 != t2 && f2 != t1 && is_edge(f1, t2) && !is_edge(f2, t1)) { + // swap + *f1t1 = t2; + *f2t2 = t1; + fast_rpl(neigh[t1], f1, f2); + fast_rpl(neigh[t2], f2, f1); + swaps++; + } + } + //assert(verify()); + // test connectivity + if (is_connected()) { + nb_swaps += swaps; + times -= iperiod; + // adjust T + T *= q1; + } else { + restore(save); + //assert(verify()); + T *= q2; + } + delete[] save; + } + return nb_swaps; +} + +long graph_molloy_opt::opt_fab_connected_shuffle(long times) { + //assert(verify()); + long nb_swaps = 0; + double T = double(min(a, times)) / 10.0; + double q1 = 1.131; + double q2 = 0.9237; + + while (times > 0) { + long iperiod = max(1, long(T)); + // Backup graph + int *save = backup(); + //assert(verify()); + // Swaps + long swaps = 0; + for (long i = iperiod; i > 0; i--) { + // Pick two random vertices + int f1 = links[my_random() % a]; + int f2 = links[my_random() % a]; + if (f1 == f2) { + continue; + } + // Pick two random neighbours + int *f1t1 = neigh[f1] + my_random() % deg[f1]; + int *f2t2 = neigh[f2] + my_random() % deg[f2]; + int t1 = *f1t1; + int t2 = *f2t2; + if ( + // test simplicity + t1 != t2 && f1 != t2 && f2 != t1 && is_edge(f1, t2) && !is_edge(f2, t1) && + // test isolated pair + (deg[f1] > 1 || deg[t2] > 1) && (deg[f2] > 1 || deg[t1] > 1) + ) { + // swap + *f1t1 = t2; + *f2t2 = t1; + fast_rpl(neigh[t1], f1, f2); + fast_rpl(neigh[t2], f2, f1); + swaps++; + } + } + //assert(verify()); + // test connectivity + if (is_connected()) { + nb_swaps += swaps; + times -= iperiod; + // adjust T + T *= q1; + } else { + restore(save); + //assert(verify()); + T *= q2; + } + delete[] save; + } + return nb_swaps; +} + +long graph_molloy_opt::gkantsidis_connected_shuffle(long times) { + //assert(verify()); + long nb_swaps = 0; + long T = min(a, times) / 10; + + while (times > 0) { + // Backup graph + int *save = backup(); + //assert(verify()); + // Swaps + long swaps = 0; + for (int i = T; i > 0; i--) { + // Pick two random vertices + int f1 = links[my_random() % a]; + int f2 = links[my_random() % a]; + if (f1 == f2) { + continue; + } + // Pick two random neighbours + int *f1t1 = neigh[f1] + my_random() % deg[f1]; + int *f2t2 = neigh[f2] + my_random() % deg[f2]; + int t1 = *f1t1; + int t2 = *f2t2; + // test simplicity + if (t1 != t2 && f1 != t2 && f2 != t1 && is_edge(f1, t2) && !is_edge(f2, t1)) { + // swap + *f1t1 = t2; + *f2t2 = t1; + fast_rpl(neigh[t1], f1, f2); + fast_rpl(neigh[t2], f2, f1); + swaps++; + } + } + //assert(verify()); + // test connectivity + if (is_connected()) { + nb_swaps += swaps; + times -= T; + // adjust T + T++; + } else { + restore(save); + //assert(verify()); + T /= 2; if (T == 0) T = 1; + } + delete[] save; + } + return nb_swaps; +} + +long graph_molloy_opt::slow_connected_shuffle(long times) { + //assert(verify()); + long nb_swaps = 0; + + while (times--) { + // Pick two random vertices + int f1 = links[my_random() % a]; + int f2 = links[my_random() % a]; + if (f1 == f2) { + continue; + } + // Pick two random neighbours + int *f1t1 = neigh[f1] + my_random() % deg[f1]; + int *f2t2 = neigh[f2] + my_random() % deg[f2]; + int t1 = *f1t1; + int t2 = *f2t2; + // test simplicity + if (t1 != t2 && f1 != t2 && f2 != t1 && is_edge(f1, t2) && !is_edge(f2, t1)) { + // swap + *f1t1 = t2; + *f2t2 = t1; + int *t1f1 = fast_rpl(neigh[t1], f1, f2); + int *t2f2 = fast_rpl(neigh[t2], f2, f1); + // test connectivity + if (is_connected()) { + nb_swaps++; + } else { + // undo swap + *t1f1 = f1; *t2f2 = f2; *f1t1 = t1; *f2t2 = t2; + } + } + } + return nb_swaps; +} + +void graph_molloy_opt::print(FILE *f, bool NOZERO) { + int i, j; + for (i = 0; i < n; i++) { + if (!NOZERO || deg[i] > 0) { + fprintf(f, "%d", i); + for (j = 0; j < deg[i]; j++) { + fprintf(f, " %d", neigh[i][j]); + } + fprintf(f, "\n"); + } + } +} + +long graph_molloy_opt::effective_isolated(int v, int K, int *Kbuff, bool *visited) { + int i; + for (i = 0; i < K; i++) { + Kbuff[i] = -1; + } + long count = 0; + int left = K; + int *KB = Kbuff; + //yapido = (my_random()%1000 == 0); + depth_isolated(v, count, left, K, KB, visited); + while (KB-- != Kbuff) { + visited[*KB] = false; + } + //if(yapido) fprintf(stderr,"\n"); + return count; +} + +void graph_molloy_opt::depth_isolated(int v, long &calls, int &left_to_explore, int dmax, int * &Kbuff, bool *visited) { + if (left_to_explore == 0) { + return; + } +// if(yapido) fprintf(stderr,"%d ",deg[v]); + if (--left_to_explore == 0) { + return; + } + if (deg[v] + 1 >= dmax) { + left_to_explore = 0; + return; + } + *(Kbuff++) = v; + visited[v] = true; + calls++; + int *w = neigh[v]; + qsort(deg, w, deg[v]); + w += deg[v]; + for (int i = deg[v]; i--; ) { + if (visited[*--w]) { + calls++; + } else { + depth_isolated(*w, calls, left_to_explore, dmax, Kbuff, visited); + } + if (left_to_explore == 0) { + break; + } + } +} + +int graph_molloy_opt::depth_search(bool *visited, int *buff, int v0) { + for (int i = 0; i < n; i++) { + visited[i] = false; + } + int *to_visit = buff; + int nb_visited = 1; + visited[v0] = true; + *(to_visit++) = v0; + while (to_visit != buff && nb_visited < n) { + int v = *(--to_visit); + int *ww = neigh[v]; + int w; + for (int k = deg[v]; k--; ww++) if (!visited[w = *ww]) { + visited[w] = true; + nb_visited++; + *(to_visit++) = w; + } + } + return nb_visited; +} + +int graph_molloy_opt::width_search(unsigned char *dist, int *buff, int v0, int toclear) { + if (toclear >= 0) for (int i = 0; i < toclear; i++) { + dist[buff[i]] = 0; + } else for (int i = 0; i < n; i++) { + dist[i] = 0; + } + int *to_visit = buff; + int *to_add = buff; + int nb_visited = 1; + dist[v0] = 1; + *(to_add++) = v0; + while (to_visit != to_add && nb_visited < n) { + int v = *(to_visit++); + int *ww = neigh[v]; + int w; + unsigned char d = next_dist(dist[v]); + for (int k = deg[v]; k--; ww++) if (dist[w = *ww] == 0) { + dist[w] = d; + nb_visited++; + *(to_add++) = w; + } + } + return nb_visited; +} + +double graph_molloy_opt::avg_dist(unsigned char *dist, int *buff, int v0, int &nb_visited, int toclear) { + nb_visited = width_search(dist, buff, v0, toclear); + unsigned char curr_dist = 1; + assert(curr_dist == dist[v0]); + double total_dist = 0.0; + int current_dist = 0; + for (int p = 0; p < nb_visited; p++) { + v0 = buff[p]; + if (dist[v0] != curr_dist) { + current_dist++; + curr_dist = dist[v0]; + } + total_dist += double(current_dist); + } + nb_visited--; + return total_dist / double(nb_visited); +} + + +void graph_molloy_opt::add_traceroute_edge(int v, int k, int *newdeg, double **edge_redudancy, double red) { + int *ww = neigh[v] + k; + int w = *ww; + int k2 = 0; + // Is neigh[v][k] a new edge ? + if (k >= newdeg[v]) { + int *p = neigh[v] + (newdeg[v]++); + *ww = *p; + *p = w; + // Now, add the dual edge + ww = neigh[w]; + p = ww + (newdeg[w]); + while (ww != p && *ww != v) { + ww++; + k2++; + } + if (ww == p) { + // dual edge was not discovered.. search it and add it. + while (*ww != v) { + ww++; + k2++; + } + *ww = *p; + *p = v; + newdeg[w]++; + } + } + // if edge redudancy is asked, look for dual edge + else if (edge_redudancy != NULL) + for (int *ww = neigh[w]; * (ww++) != v; k2++) { } + // add edge redudancy + if (edge_redudancy != NULL) { + edge_redudancy[v][k] += red; + edge_redudancy[w][k2] += red; + } + assert(newdeg[v] <= deg[v]); +} + +// dist[] MUST be full of zeros !!!! +int graph_molloy_opt::breadth_path_search(int src, int *buff, double *paths, unsigned char *dist) { + unsigned char last_dist = 0; + unsigned char curr_dist = 1; + int *to_visit = buff; + int *visited = buff; + *(to_visit++) = src; + paths[src] = 1.0; + dist[src] = curr_dist; + int nb_visited = 1; + while (visited != to_visit) { + int v = *(visited++); + if (last_dist == (curr_dist = dist[v])) { + break; + } + unsigned char nd = next_dist(curr_dist); + int *ww = neigh[v]; + double p = paths[v]; + for (int k = deg[v]; k--;) { + int w = *(ww++); + unsigned char d = dist[w]; + if (d == 0) { + // not visited yet ! + *(to_visit++) = w; + dist[w] = nd; + paths[w] = p; + // is it the last one ? + if (++nb_visited == n) { + last_dist = nd; + } + } else if (d == nd) if ((paths[w] += p) == numeric_limits::infinity()) { + IGRAPH_ERROR("Fatal error : too many (>MAX_DOUBLE) possible" + " paths in graph", IGRAPH_EOVERFLOW); + } + } + } + assert(to_visit == buff + nb_visited); + return nb_visited; +} + +// dist[] MUST be full of zeros !!!! +void graph_molloy_opt::explore_usp(double *target, int nb_vertices, int *buff, double *paths, unsigned char *dist, int *newdeg, double **edge_redudancy) { + + while (--nb_vertices) { + int v = buff[nb_vertices]; + if (target[v] > 0.0) { + unsigned char pd = prev_dist(dist[v]); + int *ww = neigh[v]; + int k = 0; + // pick ONE father at random + double father_index = my_random01() * paths[v]; + double f = 0.0; + int father = -1; + while (f < father_index) { + while (dist[father = ww[k++]] != pd) { } + f += paths[father]; + } + // increase target[] of father + target[father] += target[v]; + // add edge, if necessary + if (newdeg != NULL) { + add_traceroute_edge(v, k - 1, newdeg, edge_redudancy, target[v]); + } + } + // clear dist[] + dist[v] = 0; + } + dist[buff[0]] = 0; +} + +// dist[] MUST be full of zeros !!!! +void graph_molloy_opt::explore_asp(double *target, int nb_vertices, int *buff, double *paths, unsigned char *dist, int *newdeg, double **edge_redudancy) { + + while (--nb_vertices) { + int v = buff[nb_vertices]; + if (target[v] > 0.0) { + unsigned char pd = prev_dist(dist[v]); + int *ww = neigh[v]; + int dv = deg[v]; + double f = target[v] / paths[v]; + // pick ALL fathers + register int father; + for (int k = 0; k < dv; k++) if (dist[father = ww[k]] == pd) { + // increase target[] of father + target[father] += paths[father] * f; + // add edge, if necessary + if (newdeg != NULL) { + add_traceroute_edge(v, k, newdeg, edge_redudancy, target[v]); + } + } + } + // clear dist[] + dist[v] = 0; + } + dist[buff[0]] = 0; +} + +// dist[] MUST be full of zeros !!!! +void graph_molloy_opt::explore_rsp(double *target, int nb_vertices, int *buff, double *paths, unsigned char *dist, int *newdeg, double** edge_redudancy) { + + while (--nb_vertices) { + int v = buff[nb_vertices]; + if (target[v] > 0.0) { + unsigned char pd = prev_dist(dist[v]); + int *ww = neigh[v]; + // for all fathers : do we take it ? + int paths_left = int(target[v]); + double father_index = paths[v]; + int father; + for (int k = 0; k < deg[v]; k++) if (dist[father = ww[k]] == pd) { + double pf = paths[father]; + int to_add_to_father = my_binomial(pf / father_index, paths_left); + father_index -= pf; + if (to_add_to_father > 0) { + paths_left -= to_add_to_father; + // increase target[] of father + target[father] += to_add_to_father; + // add edge, if necessary + if (newdeg != NULL) { + add_traceroute_edge(v, k, newdeg, edge_redudancy, target[v]); + } + } + } + } + // clear dist[] + dist[v] = 0; + } + dist[buff[0]] = 0; +} + +double *graph_molloy_opt::vertex_betweenness(int mode, bool trivial_paths) { + char MODES[3] = {'U', 'A', 'R'}; + igraph_statusf("Computing vertex betweenness %cSP...", 0, MODES[mode]); + + // breadth-first search vertex fifo + int *buff = new int[n]; + // breadth-first search path count + double *paths = new double[n]; + // breadth-first search distance vector + unsigned char *dist = new unsigned char[n]; + // global betweenness + double *b = new double[n]; + // local betweenness (for one source) + double *target = new double[n]; + // init all + int progress = 0; + memset(dist, 0, sizeof(unsigned char)*n); + for (double *yo = target + n; (yo--) != target; *yo = 1.0) { } + for (double *yo = b + n; (yo--) != b; *yo = 0.0) { } + + int progress_steps = max(1000, n / 10); + // Main loop + for (int v0 = 0; v0 < n; v0++) { + // Verbose + if (v0 > (progress * n) / progress_steps) { + progress++; + igraph_progressf("Computing vertex betweenness %cSP", + 100.0 * double(progress) / double(progress_steps), 0, + MODES[mode]); + } + // Breadth-first search + int nb_vertices = breadth_path_search(v0, buff, paths, dist); + // initialize target[vertices in component] to 1 + //for(int *yo = buff+nb_vertices; (yo--)!=buff; target[*yo]=1.0); + // backwards-cumulative exploration + switch (mode) { + case MODE_USP: + explore_usp(target, nb_vertices, buff, paths, dist); break; + case MODE_ASP: + explore_asp(target, nb_vertices, buff, paths, dist); break; + case MODE_RSP: + explore_rsp(target, nb_vertices, buff, paths, dist); break; + default: + IGRAPH_WARNING("graph_molloy_opt::vertex_betweenness() " + "called with Invalid Mode"); + } + // add targets[vertices in component] to global betweenness and reset targets[] + if (nb_vertices == n) { + // cache optimization if all vertices are in component + double *bb = b; + double *tt_end = target + n; + if (trivial_paths) for (double *yo = target; yo != tt_end; * (bb++) += *(yo++)) {} + else { + for (double *yo = target; yo != tt_end; * (bb++) += (*(yo++) - 1.0)) { } + b[*buff] -= (target[*buff] - 1.0); + } + for (double *yo = target; yo != tt_end; * (yo++) = 1.0) { } + } else { + if (trivial_paths) + for (int *yo = buff + nb_vertices; (yo--) != buff; b[*yo] += target[*yo]) { } + else + for (int *yo = buff + nb_vertices; (--yo) != buff; b[*yo] += (target[*yo] - 1.0)) { } + for (int *yo = buff + nb_vertices; (yo--) != buff; target[*yo] = 1.0) { } + } + } + // Clean all & return + delete[] target; + delete[] dist; + delete[] buff; + delete[] paths; + igraph_status("Done\n", 0); + return b; +} + +double graph_molloy_opt::traceroute_sample(int mode, int nb_src, int *src, int nb_dst, int* dst, double *redudancy, double **edge_redudancy) { + // verify & verbose + assert(verify()); + char MODES[3] = {'U', 'A', 'R'}; + igraph_statusf("traceroute %cSP on G(N=%d,M=%d) with %d src and %d dst...", + 0, MODES[mode], nbvertices_real(), nbarcs(), nb_src, nb_dst); + + // create dst[] buffer if necessary + bool newdist = dst == NULL; + if (newdist) { + dst = new int[n]; + } + // breadth-first search vertex fifo + int *buff = new int[n]; + // breadth-first search path count + double *paths = new double[n]; + // breadth-first search distance vector + unsigned char *dist = new unsigned char[n]; + // newdeg[] allows to tag discovered edges + int *newdeg = new int[n]; + // target[v] is > 0 if v is a destination + double *target = new double[n]; + + // init all + int i; + memset(dist, 0, sizeof(unsigned char)*n); + memset(newdeg, 0, sizeof(int)*n); + for (double *yo = target + n; (yo--) != target; *yo = 0.0) { } + if (redudancy != NULL) + for (double *yo = redudancy + n; (yo--) != redudancy; *yo = 0.0) { } + + // src_0 counts the number of sources having degree 0 + int src_0 = 0; + // nopath counts the number of pairs (src,dst) having no possible path + int nopath = 0; + // nb_paths & total_dist are for the average distance estimator + int nb_paths = 0; + double total_dist = 0; + // s will be the current source + int s; + + while (nb_src--) if (deg[s = *(src++)] == 0) { + src_0++; + } else { + // breadth-first search + int nb_vertices = breadth_path_search(s, buff, paths, dist); + // do we have to pick new destinations ? + if (newdist) { + pick_random_dst(double(nb_dst), NULL, dst); + } + // mark reachable destinations as "targets" + for (i = 0; i < nb_dst; i++) { + if (dist[dst[i]] != 0) { + target[dst[i]] = 1.0; + } else { + nopath++; + } + } + // compute avg_dist estimator + int current_dist = 0; + unsigned char curr_dist = 1; + for (int p = 1; p < nb_vertices; p++) { + int v = buff[p]; + if (dist[v] != curr_dist) { + curr_dist = dist[v]; + current_dist++; + } + if (target[v] > 0.0) { + total_dist += double(current_dist); + nb_paths++; + } + } + // substract target[] to redudancy if needed + if (redudancy != NULL) for (i = 1; i < nb_vertices; i++) { + redudancy[buff[i]] -= (target[buff[i]]); + } + // traceroute exploration + switch (mode) { + case MODE_USP: + explore_usp(target, nb_vertices, buff, paths, dist, newdeg, edge_redudancy); break; + case MODE_ASP: + explore_asp(target, nb_vertices, buff, paths, dist, newdeg, edge_redudancy); break; + case MODE_RSP: + explore_rsp(target, nb_vertices, buff, paths, dist, newdeg, edge_redudancy); break; + default: + IGRAPH_WARNING("graph_molloy_opt::traceroute_sample() called " + "with Invalid Mode"); + } + // add target[] to redudancy[] if needed + if (redudancy != NULL) for (i = 1; i < nb_vertices; i++) { + redudancy[buff[i]] += (target[buff[i]]); + } + // clear target[] + for (int *yo = buff + nb_vertices; yo-- != buff; target[*yo] = 0.0) { } + } + // update degrees + for (i = 0; i < n; i++) { + deg[i] = newdeg[i]; + } + refresh_nbarcs(); + // clean all + delete[] buff; + delete[] paths; + delete[] dist; + delete[] newdeg; + delete[] target; + if (newdist) { + delete[] dst; + } + { + igraph_statusf("discovered %d vertices and %d edges\n", 0, + nbvertices_real(), nbarcs()); + if (src_0) igraph_warningf("%d sources had degree 0\n", __FILE__, + __LINE__, -1, src_0); + if (nopath) igraph_warningf("%d (src,dst) pairs had no possible path\n", + __FILE__, __LINE__, -1, nopath); + } + return total_dist / double(nb_paths); +} + +int graph_molloy_opt::disconnecting_edges() { + int removed = 0; + while (is_connected()) { + // replace random edge by loops + int i; + do { + i = pick_random_vertex(); + } while (i < 0 || deg[i] < 1); + int *p = neigh[i] + (my_random() % deg[i]); + int j = *p; *p = i; + fast_rpl(neigh[j], i, j); + removed++; + } + return removed; +} + +void graph_molloy_opt::vertex_covering() { + vertex_cover(n, links, deg, neigh); +} + + +// optimisations a faire : +// 1/ arreter le breadth-first search qd on a vu toutes les dst +// 2/ faire une seule redescente pour toutes les dst. + +double graph_molloy_opt::path_sampling(int *nb_dst, int *dst, double* redudancies, double **edge_redudancies) { + assert(verify()); + // do we have to store the destinations (for one src) in a temp buffer? + bool NOMEM = (dst == NULL); + if (NOMEM) { + dst = new int[n]; + } + int i; + int next_step = n + 1; + { + igraph_status("Sampling paths", 0); + next_step = 0; + } + // breadth-first search buffers buff[] and dist[] + int *buff = new int[n]; + unsigned char *dist = new unsigned char[n]; + for (i = 0; i < n; i++) { + dist[i] = 0; + } + // nb_pos[] counts the number of possible paths to get to a vertex + int *nb_pos = new int[n]; + for (i = 0; i < n; i++) { + nb_pos[i] = 0; + } + // newdeg[i] is the number of edges of vertex i "seen" by traceroute + int *newdeg = new int[n]; + for (i = 0; i < n; i++) { + newdeg[i] = 0; + } + + // src_0 counts the number of sources having degree 0 + int src_0 = 0; + // nopath counts the number of pairs (src,dst) having no possible path + int nopath = 0; + // nb_paths & total_dist are for the average distance estimator + int nb_paths = 0; + unsigned int total_dist = 0; + unsigned int total_dist64 = 0; + + // s is the source of the breadth-first search + for (int s = 0; s < n; s++) if (nb_dst[s] > 0) { + if (deg[s] == 0) { + src_0++; + } else { + if (s > next_step) { + next_step = s + (n / 1000) + 1; + igraph_progress("Sampling paths", double(s) / double(n), 0); + } + int v; + // breadth-first search + int *to_visit = buff; + int *visited = buff; + *(to_visit++) = s; + dist[s] = 1; + nb_pos[s] = 1; + while (visited != to_visit) { + v = *(visited++); + unsigned char n_dist = next_dist(dist[v]); + int *w0 = neigh[v]; + for (int *w = w0 + deg[v]; w-- != w0; ) { + unsigned char d2 = dist[*w]; + if (d2 == 0) { + dist[*w] = d2 = n_dist; + *(to_visit++) = *w; + } + if (d2 == n_dist) { + nb_pos[*w] += nb_pos[v]; + } + } + } + + // for every target, pick a random path. + int t_index = nb_dst[s]; + // create dst[] if necessary + if (NOMEM) { + pick_random_src(double(t_index), NULL, dst); + } + while (t_index--) if (dist[v = *(dst++)] == 0) { + nopath++; + } else { +#ifdef _DEBUG + igraph_statusf("Sampling path %d -> %d\n", 0, s, v); +#endif //_DEBUG + nb_paths++; + // while we haven't reached the source.. + while (v != s) { + // pick a random father + int index = my_random() % nb_pos[v]; + unsigned char p_dist = prev_dist(dist[v]); + int *w = neigh[v]; + int k = 0; + int new_father; + while (dist[new_father = w[k]] != p_dist || (index -= nb_pos[new_father]) >= 0) { + k++; + } + // add edge + add_traceroute_edge(v, k, newdeg, edge_redudancies, 1.0); + if (redudancies != NULL && new_father != s) { + redudancies[new_father] += 1.0; + } + // step down to father + v = new_father; + // increase total distance + total_dist++; + if (total_dist == 0) { + total_dist64++; + } + } + } + // reset (int *)dst if necessary + if (NOMEM) { + dst -= nb_dst[s]; + } + + // clear breadth-first search buffers + while (visited != buff) { + v = *(--visited); + dist[v] = 0; + nb_pos[v] = 0; + } + } + } + // update degrees + for (i = 0; i < n; i++) { + deg[i] = newdeg[i]; + } + refresh_nbarcs(); + // clean + delete[] newdeg; + delete[] buff; + delete[] dist; + delete[] nb_pos; + if (NOMEM) { + delete[] dst; + } + if (VERBOSE()) { + igraph_status("Sampling paths : Done \n", 0); + if (src_0) igraph_warningf("%d sources had degree 0", __FILE__, + __LINE__, -1, src_0); + if (nopath) igraph_warningf("%d (src,dst) pairs had no possible path", + __FILE__, __LINE__, -1, nopath); + } + double tdist = double(total_dist64); + if (total_dist64 > 0) { + tdist *= 4294967296.0; + } + tdist += double(total_dist); + return tdist / double(nb_paths); +} + +int *graph_molloy_opt::vertices_real(int &nb_v) { + int *yo; + if (nb_v < 0) { + nb_v = 0; + for (yo = deg; yo != deg + n; ) if (*(yo++) > 0) { + nb_v++; + } + } + if (nb_v == 0) { + IGRAPH_WARNING("graph is empty"); + return NULL; + } + int *buff = new int[nb_v]; + yo = buff; + for (int i = 0; i < n; i++) if (deg[i] > 0) { + *(yo++) = i; + } + if (yo != buff + nb_v) { + igraph_warningf("wrong #vertices in graph_molloy_opt::vertices_real(%d)", + __FILE__, __LINE__, -1, nb_v); + delete[] buff; + return NULL; + } else { + return buff; + } +} + +int *graph_molloy_opt::pick_random_vertices(int &k, int *output, int nb_v, int *among) { + int i; + bool CREATED_AMONG = false; + if (among == NULL && k > 0) { + among = vertices_real(nb_v); + CREATED_AMONG = true; + } + if (k > nb_v) { + igraph_warningf("Warning : tried to pick %d among %d vertices. " + "Picked only %d", __FILE__, __LINE__, -1, k, nb_v, nb_v); + k = nb_v; + } + if (k > 0) { + if (output == NULL) { + output = new int[k]; + } + for (i = 0; i < k; i++) { + int tmp = i + my_random() % (nb_v - i); + output[i] = among[tmp]; + among[tmp] = among[i]; + among[i] = output[i]; + } + } + if (CREATED_AMONG) { + delete[] among; + } + return output; +} + +int *graph_molloy_opt::pick_random_src(double k, int *nb, int* buff, int nb_v, int* among) { + bool AMONG_CREATED = false; + if (among == NULL || nb_v < 0) { + AMONG_CREATED = true; + among = vertices_real(nb_v); + } + int kk = int(floor(0.5 + (k >= 1.0 ? k : k * double(nb_v)))); + if (kk == 0) { + kk = 1; + } + int *yo = pick_random_vertices(kk, buff, nb_v, among); + if (nb != NULL) { + *nb = kk; + } + if (AMONG_CREATED) { + delete[] among; + } + return yo; +} + +int *graph_molloy_opt::pick_random_dst(double k, int *nb, int* buff, int nb_v, int* among) { + bool AMONG_CREATED = false; + if (among == NULL || nb_v < 0) { + AMONG_CREATED = true; + among = vertices_real(nb_v); + } + int kk = int(floor(0.5 + (k > 1.0 ? k : k * double(nb_v)))); + if (kk == 0) { + kk = 1; + } + int *yo = pick_random_vertices(kk, buff, nb_v, among); + if (nb != NULL) { + *nb = kk; + } + if (AMONG_CREATED) { + delete[] among; + } + return yo; +} + +int graph_molloy_opt::core() { + box_list b(n, deg); + int v; + int removed = 0; + while ((v = b.get_one()) >= 0) { + b.pop_vertex(v, neigh); + deg[v] = 0; + removed++; + } + refresh_nbarcs(); + return removed; +} + +int graph_molloy_opt::try_disconnect(int K, int max_tries) { + bool *visited = new bool[n]; + for (bool *p = visited + n; p != visited; * (--p) = false) { } + int *Kbuff = new int[K]; + int tries = 0; + int next_step = -1; + if (VERBOSE()) { + next_step = 0; + } + bool yo = true; + while (yo && tries < max_tries) { + if (tries == next_step) { + igraph_statusf("Trying to disconnect the graph... " + "%d edges swaps done so far", 0, tries); + next_step += 100; + } + int v1 = pick_random_vertex(); + int v2 = pick_random_vertex(); + int w1 = *(random_neighbour(v1)); + int w2 = *(random_neighbour(v2)); + if (swap_edges_simple(v1, w1, v2, w2)) { + tries++; + yo = (!isolated(v1, K, Kbuff, visited) && !isolated(v2, K, Kbuff, visited) && !is_connected()); + swap_edges(v1, w2, v2, w1); + } + } + delete[] visited; + delete[] Kbuff; + return tries; +} + +bool graph_molloy_opt::isolated(int v, int K, int *Kbuff, bool *visited) { + if (K < 2) { + return false; + } +#ifdef OPT_ISOLATED + if (K <= deg[v] + 1) { + return false; + } +#endif //OPT_ISOLATED + int *seen = Kbuff; + int *known = Kbuff; + int *max = Kbuff + (K - 1); + *(known++) = v; + visited[v] = true; + bool is_isolated = true; + + while (known != seen) { + v = *(seen++); + int *w = neigh[v]; + for (int d = deg[v]; d--; w++) if (!visited[*w]) { +#ifdef OPT_ISOLATED + if (K <= deg[*w] + 1 || known == max) { +#else //OPT_ISOLATED + if (known == max) { +#endif //OPT_ISOLATED + is_isolated = false; + goto end_isolated; + } + visited[*w] = true; + *(known++) = *w; + } + } +end_isolated: + // Undo the changes to visited[]... + while (known != Kbuff) { + visited[*(--known)] = false; + } + return is_isolated; +} + +double graph_molloy_opt::rho(int mode, int nb_src, int *src, int nb_dst, int *dst) { + assert(verify()); + + // create dst[] buffer if necessary + bool newdist = dst == NULL; + if (newdist) { + dst = new int[n]; + } + // breadth-first search vertex fifo + int *buff = new int[n]; + // breadth-first search path count + double *paths = new double[n]; + // breadth-first search distance vector + unsigned char *dist = new unsigned char[n]; + // target[v] is > 0 if v is a destination + double *target = new double[n]; + // times_seen count the times we saw each vertex + int *times_seen = new int[n]; + + // init all + int i; + memset(dist, 0, sizeof(unsigned char)*n); + memset(times_seen, 0, sizeof(int)*n); + for (double *yo = target + n; (yo--) != target; *yo = 0.0) { } + + // src_0 counts the number of sources having degree 0 + int src_0 = 0; + // nopath counts the number of pairs (src,dst) having no possible path + int nopath = 0; + // s will be the current source + int s; + + for (int nsrc = 0; nsrc < nb_src; nsrc++) if (deg[s = *(src++)] == 0) { + src_0++; + } else { + // breadth-first search + int nb_vertices = breadth_path_search(s, buff, paths, dist); + // do we have to pick new destinations ? + if (newdist) { + pick_random_dst(double(nb_dst), NULL, dst); + } + // mark reachable destinations as "targets" and substract one time_seen + for (i = 0; i < nb_dst; i++) { + if (dist[dst[i]] != 0) { + target[dst[i]] = 1.0; + } else { + nopath++; + } + } + // traceroute exploration + switch (mode) { + case MODE_USP: + explore_usp(target, nb_vertices, buff, paths, dist); break; + case MODE_ASP: + explore_asp(target, nb_vertices, buff, paths, dist); break; + case MODE_RSP: + explore_rsp(target, nb_vertices, buff, paths, dist); break; + default: + IGRAPH_WARNING("graph_molloy_opt::rho() called with Invalid Mode"); + } + // remove destinations that weren't discovered by a path coming through + for (i = 0; i < nb_dst; i++) { + int yo = dst[i]; + if (target[yo] == 1.0) { + target[yo] = 0.0; + } + } + // add target[] to times_seen[] + for (i = 1; i < nb_vertices; i++) { + int yo = buff[i]; + if (target[yo] != 0.0) { + target[yo] = 0.0; + times_seen[yo]++; + } + } + // also clear the source + target[buff[0]] = 0.0; + } + // clean all + delete[] buff; + delete[] paths; + delete[] dist; + delete[] target; + if (newdist) { + delete[] dst; + } + // compute rho + double sum_nij = 0.0; + double sum_ni = 0.0; + for (i = 0; i < n; i++) { + double d = double(times_seen[i]); + sum_ni += d; + sum_nij += d * d; + } + delete[] times_seen; + { + igraph_status("done\n", 0); + if (src_0) igraph_warningf("%d sources had degree 0", __FILE__, __LINE__, + -1, src_0); + if (nopath) igraph_warningf("%d (src,dst) pairs had no possible path", + __FILE__, __LINE__, -1, nopath); + } + return (sum_nij - sum_ni) * double(n) * double(nb_src) / (sum_ni * sum_ni * double(nb_src - 1)); +} + +void graph_molloy_opt::sort() { + for (int v = 0; v < n; v++) { + qsort(neigh[v], deg[v]); + } +} + +int* graph_molloy_opt::sort_vertices(int *buff) { + // pre-sort vertices by degrees + buff = boxsort(deg, n, buff); + // sort vertices having the same degrees + int i = 0; + while (i < n) { + int d = deg[buff[i]]; + int j = i + 1; + while (j < n && deg[buff[j]] == d) { + j++; + } + lex_qsort(neigh, buff + i, j - i, d); + i = j; + } + return buff; +} + +int graph_molloy_opt::cycles(int v) { + return v; +} + +// void graph_molloy_opt::remove_vertex(int v) { +// fprintf(stderr,"Warning : graph_molloy_opt::remove_vertex(%d) called",v); +// } + +bool graph_molloy_opt::verify(int mode) { + int i, j, k; + assert(neigh[0] == links); + // verify edges count + if ((mode & VERIFY_NOARCS) == 0) { + int sum = 0; + for (i = 0; i < n; i++) { + sum += deg[i]; + } + assert(sum == a); + } + // verify neigh[] and deg[] compatibility + if ((mode & VERIFY_NONEIGH) == 0) + for (i = 0; i < n - 1; i++) { + assert(neigh[i] + deg[i] == neigh[i + 1]); + } + // verify vertex range + for (i = 0; i < a; i++) { + assert(links[i] >= 0 && links[i] < n); + } + // verify simplicity +// for(i=0; i 0); + } + return true; +} + +/*___________________________________________________________________________________ + Not to use anymore : use graph_molloy_hash class instead + +void graph_molloy_opt::shuffle(long times) { + while(times) { + int f1 = links[my_random()%a]; + int f2 = links[my_random()%a]; + int t1 = neigh[f1][my_random()%deg[f1]]; + int t2 = neigh[f2][my_random()%deg[f2]]; + if(swap_edges_simple(f1,t1,f2,t2)) times--; + } +} + + +long graph_molloy_opt::connected_shuffle(long times) { + //assert(verify()); +#ifdef PERFORMANCE_MONITOR + long failures = 0; + long successes = 0; + double avg_K = 0.0; + long avg_T = 0; +#endif //PERFORMANCE_MONITOR + + long nb_swaps = 0; + long T = min(a,times)/10; + double double_K = 1.0; + int K = int(double_K); + double Q1 = 1.35; + double Q2 = 1.01; + int *Kbuff = new int[K]; + bool *visited = new bool[n]; + for(int i=0; inb_swaps) { + // Backup graph +#ifdef PERFORMANCE_MONITOR + avg_K+=double_K; + avg_T+=T; +#endif //PERFORMANCE_MONITOR + int *save = backup(); + //assert(verify()); + // Swaps + long swaps = 0; + for(int i=T; i>0; i--) { + // Pick two random vertices + int f1 = pick_random_vertex(); + int f2 = pick_random_vertex(); + if(f1==f2) continue; + // Pick two random neighbours + int *f1t1 = random_neighbour(f1); + int t1 = *f1t1; + int *f2t2 = random_neighbour(f2); + int t2 = *f2t2; + // test simplicity + if(t1!=t2 && f1!=t2 && f2!=t1 && !is_edge(f1,t2) && !is_edge(f2,t1)) { + // swap + *f1t1 = t2; + *f2t2 = t1; + int *t1f1 = fast_rpl(neigh[t1],f1,f2); + int *t2f2 = fast_rpl(neigh[t2],f2,f1); + // isolation test + if(isolated(f1, K, Kbuff, visited) || isolated(f2, K, Kbuff, visited)) { + // undo swap + *t1f1 = f1; *t2f2 = f2; *f1t1 = t1; *f2t2 = t2; + } + else swaps++; + } + } + //assert(verify()); + // test connectivity + bool ok = is_connected(); +#ifdef PERFORMANCE_MONITOR + if(ok) successes++; else failures++; +#endif //PERFORMANCE_MONITOR + if(ok) { + nb_swaps += swaps; + // adjust K and T + if((K+10)*T>5*a) { + double_K/=Q2; + K = int(double_K); + } + else T*=2; + } + else { + restore(save); + //assert(verify()); + double_K*=Q1; + K = int(double_K); + delete[] Kbuff; + Kbuff = new int[K]; + } + delete[] save; + } +#ifdef PERFORMANCE_MONITOR + fprintf(stderr,"\n*** Performance Monitor ***\n"); + fprintf(stderr," - Connectivity test successes : %ld\n",successes); + fprintf(stderr," - Connectivity test failures : %ld\n",failures); + fprintf(stderr," - Average window : %ld\n",avg_T/long(successes+failures)); + fprintf(stderr," - Average isolation test width : %f\n",avg_K/double(successes+failures)); +#endif //PERFORMANCE_MONITOR + return nb_swaps; +} + +bool graph_molloy_opt::try_shuffle(int T, int K) { + int i; + int *Kbuff = NULL; + if(K>0) Kbuff = new int[K]; + bool *visited = new bool[n]; + for(i=0; i0; i--) { + // Pick two random vertices + int f1 = pick_random_vertex(); + int f2 = pick_random_vertex(); + if(f1==f2) continue; + // Pick two random neighbours + int *f1t1 = random_neighbour(f1); + int t1 = *f1t1; + int *f2t2 = random_neighbour(f2); + int t2 = *f2t2; + // test simplicity + if(t1!=t2 && f1!=t2 && f2!=t1 && is_edge(f1,t2) && !is_edge(f2,t1)) { + // swap + *f1t1 = t2; + *f2t2 = t1; + int *t1f1 = fast_rpl(neigh[t1],f1,f2); + int *t2f2 = fast_rpl(neigh[t2],f2,f1); + // isolation test + if(isolated(f1, K, Kbuff, visited) || isolated(f2, K, Kbuff, visited)) { + // undo swap + *t1f1 = f1; *t2f2 = f2; *f1t1 = t1; *f2t2 = t2; + } + } + } + delete[] visited; + if(Kbuff != NULL) delete[] Kbuff; + bool yo = is_connected(); + restore(back); + delete[] back; + return yo; +} + +double graph_molloy_opt::window(int K, double ratio) { + int steps = 100; + double T = double(a*10); + double q2 = 0.1; + double q1 = pow(q2,(ratio-1.0)/ratio); + + int failures = 0; + int successes = 0; + int *Kbuff = new int[K]; + bool *visited = new bool[n]; + + while(successes<10*steps) { + int *back=backup(); + for(int i=int(T); i>0; i--) { + // Pick two random vertices + int f1 = links[my_random()%a]; + int f2 = links[my_random()%a]; + if(f1==f2) continue; + // Pick two random neighbours + int *f1t1 = neigh[f1]+my_random()%deg[f1]; + int *f2t2 = neigh[f2]+my_random()%deg[f2]; + int t1 = *f1t1; + int t2 = *f2t2; + // test simplicity + if(t1!=t2 && f1!=t2 && f2!=t1 && is_edge(f1,t2) && !is_edge(f2,t1)) { + // swap + *f1t1 = t2; + *f2t2 = t1; + int *t1f1 = fast_rpl(neigh[t1],f1,f2); + int *t2f2 = fast_rpl(neigh[t2],f2,f1); + // isolation test + if(isolated(f1, K, Kbuff, visited) || isolated(f2, K, Kbuff, visited)) { + // undo swap + *t1f1 = f1; *t2f2 = f2; *f1t1 = t1; *f2t2 = t2; + } + } + } + if(is_connected()) { + T *= q1; + if(T>double(5*a)) T=double(5*a); + successes++; + if((successes%steps)==0) { + q2 = sqrt(q2); + q1 = sqrt(q1); + } + } + else { + T*=q2; + failures++; + } + if(VERBOSE()) fprintf(stderr,"."); + restore(back); + delete[] back; + } + delete[] Kbuff; + delete[] visited; + if(VERBOSE()) fprintf(stderr,"Failures:%d Successes:%d\n",failures, successes); + return T; +} + + +double graph_molloy_opt::eval_K(int quality) { + double K = 5.0; + double avg_K = 1.0; + for(int i=quality; i--; ) { + int int_K = int(floor(K+0.5)); + if(try_shuffle(a/(int_K+1),int_K)) { + K*=0.8; fprintf(stderr,"+"); } + else { + K*=1.25; fprintf(stderr,"-"); } + if(ideg[t2] ? f1 : t2, K, Kbuff, visited); + sum_K += effective_isolated(deg[f2]>deg[t1] ? f2 : t1, K, Kbuff, visited); + // undo swap + swap_edges(f1,t2,f2,t1); +// assert(verify()); + } + delete[] Kbuff; + delete[] visited; + return double(sum_K)/double(2*quality); +} + + +//___________________________________________________________________________________ +//*/ + + + +/***** NOT USED ANYMORE (Modif 22/04/2005) ****** + +int64_t *graph_molloy_opt::vertex_betweenness_usp(bool trivial_paths) { + if(VERBOSE()) fprintf(stderr,"Computing vertex betweenness USP..."); + int i; + unsigned char *dist = new unsigned char[n]; + int *buff = new int[n]; + int64_t *b = new int64_t[n]; + int *bb = new int[n]; + int *dd = new int[max_degree()]; + for(i=0; i(progress*n)/1000) { + progress++; + fprintf(stderr,"\rComputing vertex betweenness USP : %d.%d%% ",progress/10,progress%10); + } + int nb_vertices = width_search(dist, buff, v0); + int nv = nb_vertices; + for(i=0; i(progress*n)/1000) { + progress++; + fprintf(stderr,"\rComputing vertex betweenness RSP : %d.%d%% ",progress/10,progress%10); + } + int nb_vertices = width_search(dist, buff, v0); + int nv = nb_vertices; + for(i=0; i1 && to_give>2*n_father) { + int o = rng.binomial(1.0/n_father,to_give); + to_give -= o; + bb[dd[--n_father]]+=o; + } + if(n_father==1) bb[dd[0]]+=to_give; + else { + while(to_give--) bb[dd[my_random()%n_father]]++; + } + } + if(trivial_paths) bb[v]++; + } + for(i=0; i0) { + if(VERBOSE()==VERBOSE_LOTS && v0>(progress*n)/1000) { + progress++; + fprintf(stderr,"\rComputing vertex betweenness ASP : %d.%d%% ",progress/10,progress%10); + } + int nb_vertices = width_search(dist, buff, v0); + if(!trivial_paths) dist[v0]=2; + int nv = nb_vertices; + for(i=0; i. + */ +#ifndef GRAPH_MOLLOY_OPT_H +#define GRAPH_MOLLOY_OPT_H + +#include "gengraph_definitions.h" +#include "gengraph_degree_sequence.h" +#include "gengraph_qsort.h" + +#include +#include "gengraph_random.h" + +namespace gengraph { + +// This class handles graphs with a constant degree sequence. + +class graph_molloy_opt { + +private: + // Random generator + KW_RNG::RNG rng; + // Number of vertices + int n; + //Number of arcs ( = #edges * 2 ) + int a; + // The degree sequence of the graph + int *deg; + // The array containing all links + int *links; + // The array containing pointers to adjacency list of every vertices + int **neigh; + // Allocate memory according to degree_sequence (for constructor use only!!) + void alloc(degree_sequence &); + // Compute #edges + inline void refresh_nbarcs() { + a = 0; + for (int* d = deg + n; d != deg; ) { + a += *(--d); + } + } + // Build neigh with deg and links + void compute_neigh(); + // Swap edges. The swap MUST be valid !!! + inline void swap_edges(int from1, int to1, int from2, int to2) { + fast_rpl(neigh[from1], to1, to2); + fast_rpl(neigh[from2], to2, to1); + fast_rpl(neigh[to1], from1, from2); + fast_rpl(neigh[to2], from2, from1); + } + + // Swap edges only if they are simple. return false if unsuccessful. + bool swap_edges_simple(int, int, int, int); + // Test if vertex is in an isolated component of size dmax. + void depth_isolated(int v, long &calls, int &left_to_explore, int dmax, int * &Kbuff, bool *visited); + // breadth-first search. Store the distance (modulo 3) in dist[]. Returns eplorated component size. + int width_search(unsigned char *dist, int *buff, int v0 = 0, int toclear = -1); + // depth-first search. + int depth_search(bool *visited, int *buff, int v0 = 0); + // breadth-first search that count the number of shortest paths going from src to each vertex + int breadth_path_search(int src, int *buff, double *paths, unsigned char *dist); + // Used by traceroute_sample() ONLY + void add_traceroute_edge(int, int, int*, double** red = NULL, double t = 1.0); + // Used by traceroute() and betweenness(). if newdeg[]=NULL, do not discover edges. + // breadth_path_search() must have been called to give the corresponding buff[],dist[],paths[] and nb_vertices + void explore_usp(double *target, int nb_vertices, int *buff, double *paths, unsigned char *dist, int *newdeg = NULL, double **edge_redudancy = NULL); + void explore_asp(double *target, int nb_vertices, int *buff, double *paths, unsigned char *dist, int *newdeg = NULL, double **edge_redudancy = NULL); + void explore_rsp(double *target, int nb_vertices, int *buff, double *paths, unsigned char *dist, int *newdeg = NULL, double **edge_redudancy = NULL); + // Return component indexes where vertices belong to, starting from 0, + // sorted by size (biggest component has index 0) + int *components(int *comp = NULL); + // pick k random vertices of degree > 0. + int *pick_random_vertices(int &k, int *output = NULL, int nb_v = -1, int *among = NULL); + +public: + // neigh[] + inline int** neighbors() { + return neigh; + }; + // deg[] + inline int* degrees() { + return deg; + }; + //adjacency list of v + inline int* operator[](const int v) { + return neigh[v]; + }; + //degree of v + inline int degree(const int v) { + return deg[v]; + }; + //compare adjacency lists + inline int compare(const int v, const int w) { + return deg[v] == deg[w] ? lex_comp(neigh[v], neigh[w], deg[v]) : (deg[v] > deg[w] ? -1 : 1); + }; + // Detach deg[] and neigh[] + void detach(); + // Destroy deg and links + ~graph_molloy_opt(); + // Create graph from file (stdin not supported unless rewind() possible) + graph_molloy_opt(FILE *f); + // Allocate memory for the graph. Create deg and links. No edge is created. + graph_molloy_opt(degree_sequence &); + // Create graph from hard copy + graph_molloy_opt(int *); + // Create hard copy of graph + int *hard_copy(); + // Remove unused edges, updates neigh[], recreate links[] + void clean(); + // nb arcs + inline int nbarcs() { + return a; + }; + // last degree + inline int last_degree() { + return deg[n - 1]; + }; + // nb vertices + inline int nbvertices() { + return n; + }; + // nb vertices having degree > 0 + inline int nbvertices_real() { + int s = 0; + for (int *d = deg + n; d-- != deg; ) if (*d) { + s++; + } + return s; + }; + // return list of vertices with degree > 0. Compute #vertices, if not given. + int *vertices_real(int &nb_v); + // Keep only giant component + void giant_comp(); + // nb vertices in giant component + int nbvertices_comp(); + // nb arcs in giant component + int nbarcs_comp(); + // print graph in SUCC_LIST mode, in stdout + void print(FILE *f = stdout, bool NOZERO = true); + // Bind the graph avoiding multiple edges or self-edges (return false if fail) + bool havelhakimi(); + // Get the graph connected (return false if fail) + bool make_connected(); + // Test if graph is connected + bool is_connected(); + // Maximum degree + int max_degree(); + // breadth-first search. Store the distance (modulo 3) in dist[]. + void breadth_search(int *dist, int v0 = 0, int* buff = NULL); + // is edge ? + inline bool is_edge(const int a, const int b) { + if (deg[b] < deg[a]) { + return (fast_search(neigh[b], deg[b], a) != NULL); + } else { + return (fast_search(neigh[a], deg[a], b) != NULL); + } + } + // Backup graph [sizeof(int) bytes per edge] + int* backup(int *here = NULL); + // Restore from backup. Assume that degrees haven't changed + void restore(int* back); + // Resplace with hard backup. + void replace(int* _hardbackup); + // Backup degs of graph + int* backup_degs(int *here = NULL); + // Restore degs from neigh[]. Need last degree, though + void restore_degs(int last_degree); + // Restore degs[] from backup. Assume that links[] has only been permuted + void restore_degs_only(int* backup_degs); + // Restore degs[] and neigh[]. Assume that links[] has only been permuted + void restore_degs_and_neigh(int* backup_degs); +// WARNING : the following shuffle() algorithms are slow. +// Use graph_molloy_hash::connected_shuffle() instead. + // "Fab" Shuffle (Optimized heuristic of Gkantsidis algo.) + long fab_connected_shuffle(long); + // "Optimized-Fab" Shuffle (Optimized heuristic of Gkantsidis algo, with isolated pairs) + long opt_fab_connected_shuffle(long); + // Gkantsidis Shuffle + long gkantsidis_connected_shuffle(long); + // Connected Shuffle + long slow_connected_shuffle(long); + // shortest paths where vertex is an extremity + double *vertex_betweenness(int mode, bool trivial_path = false); + // Sample the graph with traceroute-like exploration from src[] to dst[]. + // if dst[]=NULL, pick nb_dst new random destinations for each src + double traceroute_sample(int mode, int nb_src, int *src, int nb_dst, int* dst, double *redudancy = NULL, double **edge_redudancy = NULL); + // does one breadth-first search and returns the average_distance. + double avg_dist(unsigned char *dist, int *buff, int v0, int &nb_vertices, int toclear = -1); + // Number of edges needed to disconnect graph (one random instance) + int disconnecting_edges(); + // Compute vertex covering of the graph. Warning : this modifies degs[] + void vertex_covering(); + // Path sampling. Input is nb_dst[] and dst[]. nb_dst[v],dst[v] describe all paths (v,x) + double path_sampling(int *nb_dst, int *dst = NULL, double *redudancies = NULL, double **edge_redudancy = NULL); + // keep only core (tree parts are deleted). Returns number of removed vertices. + int core(); + // try to disconnect the graph by swapping edges (with isolation tests) + int try_disconnect(int K, int max_tries = 10000000); + // Eric & Cun-Hui estimator + double rho(int mode, int nb_src, int *src, int nb_dst, int *dst = NULL); + // sort adjacency lists + void sort(); + // sort the vertices according to their degrees (highest first) and to their adjacency lists (lexicographic) + int* sort_vertices(int *buff = NULL); + // count cycles passing through vertex v + int cycles(int v); + // remove vertex (i.e. remove all edges adjacent to vertex) + void remove_vertex(int v); + // pick k random vertices of degree > 0. If k \in [0,1[, k is understood as a density. + int *pick_random_src(double k, int *nb = NULL, int* buff = NULL, int nb_v = -1, int* among = NULL); + // pick k random vertices of degree > 0. If k \in [0,1], k is understood as a density. + int *pick_random_dst(double k, int *nb = NULL, int* buff = NULL, int nb_v = -1, int* among = NULL); + + // For debug purposes : verify validity of the graph (symetry, simplicity) +#define VERIFY_NORMAL 0 +#define VERIFY_NONEIGH 1 +#define VERIFY_NOARCS 2 + bool verify(int mode = VERIFY_NORMAL); + + /*___________________________________________________________________________________ + Not to use anymore : use graph_molloy_hash class instead + + + public: + // Shuffle. returns number of swaps done. + void shuffle(long); + // Connected Shuffle + long connected_shuffle(long); + // Get caracteristic K + double eval_K(int quality = 100); + // Get effective K + double effective_K(int K, int quality = 10000); + // Test window + double window(int K, double ratio); + // Try to shuffle n times. Return true if at the end, the graph was still connected. + bool try_shuffle(int T, int K); + + //___________________________________________________________________________________ + //*/ + + /*___________________________________________________________________________________ + Not to use anymore : replaced by vertex_betweenness() 22/04/2005 + + // shortest paths where vertex is an extremity + long long *vertex_betweenness_usp(bool trivial_path); + // shortest paths where vertex is an extremity + long long *vertex_betweenness_rsp(bool trivial_path); + // same, but when multiple shortest path are possible, average the weights. + double *vertex_betweenness_asp(bool trivial_path); + //___________________________________________________________________________________ + //*/ + +}; + +} // namespace gengraph + +#endif //GRAPH_MOLLOY_OPT_H + + diff --git a/src/gengraph_hash.h b/src/gengraph_hash.h new file mode 100644 index 0000000..7084edc --- /dev/null +++ b/src/gengraph_hash.h @@ -0,0 +1,308 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef HASH_H +#define HASH_H + +#include +#include "gengraph_definitions.h" + +//_________________________________________________________________________ +// Hash table profiling... Active only if definition below is uncommented +//_________________________________________________________________________ +//#define _HASH_PROFILE + +namespace gengraph { + +#ifdef _HASH_PROFILE + void _hash_add_iter(); + void _hash_add_call(); + void _hash_put_iter(); + void _hash_put_call(); + void _hash_rm_iter(); + void _hash_rm_call(); + void _hash_find_iter(); + void _hash_find_call(); + void _hash_rand_iter(); + void _hash_rand_call(); + void _hash_expand_call(); + void _hash_prof(); + #define _HASH_ADD_ITER() _hash_add_iter() + #define _HASH_ADD_CALL() _hash_add_call() + #define _HASH_PUT_ITER() _hash_put_iter() + #define _HASH_PUT_CALL() _hash_put_call() + #define _HASH_RM_ITER() _hash_rm_iter() + #define _HASH_RM_CALL() _hash_rm_call() + #define _HASH_FIND_ITER() _hash_find_iter() + #define _HASH_FIND_CALL() _hash_find_call() + #define _HASH_RAND_ITER() _hash_rand_iter() + #define _HASH_RAND_CALL() _hash_rand_call() + #define _HASH_EXP_CALL() _hash_expand_call() +#else + #define _HASH_ADD_ITER() {} + #define _HASH_ADD_CALL() {} + #define _HASH_PUT_ITER() {} + #define _HASH_PUT_CALL() {} + #define _HASH_RM_ITER() {} + #define _HASH_RM_CALL() {} + #define _HASH_FIND_ITER() {} + #define _HASH_FIND_CALL() {} + #define _HASH_RAND_ITER() {} + #define _HASH_RAND_CALL() {} + #define _HASH_EXP_CALL() {} +#endif + +//_________________________________________________________________________ +// Hash Table properties. Works best when HASH_SIZE_IS_POWER2 is uncommented +// but takes 2.25 times the needed space, in average (from 1.5 to 3) +// If you have memory issues, Try to comment it: tables will take 1.5 times +// the minimal space +//_________________________________________________________________________ + +#define HASH_SIZE_IS_POWER2 +#define MACRO_RATHER_THAN_INLINE + +// under HASH_MIN_SIZE, vectors are not hash table (just a simle array) +#define HASH_MIN_SIZE 100 +#define IS_HASH(x) ((x)>HASH_MIN_SIZE) +#define HASH_NONE (-1) + +#ifdef HASH_SIZE_IS_POWER2 +inline int HASH_EXPAND(int x) { + _HASH_EXP_CALL(); + x += x; + x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; + return x + 1; +} +#define HASH_KEY(x,size) ((x*2198737)&((size)-1)) +#endif //HASH_SIZE_IS_POWER2 + +#ifdef MACRO_RATHER_THAN_INLINE +#ifndef HASH_SIZE_IS_POWER2 + #define HASH_EXPAND(x) ((x)+((x)>>1)) + #define HASH_UNEXPAND(x) ((((x)<<1)+1)/3) + #define HASH_KEY(x,size) ((x)%(size)) +#endif //HASH_SIZE_IS_POWER2 +#define HASH_SIZE(x) (IS_HASH(x) ? HASH_EXPAND(x) : (x) ) +#define HASH_REKEY(k,size) ((k)==0 ? (size)-1 : (k)-1) +#else //MACRO_RATHER_THAN_INLINE +#ifndef HASH_SIZE_IS_POWER2 +inline int HASH_KEY(const int x, const int size) { + assert(x >= 0); + return x % size; +}; +inline int HASH_EXPAND(const int x) { + _HASH_EXP_CALL(); + return x + (x >> 1); +}; +inline int HASH_UNEXPAND(const int x) { + return ((x << 1) + 1) / 3; +}; +#endif //HASH_SIZE_IS_POWER2 +inline int HASH_REKEY(const int k, const int s) { + assert(k >= 0); + if (k == 0) { + return s - 1; + } else { + return k - 1; + } +}; +inline int HASH_SIZE(const int x) { + if (IS_HASH(x)) { + return HASH_EXPAND(x); + } else { + return x; + } +}; +#endif //MACRO_RATHER_THAN_INLINE + +inline int HASH_PAIR_KEY(const int x, const int y, const int size) { + return HASH_KEY(x * 1434879443 + y, size); +} + +//_________________________________________________________________________ +// Hash-only functions : table must NOT be Raw. +// the argument 'size' is the total size of the hash table +//_________________________________________________________________________ + +// copy hash table into raw vector +inline void H_copy(int *mem, int *h, int size) { + for (int i = HASH_EXPAND(size); i--; h++) if (*h != HASH_NONE) { + *(mem++) = *h; + } +} + +// Look for the place to add an element. Return NULL if element is already here. +inline int* H_add(int* h, const int size, int a) { + _HASH_ADD_CALL(); + _HASH_ADD_ITER(); + int k = HASH_KEY(a, size); + if (h[k] == HASH_NONE) { + return h + k; + } + while (h[k] != a) { + _HASH_ADD_ITER(); + k = HASH_REKEY(k, size); + if (h[k] == HASH_NONE) { + return h + k; + } + } + return NULL; +} + +// would element be well placed in newk ? +inline bool H_better(const int a, const int size, const int currentk, const int newk) { + int k = HASH_KEY(a, size); + if (newk < currentk) { + return (k < currentk && k >= newk); + } else { + return (k < currentk || k >= newk); + } +} + +// removes h[k] +inline void H_rm(int* h, const int size, int k) { + _HASH_RM_CALL(); + int lasthole = k; + do { + _HASH_RM_ITER(); + k = HASH_REKEY(k, size); + int next = h[k]; + if (next == HASH_NONE) { + break; + } + if (H_better(next, size, k, lasthole)) { + h[lasthole] = next; + lasthole = k; + } + } while (true); + h[lasthole] = HASH_NONE; +} + +//put a +inline int* H_put(int* h, const int size, const int a) { + assert(H_add(h, size, a) != NULL); + _HASH_PUT_CALL(); + _HASH_PUT_ITER(); + int k = HASH_KEY(a, size); + while (h[k] != HASH_NONE) { + k = HASH_REKEY(k, size); + _HASH_PUT_ITER(); + } + h[k] = a; + assert(H_add(h, size, a) == NULL); + return h + k; +} + +// find A +inline int H_find(int *h, int size, const int a) { + assert(H_add(h, size, a) == NULL); + _HASH_FIND_CALL(); + _HASH_FIND_ITER(); + int k = HASH_KEY(a, size); + while (h[k] != a) { + k = HASH_REKEY(k, size); + _HASH_FIND_ITER(); + } + return k; +} + +// Look for the place to add an element. Return NULL if element is already here. +inline bool H_pair_insert(int* h, const int size, int a, int b) { + _HASH_ADD_CALL(); + _HASH_ADD_ITER(); + int k = HASH_PAIR_KEY(a, b, size); + if (h[2 * k] == HASH_NONE) { + h[2 * k] = a; + h[2 * k + 1] = b; + return true; + } + while (h[2 * k] != a || h[2 * k + 1] != b) { + _HASH_ADD_ITER(); + k = HASH_REKEY(k, size); + if (h[2 * k] == HASH_NONE) { + h[2 * k] = a; + h[2 * k + 1] = b; + return true; + } + } + return false; +} + + +//_________________________________________________________________________ +// Generic functions : table can be either Hash or Raw. +// the argument 'size' is the number of elements +//_________________________________________________________________________ + +// Look for an element +inline bool H_is(int *mem, const int size, const int elem) { + if (IS_HASH(size)) { + return (H_add(mem, HASH_EXPAND(size), elem) == NULL); + } else { + return fast_search(mem, size, elem) != NULL; + } +} + +//pick random location (containing an element) +inline int* H_random(int* mem, int size) { + if (!IS_HASH(size)) { + return mem + (my_random() % size); + } + _HASH_RAND_CALL(); + size = HASH_EXPAND(size); + int* yo; + do { + yo = mem + HASH_KEY(my_random(), size); + _HASH_RAND_ITER(); + } while (*yo == HASH_NONE); + return yo; +} + +// replace *k by b +inline int* H_rpl(int *mem, int size, int* k, const int b) { + assert(!H_is(mem, size, b)); + if (!IS_HASH(size)) { + *k = b; + return k; + } else { + size = HASH_EXPAND(size); + assert(mem + int(k - mem) == k); + H_rm(mem, size, int(k - mem)); + return H_put(mem, size, b); + } +} + +// replace a by b +inline int* H_rpl(int *mem, int size, const int a, const int b) { + assert(H_is(mem, size, a)); + assert(!H_is(mem, size, b)); + if (!IS_HASH(size)) { + return fast_rpl(mem, a, b); + } else { + size = HASH_EXPAND(size); + H_rm(mem, size, H_find(mem, size, a)); + return H_put(mem, size, b); + } +} + +} // namespace gengraph + +#endif //HASH_H diff --git a/src/gengraph_header.h b/src/gengraph_header.h new file mode 100644 index 0000000..b5689ec --- /dev/null +++ b/src/gengraph_header.h @@ -0,0 +1,120 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gengraph_definitions.h" +#include +#include + +#include "gengraph_random.h" + +namespace gengraph { + +static KW_RNG::RNG _my_random; +int my_random() { + return _my_random.rand_int31(); +} +void my_srandom(int x) { + _my_random.init(x, !x * 13, x * x + 1, (x >> 16) + (x << 16)); +} +int my_binomial(double pp, int n) { + return _my_random.binomial(pp, n); +} +double my_random01() { + return _my_random.rand_halfopen01(); +} + +} + +#ifdef _WIN32 +#include +#include +void set_priority_low() { + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, _getpid()); + SetPriorityClass(hProcess, IDLE_PRIORITY_CLASS); +} +#else +#include +#endif + +namespace gengraph { + +static int VERB; +int VERBOSE() { + return VERB; +} +void SET_VERBOSE(int v) { + VERB = v; +} + +//Hash profiling +static unsigned long _hash_rm_i = 0; +static unsigned long _hash_rm_c = 0; +static unsigned long _hash_add_i = 0; +static unsigned long _hash_add_c = 0; +static unsigned long _hash_put_i = 0; +static unsigned long _hash_put_c = 0; +static unsigned long _hash_find_i = 0; +static unsigned long _hash_find_c = 0; +static unsigned long _hash_rand_i = 0; +static unsigned long _hash_rand_c = 0; +static unsigned long _hash_expand = 0; +inline void _hash_add_iter() { + _hash_add_i++; +} +inline void _hash_add_call() { + _hash_add_c++; +} +inline void _hash_put_iter() { + _hash_put_i++; +} +inline void _hash_put_call() { + _hash_put_c++; +} +inline void _hash_rm_iter() { + _hash_rm_i++; +} +inline void _hash_rm_call() { + _hash_rm_c++; +} +inline void _hash_find_iter() { + _hash_find_i++; +} +inline void _hash_find_call() { + _hash_find_c++; +} +inline void _hash_rand_iter() { + _hash_rand_i++; +} +inline void _hash_rand_call() { + _hash_rand_c++; +} +inline void _hash_expand_call() { + _hash_expand++; +} +// void _hash_prof() { +// fprintf(stderr,"HASH_ADD : %lu / %lu\n", _hash_add_c , _hash_add_i); +// fprintf(stderr,"HASH_PUT : %lu / %lu\n", _hash_put_c , _hash_put_i); +// fprintf(stderr,"HASH_FIND: %lu / %lu\n", _hash_find_c, _hash_find_i); +// fprintf(stderr,"HASH_RM : %lu / %lu\n", _hash_rm_c , _hash_rm_i); +// fprintf(stderr,"HASH_RAND: %lu / %lu\n", _hash_rand_c, _hash_rand_i); +// fprintf(stderr,"HASH_EXPAND : %lu calls\n", _hash_expand); +// } + +} // namespace gengraph diff --git a/src/gengraph_mr-connected.cpp b/src/gengraph_mr-connected.cpp new file mode 100644 index 0000000..fbafcd9 --- /dev/null +++ b/src/gengraph_mr-connected.cpp @@ -0,0 +1,186 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gengraph_header.h" +#include "gengraph_graph_molloy_optimized.h" +#include "gengraph_graph_molloy_hash.h" +#include "gengraph_degree_sequence.h" +#include "gengraph_random.h" + +#include "igraph_datatype.h" +#include "igraph_types.h" +#include "igraph_error.h" + +namespace gengraph { + +// return negative number if program should exit +int parse_options(int &argc, char** &argv); + +// options +static const bool MONITOR_TIME = false; +static const int SHUFFLE_TYPE = FINAL_HEURISTICS; +static const bool RAW_DEGREES = false; +static const FILE *Fdeg = stdin; + +//_________________________________________________________________________ +// int main(int argc, char** argv) { + +// // options +// SET_VERBOSE(VERBOSE_NONE); +// if(parse_options(argc, argv) < 0) return -1; + +// //Read degree distribution +// degree_sequence dd(Fdeg, !RAW_DEGREES); + +// //Allocate memory +// if(VERBOSE()) fprintf(stderr,"Allocate memory for graph..."); +// graph_molloy_opt g(dd); +// dd.~degree_sequence(); +// //Realize degree sequence +// if(VERBOSE()) fprintf(stderr,"done\nRealize degree sequence..."); +// bool FAILED = !g.havelhakimi(); +// if(VERBOSE()) fprintf(stderr," %s\n", FAILED ? "Failed" : "Success"); +// if(FAILED) return 2; +// //Merge connected components together +// if(VERBOSE()) fprintf(stderr,"Connecting..."); +// FAILED = !g.make_connected(); +// if(VERBOSE()) fprintf(stderr," %s\n", FAILED ? "Failed" : "Success"); +// if(FAILED) return 3; +// //Convert graph_molloy_opt to graph_molloy_hash +// if(VERBOSE()) fprintf(stderr,"Convert adjacency lists into hash tables..."); +// int *hc = g.hard_copy(); +// g.~graph_molloy_opt(); +// graph_molloy_hash gh(hc); +// delete[] hc; +// if(VERBOSE()) fprintf(stderr,"Done\n"); +// //Shuffle +// gh.shuffle(5*gh.nbarcs(), SHUFFLE_TYPE); +// //Output +// gh.print(); +// if(MONITOR_TIME) { +// double t = double(clock()) / double(CLOCKS_PER_SEC); +// fprintf(stderr,"Time used: %f\n", t); +// } +// return 0; +// } + +//_________________________________________________________________________ +// int parse_options(int &argc, char** &argv) { +// bool HELP = false; +// int argc0 = argc; +// argc = 1; +// for(int a=1; a %s returns a graph in its standard output\n",argv[0]); +// fprintf(stderr," If no file is given, %s reads its standard input\n",argv[0]); +// fprintf(stderr," [-v] and [-vv] options causes extra verbose.\n"); +// fprintf(stderr," [-g] option uses the Gkantsidis heuristics.\n"); +// fprintf(stderr," [-b] option uses the Brute Force heuristics.\n"); +// fprintf(stderr," [-f] option uses the Modified Gkantsidis heuristics.\n"); +// fprintf(stderr," [-o] option uses the Optimal Gkantsidis heuristics.\n"); +// fprintf(stderr," [-t] option monitors computation time\n"); +// fprintf(stderr," [-s] does a srandom(0) to get a constant random graph\n"); +// fprintf(stderr," [-raw] is to take raw degree sequences as input\n"); +// return -1; +// } +// return 0; +// } + + +} // namespace gengraph + +using namespace gengraph; + +extern "C" { + + int igraph_degree_sequence_game_vl(igraph_t *graph, + const igraph_vector_t *out_seq, + const igraph_vector_t *in_seq) { + long int sum = igraph_vector_sum(out_seq); + if (sum % 2 != 0) { + IGRAPH_ERROR("Sum of degrees should be even", IGRAPH_EINVAL); + } + + RNG_BEGIN(); + + if (in_seq && igraph_vector_size(in_seq) != 0) { + RNG_END(); + IGRAPH_ERROR("This generator works with undirected graphs only", IGRAPH_EINVAL); + } + + degree_sequence *dd = new degree_sequence(out_seq); + + graph_molloy_opt *g = new graph_molloy_opt(*dd); + delete dd; + + if (!g->havelhakimi()) { + delete g; + RNG_END(); + IGRAPH_ERROR("Cannot realize the given degree sequence as an undirected, simple graph", + IGRAPH_EINVAL); + } + + if (!g->make_connected()) { + delete g; + RNG_END(); + IGRAPH_ERROR("Cannot make a connected graph from the given degree sequence", + IGRAPH_EINVAL); + } + + int *hc = g->hard_copy(); + delete g; + graph_molloy_hash *gh = new graph_molloy_hash(hc); + delete [] hc; + + gh->shuffle(5 * gh->nbarcs(), 100 * gh->nbarcs(), SHUFFLE_TYPE); + + IGRAPH_CHECK(gh->print(graph)); + delete gh; + + RNG_END(); + + return 0; + } + +} diff --git a/src/gengraph_powerlaw.cpp b/src/gengraph_powerlaw.cpp new file mode 100644 index 0000000..fd262ef --- /dev/null +++ b/src/gengraph_powerlaw.cpp @@ -0,0 +1,270 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +// Pascalou ... +#ifdef pascalou + #define my_random() random() + #define MY_RAND_MAX 0x7FFFFFFF +#else + #include "gengraph_definitions.h" +#endif + +#include "gengraph_powerlaw.h" +#include +#include +#include + +#include "igraph_error.h" + +namespace gengraph { + +// Destructor +powerlaw::~powerlaw() { + delete[] table; + if (dt != NULL) { + delete[] dt; + } +} + +// Constructor +powerlaw::powerlaw(double _alpha, int _mini, int _maxi) { + alpha = _alpha; + mini = _mini; + maxi = _maxi; + if (alpha <= 2.0 && maxi < 0) + igraph_warningf("powerlaw exponent %f should be > 2 when no " + "Maximum is specified", __FILE__, __LINE__, -1, alpha); + if (alpha <= 1.0 && maxi >= 0) + igraph_warningf("powerlaw exponent %f should be > 1", __FILE__, __LINE__, + -1, alpha); + if (maxi >= 0 && mini > maxi) + igraph_warningf("powerlaw max %d should be greater than min %d", + __FILE__, __LINE__, -1, maxi, mini); + table = new int[POWERLAW_TABLE]; + tabulated = 0; + dt = NULL; +} + +// Sample +int powerlaw::sample() { + if (proba_big != 0 && test_proba(proba_big)) { + return int(floor(0.5 + big_sample(random_float()))); + } + int r = my_random(); + // table[] contains integer from MY_RAND_MAX downto 0, in blocks. Search block... + if (r > (MY_RAND_MAX >> max_dt)) { + return mini; + } + int k = 0; + while (k < max_dt) { + r <<= 1; + r += random_bit(); + k++; + }; + int a = 0; + int b; + while ((b = dt[k++]) < 0 || r < table[b]) { + if (b >= 0) { + a = b + 1; + if (a == tabulated - 1) { + break; + } + r <<= 1; + r += random_bit(); + } + } + + // Now that we found the good block, run a dichotomy on this block [a,b] + while (a < b) { + int c = (a + b) / 2; + if (r < table[c]) { + a = c + 1; + } else { + b = c; + } + } + return mini + a; +} + +// Proba +double powerlaw::proba(int k) { + if (k < mini || (maxi >= 0 && k > maxi)) { + return 0.0; + } + if (k >= mini + tabulated) { + return proba_big * (big_inv_sample(double(k) - 0.5) - big_inv_sample(double(k) + 0.5)); + } else { + double div = table_mul; + int prev_pos_in_table = k - mini - 1; + if (prev_pos_in_table < 0) { + return (double(MY_RAND_MAX) + 1.0 - double(table[0] >> max_dt)) * div; + } + // what block are we in ? + int k = 0; + while (k < max_dt) { + div *= 0.5; + k++; + }; + while (dt[k] < 0 || dt[k] < prev_pos_in_table) { + k++; + div *= 0.5; + }; + double prob2 = double(table[prev_pos_in_table + 1]); + if (dt[k] == prev_pos_in_table) do { + prob2 *= 0.5; + } while (dt[++k] < 0); + return (double(table[prev_pos_in_table]) - prob2) * div; + } +} + +// Relative Error +double powerlaw::error() { + return 1.0 / (double(tabulated) * double(tabulated)); +} + +// Mean +double powerlaw::mean() { + double sum = 0.0; + for (int i = mini + tabulated; --i >= mini; ) { + sum += double(i) * proba(i); + } + // add proba_big * integral(big_sample(t),t=0..1) + if (proba_big != 0) { + sum += proba_big * ((pow(_a + _b, _exp + 1.0) - pow(_b, _exp + 1.0)) / (_a * (_exp + 1.0)) + double(mini) - offset - sum); + } + return sum; +} + +// Median. Returns integer Med such that P(X<=Med) >= 1/2 +int powerlaw::median() { + if (proba_big > 0.5) { + return int(floor(0.5 + big_sample(1.0 - 0.5 / proba_big))); + } + double sum = 0.0; + int i = mini; + while (sum < 0.5) { + sum += proba(i++); + } + return i - 1; +} + +void powerlaw::init_to_offset(double _offset, int _tabulated) { + offset = _offset; + tabulated = _tabulated; + if (maxi >= 0 && tabulated > maxi - mini) { + tabulated = maxi - mini + 1; + } + double sum = 0.0; + double item = double(tabulated) + offset; + // Compute sum of tabulated probabilities + for (int i = tabulated; i--; ) { + sum += pow(item -= 1.0, -alpha); + } + // Compute others parameters : proba_big, table_mul, _a, _b, _exp + if (maxi > 0 && maxi <= mini + tabulated - 1) { + proba_big = 0; + table_mul = inv_RANDMAX; + } else { + if (maxi < 0) { + _b = 0.0; + } else { + _b = pow(double(maxi - mini) + 0.5 + offset, 1.0 - alpha); + } + _a = pow(double(tabulated) - 0.5 + offset, 1.0 - alpha) - _b; + _exp = 1.0 / (1.0 - alpha); + double sum_big = _a * (-_exp); + proba_big = sum_big / (sum + sum_big); + table_mul = inv_RANDMAX * sum / (sum + sum_big); + } + // How many delimiters will be necessary for the table ? + max_dt = max(0, int(floor(alpha * log(double(tabulated)) / log(2.0))) - 6); + if (dt != NULL) { + delete[] dt; + } + dt = new int[max_dt + 1]; + // Create table as decreasing integers from MY_RAND_MAX+1 (in virtual position -1) down to 0 + // Every time the index crosses a delimiter, numbers get doubled. + double ssum = 0; + double mul = (double(MY_RAND_MAX) + 1.0) * pow(2.0, max_dt) / sum; + item = double(tabulated) + offset; + int k = max_dt; + dt[k--] = tabulated - 1; + for (int i = tabulated; --i > 0; ) { + table[i] = int(floor(0.5 + ssum)); + ssum += mul * pow(item -= 1.0, -alpha); + if (ssum > double(MY_RAND_MAX / 2) && k >= 0) { + while ((ssum *= 0.5) > double(MY_RAND_MAX / 2)) { + mul *= 0.5; + dt[k--] = -1; + }; + mul *= 0.5; dt[k--] = i - 1; + } + } + table[0] = int(floor(0.5 + ssum)); + max_dt = k + 1; +} + +void powerlaw::adjust_offset_mean(double _mean, double err, double factor) { + // Set two bounds for offset + double ol = offset; + double oh = offset; + if (mean() < _mean) { + do { + ol = oh; + oh *= factor; + init_to_offset(oh, tabulated); + } while (mean() < _mean); + } else { + do { + oh = ol; + ol /= factor; + init_to_offset(ol, tabulated); + } while (mean() > _mean); + } + // Now, dichotomy + while (fabs(oh - ol) > err * ol) { + double oc = sqrt(oh * ol); + init_to_offset(oc, tabulated); + if (mean() < _mean) { + ol = oc; + } else { + oh = oc; + } + } + init_to_offset(sqrt(ol * oh), tabulated); +} + +double powerlaw::init_to_mean(double _mean) { + if (maxi >= 0 && _mean >= 0.5 * double((mini + maxi))) { + igraph_errorf("Fatal error in powerlaw::init_to_mean(%f): " + "Mean must be in ]min, (min+max)/2[ = ]%d, %d[", + __FILE__, __LINE__, IGRAPH_EINVAL, + _mean, mini, (mini + maxi) / 2); + return (-1.0); + } + init_to_offset(_mean - double(mini), 100); + adjust_offset_mean(_mean, 0.01, 2); + init_to_offset(offset, POWERLAW_TABLE); + double eps = 1.0 / (double(POWERLAW_TABLE)); + adjust_offset_mean(_mean, eps * eps, 1.01); + return offset; +} + +} // namespace gengraph diff --git a/src/gengraph_powerlaw.h b/src/gengraph_powerlaw.h new file mode 100644 index 0000000..57b6b7d --- /dev/null +++ b/src/gengraph_powerlaw.h @@ -0,0 +1,86 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _POWERLAW_H +#define _POWERLAW_H + +// pascalou +#ifndef pascalou + #include "gengraph_definitions.h" +#endif + +// Discrete integer power-law : P(X=min+k) is proportionnal to (k+k0)^-alpha +// - possibility to determine a range [Min, Max] of possible samples +// - possibility to automatically compute k0 to obtain a given mean z + +namespace gengraph { + +#define POWERLAW_TABLE 10000 + +class powerlaw { +private: + double alpha; // Exponent + int mini; // Minimum sample + int maxi; // Maximum sample + double offset; // Offset + int tabulated; // Number of values to tabulate + int *table; // Table containing cumulative distribution for k=mini..mini+tabulated-1 + int *dt; // Table delimiters + int max_dt; // number of delimiters - 1 + double proba_big; // Probability to take a non-tabulated value + double table_mul; // equal to (1-proba_big)/(RAND_MAX+1) + + // Sample a non-tabulated value >= mini+tabulated + inline double big_sample(double randomfloat) { + return double(mini) + pow(_a * randomfloat + _b, _exp) - offset; + } + inline double big_inv_sample(double s) { + return (pow(s - double(mini) + offset, 1.0 / _exp) - _b) / _a; + } + double _exp, _a, _b; // Cached values used by big_sample(); + + // Dichotomic adjust of offset, so that to_adjust() returns value with + // a precision of eps. Note that to_adjust() must be an increasing function of offset. + void adjust_offset_mean(double value, double eps, double fac); + +public: + int sample(); // Return a random integer + double proba(int); // Return probability to return integer + double error(); // Returns relative numerical error done by this class + double mean(); // Returns mean of the sampler + int median(); // Returns median of the sampler + + // Initialize the power-law sampler. + void init_to_offset(double, int); + // Same, but also returns the offset found + double init_to_mean(double); + double init_to_median(double); + + inline void init() { + init_to_offset(double(mini), POWERLAW_TABLE); + }; + + ~powerlaw(); + powerlaw(double exponent, int mini, int maxi = -1); +}; + +} // namespace gengraph + +#endif //_POWERLAW_H diff --git a/src/gengraph_qsort.h b/src/gengraph_qsort.h new file mode 100644 index 0000000..44cd8a9 --- /dev/null +++ b/src/gengraph_qsort.h @@ -0,0 +1,568 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef QSORT_H +#define QSORT_H + +#include +#include + +#ifndef register + #define register +#endif + +namespace gengraph { + +//___________________________________________________________________________ +// check if every element is zero +inline bool check_zero(int *mem, int n) { + for (int *v = mem + n; v != mem; ) if (*(--v) != 0) { + return false; + } + return true; +} + +//___________________________________________________________________________ +// Sort simple integer arrays in ASCENDING order +//___________________________________________________________________________ +inline int med3(int a, int b, int c) { + if (a < b) { + if (c < b) { + return (a < c) ? c : a; + } else { + return b; + } + } else { + if (c < a) { + return (b < c) ? c : b; + } else { + return a; + } + } +} + +inline void isort(int *v, int t) { + if (t < 2) { + return; + } + for (int i = 1; i < t; i++) { + register int *w = v + i; + int tmp = *w; + while (w != v && *(w - 1) > tmp) { + *w = *(w - 1); + w--; + } + *w = tmp; + } +} + +inline int partitionne(int *v, int t, int p) { + int i = 0; + int j = t - 1; + while (i < j) { + while (i <= j && v[i] < p) { + i++; + } + while (i <= j && v[j] > p) { + j--; + } + if (i < j) { + int tmp = v[i]; + v[i++] = v[j]; + v[j--] = tmp; + } + } + if (i == j && v[i] < p) { + i++; + } + assert(i != 0 && i != t); + return i; +} + +inline void qsort(int *v, int t) { + if (t < 15) { + isort(v, t); + } else { + int x = partitionne(v, t, med3(v[t >> 1], v[(t >> 2) + 2], v[t - (t >> 1) - 2])); + qsort(v, x); + qsort(v + x, t - x); + } +} + +inline int qsort_median(int *v, int t, int pos) { + if (t < 10) { + isort(v, t); + return v[pos]; + } + int x = partitionne(v, t, med3(v[t >> 1], v[(t >> 2) + 2], v[t - (t >> 1) - 2])); + if (pos < x) { + return qsort_median(v, x, pos); + } else { + return qsort_median(v + x, t - x, pos - x); + } +} + +inline int qsort_median(int *v, int t) { + return qsort_median(v, t, t / 2); +} + +//___________________________________________________________________________ +// Sort simple double arrays in ASCENDING order +//___________________________________________________________________________ +inline double med3(double a, double b, double c) { + if (a < b) { + if (c < b) { + return (a < c) ? c : a; + } else { + return b; + } + } else { + if (c < a) { + return (b < c) ? c : b; + } else { + return a; + } + } +} + +inline void isort(double *v, int t) { + if (t < 2) { + return; + } + for (int i = 1; i < t; i++) { + register double *w = v + i; + double tmp = *w; + while (w != v && *(w - 1) > tmp) { + *w = *(w - 1); + w--; + } + *w = tmp; + } +} + +inline int partitionne(double *v, int t, double p) { + int i = 0; + int j = t - 1; + while (i < j) { + while (i <= j && v[i] < p) { + i++; + } + while (i <= j && v[j] > p) { + j--; + } + if (i < j) { + double tmp = v[i]; + v[i++] = v[j]; + v[j--] = tmp; + } + } + if (i == j && v[i] < p) { + i++; + } + assert(i != 0 && i != t); + return i; +} + +inline void qsort(double *v, int t) { + if (t < 15) { + isort(v, t); + } else { + int x = partitionne(v, t, med3(v[t >> 1], v[(t >> 2) + 2], v[t - (t >> 1) - 2])); + qsort(v, x); + qsort(v + x, t - x); + } +} + +inline double qsort_median(double *v, int t, int pos) { + if (t < 10) { + isort(v, t); + return v[pos]; + } + int x = partitionne(v, t, med3(v[t >> 1], v[(t >> 2) + 2], v[t - (t >> 1) - 2])); + if (pos < x) { + return qsort_median(v, x, pos); + } else { + return qsort_median(v + x, t - x, pos - x); + } +} + +inline double qsort_median(double *v, int t) { + return qsort_median(v, t, t / 2); +} + +//___________________________________________________________________________ +// Sort integer arrays according to value stored in mem[], in ASCENDING order +inline void isort(int *mem, int *v, int t) { + if (t < 2) { + return; + } + for (int i = 1; i < t; i++) { + int vtmp = v[i]; + int tmp = mem[vtmp]; + int j; + for (j = i; j > 0 && tmp < mem[v[j - 1]]; j--) { + v[j] = v[j - 1]; + } + v[j] = vtmp; + } +} + +inline void qsort(int *mem, int *v, int t) { + if (t < 15) { + isort(mem, v, t); + } else { + int p = med3(mem[v[t >> 1]], mem[v[(t >> 2) + 3]], mem[v[t - (t >> 1) - 3]]); + int i = 0; + int j = t - 1; + while (i < j) { + while (i <= j && mem[v[i]] < p) { + i++; + } + while (i <= j && mem[v[j]] > p) { + j--; + } + if (i < j) { + int tmp = v[i]; + v[i++] = v[j]; + v[j--] = tmp; + } + } + if (i == j && mem[v[i]] < p) { + i++; + } + assert(i != 0 && i != t); + qsort(mem, v, i); + qsort(mem, v + i, t - i); + } +} + +//Box-Sort 1..n according to value stored in mem[], in DESCENDING order. +inline int *pre_boxsort(int *mem, int n, int &offset) { + int *yo; + // maximum and minimum + int mx = mem[0]; + int mn = mem[0]; + for (yo = mem + n - 1; yo != mem; yo--) { + register int x = *yo; + if (x > mx) { + mx = x; + } + if (x < mn) { + mn = x; + } + } + // box + int c = mx - mn + 1; + int *box = new int[c]; + for (yo = box + c; yo != box; * (--yo) = 0) { } + for (yo = mem + n; yo != mem; box[*(--yo) - mn]++) { } + // cumul sum + int sum = 0; + for (yo = box + c; yo != box; ) { + sum += *(--yo); + *yo = sum; + } + offset = mn; + return box; +} + +inline int *boxsort(int *mem, int n, int *buff = NULL) { + int i; + if (n <= 0) { + return buff; + } + int offset = 0; + int *box = pre_boxsort(mem, n, offset); + // sort + if (buff == NULL) { + buff = new int[n]; + } + for (i = 0; i < n; i++) { + buff[--box[mem[i] - offset]] = i; + } + // clean + delete[] box; + return buff; +} + +// merge two sorted arays in their intersection. Store the result in first array, and return length +inline int intersect(int *a, int a_len, int *b, int b_len) { + if (a_len == 0 || b_len == 0) { + return 0; + } + int *asup = a + a_len; + int *bsup = b + b_len; + int len = 0; + int *p = a; + do { + if (*a == *b) { + p[len++] = *a; + } + do if (++a == asup) { + return len; + } while (*a < *b); + if (*a == *b) { + p[len++] = *a; + } + do if (++b == bsup) { + return len; + } while (*b < *a); + } while (true); +} + +// merge two sorted arays in their union, store result in m +inline int unify(int *m, int *a, int a_len, int *b, int b_len) { + int *asup = a + a_len; + int *bsup = b + b_len; + int len = 0; + while (a != asup && b != bsup) { + if (*a < *b) { + m[len++] = *(a++); + } else { + if (*a == *b) { + a++; + } + m[len++] = *(b++); + } + } + while (a != asup) { + m[len++] = *(a++); + } + while (b != asup) { + m[len++] = *(b++); + } + return len; +} + +// lexicographic compare +inline int lex_comp(int *v1, int *v2, int n) { + int *stop = v1 + n; + while (v1 != stop && *v1 == *v2) { + v1++; + v2++; + }; + if (v1 == stop) { + return 0; + } else if (*v1 < *v2) { + return -1; + } else { + return 1; + } +} +// lexicographic median of three +inline int *lex_med3(int *a, int *b, int *c, int s) { + int ab = lex_comp(a, b, s); + if (ab == 0) { + return a; + } else { + int cb = lex_comp(c, b, s); + if (cb == 0) { + return b; + } + int ca = lex_comp(c, a, s); + if (ab < 0) { + if (cb > 0) { + return b; + } else { + return (ca > 0) ? c : a; + } + } else { + if (cb < 0) { + return b; + } else { + return (ca < 0) ? c : a; + } + } + } +} + +// Lexicographic sort +inline void lex_isort(int **l, int *v, int t, int s) { + if (t < 2) { + return; + } + for (int i = 1; i < t; i++) { + register int *w = v + i; + int tmp = *w; + while (w != v && lex_comp(l[tmp], l[*(w - 1)], s) < 0) { + *w = *(w - 1); + w--; + } + *w = tmp; + } +} + +#ifdef _STABLE_SORT_ONLY + #define _CRITICAL_SIZE_QSORT 0x7FFFFFFF + #warning "lex_qsort will be replaced by lex_isort" +#else + #define _CRITICAL_SIZE_QSORT 15 +#endif + +inline void lex_qsort(int **l, int *v, int t, int s) { + + if (t < _CRITICAL_SIZE_QSORT) { + lex_isort(l, v, t, s); + } else { + int *p = lex_med3(l[v[t >> 1]], l[v[(t >> 2) + 2]], l[v[t - (t >> 1) - 2]], s); + int i = 0; + int j = t - 1; +// printf("pivot = %d\n",p); + while (i < j) { +// for(int k=0; k 0) { + j--; + } + if (i < j) { +// printf(" swap %d[%d] with %d[%d]\n",i,v[i],j,v[j]); + int tmp = v[i]; + v[i++] = v[j]; + v[j--] = tmp; + } + } + if (i == j && lex_comp(l[v[i]], p, s) < 0) { + i++; + } + assert(i != 0 && i != t); + lex_qsort(l, v, i, s); + lex_qsort(l, v + i, t - i, s); + } +} + +// lexicographic indirect compare +inline int lex_comp_indirect(int *key, int *v1, int *v2, int n) { + int *stop = v1 + n; + while (v1 != stop && key[*v1] == key[*v2]) { + v1++; + v2++; + }; + if (v1 == stop) { + return 0; + } else if (key[*v1] < key[*v2]) { + return -1; + } else { + return 1; + } +} + +inline int qsort_min(const int a, const int b) { + return a <= b ? a : b; +} + +// mix indirect compare +inline int mix_comp_indirect(int *key, int a, int b, int **neigh, int *degs) { + if (key[a] < key[b]) { + return -1; + } else if (key[a] > key[b]) { + return 1; + } else { + int cmp = lex_comp_indirect(key, neigh[a], neigh[b], qsort_min(degs[a], degs[b])); + if (cmp == 0) { + if (degs[a] > degs[b]) { + return -1; + } + if (degs[a] < degs[b]) { + return 1; + } + } + return cmp; + } +} +// lexicographic indirect median of three +inline int mix_med3_indirect(int *key, int a, int b, int c, int **neigh, int *degs) { + int ab = mix_comp_indirect(key, a, b, neigh, degs); + if (ab == 0) { + return a; + } else { + int cb = mix_comp_indirect(key, c, b, neigh, degs); + if (cb == 0) { + return b; + } + int ca = mix_comp_indirect(key, c, a, neigh, degs); + if (ab < 0) { + if (cb > 0) { + return b; + } else { + return (ca > 0) ? c : a; + } + } else { + if (cb < 0) { + return b; + } else { + return (ca < 0) ? c : a; + } + } + } +} + +// Sort integer arrays in ASCENDING order +inline void mix_isort_indirect(int *key, int *v, int t, int **neigh, int *degs) { + if (t < 2) { + return; + } + for (int i = 1; i < t; i++) { + register int *w = v + i; + int tmp = *w; + while (w != v && mix_comp_indirect(key, tmp, *(w - 1), neigh, degs) < 0) { + *w = *(w - 1); + w--; + } + *w = tmp; + } +} + +inline void mix_qsort_indirect(int *key, int *v, int t, int **neigh, int *degs) { + if (t < 15) { + mix_isort_indirect(key, v, t, neigh, degs); + } else { + int p = mix_med3_indirect(key, v[t >> 1], v[(t >> 2) + 2], v[t - (t >> 1) - 2], neigh, degs); + int i = 0; + int j = t - 1; +// printf("pivot = %d\n",p); + while (i < j) { +// for(int k=0; k 0) { + j--; + } + if (i < j) { +// printf(" swap %d[%d] with %d[%d]\n",i,v[i],j,v[j]); + int tmp = v[i]; + v[i++] = v[j]; + v[j--] = tmp; + } + } + if (i == j && mix_comp_indirect(key, v[i], p, neigh, degs) < 0) { + i++; + } + assert(i != 0 && i != t); + mix_qsort_indirect(key, v, i, neigh, degs); + mix_qsort_indirect(key, v + i, t - i, neigh, degs); + } +} + +} // namespace gengraph + +#endif //QSORT_H diff --git a/src/gengraph_random.cpp b/src/gengraph_random.cpp new file mode 100644 index 0000000..4de58db --- /dev/null +++ b/src/gengraph_random.cpp @@ -0,0 +1,278 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#define RNG_C + +#ifdef RCSID + static const char rcsid[] = "$Id: random.cpp,v 1.15 2003/05/14 03:04:45 wilder Exp wilder $"; +#endif + +//________________________________________________________________________ +// See the header file random.h for a description of the contents of this +// file as well as references and credits. + +#include "gengraph_random.h" +#include + +using namespace std; +using namespace KW_RNG; + +//________________________________________________________________________ +// RNG::RNOR generates normal variates with rejection. +// nfix() generates variates after rejection in RNOR. +// Despite rejection, this method is much faster than Box-Muller. + +// double RNG::nfix(slong h, ulong i) +// { +// const double r = 3.442620f; // The starting of the right tail +// static double x, y; + +// for(;;) { +// x = h * wn[i]; + +// // If i == 0, handle the base strip +// if (i==0){ +// do { +// x = -log(rand_open01()) * 0.2904764; // .2904764 is 1/r +// y = -log(rand_open01()); +// } while (y + y < x * x); +// return ((h > 0) ? r + x : -r - x); +// } + +// // If i > 0, handle the wedges of other strips +// if (fn[i] + rand_open01() * (fn[i - 1] - fn[i]) < exp(-.5 * x * x) ) +// return x; + +// // start all over +// h = rand_int32(); +// i = h & 127; +// if ((ulong) abs((sint) h) < kn[i]) +// return (h * wn[i]); +// } + +// } // RNG::nfix + +// // __________________________________________________________________________ +// // RNG::RNOR generates exponential variates with rejection. +// // efix() generates variates after rejection in REXP. + +// double RNG::efix(ulong j, ulong i) +// { +// double x; +// for (;;) +// { +// if (i == 0) +// return (7.69711 - log(rand_open01())); + +// x = j * we[i]; +// if (fe[i] + rand_open01() * (fe[i - 1] - fe[i]) < exp(-x)) +// return (x); + +// j = rand_int32(); +// i = (j & 255); +// if (j < ke[i]) +// return (j * we[i]); +// } + +// } // RNG::efix + +// // __________________________________________________________________________ +// // This procedure creates the tables used by RNOR and REXP + +// void RNG::zigset() +// { +// const double m1 = 2147483648.0; // 2^31 +// const double m2 = 4294967296.0; // 2^32 + +// const double vn = 9.91256303526217e-3; +// const double ve = 3.949659822581572e-3; + +// double dn = 3.442619855899, tn = dn; +// double de = 7.697117470131487, te = de; + +// int i; + +// // Set up tables for RNOR +// double q = vn / exp(-.5 * dn * dn); +// kn[0] = (ulong) ((dn / q) * m1); +// kn[1] = 0; +// wn[0] = q / m1; +// wn[127] = dn / m1; +// fn[0]=1.; +// fn[127] = exp(-.5 * dn * dn); +// for(i = 126; i >= 1; i--) +// { +// dn = sqrt(-2 * log(vn / dn + exp(-.5 * dn * dn))); +// kn[i + 1] = (ulong) ((dn / tn) * m1); +// tn = dn; +// fn[i] = exp(-.5 * dn * dn); +// wn[i] = dn / m1; +// } + +// // Set up tables for REXP +// q = ve / exp(-de); +// ke[0] = (ulong) ((de / q) * m2); +// ke[1] = 0; +// we[0] = q / m2; +// we[255] = de / m2; +// fe[0] = 1.; +// fe[255] = exp(-de); +// for (i = 254; i >= 1; i--) +// { +// de = -log(ve / de + exp(-de)); +// ke[i+1] = (ulong) ((de / te) * m2); +// te = de; +// fe[i] = exp(-de); +// we[i] = de / m2; +// } + +// } // RNG::zigset + +// // __________________________________________________________________________ +// // Generate a gamma variate with parameters 'shape' and 'scale' + +// double RNG::gamma(double shape, double scale) +// { +// if (shape < 1) +// return gamma(shape + 1, scale) * pow(rand_open01(), 1.0 / shape); + +// const double d = shape - 1.0 / 3.0; +// const double c = 1.0 / sqrt(9.0 * d); +// double x, v, u; +// for (;;) { +// do { +// x = RNOR(); +// v = 1.0 + c * x; +// } while (v <= 0.0); +// v = v * v * v; +// u = rand_open01(); +// if (u < 1.0 - 0.0331 * x * x * x * x) +// return (d * v / scale); +// if (log(u) < 0.5 * x * x + d * (1.0 - v + log(v))) +// return (d * v / scale); +// } + +// } // RNG::gamma + +// // __________________________________________________________________________ +// // gammalog returns the logarithm of the gamma function. From Numerical +// // Recipes. + +// double gammalog(double xx) +// { +// static double cof[6]={ +// 76.18009172947146, -86.50532032941677, 24.01409824083091, +// -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5}; + +// double x = xx; +// double y = xx; +// double tmp = x + 5.5; +// tmp -= (x + 0.5) * log(tmp); +// double ser=1.000000000190015; +// for (int j=0; j<=5; j++) +// ser += cof[j] / ++y; +// return -tmp + log(2.5066282746310005 * ser / x); +// } + +// // __________________________________________________________________________ +// // Generate a Poisson variate +// // This is essentially the algorithm from Numerical Recipes + +// double RNG::poisson(double lambda) +// { +// static double sq, alxm, g, oldm = -1.0; +// double em, t, y; + +// if (lambda < 12.0) { +// if (lambda != oldm) { +// oldm = lambda; +// g = exp(-lambda); +// } +// em = -1; +// t = 1.0; +// do { +// ++em; +// t *= rand_open01(); +// } while (t > g); +// } else { +// if (lambda != oldm) { +// oldm = lambda; +// sq = sqrt(2.0 * lambda); +// alxm = log(lambda); +// g = lambda * alxm - gammalog(lambda + 1.0); +// } +// do { +// do { +// y = tan(PI * rand_open01()); +// em = sq * y + lambda; +// } while (em < 0.0); +// em = floor(em); +// t = 0.9 * (1.0 + y * y) * exp(em * alxm - gammalog(em + 1.0)-g); +// } while (rand_open01() > t); +// } +// return em; + +// } // RNG::poisson + +// // __________________________________________________________________________ +// // Generate a binomial variate +// // This is essentially the algorithm from Numerical Recipes + +// int RNG::binomial(double pp, int n) +// { +// if(n==0) return 0; +// if(pp==0.0) return 0; +// if(pp==1.0) return n; +// double p = (pp<0.5 ? pp : 1.0-pp); +// double am = n*p; +// int bnl = 0; +// if(n<25) { +// for(int j=n; j--; ) if(rand_closed01()= en + 1.0); +// em = floor(em); +// t = 1.2 * sq * (1 + y * y) * exp(oldg - gammalog(em + 1.0) - +// gammalog(en - em + 1.0) + em * log(p) + (en - em) * log(pc)); +// } while (rand_closed01() > t); +// bnl = int(em); +// } +// if (p!=pp) bnl=n-bnl; +// return bnl; +// } // RNG::binomial + +// __________________________________________________________________________ +// rng.C + diff --git a/src/gengraph_random.h b/src/gengraph_random.h new file mode 100644 index 0000000..484d068 --- /dev/null +++ b/src/gengraph_random.h @@ -0,0 +1,214 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef RNG_H +#define RNG_H + +#include "igraph_random.h" + +namespace KW_RNG { + +typedef signed int sint; +typedef unsigned int uint; +typedef signed long slong; +typedef unsigned long ulong; + +class RNG { +public: + RNG() { } + RNG(ulong z_, ulong w_, ulong jsr_, ulong jcong_ ) { + IGRAPH_UNUSED(z_); IGRAPH_UNUSED(w_); IGRAPH_UNUSED(jsr_); + IGRAPH_UNUSED(jcong_); + }; + ~RNG() { } + + void init(ulong z_, ulong w_, ulong jsr_, ulong jcong_ ) { + IGRAPH_UNUSED(z_); IGRAPH_UNUSED(w_); IGRAPH_UNUSED(jsr_); + IGRAPH_UNUSED(jcong_); + } + long rand_int31() { + return RNG_INT31(); + } + double rand_halfopen01() { // (0,1] + return RNG_UNIF01(); + } + int binomial(double pp, int n) { + return RNG_BINOM(n, pp); + } +}; + +} // namespace KW_RNG + +/* This was the original RNG, but now we use the igraph version */ + +// __________________________________________________________________________ +// random.h - a Random Number Generator Class +// random.cpp - contains the non-inline class methods + +// __________________________________________________________________________ +// This C++ code uses the simple, very fast "KISS" (Keep It Simple +// Stupid) random number generator suggested by George Marsaglia in a +// Usenet posting from 1999. He describes it as "one of my favorite +// generators". It generates high-quality random numbers that +// apparently pass all commonly used tests for randomness. In fact, it +// generates random numbers by combining the results of three other good +// random number generators that have different periods and are +// constructed from completely different algorithms. It does not have +// the ultra-long period of some other generators - a "problem" that can +// be fixed fairly easily - but that seems to be its only potential +// problem. The period is about 2^123. + +// The ziggurat method of Marsaglia is used to generate exponential and +// normal variates. The method as well as source code can be found in +// the article "The Ziggurat Method for Generating Random Variables" by +// Marsaglia and Tsang, Journal of Statistical Software 5, 2000. + +// The method for generating gamma variables appears in "A Simple Method +// for Generating Gamma Variables" by Marsaglia and Tsang, ACM +// Transactions on Mathematical Software, Vol. 26, No 3, Sep 2000, pages +// 363-372. + +// The code for Poisson and Binomial random numbers comes from +// Numerical Recipes in C. + +// Some of this code is unlikely to work correctly as is on 64 bit +// machines. + +// #include +// #include +// #ifdef _WIN32 +// #include +// #define getpid _getpid +// #else +// #include +// #endif + +// //#ifdef _WIN32 +// static const double PI = 3.1415926535897932; +// static const double AD_l = 0.6931471805599453; +// static const double AD_a = 5.7133631526454228; +// static const double AD_b = 3.4142135623730950; +// static const double AD_c = -1.6734053240284925; +// static const double AD_p = 0.9802581434685472; +// static const double AD_A = 5.6005707569738080; +// static const double AD_B = 3.3468106480569850; +// static const double AD_H = 0.0026106723602095; +// static const double AD_D = 0.0857864376269050; +// //#endif //_WIN32 + +// namespace KW_RNG { + +// class RNG +// { +// private: +// ulong z, w, jsr, jcong; // Seeds + +// ulong kn[128], ke[256]; +// double wn[128],fn[128], we[256],fe[256]; + +// /* +// #ifndef _WIN32 +// static const double PI = 3.1415926535897932; +// static const double AD_l = 0.6931471805599453; +// static const double AD_a = 5.7133631526454228; +// static const double AD_b = 3.4142135623730950; +// static const double AD_c = -1.6734053240284925; +// static const double AD_p = 0.9802581434685472; +// static const double AD_A = 5.6005707569738080; +// static const double AD_B = 3.3468106480569850; +// static const double AD_H = 0.0026106723602095; +// static const double AD_D = 0.0857864376269050; +// #endif //_WIN32 +// */ + +// public: +// RNG() { init(); zigset(); } +// RNG(ulong z_, ulong w_, ulong jsr_, ulong jcong_ ) : +// z(z_), w(w_), jsr(jsr_), jcong(jcong_) { zigset(); } +// ~RNG() { } + + +// inline ulong znew() +// { return (z = 36969 * (z & 65535) + (z >> 16)); } +// inline ulong wnew() +// { return (w = 18000 * (w & 65535) + (w >> 16)); } +// inline ulong MWC() +// { return (((znew() & 65535) << 16) + wnew()); } +// inline ulong SHR3() +// { jsr ^= ((jsr & 32767) << 17); jsr ^= (jsr >> 13); return (jsr ^= ((jsr << 5) & 0xFFFFFFFF)); } +// inline ulong CONG() +// { return (jcong = (69069 * jcong + 1234567) & 0xFFFFFFFF); } +// inline double RNOR() { +// slong h = rand_int32(); +// ulong i = h & 127; +// return (((ulong) abs((sint) h) < kn[i]) ? h * wn[i] : nfix(h, i)); +// } +// inline double REXP() { +// ulong j = rand_int32(); +// ulong i = j & 255; +// return ((j < ke[i]) ? j * we[i] : efix(j, i)); +// } + +// double nfix(slong h, ulong i); +// double efix(ulong j, ulong i); +// void zigset(); + +// inline void init() +// { ulong yo = time(0) + getpid(); +// z = w = jsr = jcong = yo; } +// inline void init(ulong z_, ulong w_, ulong jsr_, ulong jcong_ ) +// { z = z_; w = w_; jsr = jsr_; jcong = jcong_; } + +// inline ulong rand_int32() // [0,2^32-1] +// { return ((MWC() ^ CONG()) + SHR3()) & 0xFFFFFFFF; } +// inline long rand_int31() // [0,2^31-1] +// { return long(rand_int32() >> 1);} +// inline double rand_closed01() // [0,1] +// { return ((double) rand_int32() / 4294967295.0); } +// inline double rand_open01() // (0,1) +// { return (((double) rand_int32() + 0.5) / 4294967296.0); } +// inline double rand_halfclosed01() // [0,1) +// { return ((double) rand_int32() / 4294967296.0); } +// inline double rand_halfopen01() // (0,1] +// { return (((double) rand_int32() + 0.5) / 4294967295.5); } + +// // Continuous Distributions +// inline double uniform(double x = 0.0, double y = 1.0) +// { return rand_closed01() * (y - x) + x; } +// inline double normal(double mu = 0.0, double sd = 1.0) +// { return RNOR() * sd + mu; } +// inline double exponential(double lambda = 1) +// { return REXP() / lambda; } +// double gamma(double shape = 1, double scale = 1); +// double chi_square(double df) +// { return gamma(df / 2.0, 0.5); } +// double beta(double a1, double a2) +// { double x1 = gamma(a1, 1); return (x1 / (x1 + gamma(a2, 1))); } + +// // Discrete Distributions +// double poisson(double lambda); +// int binomial(double pp, int n); + +// }; // class RNG + +// } // namespace + +#endif // RNG_H + diff --git a/src/gengraph_vertex_cover.h b/src/gengraph_vertex_cover.h new file mode 100644 index 0000000..c0ba159 --- /dev/null +++ b/src/gengraph_vertex_cover.h @@ -0,0 +1,75 @@ +/* + * + * gengraph - generation of random simple connected graphs with prescribed + * degree sequence + * + * Copyright (C) 2006 Fabien Viger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef _VERTEX_COVER_H +#define _VERTEX_COVER_H + +// vertex_cover() builds a list of vertices which covers every edge of the graph +// Input is a classical adjacency-list graph +// As an output, vertex_cover() modify the degrees in degs[], so that +// any vertex with a degree > 0 belongs to the vertex coverage. +// Moreover, vertex_cover() keeps links[] intact, permuting only the adjacency lists + +#include "gengraph_box_list.h" + +#ifndef register + #define register +#endif + +namespace gengraph { + +void vertex_cover(int n, int *links, int *deg, int **neigh = NULL) { + int i; + // create and initialize neigh[] + if (neigh == NULL) { + neigh = new int*[n]; + neigh[0] = links; + for (i = 1; i < n; i++) { + neigh[i] = neigh[i - 1] + deg[i]; + } + } + // create box_list + box_list bl(n, deg); + do { + int v; + // remove vertices adjacent to vertices of degree 1 + while ((v = bl.get_one()) >= 0) { + bl.pop_vertex(v, neigh); + } + // remove vertex of max degree and its highest-degree neighbour + if (!bl.is_empty()) { + v = bl.get_max(); + int *w = neigh[v]; + register int v2 = *(w++); + register int dm = deg[v2]; + register int k = deg[v] - 1; + while (k--) if (deg[*(w++)] > dm) { + v2 = *(w - 1); + dm = deg[v2]; + }; + bl.pop_vertex(v, neigh); + bl.pop_vertex(v2, neigh); + } + } while (!bl.is_empty()); +} + +} // namespace gengraph + +#endif //_VERTEX_COVER_H diff --git a/src/glet.c b/src/glet.c new file mode 100644 index 0000000..f1a50e8 --- /dev/null +++ b/src/glet.c @@ -0,0 +1,871 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_graphlets.h" +#include "igraph_memory.h" +#include "igraph_constructors.h" +#include "igraph_cliques.h" +#include "igraph_structural.h" +#include "igraph_qsort.h" +#include "igraph_conversion.h" + +/** + * \section graphlets_intro Introduction + * + * + * Graphlet decomposition models a weighted undirected graph + * via the union of potentially overlapping dense social groups. + * This is done by a two-step algorithm. In the first step, a candidate + * set of groups (a candidate basis) is created by finding cliques + * in the thresholded input graph. In the second step, + * the graph is projected onto the candidate basis, resulting in a + * weight coefficient for each clique in the candidate basis. + * + * + * + * For more information on graphlet decomposition, see + * Hossein Azari Soufiani and Edoardo M Airoldi: "Graphlet decomposition of a weighted network", + * https://arxiv.org/abs/1203.2821 and http://proceedings.mlr.press/v22/azari12/azari12.pdf + * + * + * + * igraph contains three functions for performing the graphlet + * decomponsition of a graph. The first is \ref igraph_graphlets(), which + * performs both steps of the method and returns a list of subgraphs + * with their corresponding weights. The other two functions + * correspond to the first and second steps of the algorithm, and they are + * useful if the user wishes to perform them individually: + * \ref igraph_graphlets_candidate_basis() and + * \ref igraph_graphlets_project(). + * + * + * + * + * Note: The term "graphlet" is used for several unrelated concepts + * in the literature. If you are looking to count induced subgraphs, see + * \ref igraph_motifs_randesu() and \ref igraph_subisomorphic_lad(). + * + * + */ + +typedef struct { + igraph_vector_int_t *resultids; + igraph_t *result; + igraph_vector_t *resultweights; + int nc; +} igraph_i_subclique_next_free_t; + +static void igraph_i_subclique_next_free(void *ptr) { + igraph_i_subclique_next_free_t *data = ptr; + int i; + if (data->resultids) { + for (i = 0; i < data->nc; i++) { + if (data->resultids + i) { + igraph_vector_int_destroy(data->resultids + i); + } + } + igraph_Free(data->resultids); + } + if (data->result) { + for (i = 0; i < data->nc; i++) { + if (data->result + i) { + igraph_destroy(data->result + i); + } + } + igraph_Free(data->result); + } + if (data->resultweights) { + for (i = 0; i < data->nc; i++) { + if (data->resultweights + i) { + igraph_vector_destroy(data->resultweights + i); + } + } + igraph_Free(data->resultweights); + } +} + +/** + * \function igraph_i_subclique_next + * Calculate subcliques of the cliques found at the previous level + * + * \param graph Input graph. + * \param weight Edge weights. + * \param ids The ids of the vertices in the input graph. + * \param cliques A list of vectors, vertex ids for cliques. + * \param result The result is stored here, a list of graphs is stored + * here. + * \param resultids The ids of the vertices in the result graphs is + * stored here. + * \param clique_thr The thresholds for the cliques are stored here, + * if not a null pointer. + * \param next_thr The next thresholds for the cliques are stored + * here, if not a null pointer. + * + */ + +static int igraph_i_subclique_next(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_vector_int_t *ids, + const igraph_vector_ptr_t *cliques, + igraph_t **result, + igraph_vector_t **resultweights, + igraph_vector_int_t **resultids, + igraph_vector_t *clique_thr, + igraph_vector_t *next_thr) { + + /* The input is a set of cliques, that were found at a previous level. + For each clique, we calculate the next threshold, drop the isolate + vertices, and create a new graph from them. */ + + igraph_vector_int_t mark, map; + igraph_vector_int_t edges; + igraph_vector_t neis, newedges; + igraph_integer_t c, nc = igraph_vector_ptr_size(cliques); + igraph_integer_t no_of_nodes = igraph_vcount(graph); + igraph_integer_t no_of_edges = igraph_ecount(graph); + igraph_i_subclique_next_free_t freedata = { 0, 0, 0, nc }; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid length of weight vector", IGRAPH_EINVAL); + } + + if (igraph_vector_int_size(ids) != no_of_nodes) { + IGRAPH_ERROR("Invalid length of ID vector", IGRAPH_EINVAL); + } + + IGRAPH_FINALLY(igraph_i_subclique_next_free, &freedata); + *resultids = igraph_Calloc(nc, igraph_vector_int_t); + if (!*resultids) { + IGRAPH_ERROR("Cannot calculate next cliques", IGRAPH_ENOMEM); + } + freedata.resultids = *resultids; + *resultweights = igraph_Calloc(nc, igraph_vector_t); + if (!*resultweights) { + IGRAPH_ERROR("Cannot calculate next cliques", IGRAPH_ENOMEM); + } + freedata.resultweights = *resultweights; + *result = igraph_Calloc(nc, igraph_t); + if (!*result) { + IGRAPH_ERROR("Cannot calculate next cliques", IGRAPH_ENOMEM); + } + freedata.result = *result; + + igraph_vector_init(&newedges, 100); + IGRAPH_FINALLY(igraph_vector_destroy, &newedges); + igraph_vector_int_init(&mark, no_of_nodes); + IGRAPH_FINALLY(igraph_vector_destroy, &mark); + igraph_vector_int_init(&map, no_of_nodes); + IGRAPH_FINALLY(igraph_vector_destroy, &map); + igraph_vector_int_init(&edges, 100); + IGRAPH_FINALLY(igraph_vector_int_destroy, &edges); + igraph_vector_init(&neis, 10); + IGRAPH_FINALLY(igraph_vector_destroy, &neis); + + if (clique_thr) { + igraph_vector_resize(clique_thr, nc); + } + if (next_thr) { + igraph_vector_resize(next_thr, nc); + } + + /* Iterate over all cliques. We will create graphs for all + subgraphs defined by the cliques. */ + + for (c = 0; c < nc; c++) { + igraph_vector_t *clique = VECTOR(*cliques)[c]; + igraph_real_t minweight = IGRAPH_INFINITY, nextweight = IGRAPH_INFINITY; + igraph_integer_t e, v, clsize = igraph_vector_size(clique); + igraph_integer_t noe, nov = 0; + igraph_vector_int_t *newids = (*resultids) + c; + igraph_vector_t *neww = (*resultweights) + c; + igraph_t *newgraph = (*result) + c; + igraph_vector_int_clear(&edges); + igraph_vector_clear(&newedges); + + /* --------------------------------------------------- */ + + /* Iterate over the vertices of a clique and find the + edges within the clique, put them in a list. + At the same time, search for the minimum edge weight within + the clique and the next edge weight if any. */ + + for (v = 0; v < clsize; v++) { + igraph_integer_t i, neilen, node = VECTOR(*clique)[v]; + igraph_incident(graph, &neis, node, IGRAPH_ALL); + neilen = igraph_vector_size(&neis); + VECTOR(mark)[node] = c + 1; + for (i = 0; i < neilen; i++) { + igraph_integer_t edge = VECTOR(neis)[i]; + igraph_integer_t nei = IGRAPH_OTHER(graph, edge, node); + if (VECTOR(mark)[nei] == c + 1) { + igraph_real_t w = VECTOR(*weights)[edge]; + igraph_vector_int_push_back(&edges, edge); + if (w < minweight) { + nextweight = minweight; + minweight = w; + } else if (w > minweight && w < nextweight) { + nextweight = w; + } + } + } + } /* v < clsize */ + + /* --------------------------------------------------- */ + + /* OK, we have stored the edges and found the weight of + the clique and the next weight to consider */ + + if (clique_thr) { + VECTOR(*clique_thr)[c] = minweight; + } + if (next_thr) { + VECTOR(*next_thr )[c] = nextweight; + } + + /* --------------------------------------------------- */ + + /* Now we create the subgraph from the edges above the next + threshold, and their incident vertices. */ + + igraph_vector_int_init(newids, 0); + igraph_vector_init(neww, 0); + + /* We use mark[] to denote the vertices already mapped to + the new graph. If this is -(c+1), then the vertex was + mapped, otherwise it was not. The mapping itself is in + map[]. */ + + noe = igraph_vector_int_size(&edges); + for (e = 0; e < noe; e++) { + igraph_integer_t edge = VECTOR(edges)[e]; + igraph_integer_t from, to; + igraph_real_t w = VECTOR(*weights)[edge]; + igraph_edge(graph, edge, &from, &to); + if (w >= nextweight) { + if (VECTOR(mark)[from] == c + 1) { + VECTOR(map)[from] = nov++; + VECTOR(mark)[from] = -(c + 1); + igraph_vector_int_push_back(newids, VECTOR(*ids)[from]); + } + if (VECTOR(mark)[to] == c + 1) { + VECTOR(map)[to] = nov++; + VECTOR(mark)[to] = -(c + 1); + igraph_vector_int_push_back(newids, VECTOR(*ids)[to]); + } + igraph_vector_push_back(neww, w); + igraph_vector_push_back(&newedges, VECTOR(map)[from]); + igraph_vector_push_back(&newedges, VECTOR(map)[to]); + } + } + + igraph_create(newgraph, &newedges, nov, IGRAPH_UNDIRECTED); + + /* --------------------------------------------------- */ + + } /* c < nc */ + + igraph_vector_destroy(&neis); + igraph_vector_int_destroy(&edges); + igraph_vector_int_destroy(&mark); + igraph_vector_int_destroy(&map); + igraph_vector_destroy(&newedges); + IGRAPH_FINALLY_CLEAN(6); /* + freedata */ + + return 0; +} + +static void igraph_i_graphlets_destroy_vectorlist(igraph_vector_ptr_t *vl) { + int i, n = igraph_vector_ptr_size(vl); + for (i = 0; i < n; i++) { + igraph_vector_t *v = (igraph_vector_t*) VECTOR(*vl)[i]; + if (v) { + igraph_vector_destroy(v); + } + } + igraph_vector_ptr_destroy(vl); +} + +static int igraph_i_graphlets(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_ptr_t *cliques, + igraph_vector_t *thresholds, + const igraph_vector_int_t *ids, + igraph_real_t startthr) { + + /* This version is different from the main function, and is + appropriate to use in recursive calls, because it _adds_ the + results to 'cliques' and 'thresholds' and uses the supplied + 'startthr' */ + + igraph_vector_ptr_t mycliques; + int no_of_edges = igraph_ecount(graph); + igraph_vector_t subv; + igraph_t subg; + int i, nographs, nocliques; + igraph_t *newgraphs = 0; + igraph_vector_t *newweights = 0; + igraph_vector_int_t *newids = 0; + igraph_vector_t clique_thr, next_thr; + igraph_i_subclique_next_free_t freedata = { 0, 0, 0, 0 }; + + IGRAPH_CHECK(igraph_vector_ptr_init(&mycliques, 0)); + IGRAPH_FINALLY(igraph_i_graphlets_destroy_vectorlist, &mycliques); + IGRAPH_VECTOR_INIT_FINALLY(&subv, 0); + + /* We start by finding cliques at the lowest threshold */ + for (i = 0; i < no_of_edges; i++) { + if (VECTOR(*weights)[i] >= startthr) { + IGRAPH_CHECK(igraph_vector_push_back(&subv, i)); + } + } + igraph_subgraph_edges(graph, &subg, igraph_ess_vector(&subv), + /*delete_vertices=*/ 0); + IGRAPH_FINALLY(igraph_destroy, &subg); + igraph_maximal_cliques(&subg, &mycliques, /*min_size=*/ 0, /*max_size=*/ 0); + igraph_destroy(&subg); + IGRAPH_FINALLY_CLEAN(1); + nocliques = igraph_vector_ptr_size(&mycliques); + + igraph_vector_destroy(&subv); + IGRAPH_FINALLY_CLEAN(1); + + /* Get the next cliques and thresholds */ + IGRAPH_VECTOR_INIT_FINALLY(&next_thr, 0); + IGRAPH_VECTOR_INIT_FINALLY(&clique_thr, 0); + + igraph_i_subclique_next(graph, weights, ids, &mycliques, + &newgraphs, &newweights, &newids, + &clique_thr, &next_thr); + + freedata.result = newgraphs; + freedata.resultids = newids; + freedata.resultweights = newweights; + freedata.nc = nocliques; + IGRAPH_FINALLY(igraph_i_subclique_next_free, &freedata); + + /* Store cliques at the current level */ + igraph_vector_append(thresholds, &clique_thr); + for (i = 0; i < nocliques; i++) { + igraph_vector_t *cl = (igraph_vector_t*) VECTOR(mycliques)[i]; + int j, n = igraph_vector_size(cl); + for (j = 0; j < n; j++) { + int node = VECTOR(*cl)[j]; + VECTOR(*cl)[j] = VECTOR(*ids)[node]; + } + igraph_vector_sort(cl); + } + igraph_vector_ptr_append(cliques, &mycliques); + + /* Recursive calls for cliques found */ + nographs = igraph_vector_ptr_size(&mycliques); + for (i = 0; i < nographs; i++) { + igraph_t *g = newgraphs + i; + if (igraph_vcount(g) > 1) { + igraph_vector_t *w = newweights + i; + igraph_vector_int_t *ids = newids + i; + igraph_i_graphlets(g, w, cliques, thresholds, ids, VECTOR(next_thr)[i]); + } + } + + igraph_vector_destroy(&clique_thr); + igraph_vector_destroy(&next_thr); + igraph_i_subclique_next_free(&freedata); + igraph_vector_ptr_destroy(&mycliques); /* contents was copied over */ + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +typedef struct { + const igraph_vector_ptr_t *cliques; + const igraph_vector_t *thresholds; +} igraph_i_graphlets_filter_t; + +static int igraph_i_graphlets_filter_cmp(void *data, const void *a, const void *b) { + igraph_i_graphlets_filter_t *ddata = (igraph_i_graphlets_filter_t *) data; + int *aa = (int*) a; + int *bb = (int*) b; + igraph_real_t t_a = VECTOR(*ddata->thresholds)[*aa]; + igraph_real_t t_b = VECTOR(*ddata->thresholds)[*bb]; + igraph_vector_t *v_a, *v_b; + int s_a, s_b; + + if (t_a < t_b) { + return -1; + } else if (t_a > t_b) { + return 1; + } + + v_a = (igraph_vector_t*) VECTOR(*ddata->cliques)[*aa]; + v_b = (igraph_vector_t*) VECTOR(*ddata->cliques)[*bb]; + s_a = igraph_vector_size(v_a); + s_b = igraph_vector_size(v_b); + + if (s_a < s_b) { + return -1; + } else if (s_a > s_b) { + return 1; + } else { + return 0; + } +} + +static int igraph_i_graphlets_filter(igraph_vector_ptr_t *cliques, + igraph_vector_t *thresholds) { + + /* Filter out non-maximal cliques. Every non-maximal clique is + part of a maximal clique, at the same threshold. + + First we order the cliques, according to their threshold, and + then according to their size. So when we look for a candidate + superset, we only need to check the cliques next in the list, + until their threshold is different. */ + + int i, iptr, nocliques = igraph_vector_ptr_size(cliques); + igraph_vector_int_t order; + igraph_i_graphlets_filter_t sortdata = { cliques, thresholds }; + + igraph_vector_int_init(&order, nocliques); + IGRAPH_FINALLY(igraph_vector_int_destroy, &order); + for (i = 0; i < nocliques; i++) { + VECTOR(order)[i] = i; + } + + igraph_qsort_r(VECTOR(order), nocliques, sizeof(int), &sortdata, + igraph_i_graphlets_filter_cmp); + + for (i = 0; i < nocliques - 1; i++) { + int ri = VECTOR(order)[i]; + igraph_vector_t *needle = VECTOR(*cliques)[ri]; + igraph_real_t thr_i = VECTOR(*thresholds)[ri]; + int n_i = igraph_vector_size(needle); + int j = i + 1; + + for (j = i + 1; j < nocliques; j++) { + int rj = VECTOR(order)[j]; + igraph_real_t thr_j = VECTOR(*thresholds)[rj]; + igraph_vector_t *hay; + int n_j, pi = 0, pj = 0; + + /* Done, not found */ + if (thr_j != thr_i) { + break; + } + + /* Check size of hay */ + hay = VECTOR(*cliques)[rj]; + n_j = igraph_vector_size(hay); + if (n_i > n_j) { + continue; + } + + /* Check if hay is a superset */ + while (pi < n_i && pj < n_j && n_i - pi <= n_j - pj) { + int ei = VECTOR(*needle)[pi]; + int ej = VECTOR(*hay)[pj]; + if (ei < ej) { + break; + } else if (ei > ej) { + pj++; + } else { + pi++; pj++; + } + } + if (pi == n_i) { + /* Found, delete immediately */ + igraph_vector_destroy(needle); + igraph_free(needle); + VECTOR(*cliques)[ri] = 0; + break; + } + } + } + + /* Remove null pointers from the list of cliques */ + for (i = 0, iptr = 0; i < nocliques; i++) { + igraph_vector_t *v = VECTOR(*cliques)[i]; + if (v) { + VECTOR(*cliques)[iptr] = v; + VECTOR(*thresholds)[iptr] = VECTOR(*thresholds)[i]; + iptr++; + } + } + igraph_vector_ptr_resize(cliques, iptr); + igraph_vector_resize(thresholds, iptr); + + igraph_vector_int_destroy(&order); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_graphlets_candidate_basis + * Calculate a candidate graphlets basis + * + * \param graph The input graph, it must be a simple graph, edge directions are + * ignored. + * \param weights Weights of the edges, a vector. + * \param cliques An initialized vector of pointers. + * The graphlet basis is stored here. Each element of the pointer + * vector will be a vector of vertex ids. Each elements must be + * destroyed using \ref igraph_vector_destroy() and \ref igraph_free(). + * \param thresholds An initialized vector, the (highest possible) + * weight thresholds for finding the basis subgraphs are stored + * here. + * \return Error code. + * + * See also: \ref igraph_graphlets() and \ref igraph_graphlets_project(). + */ + +int igraph_graphlets_candidate_basis(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_ptr_t *cliques, + igraph_vector_t *thresholds) { + + int no_of_nodes = igraph_vcount(graph); + int no_of_edges = igraph_ecount(graph); + igraph_real_t minthr; + igraph_vector_int_t ids; + igraph_bool_t simple; + int i; + + /* Some checks */ + if (weights == NULL) { + IGRAPH_ERROR("Graphlet functions require weighted graphs", IGRAPH_EINVAL); + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + igraph_is_simple(graph, &simple); + if (!simple) { + IGRAPH_ERROR("Graphlets work on simple graphs only", IGRAPH_EINVAL); + } + + minthr = igraph_vector_min(weights); + igraph_vector_ptr_clear(cliques); + igraph_vector_clear(thresholds); + igraph_vector_int_init(&ids, no_of_nodes); + IGRAPH_FINALLY(igraph_vector_int_destroy, &ids); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(ids)[i] = i; + } + + igraph_i_graphlets(graph, weights, cliques, thresholds, &ids, minthr); + + igraph_vector_int_destroy(&ids); + IGRAPH_FINALLY_CLEAN(1); + + igraph_i_graphlets_filter(cliques, thresholds); + + return 0; +} + +/* TODO: not made static because it is used by the R interface */ +int igraph_i_graphlets_project(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_vector_ptr_t *cliques, + igraph_vector_t *Mu, igraph_bool_t startMu, + int niter, int vid1) { + + int no_of_nodes = igraph_vcount(graph); + int no_of_edges = igraph_ecount(graph); + int no_cliques = igraph_vector_ptr_size(cliques); + igraph_vector_int_t vcl, vclidx, ecl, eclidx, cel, celidx; + igraph_vector_t edgelist, newweights, normfact; + int i, total_vertices, e, ptr, total_edges; + igraph_bool_t simple; + + /* Check arguments */ + if (weights == NULL) { + IGRAPH_ERROR("Graphlet functions require weighted graphs", IGRAPH_EINVAL); + } + if (no_of_edges != igraph_vector_size(weights)) { + IGRAPH_ERROR("Invalid weight vector size", IGRAPH_EINVAL); + } + if (startMu && igraph_vector_size(Mu) != no_cliques) { + IGRAPH_ERROR("Invalid start coefficient vector size", IGRAPH_EINVAL); + } + if (niter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negative", IGRAPH_EINVAL); + } + igraph_is_simple(graph, &simple); + if (!simple) { + IGRAPH_ERROR("Graphlets work on simple graphs only", IGRAPH_EINVAL); + } + + if (!startMu) { + igraph_vector_resize(Mu, no_cliques); + igraph_vector_fill(Mu, 1); + } + + /* Count # cliques per vertex. Also, create an index + for the edges per clique. */ + IGRAPH_CHECK(igraph_vector_int_init(&vclidx, no_of_nodes + 2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &vclidx); + IGRAPH_CHECK(igraph_vector_int_init(&celidx, no_cliques + 3)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &celidx); + for (i = 0, total_vertices = 0, total_edges = 0; i < no_cliques; i++) { + igraph_vector_t *v = VECTOR(*cliques)[i]; + int j, n = igraph_vector_size(v); + total_vertices += n; + total_edges += n * (n - 1) / 2; + VECTOR(celidx)[i + 2] = total_edges; + for (j = 0; j < n; j++) { + int vv = VECTOR(*v)[j] - vid1; + VECTOR(vclidx)[vv + 2] += 1; + } + } + VECTOR(celidx)[i + 2] = total_edges; + + /* Finalize index vector */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(vclidx)[i + 2] += VECTOR(vclidx)[i + 1]; + } + + /* Create vertex-clique list, the cliques for each vertex. */ + IGRAPH_CHECK(igraph_vector_int_init(&vcl, total_vertices)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &vcl); + for (i = 0; i < no_cliques; i++) { + igraph_vector_t *v = VECTOR(*cliques)[i]; + int j, n = igraph_vector_size(v); + for (j = 0; j < n; j++) { + int vv = VECTOR(*v)[j] - vid1; + int p = VECTOR(vclidx)[vv + 1]; + VECTOR(vcl)[p] = i; + VECTOR(vclidx)[vv + 1] += 1; + } + } + + /* Create an edge-clique list, the cliques of each edge */ + IGRAPH_CHECK(igraph_vector_int_init(&ecl, total_edges)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &ecl); + IGRAPH_CHECK(igraph_vector_int_init(&eclidx, no_of_edges + 1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &eclidx); + IGRAPH_CHECK(igraph_vector_init(&edgelist, no_of_edges * 2)); + IGRAPH_FINALLY(igraph_vector_destroy, &edgelist); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edgelist, /*by_col=*/ 0)); + for (i = 0, e = 0, ptr = 0; e < no_of_edges; e++) { + int from = VECTOR(edgelist)[i++]; + int to = VECTOR(edgelist)[i++]; + int from_s = VECTOR(vclidx)[from]; + int from_e = VECTOR(vclidx)[from + 1]; + int to_s = VECTOR(vclidx)[to]; + int to_e = VECTOR(vclidx)[to + 1]; + VECTOR(eclidx)[e] = ptr; + while (from_s < from_e && to_s < to_e) { + int from_v = VECTOR(vcl)[from_s]; + int to_v = VECTOR(vcl)[to_s]; + if (from_v == to_v) { + VECTOR(ecl)[ptr++] = from_v; + from_s++; to_s++; + } else if (from_v < to_v) { + from_s++; + } else { + to_s++; + } + } + } + VECTOR(eclidx)[e] = ptr; + + igraph_vector_destroy(&edgelist); + IGRAPH_FINALLY_CLEAN(1); + + /* Convert the edge-clique list to a clique-edge list */ + IGRAPH_CHECK(igraph_vector_int_init(&cel, total_edges)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &cel); + for (i = 0; i < no_of_edges; i++) { + int ecl_s = VECTOR(eclidx)[i], ecl_e = VECTOR(eclidx)[i + 1], j; + for (j = ecl_s; j < ecl_e; j++) { + int cl = VECTOR(ecl)[j]; + int epos = VECTOR(celidx)[cl + 1]; + VECTOR(cel)[epos] = i; + VECTOR(celidx)[cl + 1] += 1; + } + } + + /* Normalizing factors for the iteration */ + IGRAPH_CHECK(igraph_vector_init(&normfact, no_cliques)); + IGRAPH_FINALLY(igraph_vector_destroy, &normfact); + for (i = 0; i < no_cliques; i++) { + igraph_vector_t *v = VECTOR(*cliques)[i]; + int n = igraph_vector_size(v); + VECTOR(normfact)[i] = n * (n + 1) / 2; + } + + /* We have the clique-edge list, so do the projection now */ + IGRAPH_CHECK(igraph_vector_init(&newweights, no_of_edges)); + IGRAPH_FINALLY(igraph_vector_destroy, &newweights); + for (i = 0; i < niter; i++) { + for (e = 0; e < no_of_edges; e++) { + int start = VECTOR(eclidx)[e]; + int end = VECTOR(eclidx)[e + 1]; + VECTOR(newweights)[e] = 0.0001; + while (start < end) { + int clique = VECTOR(ecl)[start++]; + VECTOR(newweights)[e] += VECTOR(*Mu)[clique]; + } + } + for (e = 0; e < no_cliques; e++) { + igraph_real_t sumratio = 0; + int start = VECTOR(celidx)[e]; + int end = VECTOR(celidx)[e + 1]; + while (start < end) { + int edge = VECTOR(cel)[start++]; + sumratio += VECTOR(*weights)[edge] / VECTOR(newweights)[edge]; + } + VECTOR(*Mu)[e] *= sumratio / VECTOR(normfact)[e]; + } + } + + igraph_vector_destroy(&newweights); + igraph_vector_destroy(&normfact); + igraph_vector_int_destroy(&cel); + igraph_vector_int_destroy(&eclidx); + igraph_vector_int_destroy(&ecl); + igraph_vector_int_destroy(&vcl); + igraph_vector_int_destroy(&celidx); + igraph_vector_int_destroy(&vclidx); + IGRAPH_FINALLY_CLEAN(8); + + return 0; +} + +/** + * \function igraph_graphlets_project + * Project a graph on a graphlets basis + * + * Note that the graph projected does not have to be the same that + * was used to calculate the graphlet basis, but it is assumed that + * it has the same number of vertices, and the vertex ids of the two + * graphs match. + * \param graph The input graph, it must be a simple graph, edge directions are + * ignored. + * \param weights Weights of the edges in the input graph, a vector. + * \param cliques The graphlet basis, a pointer vector, in which each + * element is a vector of vertex ids. + * \param Mu An initialized vector, the weights of the graphlets will + * be stored here. This vector is also used to initialize the + * the weight vector for the iterative algorithm, if the + * \c startMu argument is true (non-zero). + * \param startMu If true (non-zero), then the supplied Mu vector is + * used as the starting point of the iteration. Otherwise a + * constant 1 vector is used. + * \param niter Integer scalar, the number of iterations to perform. + * \return Error code. + * + * See also: \ref igraph_graphlets() and + * \ref igraph_graphlets_candidate_basis(). + */ + +int igraph_graphlets_project(const igraph_t *graph, + const igraph_vector_t *weights, + const igraph_vector_ptr_t *cliques, + igraph_vector_t *Mu, igraph_bool_t startMu, + int niter) { + + return igraph_i_graphlets_project(graph, weights, cliques, Mu, startMu, + niter, /*vid1=*/ 0); +} + +typedef struct igraph_i_graphlets_order_t { + const igraph_vector_ptr_t *cliques; + const igraph_vector_t *Mu; +} igraph_i_graphlets_order_t; + +static int igraph_i_graphlets_order_cmp(void *data, const void *a, const void *b) { + igraph_i_graphlets_order_t *ddata = (igraph_i_graphlets_order_t*) data; + int *aa = (int*) a; + int *bb = (int*) b; + igraph_real_t Mu_a = VECTOR(*ddata->Mu)[*aa]; + igraph_real_t Mu_b = VECTOR(*ddata->Mu)[*bb]; + + if (Mu_a < Mu_b) { + return 1; + } else if (Mu_a > Mu_b) { + return -1; + } else { + return 0; + } +} + +/** + * \function igraph_graphlets + * Calculate graphlets basis and project the graph on it + * + * This function simply calls \ref igraph_graphlets_candidate_basis() + * and \ref igraph_graphlets_project(), and then orders the graphlets + * according to decreasing weights. + * \param graph The input graph, it must be a simple graph, edge directions are + * ignored. + * \param weights Weights of the edges, a vector. + * \param cliques An initialized vector of pointers. + * The graphlet basis is stored here. Each element of the pointer + * vector will be a vector of vertex ids. + * \param Mu An initialized vector, the weights of the graphlets will + * be stored here. + * \param niter Integer scalar, the number of iterations to perform + * for the projection step. + * \return Error code. + * + * See also: \ref igraph_graphlets_candidate_basis() and + * \ref igraph_graphlets_project(). + */ + +int igraph_graphlets(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_ptr_t *cliques, + igraph_vector_t *Mu, int niter) { + + int i, nocliques; + igraph_vector_t thresholds; + igraph_vector_int_t order; + igraph_i_graphlets_order_t sortdata = { cliques, Mu }; + + igraph_vector_init(&thresholds, 0); + IGRAPH_FINALLY(igraph_vector_destroy, &thresholds); + igraph_graphlets_candidate_basis(graph, weights, cliques, &thresholds); + igraph_vector_destroy(&thresholds); + IGRAPH_FINALLY_CLEAN(1); + + igraph_graphlets_project(graph, weights, cliques, Mu, /*startMu=*/ 0, niter); + + nocliques = igraph_vector_ptr_size(cliques); + igraph_vector_int_init(&order, nocliques); + IGRAPH_FINALLY(igraph_vector_int_destroy, &order); + for (i = 0; i < nocliques; i++) { + VECTOR(order)[i] = i; + } + igraph_qsort_r(VECTOR(order), nocliques, sizeof(int), &sortdata, + igraph_i_graphlets_order_cmp); + + igraph_vector_ptr_index_int(cliques, &order); + igraph_vector_index_int(Mu, &order); + + igraph_vector_int_destroy(&order); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} diff --git a/src/glpk_support.c b/src/glpk_support.c new file mode 100644 index 0000000..15f10b5 --- /dev/null +++ b/src/glpk_support.c @@ -0,0 +1,100 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "config.h" + +#ifdef HAVE_GLPK + +#include "igraph_types.h" +#include "igraph_error.h" +#include "igraph_interrupt_internal.h" +#include +#include + +void igraph_i_glpk_interruption_hook(glp_tree *tree, void *info) { + IGRAPH_UNUSED(info); + + /* This is a special version of IGRAPH_ALLOW_INTERRUPTION(). + Calling glp_ios_terminate() from glp_intopt()'s callback function + signals to GLPK that it should terminate the optimization and return + with the code GLP_ESTOP. + */ + if (igraph_i_interruption_handler) { + if (igraph_allow_interruption(NULL) != IGRAPH_SUCCESS) { + glp_ios_terminate(tree); + } + } +} + +int igraph_i_glpk_check(int retval, const char* message) { + char* code = "none"; + char message_and_code[4096]; + + if (retval == IGRAPH_SUCCESS) { + return IGRAPH_SUCCESS; + } + + /* handle errors */ +#define HANDLE_CODE(c) case c: code = #c; retval = IGRAPH_##c; break; +#define HANDLE_CODE2(c) case c: code = #c; retval = IGRAPH_FAILURE; break; +#define HANDLE_CODE3(c) case c: code = #c; retval = IGRAPH_INTERRUPTED; break; + switch (retval) { + HANDLE_CODE(GLP_EBOUND); + HANDLE_CODE(GLP_EROOT); + HANDLE_CODE(GLP_ENOPFS); + HANDLE_CODE(GLP_ENODFS); + HANDLE_CODE(GLP_EFAIL); + HANDLE_CODE(GLP_EMIPGAP); + HANDLE_CODE(GLP_ETMLIM); + + HANDLE_CODE3(GLP_ESTOP); + + HANDLE_CODE2(GLP_EBADB); + HANDLE_CODE2(GLP_ESING); + HANDLE_CODE2(GLP_ECOND); + HANDLE_CODE2(GLP_EOBJLL); + HANDLE_CODE2(GLP_EOBJUL); + HANDLE_CODE2(GLP_EITLIM); + + default: + IGRAPH_ERROR("unknown GLPK error", IGRAPH_FAILURE); + } +#undef HANDLE_CODE +#undef HANDLE_CODE2 +#undef HANDLE_CODE3 + + sprintf(message_and_code, "%s (%s)", message, code); + IGRAPH_ERROR(message_and_code, retval); +} + +#endif + +#ifdef USING_R + +int igraph_glpk_dummy() { + return 'b' + 'a' + 's' + 's' + 'z' + 'a' + 't' + 'o' + 'k' + + 'm' + 'e' + 'g'; +} + +#endif diff --git a/src/gml_tree.c b/src/gml_tree.c new file mode 100644 index 0000000..334d502 --- /dev/null +++ b/src/gml_tree.c @@ -0,0 +1,261 @@ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_gml_tree.h" +#include "igraph_memory.h" +#include "igraph_error.h" +#include "config.h" + +#include +#include + +int igraph_gml_tree_init_integer(igraph_gml_tree_t *t, + const char *name, int namelen, + igraph_integer_t value) { + + igraph_integer_t *p; + + IGRAPH_UNUSED(namelen); + + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->names, 1); + IGRAPH_CHECK(igraph_vector_char_init(&t->types, 1)); + IGRAPH_FINALLY(igraph_vector_char_destroy, &t->types); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->children, 1); + + /* names */ + VECTOR(t->names)[0] = (void*)name; + + /* types */ + VECTOR(t->types)[0] = IGRAPH_I_GML_TREE_INTEGER; + + /* children */ + p = igraph_Calloc(1, igraph_integer_t); + if (!p) { + IGRAPH_ERROR("Cannot create integer GML tree node", IGRAPH_ENOMEM); + } + *p = value; + VECTOR(t->children)[0] = p; + + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +int igraph_gml_tree_init_real(igraph_gml_tree_t *t, + const char *name, int namelen, + igraph_real_t value) { + + igraph_real_t *p; + + IGRAPH_UNUSED(namelen); + + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->names, 1); + IGRAPH_CHECK(igraph_vector_char_init(&t->types, 1)); + IGRAPH_FINALLY(igraph_vector_char_destroy, &t->types); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->children, 1); + + /* names */ + VECTOR(t->names)[0] = (void*) name; + + /* types */ + VECTOR(t->types)[0] = IGRAPH_I_GML_TREE_REAL; + + /* children */ + p = igraph_Calloc(1, igraph_real_t); + if (!p) { + IGRAPH_ERROR("Cannot create real GML tree node", IGRAPH_ENOMEM); + } + *p = value; + VECTOR(t->children)[0] = p; + + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +int igraph_gml_tree_init_string(igraph_gml_tree_t *t, + const char *name, int namelen, + const char *value, int valuelen) { + + IGRAPH_UNUSED(namelen); + IGRAPH_UNUSED(valuelen); + + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->names, 1); + IGRAPH_CHECK(igraph_vector_char_init(&t->types, 1)); + IGRAPH_FINALLY(igraph_vector_char_destroy, &t->types); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->children, 1); + + /* names */ + VECTOR(t->names)[0] = (void*) name; + + /* types */ + VECTOR(t->types)[0] = IGRAPH_I_GML_TREE_STRING; + + /* children */ + VECTOR(t->children)[0] = (void*)value; + + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +int igraph_gml_tree_init_tree(igraph_gml_tree_t *t, + const char *name, int namelen, + igraph_gml_tree_t *value) { + + IGRAPH_UNUSED(namelen); + + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->names, 1); + IGRAPH_CHECK(igraph_vector_char_init(&t->types, 1)); + IGRAPH_FINALLY(igraph_vector_char_destroy, &t->types); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->children, 1); + + /* names */ + VECTOR(t->names)[0] = (void*)name; + + /* types */ + VECTOR(t->types)[0] = IGRAPH_I_GML_TREE_TREE; + + /* children */ + VECTOR(t->children)[0] = value; + + IGRAPH_FINALLY_CLEAN(3); + return 0; + +} + +/* merge is destructive, the _second_ tree is destroyed */ +int igraph_gml_tree_mergedest(igraph_gml_tree_t *t1, igraph_gml_tree_t *t2) { + long int i, n = igraph_vector_ptr_size(&t2->children); + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_vector_ptr_push_back(&t1->names, VECTOR(t2->names)[i])); + IGRAPH_CHECK(igraph_vector_char_push_back(&t1->types, VECTOR(t2->types)[i])); + IGRAPH_CHECK(igraph_vector_ptr_push_back(&t1->children, + VECTOR(t2->children)[i])); + } + + igraph_vector_ptr_destroy(&t2->names); + igraph_vector_char_destroy(&t2->types); + igraph_vector_ptr_destroy(&t2->children); + return 0; +} + +void igraph_gml_tree_destroy(igraph_gml_tree_t *t) { + + long int i, n = igraph_vector_ptr_size(&t->children); + for (i = 0; i < n; i++) { + int type = VECTOR(t->types)[i]; + switch (type) { + case IGRAPH_I_GML_TREE_TREE: + igraph_gml_tree_destroy(VECTOR(t->children)[i]); + igraph_Free(VECTOR(t->names)[i]); + break; + case IGRAPH_I_GML_TREE_INTEGER: + igraph_Free(VECTOR(t->children)[i]); + igraph_Free(VECTOR(t->names)[i]); + break; + case IGRAPH_I_GML_TREE_REAL: + igraph_Free(VECTOR(t->children)[i]); + igraph_Free(VECTOR(t->names)[i]); + break; + case IGRAPH_I_GML_TREE_STRING: + igraph_Free(VECTOR(t->children)[i]); + igraph_Free(VECTOR(t->names)[i]); + break; + case IGRAPH_I_GML_TREE_DELETED: + break; + } + } + igraph_vector_ptr_destroy(&t->names); + igraph_vector_char_destroy(&t->types); + igraph_vector_ptr_destroy(&t->children); + igraph_Free(t); +} + +long int igraph_gml_tree_length(const igraph_gml_tree_t *t) { + return igraph_vector_ptr_size(&t->names); +} + +long int igraph_gml_tree_find(const igraph_gml_tree_t *t, + const char *name, long int from) { + + long int size = igraph_vector_ptr_size(&t->names); + while ( from < size && (! VECTOR(t->names)[from] || + strcmp(VECTOR(t->names)[from], name)) ) { + from++; + } + + if (from == size) { + from = -1; + } + return from; +} + +long int igraph_gml_tree_findback(const igraph_gml_tree_t *t, + const char *name, long int from) { + while ( from >= 0 && (! VECTOR(t->names)[from] || + strcmp(VECTOR(t->names)[from], name)) ) { + from--; + } + + return from; +} + +int igraph_gml_tree_type(const igraph_gml_tree_t *t, long int pos) { + return VECTOR(t->types)[pos]; +} + +const char *igraph_gml_tree_name(const igraph_gml_tree_t *t, long int pos) { + return VECTOR(t->names)[pos]; +} + +igraph_integer_t igraph_gml_tree_get_integer(const igraph_gml_tree_t *t, + long int pos) { + igraph_integer_t *i = VECTOR(t->children)[pos]; + return *i; +} + +igraph_real_t igraph_gml_tree_get_real(const igraph_gml_tree_t *t, + long int pos) { + igraph_real_t *d = VECTOR(t->children)[pos]; + return *d; +} + +const char *igraph_gml_tree_get_string(const igraph_gml_tree_t *t, + long int pos) { + const char *s = VECTOR(t->children)[pos]; + return s; +} + +igraph_gml_tree_t *igraph_gml_tree_get_tree(const igraph_gml_tree_t *t, + long int pos) { + igraph_gml_tree_t *tree = VECTOR(t->children)[pos]; + return tree; +} + +void igraph_gml_tree_delete(igraph_gml_tree_t *t, long int pos) { + if (VECTOR(t->types)[pos] == IGRAPH_I_GML_TREE_TREE) { + igraph_gml_tree_destroy(VECTOR(t->children)[pos]); + } + igraph_Free(VECTOR(t->names)[pos]); + igraph_Free(VECTOR(t->children)[pos]); + VECTOR(t->children)[pos] = 0; + VECTOR(t->names)[pos] = 0; + VECTOR(t->types)[pos] = IGRAPH_I_GML_TREE_DELETED; +} diff --git a/src/hacks.c b/src/hacks.c new file mode 100644 index 0000000..a519e35 --- /dev/null +++ b/src/hacks.c @@ -0,0 +1,54 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include +#include "igraph_hacks_internal.h" + +/* These are implementations of common C functions that may be missing from some + * compilers; for instance, icc does not provide stpcpy so we implement it + * here. */ + +/** + * Drop-in replacement for strdup. + * Used only in compilers that do not have strdup or _strdup + */ +char* igraph_i_strdup(const char *s) { + size_t n = strlen(s) + 1; + char* result = (char*)malloc(sizeof(char) * n); + if (result) { + memcpy(result, s, n); + } + return result; +} + +/** + * Drop-in replacement for stpcpy. + * Used only in compilers that do not have stpcpy + */ +char* igraph_i_stpcpy(char* s1, const char* s2) { + char* result = strcpy(s1, s2); + return result + strlen(s1); +} + diff --git a/src/heap.c b/src/heap.c new file mode 100644 index 0000000..316541e --- /dev/null +++ b/src/heap.c @@ -0,0 +1,1082 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_types_internal.h" +#include "igraph_memory.h" +#include "igraph_error.h" +#include "igraph_math.h" +#include "config.h" + +#include +#include /* memcpy & co. */ +#include + +#define PARENT(x) (((x)+1)/2-1) +#define LEFTCHILD(x) (((x)+1)*2-1) +#define RIGHTCHILD(x) (((x)+1)*2) + +/** + * \ingroup indheap + * \brief Initializes an indexed heap (constructor). + * + * @return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +int igraph_indheap_init (igraph_indheap_t* h, long int alloc_size) { + if (alloc_size <= 0 ) { + alloc_size = 1; + } + h->stor_begin = igraph_Calloc(alloc_size, igraph_real_t); + if (h->stor_begin == 0) { + h->index_begin = 0; + IGRAPH_ERROR("indheap init failed", IGRAPH_ENOMEM); + } + h->index_begin = igraph_Calloc(alloc_size, long int); + if (h->index_begin == 0) { + igraph_Free(h->stor_begin); + h->stor_begin = 0; + IGRAPH_ERROR("indheap init failed", IGRAPH_ENOMEM); + } + + h->stor_end = h->stor_begin + alloc_size; + h->end = h->stor_begin; + h->destroy = 1; + + return 0; +} + +int igraph_indheap_clear(igraph_indheap_t *h) { + h->end = h->stor_begin; + return 0; +} + +/** + * \ingroup indheap + * \brief Initializes and build an indexed heap from a C array (constructor). + * + * @return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +int igraph_indheap_init_array (igraph_indheap_t *h, igraph_real_t* data, long int len) { + long int i; + + h->stor_begin = igraph_Calloc(len, igraph_real_t); + if (h->stor_begin == 0) { + h->index_begin = 0; + IGRAPH_ERROR("indheap init from array failed", IGRAPH_ENOMEM); + } + h->index_begin = igraph_Calloc(len, long int); + if (h->index_begin == 0) { + igraph_Free(h->stor_begin); + h->stor_begin = 0; + IGRAPH_ERROR("indheap init from array failed", IGRAPH_ENOMEM); + } + h->stor_end = h->stor_begin + len; + h->end = h->stor_end; + h->destroy = 1; + + memcpy(h->stor_begin, data, (size_t) len * sizeof(igraph_real_t)); + for (i = 0; i < len; i++) { + h->index_begin[i] = i + 1; + } + + igraph_indheap_i_build (h, 0); + + return 0; +} + +/** + * \ingroup indheap + * \brief Destroys an initialized indexed heap. + */ + +void igraph_indheap_destroy (igraph_indheap_t* h) { + assert(h != 0); + if (h->destroy) { + if (h->stor_begin != 0) { + igraph_Free(h->stor_begin); + h->stor_begin = 0; + } + if (h->index_begin != 0) { + igraph_Free(h->index_begin); + h->index_begin = 0; + } + } +} + +/** + * \ingroup indheap + * \brief Checks whether a heap is empty. + */ + +igraph_bool_t igraph_indheap_empty (igraph_indheap_t* h) { + assert(h != 0); + assert(h->stor_begin != 0); + return h->stor_begin == h->end; +} + +/** + * \ingroup indheap + * \brief Adds an element to an indexed heap. + */ + +int igraph_indheap_push (igraph_indheap_t* h, igraph_real_t elem) { + assert(h != 0); + assert(h->stor_begin != 0); + + /* full, allocate more storage */ + if (h->stor_end == h->end) { + long int new_size = igraph_indheap_size(h) * 2; + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(igraph_indheap_reserve(h, new_size)); + } + + *(h->end) = elem; + h->end += 1; + *(h->index_begin + igraph_indheap_size(h) - 1) = igraph_indheap_size(h) - 1; + + /* maintain indheap */ + igraph_indheap_i_shift_up(h, igraph_indheap_size(h) - 1); + + return 0; +} + +/** + * \ingroup indheap + * \brief Adds an element to an indexed heap with a given index. + */ + +int igraph_indheap_push_with_index(igraph_indheap_t* h, long int idx, igraph_real_t elem) { + assert(h != 0); + assert(h->stor_begin != 0); + + /* full, allocate more storage */ + if (h->stor_end == h->end) { + long int new_size = igraph_indheap_size(h) * 2; + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(igraph_indheap_reserve(h, new_size)); + } + + *(h->end) = elem; + h->end += 1; + *(h->index_begin + igraph_indheap_size(h) - 1) = idx; + + /* maintain indheap */ + igraph_indheap_i_shift_up(h, igraph_indheap_size(h) - 1); + + return 0; +} + +/** + * \ingroup indheap + * \brief Modifies an element in an indexed heap. + */ + +int igraph_indheap_modify(igraph_indheap_t* h, long int idx, igraph_real_t elem) { + long int i, n; + + assert(h != 0); + assert(h->stor_begin != 0); + + n = igraph_indheap_size(h); + for (i = 0; i < n; i++) + if (h->index_begin[i] == idx) { + h->stor_begin[i] = elem; + break; + } + + if (i == n) { + return 0; + } + + /* maintain indheap */ + igraph_indheap_i_build(h, 0); + + return 0; +} + +/** + * \ingroup indheap + * \brief Returns the largest element in an indexed heap. + */ + +igraph_real_t igraph_indheap_max (igraph_indheap_t* h) { + assert(h != NULL); + assert(h->stor_begin != NULL); + assert(h->stor_begin != h->end); + + return h->stor_begin[0]; +} + +/** + * \ingroup indheap + * \brief Removes the largest element from an indexed heap. + */ + +igraph_real_t igraph_indheap_delete_max(igraph_indheap_t* h) { + igraph_real_t tmp; + + assert(h != NULL); + assert(h->stor_begin != NULL); + + tmp = h->stor_begin[0]; + igraph_indheap_i_switch(h, 0, igraph_indheap_size(h) - 1); + h->end -= 1; + igraph_indheap_i_sink(h, 0); + + return tmp; +} + +/** + * \ingroup indheap + * \brief Gives the number of elements in an indexed heap. + */ + +long int igraph_indheap_size (igraph_indheap_t* h) { + assert(h != 0); + assert(h->stor_begin != 0); + return h->end - h->stor_begin; +} + +/** + * \ingroup indheap + * \brief Reserves more memory for an indexed heap. + * + * @return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +int igraph_indheap_reserve (igraph_indheap_t* h, long int size) { + long int actual_size = igraph_indheap_size(h); + igraph_real_t *tmp1; + long int *tmp2; + assert(h != 0); + assert(h->stor_begin != 0); + + if (size <= actual_size) { + return 0; + } + + tmp1 = igraph_Calloc(size, igraph_real_t); + if (tmp1 == 0) { + IGRAPH_ERROR("indheap reserve failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmp1); + tmp2 = igraph_Calloc(size, long int); + if (tmp2 == 0) { + IGRAPH_ERROR("indheap reserve failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmp2); + memcpy(tmp1, h->stor_begin, (size_t) actual_size * sizeof(igraph_real_t)); + memcpy(tmp2, h->index_begin, (size_t) actual_size * sizeof(long int)); + igraph_Free(h->stor_begin); + igraph_Free(h->index_begin); + + h->stor_begin = tmp1; + h->index_begin = tmp2; + h->stor_end = h->stor_begin + size; + h->end = h->stor_begin + actual_size; + + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/** + * \ingroup indheap + * \brief Returns the index of the largest element in an indexed heap. + */ + +long int igraph_indheap_max_index(igraph_indheap_t *h) { + assert(h != 0); + assert(h->stor_begin != 0); + return h->index_begin[0]; +} + +/** + * \ingroup indheap + * \brief Builds an indexed heap, this function should not be called + * directly. + */ + +void igraph_indheap_i_build(igraph_indheap_t* h, long int head) { + + long int size = igraph_indheap_size(h); + if (RIGHTCHILD(head) < size) { + /* both subtrees */ + igraph_indheap_i_build(h, LEFTCHILD(head) ); + igraph_indheap_i_build(h, RIGHTCHILD(head)); + igraph_indheap_i_sink(h, head); + } else if (LEFTCHILD(head) < size) { + /* only left */ + igraph_indheap_i_build(h, LEFTCHILD(head)); + igraph_indheap_i_sink(h, head); + } else { + /* none */ + } +} + +/** + * \ingroup indheap + * \brief Moves an element up in the heap, don't call this function + * directly. + */ + +void igraph_indheap_i_shift_up(igraph_indheap_t *h, long int elem) { + + if (elem == 0 || h->stor_begin[elem] < h->stor_begin[PARENT(elem)]) { + /* at the top */ + } else { + igraph_indheap_i_switch(h, elem, PARENT(elem)); + igraph_indheap_i_shift_up(h, PARENT(elem)); + } +} + +/** + * \ingroup indheap + * \brief Moves an element down in the heap, don't call this function + * directly. + */ + +void igraph_indheap_i_sink(igraph_indheap_t* h, long int head) { + + long int size = igraph_indheap_size(h); + if (LEFTCHILD(head) >= size) { + /* no subtrees */ + } else if (RIGHTCHILD(head) == size || + h->stor_begin[LEFTCHILD(head)] >= h->stor_begin[RIGHTCHILD(head)]) { + /* sink to the left if needed */ + if (h->stor_begin[head] < h->stor_begin[LEFTCHILD(head)]) { + igraph_indheap_i_switch(h, head, LEFTCHILD(head)); + igraph_indheap_i_sink(h, LEFTCHILD(head)); + } + } else { + /* sink to the right */ + if (h->stor_begin[head] < h->stor_begin[RIGHTCHILD(head)]) { + igraph_indheap_i_switch(h, head, RIGHTCHILD(head)); + igraph_indheap_i_sink(h, RIGHTCHILD(head)); + } + } +} + +/** + * \ingroup indheap + * \brief Switches two elements in a heap, don't call this function + * directly. + */ + +void igraph_indheap_i_switch(igraph_indheap_t* h, long int e1, long int e2) { + if (e1 != e2) { + igraph_real_t tmp = h->stor_begin[e1]; + h->stor_begin[e1] = h->stor_begin[e2]; + h->stor_begin[e2] = tmp; + + tmp = h->index_begin[e1]; + h->index_begin[e1] = h->index_begin[e2]; + h->index_begin[e2] = (long int) tmp; + } +} + + +/** + * \ingroup doubleindheap + * \brief Initializes an empty doubly indexed heap object (constructor). + * + * @return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +int igraph_d_indheap_init (igraph_d_indheap_t* h, long int alloc_size) { + if (alloc_size <= 0 ) { + alloc_size = 1; + } + h->stor_begin = igraph_Calloc(alloc_size, igraph_real_t); + if (h->stor_begin == 0) { + h->index_begin = 0; + h->index2_begin = 0; + IGRAPH_ERROR("d_indheap init failed", IGRAPH_ENOMEM); + } + h->stor_end = h->stor_begin + alloc_size; + h->end = h->stor_begin; + h->destroy = 1; + h->index_begin = igraph_Calloc(alloc_size, long int); + if (h->index_begin == 0) { + igraph_Free(h->stor_begin); + h->stor_begin = 0; + h->index2_begin = 0; + IGRAPH_ERROR("d_indheap init failed", IGRAPH_ENOMEM); + } + h->index2_begin = igraph_Calloc(alloc_size, long int); + if (h->index2_begin == 0) { + igraph_Free(h->stor_begin); + igraph_Free(h->index_begin); + h->stor_begin = 0; + h->index_begin = 0; + IGRAPH_ERROR("d_indheap init failed", IGRAPH_ENOMEM); + } + + return 0; +} + +/** + * \ingroup doubleindheap + * \brief Destroys an initialized doubly indexed heap object. + */ + +void igraph_d_indheap_destroy (igraph_d_indheap_t* h) { + assert(h != 0); + if (h->destroy) { + if (h->stor_begin != 0) { + igraph_Free(h->stor_begin); + h->stor_begin = 0; + } + if (h->index_begin != 0) { + igraph_Free(h->index_begin); + h->index_begin = 0; + } + if (h->index2_begin != 0) { + igraph_Free(h->index2_begin); + h->index2_begin = 0; + } + } +} + +/** + * \ingroup doubleindheap + * \brief Decides whether a heap is empty. + */ + +igraph_bool_t igraph_d_indheap_empty (igraph_d_indheap_t* h) { + assert(h != 0); + assert(h->stor_begin != 0); + return h->stor_begin == h->end; +} + +/** + * \ingroup doubleindheap + * \brief Adds an element to the heap. + */ + +int igraph_d_indheap_push (igraph_d_indheap_t* h, igraph_real_t elem, + long int idx, long int idx2) { + assert(h != 0); + assert(h->stor_begin != 0); + + /* full, allocate more storage */ + if (h->stor_end == h->end) { + long int new_size = igraph_d_indheap_size(h) * 2; + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(igraph_d_indheap_reserve(h, new_size)); + } + + *(h->end) = elem; + h->end += 1; + *(h->index_begin + igraph_d_indheap_size(h) - 1) = idx ; + *(h->index2_begin + igraph_d_indheap_size(h) - 1) = idx2 ; + + /* maintain d_indheap */ + igraph_d_indheap_i_shift_up(h, igraph_d_indheap_size(h) - 1); + + return 0; +} + +/** + * \ingroup doubleindheap + * \brief Returns the largest element in the heap. + */ + +igraph_real_t igraph_d_indheap_max (igraph_d_indheap_t* h) { + assert(h != NULL); + assert(h->stor_begin != NULL); + assert(h->stor_begin != h->end); + + return h->stor_begin[0]; +} + +/** + * \ingroup doubleindheap + * \brief Removes the largest element from the heap. + */ + +igraph_real_t igraph_d_indheap_delete_max(igraph_d_indheap_t* h) { + igraph_real_t tmp; + + assert(h != NULL); + assert(h->stor_begin != NULL); + + tmp = h->stor_begin[0]; + igraph_d_indheap_i_switch(h, 0, igraph_d_indheap_size(h) - 1); + h->end -= 1; + igraph_d_indheap_i_sink(h, 0); + + return tmp; +} + +/** + * \ingroup doubleindheap + * \brief Gives the number of elements in the heap. + */ + +long int igraph_d_indheap_size (igraph_d_indheap_t* h) { + assert(h != 0); + assert(h->stor_begin != 0); + return h->end - h->stor_begin; +} + +/** + * \ingroup doubleindheap + * \brief Allocates memory for a heap. + * + * @return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +int igraph_d_indheap_reserve (igraph_d_indheap_t* h, long int size) { + long int actual_size = igraph_d_indheap_size(h); + igraph_real_t *tmp1; + long int *tmp2, *tmp3; + assert(h != 0); + assert(h->stor_begin != 0); + + if (size <= actual_size) { + return 0; + } + + tmp1 = igraph_Calloc(size, igraph_real_t); + if (tmp1 == 0) { + IGRAPH_ERROR("d_indheap reserve failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmp1); + tmp2 = igraph_Calloc(size, long int); + if (tmp2 == 0) { + IGRAPH_ERROR("d_indheap reserve failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmp2); + tmp3 = igraph_Calloc(size, long int); + if (tmp3 == 0) { + IGRAPH_ERROR("d_indheap reserve failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmp3); + + memcpy(tmp1, h->stor_begin, (size_t) actual_size * sizeof(igraph_real_t)); + memcpy(tmp2, h->index_begin, (size_t) actual_size * sizeof(long int)); + memcpy(tmp3, h->index2_begin, (size_t) actual_size * sizeof(long int)); + igraph_Free(h->stor_begin); + igraph_Free(h->index_begin); + igraph_Free(h->index2_begin); + + h->stor_begin = tmp1; + h->stor_end = h->stor_begin + size; + h->end = h->stor_begin + actual_size; + h->index_begin = tmp2; + h->index2_begin = tmp3; + + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +/** + * \ingroup doubleindheap + * \brief Gives the indices of the maximal element in the heap. + */ + +void igraph_d_indheap_max_index(igraph_d_indheap_t *h, long int *idx, long int *idx2) { + assert(h != 0); + assert(h->stor_begin != 0); + (*idx) = h->index_begin[0]; + (*idx2) = h->index2_begin[0]; +} + +/** + * \ingroup doubleindheap + * \brief Builds the heap, don't call it directly. + */ + +void igraph_d_indheap_i_build(igraph_d_indheap_t* h, long int head) { + + long int size = igraph_d_indheap_size(h); + if (RIGHTCHILD(head) < size) { + /* both subtrees */ + igraph_d_indheap_i_build(h, LEFTCHILD(head) ); + igraph_d_indheap_i_build(h, RIGHTCHILD(head)); + igraph_d_indheap_i_sink(h, head); + } else if (LEFTCHILD(head) < size) { + /* only left */ + igraph_d_indheap_i_build(h, LEFTCHILD(head)); + igraph_d_indheap_i_sink(h, head); + } else { + /* none */ + } +} + +/** + * \ingroup doubleindheap + * \brief Moves an element up in the heap, don't call it directly. + */ + +void igraph_d_indheap_i_shift_up(igraph_d_indheap_t *h, long int elem) { + + if (elem == 0 || h->stor_begin[elem] < h->stor_begin[PARENT(elem)]) { + /* at the top */ + } else { + igraph_d_indheap_i_switch(h, elem, PARENT(elem)); + igraph_d_indheap_i_shift_up(h, PARENT(elem)); + } +} + +/** + * \ingroup doubleindheap + * \brief Moves an element down in the heap, don't call it directly. + */ + +void igraph_d_indheap_i_sink(igraph_d_indheap_t* h, long int head) { + + long int size = igraph_d_indheap_size(h); + if (LEFTCHILD(head) >= size) { + /* no subtrees */ + } else if (RIGHTCHILD(head) == size || + h->stor_begin[LEFTCHILD(head)] >= h->stor_begin[RIGHTCHILD(head)]) { + /* sink to the left if needed */ + if (h->stor_begin[head] < h->stor_begin[LEFTCHILD(head)]) { + igraph_d_indheap_i_switch(h, head, LEFTCHILD(head)); + igraph_d_indheap_i_sink(h, LEFTCHILD(head)); + } + } else { + /* sink to the right */ + if (h->stor_begin[head] < h->stor_begin[RIGHTCHILD(head)]) { + igraph_d_indheap_i_switch(h, head, RIGHTCHILD(head)); + igraph_d_indheap_i_sink(h, RIGHTCHILD(head)); + } + } +} + +/** + * \ingroup doubleindheap + * \brief Switches two elements in the heap, don't call it directly. + */ + +void igraph_d_indheap_i_switch(igraph_d_indheap_t* h, long int e1, long int e2) { + if (e1 != e2) { + long int tmpi; + igraph_real_t tmp = h->stor_begin[e1]; + h->stor_begin[e1] = h->stor_begin[e2]; + h->stor_begin[e2] = tmp; + + tmpi = h->index_begin[e1]; + h->index_begin[e1] = h->index_begin[e2]; + h->index_begin[e2] = tmpi; + + tmpi = h->index2_begin[e1]; + h->index2_begin[e1] = h->index2_begin[e2]; + h->index2_begin[e2] = tmpi; + } +} + +/*************************************************/ + +#undef PARENT +#undef LEFTCHILD +#undef RIGHTCHILD +#define PARENT(x) ((x)/2) +#define LEFTCHILD(x) ((x)*2+1) +#define RIGHTCHILD(x) ((x)*2) +#define INACTIVE IGRAPH_INFINITY +#define UNDEFINED 0.0 +#define INDEXINC 1 + +void igraph_i_cutheap_switch(igraph_i_cutheap_t *ch, + long int hidx1, long int hidx2) { + if (hidx1 != hidx2) { + long int idx1 = (long int) VECTOR(ch->index)[hidx1]; + long int idx2 = (long int) VECTOR(ch->index)[hidx2]; + + igraph_real_t tmp = VECTOR(ch->heap)[hidx1]; + VECTOR(ch->heap)[hidx1] = VECTOR(ch->heap)[hidx2]; + VECTOR(ch->heap)[hidx2] = tmp; + + VECTOR(ch->index)[hidx1] = idx2; + VECTOR(ch->index)[hidx2] = idx1; + + VECTOR(ch->hptr)[idx1] = hidx2 + INDEXINC; + VECTOR(ch->hptr)[idx2] = hidx1 + INDEXINC; + } +} + +void igraph_i_cutheap_sink(igraph_i_cutheap_t *ch, long int hidx) { + long int size = igraph_vector_size(&ch->heap); + if (LEFTCHILD(hidx) >= size) { + /* leaf node */ + } else if (RIGHTCHILD(hidx) == size || + VECTOR(ch->heap)[LEFTCHILD(hidx)] >= + VECTOR(ch->heap)[RIGHTCHILD(hidx)]) { + /* sink to the left if needed */ + if (VECTOR(ch->heap)[hidx] < VECTOR(ch->heap)[LEFTCHILD(hidx)]) { + igraph_i_cutheap_switch(ch, hidx, LEFTCHILD(hidx)); + igraph_i_cutheap_sink(ch, LEFTCHILD(hidx)); + } + } else { + /* sink to the right */ + if (VECTOR(ch->heap)[hidx] < VECTOR(ch->heap)[RIGHTCHILD(hidx)]) { + igraph_i_cutheap_switch(ch, hidx, RIGHTCHILD(hidx)); + igraph_i_cutheap_sink(ch, RIGHTCHILD(hidx)); + } + } +} + +void igraph_i_cutheap_shift_up(igraph_i_cutheap_t *ch, long int hidx) { + if (hidx == 0 || VECTOR(ch->heap)[hidx] < VECTOR(ch->heap)[PARENT(hidx)]) { + /* at the top */ + } else { + igraph_i_cutheap_switch(ch, hidx, PARENT(hidx)); + igraph_i_cutheap_shift_up(ch, PARENT(hidx)); + } +} + +int igraph_i_cutheap_init(igraph_i_cutheap_t *ch, igraph_integer_t nodes) { + ch->dnodes = nodes; + IGRAPH_VECTOR_INIT_FINALLY(&ch->heap, nodes); /* all zero */ + IGRAPH_CHECK(igraph_vector_init_seq(&ch->index, 0, nodes - 1)); + IGRAPH_FINALLY(igraph_vector_destroy, &ch->index); + IGRAPH_CHECK(igraph_vector_init_seq(&ch->hptr, INDEXINC, nodes + INDEXINC - 1)); + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +void igraph_i_cutheap_destroy(igraph_i_cutheap_t *ch) { + igraph_vector_destroy(&ch->hptr); + igraph_vector_destroy(&ch->index); + igraph_vector_destroy(&ch->heap); +} + +igraph_bool_t igraph_i_cutheap_empty(igraph_i_cutheap_t *ch) { + return igraph_vector_empty(&ch->heap); +} + +/* Number of active vertices */ + +igraph_integer_t igraph_i_cutheap_active_size(igraph_i_cutheap_t *ch) { + return (igraph_integer_t) igraph_vector_size(&ch->heap); +} + +/* Number of all (defined) vertices */ + +igraph_integer_t igraph_i_cutheap_size(igraph_i_cutheap_t *ch) { + return (igraph_integer_t) (ch->dnodes); +} + +igraph_real_t igraph_i_cutheap_maxvalue(igraph_i_cutheap_t *ch) { + return VECTOR(ch->heap)[0]; +} + +igraph_integer_t igraph_i_cutheap_popmax(igraph_i_cutheap_t *ch) { + long int size = igraph_vector_size(&ch->heap); + igraph_integer_t maxindex = (igraph_integer_t) VECTOR(ch->index)[0]; + /* put the last element to the top */ + igraph_i_cutheap_switch(ch, 0, size - 1); + /* remove the last element */ + VECTOR(ch->hptr)[(long int) igraph_vector_tail(&ch->index)] = INACTIVE; + igraph_vector_pop_back(&ch->heap); + igraph_vector_pop_back(&ch->index); + igraph_i_cutheap_sink(ch, 0); + + return maxindex; +} + +/* Update the value of an active vertex, if not active it will be ignored */ + +int igraph_i_cutheap_update(igraph_i_cutheap_t *ch, igraph_integer_t index, + igraph_real_t add) { + igraph_real_t hidx = VECTOR(ch->hptr)[(long int)index]; + if (hidx != INACTIVE && hidx != UNDEFINED) { + long int hidx2 = (long int) (hidx - INDEXINC); + /* printf("updating vertex %li, heap index %li\n", (long int) index, hidx2); */ + VECTOR(ch->heap)[hidx2] += add; + igraph_i_cutheap_sink(ch, hidx2); + igraph_i_cutheap_shift_up(ch, hidx2); + } + return 0; +} + +/* Reset the value of all vertices to zero and make them active */ + +int igraph_i_cutheap_reset_undefine(igraph_i_cutheap_t *ch, long int vertex) { + long int i, j, n = igraph_vector_size(&ch->hptr); + /* undefine */ + VECTOR(ch->hptr)[vertex] = UNDEFINED; + ch->dnodes -= 1; + + IGRAPH_CHECK(igraph_vector_resize(&ch->heap, ch->dnodes)); + igraph_vector_null(&ch->heap); + + IGRAPH_CHECK(igraph_vector_resize(&ch->index, ch->dnodes)); + + j = 0; + for (i = 0; i < n; i++) { + if (VECTOR(ch->hptr)[i] != UNDEFINED) { + VECTOR(ch->index)[j] = i; + VECTOR(ch->hptr)[i] = j + INDEXINC; + j++; + } + } + + return 0; +} + +/* -------------------------------------------------- */ +/* Two-way indexed heap */ +/* -------------------------------------------------- */ + +#undef PARENT +#undef LEFTCHILD +#undef RIGHTCHILD +#define PARENT(x) (((x)+1)/2-1) +#define LEFTCHILD(x) (((x)+1)*2-1) +#define RIGHTCHILD(x) (((x)+1)*2) + +/* This is a smart indexed heap. In addition to the "normal" indexed heap + it allows to access every element through its index in O(1) time. + In other words, for this heap the indexing operation is O(1), the + normal heap does this in O(n) time.... */ + +void igraph_i_2wheap_switch(igraph_2wheap_t *h, + long int e1, long int e2) { + if (e1 != e2) { + long int tmp1, tmp2; + igraph_real_t tmp3 = VECTOR(h->data)[e1]; + VECTOR(h->data)[e1] = VECTOR(h->data)[e2]; + VECTOR(h->data)[e2] = tmp3; + + tmp1 = VECTOR(h->index)[e1]; + tmp2 = VECTOR(h->index)[e2]; + + VECTOR(h->index2)[tmp1] = e2 + 2; + VECTOR(h->index2)[tmp2] = e1 + 2; + + VECTOR(h->index)[e1] = tmp2; + VECTOR(h->index)[e2] = tmp1; + } +} + +void igraph_i_2wheap_shift_up(igraph_2wheap_t *h, + long int elem) { + if (elem == 0 || VECTOR(h->data)[elem] < VECTOR(h->data)[PARENT(elem)]) { + /* at the top */ + } else { + igraph_i_2wheap_switch(h, elem, PARENT(elem)); + igraph_i_2wheap_shift_up(h, PARENT(elem)); + } +} + +void igraph_i_2wheap_sink(igraph_2wheap_t *h, + long int head) { + long int size = igraph_2wheap_size(h); + if (LEFTCHILD(head) >= size) { + /* no subtrees */ + } else if (RIGHTCHILD(head) == size || + VECTOR(h->data)[LEFTCHILD(head)] >= VECTOR(h->data)[RIGHTCHILD(head)]) { + /* sink to the left if needed */ + if (VECTOR(h->data)[head] < VECTOR(h->data)[LEFTCHILD(head)]) { + igraph_i_2wheap_switch(h, head, LEFTCHILD(head)); + igraph_i_2wheap_sink(h, LEFTCHILD(head)); + } + } else { + /* sink to the right */ + if (VECTOR(h->data)[head] < VECTOR(h->data)[RIGHTCHILD(head)]) { + igraph_i_2wheap_switch(h, head, RIGHTCHILD(head)); + igraph_i_2wheap_sink(h, RIGHTCHILD(head)); + } + } +} + +/* ------------------ */ +/* These are public */ +/* ------------------ */ + +int igraph_2wheap_init(igraph_2wheap_t *h, long int size) { + h->size = size; + /* We start with the biggest */ + IGRAPH_CHECK(igraph_vector_long_init(&h->index2, size)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &h->index2); + IGRAPH_VECTOR_INIT_FINALLY(&h->data, 0); + IGRAPH_CHECK(igraph_vector_long_init(&h->index, 0)); + /* IGRAPH_FINALLY(igraph_vector_long_destroy, &h->index); */ + + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +void igraph_2wheap_destroy(igraph_2wheap_t *h) { + igraph_vector_destroy(&h->data); + igraph_vector_long_destroy(&h->index); + igraph_vector_long_destroy(&h->index2); +} + +int igraph_2wheap_clear(igraph_2wheap_t *h) { + igraph_vector_clear(&h->data); + igraph_vector_long_clear(&h->index); + igraph_vector_long_null(&h->index2); + return 0; +} + +igraph_bool_t igraph_2wheap_empty(const igraph_2wheap_t *h) { + return igraph_vector_empty(&h->data); +} + +int igraph_2wheap_push_with_index(igraph_2wheap_t *h, + long int idx, igraph_real_t elem) { + + /* printf("-> %.2g [%li]\n", elem, idx); */ + + long int size = igraph_vector_size(&h->data); + IGRAPH_CHECK(igraph_vector_push_back(&h->data, elem)); + IGRAPH_CHECK(igraph_vector_long_push_back(&h->index, idx)); + VECTOR(h->index2)[idx] = size + 2; + + /* maintain heap */ + igraph_i_2wheap_shift_up(h, size); + return 0; +} + +long int igraph_2wheap_size(const igraph_2wheap_t *h) { + return igraph_vector_size(&h->data); +} + +long int igraph_2wheap_max_size(const igraph_2wheap_t *h) { + return h->size; +} + +igraph_real_t igraph_2wheap_max(const igraph_2wheap_t *h) { + return VECTOR(h->data)[0]; +} + +long int igraph_2wheap_max_index(const igraph_2wheap_t *h) { + return VECTOR(h->index)[0]; +} + +igraph_bool_t igraph_2wheap_has_elem(const igraph_2wheap_t *h, long int idx) { + return VECTOR(h->index2)[idx] != 0; +} + +igraph_bool_t igraph_2wheap_has_active(const igraph_2wheap_t *h, long int idx) { + return VECTOR(h->index2)[idx] > 1; +} + +igraph_real_t igraph_2wheap_get(const igraph_2wheap_t *h, long int idx) { + long int i = VECTOR(h->index2)[idx] - 2; + return VECTOR(h->data)[i]; +} + +igraph_real_t igraph_2wheap_delete_max(igraph_2wheap_t *h) { + + igraph_real_t tmp = VECTOR(h->data)[0]; + long int tmpidx = VECTOR(h->index)[0]; + igraph_i_2wheap_switch(h, 0, igraph_2wheap_size(h) - 1); + igraph_vector_pop_back(&h->data); + igraph_vector_long_pop_back(&h->index); + VECTOR(h->index2)[tmpidx] = 0; + igraph_i_2wheap_sink(h, 0); + + /* printf("<-max %.2g\n", tmp); */ + + return tmp; +} + +igraph_real_t igraph_2wheap_deactivate_max(igraph_2wheap_t *h) { + + igraph_real_t tmp = VECTOR(h->data)[0]; + long int tmpidx = VECTOR(h->index)[0]; + igraph_i_2wheap_switch(h, 0, igraph_2wheap_size(h) - 1); + igraph_vector_pop_back(&h->data); + igraph_vector_long_pop_back(&h->index); + VECTOR(h->index2)[tmpidx] = 1; + igraph_i_2wheap_sink(h, 0); + + return tmp; +} + +igraph_real_t igraph_2wheap_delete_max_index(igraph_2wheap_t *h, long int *idx) { + + igraph_real_t tmp = VECTOR(h->data)[0]; + long int tmpidx = VECTOR(h->index)[0]; + igraph_i_2wheap_switch(h, 0, igraph_2wheap_size(h) - 1); + igraph_vector_pop_back(&h->data); + igraph_vector_long_pop_back(&h->index); + VECTOR(h->index2)[tmpidx] = 0; + igraph_i_2wheap_sink(h, 0); + + if (idx) { + *idx = tmpidx; + } + return tmp; +} + +int igraph_2wheap_modify(igraph_2wheap_t *h, long int idx, igraph_real_t elem) { + + long int pos = VECTOR(h->index2)[idx] - 2; + + /* printf("-- %.2g -> %.2g\n", VECTOR(h->data)[pos], elem); */ + + VECTOR(h->data)[pos] = elem; + igraph_i_2wheap_sink(h, pos); + igraph_i_2wheap_shift_up(h, pos); + + return 0; +} + +/* Check that the heap is in a consistent state */ + +int igraph_2wheap_check(igraph_2wheap_t *h) { + long int size = igraph_2wheap_size(h); + long int i; + igraph_bool_t error = 0; + + /* Check the heap property */ + for (i = 0; i < size; i++) { + if (LEFTCHILD(i) >= size) { + break; + } + if (VECTOR(h->data)[LEFTCHILD(i)] > VECTOR(h->data)[i]) { + error = 1; break; + } + if (RIGHTCHILD(i) >= size) { + break; + } + if (VECTOR(h->data)[RIGHTCHILD(i)] > VECTOR(h->data)[i]) { + error = 1; break; + } + } + + if (error) { + IGRAPH_ERROR("Inconsistent heap", IGRAPH_EINTERNAL); + } + + return 0; +} diff --git a/src/heap.pmt b/src/heap.pmt new file mode 100644 index 0000000..509f9ec --- /dev/null +++ b/src/heap.pmt @@ -0,0 +1,350 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_memory.h" +#include "igraph_error.h" +#include "config.h" + +#include +#include /* memcpy & co. */ +#include + +#define PARENT(x) (((x)+1)/2-1) +#define LEFTCHILD(x) (((x)+1)*2-1) +#define RIGHTCHILD(x) (((x)+1)*2) + +/** + * \ingroup heap + * \function igraph_heap_init + * \brief Initializes an empty heap object. + * + * Creates an empty heap, but allocates size for some elements. + * \param h Pointer to an uninitialized heap object. + * \param alloc_size Number of elements to allocate memory for. + * \return Error code. + * + * Time complexity: O(\p alloc_size), assuming memory allocation is a + * linear operation. + */ + +int FUNCTION(igraph_heap, init)(TYPE(igraph_heap)* h, long int alloc_size) { + if (alloc_size <= 0 ) { + alloc_size = 1; + } + h->stor_begin = igraph_Calloc(alloc_size, BASE); + if (h->stor_begin == 0) { + IGRAPH_ERROR("heap init failed", IGRAPH_ENOMEM); + } + h->stor_end = h->stor_begin + alloc_size; + h->end = h->stor_begin; + h->destroy = 1; + + return 0; +} + +/** + * \ingroup heap + * \function igraph_heap_init_array + * \brief Build a heap from an array. + * + * Initializes a heap object from an array, the heap is also + * built of course (constructor). + * \param h Pointer to an uninitialized heap object. + * \param data Pointer to an array of base data type. + * \param len The length of the array at \p data. + * \return Error code. + * + * Time complexity: O(n), the number of elements in the heap. + */ + +int FUNCTION(igraph_heap, init_array)(TYPE(igraph_heap) *h, BASE* data, long int len) { + h->stor_begin = igraph_Calloc(len, BASE); + if (h->stor_begin == 0) { + IGRAPH_ERROR("heap init from array failed", IGRAPH_ENOMEM); + } + h->stor_end = h->stor_begin + len; + h->end = h->stor_end; + h->destroy = 1; + + memcpy(h->stor_begin, data, (size_t) len * sizeof(igraph_real_t)); + + FUNCTION(igraph_heap, i_build) (h->stor_begin, h->end - h->stor_begin, 0); + + return 0; +} + +/** + * \ingroup heap + * \function igraph_heap_destroy + * \brief Destroys an initialized heap object. + * + * \param h The heap object. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_heap, destroy)(TYPE(igraph_heap)* h) { + if (h->destroy) { + if (h->stor_begin != 0) { + igraph_Free(h->stor_begin); + h->stor_begin = 0; + } + } +} + +/** + * \ingroup heap + * \function igraph_heap_empty + * \brief Decides whether a heap object is empty. + * + * \param h The heap object. + * \return \c TRUE if the heap is empty, \c FALSE otherwise. + * + * TIme complexity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_heap, empty)(TYPE(igraph_heap)* h) { + assert(h != NULL); + assert(h->stor_begin != NULL); + return h->stor_begin == h->end; +} + +/** + * \ingroup heap + * \function igraph_heap_push + * \brief Add an element. + * + * Adds an element to the heap. + * \param h The heap object. + * \param elem The element to add. + * \return Error code. + * + * Time complexity: O(log n), n is the number of elements in the + * heap if no reallocation is needed, O(n) otherwise. It is ensured + * that n push operations are performed in O(n log n) time. + */ + +int FUNCTION(igraph_heap, push)(TYPE(igraph_heap)* h, BASE elem) { + assert(h != NULL); + assert(h->stor_begin != NULL); + + /* full, allocate more storage */ + if (h->stor_end == h->end) { + long int new_size = FUNCTION(igraph_heap, size)(h) * 2; + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(FUNCTION(igraph_heap, reserve)(h, new_size)); + } + + *(h->end) = elem; + h->end += 1; + + /* maintain heap */ + FUNCTION(igraph_heap, i_shift_up)(h->stor_begin, FUNCTION(igraph_heap, size)(h), + FUNCTION(igraph_heap, size)(h) - 1); + + return 0; +} + +/** + * \ingroup heap + * \function igraph_heap_top + * \brief Top element. + * + * For maximum heaps this is the largest, for minimum heaps the + * smallest element of the heap. + * \param h The heap object. + * \return The top element. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_heap, top)(TYPE(igraph_heap)* h) { + assert(h != NULL); + assert(h->stor_begin != NULL); + assert(h->stor_begin != h->end); + + return h->stor_begin[0]; +} + +/** + * \ingroup heap + * \function igraph_heap_delete_top + * \brief Return and removes the top element + * + * Removes and returns the top element of the heap. For maximum heaps + * this is the largest, for minimum heaps the smallest element. + * \param h The heap object. + * \return The top element. + * + * Time complexity: O(log n), n is the number of elements in the + * heap. + */ + +BASE FUNCTION(igraph_heap, delete_top)(TYPE(igraph_heap)* h) { + BASE tmp; + + assert(h != NULL); + assert(h->stor_begin != NULL); + + tmp = h->stor_begin[0]; + FUNCTION(igraph_heap, i_switch)(h->stor_begin, 0, FUNCTION(igraph_heap, size)(h) - 1); + h->end -= 1; + FUNCTION(igraph_heap, i_sink)(h->stor_begin, h->end - h->stor_begin, 0); + + return tmp; +} + +/** + * \ingroup heap + * \function igraph_heap_size + * \brief Number of elements + * + * Gives the number of elements in a heap. + * \param h The heap object. + * \return The number of elements in the heap. + * + * Time complexity: O(1). + */ + +long int FUNCTION(igraph_heap, size)(TYPE(igraph_heap)* h) { + assert(h != NULL); + assert(h->stor_begin != NULL); + return h->end - h->stor_begin; +} + +/** + * \ingroup heap + * \function igraph_heap_reserve + * \brief Allocate more memory + * + * Allocates memory for future use. The size of the heap is + * unchanged. If the heap is larger than the \p size parameter then + * nothing happens. + * \param h The heap object. + * \param size The number of elements to allocate memory for. + * \return Error code. + * + * Time complexity: O(\p size) if \p size is larger than the current + * number of elements. O(1) otherwise. + */ + +int FUNCTION(igraph_heap, reserve)(TYPE(igraph_heap)* h, long int size) { + long int actual_size = FUNCTION(igraph_heap, size)(h); + BASE *tmp; + assert(h != NULL); + assert(h->stor_begin != NULL); + + if (size <= actual_size) { + return 0; + } + + tmp = igraph_Realloc(h->stor_begin, (size_t) size, BASE); + if (tmp == 0) { + IGRAPH_ERROR("heap reserve failed", IGRAPH_ENOMEM); + } + h->stor_begin = tmp; + h->stor_end = h->stor_begin + size; + h->end = h->stor_begin + actual_size; + + return 0; +} + +/** + * \ingroup heap + * \brief Build a heap, this should not be called directly. + */ + +void FUNCTION(igraph_heap, i_build)(BASE* arr, + long int size, long int head) { + + if (RIGHTCHILD(head) < size) { + /* both subtrees */ + FUNCTION(igraph_heap, i_build)(arr, size, LEFTCHILD(head) ); + FUNCTION(igraph_heap, i_build)(arr, size, RIGHTCHILD(head)); + FUNCTION(igraph_heap, i_sink)(arr, size, head); + } else if (LEFTCHILD(head) < size) { + /* only left */ + FUNCTION(igraph_heap, i_build)(arr, size, LEFTCHILD(head)); + FUNCTION(igraph_heap, i_sink)(arr, size, head); + } else { + /* none */ + } +} + +/** + * \ingroup heap + * \brief Shift an element upwards in a heap, this should not be + * called directly. + */ + +void FUNCTION(igraph_heap, i_shift_up)(BASE* arr, long int size, long int elem) { + + if (elem == 0 || arr[elem] HEAPLESS arr[PARENT(elem)]) { + /* at the top */ + } else { + FUNCTION(igraph_heap, i_switch)(arr, elem, PARENT(elem)); + FUNCTION(igraph_heap, i_shift_up)(arr, size, PARENT(elem)); + } +} + +/** + * \ingroup heap + * \brief Moves an element down in a heap, this function should not be + * called directly. + */ + +void FUNCTION(igraph_heap, i_sink)(BASE* arr, long int size, long int head) { + + if (LEFTCHILD(head) >= size) { + /* no subtrees */ + } else if (RIGHTCHILD(head) == size || + arr[LEFTCHILD(head)] HEAPMOREEQ arr[RIGHTCHILD(head)]) { + /* sink to the left if needed */ + if (arr[head] HEAPLESS arr[LEFTCHILD(head)]) { + FUNCTION(igraph_heap, i_switch)(arr, head, LEFTCHILD(head)); + FUNCTION(igraph_heap, i_sink)(arr, size, LEFTCHILD(head)); + } + } else { + /* sink to the right */ + if (arr[head] HEAPLESS arr[RIGHTCHILD(head)]) { + FUNCTION(igraph_heap, i_switch)(arr, head, RIGHTCHILD(head)); + FUNCTION(igraph_heap, i_sink)(arr, size, RIGHTCHILD(head)); + } + } +} + +/** + * \ingroup heap + * \brief Switches two elements in a heap, this function should not be + * called directly. + */ + +void FUNCTION(igraph_heap, i_switch)(BASE* arr, long int e1, long int e2) { + if (e1 != e2) { + BASE tmp = arr[e1]; + arr[e1] = arr[e2]; + arr[e2] = tmp; + } +} diff --git a/src/hrg_dendro.h b/src/hrg_dendro.h new file mode 100644 index 0000000..ecd8e91 --- /dev/null +++ b/src/hrg_dendro.h @@ -0,0 +1,313 @@ +/* -*- mode: C++ -*- */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +// **************************************************************************************************** +// *** COPYRIGHT NOTICE ******************************************************************************* +// dendro_eq.h - hierarchical random graph (hrg) data structure +// Copyright (C) 2006-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// **************************************************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science AND Santa Fe Institute +// Created : 19 April 2006 +// Modified : 19 May 2007 +// : 19 May 2008 (cleaned up for public consumption) +// +// **************************************************************************************************** +// +// Maximum likelihood dendrogram data structure. This is the heart of the HRG algorithm: all +// manipulations are done here and all data is stored here. The data structure uses the separate +// graph data structure to store the basic adjacency information (in a dangerously mutable way). +// +// Note: This version (dendro_eq.h) differs from other versions because it includes methods for +// doing the consensus dendrogram calculation. +// +// **************************************************************************************************** + +#ifndef IGRAPH_HRG_DENDRO +#define IGRAPH_HRG_DENDRO + +#include "hrg_graph.h" +#include "hrg_rbtree.h" +#include "hrg_splittree_eq.h" + +#include "igraph_hrg.h" + +#include +#include + +using namespace fitHRG; + +namespace fitHRG { + +// *********************************************************************** +// ******** Basic Structures ********************************************* + +#ifndef IGRAPH_HRG_LIST +#define IGRAPH_HRG_LIST + +class list { +public: + int x; // stored elementd in linked-list + list* next; // pointer to next elementd + list::list(): x(-1), next(0) { } + list::~list() { } +}; +#endif + +enum {DENDRO, GRAPH, LEFT, RIGHT}; +struct block { + double x; + int y; +}; +struct ipair { + int x; + int y; + short int t; + std::string sp; +}; +struct child { + int index; + short int type; + child* next; +}; + +// *********************************************************************** +// ******** Cnode Class ************************************************** + +#ifndef IGRAPH_HRG_CNODE +#define IGRAPH_HRG_CNODE +class cnode { +public: + int index; // array index of this node + int degree; // number of children in list + int parent; // index of parent node + double weight; // sampled posterior weight + child* children; // list of children (and their types) + child* lastChild; // pointer to last child in list + cnode(): index(-1), degree(0), parent(-1), weight(0.0), + children(0), lastChild(0) { } + ~cnode() { + child *curr, *prev; + curr = children; + while (curr != NULL) { + prev = curr; + curr = curr->next; + delete prev; + prev = NULL; + } + lastChild = NULL; + } +}; +#endif + +// *********************************************************************** +// ******** Split Class ************************************************** + +class split { +public: + std::string s; // partition assignment of leaf vertices + split(): s("") { } + ~split() { } + void initializeSplit(const int n) { + s = ""; + for (int i = 0; i < n; i++) { + s += "-"; + } + } + bool checkSplit() { + if (s.empty() || s.find("-", 0) != std::string::npos) { + return false; + } else { + return true; + } + } +}; + +// *********************************************************************** +// ******** Internal Edge Class ****************************************** +// The usefulness of this data structure is to provide an easy to way +// maintain the set of internal edges, and the corresponding splits, +// in the dendrogram D. It allows for the selection of a random +// internal edge in O(1) time, and it takes O(1) time to update its +// structure given an internal move. This structure does not provide +// any means to directly manipulate the splits, but does allow them to +// be replaced. A split has the form "int.int...int#int.int...int", +// where all ints on the left side of the # are in the left partition +// and all ints on the right side of the # marker are in the right +// partition defined by the split. + +class interns { +private: + ipair* edgelist; // list of internal edges represented + std::string* splitlist; // split representation of the internal edges + int** indexLUT; // table of indices of internal edges in edgelist + int q; // number of internal edges + int count; // (for adding edges) edgelist index of new edge to add +public: + interns(const int); + ~interns(); + + // add an internal edge, O(1) + bool addEdge(const int, const int, const short int); + // returns the ith edge of edgelist, O(1) + ipair* getEdge(const int); + // returns a uniformly random internal edge, O(1) + ipair* getRandomEdge(); + // returns the ith split of the splitlist, O(1) + std::string getSplit(const int); + // replace an existing split, O(1) + bool replaceSplit(const int, const std::string); + // swaps two edges, O(1) + bool swapEdges(const int, const int, const short int, const int, + const int, const short int); +}; + +// *********************************************************************** +// ******** Tree elementd Class ****************************************** + +class elementd { +public: + short int type; // either DENDRO or GRAPH + double logL; // log-likelihood contribution of this internal node + double p; // probability p_i that an edge exists between L and + // R subtrees + int e; // number of edges between L and R subtrees + int n; // number of leafs in subtree rooted here + int label; // subtree label: smallest leaf index + int index; // index in containing array + + elementd *M; // pointer to parent node + elementd *L; // pointer for L subtree + elementd *R; // pointer for R subtree + + elementd(): type(DENDRO), logL(0.0), p(0.0), e(0), n(0), + label(-1), index(-1), M(0), L(0), R(0) { } + ~elementd() { } +}; + +// *********************************************************************** +// ******** Dendrogram Class ********************************************* + +class dendro { +private: + elementd* root; // root of the dendrogram + elementd* internal; // array of n-1 internal vertices (the dendrogram D) + elementd* leaf; // array of n leaf vertices (the graph G) + int n; // number of leaf vertices to allocate + interns* d; // list of internal edges of dendrogram D + splittree* splithist; // histogram of cumulative split weights + list** paths; // array of path-lists from root to leaf + double L; // log-likelihood of graph G given dendrogram D + rbtree subtreeL, subtreeR; // trees for computeEdgeCount() function + cnode* ctree; // (consensus tree) array of internal tree nodes + int* cancestor; // (consensus tree) oldest ancetor's index for + // each leaf + + // insert node i according to binary search property + void binarySearchInsert(elementd*, elementd*); + // return path to root from leaf + list* binarySearchFind(const double); + // build split for this internal edge + std::string buildSplit(elementd*); + // compute number of edges between two internal subtrees + int computeEdgeCount(const int, const short int, const int, + const short int); + // (consensus tree) counts children + int countChildren(const std::string); + // find internal node of D that is common ancestor of i,j + elementd* findCommonAncestor(list**, const int, const int); + // return reverse of path to leaf from root + list* reversePathToRoot(const int); +// quicksort functions + void QsortMain(block*, int, int); + int QsortPartition(block*, int, int, int); + +public: + // underlying G (dangerously accessible) + graph* g; + + // constructor / destructor + dendro(); ~dendro(); + // build dendrogram from g + void buildDendrogram(); + // delete dendrograph in prep for importDendrogramStructure + void clearDendrograph(); + // read dendrogram structure from HRG structure + bool importDendrogramStructure(const igraph_hrg_t *hrg); + // (consensus tree) delete splits with less than 0.5 weight + void cullSplitHist(); + // return size of consensus split + int getConsensusSize(); + // return split tree with consensus splits + splittree* getConsensusSplits(); + // return likelihood of G given D + double getLikelihood(); + // store splits in this splittree + void getSplitList(splittree*); + // return total weight of splittree + double getSplitTotalWeight(); + // make random G from D + void makeRandomGraph(); + // make single MCMC move + bool monteCarloMove(double&, bool&, const double); + // record consensus tree from splithist + void recordConsensusTree(igraph_vector_t *parents, + igraph_vector_t *weights); + // record D structure + void recordDendrogramStructure(igraph_hrg_t *hrg); + // record G structure to igraph graph + void recordGraphStructure(igraph_t *graph); + // force refresh of log-likelihood value + void refreshLikelihood(); + // sample dendrogram edge likelihoods and update edge histograms + void sampleAdjacencyLikelihoods(); + // reset the dendrograph structures + void resetDendrograph(); + // sample dendrogram's splits and update the split histogram + bool sampleSplitLikelihoods(int&); + // reset splits histogram + void resetAllSplits(); +}; + +} // namespace fitHRG + +#endif diff --git a/src/hrg_graph.h b/src/hrg_graph.h new file mode 100644 index 0000000..cf7ab59 --- /dev/null +++ b/src/hrg_graph.h @@ -0,0 +1,167 @@ +/* -*- mode: C++ -*- */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +// **************************************************************************************************** +// *** COPYRIGHT NOTICE ******************************************************************************* +// graph.h - graph data structure for hierarchical random graphs +// Copyright (C) 2005-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// **************************************************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science AND Santa Fe Institute +// Created : 8 November 2005 +// Modified : 23 December 2007 (cleaned up for public consumption) +// +// **************************************************************************************************** +// +// Graph data structure for hierarchical random graphs. The basic structure is an adjacency list of +// edges; however, many additional pieces of metadata are stored as well. Each node stores its +// external name, its degree and (if assigned) its group index. +// +// **************************************************************************************************** + +#ifndef IGRAPH_HRG_GRAPH +#define IGRAPH_HRG_GRAPH + +#include "hrg_rbtree.h" + +#include +#include +#include + +namespace fitHRG { + +// ******** Basic Structures ********************************************* + +#ifndef IGRAPH_HRG_EDGE +#define IGRAPH_HRG_EDGE +class edge { +public: + int x; // stored integer value (edge terminator) + double* h; // (histogram) weights of edge existence + double total_weight; // (histogram) total weight observed + int obs_count; // number of observations in histogram + edge* next; // pointer to next elementd + edge(): x(-1), h(0), total_weight(0.0), obs_count(0), next(0) { } + ~edge() { + if (h != NULL) { + delete [] h; + } + h = NULL; + } +}; +#endif + +#ifndef IGRAPH_HRG_VERT +#define IGRAPH_HRG_VERT +class vert { +public: + std::string name; // (external) name of vertex + int degree; // degree of this vertex + + vert(): name(""), degree(0) { } + ~vert() { } +}; +#endif + +// ******** Graph Class with Edge Statistics ***************************** + +class graph { +public: + graph(const int, bool predict = false); + ~graph(); + + // add (i,j) to graph + bool addLink(const int, const int); + // add weight to (i,j)'s histogram + bool addAdjacencyObs(const int, const int, const double, const double); + // add to obs_count and total_weight + void addAdjacencyEnd(); + // true if (i,j) is already in graph + bool doesLinkExist(const int, const int); + // returns degree of vertex i + int getDegree(const int); + // returns name of vertex i + std::string getName(const int); + // returns edge list of vertex i + edge* getNeighborList(const int); + // return ptr to histogram of edge (i,j) + double* getAdjacencyHist(const int, const int); + // return average value of adjacency A(i,j) + double getAdjacencyAverage(const int, const int); + // returns bin_resolution + double getBinResolution(); + // returns num_bins + int getNumBins(); + // returns m + int numLinks(); + // returns n + int numNodes(); + // returns total_weight + double getTotalWeight(); + // reset edge (i,j)'s histogram + void resetAdjacencyHistogram(const int, const int); + // reset all edge histograms + void resetAllAdjacencies(); + // clear all links from graph + void resetLinks(); + // allocate edge histograms + void setAdjacencyHistograms(const int); + // set name of vertex i + bool setName(const int, const std::string); + +private: + bool predict; // do we need prediction? + vert* nodes; // list of nodes + edge** nodeLink; // linked list of neighbors to vertex + edge** nodeLinkTail; // pointers to tail of neighbor list + double*** A; // stochastic adjacency matrix for this graph + int obs_count; // number of observations in A + double total_weight; // total weight added to A + int n; // number of vertices + int m; // number of directed edges + int num_bins; // number of bins in edge histograms + double bin_resolution; // width of histogram bin +}; + +} // namespace fitHRG + +#endif diff --git a/src/hrg_graph_simp.h b/src/hrg_graph_simp.h new file mode 100644 index 0000000..3d1e3fa --- /dev/null +++ b/src/hrg_graph_simp.h @@ -0,0 +1,160 @@ +/* -*- mode: C++ -*- */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +// **************************************************************************************************** +// *** COPYRIGHT NOTICE ******************************************************************************* +// graph_simp.h - graph data structure +// Copyright (C) 2006-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// **************************************************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science AND Santa Fe Institute +// Created : 21 June 2006 +// Modified : 23 December 2007 (cleaned up for public consumption) +// +// ************************************************************************ +// +// Simple graph data structure. The basic structure is an adjacency +// list of edges, along with degree information for the vertices. +// +// ************************************************************************ + +#ifndef IGRAPH_HRG_SIMPLEGRAPH +#define IGRAPH_HRG_SIMPLEGRAPH + +#include "hrg_rbtree.h" +#include "hrg_dendro.h" + +#include +#include + +namespace fitHRG { + +// ******** Basic Structures ********************************************* + +#ifndef IGRAPH_HRG_SIMPLEEDGE +#define IGRAPH_HRG_SIMPLEEDGE +class simpleEdge { +public: + int x; // index of edge terminator + simpleEdge* next; // pointer to next elementd + + simpleEdge(): x(-1), next(0) { } + ~simpleEdge() { } +}; +#endif + +#ifndef IGRAPH_HRG_SIMPLEVERT +#define IGRAPH_HRG_SIMPLEVERT +class simpleVert { +public: + std::string name; // (external) name of vertex + int degree; // degree of this vertex + int group_true; // index of vertex's true group + + simpleVert(): name(""), degree(0), group_true(-1) { } + ~simpleVert() { } +}; +#endif + +#ifndef IGRAPH_HRG_TWOEDGE +#define IGRAPH_HRG_TWOEDGE +class twoEdge { +public: + int o; // index of edge originator + int x; // index of edge terminator + + twoEdge(): o(-1), x(-1) { } + ~twoEdge() { } +}; +#endif + +// ******** Graph Class with Edge Statistics ***************************** + +class simpleGraph { +public: + simpleGraph(const int); ~simpleGraph(); + + // add group label to vertex i + bool addGroup(const int, const int); + // add (i,j) to graph + bool addLink(const int, const int); + // true if (i,j) is already in graph + bool doesLinkExist(const int, const int); + // returns A(i,j) + double getAdjacency(const int, const int); + // returns degree of vertex i + int getDegree(const int); + // returns group label of vertex i + int getGroupLabel(const int); + // returns name of vertex i + std::string getName(const int); + // returns edge list of vertex i + simpleEdge* getNeighborList(const int); + // return pointer to a node + simpleVert* getNode(const int); + // returns num_groups + int getNumGroups(); + // returns m + int getNumLinks(); + // returns n + int getNumNodes(); + // set name of vertex i + bool setName(const int, const std::string); + +private: + simpleVert* nodes; // list of nodes + simpleEdge** nodeLink; // linked list of neighbors to vertex + simpleEdge** nodeLinkTail; // pointers to tail of neighbor list + double** A; // adjacency matrix for this graph + twoEdge* E; // list of all edges (array) + int n; // number of vertices + int m; // number of directed edges + int num_groups; // number of bins in node histograms + + // quicksort functions + void QsortMain(block*, int, int); + int QsortPartition(block*, int, int, int); +}; + +} // namespace fitHRG + +#endif diff --git a/src/hrg_rbtree.h b/src/hrg_rbtree.h new file mode 100644 index 0000000..85f4ee7 --- /dev/null +++ b/src/hrg_rbtree.h @@ -0,0 +1,160 @@ +/* -*- mode: C++ -*- */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +// **************************************************************************************************** +// *** COPYRIGHT NOTICE ******************************************************************************* +// rbtree - red-black tree (self-balancing binary tree data structure) +// Copyright (C) 2004 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// **************************************************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science AND Santa Fe Institute +// Created : Spring 2004 +// Modified : many, many times +// +// **************************************************************************************************** + +#ifndef IGRAPH_HRG_RBTREE +#define IGRAPH_HRG_RBTREE + +namespace fitHRG { + +// ******** Basic Structures ********************************************* + +#ifndef IGRAPH_HRG_LIST +#define IGRAPH_HRG_LIST + +class list { +public: + int x; // stored elementd in linked-list + list* next; // pointer to next elementd + list(): x(-1), next(0) { } + ~list() { } +}; +#endif + +class keyValuePair { +public: + int x; // elementrb key (int) + int y; // stored value (int) + keyValuePair* next; // linked-list pointer + keyValuePair(): x(-1), y(-1), next(0) { } + ~keyValuePair() { } +}; + +// ******** Tree elementrb Class ***************************************** + +class elementrb { +public: + int key; // search key (int) + int value; // stored value (int) + + bool color; // F: BLACK, T: RED + short int mark; // marker + + elementrb *parent; // pointer to parent node + elementrb *left; // pointer for left subtree + elementrb *right; // pointer for right subtree + + elementrb(): key(-1), value(-1), color(false), mark(0), parent(0), + left(0), right(0) { } + ~elementrb() { } +}; + +// ******** Red-Black Tree Class ***************************************** +// This vector implementation is a red-black balanced binary tree data +// structure. It provides find a stored elementrb in time O(log n), +// find the maximum elementrb in time O(1), delete an elementrb in +// time O(log n), and insert an elementrb in time O(log n). +// +// Note that the key=0 is assumed to be a special value, and thus you +// cannot insert such an item. Beware of this limitation. + +class rbtree { +private: + elementrb* root; // binary tree root + elementrb* leaf; // all leaf nodes + int support; // number of nodes in the tree + + void rotateLeft(elementrb *x); // left-rotation operator + void rotateRight(elementrb *y); // right-rotation operator + void insertCleanup(elementrb *z); // house-keeping after insertion + void deleteCleanup(elementrb *x); // house-keeping after deletion + keyValuePair* returnSubtreeAsList(elementrb *z, keyValuePair *head); + void deleteSubTree(elementrb *z); // delete subtree rooted at z + elementrb* returnMinKey(elementrb *z); // returns minimum of subtree + // rooted at z + elementrb* returnSuccessor(elementrb *z); // returns successor of z's key + +public: + rbtree(); ~rbtree(); // default constructor/destructor + + // returns value associated with searchKey + int returnValue(const int searchKey); + // returns T if searchKey found, and points foundNode at the + // corresponding node + elementrb* findItem(const int searchKey); + // insert a new key with stored value + void insertItem(int newKey, int newValue); + // selete a node with given key + void deleteItem(int killKey); + // replace value of a node with given key + void replaceItem(int key, int newValue); + // increment the value of the given key + void incrementValue(int key); + // delete the entire tree + void deleteTree(); + // return array of keys in tree + int* returnArrayOfKeys(); + // return list of keys in tree + list* returnListOfKeys(); + // return the tree as a list of keyValuePairs + keyValuePair* returnTreeAsList(); + // returns the maximum key in the tree + keyValuePair returnMaxKey(); + // returns the minimum key in the tree + keyValuePair returnMinKey(); + // returns number of items in tree + int returnNodecount(); +}; + +} +#endif diff --git a/src/hrg_splittree_eq.h b/src/hrg_splittree_eq.h new file mode 100644 index 0000000..8acab77 --- /dev/null +++ b/src/hrg_splittree_eq.h @@ -0,0 +1,183 @@ +/* -*- mode: C++ -*- */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +// **************************************************************************************************** +// *** COPYRIGHT NOTICE ******************************************************************************* +// splittree_eq.h - a binary search tree data structure for storing dendrogram split frequencies +// Copyright (C) 2006-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// **************************************************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science AND Santa Fe Institute +// Created : 19 April 2006 +// Modified : 19 May 2007 +// : 20 May 2008 (cleaned up for public consumption) +// +// *********************************************************************** +// +// Data structure for storing the split frequences in the sampled +// dendrograms. Data is stored efficiently as a red-black binary +// search tree (this is a modified version of the rbtree.h file). +// +// *********************************************************************** + +#ifndef IGRAPH_HRG_SPLITTREE +#define IGRAPH_HRG_SPLITTREE + +#include + +namespace fitHRG { + +// ******** Basic Structures ********************************************* + +#ifndef IGRAPH_HRG_SLIST +#define IGRAPH_HRG_SLIST +class slist { +public: + std::string x; // stored elementd in linked-list + slist* next; // pointer to next elementd + slist(): x(""), next(0) { } + ~slist() { } +}; +#endif + +class keyValuePairSplit { +public: + std::string x; // elementsp split (string) + double y; // stored weight (double) + int c; // stored count (int) + keyValuePairSplit* next; // linked-list pointer + keyValuePairSplit(): x(""), y(0.0), c(0), next(0) { } + ~keyValuePairSplit() { } +}; + +// ******** Tree elementsp Class ***************************************** + +class elementsp { +public: + std::string split; // split represented as a string + double weight; // total weight of this split + int count; // number of observations of this split + + bool color; // F: BLACK, T: RED + short int mark; // marker + + elementsp *parent; // pointer to parent node + elementsp *left; // pointer for left subtree + elementsp *right; // pointer for right subtree + + elementsp(): split(""), weight(0.0), count(0), color(false), mark(0), + parent(0), left(0), right(0) { } + ~elementsp() { } +}; + +// ******** Red-Black Tree Class ***************************************** +// This vector implementation is a red-black balanced binary tree data +// structure. It provides find a stored elementsp in time O(log n), +// find the maximum elementsp in time O(1), delete an elementsp in +// time O(log n), and insert an elementsp in time O(log n). +// +// Note that the split="" is assumed to be a special value, and thus +// you cannot insert such an item. Beware of this limitation. +// + +class splittree { +private: + elementsp* root; // binary tree root + elementsp* leaf; // all leaf nodes + int support; // number of nodes in the tree + double total_weight; // total weight stored + int total_count; // total number of observations stored + + // left-rotation operator + void rotateLeft(elementsp*); + // right-rotation operator + void rotateRight(elementsp*); + // house-keeping after insertion + void insertCleanup(elementsp*); + // house-keeping after deletion + void deleteCleanup(elementsp*); + keyValuePairSplit* returnSubtreeAsList(elementsp*, keyValuePairSplit*); + // delete subtree rooted at z + void deleteSubTree(elementsp*); + // returns minimum of subtree rooted at z + elementsp* returnMinKey(elementsp*); + // returns successor of z's key + elementsp* returnSuccessor(elementsp*); + +public: + // default constructor/destructor + splittree(); ~splittree(); + // returns value associated with searchKey + double returnValue(const std::string); + // returns T if searchKey found, and points foundNode at the + // corresponding node + elementsp* findItem(const std::string); + // update total_count and total_weight + void finishedThisRound(); + // insert a new key with stored value + bool insertItem(std::string, double); + void clearTree(); + // delete a node with given key + void deleteItem(std::string); + // delete the entire tree + void deleteTree(); + // return array of keys in tree + std::string* returnArrayOfKeys(); + // return list of keys in tree + slist* returnListOfKeys(); + // return the tree as a list of keyValuePairSplits + keyValuePairSplit* returnTreeAsList(); + // returns the maximum key in the tree + keyValuePairSplit returnMaxKey(); + // returns the minimum key in the tree + keyValuePairSplit returnMinKey(); + // returns number of items in tree + int returnNodecount(); + // returns list of splits with given number of Ms + keyValuePairSplit* returnTheseSplits(const int); + // returns sum of stored values + double returnTotal(); +}; + +} // namespace fitHRG + +#endif diff --git a/src/igraph_arpack_internal.h b/src/igraph_arpack_internal.h new file mode 100644 index 0000000..c7bbaee --- /dev/null +++ b/src/igraph_arpack_internal.h @@ -0,0 +1,219 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef ARPACK_INTERNAL_H +#define ARPACK_INTERNAL_H + +/* Note: only files calling the arpack routines directly need to + include this header. +*/ + +#include "igraph_types.h" +#include "config.h" + +#ifndef INTERNAL_ARPACK + #define igraphdsaupd_ dsaupd_ + #define igraphdseupd_ dseupd_ + #define igraphdsaup2_ dsaup2_ + #define igraphdstats_ dstats_ + #define igraphdsesrt_ dsesrt_ + #define igraphdsortr_ dsortr_ + #define igraphdsortc_ dsortc_ + #define igraphdgetv0_ dgetv0_ + #define igraphdsaitr_ dsaitr_ + #define igraphdsapps_ dsapps_ + #define igraphdsconv_ dsconv_ + #define igraphdseigt_ dseigt_ + #define igraphdsgets_ dsgets_ + #define igraphdstqrb_ dstqrb_ + #define igraphdmout_ dmout_ + #define igraphivout_ ivout_ + #define igraphsecond_ second_ + #define igraphdvout_ dvout_ + #define igraphdnaitr_ dnaitr_ + #define igraphdnapps_ dnapps_ + #define igraphdnaup2_ dnaup2_ + #define igraphdnaupd_ dnaupd_ + #define igraphdnconv_ dnconv_ + #define igraphdlabad_ dlabad_ + #define igraphdlanhs_ dlanhs_ + #define igraphdsortc_ dsortc_ + #define igraphdneigh_ dneigh_ + #define igraphdngets_ dngets_ + #define igraphdstatn_ dstatn_ + #define igraphdlaqrb_ dlaqrb_ + + #define igraphdsaupd_ dsaupd_ + #define igraphdseupd_ dseupd_ + #define igraphdnaupd_ dnaupd_ + #define igraphdneupd_ dneupd_ +#endif + +#ifndef INTERNAL_LAPACK + #define igraphdlarnv_ dlarnv_ + #define igraphdlascl_ dlascl_ + #define igraphdlartg_ dlartg_ + #define igraphdlaset_ dlaset_ + #define igraphdlae2_ dlae2_ + #define igraphdlaev2_ dlaev2_ + #define igraphdlasr_ dlasr_ + #define igraphdlasrt_ dlasrt_ + #define igraphdgeqr2_ dgeqr2_ + #define igraphdlacpy_ dlacpy_ + #define igraphdorm2r_ dorm2r_ + #define igraphdsteqr_ dsteqr_ + #define igraphdlanst_ dlanst_ + #define igraphdlapy2_ dlapy2_ + #define igraphdlamch_ dlamch_ + #define igraphdlaruv_ dlaruv_ + #define igraphdlarfg_ dlarfg_ + #define igraphdlarf_ dlarf_ + #define igraphdlassq_ dlassq_ + #define igraphdlamc2_ dlamc2_ + #define igraphdlamc1_ dlamc1_ + #define igraphdlamc2_ dlamc2_ + #define igraphdlamc3_ dlamc3_ + #define igraphdlamc4_ dlamc4_ + #define igraphdlamc5_ dlamc5_ + #define igraphdlabad_ dlabad_ + #define igraphdlanhs_ dlanhs_ + #define igraphdtrevc_ dtrevc_ + #define igraphdlanv2_ dlanv2_ + #define igraphdlaln2_ dlaln2_ + #define igraphdladiv_ dladiv_ + #define igraphdtrsen_ dtrsen_ + #define igraphdlahqr_ dlahqr_ + #define igraphdtrsen_ dtrsen_ + #define igraphdlacon_ dlacon_ + #define igraphdtrsyl_ dtrsyl_ + #define igraphdtrexc_ dtrexc_ + #define igraphdlange_ dlange_ + #define igraphdlaexc_ dlaexc_ + #define igraphdlasy2_ dlasy2_ + #define igraphdlarfx_ dlarfx_ +#endif + +#if 0 /* internal f2c functions always used */ + #define igraphd_sign d_sign + #define igraphetime_ etime_ + #define igraphpow_dd pow_dd + #define igraphpow_di pow_di + #define igraphs_cmp s_cmp + #define igraphs_copy s_copy + #define igraphd_lg10_ d_lg10_ + #define igraphi_dnnt_ i_dnnt_ +#endif + +#ifdef HAVE_GFORTRAN + +int igraphdsaupd_(int *ido, char *bmat, int *n, + char *which, int *nev, igraph_real_t *tol, + igraph_real_t *resid, int *ncv, igraph_real_t *v, + int *ldv, int *iparam, int *ipntr, + igraph_real_t *workd, igraph_real_t *workl, + int *lworkl, int *info, + int bmat_len, int which_len); + +int igraphdseupd_(int *rvec, char *howmny, int *select, + igraph_real_t *d, igraph_real_t *z, int *ldz, + igraph_real_t *sigma, char *bmat, int *n, + char *which, int *nev, igraph_real_t *tol, + igraph_real_t *resid, int *ncv, igraph_real_t *v, + int *ldv, int *iparam, int *ipntr, + igraph_real_t *workd, igraph_real_t *workl, + int *lworkl, int *info, + int howmny_len, int bmat_len, int which_len); + +int igraphdnaupd_(int *ido, char *bmat, int *n, + char *which, int *nev, igraph_real_t *tol, + igraph_real_t *resid, int *ncv, igraph_real_t *v, + int *ldv, int *iparam, int *ipntr, + igraph_real_t *workd, igraph_real_t *workl, + int *lworkl, int *info, + int bmat_len, int which_len); + +int igraphdneupd_(int *rvec, char *howmny, int *select, + igraph_real_t *dr, igraph_real_t *di, + igraph_real_t *z, int *ldz, + igraph_real_t *sigmar, igraph_real_t *sigmai, + igraph_real_t *workev, char *bmat, int *n, + char *which, int *nev, igraph_real_t *tol, + igraph_real_t *resid, int *ncv, igraph_real_t *v, + int *ldv, int *iparam, int *ipntr, + igraph_real_t *workd, igraph_real_t *workl, + int *lworkl, int *info, + int howmny_len, int bmat_len, int which_len); + +int igraphdsortr_(char *which, int *apply, int* n, igraph_real_t *x1, + igraph_real_t *x2, + int which_len); + +int igraphdsortc_(char *which, int *apply, int* n, igraph_real_t *xreal, + igraph_real_t *ximag, igraph_real_t *y, + int which_len); + +#else + +int igraphdsaupd_(int *ido, char *bmat, int *n, + char *which, int *nev, igraph_real_t *tol, + igraph_real_t *resid, int *ncv, igraph_real_t *v, + int *ldv, int *iparam, int *ipntr, + igraph_real_t *workd, igraph_real_t *workl, + int *lworkl, int *info); + +int igraphdseupd_(int *rvec, char *howmny, int *select, + igraph_real_t *d, igraph_real_t *z, int *ldz, + igraph_real_t *sigma, char *bmat, int *n, + char *which, int *nev, igraph_real_t *tol, + igraph_real_t *resid, int *ncv, igraph_real_t *v, + int *ldv, int *iparam, int *ipntr, + igraph_real_t *workd, igraph_real_t *workl, + int *lworkl, int *info); + +int igraphdnaupd_(int *ido, char *bmat, int *n, + char *which, int *nev, igraph_real_t *tol, + igraph_real_t *resid, int *ncv, igraph_real_t *v, + int *ldv, int *iparam, int *ipntr, + igraph_real_t *workd, igraph_real_t *workl, + int *lworkl, int *info); + +int igraphdneupd_(int *rvec, char *howmny, int *select, + igraph_real_t *dr, igraph_real_t *di, + igraph_real_t *z, int *ldz, + igraph_real_t *sigmar, igraph_real_t *sigmai, + igraph_real_t *workev, char *bmat, int *n, + char *which, int *nev, igraph_real_t *tol, + igraph_real_t *resid, int *ncv, igraph_real_t *v, + int *ldv, int *iparam, int *ipntr, + igraph_real_t *workd, igraph_real_t *workl, + int *lworkl, int *info); + +int igraphdsortr_(char *which, int *apply, int* n, igraph_real_t *x1, + igraph_real_t *x2); + +int igraphdsortc_(char *which, int *apply, int* n, igraph_real_t *xreal, + igraph_real_t *ximag, igraph_real_t *y); + +#endif + +#endif /* ARPACK_INTERNAL_H */ diff --git a/src/igraph_blas_internal.h b/src/igraph_blas_internal.h new file mode 100644 index 0000000..f4ea577 --- /dev/null +++ b/src/igraph_blas_internal.h @@ -0,0 +1,65 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef BLAS_INTERNAL_H +#define BLAS_INTERNAL_H + +/* Note: only files calling the BLAS routines directly need to + include this header. +*/ + +#include "igraph_types.h" +#include "config.h" + +#ifndef INTERNAL_BLAS + #define igraphdaxpy_ daxpy_ + #define igraphdger_ dger_ + #define igraphdcopy_ dcopy_ + #define igraphdscal_ dscal_ + #define igraphdswap_ dswap_ + #define igraphdgemm_ dgemm_ + #define igraphdgemv_ dgemv_ + #define igraphddot_ ddot_ + #define igraphdnrm2_ dnrm2_ + #define igraphlsame_ lsame_ + #define igraphdrot_ drot_ + #define igraphidamax_ idamax_ + #define igraphdtrmm_ dtrmm_ + #define igraphdasum_ dasum_ + #define igraphdtrsm_ dtrsm_ + #define igraphdtrsv_ dtrsv_ + #define igraphdnrm2_ dnrm2_ +#endif + +int igraphdgemv_(char *trans, int *m, int *n, igraph_real_t *alpha, + igraph_real_t *a, int *lda, igraph_real_t *x, int *incx, + igraph_real_t *beta, igraph_real_t *y, int *incy); + +int igraphdgemm_(char *transa, char *transb, int *m, int *n, int *k, + double *alpha, double *a, int *lda, double *b, int *ldb, + double *beta, double *c__, int *ldc); + +double igraphdnrm2_(int *n, double *x, int *incx); + +#endif diff --git a/src/igraph_buckets.c b/src/igraph_buckets.c new file mode 100644 index 0000000..bb933ca --- /dev/null +++ b/src/igraph_buckets.c @@ -0,0 +1,198 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_types_internal.h" +#include "config.h" + +#include + +/* The igraph_buckets_t data structure can store at most 'size' + * unique integers in 'bsize' buckets. It has the following simple + * operations (in addition to _init() and _destroy(): + * - _add() adding an element to the given bucket. + * - _popmax() removing an element from the bucket with the highest + * id. + * Currently buckets work as stacks, last-in-first-out mode. + * - _empty() queries whether the buckets is empty. + * + * Internal representation: we use a vector to create single linked + * lists, and another vector that points to the starting element of + * each bucket. Zero means the end of the chain. So bucket i contains + * elements bptr[i], buckets[bptr[i]], buckets[buckets[bptr[i]]], + * etc., until a zero is found. + * + * We also keep the total number of elements in the buckets and the + * id of the non-empty bucket with the highest id, to facilitate the + * _empty() and _popmax() operations. + */ + +int igraph_buckets_init(igraph_buckets_t *b, long int bsize, long int size) { + IGRAPH_VECTOR_LONG_INIT_FINALLY(&b->bptr, bsize); + IGRAPH_VECTOR_LONG_INIT_FINALLY(&b->buckets, size); + b->max = -1; b->no = 0; + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +void igraph_buckets_destroy(igraph_buckets_t *b) { + igraph_vector_long_destroy(&b->bptr); + igraph_vector_long_destroy(&b->buckets); +} + +long int igraph_buckets_popmax(igraph_buckets_t *b) { + /* Precondition: there is at least a non-empty bucket */ + /* Search for the highest bucket first */ + long int max; + while ( (max = (long int) VECTOR(b->bptr)[(long int) b->max]) == 0) { + b->max --; + } + VECTOR(b->bptr)[(long int) b->max] = VECTOR(b->buckets)[max - 1]; + b->no--; + + return max - 1; +} + +long int igraph_buckets_pop(igraph_buckets_t *b, long int bucket) { + long int ret = VECTOR(b->bptr)[bucket] - 1; + VECTOR(b->bptr)[bucket] = VECTOR(b->buckets)[ret]; + b->no--; + return ret; +} + +igraph_bool_t igraph_buckets_empty(const igraph_buckets_t *b) { + return (b->no == 0); +} + +igraph_bool_t igraph_buckets_empty_bucket(const igraph_buckets_t *b, + long int bucket) { + return VECTOR(b->bptr)[bucket] == 0; +} + +void igraph_buckets_add(igraph_buckets_t *b, long int bucket, + long int elem) { + + VECTOR(b->buckets)[(long int) elem] = VECTOR(b->bptr)[(long int) bucket]; + VECTOR(b->bptr)[(long int) bucket] = elem + 1; + if (bucket > b->max) { + b->max = (int) bucket; + } + b->no++; +} + +void igraph_buckets_clear(igraph_buckets_t *b) { + igraph_vector_long_null(&b->bptr); + igraph_vector_long_null(&b->buckets); + b->max = -1; + b->no = 0; +} + +int igraph_dbuckets_init(igraph_dbuckets_t *b, long int bsize, long int size) { + IGRAPH_VECTOR_LONG_INIT_FINALLY(&b->bptr, bsize); + IGRAPH_VECTOR_LONG_INIT_FINALLY(&b->next, size); + IGRAPH_VECTOR_LONG_INIT_FINALLY(&b->prev, size); + b->max = -1; b->no = 0; + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +void igraph_dbuckets_destroy(igraph_dbuckets_t *b) { + igraph_vector_long_destroy(&b->bptr); + igraph_vector_long_destroy(&b->next); + igraph_vector_long_destroy(&b->prev); +} + +void igraph_dbuckets_clear(igraph_dbuckets_t *b) { + igraph_vector_long_null(&b->bptr); + igraph_vector_long_null(&b->next); + igraph_vector_long_null(&b->prev); + b->max = -1; + b->no = 0; +} + +long int igraph_dbuckets_popmax(igraph_dbuckets_t *b) { + long int max; + while ( (max = (long int) VECTOR(b->bptr)[(long int) b->max]) == 0) { + b->max --; + } + return igraph_dbuckets_pop(b, b->max); +} + +long int igraph_dbuckets_pop(igraph_dbuckets_t *b, long int bucket) { + long int ret = VECTOR(b->bptr)[bucket] - 1; + long int next = VECTOR(b->next)[ret]; + VECTOR(b->bptr)[bucket] = next; + if (next != 0) { + VECTOR(b->prev)[next - 1] = 0; + } + + b->no--; + return ret; +} + +igraph_bool_t igraph_dbuckets_empty(const igraph_dbuckets_t *b) { + return (b->no == 0); +} + +igraph_bool_t igraph_dbuckets_empty_bucket(const igraph_dbuckets_t *b, + long int bucket) { + return VECTOR(b->bptr)[bucket] == 0; +} + +void igraph_dbuckets_add(igraph_dbuckets_t *b, long int bucket, + long int elem) { + long int oldfirst = VECTOR(b->bptr)[bucket]; + VECTOR(b->bptr)[bucket] = elem + 1; + VECTOR(b->next)[elem] = oldfirst; + if (oldfirst != 0) { + VECTOR(b->prev)[oldfirst - 1] = elem + 1; + } + if (bucket > b->max) { + b->max = (int) bucket; + } + b->no++; +} + +/* Remove an arbitrary element */ + +void igraph_dbuckets_delete(igraph_dbuckets_t *b, long int bucket, + long int elem) { + if (VECTOR(b->bptr)[bucket] == elem + 1) { + /* First element in bucket */ + long int next = VECTOR(b->next)[elem]; + if (next != 0) { + VECTOR(b->prev)[next - 1] = 0; + } + VECTOR(b->bptr)[bucket] = next; + } else { + long int next = VECTOR(b->next)[elem]; + long int prev = VECTOR(b->prev)[elem]; + if (next != 0) { + VECTOR(b->prev)[next - 1] = prev; + } + if (prev != 0) { + VECTOR(b->next)[prev - 1] = next; + } + } + b->no--; +} diff --git a/src/igraph_cliquer.c b/src/igraph_cliquer.c new file mode 100644 index 0000000..50ca57d --- /dev/null +++ b/src/igraph_cliquer.c @@ -0,0 +1,399 @@ + +#include "igraph_cliquer.h" +#include "igraph_memory.h" +#include "igraph_constants.h" +#include "igraph_interrupt_internal.h" +#include "cliquer/cliquer.h" +#include "config.h" + +#include + + +/* Call this to allow for interruption in Cliquer callback functions */ +#define CLIQUER_ALLOW_INTERRUPTION() \ + { \ + if (igraph_i_interruption_handler) \ + if (igraph_allow_interruption(NULL) != IGRAPH_SUCCESS) { \ + cliquer_interrupted = 1; \ + return FALSE; \ + } \ + } + +/* Interruptable Cliquer functions must be wrapped in CLIQUER_INTERRUPTABLE when called */ +#define CLIQUER_INTERRUPTABLE(x) \ + { \ + cliquer_interrupted = 0; \ + x; \ + if (cliquer_interrupted) return IGRAPH_INTERRUPTED; \ + } + + +/* Nonzero value signals interuption from Cliquer callback function */ +static IGRAPH_THREAD_LOCAL int cliquer_interrupted; + + +/* For use with IGRAPH_FINALLY */ +static void free_clique_list(igraph_vector_ptr_t *vp) { + igraph_integer_t i, len; + len = igraph_vector_ptr_size(vp); + for (i = 0; i < len; ++i) { + igraph_vector_destroy((igraph_vector_t *) VECTOR(*vp)[i]); + } + igraph_vector_ptr_free_all(vp); +} + +/* We shall use this option struct for all calls to Cliquer */ +static IGRAPH_THREAD_LOCAL clique_options igraph_cliquer_opt = { + reorder_by_default, NULL, NULL, NULL, NULL, NULL, NULL, 0 +}; + + +/* Convert an igraph graph to a Cliquer graph */ +static void igraph_to_cliquer(const igraph_t *ig, graph_t **cg) { + igraph_integer_t vcount, ecount; + int i; + + if (igraph_is_directed(ig)) { + IGRAPH_WARNING("Edge directions are ignored for clique calculations"); + } + + vcount = igraph_vcount(ig); + ecount = igraph_ecount(ig); + + *cg = graph_new(vcount); + + for (i = 0; i < ecount; ++i) { + long s, t; + s = IGRAPH_FROM(ig, i); + t = IGRAPH_TO(ig, i); + if (s != t) { + GRAPH_ADD_EDGE(*cg, s, t); + } + } +} + + +/* Copy weights to a Cliquer graph */ +static int set_weights(const igraph_vector_t *vertex_weights, graph_t *g) { + int i; + + assert(vertex_weights != NULL); + + if (igraph_vector_size(vertex_weights) != g->n) { + IGRAPH_ERROR("Invalid vertex weight vector length", IGRAPH_EINVAL); + } + + for (i = 0; i < g->n; ++i) { + g->weights[i] = VECTOR(*vertex_weights)[i]; + if (g->weights[i] != VECTOR(*vertex_weights)[i]) { + IGRAPH_WARNING("Only integer vertex weights are supported; weights will be truncated to their integer parts"); + } + if (g->weights[i] <= 0) { + IGRAPH_ERROR("Vertex weights must be positive", IGRAPH_EINVAL); + } + } + + return IGRAPH_SUCCESS; +} + + +/* Find all cliques. */ + +static boolean collect_cliques_callback(set_t s, graph_t *g, clique_options *opt) { + igraph_vector_ptr_t *list; + igraph_vector_t *clique; + int i, j; + + CLIQUER_ALLOW_INTERRUPTION(); + + list = (igraph_vector_ptr_t *) opt->user_data; + clique = (igraph_vector_t *) malloc(sizeof(igraph_vector_t)); + igraph_vector_init(clique, set_size(s)); + + i = -1; j = 0; + while ((i = set_return_next(s, i)) >= 0) { + VECTOR(*clique)[j++] = i; + } + + igraph_vector_ptr_push_back(list, clique); + + return TRUE; +} + +int igraph_i_cliquer_cliques(const igraph_t *graph, igraph_vector_ptr_t *res, + igraph_integer_t min_size, igraph_integer_t max_size) { + graph_t *g; + igraph_integer_t vcount = igraph_vcount(graph); + + if (vcount == 0) { + igraph_vector_ptr_clear(res); + return IGRAPH_SUCCESS; + } + + if (min_size <= 0) { + min_size = 1; + } + if (max_size <= 0) { + max_size = 0; + } + + if (max_size > 0 && max_size < min_size) { + IGRAPH_ERROR("max_size must not be smaller than min_size", IGRAPH_EINVAL); + } + + igraph_to_cliquer(graph, &g); + IGRAPH_FINALLY(graph_free, g); + + igraph_vector_ptr_clear(res); + igraph_cliquer_opt.user_data = res; + igraph_cliquer_opt.user_function = &collect_cliques_callback; + + IGRAPH_FINALLY(free_clique_list, res); + CLIQUER_INTERRUPTABLE(clique_unweighted_find_all(g, min_size, max_size, /* maximal= */ FALSE, &igraph_cliquer_opt)); + IGRAPH_FINALLY_CLEAN(1); + + graph_free(g); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/* Count cliques of each size. */ + +static boolean count_cliques_callback(set_t s, graph_t *g, clique_options *opt) { + igraph_vector_t *hist; + + CLIQUER_ALLOW_INTERRUPTION(); + + hist = (igraph_vector_t *) opt->user_data; + VECTOR(*hist)[set_size(s) - 1] += 1; + + return TRUE; +} + +int igraph_i_cliquer_histogram(const igraph_t *graph, igraph_vector_t *hist, + igraph_integer_t min_size, igraph_integer_t max_size) { + graph_t *g; + int i; + igraph_integer_t vcount = igraph_vcount(graph); + + if (vcount == 0) { + igraph_vector_clear(hist); + return IGRAPH_SUCCESS; + } + + if (min_size <= 0) { + min_size = 1; + } + if (max_size <= 0) { + max_size = vcount; /* also used for initial hist vector size, do not set to zero */ + } + + if (max_size < min_size) { + IGRAPH_ERROR("max_size must not be smaller than min_size", IGRAPH_EINVAL); + } + + igraph_to_cliquer(graph, &g); + IGRAPH_FINALLY(graph_free, g); + + igraph_vector_resize(hist, max_size); + igraph_vector_null(hist); + igraph_cliquer_opt.user_data = hist; + igraph_cliquer_opt.user_function = &count_cliques_callback; + + CLIQUER_INTERRUPTABLE(clique_unweighted_find_all(g, min_size, max_size, /* maximal= */ FALSE, &igraph_cliquer_opt)); + + for (i = max_size; i > 0; --i) + if (VECTOR(*hist)[i - 1] > 0) { + break; + } + igraph_vector_resize(hist, i); + igraph_vector_resize_min(hist); + + graph_free(g); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/* Call function for each clique. */ + +struct callback_data { + igraph_clique_handler_t *handler; + void *arg; +}; + +static boolean callback_callback(set_t s, graph_t *g, clique_options *opt) { + igraph_vector_t *clique; + struct callback_data *cd; + int i, j; + + CLIQUER_ALLOW_INTERRUPTION(); + + cd = (struct callback_data *) opt->user_data; + + clique = (igraph_vector_t *) malloc(sizeof(igraph_vector_t)); + igraph_vector_init(clique, set_size(s)); + + i = -1; j = 0; + while ((i = set_return_next(s, i)) >= 0) { + VECTOR(*clique)[j++] = i; + } + + return (*(cd->handler))(clique, cd->arg); +} + +int igraph_i_cliquer_callback(const igraph_t *graph, + igraph_integer_t min_size, igraph_integer_t max_size, + igraph_clique_handler_t *cliquehandler_fn, void *arg) { + graph_t *g; + struct callback_data cd; + igraph_integer_t vcount = igraph_vcount(graph); + + if (vcount == 0) { + return IGRAPH_SUCCESS; + } + + if (min_size <= 0) { + min_size = 1; + } + if (max_size <= 0) { + max_size = 0; + } + + if (max_size > 0 && max_size < min_size) { + IGRAPH_ERROR("max_size must not be smaller than min_size", IGRAPH_EINVAL); + } + + igraph_to_cliquer(graph, &g); + IGRAPH_FINALLY(graph_free, g); + + cd.handler = cliquehandler_fn; + cd.arg = arg; + igraph_cliquer_opt.user_data = &cd; + igraph_cliquer_opt.user_function = &callback_callback; + + CLIQUER_INTERRUPTABLE(clique_unweighted_find_all(g, min_size, max_size, /* maximal= */ FALSE, &igraph_cliquer_opt)); + + graph_free(g); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/* Find weighted cliques in given weight range. */ + +int igraph_i_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_ptr_t *res, + igraph_real_t min_weight, igraph_real_t max_weight, igraph_bool_t maximal) { + graph_t *g; + igraph_integer_t vcount = igraph_vcount(graph); + + if (vcount == 0) { + igraph_vector_ptr_clear(res); + return IGRAPH_SUCCESS; + } + + if (min_weight != (int) min_weight) { + IGRAPH_WARNING("Only integer vertex weights are supported; the minimum weight will be truncated to its integer part"); + min_weight = (int) min_weight; + } + + if (max_weight != (int) max_weight) { + IGRAPH_WARNING("Only integer vertex weights are supported; the maximum weight will be truncated to its integer part"); + max_weight = (int) max_weight; + } + + if (min_weight <= 0) { + min_weight = 1; + } + if (max_weight <= 0) { + max_weight = 0; + } + + if (max_weight > 0 && max_weight < min_weight) { + IGRAPH_ERROR("max_weight must not be smaller than min_weight", IGRAPH_EINVAL); + } + + igraph_to_cliquer(graph, &g); + IGRAPH_FINALLY(graph_free, g); + + IGRAPH_CHECK(set_weights(vertex_weights, g)); + + igraph_vector_ptr_clear(res); + igraph_cliquer_opt.user_data = res; + igraph_cliquer_opt.user_function = &collect_cliques_callback; + + IGRAPH_FINALLY(free_clique_list, res); + CLIQUER_INTERRUPTABLE(clique_find_all(g, min_weight, max_weight, maximal, &igraph_cliquer_opt)); + IGRAPH_FINALLY_CLEAN(1); + + graph_free(g); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/* Find largest weighted cliques. */ + +int igraph_i_largest_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_ptr_t *res) { + graph_t *g; + igraph_integer_t vcount = igraph_vcount(graph); + + if (vcount == 0) { + igraph_vector_ptr_clear(res); + return IGRAPH_SUCCESS; + } + + igraph_to_cliquer(graph, &g); + IGRAPH_FINALLY(graph_free, g); + + IGRAPH_CHECK(set_weights(vertex_weights, g)); + + igraph_vector_ptr_clear(res); + igraph_cliquer_opt.user_data = res; + igraph_cliquer_opt.user_function = &collect_cliques_callback; + + IGRAPH_FINALLY(free_clique_list, res); + CLIQUER_INTERRUPTABLE(clique_find_all(g, 0, 0, FALSE, &igraph_cliquer_opt)); + IGRAPH_FINALLY_CLEAN(1); + + graph_free(g); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + + +/* Find weight of largest weight clique. */ + +int igraph_i_weighted_clique_number(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_real_t *res) { + graph_t *g; + igraph_integer_t vcount = igraph_vcount(graph); + + if (vcount == 0) { + *res = 0; + return IGRAPH_SUCCESS; + } + + igraph_to_cliquer(graph, &g); + IGRAPH_FINALLY(graph_free, g); + + IGRAPH_CHECK(set_weights(vertex_weights, g)); + + igraph_cliquer_opt.user_function = NULL; + + /* we are not using a callback function, thus this is not interruptable */ + *res = clique_max_weight(g, &igraph_cliquer_opt); + + graph_free(g); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/igraph_cliquer.h b/src/igraph_cliquer.h new file mode 100644 index 0000000..74318e3 --- /dev/null +++ b/src/igraph_cliquer.h @@ -0,0 +1,29 @@ +#ifndef IGRAPH_CLIQUER_H +#define IGRAPH_CLIQUER_H + +#include "igraph_types_internal.h" +#include "igraph_interface.h" +#include "igraph_cliques.h" + +int igraph_i_cliquer_cliques(const igraph_t *graph, igraph_vector_ptr_t *res, + igraph_integer_t min_size, igraph_integer_t max_size); + +int igraph_i_cliquer_histogram(const igraph_t *graph, igraph_vector_t *hist, + igraph_integer_t min_size, igraph_integer_t max_size); + +int igraph_i_cliquer_callback(const igraph_t *graph, + igraph_integer_t min_size, igraph_integer_t max_size, + igraph_clique_handler_t *cliquehandler_fn, void *arg); + +int igraph_i_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_ptr_t *res, + igraph_real_t min_weight, igraph_real_t max_weight, igraph_bool_t maximal); + +int igraph_i_largest_weighted_cliques(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_vector_ptr_t *res); + +int igraph_i_weighted_clique_number(const igraph_t *graph, + const igraph_vector_t *vertex_weights, igraph_real_t *res); + +#endif // IGRAPH_CLIQUER_H + diff --git a/src/igraph_error.c b/src/igraph_error.c new file mode 100644 index 0000000..da68090 --- /dev/null +++ b/src/igraph_error.c @@ -0,0 +1,290 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "config.h" +#include "igraph_error.h" +#include "igraph_types.h" + +#include +#include +#include +#include + +static IGRAPH_THREAD_LOCAL igraph_error_handler_t *igraph_i_error_handler = 0; +static IGRAPH_THREAD_LOCAL char igraph_i_errormsg_buffer[500]; +static IGRAPH_THREAD_LOCAL char igraph_i_warningmsg_buffer[500]; + +/* Error strings corresponding to each igraph_error_type_t enum value. */ +static const char *igraph_i_error_strings[] = { + /* 0 */ "No error", + /* 1 */ "Failed", + /* 2 */ "Out of memory", + /* 3 */ "Parse error", + /* 4 */ "Invalid value", + /* 5 */ "Already exists", + /* 6 */ "Invalid edge vector", + /* 7 */ "Invalid vertex id", + /* 8 */ "Non-square matrix", + /* 9 */ "Invalid mode", + /* 10 */ "File operation error", + /* 11 */ "Unfold infinite iterator", + /* 12 */ "Unimplemented function call", + /* 13 */ "Interrupted", + /* 14 */ "Numeric procedure did not converge", + /* 15 */ "Matrix-vector product failed", + /* 16 */ "N must be positive", + /* 17 */ "NEV must be positive", + /* 18 */ "NCV must be greater than NEV and less than or equal to N " + "(and for the non-symmetric solver NCV-NEV >=2 must also hold)", + /* 19 */ "Maximum number of iterations should be positive", + /* 20 */ "Invalid WHICH parameter", + /* 21 */ "Invalid BMAT parameter", + /* 22 */ "WORKL is too small", + /* 23 */ "LAPACK error in tridiagonal eigenvalue calculation", + /* 24 */ "Starting vector is zero", + /* 25 */ "MODE is invalid", + /* 26 */ "MODE and BMAT are not compatible", + /* 27 */ "ISHIFT must be 0 or 1", + /* 28 */ "NEV and WHICH='BE' are incompatible", + /* 29 */ "Could not build an Arnoldi factorization", + /* 30 */ "No eigenvalues to sufficient accuracy", + /* 31 */ "HOWMNY is invalid", + /* 32 */ "HOWMNY='S' is not implemented", + /* 33 */ "Different number of converged Ritz values", + /* 34 */ "Error from calculation of a real Schur form", + /* 35 */ "LAPACK (dtrevc) error for calculating eigenvectors", + /* 36 */ "Unknown ARPACK error", + /* 37 */ "Negative loop detected while calculating shortest paths", + /* 38 */ "Internal error, likely a bug in igraph", + /* 39 */ "Maximum number of iterations reached", + /* 40 */ "No shifts could be applied during a cycle of the " + "Implicitly restarted Arnoldi iteration. One possibility " + "is to increase the size of NCV relative to NEV", + /* 41 */ "The Schur form computed by LAPACK routine dlahqr " + "could not be reordered by LAPACK routine dtrsen.", + /* 42 */ "Big integer division by zero", + /* 43 */ "GLPK Error, GLP_EBOUND", + /* 44 */ "GLPK Error, GLP_EROOT", + /* 45 */ "GLPK Error, GLP_ENOPFS", + /* 46 */ "GLPK Error, GLP_ENODFS", + /* 47 */ "GLPK Error, GLP_EFAIL", + /* 48 */ "GLPK Error, GLP_EMIPGAP", + /* 49 */ "GLPK Error, GLP_ETMLIM", + /* 50 */ "GLPK Error, GLP_STOP", + /* 51 */ "Internal attribute handler error", + /* 52 */ "Unimplemented attribute combination for this type", + /* 53 */ "LAPACK call resulted an error", + /* 54 */ "Internal DrL error", + /* 55 */ "Integer or double overflow", + /* 56 */ "Internal GPLK error", + /* 57 */ "CPU time exceeded", + /* 58 */ "Integer or double underflow", + /* 59 */ "Random walk got stuck", + /* 60 */ "Search stopped; this error should never be visible to the user, " + "please report this error along with the steps to reproduce it." +}; + +const char* igraph_strerror(const int igraph_errno) { + if (igraph_errno < 0 || igraph_errno >= sizeof(igraph_i_error_strings) / sizeof(char *)) { + return "Invalid error code; no error string available."; + } + return igraph_i_error_strings[igraph_errno]; +} + +int igraph_error(const char *reason, const char *file, int line, + int igraph_errno) { + + if (igraph_i_error_handler) { + igraph_i_error_handler(reason, file, line, igraph_errno); +#ifndef USING_R + } else { + igraph_error_handler_abort(reason, file, line, igraph_errno); +#endif + } + return igraph_errno; +} + +int igraph_errorf(const char *reason, const char *file, int line, + int igraph_errno, ...) { + va_list ap; + va_start(ap, igraph_errno); + vsnprintf(igraph_i_errormsg_buffer, + sizeof(igraph_i_errormsg_buffer) / sizeof(char), reason, ap); + return igraph_error(igraph_i_errormsg_buffer, file, line, igraph_errno); +} + +int igraph_errorvf(const char *reason, const char *file, int line, + int igraph_errno, va_list ap) { + vsnprintf(igraph_i_errormsg_buffer, + sizeof(igraph_i_errormsg_buffer) / sizeof(char), reason, ap); + return igraph_error(igraph_i_errormsg_buffer, file, line, igraph_errno); +} + +#ifndef USING_R +void igraph_error_handler_abort (const char *reason, const char *file, + int line, int igraph_errno) { + fprintf(stderr, "Error at %s:%i :%s, %s\n", file, line, reason, + igraph_strerror(igraph_errno)); + abort(); +} +#endif + +void igraph_error_handler_ignore (const char *reason, const char *file, + int line, int igraph_errno) { + IGRAPH_UNUSED(reason); + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + IGRAPH_UNUSED(igraph_errno); + + IGRAPH_FINALLY_FREE(); +} + +#ifndef USING_R +void igraph_error_handler_printignore (const char *reason, const char *file, + int line, int igraph_errno) { + IGRAPH_FINALLY_FREE(); + fprintf(stderr, "Error at %s:%i :%s, %s\n", file, line, reason, + igraph_strerror(igraph_errno)); +} +#endif + +igraph_error_handler_t * +igraph_set_error_handler (igraph_error_handler_t * new_handler) { + igraph_error_handler_t * previous_handler = igraph_i_error_handler; + igraph_i_error_handler = new_handler; + return previous_handler; +} + +IGRAPH_THREAD_LOCAL struct igraph_i_protectedPtr igraph_i_finally_stack[100]; + +/* + * Adds another element to the free list + */ + +void IGRAPH_FINALLY_REAL(void (*func)(void*), void* ptr) { + int no = igraph_i_finally_stack[0].all; + assert (no < 100); + assert (no >= 0); + igraph_i_finally_stack[no].ptr = ptr; + igraph_i_finally_stack[no].func = func; + igraph_i_finally_stack[0].all ++; + /* printf("--> Finally stack contains now %d elements\n", igraph_i_finally_stack[0].all); */ +} + +void IGRAPH_FINALLY_CLEAN(int minus) { + igraph_i_finally_stack[0].all -= minus; + if (igraph_i_finally_stack[0].all < 0) { + /* fprintf(stderr, "corrupt finally stack, popping %d elements when only %d left\n", minus, igraph_i_finally_stack[0].all+minus); */ + igraph_i_finally_stack[0].all = 0; + } + /* printf("<-- Finally stack contains now %d elements\n", igraph_i_finally_stack[0].all); */ +} + +void IGRAPH_FINALLY_FREE(void) { + int p; + /* printf("[X] Finally stack will be cleaned (contained %d elements)\n", igraph_i_finally_stack[0].all); */ + for (p = igraph_i_finally_stack[0].all - 1; p >= 0; p--) { + igraph_i_finally_stack[p].func(igraph_i_finally_stack[p].ptr); + } + igraph_i_finally_stack[0].all = 0; +} + +int IGRAPH_FINALLY_STACK_SIZE(void) { + return igraph_i_finally_stack[0].all; +} + +static IGRAPH_THREAD_LOCAL igraph_warning_handler_t *igraph_i_warning_handler = 0; + +/** + * \function igraph_warning_handler_ignore + * Ignore all warnings + * + * This warning handler function simply ignores all warnings. + * \param reason Textual description of the warning. + * \param file The source file in which the warning was noticed. + * \param line The number of line in the source file which triggered the + * warning.. + * \param igraph_errno Warnings could have potentially error codes as well, + * but this is currently not used in igraph. + */ + +void igraph_warning_handler_ignore (const char *reason, const char *file, + int line, int igraph_errno) { + IGRAPH_UNUSED(reason); + IGRAPH_UNUSED(file); + IGRAPH_UNUSED(line); + IGRAPH_UNUSED(igraph_errno); +} + +#ifndef USING_R + +/** + * \function igraph_warning_handler_print + * Print all warning to the standard error + * + * This warning handler function simply prints all warnings to the + * standard error. + * \param reason Textual description of the warning. + * \param file The source file in which the warning was noticed. + * \param line The number of line in the source file which triggered the + * warning.. + * \param igraph_errno Warnings could have potentially error codes as well, + * but this is currently not used in igraph. + */ + +void igraph_warning_handler_print (const char *reason, const char *file, + int line, int igraph_errno) { + IGRAPH_UNUSED(igraph_errno); + fprintf(stderr, "Warning: %s in file %s, line %i\n", reason, file, line); +} +#endif + +int igraph_warning(const char *reason, const char *file, int line, + int igraph_errno) { + + if (igraph_i_warning_handler) { + igraph_i_warning_handler(reason, file, line, igraph_errno); +#ifndef USING_R + } else { + igraph_warning_handler_print(reason, file, line, igraph_errno); +#endif + } + return igraph_errno; +} + +int igraph_warningf(const char *reason, const char *file, int line, + int igraph_errno, ...) { + va_list ap; + va_start(ap, igraph_errno); + vsnprintf(igraph_i_warningmsg_buffer, + sizeof(igraph_i_warningmsg_buffer) / sizeof(char), reason, ap); + return igraph_warning(igraph_i_warningmsg_buffer, file, line, + igraph_errno); +} + +igraph_warning_handler_t * +igraph_set_warning_handler (igraph_warning_handler_t * new_handler) { + igraph_warning_handler_t * previous_handler = igraph_i_warning_handler; + igraph_i_warning_handler = new_handler; + return previous_handler; +} diff --git a/src/igraph_estack.c b/src/igraph_estack.c new file mode 100644 index 0000000..2f32c63 --- /dev/null +++ b/src/igraph_estack.c @@ -0,0 +1,67 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_estack.h" + +int igraph_estack_init(igraph_estack_t *s, long int setsize, + long int stacksize) { + IGRAPH_CHECK(igraph_vector_bool_init(&s->isin, setsize)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &s->isin); + IGRAPH_CHECK(igraph_stack_long_init(&s->stack, stacksize)); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +void igraph_estack_destroy(igraph_estack_t *s) { + igraph_stack_long_destroy(&s->stack); + igraph_vector_bool_destroy(&s->isin); +} + +int igraph_estack_push(igraph_estack_t *s, long int elem) { + if ( !VECTOR(s->isin)[elem] ) { + IGRAPH_CHECK(igraph_stack_long_push(&s->stack, elem)); + VECTOR(s->isin)[elem] = 1; + } + return 0; +} + +long int igraph_estack_pop(igraph_estack_t *s) { + long int elem = igraph_stack_long_pop(&s->stack); + VECTOR(s->isin)[elem] = 0; + return elem; +} + +igraph_bool_t igraph_estack_iselement(const igraph_estack_t *s, + long int elem) { + return VECTOR(s->isin)[elem]; +} + +long int igraph_estack_size(const igraph_estack_t *s) { + return igraph_stack_long_size(&s->stack); +} + +#ifndef USING_R +int igraph_estack_print(const igraph_estack_t *s) { + return igraph_stack_long_print(&s->stack); +} +#endif diff --git a/src/igraph_estack.h b/src/igraph_estack.h new file mode 100644 index 0000000..c413613 --- /dev/null +++ b/src/igraph_estack.h @@ -0,0 +1,47 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_ESTACK_H +#define IGRAPH_ESTACK_H + +#include "igraph_stack.h" +#include "igraph_vector.h" + +typedef struct igraph_estack_t { + igraph_stack_long_t stack; + igraph_vector_bool_t isin; +} igraph_estack_t; + +int igraph_estack_init(igraph_estack_t *s, long int setsize, + long int stacksize); +void igraph_estack_destroy(igraph_estack_t *s); + +int igraph_estack_push(igraph_estack_t *s, long int elem); +long int igraph_estack_pop(igraph_estack_t *s); +igraph_bool_t igraph_estack_iselement(const igraph_estack_t *s, + long int elem); +long int igraph_estack_size(const igraph_estack_t *s); + +int igraph_estack_print(const igraph_estack_t *s); + +#endif diff --git a/src/igraph_f2c.h b/src/igraph_f2c.h new file mode 100644 index 0000000..8b35498 --- /dev/null +++ b/src/igraph_f2c.h @@ -0,0 +1,227 @@ +/* f2c.h -- Standard Fortran to C header file */ + +/** barf [ba:rf] 2. "He suggested using FORTRAN, and everybody barfed." + + - From The Shogakukan DICTIONARY OF NEW ENGLISH (Second edition) */ + +#ifndef F2C_INCLUDE +#define F2C_INCLUDE + +typedef long int integer; +typedef unsigned long int uinteger; +typedef char *address; +typedef short int shortint; +typedef float real; +typedef double doublereal; +typedef struct { + real r, i; +} complex; +typedef struct { + doublereal r, i; +} doublecomplex; +typedef long int logical; +typedef short int shortlogical; +typedef char logical1; +typedef char integer1; +#ifdef INTEGER_STAR_8 /* Adjust for integer*8. */ + typedef long long longint; /* system-dependent */ + typedef unsigned long long ulongint; /* system-dependent */ + #define qbit_clear(a,b) ((a) & ~((ulongint)1 << (b))) + #define qbit_set(a,b) ((a) | ((ulongint)1 << (b))) +#endif + +#define TRUE_ (1) +#define FALSE_ (0) + +/* Extern is for use with -E */ +#ifndef Extern + #define Extern extern +#endif + +/* I/O stuff */ + +#ifdef f2c_i2 + /* for -i2 */ + typedef short flag; + typedef short ftnlen; + typedef short ftnint; +#else + typedef long int flag; + typedef long int ftnlen; + typedef long int ftnint; +#endif + +/*external read, write*/ +typedef struct { + flag cierr; + ftnint ciunit; + flag ciend; + char *cifmt; + ftnint cirec; +} cilist; + +/*internal read, write*/ +typedef struct { + flag icierr; + char *iciunit; + flag iciend; + char *icifmt; + ftnint icirlen; + ftnint icirnum; +} icilist; + +/*open*/ +typedef struct { + flag oerr; + ftnint ounit; + char *ofnm; + ftnlen ofnmlen; + char *osta; + char *oacc; + char *ofm; + ftnint orl; + char *oblnk; +} olist; + +/*close*/ +typedef struct { + flag cerr; + ftnint cunit; + char *csta; +} cllist; + +/*rewind, backspace, endfile*/ +typedef struct { + flag aerr; + ftnint aunit; +} alist; + +/* inquire */ +typedef struct { + flag inerr; + ftnint inunit; + char *infile; + ftnlen infilen; + ftnint *inex; /*parameters in standard's order*/ + ftnint *inopen; + ftnint *innum; + ftnint *innamed; + char *inname; + ftnlen innamlen; + char *inacc; + ftnlen inacclen; + char *inseq; + ftnlen inseqlen; + char *indir; + ftnlen indirlen; + char *infmt; + ftnlen infmtlen; + char *inform; + ftnint informlen; + char *inunf; + ftnlen inunflen; + ftnint *inrecl; + ftnint *innrec; + char *inblank; + ftnlen inblanklen; +} inlist; + +#define VOID void + +union Multitype { /* for multiple entry points */ + integer1 g; + shortint h; + integer i; + /* longint j; */ + real r; + doublereal d; + complex c; + doublecomplex z; +}; + +typedef union Multitype Multitype; + +/*typedef long int Long;*/ /* No longer used; formerly in Namelist */ + +struct Vardesc { /* for Namelist */ + char *name; + char *addr; + ftnlen *dims; + int type; +}; +typedef struct Vardesc Vardesc; + +struct Namelist { + char *name; + Vardesc **vars; + int nvars; +}; +typedef struct Namelist Namelist; + +#define abs(x) ((x) >= 0 ? (x) : -(x)) +#define dabs(x) (doublereal)abs(x) +#define min(a,b) ((a) <= (b) ? (a) : (b)) +#define max(a,b) ((a) >= (b) ? (a) : (b)) +#define dmin(a,b) (doublereal)min(a,b) +#define dmax(a,b) (doublereal)max(a,b) +#define bit_test(a,b) ((a) >> (b) & 1) +#define bit_clear(a,b) ((a) & ~((uinteger)1 << (b))) +#define bit_set(a,b) ((a) | ((uinteger)1 << (b))) + +/* procedure parameter types for -A and -C++ */ + +#define F2C_proc_par_types 1 +#ifdef __cplusplus + typedef int /* Unknown procedure type */ (*U_fp)(...); + typedef shortint (*J_fp)(...); + typedef integer (*I_fp)(...); + typedef real (*R_fp)(...); + typedef doublereal (*D_fp)(...), (*E_fp)(...); + typedef /* Complex */ VOID (*C_fp)(...); + typedef /* Double Complex */ VOID (*Z_fp)(...); + typedef logical (*L_fp)(...); + typedef shortlogical (*K_fp)(...); + typedef /* Character */ VOID (*H_fp)(...); + typedef /* Subroutine */ int (*S_fp)(...); +#else + typedef int /* Unknown procedure type */ (*U_fp)(); + typedef shortint (*J_fp)(); + typedef integer (*I_fp)(); + typedef real (*R_fp)(); + typedef doublereal (*D_fp)(), (*E_fp)(); + typedef /* Complex */ VOID (*C_fp)(); + typedef /* Double Complex */ VOID (*Z_fp)(); + typedef logical (*L_fp)(); + typedef shortlogical (*K_fp)(); + typedef /* Character */ VOID (*H_fp)(); + typedef /* Subroutine */ int (*S_fp)(); +#endif +/* E_fp is for real functions when -R is not specified */ +typedef VOID C_f; /* complex function */ +typedef VOID H_f; /* character function */ +typedef VOID Z_f; /* double complex function */ +typedef doublereal E_f; /* real function with -R not specified */ + +/* undef any lower-case symbols that your C compiler predefines, e.g.: */ + +#ifndef Skip_f2c_Undefs + #undef cray + #undef gcos + #undef mc68010 + #undef mc68020 + #undef mips + #undef pdp11 + #undef sgi + #undef sparc + #undef sun + #undef sun2 + #undef sun3 + #undef sun4 + #undef u370 + #undef u3b + #undef u3b2 + #undef u3b5 + #undef unix + #undef vax +#endif +#endif diff --git a/src/igraph_fixed_vectorlist.c b/src/igraph_fixed_vectorlist.c new file mode 100644 index 0000000..a52b9bd --- /dev/null +++ b/src/igraph_fixed_vectorlist.c @@ -0,0 +1,80 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types_internal.h" +#include "igraph_memory.h" + +void igraph_fixed_vectorlist_destroy(igraph_fixed_vectorlist_t *l) { + long int i, n = igraph_vector_ptr_size(&l->v); + for (i = 0; i < n; i++) { + igraph_vector_t *v = VECTOR(l->v)[i]; + if (v) { + igraph_vector_destroy(v); + } + } + igraph_vector_ptr_destroy(&l->v); + igraph_free(l->vecs); +} + +int igraph_fixed_vectorlist_convert(igraph_fixed_vectorlist_t *l, + const igraph_vector_t *from, + long int size) { + + igraph_vector_t sizes; + long int i, no = igraph_vector_size(from); + + l->vecs = igraph_Calloc(size, igraph_vector_t); + if (!l->vecs) { + IGRAPH_ERROR("Cannot merge attributes for simplify", + IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, l->vecs); + IGRAPH_CHECK(igraph_vector_ptr_init(&l->v, size)); + IGRAPH_FINALLY(igraph_fixed_vectorlist_destroy, &l->v); + IGRAPH_VECTOR_INIT_FINALLY(&sizes, size); + + for (i = 0; i < no; i++) { + long int to = (long int) VECTOR(*from)[i]; + if (to >= 0) { + VECTOR(sizes)[to] += 1; + } + } + for (i = 0; i < size; i++) { + igraph_vector_t *v = &(l->vecs[i]); + IGRAPH_CHECK(igraph_vector_init(v, (long int) VECTOR(sizes)[i])); + igraph_vector_clear(v); + VECTOR(l->v)[i] = v; + } + for (i = 0; i < no; i++) { + long int to = (long int) VECTOR(*from)[i]; + if (to >= 0) { + igraph_vector_t *v = &(l->vecs[to]); + igraph_vector_push_back(v, i); + } + } + + igraph_vector_destroy(&sizes); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} diff --git a/src/igraph_flow_internal.h b/src/igraph_flow_internal.h new file mode 100644 index 0000000..5011b40 --- /dev/null +++ b/src/igraph_flow_internal.h @@ -0,0 +1,42 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_FLOW_INTERNAL_H +#define IGRAPH_FLOW_INTERNAL_H + +#include "igraph_types.h" +#include "igraph_marked_queue.h" +#include "igraph_estack.h" +#include "igraph_datatype.h" + +typedef int igraph_provan_shier_pivot_t(const igraph_t *graph, + const igraph_marked_queue_t *S, + const igraph_estack_t *T, + long int source, + long int target, + long int *v, + igraph_vector_t *Isv, + void *arg); + +#endif + diff --git a/src/igraph_glpk_support.h b/src/igraph_glpk_support.h new file mode 100644 index 0000000..f8ae4f3 --- /dev/null +++ b/src/igraph_glpk_support.h @@ -0,0 +1,48 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_GLPK_SUPPORT_H +#define IGRAPH_GLPK_SUPPORT_H + +#include "config.h" + +/* Note: only files calling the GLPK routines directly need to + include this header. +*/ + +#ifdef HAVE_GLPK + +#include + +int igraph_i_glpk_check(int retval, const char* message); +void igraph_i_glpk_interruption_hook(glp_tree *tree, void *info); +#define IGRAPH_GLPK_CHECK(func, message) do {\ + int igraph_i_ret = igraph_i_glpk_check(func, message); \ + if (IGRAPH_UNLIKELY(igraph_i_ret != 0)) {\ + return igraph_i_ret; \ + } } while (0) + +#endif + +#endif diff --git a/src/igraph_gml_tree.h b/src/igraph_gml_tree.h new file mode 100644 index 0000000..bf98cdd --- /dev/null +++ b/src/igraph_gml_tree.h @@ -0,0 +1,91 @@ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef REST_GML_TREE_H +#define REST_GML_TREE_H + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus + #define __BEGIN_DECLS extern "C" { + #define __END_DECLS } +#else + #define __BEGIN_DECLS /* empty */ + #define __END_DECLS /* empty */ +#endif + +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" + +__BEGIN_DECLS + +typedef enum { IGRAPH_I_GML_TREE_TREE = 0, + IGRAPH_I_GML_TREE_INTEGER, + IGRAPH_I_GML_TREE_REAL, + IGRAPH_I_GML_TREE_STRING, + IGRAPH_I_GML_TREE_DELETED + } igraph_i_gml_tree_type_t; + +typedef struct igraph_gml_tree_t { + igraph_vector_ptr_t names; + igraph_vector_char_t types; + igraph_vector_ptr_t children; +} igraph_gml_tree_t; + +int igraph_gml_tree_init_integer(igraph_gml_tree_t *t, + const char *name, int namelen, + igraph_integer_t value); +int igraph_gml_tree_init_real(igraph_gml_tree_t *t, + const char *name, int namelen, + igraph_real_t value); +int igraph_gml_tree_init_string(igraph_gml_tree_t *t, + const char *name, int namelen, + const char *value, int valuelen); +int igraph_gml_tree_init_tree(igraph_gml_tree_t *t, + const char *name, int namelen, + igraph_gml_tree_t *value); +void igraph_gml_tree_destroy(igraph_gml_tree_t *t); + +void igraph_gml_tree_delete(igraph_gml_tree_t *t, long int pos); +int igraph_gml_tree_mergedest(igraph_gml_tree_t *t1, igraph_gml_tree_t *t2); + +long int igraph_gml_tree_length(const igraph_gml_tree_t *t); +long int igraph_gml_tree_find(const igraph_gml_tree_t *t, + const char *name, long int from); +long int igraph_gml_tree_findback(const igraph_gml_tree_t *t, + const char *name, long int from); +int igraph_gml_tree_type(const igraph_gml_tree_t *t, long int pos); +const char *igraph_gml_tree_name(const igraph_gml_tree_t *t, long int pos); +igraph_integer_t igraph_gml_tree_get_integer(const igraph_gml_tree_t *t, + long int pos); +igraph_real_t igraph_gml_tree_get_real(const igraph_gml_tree_t *t, + long int pos); +const char *igraph_gml_tree_get_string(const igraph_gml_tree_t *t, + long int pos); + +igraph_gml_tree_t *igraph_gml_tree_get_tree(const igraph_gml_tree_t *t, + long int pos); + +__END_DECLS + +#endif diff --git a/src/igraph_grid.c b/src/igraph_grid.c new file mode 100644 index 0000000..8d01d81 --- /dev/null +++ b/src/igraph_grid.c @@ -0,0 +1,543 @@ +/* -*- mode: C -*- */ +/* + IGraph R package. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_types_internal.h" +#include "igraph_memory.h" +#include "config.h" + +#include + +/* internal function */ + +int igraph_2dgrid_which(igraph_2dgrid_t *grid, igraph_real_t xc, igraph_real_t yc, + long int *x, long int *y) { + + if (xc <= grid->minx) { + *x = 0; + } else if (xc >= grid->maxx) { + *x = grid->stepsx - 1; + } else { + *x = (long int) floor((xc - (grid->minx)) / (grid->deltax)); + } + + if (yc <= grid->miny) { + *y = 0; + } else if (yc >= grid->maxy) { + *y = grid->stepsy - 1; + } else { + *y = (long int) floor((yc - (grid->miny)) / (grid->deltay)); + } + + return 0; +} + +int igraph_2dgrid_init(igraph_2dgrid_t *grid, igraph_matrix_t *coords, + igraph_real_t minx, igraph_real_t maxx, igraph_real_t deltax, + igraph_real_t miny, igraph_real_t maxy, igraph_real_t deltay) { + long int i; + + grid->coords = coords; + grid->minx = minx; + grid->maxx = maxx; + grid->deltax = deltax; + grid->miny = miny; + grid->maxy = maxy; + grid->deltay = deltay; + + grid->stepsx = (long int) ceil((maxx - minx) / deltax); + grid->stepsy = (long int) ceil((maxy - miny) / deltay); + + IGRAPH_CHECK(igraph_matrix_init(&grid->startidx, + grid->stepsx, grid->stepsy)); + IGRAPH_FINALLY(igraph_matrix_destroy, &grid->startidx); + IGRAPH_VECTOR_INIT_FINALLY(&grid->next, igraph_matrix_nrow(coords)); + IGRAPH_VECTOR_INIT_FINALLY(&grid->prev, igraph_matrix_nrow(coords)); + + for (i = 0; i < igraph_vector_size(&grid->next); i++) { + VECTOR(grid->next)[i] = -1; + } + + grid->massx = 0; + grid->massy = 0; + grid->vertices = 0; + + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +void igraph_2dgrid_destroy(igraph_2dgrid_t *grid) { + igraph_matrix_destroy(&grid->startidx); + igraph_vector_destroy(&grid->next); + igraph_vector_destroy(&grid->prev); +} + +void igraph_2dgrid_add(igraph_2dgrid_t *grid, long int elem, + igraph_real_t xc, igraph_real_t yc) { + long int x, y; + long int first; + + MATRIX(*grid->coords, elem, 0) = xc; + MATRIX(*grid->coords, elem, 1) = yc; + + /* add to cell */ + igraph_2dgrid_which(grid, xc, yc, &x, &y); + first = (long int) MATRIX(grid->startidx, x, y); + VECTOR(grid->prev)[elem] = 0; + VECTOR(grid->next)[elem] = first; + if (first != 0) { + VECTOR(grid->prev)[first - 1] = elem + 1; + } + MATRIX(grid->startidx, x, y) = elem + 1; + + grid->massx += xc; + grid->massy += yc; + grid->vertices += 1; +} + +void igraph_2dgrid_add2(igraph_2dgrid_t *grid, long int elem) { + long int x, y; + long int first; + igraph_real_t xc, yc; + + xc = MATRIX(*grid->coords, elem, 0); + yc = MATRIX(*grid->coords, elem, 1); + + /* add to cell */ + igraph_2dgrid_which(grid, xc, yc, &x, &y); + first = (long int) MATRIX(grid->startidx, x, y); + VECTOR(grid->prev)[elem] = 0; + VECTOR(grid->next)[elem] = first; + if (first != 0) { + VECTOR(grid->prev)[first - 1] = elem + 1; + } + MATRIX(grid->startidx, x, y) = elem + 1; + + grid->massx += xc; + grid->massy += yc; + grid->vertices += 1; +} + +void igraph_2dgrid_move(igraph_2dgrid_t *grid, long int elem, + igraph_real_t xc, igraph_real_t yc) { + long int oldx, oldy; + long int newx, newy; + igraph_real_t oldxc = MATRIX(*grid->coords, elem, 0); + igraph_real_t oldyc = MATRIX(*grid->coords, elem, 1); + long int first; + + xc = oldxc + xc; yc = oldyc + yc; + + igraph_2dgrid_which(grid, oldxc, oldyc, &oldx, &oldy); + igraph_2dgrid_which(grid, xc, yc, &newx, &newy); + if (oldx != newx || oldy != newy) { + /* remove from this cell */ + if (VECTOR(grid->prev)[elem] != 0) { + VECTOR(grid->next) [ (long int) VECTOR(grid->prev)[elem] - 1 ] = + VECTOR(grid->next)[elem]; + } else { + MATRIX(grid->startidx, oldx, oldy) = VECTOR(grid->next)[elem]; + } + if (VECTOR(grid->next)[elem] != 0) { + VECTOR(grid->prev)[ (long int) VECTOR(grid->next)[elem] - 1 ] = + VECTOR(grid->prev)[elem]; + } + + /* add to this cell */ + first = (long int) MATRIX(grid->startidx, newx, newy); + VECTOR(grid->prev)[elem] = 0; + VECTOR(grid->next)[elem] = first; + if (first != 0) { + VECTOR(grid->prev)[first - 1] = elem + 1; + } + MATRIX(grid->startidx, newx, newy) = elem + 1; + } + + grid->massx += -oldxc + xc; + grid->massy += -oldyc + yc; + + MATRIX(*grid->coords, elem, 0) = xc; + MATRIX(*grid->coords, elem, 1) = yc; + +} + +void igraph_2dgrid_getcenter(const igraph_2dgrid_t *grid, + igraph_real_t *massx, igraph_real_t *massy) { + *massx = (grid->massx) / (grid->vertices); + *massy = (grid->massy) / (grid->vertices); +} + +igraph_bool_t igraph_2dgrid_in(const igraph_2dgrid_t *grid, long int elem) { + return VECTOR(grid->next)[elem] != -1; +} + +igraph_real_t igraph_2dgrid_dist(const igraph_2dgrid_t *grid, + long int e1, long int e2) { + igraph_real_t x = MATRIX(*grid->coords, e1, 0) - MATRIX(*grid->coords, e2, 0); + igraph_real_t y = MATRIX(*grid->coords, e1, 1) - MATRIX(*grid->coords, e2, 1); + + return sqrt(x * x + y * y); +} + +igraph_real_t igraph_2dgrid_dist2(const igraph_2dgrid_t *grid, + long int e1, long int e2) { + igraph_real_t x = MATRIX(*grid->coords, e1, 0) - MATRIX(*grid->coords, e2, 0); + igraph_real_t y = MATRIX(*grid->coords, e1, 1) - MATRIX(*grid->coords, e2, 1); + + return x * x + y * y; +} + +int igraph_i_2dgrid_addvertices(igraph_2dgrid_t *grid, igraph_vector_t *eids, + igraph_integer_t vid, igraph_real_t r, + long int x, long int y) { + long int act; + igraph_real_t *v = VECTOR(grid->next); + + r = r * r; + act = (long int) MATRIX(grid->startidx, x, y); + while (act != 0) { + if (igraph_2dgrid_dist2(grid, vid, act - 1) < r) { + IGRAPH_CHECK(igraph_vector_push_back(eids, act - 1)); + } + act = (long int) v[act - 1]; + } + return 0; +} + +int igraph_2dgrid_neighbors(igraph_2dgrid_t *grid, igraph_vector_t *eids, + igraph_integer_t vid, igraph_real_t r) { + igraph_real_t xc = MATRIX(*grid->coords, (long int)vid, 0); + igraph_real_t yc = MATRIX(*grid->coords, (long int)vid, 1); + long int x, y; + igraph_vector_clear(eids); + + igraph_2dgrid_which(grid, xc, yc, &x, &y); + + /* this cell */ + igraph_i_2dgrid_addvertices(grid, eids, vid, r, x, y); + + /* left */ + if (x != 0) { + igraph_i_2dgrid_addvertices(grid, eids, vid, r, x - 1, y); + } + /* right */ + if (x != grid->stepsx - 1) { + igraph_i_2dgrid_addvertices(grid, eids, vid, r, x + 1, y); + } + /* up */ + if (y != 0) { + igraph_i_2dgrid_addvertices(grid, eids, vid, r, x, y - 1); + } + /* down */ + if (y != grid->stepsy - 1) { + igraph_i_2dgrid_addvertices(grid, eids, vid, r, x, y + 1); + } + /* up & left */ + if (x != 0 && y != 0) { + igraph_i_2dgrid_addvertices(grid, eids, vid, r, x - 1, y - 1); + } + /* up & right */ + if (x != grid->stepsx - 1 && y != 0) { + igraph_i_2dgrid_addvertices(grid, eids, vid, r, x + 1, y - 1); + } + /* down & left */ + if (x != 0 && y != grid->stepsy - 1) { + igraph_i_2dgrid_addvertices(grid, eids, vid, r, x - 1, y + 1); + } + /* down & right */ + if (x != grid->stepsx - 1 && y != grid->stepsy - 1) { + igraph_i_2dgrid_addvertices(grid, eids, vid, r, x - 1, y + 1); + } + + return 0; +} + +void igraph_2dgrid_reset(igraph_2dgrid_t *grid, igraph_2dgrid_iterator_t *it) { + /* Search for the first cell containing a vertex */ + it->x = 0; it->y = 0; it->vid = (long int) MATRIX(grid->startidx, 0, 0); + while ( it->vid == 0 && (it->x < grid->stepsx - 1 || it->y < grid->stepsy - 1)) { + it->x += 1; + if (it->x == grid->stepsx) { + it->x = 0; it->y += 1; + } + it->vid = (long int) MATRIX(grid->startidx, it->x, it->y); + } +} + +igraph_integer_t igraph_2dgrid_next(igraph_2dgrid_t *grid, + igraph_2dgrid_iterator_t *it) { + long int ret = it->vid; + + if (ret == 0) { + return 0; + } + + /* First neighbor */ + it->ncells = -1; + if (it->x != grid->stepsx - 1) { + it->ncells += 1; + it->nx[it->ncells] = it->x + 1; + it->ny[it->ncells] = it->y; + } + if (it->y != grid->stepsy - 1) { + it->ncells += 1; + it->nx[it->ncells] = it->x; + it->ny[it->ncells] = it->y + 1; + } + if (it->ncells == 1) { + it->ncells += 1; + it->nx[it->ncells] = it->x + 1; + it->ny[it->ncells] = it->y + 1; + } + it->ncells += 1; + it->nx[it->ncells] = it->x; + it->ny[it->ncells] = it->y; + + it->nei = (long int) VECTOR(grid->next) [ ret - 1 ]; + while (it->ncells > 0 && it->nei == 0 ) { + it->ncells -= 1; + it->nei = (long int) MATRIX(grid->startidx, it->nx[it->ncells], it->ny[it->ncells]); + } + + /* Next vertex */ + it->vid = (long int) VECTOR(grid->next)[ it->vid - 1 ]; + while ( (it->x < grid->stepsx - 1 || it->y < grid->stepsy - 1) && + it->vid == 0) { + it->x += 1; + if (it->x == grid->stepsx) { + it->x = 0; it->y += 1; + } + it->vid = (long int) MATRIX(grid->startidx, it->x, it->y); + } + + return (igraph_integer_t) ret; +} + +igraph_integer_t igraph_2dgrid_next_nei(igraph_2dgrid_t *grid, + igraph_2dgrid_iterator_t *it) { + long int ret = it->nei; + + if (it->nei != 0) { + it->nei = (long int) VECTOR(grid->next) [ ret - 1 ]; + } + while (it->ncells > 0 && it->nei == 0 ) { + it->ncells -= 1; + it->nei = (long int) MATRIX(grid->startidx, it->nx[it->ncells], it->ny[it->ncells]); + } + + return (igraph_integer_t) ret; +} + +/*-----------------------------------------------------------------------*/ + +int igraph_i_layout_mergegrid_which(igraph_i_layout_mergegrid_t *grid, + igraph_real_t xc, igraph_real_t yc, + long int *x, long int *y) { + if (xc <= grid->minx) { + *x = 0; + } else if (xc >= grid->maxx) { + *x = grid->stepsx - 1; + } else { + *x = (long int) floor((xc - (grid->minx)) / (grid->deltax)); + } + + if (yc <= grid->miny) { + *y = 0; + } else if (yc >= grid->maxy) { + *y = grid->stepsy - 1; + } else { + *y = (long int) floor((yc - (grid->miny)) / (grid->deltay)); + } + + return 0; +} + +int igraph_i_layout_mergegrid_init(igraph_i_layout_mergegrid_t *grid, + igraph_real_t minx, igraph_real_t maxx, long int stepsx, + igraph_real_t miny, igraph_real_t maxy, long int stepsy) { + grid->minx = minx; + grid->maxx = maxx; + grid->stepsx = stepsx; + grid->deltax = (maxx - minx) / stepsx; + grid->miny = miny; + grid->maxy = maxy; + grid->stepsy = stepsy; + grid->deltay = (maxy - miny) / stepsy; + + grid->data = igraph_Calloc(stepsx * stepsy, long int); + if (grid->data == 0) { + IGRAPH_ERROR("Cannot create grid", IGRAPH_ENOMEM); + } + return 0; +} + +void igraph_i_layout_mergegrid_destroy(igraph_i_layout_mergegrid_t *grid) { + igraph_Free(grid->data); +} + +#define MAT(i,j) (grid->data[(grid->stepsy)*(j)+(i)]) +#define DIST2(x2,y2) (sqrt(pow(x-(x2),2)+pow(y-(y2), 2))) + +int igraph_i_layout_merge_place_sphere(igraph_i_layout_mergegrid_t *grid, + igraph_real_t x, igraph_real_t y, igraph_real_t r, + long int id) { + long int cx, cy; + long int i, j; + + igraph_i_layout_mergegrid_which(grid, x, y, &cx, &cy); + + MAT(cx, cy) = id + 1; + +#define DIST(i,j) (DIST2(grid->minx+(cx+(i))*grid->deltax, \ + grid->miny+(cy+(j))*grid->deltay)) + + for (i = 0; cx + i < grid->stepsx && DIST(i, 0) < r; i++) { + for (j = 0; cy + j < grid->stepsy && DIST(i, j) < r; j++) { + MAT(cx + i, cy + j) = id + 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx+(i))*grid->deltax, \ + grid->miny+(cy-(j)+1)*grid->deltay)) + + for (i = 0; cx + i < grid->stepsx && DIST(i, 0) < r; i++) { + for (j = 1; cy - j > 0 && DIST(i, j) < r; j++) { + MAT(cx + i, cy - j) = id + 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx-(i)+1)*grid->deltax, \ + grid->miny+(cy+(j))*grid->deltay)) + + for (i = 1; cx - i > 0 && DIST(i, 0) < r; i++) { + for (j = 0; cy + j < grid->stepsy && DIST(i, j) < r; j++) { + MAT(cx - i, cy + j) = id + 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx-(i)+1)*grid->deltax, \ + grid->miny+(cy-(j)+1)*grid->deltay)) + + for (i = 1; cx - i > 0 && DIST(i, 0) < r; i++) { + for (j = 1; cy - j > 0 && DIST(i, j) < r; j++) { + MAT(cx - i, cy - j) = id + 1; + } + } + +#undef DIST +#undef DIST2 + + return 0; +} + +long int igraph_i_layout_mergegrid_get(igraph_i_layout_mergegrid_t *grid, + igraph_real_t x, igraph_real_t y) { + long int cx, cy; + long int res; + + if (x <= grid->minx || x >= grid->maxx || + y <= grid->miny || y >= grid->maxy) { + res = -1; + } else { + igraph_i_layout_mergegrid_which(grid, x, y, &cx, &cy); + res = MAT(cx, cy) - 1; + } + + return res; +} + +#define DIST2(x2,y2) (sqrt(pow(x-(x2),2)+pow(y-(y2), 2))) + +long int igraph_i_layout_mergegrid_get_sphere(igraph_i_layout_mergegrid_t *grid, + igraph_real_t x, igraph_real_t y, igraph_real_t r) { + long int cx, cy; + long int i, j; + long int ret; + + if (x - r <= grid->minx || x + r >= grid->maxx || + y - r <= grid->miny || y + r >= grid->maxy) { + ret = -1; + } else { + igraph_i_layout_mergegrid_which(grid, x, y, &cx, &cy); + + ret = MAT(cx, cy) - 1; + +#define DIST(i,j) (DIST2(grid->minx+(cx+(i))*grid->deltax, \ + grid->miny+(cy+(j))*grid->deltay)) + + for (i = 0; ret < 0 && cx + i < grid->stepsx && DIST(i, 0) < r; i++) { + for (j = 0; ret < 0 && cy + j < grid->stepsy && DIST(i, j) < r; j++) { + ret = MAT(cx + i, cy + j) - 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx+(i))*grid->deltax, \ + grid->miny+(cy-(j)+1)*grid->deltay)) + + for (i = 0; ret < 0 && cx + i < grid->stepsx && DIST(i, 0) < r; i++) { + for (j = 1; ret < 0 && cy - j > 0 && DIST(i, j) < r; j++) { + ret = MAT(cx + i, cy - j) - 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx-(i)+1)*grid->deltax, \ + grid->miny+(cy+(j))*grid->deltay)) + + for (i = 1; ret < 0 && cx - i > 0 && DIST(i, 0) < r; i++) { + for (j = 0; ret < 0 && cy + j < grid->stepsy && DIST(i, j) < r; j++) { + ret = MAT(cx - i, cy + j) - 1; + } + } + +#undef DIST +#define DIST(i,j) (DIST2(grid->minx+(cx-(i)+1)*grid->deltax, \ + grid->miny+(cy-(j)+1)*grid->deltay)) + + for (i = 1; ret < 0 && cx + i > 0 && DIST(i, 0) < r; i++) { + for (j = 1; ret < 0 && cy + i > 0 && DIST(i, j) < r; j++) { + ret = MAT(cx - i, cy - j) - 1; + } + } + +#undef DIST + + } + + return ret; +} + +/* int print_grid(igraph_i_layout_mergegrid_t *grid) { */ +/* long int i,j; */ + +/* for (i=0; istepsx; i++) { */ +/* for (j=0; jstepsy; j++) { */ +/* printf("%li ", MAT(i,j)-1); */ +/* } */ +/* printf("\n"); */ +/* } */ +/* } */ diff --git a/src/igraph_hacks_internal.h b/src/igraph_hacks_internal.h new file mode 100644 index 0000000..00cd93a --- /dev/null +++ b/src/igraph_hacks_internal.h @@ -0,0 +1,57 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_HACKS_INTERNAL_H +#define IGRAPH_HACKS_INTERNAL_H + +#include "config.h" + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus + #define __BEGIN_DECLS extern "C" { + #define __END_DECLS } +#else + #define __BEGIN_DECLS /* empty */ + #define __END_DECLS /* empty */ +#endif + +__BEGIN_DECLS + +#ifndef HAVE_STRDUP + #define strdup igraph_i_strdup + char* igraph_i_strdup(const char *s); +#endif + +#ifndef HAVE_STPCPY + #define stpcpy igraph_i_stpcpy + char* igraph_i_stpcpy(char* s1, const char* s2); +#else + #ifndef HAVE_STPCPY_SIGNATURE + char* stpcpy(char* s1, const char* s2); + #endif +#endif + +__END_DECLS + +#endif diff --git a/src/igraph_hashtable.c b/src/igraph_hashtable.c new file mode 100644 index 0000000..396bc58 --- /dev/null +++ b/src/igraph_hashtable.c @@ -0,0 +1,128 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_types_internal.h" +#include "igraph_memory.h" +#include "igraph_error.h" +#include "config.h" +#include + +int igraph_hashtable_init(igraph_hashtable_t *ht) { + IGRAPH_CHECK(igraph_trie_init(&ht->keys, 1)); + IGRAPH_FINALLY(igraph_trie_destroy, &ht->keys); + IGRAPH_CHECK(igraph_strvector_init(&ht->elements, 0)); + IGRAPH_FINALLY(igraph_trie_destroy, &ht->elements); + IGRAPH_CHECK(igraph_strvector_init(&ht->defaults, 0)); + + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +void igraph_hashtable_destroy(igraph_hashtable_t *ht) { + igraph_trie_destroy(&ht->keys); + igraph_strvector_destroy(&ht->elements); + igraph_strvector_destroy(&ht->defaults); +} + +/* Note: may leave the hash table in an inconsistent state if a new + element is added, but this is not a big problem, since while the + defaults, or the defaults plus the elements may contain more elements + than the keys trie, but the data is always retrieved based on the trie +*/ + +int igraph_hashtable_addset(igraph_hashtable_t *ht, + const char *key, const char *def, + const char *elem) { + long int size = igraph_trie_size(&ht->keys); + long int newid; + IGRAPH_CHECK(igraph_trie_get(&ht->keys, key, &newid)); + + if (newid == size) { + /* this is a new element */ + IGRAPH_CHECK(igraph_strvector_resize(&ht->defaults, newid + 1)); + IGRAPH_CHECK(igraph_strvector_resize(&ht->elements, newid + 1)); + IGRAPH_CHECK(igraph_strvector_set(&ht->defaults, newid, def)); + IGRAPH_CHECK(igraph_strvector_set(&ht->elements, newid, elem)); + } else { + /* set an already existing element */ + IGRAPH_CHECK(igraph_strvector_set(&ht->elements, newid, elem)); + } + + return 0; +} + +/* Previous comment also applies here */ + +int igraph_hashtable_addset2(igraph_hashtable_t *ht, + const char *key, const char *def, + const char *elem, int elemlen) { + long int size = igraph_trie_size(&ht->keys); + long int newid; + char *tmp; + + IGRAPH_CHECK(igraph_trie_get(&ht->keys, key, &newid)); + + tmp = igraph_Calloc(elemlen + 1, char); + if (tmp == 0) { + IGRAPH_ERROR("cannot add element to hash table", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, tmp); + strncpy(tmp, elem, elemlen); + tmp[elemlen] = '\0'; + + if (newid == size) { + IGRAPH_CHECK(igraph_strvector_resize(&ht->defaults, newid + 1)); + IGRAPH_CHECK(igraph_strvector_resize(&ht->elements, newid + 1)); + IGRAPH_CHECK(igraph_strvector_set(&ht->defaults, newid, def)); + IGRAPH_CHECK(igraph_strvector_set(&ht->elements, newid, tmp)); + } else { + IGRAPH_CHECK(igraph_strvector_set(&ht->elements, newid, tmp)); + } + + igraph_Free(tmp); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +int igraph_hashtable_get(igraph_hashtable_t *ht, + const char *key, char **elem) { + long int newid; + IGRAPH_CHECK(igraph_trie_get(&ht->keys, key, &newid)); + + igraph_strvector_get(&ht->elements, newid, elem); + + return 0; +} + +int igraph_hashtable_reset(igraph_hashtable_t *ht) { + igraph_strvector_destroy(&ht->elements); + IGRAPH_CHECK(igraph_strvector_copy(&ht->elements, &ht->defaults)); + return 0; +} + +int igraph_hashtable_getkeys(igraph_hashtable_t *ht, + const igraph_strvector_t **sv) { + return igraph_trie_getkeys(&ht->keys, sv); +} diff --git a/src/igraph_heap.c b/src/igraph_heap.c new file mode 100644 index 0000000..b94a8d1 --- /dev/null +++ b/src/igraph_heap.c @@ -0,0 +1,64 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_heap.h" + +#define BASE_IGRAPH_REAL +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_IGRAPH_REAL + +#define BASE_LONG +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_LONG + +#define BASE_CHAR +#define HEAP_TYPE_MAX +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MAX +#define HEAP_TYPE_MIN +#include "igraph_pmt.h" +#include "heap.pmt" +#include "igraph_pmt_off.h" +#undef HEAP_TYPE_MIN +#undef BASE_CHAR diff --git a/src/igraph_hrg.cc b/src/igraph_hrg.cc new file mode 100644 index 0000000..115444e --- /dev/null +++ b/src/igraph_hrg.cc @@ -0,0 +1,1074 @@ +/* -*- mode: C++ -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_interface.h" +#include "igraph_community.h" +#include "igraph_memory.h" +#include "igraph_constructors.h" +#include "igraph_attributes.h" +#include "igraph_foreign.h" +#include "igraph_hrg.h" +#include "igraph_random.h" + +#include "hrg_dendro.h" +#include "hrg_graph.h" +#include "hrg_graph_simp.h" + +using namespace fitHRG; + +/** + * \section hrg_intro Introduction + * + * A hierarchical random graph is an ensemble of undirected + * graphs with \c n vertices. It is defined via a binary tree with \c + * n leaf and \c n-1 internal vertices, where the + * internal vertices are labeled with probabilities. + * The probability that two vertices are connected in the random graph + * is given by the probability label at their closest common + * ancestor. + * + * + * Please read the following two articles for more about + * hierarchical random graphs: A. Clauset, C. Moore, and M.E.J. Newman. + * Hierarchical structure and the prediction of missing links in networks. + * Nature 453, 98 - 101 (2008); and A. Clauset, C. Moore, and M.E.J. Newman. + * Structural Inference of Hierarchies in Networks. In E. M. Airoldi + * et al. (Eds.): ICML 2006 Ws, Lecture Notes in Computer Science + * 4503, 1-13. Springer-Verlag, Berlin Heidelberg (2007). + * + * + * + * igraph contains functions for fitting HRG models to a given network + * (\ref igraph_hrg_fit), for generating networks from a given HRG + * ensemble (\ref igraph_hrg_game, \ref igraph_hrg_sample), converting + * an igraph graph to a HRG and back (\ref igraph_hrg_create, \ref + * igraph_hrg_dendrogram), for calculating a consensus tree from a + * set of sampled HRGs (\ref igraph_hrg_consensus) and for predicting + * missing edges in a network based on its HRG models (\ref + * igraph_hrg_predict). + * + * + * The igraph HRG implementation is heavily based on the code + * published by Aaron Clauset, at his website, + * http://tuvalu.santafe.edu/~aaronc/hierarchy/ + * + */ + +namespace fitHRG { +struct pblock { + double L; + int i; + int j; +}; +} + +static int markovChainMonteCarlo(dendro *d, unsigned int period, + igraph_hrg_t *hrg) { + + igraph_real_t bestL = d->getLikelihood(); + double dL; + bool flag_taken; + + // Because moves in the dendrogram space are chosen (Monte + // Carlo) so that we sample dendrograms with probability + // proportional to their likelihood, a likelihood-proportional + // sampling of the dendrogram models would be equivalent to a + // uniform sampling of the walk itself. We would still have to + // decide how often to sample the walk (at most once every n + // steps is recommended) but for simplicity, the code here + // simply runs the MCMC itself. To actually compute something + // over the set of sampled dendrogram models (in a Bayesian + // model averaging sense), you'll need to code that yourself. + + // do 'period' MCMC moves before doing anything else + for (unsigned int i = 0; i < period; i++) { + + // make a MCMC move + IGRAPH_CHECK(! d->monteCarloMove(dL, flag_taken, 1.0)); + + // get likelihood of this D given G + igraph_real_t cl = d->getLikelihood(); + if (cl > bestL) { + // store the current best likelihood + bestL = cl; + // record the HRG structure + d->recordDendrogramStructure(hrg); + } + } + // corrects floating-point errors O(n) + d->refreshLikelihood(); + + return 0; +} + +static int markovChainMonteCarlo2(dendro *d, int num_samples) { + bool flag_taken; + double dL, ptest = 1.0 / (50.0 * (double)(d->g->numNodes())); + int sample_num = 0, t = 1, thresh = 200 * d->g->numNodes(); + + // Since we're sampling uniformly at random over the equilibrium + // walk, we just need to do a bunch of MCMC moves and let the + // sampling happen on its own. + while (sample_num < num_samples) { + // Make a single MCMC move + d->monteCarloMove(dL, flag_taken, 1.0); + + // We sample the dendrogram space once every n MCMC moves (on + // average). Depending on the flags on the command line, we sample + // different aspects of the dendrograph structure. + if (t > thresh && RNG_UNIF01() < ptest) { + sample_num++; + d->sampleSplitLikelihoods(sample_num); + } + + t++; + + // correct floating-point errors O(n) + d->refreshLikelihood(); // TODO: less frequently + } + + return 0; +} + +static int MCMCEquilibrium_Find(dendro *d, igraph_hrg_t *hrg) { + + // We want to run the MCMC until we've found equilibrium; we + // use the heuristic of the average log-likelihood (which is + // exactly the entropy) over X steps being very close to the + // average log-likelihood (entropy) over the X steps that + // preceded those. In other words, we look for an apparent + // local convergence of the entropy measure of the MCMC. + + bool flag_taken; + igraph_real_t dL, Likeli; + igraph_real_t oldMeanL; + igraph_real_t newMeanL = -1e-49; + + while (1) { + oldMeanL = newMeanL; + newMeanL = 0.0; + for (int i = 0; i < 65536; i++) { + IGRAPH_CHECK(! d->monteCarloMove(dL, flag_taken, 1.0)); + Likeli = d->getLikelihood(); + newMeanL += Likeli; + } + // corrects floating-point errors O(n) + d->refreshLikelihood(); + if (fabs(newMeanL - oldMeanL) / 65536.0 < 1.0) { + break; + } + } + + // Record the result + if (hrg) { + d->recordDendrogramStructure(hrg); + } + + return 0; +} + +static int igraph_i_hrg_getgraph(const igraph_t *igraph, + dendro *d) { + + int no_of_nodes = igraph_vcount(igraph); + int no_of_edges = igraph_ecount(igraph); + int i; + + // Create graph + d->g = new graph(no_of_nodes); + + // Add edges + for (i = 0; i < no_of_edges; i++) { + int from = IGRAPH_FROM(igraph, i); + int to = IGRAPH_TO(igraph, i); + if (from == to) { + continue; + } + if (!d->g->doesLinkExist(from, to)) { + d->g->addLink(from, to); + } + if (!d->g->doesLinkExist(to, from)) { + d->g->addLink(to, from); + } + } + + d->buildDendrogram(); + + return 0; +} + +static int igraph_i_hrg_getsimplegraph(const igraph_t *igraph, + dendro *d, simpleGraph **sg, + int num_bins) { + + int no_of_nodes = igraph_vcount(igraph); + int no_of_edges = igraph_ecount(igraph); + int i; + + // Create graphs + d->g = new graph(no_of_nodes, true); + d->g->setAdjacencyHistograms(num_bins); + (*sg) = new simpleGraph(no_of_nodes); + + for (i = 0; i < no_of_edges; i++) { + int from = IGRAPH_FROM(igraph, i); + int to = IGRAPH_TO(igraph, i); + if (from == to) { + continue; + } + if (!d->g->doesLinkExist(from, to)) { + d->g->addLink(from, to); + } + if (!d->g->doesLinkExist(to, from)) { + d->g->addLink(to, from); + } + if (!(*sg)->doesLinkExist(from, to)) { + (*sg)->addLink(from, to); + } + if (!(*sg)->doesLinkExist(to, from)) { + (*sg)->addLink(to, from); + } + } + + d->buildDendrogram(); + + return 0; +} + +/** + * \function igraph_hrg_init + * Allocate memory for a HRG. + * + * This function must be called before passing an \ref igraph_hrg_t to + * an igraph function. + * \param hrg Pointer to the HRG data structure to initialize. + * \param n The number of vertices in the graph that is modeled by + * this HRG. It can be zero, if this is not yet known. + * \return Error code. + * + * Time complexity: O(n), the number of vertices in the graph. + */ + +int igraph_hrg_init(igraph_hrg_t *hrg, int n) { + IGRAPH_VECTOR_INIT_FINALLY(&hrg->left, n - 1); + IGRAPH_VECTOR_INIT_FINALLY(&hrg->right, n - 1); + IGRAPH_VECTOR_INIT_FINALLY(&hrg->prob, n - 1); + IGRAPH_VECTOR_INIT_FINALLY(&hrg->edges, n - 1); + IGRAPH_VECTOR_INIT_FINALLY(&hrg->vertices, n - 1); + IGRAPH_FINALLY_CLEAN(5); + return 0; +} + +/** + * \function igraph_hrg_destroy + * Deallocate memory for an HRG. + * + * The HRG data structure can be reinitialized again with an \ref + * igraph_hrg_destroy call. + * \param hrg Pointer to the HRG data structure to deallocate. + * + * Time complexity: operating system dependent. + */ + +void igraph_hrg_destroy(igraph_hrg_t *hrg) { + igraph_vector_destroy(&hrg->left); + igraph_vector_destroy(&hrg->right); + igraph_vector_destroy(&hrg->prob); + igraph_vector_destroy(&hrg->edges); + igraph_vector_destroy(&hrg->vertices); +} + +/** + * \function igraph_hrg_size + * Returns the size of the HRG, the number of leaf nodes. + * + * \param hrg Pointer to the HRG. + * \return The number of leaf nodes in the HRG. + * + * Time complexity: O(1). + */ + +int igraph_hrg_size(const igraph_hrg_t *hrg) { + return igraph_vector_size(&hrg->left) + 1; +} + +/** + * \function igraph_hrg_resize + * Resize a HRG. + * + * \param hrg Pointer to an initialized (see \ref igraph_hrg_init) + * HRG. + * \param newsize The new size, i.e. the number of leaf nodes. + * \return Error code. + * + * Time complexity: O(n), n is the new size. + */ + +int igraph_hrg_resize(igraph_hrg_t *hrg, int newsize) { + int origsize = igraph_hrg_size(hrg); + int ret = 0; + igraph_error_handler_t *oldhandler = + igraph_set_error_handler(igraph_error_handler_ignore); + + ret = igraph_vector_resize(&hrg->left, newsize - 1); + ret |= igraph_vector_resize(&hrg->right, newsize - 1); + ret |= igraph_vector_resize(&hrg->prob, newsize - 1); + ret |= igraph_vector_resize(&hrg->edges, newsize - 1); + ret |= igraph_vector_resize(&hrg->vertices, newsize - 1); + + igraph_set_error_handler(oldhandler); + + if (ret) { + igraph_vector_resize(&hrg->left, origsize); + igraph_vector_resize(&hrg->right, origsize); + igraph_vector_resize(&hrg->prob, origsize); + igraph_vector_resize(&hrg->edges, origsize); + igraph_vector_resize(&hrg->vertices, origsize); + IGRAPH_ERROR("Cannot resize HRG", ret); + } + + return 0; +} + +/** + * \function igraph_hrg_fit + * Fit a hierarchical random graph model to a network + * + * \param graph The igraph graph to fit the model to. Edge directions + * are ignored in directed graphs. + * \param hrg Pointer to an initialized HRG, the result of the fitting + * is stored here. It can also be used to pass a HRG to the + * function, that can be used as the starting point of the Markov + * Chain Monte Carlo fitting, if the \c start argument is true. + * \param start Logical, whether to start the fitting from the given + * HRG. + * \param steps Integer, the number of MCMC steps to take in the + * fitting procedure. If this is zero, then the fitting stop is a + * convergence criteria is fulfilled. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_hrg_fit(const igraph_t *graph, + igraph_hrg_t *hrg, + igraph_bool_t start, + int steps) { + + int no_of_nodes = igraph_vcount(graph); + dendro *d; + + RNG_BEGIN(); + + d = new dendro; + + // If we want to start from HRG + if (start) { + d->clearDendrograph(); + if (igraph_hrg_size(hrg) != no_of_nodes) { + delete d; + IGRAPH_ERROR("Invalid HRG to start from", IGRAPH_EINVAL); + } + // Convert the igraph graph + IGRAPH_CHECK(igraph_i_hrg_getgraph(graph, d)); + d->importDendrogramStructure(hrg); + } else { + // Convert the igraph graph + IGRAPH_CHECK(igraph_i_hrg_getgraph(graph, d)); + IGRAPH_CHECK(igraph_hrg_resize(hrg, no_of_nodes)); + } + + // Run fixed number of steps, or until convergence + if (steps > 0) { + IGRAPH_CHECK(markovChainMonteCarlo(d, steps, hrg)); + } else { + IGRAPH_CHECK(MCMCEquilibrium_Find(d, hrg)); + } + + delete d; + + RNG_END(); + + return 0; + +} + +/** + * \function igraph_hrg_sample + * Sample from a hierarchical random graph model + * + * Sample from a hierarchical random graph ensemble. The ensemble can + * be given as a graph (\c input_graph), or as a HRG object (\c hrg). + * If a graph is given, then first an MCMC optimization is performed + * to find the optimal fitting model; then the MCMC is used to sample + * the graph(s). + * \param input_graph An igraph graph, or a null pointer. If not a + * null pointer, then a HRG is first fitted to the graph, possibly + * starting from the given HRG, if the \c start argument is true. If + * is is a null pointer, then the given HRG is used as a starting + * point, to find the optimum of the Markov chain, before the + * sampling. + * \param sample Pointer to an uninitialized graph, or a null + * pointer. If only one sample is requested, and it is not a null + * pointer, then the sample is stored here. + * \param samples An initialized vector of pointers. If more than one + * samples are requested, then they are stored here. Note that to + * free this data structure, you need to call \ref igraph_destroy on + * each graph first, then \c free() on all pointers, and finally + * \ref igraph_vector_ptr_destroy. + * \param no_samples The number of samples to generate. + * \param hrg A HRG. It is modified during the sampling. + * \param start Logical, whether to start the MCMC from the given + * HRG. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_hrg_sample(const igraph_t *input_graph, + igraph_t *sample, + igraph_vector_ptr_t *samples, + int no_samples, + igraph_hrg_t *hrg, + igraph_bool_t start) { + + int i; + dendro *d; + + if (no_samples < 0) { + IGRAPH_ERROR("Number of samples must be non-negative", IGRAPH_EINVAL); + } + + if (!sample && !samples) { + IGRAPH_ERROR("Give at least one of `sample' and `samples'", + IGRAPH_EINVAL); + } + + if (no_samples != 1 && sample) { + IGRAPH_ERROR("Number of samples should be one if `sample' is given", + IGRAPH_EINVAL); + } + + if (no_samples > 1 && !samples) { + IGRAPH_ERROR("`samples' must be non-null if number of samples " + "is larger than 1", IGRAPH_EINVAL); + } + + if (!start && !input_graph) { + IGRAPH_ERROR("Input graph must be given if initial HRG is not used", + IGRAPH_EINVAL); + } + + if (!start) { + IGRAPH_CHECK(igraph_hrg_resize(hrg, igraph_vcount(input_graph))); + } + + if (input_graph && igraph_hrg_size(hrg) != igraph_vcount(input_graph)) { + IGRAPH_ERROR("Invalid HRG size, should match number of nodes", + IGRAPH_EINVAL); + } + + RNG_BEGIN(); + + d = new dendro; + + // Need to find equilibrium first? + if (start) { + d->clearDendrograph(); + d->importDendrogramStructure(hrg); + } else { + IGRAPH_CHECK(MCMCEquilibrium_Find(d, hrg)); + } + + // TODO: free on error + + if (sample) { + // A single graph + d->makeRandomGraph(); + d->recordGraphStructure(sample); + if (samples) { + igraph_t *G = igraph_Calloc(1, igraph_t); + if (!G) { + IGRAPH_ERROR("Cannot sample HRG graphs", IGRAPH_ENOMEM); + } + d->recordGraphStructure(G); + IGRAPH_CHECK(igraph_vector_ptr_resize(samples, 1)); + VECTOR(*samples)[0] = G; + } + } else { + // Sample many + IGRAPH_CHECK(igraph_vector_ptr_resize(samples, no_samples)); + for (i = 0; i < no_samples; i++) { + igraph_t *G = igraph_Calloc(1, igraph_t); + if (!G) { + IGRAPH_ERROR("Cannot sample HRG graphs", IGRAPH_ENOMEM); + } + d->makeRandomGraph(); + d->recordGraphStructure(G); + VECTOR(*samples)[i] = G; + } + } + + delete d; + + RNG_END(); + + return 0; +} + +/** + * \function igraph_hrg_game + * Generate a hierarchical random graph + * + * This function is a simple shortcut to \ref igraph_hrg_sample. + * It creates a single graph, from the given HRG. + * \param graph Pointer to an uninitialized graph, the new graph is + * created here. + * \param hrg The hierarchical random graph model to sample from. It + * is modified during the MCMC process. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_hrg_game(igraph_t *graph, + const igraph_hrg_t *hrg) { + return igraph_hrg_sample(/* input_graph= */ 0, /* sample= */ graph, + /* samples= */ 0, /* no_samples=*/ 1, + /* hrg= */ (igraph_hrg_t*) hrg, + /* start= */ 1); +} + +/** + * \function igraph_hrg_dendrogram + * Create a dendrogram from a hierarchical random graph. + * + * Creates the igraph graph equivalent of an \ref igraph_hrg_t data + * structure. + * \param graph Pointer to an uninitialized graph, the result is + * stored here. + * \param hrg The hierarchical random graph to convert. + * \return Error code. + * + * Time complexity: O(n), the number of vertices in the graph. + */ + +int igraph_hrg_dendrogram(igraph_t *graph, + const igraph_hrg_t *hrg) { + + int orig_nodes = igraph_hrg_size(hrg); + int no_of_nodes = orig_nodes * 2 - 1; + int no_of_edges = no_of_nodes - 1; + igraph_vector_t edges; + int i, idx = 0; + igraph_vector_ptr_t vattrs; + igraph_vector_t prob; + igraph_attribute_record_t rec = { "probability", + IGRAPH_ATTRIBUTE_NUMERIC, + &prob + }; + + // Probability labels, for leaf nodes they are IGRAPH_NAN + IGRAPH_VECTOR_INIT_FINALLY(&prob, no_of_nodes); + for (i = 0; i < orig_nodes; i++) { + VECTOR(prob)[i] = IGRAPH_NAN; + } + for (i = 0; i < orig_nodes - 1; i++) { + VECTOR(prob)[orig_nodes + i] = VECTOR(hrg->prob)[i]; + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + IGRAPH_CHECK(igraph_vector_ptr_init(&vattrs, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &vattrs); + VECTOR(vattrs)[0] = &rec; + + for (i = 0; i < orig_nodes - 1; i++) { + int left = VECTOR(hrg->left)[i]; + int right = VECTOR(hrg->right)[i]; + + VECTOR(edges)[idx++] = orig_nodes + i; + VECTOR(edges)[idx++] = left < 0 ? orig_nodes - left - 1 : left; + VECTOR(edges)[idx++] = orig_nodes + i; + VECTOR(edges)[idx++] = right < 0 ? orig_nodes - right - 1 : right; + } + + IGRAPH_CHECK(igraph_empty(graph, 0, IGRAPH_DIRECTED)); + IGRAPH_FINALLY(igraph_destroy, graph); + IGRAPH_CHECK(igraph_add_vertices(graph, no_of_nodes, &vattrs)); + IGRAPH_CHECK(igraph_add_edges(graph, &edges, 0)); + + igraph_vector_ptr_destroy(&vattrs); + igraph_vector_destroy(&edges); + igraph_vector_destroy(&prob); + IGRAPH_FINALLY_CLEAN(4); // + 1 for graph + + return 0; +} + +/** + * \function igraph_hrg_consensus + * Calculate a consensus tree for a HRG. + * + * The calculation can be started from the given HRG (\c hrg), or (if + * \c start is false), a HRG is first fitted to the given graph. + * + * \param graph The input graph. + * \param parents An initialized vector, the results are stored + * here. For each vertex, the id of its parent vertex is stored, or + * -1, if the vertex is the root vertex in the tree. The first n + * vertex ids (from 0) refer to the original vertices of the graph, + * the other ids refer to vertex groups. + * \param weights Numeric vector, counts the number of times a given + * tree split occured in the generated network samples, for each + * internal vertices. The order is the same as in \c parents. + * \param hrg A hierarchical random graph. It is used as a starting + * point for the sampling, if the \c start argument is true. It is + * modified along the MCMC. + * \param start Logical, whether to use the supplied HRG (in \c hrg) + * as a starting point for the MCMC. + * \param num_samples The number of samples to generate for creating + * the consensus tree. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_hrg_consensus(const igraph_t *graph, + igraph_vector_t *parents, + igraph_vector_t *weights, + igraph_hrg_t *hrg, + igraph_bool_t start, + int num_samples) { + + dendro *d; + + if (start && !hrg) { + IGRAPH_ERROR("`hrg' must be given is `start' is true", IGRAPH_EINVAL); + } + + RNG_BEGIN(); + + d = new dendro; + + if (start) { + d->clearDendrograph(); + IGRAPH_CHECK(igraph_i_hrg_getgraph(graph, d)); + d->importDendrogramStructure(hrg); + } else { + IGRAPH_CHECK(igraph_i_hrg_getgraph(graph, d)); + if (hrg) { + igraph_hrg_resize(hrg, igraph_vcount(graph)); + } + IGRAPH_CHECK(MCMCEquilibrium_Find(d, hrg)); + } + + IGRAPH_CHECK(markovChainMonteCarlo2(d, num_samples)); + + d->recordConsensusTree(parents, weights); + + delete d; + + RNG_END(); + + return 0; +} + +static int MCMCEquilibrium_Sample(dendro *d, int num_samples) { + + // Because moves in the dendrogram space are chosen (Monte + // Carlo) so that we sample dendrograms with probability + // proportional to their likelihood, a likelihood-proportional + // sampling of the dendrogram models would be equivalent to a + // uniform sampling of the walk itself. We would still have to + // decide how often to sample the walk (at most once every n steps + // is recommended) but for simplicity, the code here simply runs the + // MCMC itself. To actually compute something over the set of + // sampled dendrogram models (in a Bayesian model averaging sense), + // you'll need to code that yourself. + + double dL; + bool flag_taken; + int sample_num = 0; + int t = 1, thresh = 100 * d->g->numNodes(); + double ptest = 1.0 / 10.0 / d->g->numNodes(); + + while (sample_num < num_samples) { + d->monteCarloMove(dL, flag_taken, 1.0); + if (t > thresh && RNG_UNIF01() < ptest) { + sample_num++; + d->sampleAdjacencyLikelihoods(); + } + d->refreshLikelihood(); // TODO: less frequently + t++; + } + + return 0; +} + +static int QsortPartition (pblock* array, int left, int right, int index) { + pblock p_value, temp; + p_value.L = array[index].L; + p_value.i = array[index].i; + p_value.j = array[index].j; + + // swap(array[p_value], array[right]) + temp.L = array[right].L; + temp.i = array[right].i; + temp.j = array[right].j; + array[right].L = array[index].L; + array[right].i = array[index].i; + array[right].j = array[index].j; + array[index].L = temp.L; + array[index].i = temp.i; + array[index].j = temp.j; + + int stored = left; + for (int i = left; i < right; i++) { + if (array[i].L <= p_value.L) { + // swap(array[stored], array[i]) + temp.L = array[i].L; + temp.i = array[i].i; + temp.j = array[i].j; + array[i].L = array[stored].L; + array[i].i = array[stored].i; + array[i].j = array[stored].j; + array[stored].L = temp.L; + array[stored].i = temp.i; + array[stored].j = temp.j; + stored++; + } + } + // swap(array[right], array[stored]) + temp.L = array[stored].L; + temp.i = array[stored].i; + temp.j = array[stored].j; + array[stored].L = array[right].L; + array[stored].i = array[right].i; + array[stored].j = array[right].j; + array[right].L = temp.L; + array[right].i = temp.i; + array[right].j = temp.j; + + return stored; +} + +static void QsortMain (pblock* array, int left, int right) { + if (right > left) { + int pivot = left; + int part = QsortPartition(array, left, right, pivot); + QsortMain(array, left, part - 1); + QsortMain(array, part + 1, right ); + } + return; +} + +static int rankCandidatesByProbability(simpleGraph *sg, dendro *d, + pblock *br_list, int mk) { + int mkk = 0; + int n = sg->getNumNodes(); + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (sg->getAdjacency(i, j) < 0.5) { + double temp = d->g->getAdjacencyAverage(i, j); + br_list[mkk].L = temp * (1.0 + RNG_UNIF01() / 1000.0); + br_list[mkk].i = i; + br_list[mkk].j = j; + mkk++; + } + } + } + + // Sort the candidates by their average probability + QsortMain(br_list, 0, mk - 1); + + return 0; +} + +static int recordPredictions(pblock *br_list, igraph_vector_t *edges, + igraph_vector_t *prob, int mk) { + + IGRAPH_CHECK(igraph_vector_resize(edges, mk * 2)); + IGRAPH_CHECK(igraph_vector_resize(prob, mk)); + + for (int i = mk - 1, idx = 0, idx2 = 0; i >= 0; i--) { + VECTOR(*edges)[idx++] = br_list[i].i; + VECTOR(*edges)[idx++] = br_list[i].j; + VECTOR(*prob)[idx2++] = br_list[i].L; + } + + return 0; +} + +/** + * \function igraph_hrg_predict + * Predict missing edges in a graph, based on HRG models + * + * Samples HRG models for a network, and estimated the probability + * that an edge was falsely observed as non-existent in the network. + * \param graph The input graph. + * \param edges The list of missing edges is stored here, the first + * two elements are the first edge, the next two the second edge, + * etc. + * \param prob Vector of probabilies for the existence of missing + * edges, in the order corresponding to \c edges. + * \param hrg A HRG, it is used as a starting point if \c start is + * true. It is also modified during the MCMC sampling. + * \param start Logical, whether to start the MCMC from the given HRG. + * \param num_samples The number of samples to generate. + * \param num_bins Controls the resolution of the edge + * probabilities. Higher numbers result higher resolution. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_hrg_predict(const igraph_t *graph, + igraph_vector_t *edges, + igraph_vector_t *prob, + igraph_hrg_t *hrg, + igraph_bool_t start, + int num_samples, + int num_bins) { + + dendro *d; + pblock *br_list; + int mk; + simpleGraph *sg; + + if (start && !hrg) { + IGRAPH_ERROR("`hrg' must be given is `start' is true", IGRAPH_EINVAL); + } + + RNG_BEGIN(); + + d = new dendro; + + IGRAPH_CHECK(igraph_i_hrg_getsimplegraph(graph, d, &sg, num_bins)); + + mk = sg->getNumNodes() * (sg->getNumNodes() - 1) / 2 - sg->getNumLinks() / 2; + br_list = new pblock[mk]; + for (int i = 0; i < mk; i++) { + br_list[i].L = 0.0; + br_list[i].i = -1; + br_list[i].j = -1; + } + + if (start) { + d->clearDendrograph(); + // this has cleared the graph as well.... bug? + IGRAPH_CHECK(igraph_i_hrg_getsimplegraph(graph, d, &sg, num_bins)); + d->importDendrogramStructure(hrg); + } else { + if (hrg) { + igraph_hrg_resize(hrg, igraph_vcount(graph)); + } + IGRAPH_CHECK(MCMCEquilibrium_Find(d, hrg)); + } + + IGRAPH_CHECK(MCMCEquilibrium_Sample(d, num_samples)); + IGRAPH_CHECK(rankCandidatesByProbability(sg, d, br_list, mk)); + IGRAPH_CHECK(recordPredictions(br_list, edges, prob, mk)); + + delete d; + delete sg; + delete [] br_list; + + RNG_END(); + + return 0; +} + +/** + * \function igraph_hrg_create + * Create a HRG from an igraph graph. + * + * \param hrg Pointer to an initialized \ref igraph_hrg_t. The result + * is stored here. + * \param graph The igraph graph to convert. It must be a directed + * binary tree, with n-1 internal and n leaf vertices. The root + * vertex must have in-degree zero. + * \param prob The vector of probabilities, this is used to label the + * internal nodes of the hierarchical random graph. The values + * corresponding to the leaves are ignored. + * \return Error code. + * + * Time complexity: O(n), the number of vertices in the tree. + */ + +int igraph_hrg_create(igraph_hrg_t *hrg, + const igraph_t *graph, + const igraph_vector_t *prob) { + + int no_of_nodes = igraph_vcount(graph); + int no_of_internal = (no_of_nodes - 1) / 2; + igraph_vector_t deg, idx; + int root = 0; + int d0 = 0, d1 = 0, d2 = 0; + int ii = 0, il = 0; + igraph_vector_t neis; + igraph_vector_t path; + + // -------------------------------------------------------- + // CHECKS + // -------------------------------------------------------- + + // At least three vertices are required + if (no_of_nodes < 3) { + IGRAPH_ERROR("HRG tree must have at least three vertices", + IGRAPH_EINVAL); + } + + // Prob vector was given + if (!prob) { + IGRAPH_ERROR("Probability vector must be given for HRG", + IGRAPH_EINVAL); + } + + // Length of prob vector + if (igraph_vector_size(prob) != no_of_nodes) { + IGRAPH_ERROR("HRG probability vector of wrong size", IGRAPH_EINVAL); + } + + // Must be a directed graph + if (!igraph_is_directed(graph)) { + IGRAPH_ERROR("HRG graph must be directed", IGRAPH_EINVAL); + } + + // Number of nodes must be odd + if (no_of_nodes % 2 == 0) { + IGRAPH_ERROR("Complete HRG graph must have odd number of vertices", + IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(°, 0); + + // Every vertex, except for the root must have in-degree one. + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), IGRAPH_IN, + IGRAPH_LOOPS)); + for (int i = 0; i < no_of_nodes; i++) { + int d = VECTOR(deg)[i]; + switch (d) { + case 0: d0++; root = i; break; + case 1: d1++; break; + default: + IGRAPH_ERROR("HRG nodes must have in-degree one, except for the " + "root vertex", IGRAPH_EINVAL); + } + } + if (d1 != no_of_nodes - 1 || d0 != 1) { + IGRAPH_ERROR("HRG nodes must have in-degree one, except for the " + "root vertex", IGRAPH_EINVAL); + } + + // Every internal vertex must have out-degree two, + // leaves out-degree zero + d0 = d1 = d2 = 0; + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), IGRAPH_OUT, + IGRAPH_LOOPS)); + for (int i = 0; i < no_of_nodes; i++) { + int d = VECTOR(deg)[i]; + switch (d) { + case 0: d0++; break; + case 2: d2++; break; + default: + IGRAPH_ERROR("HRG nodes must have out-degree 2 (internal nodes) or " + "degree 0 (leaves)", IGRAPH_EINVAL); + } + } + + // Number of internal and external nodes is correct + // This basically checks that the graph has one component + if (d0 != d2 + 1) { + IGRAPH_ERROR("HRG degrees are incorrect, maybe multiple components?", + IGRAPH_EINVAL); + } + + // -------------------------------------------------------- + // Graph is good, do the conversion + // -------------------------------------------------------- + + // Create an index, that maps the root node as first, then + // the internal nodes, then the leaf nodes + IGRAPH_VECTOR_INIT_FINALLY(&idx, no_of_nodes); + VECTOR(idx)[root] = - (ii++) - 1; + for (int i = 0; i < no_of_nodes; i++) { + int d = VECTOR(deg)[i]; + if (i == root) { + continue; + } + if (d == 2) { + VECTOR(idx)[i] = - (ii++) - 1; + } + if (d == 0) { + VECTOR(idx)[i] = (il++); + } + } + + igraph_hrg_resize(hrg, no_of_internal + 1); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + for (int i = 0; i < no_of_nodes; i++) { + int ri = VECTOR(idx)[i]; + if (ri >= 0) { + continue; + } + IGRAPH_CHECK(igraph_neighbors(graph, &neis, i, IGRAPH_OUT)); + VECTOR(hrg->left )[-ri - 1] = VECTOR(idx)[ (int) VECTOR(neis)[0] ]; + VECTOR(hrg->right)[-ri - 1] = VECTOR(idx)[ (int) VECTOR(neis)[1] ]; + VECTOR(hrg->prob )[-ri - 1] = VECTOR(*prob)[i]; + } + + // Calculate the number of vertices and edges in each subtree + igraph_vector_null(&hrg->edges); + igraph_vector_null(&hrg->vertices); + IGRAPH_VECTOR_INIT_FINALLY(&path, 0); + IGRAPH_CHECK(igraph_vector_push_back(&path, VECTOR(idx)[root])); + while (!igraph_vector_empty(&path)) { + int ri = igraph_vector_tail(&path); + int lc = VECTOR(hrg->left)[-ri - 1]; + int rc = VECTOR(hrg->right)[-ri - 1]; + if (lc < 0 && VECTOR(hrg->vertices)[-lc - 1] == 0) { + // Go left + IGRAPH_CHECK(igraph_vector_push_back(&path, lc)); + } else if (rc < 0 && VECTOR(hrg->vertices)[-rc - 1] == 0) { + // Go right + IGRAPH_CHECK(igraph_vector_push_back(&path, rc)); + } else { + // Subtrees are done, update node and go up + VECTOR(hrg->vertices)[-ri - 1] += + lc < 0 ? VECTOR(hrg->vertices)[-lc - 1] : 1; + VECTOR(hrg->vertices)[-ri - 1] += + rc < 0 ? VECTOR(hrg->vertices)[-rc - 1] : 1; + VECTOR(hrg->edges)[-ri - 1] += lc < 0 ? VECTOR(hrg->edges)[-lc - 1] + 1 : 1; + VECTOR(hrg->edges)[-ri - 1] += rc < 0 ? VECTOR(hrg->edges)[-rc - 1] + 1 : 1; + igraph_vector_pop_back(&path); + } + } + + igraph_vector_destroy(&path); + igraph_vector_destroy(&neis); + igraph_vector_destroy(&idx); + igraph_vector_destroy(°); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} diff --git a/src/igraph_hrg_types.cc b/src/igraph_hrg_types.cc new file mode 100644 index 0000000..cd5c044 --- /dev/null +++ b/src/igraph_hrg_types.cc @@ -0,0 +1,3726 @@ +// *********************************************************************** +// *** COPYRIGHT NOTICE ************************************************** +// rbtree - red-black tree (self-balancing binary tree data structure) +// Copyright (C) 2004 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// *********************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | +// http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science +// AND Santa Fe Institute +// Created : Spring 2004 +// Modified : many, many times +// +// *********************************************************************** + +#include "hrg_rbtree.h" +#include "hrg_dendro.h" +#include "hrg_graph.h" +#include "hrg_splittree_eq.h" +#include "hrg_graph_simp.h" + +#include "igraph_hrg.h" +#include "igraph_constructors.h" +#include "igraph_random.h" + +using namespace std; +using namespace fitHRG; + +// ******** Red-Black Tree Methods *************************************** + +rbtree::rbtree() { + root = new elementrb; + leaf = new elementrb; + + leaf->parent = root; + + root->left = leaf; + root->right = leaf; + support = 0; +} + +rbtree::~rbtree() { + if (root != NULL && + (root->left != leaf || root->right != leaf)) { + deleteSubTree(root); + } + if (root) { + delete root; + } + delete leaf; + support = 0; + root = 0; + leaf = 0; +} + +void rbtree::deleteTree() { + if (root != NULL) { + deleteSubTree(root); + } +} // does not leak memory + +void rbtree::deleteSubTree(elementrb *z) { + if (z->left != leaf) { + deleteSubTree(z->left); + } + if (z->right != leaf) { + deleteSubTree(z->right); + } + delete z; +} + +// ******** Search Functions ********************************************* +// public search function - if there exists a elementrb in the tree +// with key=searchKey, it returns TRUE and foundNode is set to point +// to the found node; otherwise, it sets foundNode=NULL and returns +// FALSE +elementrb* rbtree::findItem(const int searchKey) { + elementrb *current = root; + + // empty tree; bail out + if (current->key == -1) { + return NULL; + } + + while (current != leaf) { + // left-or-right? + if (searchKey < current->key) { + // try moving down-left + if (current->left != leaf) { + current = current->left; + } else { + // failure; bail out + return NULL; + } + } else { + // left-or-right? + if (searchKey > current->key) { + // try moving down-left + if (current->right != leaf) { + current = current->right; + } else { + // failure; bail out + return NULL; + } + } else { + // found (searchKey==current->key) + return current; + } + } + } + return NULL; +} + +int rbtree::returnValue(const int searchKey) { + elementrb* test = findItem(searchKey); + if (!test) { + return 0; + } else { + return test->value; + } +} + + +// ******** Return Item Functions **************************************** + +int* rbtree::returnArrayOfKeys() { + int* array; + array = new int [support]; + bool flag_go = true; + int index = 0; + elementrb *curr; + + if (support == 1) { + array[0] = root->key; + } else if (support == 2) { + array[0] = root->key; + if (root->left == leaf) { + array[1] = root->right->key; + } else { + array[1] = root->left->key; + } + } else { + for (int i = 0; i < support; i++) { + array[i] = -1; + } + // non-recursive traversal of tree structure + curr = root; + curr->mark = 1; + while (flag_go) { + // - is it time, and is left child the leaf node? + if (curr->mark == 1 && curr->left == leaf) { + curr->mark = 2; + } + // - is it time, and is right child the leaf node? + if (curr->mark == 2 && curr->right == leaf) { + curr->mark = 3; + } + if (curr->mark == 1) { + // - go left + curr->mark = 2; + curr = curr->left; + curr->mark = 1; + } else if (curr->mark == 2) { + // - else go right + curr->mark = 3; + curr = curr->right; + curr->mark = 1; + } else { + // - else go up a level + curr->mark = 0; + array[index++] = curr->key; + curr = curr->parent; + if (curr == NULL) { + flag_go = false; + } + } + } + } + + return array; +} + +list* rbtree::returnListOfKeys() { + keyValuePair *curr, *prev; + list *head = 0, *tail = 0, *newlist; + + curr = returnTreeAsList(); + while (curr != NULL) { + newlist = new list; + newlist->x = curr->x; + if (head == NULL) { + head = newlist; tail = head; + } else { + tail->next = newlist; tail = newlist; + } + prev = curr; + curr = curr->next; + delete prev; + prev = NULL; + } + return head; +} + +keyValuePair* rbtree::returnTreeAsList() { + // pre-order traversal + keyValuePair *head, *tail; + + head = new keyValuePair; + head->x = root->key; + head->y = root->value; + tail = head; + + if (root->left != leaf) { + tail = returnSubtreeAsList(root->left, tail); + } + if (root->right != leaf) { + tail = returnSubtreeAsList(root->right, tail); + } + + if (head->x == -1) { + return NULL; /* empty tree */ + } else { + return head; + } +} + +keyValuePair* rbtree::returnSubtreeAsList(elementrb *z, keyValuePair *head) { + keyValuePair *newnode, *tail; + + newnode = new keyValuePair; + newnode->x = z->key; + newnode->y = z->value; + head->next = newnode; + tail = newnode; + + if (z->left != leaf) { + tail = returnSubtreeAsList(z->left, tail); + } + if (z->right != leaf) { + tail = returnSubtreeAsList(z->right, tail); + } + + return tail; +} + +keyValuePair rbtree::returnMaxKey() { + keyValuePair themax; + elementrb *current; + current = root; + + // search to bottom-right corner of tree + while (current->right != leaf) { + current = current->right; + } + themax.x = current->key; + themax.y = current->value; + + return themax; +} + +keyValuePair rbtree::returnMinKey() { + keyValuePair themin; + elementrb *current; + current = root; + // search to bottom-left corner of tree + while (current->left != leaf) { + current = current->left; + } + themin.x = current->key; + themin.y = current->value; + + return themin; +} + +// private functions for deleteItem() (although these could easily be +// made public, I suppose) +elementrb* rbtree::returnMinKey(elementrb *z) { + elementrb *current; + + current = z; + // search to bottom-right corner of tree + while (current->left != leaf) { + current = current->left; + } + return current; +} + +elementrb* rbtree::returnSuccessor(elementrb *z) { + elementrb *current, *w; + + w = z; + // if right-subtree exists, return min of it + if (w->right != leaf) { + return returnMinKey(w->right); + } + // else search up in tree + current = w->parent; + while ((current != NULL) && (w == current->right)) { + w = current; + // move up in tree until find a non-right-child + current = current->parent; + } + return current; +} + +int rbtree::returnNodecount() { + return support; +} + +// ******** Insert Functions ********************************************* +// public insert function +void rbtree::insertItem(int newKey, int newValue) { + + // first we check to see if newKey is already present in the tree; + // if so, we do nothing; if not, we must find where to insert the + // key + elementrb *newNode, *current; + + // find newKey in tree; return pointer to it O(log k) + current = findItem(newKey); + if (current == NULL) { + newNode = new elementrb; // elementrb for the rbtree + newNode->key = newKey; + newNode->value = newValue; + newNode->color = true; // new nodes are always RED + newNode->parent = NULL; // new node initially has no parent + newNode->left = leaf; // left leaf + newNode->right = leaf; // right leaf + support++; // increment node count in rbtree + + // must now search for where to insert newNode, i.e., find the + // correct parent and set the parent and child to point to each + // other properly + current = root; + if (current->key == -1) { // insert as root + delete root; // delete old root + root = newNode; // set root to newNode + leaf->parent = newNode; // set leaf's parent + current = leaf; // skip next loop + } + + // search for insertion point + while (current != leaf) { + // left-or-right? + if (newKey < current->key) { + // try moving down-left + if (current->left != leaf) { + current = current->left; + } else { + // else found new parent + newNode->parent = current; // set parent + current->left = newNode; // set child + current = leaf; // exit search + } + } else { + // try moving down-right + if (current->right != leaf) { + current = current->right; + } else { + // else found new parent + newNode->parent = current; // set parent + current->right = newNode; // set child + current = leaf; // exit search + } + } + } + + // now do the house-keeping necessary to preserve the red-black + // properties + insertCleanup(newNode); + } + return; +} + +// private house-keeping function for insertion +void rbtree::insertCleanup(elementrb *z) { + + // fix now if z is root + if (z->parent == NULL) { + z->color = false; + return; + } + + elementrb *temp; + + // while z is not root and z's parent is RED + while (z->parent != NULL && z->parent->color) { + if (z->parent == z->parent->parent->left) { + + // z's parent is LEFT-CHILD + + temp = z->parent->parent->right; // grab z's uncle + if (temp->color) { + z->parent->color = false; // color z's parent BLACK (Case 1) + temp->color = false; // color z's uncle BLACK (Case 1) + z->parent->parent->color = true; // color z's grandpar. RED (Case 1) + z = z->parent->parent; // set z = z's grandparent (Case 1) + } else { + if (z == z->parent->right) { + // z is RIGHT-CHILD + z = z->parent; // set z = z's parent (Case 2) + rotateLeft(z); // perform left-rotation (Case 2) + } + z->parent->color = false; // color z's parent BLACK (Case 3) + z->parent->parent->color = true; // color z's grandpar. RED (Case 3) + rotateRight(z->parent->parent); // perform right-rotation (Case 3) + } + } else { + + // z's parent is RIGHT-CHILD + + temp = z->parent->parent->left; // grab z's uncle + if (temp->color) { + z->parent->color = false; // color z's parent BLACK (Case 1) + temp->color = false; // color z's uncle BLACK (Case 1) + z->parent->parent->color = true; // color z's grandpar. RED (Case 1) + z = z->parent->parent; // set z = z's grandparent (Case 1) + } else { + if (z == z->parent->left) { + // z is LEFT-CHILD + z = z->parent; // set z = z's parent (Case 2) + rotateRight(z); // perform right-rotation (Case 2) + } + z->parent->color = false; // color z's parent BLACK (Case 3) + z->parent->parent->color = true; // color z's grandpar. RED (Case 3) + rotateLeft(z->parent->parent); // perform left-rotation (Case 3) + } + } + } + + root->color = false; // color the root BLACK + return; +} + +// ******** Delete +// ******** Functions ********************************************* + +void rbtree::replaceItem(int key, int newValue) { + elementrb* ptr; + ptr = findItem(key); + ptr->value = newValue; + return; +} + +void rbtree::incrementValue(int key) { + elementrb* ptr; + ptr = findItem(key); + ptr->value = 1 + ptr->value; + return; +} + +// public delete function +void rbtree::deleteItem(int killKey) { + elementrb *x, *y, *z; + + z = findItem(killKey); + if (z == NULL) { + return; // item not present; bail out + } + + if (support == 1) { // attempt to delete the root + root->key = -1; // restore root node to default state + root->value = -1; + root->color = false; + root->parent = NULL; + root->left = leaf; + root->right = leaf; + support--; // set support to zero + return; // exit - no more work to do + } + + if (z != NULL) { + support--; // decrement node count + if ((z->left == leaf) || (z->right == leaf)) { + y = z; // case of less than two children, + // set y to be z + } else { + y = returnSuccessor(z); // set y to be z's key-successor + } + + if (y->left != leaf) { + x = y->left; // pick y's one child (left-child) + } else { + x = y->right; // (right-child) + } + x->parent = y->parent; // make y's child's parent be y's parent + + if (y->parent == NULL) { + root = x; // if y is the root, x is now root + } else { + if (y == y->parent->left) { // decide y's relationship with y's parent + y->parent->left = x; // replace x as y's parent's left child + } else { + y->parent->right = x; // replace x as y's parent's left child + } + } + + if (y != z) { // insert y into z's spot + z->key = y->key; // copy y data into z + z->value = y->value; + } + + // do house-keeping to maintain balance + if (y->color == false) { + deleteCleanup(x); + } + + delete y; + y = NULL; + } + + return; +} + +void rbtree::deleteCleanup(elementrb *x) { + elementrb *w, *t; + + // until x is the root, or x is RED + while ((x != root) && (x->color == false)) { + if (x == x->parent->left) { // branch on x being a LEFT-CHILD + w = x->parent->right; // grab x's sibling + if (w->color == true) { // if x's sibling is RED + w->color = false; // color w BLACK (case 1) + x->parent->color = true; // color x's parent RED (case 1) + rotateLeft(x->parent); // left rotation on x's parent (case 1) + w = x->parent->right; // make w be x's right sibling (case 1) + } + if ((w->left->color == false) && (w->right->color == false)) { + w->color = true; // color w RED (case 2) + x = x->parent; // examine x's parent (case 2) + } else { + if (w->right->color == false) { + w->left->color = false; // color w's left child BLACK (case 3) + w->color = true; // color w RED (case 3) + t = x->parent; // store x's parent (case 3) + rotateRight(w); // right rotation on w (case 3) + x->parent = t; // restore x's parent (case 3) + w = x->parent->right; // make w be x's right sibling (case 3) + } + w->color = x->parent->color; // w's color := x's parent's (case 4) + x->parent->color = false; // color x's parent BLACK (case 4) + w->right->color = false; // color w's right child BLACK (case 4) + rotateLeft(x->parent); // left rotation on x's parent (case 4) + x = root; // finished work. bail out (case 4) + } + } else { // x is RIGHT-CHILD + w = x->parent->left; // grab x's sibling + if (w->color == true) { // if x's sibling is RED + w->color = false; // color w BLACK (case 1) + x->parent->color = true; // color x's parent RED (case 1) + rotateRight(x->parent); // right rotation on x's parent (case 1) + w = x->parent->left; // make w be x's left sibling (case 1) + } + if ((w->right->color == false) && (w->left->color == false)) { + w->color = true; // color w RED (case 2) + x = x->parent; // examine x's parent (case 2) + } else { + if (w->left->color == false) { + w->right->color = false; // color w's right child BLACK (case 3) + w->color = true; // color w RED (case 3) + t = x->parent; // store x's parent (case 3) + rotateLeft(w); // left rotation on w (case 3) + x->parent = t; // restore x's parent (case 3) + w = x->parent->left; // make w be x's left sibling (case 3) + } + w->color = x->parent->color; // w's color := x's parent's (case 4) + x->parent->color = false; // color x's parent BLACK (case 4) + w->left->color = false; // color w's left child BLACK (case 4) + rotateRight(x->parent); // right rotation on x's parent (case 4) + x = root; // x is now the root (case 4) + } + } + } + x->color = false; // color x (the root) BLACK (exit) + + return; +} + +// ******** Rotation Functions ****************************************** + +void rbtree::rotateLeft(elementrb *x) { + elementrb *y; + // do pointer-swapping operations for left-rotation + y = x->right; // grab right child + x->right = y->left; // make x's RIGHT-CHILD be y's LEFT-CHILD + y->left->parent = x; // make x be y's LEFT-CHILD's parent + y->parent = x->parent; // make y's new parent be x's old parent + + if (x->parent == NULL) { + root = y; // if x was root, make y root + } else { + // if x is LEFT-CHILD, make y be x's parent's + if (x == x->parent->left) { + x->parent->left = y; // left-child + } else { + x->parent->right = y; // right-child + } + } + y->left = x; // make x be y's LEFT-CHILD + x->parent = y; // make y be x's parent + + return; +} + +void rbtree::rotateRight(elementrb *y) { + elementrb *x; + // do pointer-swapping operations for right-rotation + x = y->left; // grab left child + y->left = x->right; // replace left child yith x's right subtree + x->right->parent = y; // replace y as x's right subtree's parent + + x->parent = y->parent; // make x's new parent be y's old parent + + // if y was root, make x root + if (y->parent == NULL) { + root = x; + } else { + // if y is RIGHT-CHILD, make x be y's parent's + if (y == y->parent->right) { + // right-child + y->parent->right = x; + } else { + // left-child + y->parent->left = x; + } + } + x->right = y; // make y be x's RIGHT-CHILD + y->parent = x; // make x be y's parent + + return; +} + +// *********************************************************************** +// *** COPYRIGHT NOTICE ************************************************** +// dendro.h - hierarchical random graph (hrg) data structure +// Copyright (C) 2005-2009 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// *********************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | +// http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science +// AND Santa Fe Institute +// Created : 26 October 2005 - 7 December 2005 +// Modified : 23 December 2007 (cleaned up for public consumption) +// +// *********************************************************************** +// +// Maximum likelihood dendrogram data structure. This is the heart of +// the HRG algorithm: all manipulations are done here and all data is +// stored here. The data structure uses the separate graph data +// structure to store the basic adjacency information (in a +// dangerously mutable way). +// +// *********************************************************************** + +// ******** Dendrogram Methods ******************************************* + +dendro::dendro(): root(0), internal(0), leaf(0), d(0), splithist(0), + paths(0), ctree(0), cancestor(0), g(0) { } +dendro::~dendro() { + list *curr, *prev; + + if (g) { + delete g; // O(m) + g = 0; + } + if (internal) { + delete [] internal; // O(n) + internal = 0; + } + if (leaf) { + delete [] leaf; // O(n) + leaf = 0; + } + if (d) { + delete d; // O(n) + d = 0; + } + if (splithist) { + delete splithist; // potentially long + splithist = 0; + } + + if (paths) { + for (int i = 0; i < n; i++) { + curr = paths[i]; + while (curr) { + prev = curr; + curr = curr->next; + delete prev; + prev = 0; + } + paths[i] = 0; + } + delete [] paths; + } + paths = 0; + + if (ctree) { + delete [] ctree; // O(n) + ctree = 0; + } + if (cancestor) { + delete [] cancestor; // O(n) + cancestor = 0; + } +} + +// ********************************************************************* + +void dendro::binarySearchInsert(elementd* x, elementd* y) { + if (y->p < x->p) { // go to left subtree + if (x->L == NULL) { // check if left subtree is empty + x->L = y; // make x left child + y->M = x; // make y parent of child + return; + } else { + binarySearchInsert(x->L, y); + } + } else { // go to right subtree + if (x->R == NULL) { // check if right subtree is empty + x->R = y; // make x right child + y->M = x; // make y parent of child + return; + } else { + binarySearchInsert(x->R, y); + } + } + return; +} + +// ********************************************************************** + +list* dendro::binarySearchFind(const double v) { + list *head = NULL, *tail = NULL, *newlist; + elementd *current = root; + bool flag_stopSearch = false; + + while (!flag_stopSearch) { // continue until we're finished + newlist = new list; // add this node to the path + newlist->x = current->label; + if (current == root) { + head = newlist; tail = head; + } else { + tail->next = newlist; tail = newlist; + } + if (v < current->p) { // now try left subtree + if (current->L->type == GRAPH) { + flag_stopSearch = true; + } else { + current = current->L; + } + } else { // else try right subtree + if (current->R->type == GRAPH) { + flag_stopSearch = true; + } else { + current = current->R; + } + } + } + return head; +} + +// *********************************************************************** + +string dendro::buildSplit(elementd* thisNode) { + // A "split" is defined as the bipartition of vertices into the sets + // of leaves below the internal vertex in the tree (denoted by "C"), + // and those above it (denoted as "M"). For simplicity, we represent + // this bipartition as a character string of length n, where the ith + // character denotes the partition membership (C,M) of the ith leaf + // node. + + bool flag_go = true; + const short int k = 1 + DENDRO + GRAPH; + elementd* curr; + split sp; + + sp.initializeSplit(n); // default split string O(n) + + curr = thisNode; // - set start node as top this sub-tree + curr->type = k + 1; // - initialize in-order tree traversal + while (flag_go) { + + // - is it time, and is left child a graph node? + if (curr->type == k + 1 && curr->L->type == GRAPH) { + sp.s[curr->L->index] = 'C'; // - mark this leaf + curr->type = k + 2; + } + + // - is it time, and is right child a graph node? + if (curr->type == k + 2 && curr->R->type == GRAPH) { + sp.s[curr->R->index] = 'C'; // - mark this leaf + curr->type = k + 3; + } + if (curr->type == k + 1) { // - go left + curr->type = k + 2; + curr = curr->L; + curr->type = k + 1; + } else if (curr->type == k + 2) { // - else go right + curr->type = k + 3; + curr = curr->R; + curr->type = k + 1; + } else { // - else go up a level + curr->type = DENDRO; + if (curr->index == thisNode->index || curr->M == NULL) { + flag_go = false; curr = NULL; + } else { + curr = curr->M; + } + } + } + + // any leaf that was not already marked must be in the remainder of + // the tree + for (int i = 0; i < n; i++) { + if (sp.s[i] != 'C') { + sp.s[i] = 'M'; + } + } + + return sp.s; +} + +// ********************************************************************** + +void dendro::buildDendrogram() { + + /* the initialization of the dendrogram structure goes like this: + * 1) we allocate space for the n-1 internal nodes of the + * dendrogram, and then the n leaf nodes + * 2) we build a random binary tree structure out of the internal + * nodes by assigning each a uniformly random value over [0,1] and + * then inserting it into the tree according to the + * binary-search rule. + * 3) next, we make a random permutation of the n leaf nodes and add + * them to the dendrogram D by replacing the emptpy spots in-order + * 4) then, we compute the path from the root to each leaf and store + * that in each leaf (this is prep work for the next step) + * 5) finally, we compute the values for nL, nR, e (and thus p) and + * the label for each internal node by allocating each of the m + * edges in g to the appropriate internal node + */ + + // --- Initialization and memory allocation for data structures + // After allocating the memory for D and G, we need to mark the + // nodes for G as being non-internal vertices, and then insert them + // into a random binary tree structure. For simplicity, we make the + // first internal node in the array the root. + + n = g->numNodes(); // size of graph + leaf = new elementd [n]; // allocate memory for G, O(n) + internal = new elementd [n - 1]; // allocate memory for D, O(n) + d = new interns(n - 2); // allocate memory for internal + // edges of D, O(n) + for (int i = 0; i < n; i++) { // initialize leaf nodes + leaf[i].type = GRAPH; + leaf[i].label = i; + leaf[i].index = i; + leaf[i].n = 1; + } + +// initialize internal nodes + root = &internal[0]; + root->label = 0; + root->index = 0; + root->p = RNG_UNIF01(); + + // insert remaining internal vertices, O(n log n) + for (int i = 1; i < (n - 1); i++) { + internal[i].label = i; + internal[i].index = i; + internal[i].p = RNG_UNIF01(); + binarySearchInsert(root, &internal[i]); + } + + // --- Hang leaf nodes off end of dendrogram O(n log n) + // To impose this random hierarchical relationship on G, we first + // take a random permutation of the leaf vertices and then replace + // the NULLs at the bottom of the tree in-order with the leafs. As a + // hack to ensure that we can find the leafs later using a binary + // search, we assign each of them the p value of their parent, + // perturbed slightly so as to preserve the binary search property. + + block* array; array = new block [n]; + for (int i = 0; i < n; i++) { + array[i].x = RNG_UNIF01(); + array[i].y = i; + } + QsortMain(array, 0, n - 1); + + int k = 0; // replace NULLs with leaf nodes, and + for (int i = 0; i < (n - 1); i++) { // maintain binary search property, O(n) + if (internal[i].L == NULL) { + internal[i].L = &leaf[array[k].y]; + leaf[array[k].y].M = &internal[i]; + leaf[array[k++].y].p = internal[i].p - 0.0000000000001; + } + if (internal[i].R == NULL) { + internal[i].R = &leaf[array[k].y]; + leaf[array[k].y].M = &internal[i]; + leaf[array[k++].y].p = internal[i].p + 0.0000000000001; + } + } + delete [] array; + + // --- Compute the path from root -> leaf for each leaf O(n log n) + // Using the binary search property, we can find each leaf node in + // O(log n) time. The binarySearchFind() function returns the list + // of internal node indices that the search crossed, in the order of + // root -> ... -> leaf, for use in the subsequent few operations. + + if (paths != NULL) { + list *curr, *prev; + for (int i = 0; i < n; i++) { + curr = paths[i]; + while (curr != NULL) { + prev = curr; + curr = curr->next; + delete prev; + prev = NULL; + } + paths[i] = NULL; + } + delete [] paths; + } + paths = NULL; + paths = new list* [n]; + for (int i = 0; i < n; i++) { + paths[i] = binarySearchFind(leaf[i].p); + } + + // --- Count e for each internal node O(m) + // To count the number of edges that span the L and R subtrees for + // each internal node, we use the path information we just + // computed. Then, we loop over all edges in G and find the common + // ancestor in D of the two endpoints and increment that internal + // node's e count. This process takes O(m) time because in a roughly + // balanced binary tree (given by our random dendrogram), the vast + // majority of vertices take basically constant time to find their + // common ancestor. Note that because our adjacency list is + // symmetric, we overcount each e by a factor of 2, so we need to + // correct this after. + + elementd* ancestor; edge* curr; + for (int i = 0; i < (n - 1); i++) { + internal[i].e = 0; + internal[i].label = -1; + } + for (int i = 0; i < n; i++) { + curr = g->getNeighborList(i); + while (curr != NULL) { + ancestor = findCommonAncestor(paths, i, curr->x); + ancestor->e += 1; + curr = curr->next; + } + } + for (int i = 0; i < (n - 1); i++) { + internal[i].e /= 2; + } + + // --- Count n for each internal node O(n log n) + // To tabulate the number of leafs in each subtree rooted at an + // internal node, we use the path information computed above. + for (int i = 0; i < n; i++) { + ancestor = &leaf[i]; + ancestor = ancestor->M; + while (ancestor != NULL) { + ancestor->n++; + ancestor = ancestor->M; + } + } + + // --- Label all internal vertices O(n log n) + // We want to label each internal vertex with the smallest leaf + // index of its children. This will allow us to collapse many + // leaf-orderings into a single dendrogram structure that is + // independent of child-exhanges (since these have no impact on the + // likelihood of the hierarchical structure). To do this, we loop + // over the leaf vertices from smallest to largest and walk along + // that leaf's path from the root. If we find an unlabeled internal + // node, then we mark it with this leaf's index. + + for (int i = 0; i < n; i++) { + ancestor = &leaf[i]; + while (ancestor != NULL) { + if (ancestor->label == -1 || ancestor->label > leaf[i].label) { + ancestor->label = leaf[i].label; + } + ancestor = ancestor->M; + } + } + + // --- Exchange children to enforce order-property O(n) + // We state that the order-property requires that an internal node's + // label is the smallest index of its left subtree. The dendrogram + // so far doesn't reflect this, so we need to step through each + // internal vertex and make that adjustment (swapping nL and nR if + // we make a change). + + elementd *tempe; + for (int i = 0; i < (n - 1); i++) { + if (internal[i].L->label > internal[i].label) { + tempe = internal[i].L; + internal[i].L = internal[i].R; + internal[i].R = tempe; + } + } + + // --- Tabulate internal dendrogram edges O(n^2) + // For the MCMC moves later on, we'll need to be able to choose, + // uniformly at random, an internal edge of the dendrogram to + // manipulate. There are always n-2 of them, and we can find them + // simply by scanning across the internal vertices and observing + // which have children that are also internal vertices. Note: very + // important that the order property be enforced before this step is + // taken; otherwise, the internal edges wont reflect the actual + // dendrogram structure. + + for (int i = 0; i < (n - 1); i++) { + if (internal[i].L->type == DENDRO) { + d->addEdge(i, internal[i].L->index, LEFT); + } + if (internal[i].R->type == DENDRO) { + d->addEdge(i, internal[i].R->index, RIGHT); + } + } + + // --- Clear memory for paths O(n log n) + // Now that we're finished using the paths, we need to deallocate + // them manually. + + list *current, *previous; + for (int i = 0; i < n; i++) { + current = paths[i]; + while (current) { + previous = current; + current = current->next; + delete previous; + previous = NULL; + } + paths[i] = NULL; + } + delete [] paths; + paths = NULL; + + // --- Compute p_i for each internal node O(n) + // Each internal node's p_i = e_i / (nL_i*nR_i), and now that we + // have each of those pieces, we may calculate this value for each + // internal node. Given these, we can then calculate the + // log-likelihood of the entire dendrogram structure \log(L) = + // \sum_{i=1}^{n} ( ( e_i \log[p_i] ) + ( (nL_i*nR_i - e_i) + // \log[1-p_i] ) ) + + L = 0.0; double dL; + int nL_nR, ei; + for (int i = 0; i < (n - 1); i++) { + nL_nR = internal[i].L->n * internal[i].R->n; + ei = internal[i].e; + internal[i].p = (double)(ei) / (double)(nL_nR); + if (ei == 0 || ei == nL_nR) { + dL = 0.0; + } else { + dL = ei * log(internal[i].p) + (nL_nR - ei) * log(1.0 - internal[i].p); + } + internal[i].logL = dL; + L += dL; + } + + for (int i = 0; i < (n - 1); i++) { + if (internal[i].label > internal[i].L->label) { + tempe = internal[i].L; + internal[i].L = internal[i].R; + internal[i].R = tempe; + } + } + + // Dendrogram is now built + + return; +} + +// *********************************************************************** + +void dendro::clearDendrograph() { + // Clear out the memory and references used by the dendrograph + // structure - this is intended to be called just before an + // importDendrogramStructure call so as to avoid memory leaks and + // overwriting the references therein. + + if (g != NULL) { + delete g; // O(m) + g = NULL; + } + if (leaf != NULL) { + delete [] leaf; // O(n) + leaf = NULL; + } + if (internal != NULL) { + delete [] internal; // O(n) + internal = NULL; + } + if (d != NULL) { + delete d; // O(n) + d = NULL; + } + root = NULL; + + return; +} + +// ********************************************************************** + +int dendro::computeEdgeCount(const int a, const short int atype, + const int b, const short int btype) { + // This function computes the number of edges that cross between the + // subtree internal[a] and the subtree internal[b]. To do this, we + // use an array A[1..n] integers which take values -1 if A[i] is in + // the subtree defined by internal[a], +1 if A[i] is in the subtree + // internal[b], and 0 otherwise. Taking the smaller of the two sets, + // we then scan over the edges attached to that set of vertices and + // count the number of endpoints we see in the other set. + + bool flag_go = true; + int nA, nB; + int count = 0; + const short int k = 1 + DENDRO + GRAPH; + + elementd* curr; + + // First, we push the leaf nodes in the L and R subtrees into + // balanced binary tree structures so that we can search them + // quickly later on. + + if (atype == GRAPH) { + // default case, subtree A is size 1 + // insert single node as member of left subtree + subtreeL.insertItem(a, -1); + nA = 1; // + } else { + // explore subtree A, O(|A|) + curr = &internal[a]; + curr->type = k + 1; + nA = 0; + while (flag_go) { + if (curr->index == internal[a].M->index) { + internal[a].type = DENDRO; + flag_go = false; + } else { + // - is it time, and is left child a graph node? + if (curr->type == k + 1 && curr->L->type == GRAPH) { + subtreeL.insertItem(curr->L->index, -1); + curr->type = k + 2; + nA++; + } + // - is it time, and is right child a graph node? + if (curr->type == k + 2 && curr->R->type == GRAPH) { + subtreeL.insertItem(curr->R->index, -1); + curr->type = k + 3; + nA++; + } + if (curr->type == k + 1) { // - go left + curr->type = k + 2; + curr = curr->L; + curr->type = k + 1; + } else if (curr->type == k + 2) { // - else go right + curr->type = k + 3; + curr = curr->R; + curr->type = k + 1; + } else { // - else go up a level + curr->type = DENDRO; + curr = curr->M; + if (curr == NULL) { + flag_go = false; + } + } + } + } + } + + if (btype == GRAPH) { + // default case, subtree A is size 1 + // insert node as single member of right subtree + subtreeR.insertItem(b, 1); + nB = 1; + } else { + flag_go = true; + // explore subtree B, O(|B|) + curr = &internal[b]; + curr->type = k + 1; + nB = 0; + while (flag_go) { + if (curr->index == internal[b].M->index) { + internal[b].type = DENDRO; + flag_go = false; + } else { + // - is it time, and is left child a graph node? + if (curr->type == k + 1 && curr->L->type == GRAPH) { + subtreeR.insertItem(curr->L->index, 1); + curr->type = k + 2; + nB++; + } + // - is it time, and is right child a graph node? + if (curr->type == k + 2 && curr->R->type == GRAPH) { + subtreeR.insertItem(curr->R->index, 1); + curr->type = k + 3; + nB++; + } + if (curr->type == k + 1) { // - look left + curr->type = k + 2; + curr = curr->L; + curr->type = k + 1; + } else if (curr->type == k + 2) { // - look right + curr->type = k + 3; + curr = curr->R; + curr->type = k + 1; + } else { // - else go up a level + curr->type = DENDRO; + curr = curr->M; + if (curr == NULL) { + flag_go = false; + } + } + } + } + } + + // Now, we take the smaller subtree and ask how many of its + // emerging edges have their partner in the other subtree. O(|A| log + // |A|) time + + edge* current; + int* treeList; + if (nA < nB) { + // subtreeL is smaller + treeList = subtreeL.returnArrayOfKeys(); + for (int i = 0; i < nA; i++) { + current = g->getNeighborList(treeList[i]); + // loop over each of its neighbors v_j + while (current != NULL) { + // to see if v_j is in A + if (subtreeR.findItem(current->x) != NULL) { + count++; + } + current = current->next; + } + subtreeL.deleteItem(treeList[i]); + } + delete [] treeList; + treeList = subtreeR.returnArrayOfKeys(); + for (int i = 0; i < nB; i++) { + subtreeR.deleteItem(treeList[i]); + } + delete [] treeList; + } else { + // subtreeR is smaller + treeList = subtreeR.returnArrayOfKeys(); + for (int i = 0; i < nB; i++) { + current = g->getNeighborList(treeList[i]); + // loop over each of its neighbors v_j + while (current != NULL) { + // to see if v_j is in B + if (subtreeL.findItem(current->x) != NULL) { + count++; + } + current = current->next; + } + subtreeR.deleteItem(treeList[i]); + } + delete [] treeList; + treeList = subtreeL.returnArrayOfKeys(); + for (int i = 0; i < nA; i++) { + subtreeL.deleteItem(treeList[i]); + } + delete [] treeList; + } + + return count; +} + +// *********************************************************************** + +int dendro::countChildren(const string s) { + int len = s.size(); + int numC = 0; + for (int i = 0; i < len; i++) { + if (s[i] == 'C') { + numC++; + } + } + return numC; +} + +// *********************************************************************** + +void dendro::cullSplitHist() { + string* array; + int tot, leng; + + array = splithist->returnArrayOfKeys(); + tot = splithist->returnTotal(); + leng = splithist->returnNodecount(); + for (int i = 0; i < leng; i++) { + if ((splithist->returnValue(array[i]) / tot) < 0.5) { + splithist->deleteItem(array[i]); + } + } + delete [] array; array = NULL; + + return; +} + +// ********************************************************************** + +elementd* dendro::findCommonAncestor(list** paths, const int i, const int j) { + list* headOne = paths[i]; + list* headTwo = paths[j]; + elementd* lastStep = NULL; + while (headOne->x == headTwo->x) { + lastStep = &internal[headOne->x]; + headOne = headOne->next; + headTwo = headTwo->next; + if (headOne == NULL || headTwo == NULL) { + break; + } + } + return lastStep; // Returns address of an internal node; do not deallocate +} + +// ********************************************************************** + +int dendro::getConsensusSize() { + string *array; + double value, tot; + int numSplits, numCons; + numSplits = splithist->returnNodecount(); + array = splithist->returnArrayOfKeys(); + tot = splithist->returnTotal(); + numCons = 0; + for (int i = 0; i < numSplits; i++) { + value = splithist->returnValue(array[i]); + if (value / tot > 0.5) { + numCons++; + } + } + delete [] array; array = NULL; + return numCons; +} + +// ********************************************************************** + +splittree* dendro::getConsensusSplits() { + string *array; + splittree *consensusTree; + double value, tot; + consensusTree = new splittree; + int numSplits; + + // We look at all of the splits in our split histogram and add any + // one that's in the majority to our consensusTree, which we then + // return (note that consensusTree needs to be deallocated by the + // user). + numSplits = splithist->returnNodecount(); + array = splithist->returnArrayOfKeys(); + tot = splithist->returnTotal(); + for (int i = 0; i < numSplits; i++) { + value = splithist->returnValue(array[i]); + if (value / tot > 0.5) { + consensusTree->insertItem(array[i], value / tot); + } + } + delete [] array; array = NULL; + return consensusTree; +} + +// *********************************************************************** + +double dendro::getLikelihood() { + return L; +} + +// *********************************************************************** + +void dendro::getSplitList(splittree* split_tree) { + string sp; + for (int i = 0; i < (n - 1); i++) { + sp = d->getSplit(i); + if (!sp.empty() && sp[1] != '-') { + split_tree->insertItem(sp, 0.0); + } + } + return; +} + +// *********************************************************************** + +double dendro::getSplitTotalWeight() { + if (splithist) { + return splithist->returnTotal(); + } else { + return 0; + } +} + +// *********************************************************************** + +bool dendro::importDendrogramStructure(const igraph_hrg_t *hrg) { + n = igraph_hrg_size(hrg); + + // allocate memory for G, O(n) + leaf = new elementd[n]; + // allocate memory for D, O(n) + internal = new elementd[n - 1]; + // allocate memory for internal edges of D, O(n) + d = new interns(n - 2); + + // initialize leaf nodes + for (int i = 0; i < n; i++) { + leaf[i].type = GRAPH; + leaf[i].label = i; + leaf[i].index = i; + leaf[i].n = 1; + } + + // initialize internal nodes + root = &internal[0]; + root->label = 0; + for (int i = 1; i < n - 1; i++) { + internal[i].index = i; + internal[i].label = -1; + } + + // import basic structure from hrg object, O(n) + for (int i = 0; i < n - 1; i++) { + int L = VECTOR(hrg->left)[i]; + int R = VECTOR(hrg->right)[i]; + + if (L < 0) { + internal[i].L = &internal[-L - 1]; + internal[-L - 1].M = &internal[i]; + } else { + internal[i].L = &leaf[L]; + leaf[L].M = &internal[i]; + } + + if (R < 0) { + internal[i].R = &internal[-R - 1]; + internal[-R - 1].M = &internal[i]; + } else { + internal[i].R = &leaf[R]; + leaf[R].M = &internal[i]; + } + + internal[i].p = VECTOR(hrg->prob)[i]; + internal[i].e = VECTOR(hrg->edges)[i]; + internal[i].n = VECTOR(hrg->vertices)[i]; + internal[i].index = i; + } + + // --- Label all internal vertices O(n log n) + elementd *curr; + for (int i = 0; i < n; i++) { + curr = &leaf[i]; + while (curr) { + if (curr->label == -1 || curr->label > leaf[i].label) { + curr->label = leaf[i].label; + } + curr = curr -> M; + } + } + + // --- Exchange children to enforce order-property O(n) + elementd *tempe; + for (int i = 0; i < n - 1; i++) { + if (internal[i].L->label > internal[i].label) { + tempe = internal[i].L; + internal[i].L = internal[i].R; + internal[i].R = tempe; + } + } + + // --- Tabulate internal dendrogram edges O(n) + for (int i = 0; i < (n - 1); i++) { + if (internal[i].L->type == DENDRO) { + d->addEdge(i, internal[i].L->index, LEFT); + } + if (internal[i].R->type == DENDRO) { + d->addEdge(i, internal[i].R->index, RIGHT); + } + } + + // --- Compute p_i for each internal node O(n) + // Each internal node's p_i = e_i / (nL_i*nR_i), and now that we + // have each of those pieces, we may calculate this value for each + // internal node. Given these, we can then calculate the + // log-likelihood of the entire dendrogram structure + // \log(L) = \sum_{i=1}^{n} ( ( e_i \log[p_i] ) + + // ( (nL_i*nR_i - e_i) \log[1-p_i] ) ) + L = 0.0; double dL; + int nL_nR, ei; + for (int i = 0; i < (n - 1); i++) { + nL_nR = internal[i].L->n * internal[i].R->n; + ei = internal[i].e; + if (ei == 0 || ei == nL_nR) { + dL = 0.0; + } else { + dL = (double)(ei) * log(internal[i].p) + + (double)(nL_nR - ei) * log(1.0 - internal[i].p); + } + internal[i].logL = dL; + L += dL; + } + + return true; +} + +// *********************************************************************** + +void dendro::makeRandomGraph() { + if (g != NULL) { + delete g; + } g = NULL; g = new graph(n); + + list *curr, *prev; + if (paths) { + for (int i = 0; i < n; i++) { + curr = paths[i]; + while (curr != NULL) { + prev = curr; + curr = curr->next; + delete prev; + prev = NULL; + } + paths[i] = NULL; + } + delete [] paths; + } +// build paths from root O(n d) + paths = new list* [n]; + for (int i = 0; i < n; i++) { + paths[i] = reversePathToRoot(i); + } + + elementd* commonAncestor; +// O((h+d)*n^2) - h: height of D; d: average degree in G + for (int i = 0; i < n; i++) { + // decide neighbors of v_i + for (int j = (i + 1); j < n; j++) { + commonAncestor = findCommonAncestor(paths, i, j); + if (RNG_UNIF01() < commonAncestor->p) { + if (!(g->doesLinkExist(i, j))) { + g->addLink(i, j); + } + if (!(g->doesLinkExist(j, i))) { + g->addLink(j, i); + } + } + } + } + + for (int i = 0; i < n; i++) { + curr = paths[i]; + while (curr != NULL) { + prev = curr; + curr = curr->next; + delete prev; + prev = NULL; + } + paths[i] = NULL; + } + delete [] paths; // delete paths data structure O(n log n) + paths = NULL; + + return; +} + +// ********************************************************************** + +bool dendro::monteCarloMove(double& delta, bool& ftaken, const double T) { + // A single MC move begins with the selection of a random internal + // edge (a,b) of the dendrogram. This also determines the three + // subtrees i, j, k that we will rearrange, and we choose uniformly + // from among the options. + // + // If (a,b) is a left-edge, then we have ((i,j),k), and moves + // ((i,j),k) -> ((i,k),j) (alpha move) + // -> (i,(j,k)) + enforce order-property for (j,k) (beta move) + // + // If (a,b) is a right-edge, then we have (i,(j,k)), and moves + // (i,(j,k)) -> ((i,k),j) (alpha move) + // -> ((i,j),k) (beta move) + // + // For each of these moves, we need to know what the change in + // likelihood will be, so that we can determine with what + // probability we execute the move. + + elementd *temp; + ipair *tempPair; + int x, y, e_x, e_y, n_i, n_j, n_k, n_x, n_y; + short int t; + double p_x, p_y, L_x, L_y, dLogL; + string new_split; + + // The remainder of the code executes a single MCMC move, where we + // sample the dendrograms proportionally to their likelihoods (i.e., + // temperature=1, if you're comparing it to the usual MCMC + // framework). + + delta = 0.0; + ftaken = false; + tempPair = d->getRandomEdge(); // returns address; no need to deallocate + x = tempPair->x; // copy contents of referenced random edge + y = tempPair->y; // into local variables + t = tempPair->t; + + if (t == LEFT) { + if (RNG_UNIF01() < 0.5) { // ## LEFT ALPHA move: ((i,j),k) -> ((i,k),j) + // We need to calculate the change in the likelihood (dLogL) + // that would result from this move. Most of the information + // needed to do this is already available, the exception being + // e_ik, the number of edges that span the i and k subtrees. I + // use a slow algorithm O(n) to do this, since I don't know of a + // better way at this point. (After several attempts to find a + // faster method, no luck.) + + n_i = internal[y].L->n; + n_j = internal[y].R->n; + n_k = internal[x].R->n; + + n_y = n_i * n_k; + e_y = computeEdgeCount(internal[y].L->index, internal[y].L->type, + internal[x].R->index, internal[x].R->type); + p_y = (double)(e_y) / (double)(n_y); + if (e_y == 0 || e_y == n_y) { + L_y = 0.0; + } else { + L_y = (double)(e_y) * log(p_y) + (double)(n_y - e_y) * log(1.0 - p_y); + } + + n_x = (n_i + n_k) * n_j; + e_x = internal[x].e + internal[y].e - e_y; // e_yj + p_x = (double)(e_x) / (double)(n_x); + if (e_x == 0 || e_x == n_x) { + L_x = 0.0; + } else { + L_x = (double)(e_x) * log(p_x) + (double)(n_x - e_x) * log(1.0 - p_x); + } + + dLogL = (L_x - internal[x].logL) + (L_y - internal[y].logL); + if ((dLogL > 0.0) || (RNG_UNIF01() < exp(T * dLogL))) { + + // make LEFT ALPHA move + + ftaken = true; + d->swapEdges(x, internal[x].R->index, RIGHT, y, + internal[y].R->index, RIGHT); + temp = internal[x].R; // - swap j and k + internal[x].R = internal[y].R; + internal[y].R = temp; + internal[x].R->M = &internal[x]; // - adjust parent pointers + internal[y].R->M = &internal[y]; + internal[y].n = n_i + n_k; // - update n for [y] + internal[x].e = e_x; // - update e_i for [x] and [y] + internal[y].e = e_y; + internal[x].p = p_x; // - update p_i for [x] and [y] + internal[y].p = p_y; + internal[x].logL = L_x; // - update L_i for [x] and [y] + internal[y].logL = L_y; + // - order-property maintained + L += dLogL; // - update LogL + delta = dLogL; + + } + } else { + + // ## LEFT BETA move: ((i,j),k) -> (i,(j,k)) + + n_i = internal[y].L->n; + n_j = internal[y].R->n; + n_k = internal[x].R->n; + + n_y = n_j * n_k; + e_y = computeEdgeCount(internal[y].R->index, internal[y].R->type, + internal[x].R->index, internal[x].R->type); + p_y = (double)(e_y) / (double)(n_y); + if (e_y == 0 || e_y == n_y) { + L_y = 0.0; + } else { + L_y = (double)(e_y) * log(p_y) + + (double)(n_y - e_y) * log(1.0 - p_y); + } + + n_x = (n_j + n_k) * n_i; + e_x = internal[x].e + internal[y].e - e_y; // e_yj + p_x = (double)(e_x) / (double)(n_x); + if (e_x == 0 || e_x == n_x) { + L_x = 0.0; + } else { + L_x = (double)(e_x) * log(p_x) + (double)(n_x - e_x) * log(1.0 - p_x); + } + + dLogL = (L_x - internal[x].logL) + (L_y - internal[y].logL); + if ((dLogL > 0.0) || (RNG_UNIF01() < exp(T * dLogL))) { + + // make LEFT BETA move + + ftaken = true; + d->swapEdges(y, internal[y].L->index, LEFT, y, + internal[y].R->index, RIGHT); + temp = internal[y].L; // - swap L and R of [y] + internal[y].L = internal[y].R; + internal[y].R = temp; + d->swapEdges(x, internal[x].R->index, RIGHT, + y, internal[y].R->index, RIGHT); + temp = internal[x].R; // - swap i and k + internal[x].R = internal[y].R; + internal[y].R = temp; + internal[x].R->M = &internal[x]; // - adjust parent pointers + internal[y].R->M = &internal[y]; + d->swapEdges(x, internal[x].L->index, LEFT, + x, internal[x].R->index, RIGHT); + temp = internal[x].L; // - swap L and R of [x] + internal[x].L = internal[x].R; + internal[x].R = temp; + internal[y].n = n_j + n_k; // - update n + internal[x].e = e_x; // - update e_i + internal[y].e = e_y; + internal[x].p = p_x; // - update p_i + internal[y].p = p_y; + internal[x].logL = L_x; // - update logL_i + internal[y].logL = L_y; + if (internal[y].R->label < internal[y].L->label) { + // - enforce order-property if necessary + d->swapEdges(y, internal[y].L->index, LEFT, + y, internal[y].R->index, RIGHT); + temp = internal[y].L; + internal[y].L = internal[y].R; + internal[y].R = temp; + } // + internal[y].label = internal[y].L->label; + L += dLogL; // - update LogL + delta = dLogL; + } + } + } else { + + // right-edge: t == RIGHT + + if (RNG_UNIF01() < 0.5) { + + // alpha move: (i,(j,k)) -> ((i,k),j) + + n_i = internal[x].L->n; + n_j = internal[y].L->n; + n_k = internal[y].R->n; + + n_y = n_i * n_k; + e_y = computeEdgeCount(internal[x].L->index, internal[x].L->type, + internal[y].R->index, internal[y].R->type); + p_y = (double)(e_y) / (double)(n_y); + if (e_y == 0 || e_y == n_y) { + L_y = 0.0; + } else { + L_y = (double)(e_y) * log(p_y) + (double)(n_y - e_y) * log(1.0 - p_y); + } + + n_x = (n_i + n_k) * n_j; + e_x = internal[x].e + internal[y].e - e_y; // e_yj + p_x = (double)(e_x) / (double)(n_x); + if (e_x == 0 || e_x == n_x) { + L_x = 0.0; + } else { + L_x = (double)(e_x) * log(p_x) + (double)(n_x - e_x) * log(1.0 - p_x); + } + + dLogL = (L_x - internal[x].logL) + (L_y - internal[y].logL); + if ((dLogL > 0.0) || (RNG_UNIF01() < exp(T * dLogL))) { + + // make RIGHT ALPHA move + + ftaken = true; + d->swapEdges(x, internal[x].L->index, LEFT, + x, internal[x].R->index, RIGHT); + temp = internal[x].L; // - swap L and R of [x] + internal[x].L = internal[x].R; + internal[x].R = temp; + d->swapEdges(y, internal[y].L->index, LEFT, + x, internal[x].R->index, RIGHT); + temp = internal[y].L; // - swap i and j + internal[y].L = internal[x].R; + internal[x].R = temp; + internal[x].R->M = &internal[x]; // - adjust parent pointers + internal[y].L->M = &internal[y]; + internal[y].n = n_i + n_k; // - update n + internal[x].e = e_x; // - update e_i + internal[y].e = e_y; + internal[x].p = p_x; // - update p_i + internal[y].p = p_y; + internal[x].logL = L_x; // - update logL_i + internal[y].logL = L_y; + internal[y].label = internal[x].label; // - update order property + L += dLogL; // - update LogL + delta = dLogL; + } + } else { + + // beta move: (i,(j,k)) -> ((i,j),k) + + n_i = internal[x].L->n; + n_j = internal[y].L->n; + n_k = internal[y].R->n; + + n_y = n_i * n_j; + e_y = computeEdgeCount(internal[x].L->index, internal[x].L->type, + internal[y].L->index, internal[y].L->type); + p_y = (double)(e_y) / (double)(n_y); + if (e_y == 0 || e_y == n_y) { + L_y = 0.0; + } else { + L_y = (double)(e_y) * log(p_y) + (double)(n_y - e_y) * log(1.0 - p_y); + } + + n_x = (n_i + n_j) * n_k; + e_x = internal[x].e + internal[y].e - e_y; // e_yk + p_x = (double)(e_x) / (double)(n_x); + if (e_x == 0 || e_x == n_x) { + L_x = 0.0; + } else { + L_x = (double)(e_x) * log(p_x) + (double)(n_x - e_x) * log(1.0 - p_x); + } + + dLogL = (L_x - internal[x].logL) + (L_y - internal[y].logL); + if ((dLogL > 0.0) || (RNG_UNIF01() < exp(T * dLogL))) { + + // make RIGHT BETA move + + ftaken = true; + d->swapEdges(x, internal[x].L->index, LEFT, + x, internal[x].R->index, RIGHT); + temp = internal[x].L; // - swap L and R of [x] + internal[x].L = internal[x].R; + internal[x].R = temp; + d->swapEdges(x, internal[x].R->index, RIGHT, + y, internal[y].R->index, RIGHT); + temp = internal[x].R; // - swap i and k + internal[x].R = internal[y].R; + internal[y].R = temp; + internal[x].R->M = &internal[x]; // - adjust parent pointers + internal[y].R->M = &internal[y]; + d->swapEdges(y, internal[y].L->index, LEFT, + y, internal[y].R->index, RIGHT); + temp = internal[y].L; // - swap L and R of [y] + internal[y].L = internal[y].R; + internal[y].R = temp; + internal[y].n = n_i + n_j; // - update n + internal[x].e = e_x; // - update e_i + internal[y].e = e_y; + internal[x].p = p_x; // - update p_i + internal[y].p = p_y; + internal[x].logL = L_x; // - update logL_i + internal[y].logL = L_y; + internal[y].label = internal[x].label; // - order-property + L += dLogL; // - update LogL + delta = dLogL; + } + } + } + return true; +} + +// ********************************************************************** + +void dendro::refreshLikelihood() { + // recalculates the log-likelihood of the dendrogram structure + L = 0.0; double dL; + int nL_nR, ei; + for (int i = 0; i < (n - 1); i++) { + nL_nR = internal[i].L->n * internal[i].R->n; + ei = internal[i].e; + internal[i].p = (double)(ei) / (double)(nL_nR); + if (ei == 0 || ei == nL_nR) { + dL = 0.0; + } else { + dL = ei * log(internal[i].p) + (nL_nR - ei) * log(1.0 - internal[i].p); + } + internal[i].logL = dL; + L += dL; + } + return; +} + +// ********************************************************************** + +void dendro::QsortMain (block* array, int left, int right) { + if (right > left) { + int pivot = left; + int part = QsortPartition(array, left, right, pivot); + QsortMain(array, left, part - 1); + QsortMain(array, part + 1, right ); + } + return; +} + +int dendro::QsortPartition (block* array, int left, int right, int index) { + block p_value, temp; + p_value.x = array[index].x; + p_value.y = array[index].y; + + // swap(array[p_value], array[right]) + temp.x = array[right].x; + temp.y = array[right].y; + array[right].x = array[index].x; + array[right].y = array[index].y; + array[index].x = temp.x; + array[index].y = temp.y; + + int stored = left; + for (int i = left; i < right; i++) { + if (array[i].x <= p_value.x) { + // swap(array[stored], array[i]) + temp.x = array[i].x; + temp.y = array[i].y; + array[i].x = array[stored].x; + array[i].y = array[stored].y; + array[stored].x = temp.x; + array[stored].y = temp.y; + stored++; + } + } + // swap(array[right], array[stored]) + temp.x = array[stored].x; + temp.y = array[stored].y; + array[stored].x = array[right].x; + array[stored].y = array[right].y; + array[right].x = temp.x; + array[right].y = temp.y; + + return stored; +} + +void dendro::recordConsensusTree(igraph_vector_t *parents, + igraph_vector_t *weights) { + + keyValuePairSplit *curr, *prev; + child *newChild; + int orig_nodes = g->numNodes(); + + // First, cull the split hist so that only splits with weight >= 0.5 + // remain + cullSplitHist(); + int treesize = splithist->returnNodecount(); + + // Now, initialize the various arrays we use to keep track of the + // internal structure of the consensus tree. + ctree = new cnode[treesize]; + cancestor = new int[n]; + for (int i = 0; i < treesize; i++) { + ctree[i].index = i; + } + for (int i = 0; i < n; i++) { + cancestor[i] = -1; + } + int ii = 0; + + // To build the majority consensus tree, we do the following: For + // each possible number of Ms in the split string (a number that + // ranges from n-2 down to 0), and for each split with that number + // of Ms, we create a new internal node of the tree, and connect the + // oldest ancestor of each C to that node (at most once). Then, we + // update our list of oldest ancestors to reflect this new join, and + // proceed. + for (int i = n - 2; i >= 0; i--) { + // First, we get a list of all the splits with this exactly i Ms + curr = splithist->returnTheseSplits(i); + + // Now we loop over that list + while (curr != NULL) { + splithist->deleteItem(curr->x); + // add weight to this internal node + ctree[ii].weight = curr->y; + // examine each letter of this split + for (int j = 0; j < n; j++) { + if (curr->x[j] == 'C') { + // - node is child of this internal node + if (cancestor[j] == -1) { + // - first time this leaf has ever been seen + newChild = new child; + newChild->type = GRAPH; + newChild->index = j; + newChild->next = NULL; + // - attach child to list + if (ctree[ii].lastChild == NULL) { + ctree[ii].children = newChild; + ctree[ii].lastChild = newChild; + ctree[ii].degree = 1; + } else { + ctree[ii].lastChild->next = newChild; + ctree[ii].lastChild = newChild; + ctree[ii].degree += 1; + } + } else { + // - this leaf has been seen before + // If the parent of the ancestor of this leaf is the + // current internal node then this leaf is already a + // descendant of this internal node, and we can move on; + // otherwise, we need to add that ancestor to this + // internal node's child list, and update various + // relations + if (ctree[cancestor[j]].parent != ii) { + ctree[cancestor[j]].parent = ii; + newChild = new child; + newChild->type = DENDRO; + newChild->index = cancestor[j]; + newChild->next = NULL; + // - attach child to list + if (ctree[ii].lastChild == NULL) { + ctree[ii].children = newChild; + ctree[ii].lastChild = newChild; + ctree[ii].degree = 1; + } else { + ctree[ii].lastChild->next = newChild; + ctree[ii].lastChild = newChild; + ctree[ii].degree += 1; + } + } + } + // note new ancestry for this leaf + cancestor[j] = ii; + } + } + // update internal node index + ii++; + prev = curr; + curr = curr->next; + delete prev; + } + } + + // Return the consensus tree + igraph_vector_resize(parents, ii + orig_nodes); + if (weights) { + igraph_vector_resize(weights, ii); + } + + for (int i = 0; i < ii; i++) { + child *sat, *sit = ctree[i].children; + while (sit) { + VECTOR(*parents)[orig_nodes + i] = + ctree[i].parent < 0 ? -1 : orig_nodes + ctree[i].parent; + if (sit->type == GRAPH) { + VECTOR(*parents)[sit->index] = orig_nodes + i; + } + sat = sit; + sit = sit->next; + delete sat; + } + if (weights) { + VECTOR(*weights)[i] = ctree[i].weight; + } + ctree[i].children = 0; + } + + // Plus the isolate nodes + for (int i = 0; i < n; i++) { + if (cancestor[i] == -1) { + VECTOR(*parents)[i] = -1; + } + } + + +} + +// ********************************************************************** + +void dendro::recordDendrogramStructure(igraph_hrg_t *hrg) { + for (int i = 0; i < n - 1; i++) { + int li = internal[i].L->index; + int ri = internal[i].R->index; + VECTOR(hrg->left )[i] = internal[i].L->type == DENDRO ? -li - 1 : li; + VECTOR(hrg->right)[i] = internal[i].R->type == DENDRO ? -ri - 1 : ri; + VECTOR(hrg->prob )[i] = internal[i].p; + VECTOR(hrg->edges)[i] = internal[i].e; + VECTOR(hrg->vertices)[i] = internal[i].n; + } +} + +void dendro::recordGraphStructure(igraph_t *graph) { + igraph_vector_t edges; + int no_of_nodes = g->numNodes(); + int no_of_edges = g->numLinks() / 2; + int idx = 0; + + igraph_vector_init(&edges, no_of_edges * 2); + IGRAPH_FINALLY(igraph_vector_destroy, &edges); + + for (int i = 0; i < n; i++) { + edge *curr = g->getNeighborList(i); + while (curr) { + if (i < curr->x) { + VECTOR(edges)[idx++] = i; + VECTOR(edges)[idx++] = curr->x; + } + curr = curr->next; + } + } + + igraph_create(graph, &edges, no_of_nodes, /* directed= */ 0); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); +} + +// ********************************************************************** + +list* dendro::reversePathToRoot(const int leafIndex) { + list *head, *subhead, *newlist; + head = subhead = newlist = NULL; + elementd *current = &leaf[leafIndex]; + + // continue until we're finished + while (current != NULL) { + // add this node to the path + newlist = new list; + newlist->x = current->index; + newlist->next = NULL; + if (head == NULL) { + head = newlist; + } else { + subhead = head; + head = newlist; + head->next = subhead; + } + current = current->M; + } + return head; +} + +// *********************************************************************** + +bool dendro::sampleSplitLikelihoods(int &sample_num) { + // In order to compute the majority agreement dendrogram at + // equilibrium, we need to calculate the leaf partition defined by + // each split (internal edge) of the tree. Because splits are only + // defined on a Cayley tree, the buildSplit() function returns the + // default "--...--" string for the root and the root's left + // child. When tabulating the frequency of splits, one of these + // needs to be excluded. + + IGRAPH_UNUSED(sample_num); + + string* array; + int k; + double tot; + + string new_split; + // To decompose the tree into its splits, we simply loop over all + // the internal nodes and replace the old split for the ith internal + // node with its new split. This is a bit time consuming to do + // O(n^2), so try not to do this very often. Once the decomposition + // is had, we insert them into the split histogram, which tracks the + // cumulative weight for each respective split observed. + + if (splithist == NULL) { + splithist = new splittree; + } + for (int i = 0; i < (n - 1); i++) { + new_split = buildSplit(&internal[i]); + d->replaceSplit(i, new_split); + if (!new_split.empty() && new_split[1] != '-') { + if (!splithist->insertItem(new_split, 1.0)) { + return false; + } + } + } + splithist->finishedThisRound(); + + // For large graphs, the split histogram can get extremely large, so + // we need to employ some measures to prevent it from swamping the + // available memory. When the number of splits exceeds a threshold + // (say, a million), we progressively delete splits that have a + // weight less than a rising (k*0.001 of the total weight) fraction + // of the splits, on the assumption that losing such weight is + // unlikely to effect the ultimate split statistics. This deletion + // procedure is slow O(m lg m), but should only happen very rarely. + + int split_max = n * 500; + int leng; + if (splithist->returnNodecount() > split_max) { + k = 1; + while (splithist->returnNodecount() > split_max) { + array = splithist->returnArrayOfKeys(); + tot = splithist->returnTotal(); + leng = splithist->returnNodecount(); + for (int i = 0; i < leng; i++) { + if ((splithist->returnValue(array[i]) / tot) < k * 0.001) { + splithist->deleteItem(array[i]); + } + } + delete [] array; array = NULL; + k++; + } + } + + return true; +} + +void dendro::sampleAdjacencyLikelihoods() { + // Here, we sample the probability values associated with every + // adjacency in A, weighted by their likelihood. The weighted + // histogram is stored in the graph data structure, so we simply + // need to add an observation to each node-pair that corresponds to + // the associated branch point's probability and the dendrogram's + // overall likelihood. + + double nn; + double norm = ((double)(n) * (double)(n)) / 4.0; + + if (L > 0.0) { + L = 0.0; + } + elementd* ancestor; + list *currL, *prevL; + if (paths != NULL) { + for (int i = 0; i < n; i++) { + currL = paths[i]; + while (currL != NULL) { + prevL = currL; + currL = currL->next; + delete prevL; + prevL = NULL; + } + paths[i] = NULL; + } + delete [] paths; + } + paths = NULL; + paths = new list* [n]; + for (int i = 0; i < n; i++) { + // construct paths from root, O(n^2) at worst + paths[i] = reversePathToRoot(i); + } + + // add obs for every node-pair, always O(n^2) + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + // find internal node, O(n) at worst + ancestor = findCommonAncestor(paths, i, j); + nn = ((double)(ancestor->L->n) * (double)(ancestor->R->n)) / norm; + // add obs of ->p to (i,j) histogram, and + g->addAdjacencyObs(i, j, ancestor->p, nn); + // add obs of ->p to (j,i) histogram + g->addAdjacencyObs(j, i, ancestor->p, nn); + } + } + + // finish-up: upate total weight in histograms + g->addAdjacencyEnd(); + + return; +} + +void dendro::resetDendrograph() { + // Reset the dendrograph structure for the next trial + if (leaf != NULL) { + delete [] leaf; // O(n) + leaf = NULL; + } + if (internal != NULL) { + delete [] internal; // O(n) + internal = NULL; + } + if (d != NULL) { + delete d; // O(n) + d = NULL; + } + root = NULL; + if (paths != NULL) { + list *curr, *prev; + for (int i = 0; i < n; i++) { + curr = paths[i]; + while (curr != NULL) { + prev = curr; + curr = curr->next; + delete prev; + prev = NULL; + } + paths[i] = NULL; + } + delete [] paths; + } + paths = NULL; + L = 1.0; + + return; +} + +// ********************************************************************** +// *** COPYRIGHT NOTICE ************************************************* +// graph.h - graph data structure for hierarchical random graphs +// Copyright (C) 2005-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// ********************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | +// http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science +// AND Santa Fe Institute +// Created : 8 November 2005 +// Modified : 23 December 2007 (cleaned up for public consumption) +// +// *********************************************************************** +// +// Graph data structure for hierarchical random graphs. The basic +// structure is an adjacency list of edges; however, many additional +// pieces of metadata are stored as well. Each node stores its +// external name, its degree and (if assigned) its group index. +// +// *********************************************************************** + +// ******** Constructor / Destructor ************************************* + +graph::graph(const int size, bool predict) : predict(predict) { + n = size; + m = 0; + nodes = new vert [n]; + nodeLink = new edge* [n]; + nodeLinkTail = new edge* [n]; + for (int i = 0; i < n; i++) { + nodeLink[i] = NULL; + nodeLinkTail[i] = NULL; + } + if (predict) { + A = new double** [n]; + for (int i = 0; i < n; i++) { + A[i] = new double* [n]; + } + obs_count = 0; + total_weight = 0.0; + bin_resolution = 0.0; + num_bins = 0; + } +} + +graph::~graph() { + edge *curr, *prev; + for (int i = 0; i < n; i++) { + curr = nodeLink[i]; + while (curr != NULL) { + prev = curr; + curr = curr->next; + delete prev; + } + } + delete [] nodeLink; nodeLink = NULL; + delete [] nodeLinkTail; nodeLinkTail = NULL; + delete [] nodes; nodes = NULL; + + if (predict) { + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + delete [] A[i][j]; + } + delete [] A[i]; + } + delete [] A; A = NULL; + } +} + +// ********************************************************************** + +bool graph::addLink(const int i, const int j) { + // Adds the directed edge (i,j) to the adjacency list for v_i + edge* newedge; + if (i >= 0 && i < n && j >= 0 && j < n) { + newedge = new edge; + newedge->x = j; + if (nodeLink[i] == NULL) { + // first neighbor + nodeLink[i] = newedge; + nodeLinkTail[i] = newedge; + nodes[i].degree = 1; + } else { + // subsequent neighbor + nodeLinkTail[i]->next = newedge; + nodeLinkTail[i] = newedge; + nodes[i].degree++; + } + // increment edge count + m++; + return true; + } else { + return false; + } +} + +// *********************************************************************** + +bool graph::addAdjacencyObs(const int i, const int j, + const double probability, const double size) { + // Adds the observation obs to the histogram of the edge (i,j) + // Note: user must manually add observation to edge (j,i) by calling + // this function with that argument + if (bin_resolution > 0.0 && probability >= 0.0 && probability <= 1.0 + && size >= 0.0 && size <= 1.0 + && i >= 0 && i < n && j >= 0 && j < n) { + int index = (int)(probability / bin_resolution + 0.5); + if (index < 0) { + index = 0; + } else if (index > num_bins) { + index = num_bins; + } + + // Add the weight to the proper probability bin + if (A[i][j][index] < 0.5) { + A[i][j][index] = 1.0; + } else { + A[i][j][index] += 1.0; + } + return true; + } + return false; +} + +// ********************************************************************** + +void graph::addAdjacencyEnd() { + // We need to also keep a running total of how much weight has been added + // to the histogram, and the number of observations in the histogram. + if (obs_count == 0) { + total_weight = 1.0; obs_count = 1; + } else { + total_weight += 1.0; obs_count++; + } + return; +} + +bool graph::doesLinkExist(const int i, const int j) { + // This function determines if the edge (i,j) already exists in the + // adjacency list of v_i + edge* curr; + if (i >= 0 && i < n && j >= 0 && j < n) { + curr = nodeLink[i]; + while (curr != NULL) { + if (curr->x == j) { + return true; + } + curr = curr->next; + } + } + return false; +} + +// ********************************************************************** + +int graph::getDegree(const int i) { + if (i >= 0 && i < n) { + return nodes[i].degree; + } else { + return -1; + } +} + +string graph::getName(const int i) { + if (i >= 0 && i < n) { + return nodes[i].name; + } else { + return ""; + } +} + +// NOTE: Returns address; deallocation of returned object is dangerous +edge* graph::getNeighborList(const int i) { + if (i >= 0 && i < n) { + return nodeLink[i]; + } else { + return NULL; + } +} + +double* graph::getAdjacencyHist(const int i, const int j) { + if (i >= 0 && i < n && j >= 0 && j < n) { + return A[i][j]; + } else { + return NULL; + } +} + +// ********************************************************************** + +double graph::getAdjacencyAverage(const int i, const int j) { + double average = 0.0; + if (i != j) { + for (int k = 0; k < num_bins; k++) { + if (A[i][j][k] > 0.0) { + average += (A[i][j][k] / total_weight) * ((double)(k) * bin_resolution); + } + } + } + return average; +} + +int graph::numLinks() { + return m; +} + +int graph::numNodes() { + return n; +} + +double graph::getBinResolution() { + return bin_resolution; +} + +int graph::getNumBins() { + return num_bins; +} + +double graph::getTotalWeight() { + return total_weight; +} + +// *********************************************************************** + +void graph::resetAllAdjacencies() { + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + for (int k = 0; k < num_bins; k++) { + A[i][j][k] = 0.0; + } + } + } + obs_count = 0; + total_weight = 0.0; + return; +} + +// ********************************************************************** + +void graph::resetAdjacencyHistogram(const int i, const int j) { + if (i >= 0 && i < n && j >= 0 && j < n) { + for (int k = 0; k < num_bins; k++) { + A[i][j][k] = 0.0; + } + } + return; +} + +// ********************************************************************** + +void graph::resetLinks() { + edge *curr, *prev; + for (int i = 0; i < n; i++) { + curr = nodeLink[i]; + while (curr != NULL) { + prev = curr; + curr = curr->next; + delete prev; + } + nodeLink[i] = NULL; + nodeLinkTail[i] = NULL; + nodes[i].degree = 0; + } + m = 0; + return; +} + +// ********************************************************************** + +void graph::setAdjacencyHistograms(const int bin_count) { + // For all possible adjacencies, setup an edge histograms + num_bins = bin_count + 1; + bin_resolution = 1.0 / (double)(bin_count); + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + A[i][j] = new double [num_bins]; + for (int k = 0; k < num_bins; k++) { + A[i][j][k] = 0.0; + } + } + } + return; +} + +bool graph::setName(const int i, const string text) { + if (i >= 0 && i < n) { + nodes[i].name = text; + return true; + } else { + return false; + } +} + +// ********************************************************************** + +interns::interns(const int n) { + q = n; + count = 0; + edgelist = new ipair [q]; + splitlist = new string [q + 1]; + indexLUT = new int* [q + 1]; + for (int i = 0; i < (q + 1); i++) { + indexLUT[i] = new int [2]; + indexLUT[i][0] = indexLUT[i][1] = -1; + } +} +interns::~interns() { + delete [] edgelist; + delete [] splitlist; + for (int i = 0; i < (q + 1); i++) { + delete [] indexLUT[i]; + } + delete [] indexLUT; +} + +// *********************************************************************** + +// NOTE: Returns an address to another object -- do not deallocate +ipair* interns::getEdge(const int i) { + return &edgelist[i]; +} + +// *********************************************************************** + +// NOTE: Returns an address to another object -- do not deallocate +ipair* interns::getRandomEdge() { + return &edgelist[(int)(floor((double)(q) * RNG_UNIF01()))]; +} + +// *********************************************************************** + +string interns::getSplit(const int i) { + if (i >= 0 && i <= q) { + return splitlist[i]; + } else { + return ""; + } +} + +// ********************************************************************** + +bool interns::addEdge(const int new_x, const int new_y, + const short int new_type) { + // This function adds a new edge (i,j,t,sp) to the list of internal + // edges. After checking that the inputs fall in the appropriate + // range of values, it records the new edgelist index in the + // indexLUT and then puts the input values into that edgelist + // location. + + if (count < q && new_x >= 0 && new_x < (q + 1) && new_y >= 0 && + new_y < (q + 2) && (new_type == LEFT || new_type == RIGHT)) { + if (new_type == LEFT) { + indexLUT[new_x][0] = count; + } else { + indexLUT[new_x][1] = count; + } + edgelist[count].x = new_x; + edgelist[count].y = new_y; + edgelist[count].t = new_type; + count++; + return true; + } else { + return false; + } +} + +// ********************************************************************** + +bool interns::replaceSplit(const int i, const string sp) { + // When an internal edge is changed, its split must be replaced as + // well. This function provides that access; it stores the split + // defined by an internal edge (x,y) at the location [y], which + // is unique. + + if (i >= 0 && i <= q) { + splitlist[i] = sp; + return true; + } + return false; +} + +// *********************************************************************** + +bool interns::swapEdges(const int one_x, const int one_y, + const short int one_type, const int two_x, + const int two_y, const short int two_type) { + // The moves on the dendrogram always swap edges, either of which + // (or both, or neither) can by internal edges. So, this function + // mirrors that operation for the internal edgelist and indexLUT. + + int index, jndex, temp; + bool one_isInternal = false; + bool two_isInternal = false; + + if (one_x >= 0 && one_x < (q + 1) && two_x >= 0 && two_x < (q + 1) && + (two_type == LEFT || two_type == RIGHT) && + one_y >= 0 && one_y < (q + 2) && two_y >= 0 && + two_y < (q + 2) && (one_type == LEFT || one_type == RIGHT)) { + + if (one_type == LEFT) { + temp = 0; + } else { + temp = 1; + } + if (indexLUT[one_x][temp] > -1) { + one_isInternal = true; + } + if (two_type == LEFT) { + temp = 0; + } else { + temp = 1; + } + if (indexLUT[two_x][temp] > -1) { + two_isInternal = true; + } + + if (one_isInternal && two_isInternal) { + if (one_type == LEFT) { + index = indexLUT[one_x][0]; + } else { + index = indexLUT[one_x][1]; + } + if (two_type == LEFT) { + jndex = indexLUT[two_x][0]; + } else { + jndex = indexLUT[two_x][1]; + } + temp = edgelist[index].y; + edgelist[index].y = edgelist[jndex].y; + edgelist[jndex].y = temp; + + } else if (one_isInternal) { + if (one_type == LEFT) { + index = indexLUT[one_x][0]; indexLUT[one_x][0] = -1; + } else { + index = indexLUT[one_x][1]; indexLUT[one_x][1] = -1; + } + edgelist[index].x = two_x; + edgelist[index].t = two_type; + if (two_type == LEFT) { + indexLUT[two_x][0] = index; + } else { + indexLUT[two_x][1] = index; + } // add new + + } else if (two_isInternal) { + if (two_type == LEFT) { + index = indexLUT[two_x][0]; indexLUT[two_x][0] = -1; + } else { + index = indexLUT[two_x][1]; indexLUT[two_x][1] = -1; + } + edgelist[index].x = one_x; + edgelist[index].t = one_type; + if (one_type == LEFT) { + indexLUT[one_x][0] = index; + } else { + indexLUT[one_x][1] = index; + } // add new + } else { + ; + } // else neither is internal + + return true; + } else { + return false; + } +} + +// ******** Red-Black Tree Methods *************************************** + +splittree::splittree() { + root = new elementsp; + leaf = new elementsp; + + leaf->parent = root; + + root->left = leaf; + root->right = leaf; + support = 0; + total_weight = 0.0; + total_count = 0; +} + +splittree::~splittree() { + if (root != NULL && (root->left != leaf || root->right != leaf)) { + deleteSubTree(root); root = NULL; + } + support = 0; + total_weight = 0.0; + total_count = 0; + if (root) { + delete root; + } + delete leaf; + root = NULL; + leaf = NULL; +} + +void splittree::deleteTree() { + if (root != NULL) { + deleteSubTree(root); + root = NULL; + } + return; +} + +void splittree::deleteSubTree(elementsp *z) { + if (z->left != leaf) { + deleteSubTree(z->left); + z->left = NULL; + } + if (z->right != leaf) { + deleteSubTree(z->right); + z->right = NULL; + } + delete z; + /* No point in setting z to NULL here because z is passed by value */ + /* z = NULL; */ + return; +} + +// ******** Reset Functions ********************************************* + +// O(n lg n) +void splittree::clearTree() { + string *array = returnArrayOfKeys(); + for (int i = 0; i < support; i++) { + deleteItem(array[i]); + } + delete [] array; + return; +} + +// ******** Search Functions ********************************************* +// public search function - if there exists a elementsp in the tree +// with key=searchKey, it returns TRUE and foundNode is set to point +// to the found node; otherwise, it sets foundNode=NULL and returns +// FALSE +elementsp* splittree::findItem(const string searchKey) { + + elementsp *current = root; + if (current->split.empty()) { + return NULL; // empty tree; bail out + } + while (current != leaf) { + if (searchKey.compare(current->split) < 0) { // left-or-right? + // try moving down-left + if (current->left != leaf) { + current = current->left; + } else { + // failure; bail out + return NULL; + } + } else { + if (searchKey.compare(current->split) > 0) { + // left-or-right? + if (current->right != leaf) { + // try moving down-left + current = current->right; + } else { + // failure; bail out + return NULL; + } + } else { + // found (searchKey==current->split) + return current; + } + } + } + return NULL; +} + +double splittree::returnValue(const string searchKey) { + elementsp* test = findItem(searchKey); + if (test == NULL) { + return 0.0; + } else { + return test->weight; + } +} + + +// ******** Return Item Functions *************************************** +// public function which returns the tree, via pre-order traversal, as +// a linked list + +string* splittree::returnArrayOfKeys() { + string* array; + array = new string [support]; + bool flag_go = true; + int index = 0; + elementsp *curr; + + if (support == 1) { + array[0] = root->split; + } else if (support == 2) { + array[0] = root->split; + if (root->left == leaf) { + array[1] = root->right->split; + } else { + array[1] = root->left->split; + } + } else { + for (int i = 0; i < support; i++) { + array[i] = -1; + } + // non-recursive traversal of tree structure + curr = root; + curr->mark = 1; + while (flag_go) { + + // - is it time, and is left child the leaf node? + if (curr->mark == 1 && curr->left == leaf) { + curr->mark = 2; + } + // - is it time, and is right child the leaf node? + if (curr->mark == 2 && curr->right == leaf) { + curr->mark = 3; + } + if (curr->mark == 1) { // - go left + curr->mark = 2; + curr = curr->left; + curr->mark = 1; + } else if (curr->mark == 2) { // - else go right + curr->mark = 3; + curr = curr->right; + curr->mark = 1; + } else { // - else go up a level + curr->mark = 0; + array[index++] = curr->split; + curr = curr->parent; + if (curr == NULL) { + flag_go = false; + } + } + } + } + + return array; +} + +slist* splittree::returnListOfKeys() { + keyValuePairSplit *curr, *prev; + slist *head = NULL, *tail = NULL, *newlist; + + curr = returnTreeAsList(); + while (curr != NULL) { + newlist = new slist; + newlist->x = curr->x; + if (head == NULL) { + head = newlist; tail = head; + } else { + tail->next = newlist; tail = newlist; + } + prev = curr; + curr = curr->next; + delete prev; + prev = NULL; + } + return head; +} + +// pre-order traversal +keyValuePairSplit* splittree::returnTreeAsList() { + keyValuePairSplit *head, *tail; + + head = new keyValuePairSplit; + head->x = root->split; + head->y = root->weight; + head->c = root->count; + tail = head; + + if (root->left != leaf) { + tail = returnSubtreeAsList(root->left, tail); + } + if (root->right != leaf) { + tail = returnSubtreeAsList(root->right, tail); + } + + if (head->x.empty()) { + return NULL; /* empty tree */ + } else { + return head; + } +} + +keyValuePairSplit* splittree::returnSubtreeAsList(elementsp *z, + keyValuePairSplit *head) { + keyValuePairSplit *newnode, *tail; + + newnode = new keyValuePairSplit; + newnode->x = z->split; + newnode->y = z->weight; + newnode->c = z->count; + head->next = newnode; + tail = newnode; + + if (z->left != leaf) { + tail = returnSubtreeAsList(z->left, tail); + } + if (z->right != leaf) { + tail = returnSubtreeAsList(z->right, tail); + } + + return tail; +} + +keyValuePairSplit splittree::returnMaxKey() { + keyValuePairSplit themax; + elementsp *current; + current = root; + // search to bottom-right corner of tree + while (current->right != leaf) { + current = current->right; + } + themax.x = current->split; + themax.y = current->weight; + + return themax; +} + +keyValuePairSplit splittree::returnMinKey() { + keyValuePairSplit themin; + elementsp *current; + current = root; + // search to bottom-left corner of tree + while (current->left != leaf) { + current = current->left; + } + themin.x = current->split; + themin.y = current->weight; + + return themin; +} + +// private functions for deleteItem() (although these could easily be +// made public, I suppose) +elementsp* splittree::returnMinKey(elementsp *z) { + elementsp *current; + + current = z; + // search to bottom-right corner of tree + while (current->left != leaf) { + current = current->left; + } + // return pointer to the minimum + return current; +} + +elementsp* splittree::returnSuccessor(elementsp *z) { + elementsp *current, *w; + + w = z; +// if right-subtree exists, return min of it + if (w->right != leaf) { + return returnMinKey(w->right); + } + // else search up in tree + // move up in tree until find a non-right-child + current = w->parent; + while ((current != NULL) && (w == current->right)) { + w = current; + current = current->parent; + } + return current; +} + +int splittree::returnNodecount() { + return support; +} + +keyValuePairSplit* splittree::returnTheseSplits(const int target) { + keyValuePairSplit *head, *curr, *prev, *newhead, *newtail, *newpair; + int count, len; + + head = returnTreeAsList(); + prev = newhead = newtail = newpair = NULL; + curr = head; + + while (curr != NULL) { + count = 0; + len = curr->x.size(); + for (int i = 0; i < len; i++) { + if (curr->x[i] == 'M') { + count++; + } + } + if (count == target && curr->x[1] != '*') { + newpair = new keyValuePairSplit; + newpair->x = curr->x; + newpair->y = curr->y; + newpair->next = NULL; + if (newhead == NULL) { + newhead = newpair; newtail = newpair; + } else { + newtail->next = newpair; newtail = newpair; + } + } + prev = curr; + curr = curr->next; + delete prev; + prev = NULL; + } + + return newhead; +} + +double splittree::returnTotal() { + return total_weight; +} + +// ******** Insert Functions ********************************************* + +void splittree::finishedThisRound() { + // We need to also keep a running total of how much weight has been + // added to the histogram. + if (total_count == 0) { + total_weight = 1.0; total_count = 1; + } else { + total_weight += 1.0; total_count++; + } + return; +} + +// public insert function +bool splittree::insertItem(string newKey, double newValue) { + + // first we check to see if newKey is already present in the tree; + // if so, we do nothing; if not, we must find where to insert the + // key + elementsp *newNode, *current; + +// find newKey in tree; return pointer to it O(log k) + current = findItem(newKey); + if (current != NULL) { + current->weight += 1.0; + // And finally, we keep track of how many observations went into + // the histogram + current->count++; + return true; + } else { + newNode = new elementsp; // elementsp for the splittree + newNode->split = newKey; // store newKey + newNode->weight = newValue; // store newValue + newNode->color = true; // new nodes are always RED + newNode->parent = NULL; // new node initially has no parent + newNode->left = leaf; // left leaf + newNode->right = leaf; // right leaf + newNode->count = 1; + support++; // increment node count in splittree + + // must now search for where to insert newNode, i.e., find the + // correct parent and set the parent and child to point to each + // other properly + current = root; + if (current->split.empty()) { // insert as root + delete root; // delete old root + root = newNode; // set root to newNode + leaf->parent = newNode; // set leaf's parent + current = leaf; // skip next loop + } + + // search for insertion point + while (current != leaf) { + // left-or-right? + if (newKey.compare(current->split) < 0) { + // try moving down-left + if (current->left != leaf) { + current = current->left; + } else { + // else found new parent + newNode->parent = current; // set parent + current->left = newNode; // set child + current = leaf; // exit search + } + } else { // + if (current->right != leaf) { + // try moving down-right + current = current->right; + } else { + // else found new parent + newNode->parent = current; // set parent + current->right = newNode; // set child + current = leaf; // exit search + } + } + } + + // now do the house-keeping necessary to preserve the red-black + // properties + insertCleanup(newNode); + + } + return true; +} + +// private house-keeping function for insertion +void splittree::insertCleanup(elementsp *z) { + + // fix now if z is root + if (z->parent == NULL) { + z->color = false; return; + } + elementsp *temp; + // while z is not root and z's parent is RED + while (z->parent != NULL && z->parent->color) { + if (z->parent == z->parent->parent->left) { // z's parent is LEFT-CHILD + temp = z->parent->parent->right; // grab z's uncle + if (temp->color) { + z->parent->color = false; // color z's parent BLACK (Case 1) + temp->color = false; // color z's uncle BLACK (Case 1) + z->parent->parent->color = true; // color z's grandpa RED (Case 1) + z = z->parent->parent; // set z = z's grandpa (Case 1) + } else { + if (z == z->parent->right) { // z is RIGHT-CHILD + z = z->parent; // set z = z's parent (Case 2) + rotateLeft(z); // perform left-rotation (Case 2) + } + z->parent->color = false; // color z's parent BLACK (Case 3) + z->parent->parent->color = true; // color z's grandpa RED (Case 3) + rotateRight(z->parent->parent); // perform right-rotation (Case 3) + } + } else { // z's parent is RIGHT-CHILD + temp = z->parent->parent->left; // grab z's uncle + if (temp->color) { + z->parent->color = false; // color z's parent BLACK (Case 1) + temp->color = false; // color z's uncle BLACK (Case 1) + z->parent->parent->color = true; // color z's grandpa RED (Case 1) + z = z->parent->parent; // set z = z's grandpa (Case 1) + } else { + if (z == z->parent->left) { // z is LEFT-CHILD + z = z->parent; // set z = z's parent (Case 2) + rotateRight(z); // perform right-rotation (Case 2) + } + z->parent->color = false; // color z's parent BLACK (Case 3) + z->parent->parent->color = true; // color z's grandpa RED (Case 3) + rotateLeft(z->parent->parent); // perform left-rotation (Case 3) + } + } + } + + root->color = false; // color the root BLACK + return; +} + +// ******** Delete Functions ******************************************** +// public delete function +void splittree::deleteItem(string killKey) { + elementsp *x, *y, *z; + + z = findItem(killKey); + if (z == NULL) { + return; // item not present; bail out + } + + if (support == 1) { // -- attempt to delete the root + root->split = ""; // restore root node to default state + root->weight = 0.0; // + root->color = false; // + root->parent = NULL; // + root->left = leaf; // + root->right = leaf; // + support--; // set support to zero + total_weight = 0.0; // set total weight to zero + total_count--; // + return; // exit - no more work to do + } + + if (z != NULL) { + support--; // decrement node count + if ((z->left == leaf) || (z->right == leaf)) { + // case of less than two children + y = z; // set y to be z + } else { + y = returnSuccessor(z); // set y to be z's key-successor + } + + if (y->left != leaf) { + x = y->left; // pick y's one child (left-child) + } else { + x = y->right; // (right-child) + } + x->parent = y->parent; // make y's child's parent be y's parent + + if (y->parent == NULL) { + root = x; // if y is the root, x is now root + } else { + if (y == y->parent->left) {// decide y's relationship with y's parent + y->parent->left = x; // replace x as y's parent's left child + } else { + y->parent->right = x; + } // replace x as y's parent's left child + } + + if (y != z) { // insert y into z's spot + z->split = y->split; // copy y data into z + z->weight = y->weight; // + z->count = y->count; // + } // + + // do house-keeping to maintain balance + if (y->color == false) { + deleteCleanup(x); + } + delete y; // deallocate y + y = NULL; // point y to NULL for safety + } // + + return; +} + +void splittree::deleteCleanup(elementsp *x) { + elementsp *w, *t; + // until x is the root, or x is RED + while ((x != root) && (x->color == false)) { + if (x == x->parent->left) { // branch on x being a LEFT-CHILD + w = x->parent->right; // grab x's sibling + if (w->color == true) { // if x's sibling is RED + w->color = false; // color w BLACK (case 1) + x->parent->color = true; // color x's parent RED (case 1) + rotateLeft(x->parent); // left rotation on x's parent (case 1) + w = x->parent->right; // make w be x's right sibling (case 1) + } + if ((w->left->color == false) && (w->right->color == false)) { + w->color = true; // color w RED (case 2) + x = x->parent; // examine x's parent (case 2) + } else { // + if (w->right->color == false) { + w->left->color = false; // color w's left child BLACK (case 3) + w->color = true; // color w RED (case 3) + t = x->parent; // store x's parent + rotateRight(w); // right rotation on w (case 3) + x->parent = t; // restore x's parent + w = x->parent->right; // make w be x's right sibling (case 3) + } // + w->color = x->parent->color; // w's color := x's parent's (case 4) + x->parent->color = false; // color x's parent BLACK (case 4) + w->right->color = false; // color w's right child BLACK (case 4) + rotateLeft(x->parent); // left rotation on x's parent (case 4) + x = root; // finished work. bail out (case 4) + } // + } else { // x is RIGHT-CHILD + w = x->parent->left; // grab x's sibling + if (w->color == true) { // if x's sibling is RED + w->color = false; // color w BLACK (case 1) + x->parent->color = true; // color x's parent RED (case 1) + rotateRight(x->parent); // right rotation on x's parent (case 1) + w = x->parent->left; // make w be x's left sibling (case 1) + } + if ((w->right->color == false) && (w->left->color == false)) { + w->color = true; // color w RED (case 2) + x = x->parent; // examine x's parent (case 2) + } else { // + if (w->left->color == false) { // + w->right->color = false; // color w's right child BLACK (case 3) + w->color = true; // color w RED (case 3) + t = x->parent; // store x's parent + rotateLeft(w); // left rotation on w (case 3) + x->parent = t; // restore x's parent + w = x->parent->left; // make w be x's left sibling (case 3) + } // + w->color = x->parent->color; // w's color := x's parent's (case 4) + x->parent->color = false; // color x's parent BLACK (case 4) + w->left->color = false; // color w's left child BLACK (case 4) + rotateRight(x->parent); // right rotation on x's parent (case 4) + x = root; // x is now the root (case 4) + } + } + } + x->color = false; // color x (the root) BLACK (exit) + + return; +} + +// ******** Rotation Functions ******************************************* + +void splittree::rotateLeft(elementsp *x) { + elementsp *y; + // do pointer-swapping operations for left-rotation + y = x->right; // grab right child + x->right = y->left; // make x's RIGHT-CHILD be y's LEFT-CHILD + y->left->parent = x; // make x be y's LEFT-CHILD's parent + y->parent = x->parent; // make y's new parent be x's old parent + + if (x->parent == NULL) { + root = y; // if x was root, make y root + } else { // + if (x == x->parent->left) { // if x is LEFT-CHILD, make y be x's parent's + x->parent->left = y; // left-child + } else { + x->parent->right = y; // right-child + } + } + y->left = x; // make x be y's LEFT-CHILD + x->parent = y; // make y be x's parent + + return; +} + +void splittree::rotateRight(elementsp *y) { + elementsp *x; + // do pointer-swapping operations for right-rotation + x = y->left; // grab left child + y->left = x->right; // replace left child yith x's right subtree + x->right->parent = y; // replace y as x's right subtree's parent + + x->parent = y->parent; // make x's new parent be y's old parent + if (y->parent == NULL) { + root = x; // if y was root, make x root + } else { + if (y == y->parent->right) { // if y is R-CHILD, make x be y's parent's + y->parent->right = x; // right-child + } else { + y->parent->left = x; // left-child + } + } + x->right = y; // make y be x's RIGHT-CHILD + y->parent = x; // make x be y's parent + + return; +} + +// *********************************************************************** +// *** COPYRIGHT NOTICE ************************************************** +// graph_simp.h - graph data structure +// Copyright (C) 2006-2008 Aaron Clauset +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.gnu.org/licenses/gpl.txt for more details. +// +// *********************************************************************** +// Author : Aaron Clauset ( aaronc@santafe.edu | +// http://www.santafe.edu/~aaronc/ ) +// Collaborators: Cristopher Moore and Mark E.J. Newman +// Project : Hierarchical Random Graphs +// Location : University of New Mexico, Dept. of Computer Science +// AND Santa Fe Institute +// Created : 21 June 2006 +// Modified : 23 December 2007 (cleaned up for public consumption) +// +// ************************************************************************ + +// ******** Constructor / Destructor ************************************* + +simpleGraph::simpleGraph(const int size): n(size), m(0), num_groups(0) { + nodes = new simpleVert [n]; + nodeLink = new simpleEdge* [n]; + nodeLinkTail = new simpleEdge* [n]; + A = new double* [n]; + for (int i = 0; i < n; i++) { + nodeLink[i] = NULL; nodeLinkTail[i] = NULL; + A[i] = new double [n]; + for (int j = 0; j < n; j++) { + A[i][j] = 0.0; + } + } + E = NULL; +} + +simpleGraph::~simpleGraph() { + simpleEdge *curr, *prev; + for (int i = 0; i < n; i++) { + curr = nodeLink[i]; + delete [] A[i]; + while (curr != NULL) { + prev = curr; + curr = curr->next; + delete prev; + } + } + curr = NULL; prev = NULL; + if (E != NULL) { + delete [] E; + E = NULL; + } + delete [] A; A = NULL; + delete [] nodeLink; nodeLink = NULL; + delete [] nodeLinkTail; nodeLinkTail = NULL; + delete [] nodes; nodes = NULL; +} + +// *********************************************************************** + +bool simpleGraph::addGroup(const int i, const int group_index) { + if (i >= 0 && i < n) { + nodes[i].group_true = group_index; + return true; + } else { + return false; + } +} + +// *********************************************************************** + +bool simpleGraph::addLink(const int i, const int j) { + // Adds the directed edge (i,j) to the adjacency list for v_i + simpleEdge* newedge; + if (i >= 0 && i < n && j >= 0 && j < n) { + A[i][j] = 1.0; + newedge = new simpleEdge; + newedge->x = j; + if (nodeLink[i] == NULL) { // first neighbor + nodeLink[i] = newedge; + nodeLinkTail[i] = newedge; + nodes[i].degree = 1; + } else { // subsequent neighbor + nodeLinkTail[i]->next = newedge; + nodeLinkTail[i] = newedge; + nodes[i].degree++; + } + m++; // increment edge count + newedge = NULL; + return true; + } else { + return false; + } +} + +// *********************************************************************** + +bool simpleGraph::doesLinkExist(const int i, const int j) { + // This function determines if the edge (i,j) already exists in the + // adjacency list of v_i + if (i >= 0 && i < n && j >= 0 && j < n) { + if (A[i][j] > 0.1) { + return true; + } else { + return false; + } + } else { + return false; + } + return false; +} + +// ********************************************************************** + +double simpleGraph::getAdjacency(const int i, const int j) { + if (i >= 0 && i < n && j >= 0 && j < n) { + return A[i][j]; + } else { + return -1.0; + } +} + +int simpleGraph::getDegree(const int i) { + if (i >= 0 && i < n) { + return nodes[i].degree; + } else { + return -1; + } +} + +int simpleGraph::getGroupLabel(const int i) { + if (i >= 0 && i < n) { + return nodes[i].group_true; + } else { + return -1; + } +} + +string simpleGraph::getName(const int i) { + if (i >= 0 && i < n) { + return nodes[i].name; + } else { + return ""; + } +} + +// NOTE: The following three functions return addresses; deallocation +// of returned object is dangerous +simpleEdge* simpleGraph::getNeighborList(const int i) { + if (i >= 0 && i < n) { + return nodeLink[i]; + } else { + return NULL; + } +} +// END-NOTE + +// ********************************************************************* + +int simpleGraph::getNumGroups() { + return num_groups; +} +int simpleGraph::getNumLinks() { + return m; +} +int simpleGraph::getNumNodes() { + return n; +} +simpleVert* simpleGraph::getNode(const int i) { + if (i >= 0 && i < n) { + return &nodes[i]; + } else { + return NULL; + } +} + +// ********************************************************************** + +bool simpleGraph::setName(const int i, const string text) { + if (i >= 0 && i < n) { + nodes[i].name = text; + return true; + } else { + return false; + } +} + +// ********************************************************************** + +void simpleGraph::QsortMain (block* array, int left, int right) { + if (right > left) { + int pivot = left; + int part = QsortPartition(array, left, right, pivot); + QsortMain(array, left, part - 1); + QsortMain(array, part + 1, right ); + } + return; +} + +int simpleGraph::QsortPartition (block* array, int left, int right, + int index) { + block p_value, temp; + p_value.x = array[index].x; + p_value.y = array[index].y; + + // swap(array[p_value], array[right]) + temp.x = array[right].x; + temp.y = array[right].y; + array[right].x = array[index].x; + array[right].y = array[index].y; + array[index].x = temp.x; + array[index].y = temp.y; + + int stored = left; + for (int i = left; i < right; i++) { + if (array[i].x <= p_value.x) { + // swap(array[stored], array[i]) + temp.x = array[i].x; + temp.y = array[i].y; + array[i].x = array[stored].x; + array[i].y = array[stored].y; + array[stored].x = temp.x; + array[stored].y = temp.y; + stored++; + } + } + // swap(array[right], array[stored]) + temp.x = array[stored].x; + temp.y = array[stored].y; + array[stored].x = array[right].x; + array[stored].y = array[right].y; + array[right].x = temp.x; + array[right].y = temp.y; + + return stored; +} + +// *********************************************************************** diff --git a/src/igraph_interrupt_internal.h b/src/igraph_interrupt_internal.h new file mode 100644 index 0000000..d81676e --- /dev/null +++ b/src/igraph_interrupt_internal.h @@ -0,0 +1,69 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_INTERRUPT_INTERNAL_H +#define IGRAPH_INTERRUPT_INTERNAL_H + +#include "config.h" +#include "igraph_interrupt.h" + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus + #define __BEGIN_DECLS extern "C" { + #define __END_DECLS } +#else + #define __BEGIN_DECLS /* empty */ + #define __END_DECLS /* empty */ +#endif + +__BEGIN_DECLS + +extern IGRAPH_THREAD_LOCAL igraph_interruption_handler_t +*igraph_i_interruption_handler; + +/** + * \define IGRAPH_ALLOW_INTERRUPTION + * \brief + * + * This macro should be called when interruption is allowed. It calls + * \ref igraph_allow_interruption() with the proper parameters and if that returns + * anything but \c IGRAPH_SUCCESS then + * the macro returns the "calling" function as well, with the proper + * error code (\c IGRAPH_INTERRUPTED). + */ + +#define IGRAPH_ALLOW_INTERRUPTION() \ + do { \ + if (igraph_i_interruption_handler) { if (igraph_allow_interruption(NULL) != IGRAPH_SUCCESS) return IGRAPH_INTERRUPTED; \ + } } while (0) + +#define IGRAPH_ALLOW_INTERRUPTION_NORETURN() \ + do { \ + if (igraph_i_interruption_handler) { igraph_allow_interruption(NULL); } \ + } while (0) + +__END_DECLS + +#endif + diff --git a/src/igraph_lapack_internal.h b/src/igraph_lapack_internal.h new file mode 100644 index 0000000..dd20b2d --- /dev/null +++ b/src/igraph_lapack_internal.h @@ -0,0 +1,184 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef LAPACK_INTERNAL_H +#define LAPACK_INTERNAL_H + +/* Note: only files calling the LAPACK routines directly need to + include this header. +*/ + +#include "igraph_types.h" +#include "config.h" + +#ifndef INTERNAL_LAPACK + #define igraphdgeevx_ dgeevx_ + #define igraphdgeev_ dgeev_ + #define igraphdgebak_ dgebak_ + #define igraphxerbla_ xerbla_ + #define igraphdgebal_ dgebal_ + #define igraphdisnan_ disnan_ + #define igraphdlaisnan_ dlaisnan_ + #define igraphdgehrd_ dgehrd_ + #define igraphdgehd2_ dgehd2_ + #define igraphdlarf_ dlarf_ + #define igraphiladlc_ iladlc_ + #define igraphiladlr_ iladlr_ + #define igraphdlarfg_ dlarfg_ + #define igraphdlapy2_ dlapy2_ + #define igraphdlahr2_ dlahr2_ + #define igraphdlacpy_ dlacpy_ + #define igraphdlarfb_ dlarfb_ + #define igraphilaenv_ ilaenv_ + #define igraphieeeck_ ieeeck_ + #define igraphiparmq_ iparmq_ + #define igraphdhseqr_ dhseqr_ + #define igraphdlahqr_ dlahqr_ + #define igraphdlabad_ dlabad_ + #define igraphdlanv2_ dlanv2_ + #define igraphdlaqr0_ dlaqr0_ + #define igraphdlaqr3_ dlaqr3_ + #define igraphdlaqr4_ dlaqr4_ + #define igraphdlaqr2_ dlaqr2_ + #define igraphdlaset_ dlaset_ + #define igraphdormhr_ dormhr_ + #define igraphdormqr_ dormqr_ + #define igraphdlarft_ dlarft_ + #define igraphdorm2r_ dorm2r_ + #define igraphdtrexc_ dtrexc_ + #define igraphdlaexc_ dlaexc_ + #define igraphdlange_ dlange_ + #define igraphdlassq_ dlassq_ + #define igraphdlarfx_ dlarfx_ + #define igraphdlartg_ dlartg_ + #define igraphdlasy2_ dlasy2_ + #define igraphdlaqr5_ dlaqr5_ + #define igraphdlaqr1_ dlaqr1_ + #define igraphdlascl_ dlascl_ + #define igraphdorghr_ dorghr_ + #define igraphdorgqr_ dorgqr_ + #define igraphdorg2r_ dorg2r_ + #define igraphdtrevc_ dtrevc_ + #define igraphdlaln2_ dlaln2_ + #define igraphdladiv_ dladiv_ + #define igraphdsyevr_ dsyevr_ + #define igraphdsyrk_ dsyrk_ + #define igraphdlansy_ dlansy_ + #define igraphdormtr_ dormtr_ + #define igraphdormql_ dormql_ + #define igraphdorm2l_ dorm2l_ + #define igraphdstebz_ dstebz_ + #define igraphdlaebz_ dlaebz_ + #define igraphdstein_ dstein_ + #define igraphdlagtf_ dlagtf_ + #define igraphdlagts_ dlagts_ + #define igraphdlarnv_ dlarnv_ + #define igraphdlaruv_ dlaruv_ + #define igraphdstemr_ dstemr_ + #define igraphdlae2_ dlae2_ + #define igraphdlaev2_ dlaev2_ + #define igraphdlanst_ dlanst_ + #define igraphdlarrc_ dlarrc_ + #define igraphdlarre_ dlarre_ + #define igraphdlarra_ dlarra_ + #define igraphdlarrb_ dlarrb_ + #define igraphdlaneg_ dlaneg_ + #define igraphdlarrd_ dlarrd_ + #define igraphdlarrk_ dlarrk_ + #define igraphdlasq2_ dlasq2_ + #define igraphdlasq3_ dlasq3_ + #define igraphdlasq4_ dlasq4_ + #define igraphdlasq5_ dlasq5_ + #define igraphdlasq6_ dlasq6_ + #define igraphdlasrt_ dlasrt_ + #define igraphdlarrj_ dlarrj_ + #define igraphdlarrr_ dlarrr_ + #define igraphdlarrv_ dlarrv_ + #define igraphdlar1v_ dlar1v_ + #define igraphdlarrf_ dlarrf_ + #define igraphdpotrf_ dpotrf_ + #define igraphdsterf_ dsterf_ + #define igraphdsytrd_ dsytrd_ + #define igraphdlatrd_ dlatrd_ + #define igraphdsytd2_ dsytd2_ + #define igraphdlanhs_ dlanhs_ + #define igraphdgeqr2_ dgeqr2_ + #define igraphdtrsen_ dtrsen_ + #define igraphdlacn2_ dlacn2_ + #define igraphdtrsyl_ dtrsyl_ + #define igraphdlasr_ dlasr_ + #define igraphdsteqr_ dsteqr_ + #define igraphdgesv_ dgesv_ + #define igraphdgetrf_ dgetrf_ + #define igraphdgetf2_ dgetf2_ + #define igraphdlaswp_ dlaswp_ + #define igraphdgetrs_ dgetrs_ + #define igraphlen_trim_ len_trim_ + #define igraph_dlamc1_ dlamc1_ + #define igraph_dlamc2_ dlamc2_ + #define igraph_dlamc3_ dlamc3_ + #define igraph_dlamc4_ dlamc4_ + #define igraph_dlamc5_ dlamc5_ + #define igraphddot_ ddot_ +#endif + +int igraphdgetrf_(int *m, int *n, igraph_real_t *a, int *lda, int *ipiv, + int *info); +int igraphdgetrs_(char *trans, int *n, int *nrhs, igraph_real_t *a, + int *lda, int *ipiv, igraph_real_t *b, int *ldb, + int *info); +int igraphdgesv_(int *n, int *nrhs, igraph_real_t *a, int *lda, + int *ipiv, igraph_real_t *b, int *ldb, int *info); + +igraph_real_t igraphdlapy2_(igraph_real_t *x, igraph_real_t *y); + +int igraphdsyevr_(char *jobz, char *range, char *uplo, int *n, + igraph_real_t *a, int *lda, igraph_real_t *vl, + igraph_real_t *vu, int * il, int *iu, + igraph_real_t *abstol, int *m, igraph_real_t *w, + igraph_real_t *z, int *ldz, int *isuppz, + igraph_real_t *work, int *lwork, int *iwork, + int *liwork, int *info); + +int igraphdgeev_(char *jobvl, char *jobvr, int *n, igraph_real_t *a, + int *lda, igraph_real_t *wr, igraph_real_t *wi, + igraph_real_t *vl, int *ldvl, igraph_real_t *vr, int *ldvr, + igraph_real_t *work, int *lwork, int *info); + +int igraphdgeevx_(char *balanc, char *jobvl, char *jobvr, char *sense, + int *n, igraph_real_t *a, int *lda, igraph_real_t *wr, + igraph_real_t *wi, igraph_real_t *vl, int *ldvl, + igraph_real_t *vr, int *ldvr, int *ilo, int *ihi, + igraph_real_t *scale, igraph_real_t *abnrm, + igraph_real_t *rconde, igraph_real_t *rcondv, + igraph_real_t *work, int *lwork, int *iwork, int *info); + +int igraphdgehrd_(int *n, int *ilo, int *ihi, igraph_real_t *A, int *lda, + igraph_real_t *tau, igraph_real_t *work, int *lwork, + int *info); + +igraph_real_t igraphddot_(int *n, igraph_real_t *dx, int *incx, + igraph_real_t *dy, int *incy); + +#endif diff --git a/src/igraph_marked_queue.c b/src/igraph_marked_queue.c new file mode 100644 index 0000000..e9663ab --- /dev/null +++ b/src/igraph_marked_queue.c @@ -0,0 +1,115 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_marked_queue.h" + +#define BATCH_MARKER -1 + +int igraph_marked_queue_init(igraph_marked_queue_t *q, + long int size) { + IGRAPH_CHECK(igraph_dqueue_init(&q->Q, 0)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &q->Q); + IGRAPH_CHECK(igraph_vector_long_init(&q->set, size)); + q->mark = 1; + q->size = 0; + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +void igraph_marked_queue_destroy(igraph_marked_queue_t *q) { + igraph_vector_long_destroy(&q->set); + igraph_dqueue_destroy(&q->Q); +} + +void igraph_marked_queue_reset(igraph_marked_queue_t *q) { + igraph_dqueue_clear(&q->Q); + q->size = 0; + q->mark += 1; + if (q->mark == 0) { + igraph_vector_long_null(&q->set); + q->mark += 1; + } +} + +igraph_bool_t igraph_marked_queue_empty(const igraph_marked_queue_t *q) { + return q->size == 0; +} + +long int igraph_marked_queue_size(const igraph_marked_queue_t *q) { + return q->size; +} + +igraph_bool_t igraph_marked_queue_iselement(const igraph_marked_queue_t *q, + long int elem) { + return (VECTOR(q->set)[elem] == q->mark); +} + +int igraph_marked_queue_push(igraph_marked_queue_t *q, long int elem) { + if (VECTOR(q->set)[elem] != q->mark) { + IGRAPH_CHECK(igraph_dqueue_push(&q->Q, elem)); + VECTOR(q->set)[elem] = q->mark; + q->size += 1; + } + return 0; +} + +int igraph_marked_queue_start_batch(igraph_marked_queue_t *q) { + IGRAPH_CHECK(igraph_dqueue_push(&q->Q, BATCH_MARKER)); + return 0; +} + +void igraph_marked_queue_pop_back_batch(igraph_marked_queue_t *q) { + long int size = igraph_dqueue_size(&q->Q); + long int elem; + while (size > 0 && + (elem = (long int) igraph_dqueue_pop_back(&q->Q)) != BATCH_MARKER) { + VECTOR(q->set)[elem] = 0; + size--; + q->size--; + } +} + +#ifndef USING_R +int igraph_marked_queue_print(const igraph_marked_queue_t *q) { + IGRAPH_CHECK(igraph_dqueue_print(&q->Q)); + return 0; +} +#endif + +int igraph_marked_queue_fprint(const igraph_marked_queue_t *q, FILE *file) { + IGRAPH_CHECK(igraph_dqueue_fprint(&q->Q, file)); + return 0; +} + +int igraph_marked_queue_as_vector(const igraph_marked_queue_t *q, + igraph_vector_t *vec) { + long int i, p, n = igraph_dqueue_size(&q->Q); + IGRAPH_CHECK(igraph_vector_resize(vec, q->size)); + for (i = 0, p = 0; i < n; i++) { + igraph_real_t e = igraph_dqueue_e(&q->Q, i); + if (e != BATCH_MARKER) { + VECTOR(*vec)[p++] = e; + } + } + return 0; +} diff --git a/src/igraph_marked_queue.h b/src/igraph_marked_queue.h new file mode 100644 index 0000000..88132d4 --- /dev/null +++ b/src/igraph_marked_queue.h @@ -0,0 +1,70 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_MARKED_QUEUE_H +#define IGRAPH_MARKED_QUEUE_H + +#include "igraph_vector.h" +#include "igraph_dqueue.h" + +#include + +/* This is essentially a double ended queue, with some extra features: + (1) The is-element? operation is fast, O(1). This requires that we + know a limit for the number of elements in the queue. + (2) We can insert elements in batches, and the whole batch can be + removed at once. + + Currently only the top-end operations are implemented, so the queue + is essentially a stack. +*/ + +typedef struct igraph_marked_queue_t { + igraph_dqueue_t Q; + igraph_vector_long_t set; + long int mark; + long int size; +} igraph_marked_queue_t; + +int igraph_marked_queue_init(igraph_marked_queue_t *q, + long int size); +void igraph_marked_queue_destroy(igraph_marked_queue_t *q); +void igraph_marked_queue_reset(igraph_marked_queue_t *q); + +igraph_bool_t igraph_marked_queue_empty(const igraph_marked_queue_t *q); +long int igraph_marked_queue_size(const igraph_marked_queue_t *q); +int igraph_marked_queue_print(const igraph_marked_queue_t *q); +int igraph_marked_queue_fprint(const igraph_marked_queue_t *q, FILE *file); + +igraph_bool_t igraph_marked_queue_iselement(const igraph_marked_queue_t *q, + long int elem); + +int igraph_marked_queue_push(igraph_marked_queue_t *q, long int elem); + +int igraph_marked_queue_start_batch(igraph_marked_queue_t *q); +void igraph_marked_queue_pop_back_batch(igraph_marked_queue_t *q); + +int igraph_marked_queue_as_vector(const igraph_marked_queue_t *q, + igraph_vector_t *vec); + +#endif diff --git a/src/igraph_math.h b/src/igraph_math.h new file mode 100644 index 0000000..a0d6039 --- /dev/null +++ b/src/igraph_math.h @@ -0,0 +1,100 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2008-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_MATH_H +#define IGRAPH_MATH_H + +#include "config.h" +#include +#include + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus + #define __BEGIN_DECLS extern "C" { + #define __END_DECLS } +#else + #define __BEGIN_DECLS /* empty */ + #define __END_DECLS /* empty */ +#endif + +__BEGIN_DECLS + +/** + * \def IGRAPH_SHORTEST_PATH_EPSILON + * + * Relative error threshold used in weighted shortest path calculations + * to decide whether two shortest paths are of equal length. + */ +#define IGRAPH_SHORTEST_PATH_EPSILON 1e-10 + +/* + * Compiler-related hacks, mostly because of Microsoft Visual C++ + */ +double igraph_i_round(double X); +int igraph_i_snprintf(char *buffer, size_t count, const char *format, ...); + +double igraph_log2(const double a); +double igraph_log1p(double a); +long double igraph_fabsl(long double a); +double igraph_fmin(double a, double b); +#ifndef HAVE_LOG2 + #define log2(a) igraph_log2(a) +#endif +#ifndef HAVE_LOG1P + #define log1p(a) igraph_log1p(a) +#endif +#ifndef HAVE_FABSL + #define fabsl(a) igraph_fabsl(a) +#endif +#ifndef HAVE_FMIN + #define fmin(a,b) igraph_fmin((a),(b)) +#endif +#ifndef HAVE_ROUND + #define round igraph_i_round +#endif + +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif +#ifndef M_PI_2 + #define M_PI_2 1.57079632679489661923 +#endif +#ifndef M_LN2 + #define M_LN2 0.69314718055994530942 +#endif +#ifndef M_SQRT2 + #define M_SQRT2 1.4142135623730950488016887 +#endif +#ifndef M_LN_SQRT_2PI + #define M_LN_SQRT_2PI 0.918938533204672741780329736406 /* log(sqrt(2*pi)) + == log(2*pi)/2 */ +#endif + +int igraph_almost_equals(double a, double b, double eps); +int igraph_cmp_epsilon(double a, double b, double eps); + +__END_DECLS + +#endif + diff --git a/src/igraph_psumtree.c b/src/igraph_psumtree.c new file mode 100644 index 0000000..8487827 --- /dev/null +++ b/src/igraph_psumtree.c @@ -0,0 +1,102 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + Copyright (C) 2006 Elliot Paquette + Kalamazoo College, 1200 Academy st, Kalamazoo, MI + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_psumtree.h" +#include "igraph_error.h" +#include "config.h" + +#include +#include + +static double igraph_i_log2(double f) { + return log(f) / log(2.0); +} + +int igraph_psumtree_init(igraph_psumtree_t *t, long int size) { + t->size = size; + t->offset = (long int) (pow(2, ceil(igraph_i_log2(size))) - 1); + IGRAPH_CHECK(igraph_vector_init((igraph_vector_t *)t, t->offset + t->size)); + return 0; +} + +void igraph_psumtree_reset(igraph_psumtree_t *t) { + igraph_vector_fill(&(t->v), 0); +} + +void igraph_psumtree_destroy(igraph_psumtree_t *t) { + igraph_vector_destroy((igraph_vector_t *)t); +} + +igraph_real_t igraph_psumtree_get(const igraph_psumtree_t *t, long int idx) { + const igraph_vector_t *tree = &t->v; + return VECTOR(*tree)[t->offset + idx]; +} + +int igraph_psumtree_search(const igraph_psumtree_t *t, long int *idx, + igraph_real_t search) { + const igraph_vector_t *tree = &t->v; + long int i = 1; + long int size = igraph_vector_size(tree); + + while ( 2 * i + 1 <= size) { + if ( search <= VECTOR(*tree)[i * 2 - 1] ) { + i <<= 1; + } else { + search -= VECTOR(*tree)[i * 2 - 1]; + i <<= 1; + i += 1; + } + } + if (2 * i <= size) { + i = 2 * i; + } + + *idx = i - t->offset - 1; + return IGRAPH_SUCCESS; +} + +int igraph_psumtree_update(igraph_psumtree_t *t, long int idx, + igraph_real_t new_value) { + const igraph_vector_t *tree = &t->v; + igraph_real_t difference; + + idx = idx + t->offset + 1; + difference = new_value - VECTOR(*tree)[idx - 1]; + + while ( idx >= 1 ) { + VECTOR(*tree)[idx - 1] += difference; + idx >>= 1; + } + return IGRAPH_SUCCESS; +} + +long int igraph_psumtree_size(const igraph_psumtree_t *t) { + return t->size; +} + +igraph_real_t igraph_psumtree_sum(const igraph_psumtree_t *t) { + return VECTOR(t->v)[0]; +} diff --git a/src/igraph_set.c b/src/igraph_set.c new file mode 100644 index 0000000..a93386c --- /dev/null +++ b/src/igraph_set.c @@ -0,0 +1,320 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_memory.h" +#include "igraph_error.h" +#include "igraph_types_internal.h" +#include "config.h" + +#include +#include /* memmove */ + +#define SET(s) ((s).stor_begin) + +/** + * \ingroup set + * \function igraph_set_init + * \brief Initializes a set. + * + * \param set pointer to the set to be initialized + * \param size the expected number of elements in the set + * + * \return error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, should be around + * O(n), n is the expected size of the set. + */ +int igraph_set_init(igraph_set_t *set, int long size) { + long int alloc_size = size > 0 ? size : 1; + if (size < 0) { + size = 0; + } + set->stor_begin = igraph_Calloc(alloc_size, igraph_integer_t); + set->stor_end = set->stor_begin + alloc_size; + set->end = set->stor_begin; + + return 0; +} + +/** + * \ingroup set + * \function igraph_set_destroy + * \brief Destroys a set object. + * + * \param set pointer to the set to be destroyed + * + * Time complexity: operating system dependent. + */ +void igraph_set_destroy(igraph_set_t* set) { + assert(set != 0); + if (set->stor_begin != 0) { + igraph_Free(set->stor_begin); + set->stor_begin = NULL; + } +} + +/** + * \ingroup set + * \function igraph_set_inited + * \brief Determines whether a set is initialized or not. + * + * This function checks whether the internal storage for the members of the + * set has been allocated or not, and it assumes that the pointer for the + * internal storage area contains \c NULL if the area is not initialized yet. + * This only applies if you have allocated an array of sets with \c igraph_Calloc or + * if you used the \c IGRAPH_SET_NULL constant to initialize the set. + * + * \param set The set object. + * + * Time complexity: O(1) + */ +igraph_bool_t igraph_set_inited(igraph_set_t* set) { + return (set->stor_begin != 0); +} + +/** + * \ingroup set + * \function igraph_set_reserve + * \brief Reserve memory for a set. + * + * \param set The set object. + * \param size the new \em allocated size of the set. + * + * Time complexity: operating system dependent, should be around + * O(n), n is the new allocated size of the set. + */ +int igraph_set_reserve(igraph_set_t* set, long int size) { + long int actual_size = igraph_set_size(set); + igraph_integer_t *tmp; + assert(set != NULL); + assert(set->stor_begin != NULL); + if (size <= actual_size) { + return 0; + } + + tmp = igraph_Realloc(set->stor_begin, (size_t) size, igraph_integer_t); + if (tmp == 0) { + IGRAPH_ERROR("cannot reserve space for set", IGRAPH_ENOMEM); + } + set->stor_begin = tmp; + set->stor_end = set->stor_begin + size; + set->end = set->stor_begin + actual_size; + + return 0; +} + +/** + * \ingroup set + * \function igraph_set_empty + * \brief Decides whether the size of the set is zero. + * + * \param set The set object. + * \return Non-zero number if the size of the set is not zero and + * zero otherwise. + * + * Time complexity: O(1). + */ +igraph_bool_t igraph_set_empty(const igraph_set_t* set) { + assert(set != NULL); + assert(set->stor_begin != NULL); + return set->stor_begin == set->end; +} + +/** + * \ingroup set + * \function igraph_set_clear + * \brief Removes all elements from a set. + * + * + * This function simply sets the size of the set to zero, it does + * not free any allocated memory. For that you have to call + * \ref igraph_set_destroy(). + * \param v The set object. + * + * Time complexity: O(1). + */ +void igraph_set_clear(igraph_set_t* set) { + assert(set != NULL); + assert(set->stor_begin != NULL); + set->end = set->stor_begin; +} + + +/** + * \ingroup set + * \function igraph_set_size + * \brief Gives the size (=length) of the set. + * + * \param v The set object + * \return The size of the set. + * + * Time complexity: O(1). + */ + +long int igraph_set_size(const igraph_set_t* set) { + assert(set != NULL); + assert(set->stor_begin != NULL); + return set->end - set->stor_begin; +} + + +/** + * \ingroup set + * \function igraph_set_add + * \brief Adds an element to the set. + * + * \param set The set object. + * \param e The element to be added. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: O(log(n)), n is the number of elements in \p set. + */ +int igraph_set_add(igraph_set_t* set, igraph_integer_t e) { + long int left, right, middle; + long int size; + assert(set != NULL); + assert(set->stor_begin != NULL); + + size = igraph_set_size(set); + + /* search where to insert the new element */ + left = 0; + right = size - 1; + while (left < right - 1) { + middle = (left + right) / 2; + if (SET(*set)[middle] > e) { + right = middle; + } else if (SET(*set)[middle] < e) { + left = middle; + } else { + left = middle; + break; + } + } + + if (right >= 0 && SET(*set)[left] != e && SET(*set)[right] == e) { + left = right; + } + + while (left < size && set->stor_begin[left] < e) { + left++; + } + if (left >= size || set->stor_begin[left] != e) { + /* full, allocate more storage */ + if (set->stor_end == set->end) { + long int new_size = size * 2; + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(igraph_set_reserve(set, new_size)); + } + + /* Element should be inserted at position 'left' */ + if (left < size) + memmove(set->stor_begin + left + 1, set->stor_begin + left, + (size_t) (size - left)*sizeof(set->stor_begin[0])); + + set->stor_begin[left] = e; + set->end += 1; + } + + return 0; +} + +/** + * \ingroup set + * \function igraph_set_contains + * \brief Checks whether a given element is in the set or not. + * + * \param set The set object. + * \param e The element being sought. + * \return Positive integer (true) if \p e is found, zero (false) otherwise. + * + * Time complexity: O(log(n)), n is the number of elements in \p set. + */ +int igraph_set_contains(igraph_set_t* set, igraph_integer_t e) { + long int left, right, middle; + + assert(set != NULL); + assert(set->stor_begin != NULL); + + left = 0; + right = igraph_set_size(set) - 1; + + if (right == -1) { + return 0; /* the set is empty */ + } + + /* search for the new element */ + while (left < right - 1) { + middle = (left + right) / 2; + if (SET(*set)[middle] > e) { + right = middle; + } else if (SET(*set)[middle] < e) { + left = middle; + } else { + return 1; + } + } + + return SET(*set)[left] == e || SET(*set)[right] == e; +} + +/** + * \ingroup set + * \function igraph_set_iterate + * \brief Iterates through the element to the set. + * + * Elements are returned in an arbitrary order. + * + * \param set The set object. + * \param state Internal state of the iteration. + * This should be a pointer to a \c long variable + * which must be zero for the first invocation. + * The object should not be adjusted and its value should + * not be used for anything during the iteration. + * \param element The next element or \c NULL (if the iteration + * has ended) is returned here. + * + * \return Nonzero if there are more elements, zero otherwise. + */ +igraph_bool_t igraph_set_iterate(igraph_set_t* set, long int* state, + igraph_integer_t* element) { + assert(set != 0); + assert(set->stor_begin != 0); + assert(state != 0); + assert(element != 0); + + if (*state < igraph_set_size(set)) { + *element = set->stor_begin[*state]; + *state = *state + 1; + return 1; + } else { + *element = 0; + return 0; + } +} + diff --git a/src/igraph_stack.c b/src/igraph_stack.c new file mode 100644 index 0000000..f9c042c --- /dev/null +++ b/src/igraph_stack.c @@ -0,0 +1,89 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_stack.h" + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "stack.pmt" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_LONG +#include "igraph_pmt.h" +#include "stack.pmt" +#include "igraph_pmt_off.h" +#undef BASE_LONG + +#define BASE_INT +#include "igraph_pmt.h" +#include "stack.pmt" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "stack.pmt" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "stack.pmt" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_PTR +#include "igraph_pmt.h" +#include "stack.pmt" +#include "igraph_pmt_off.h" +#undef BASE_PTR + +/** + * \ingroup stack + * \brief Calls free() on all elements of a pointer stack. + */ + +void igraph_stack_ptr_free_all (igraph_stack_ptr_t* v) { + void **ptr; + assert(v != 0); + assert(v->stor_begin != 0); + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + igraph_Free(*ptr); + } +} + +/** + * \ingroup stack + * \brief Calls free() on all elements and destroys the stack. + */ + +void igraph_stack_ptr_destroy_all (igraph_stack_ptr_t* v) { + assert(v != 0); + assert(v->stor_begin != 0); + igraph_stack_ptr_free_all(v); + igraph_stack_ptr_destroy(v); +} + + diff --git a/src/igraph_strvector.c b/src/igraph_strvector.c new file mode 100644 index 0000000..f53ddf3 --- /dev/null +++ b/src/igraph_strvector.c @@ -0,0 +1,591 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_strvector.h" +#include "igraph_memory.h" +#include "igraph_error.h" +#include "config.h" + +#include +#include /* memcpy & co. */ +#include + +/** + * \section igraph_strvector_t + * The igraph_strvector_t type is a vector of strings. + * The current implementation is very simple and not too efficient. It + * works fine for not too many strings, e.g. the list of attribute + * names is returned in a string vector by \ref + * igraph_cattribute_list(). Do not expect great performance from this + * type. + * + * + * \example examples/simple/igraph_strvector.c + * + */ + +/** + * \ingroup strvector + * \function igraph_strvector_init + * \brief Initialize + * + * Reserves memory for the string vector, a string vector must be + * first initialized before calling other functions on it. + * All elements of the string vector are set to the empty string. + * \param sv Pointer to an initialized string vector. + * \param len The (initial) length of the string vector. + * \return Error code. + * + * Time complexity: O(\p len). + */ + +int igraph_strvector_init(igraph_strvector_t *sv, long int len) { + long int i; + sv->data = igraph_Calloc(len, char*); + if (sv->data == 0) { + IGRAPH_ERROR("strvector init failed", IGRAPH_ENOMEM); + } + for (i = 0; i < len; i++) { + sv->data[i] = igraph_Calloc(1, char); + if (sv->data[i] == 0) { + igraph_strvector_destroy(sv); + IGRAPH_ERROR("strvector init failed", IGRAPH_ENOMEM); + } + sv->data[i][0] = '\0'; + } + sv->len = len; + + return 0; +} + +/** + * \ingroup strvector + * \function igraph_strvector_destroy + * \brief Free allocated memory + * + * Destroy a string vector. It may be reinitialized with \ref + * igraph_strvector_init() later. + * \param sv The string vector. + * + * Time complexity: O(l), the total length of the strings, maybe less + * depending on the memory manager. + */ + +void igraph_strvector_destroy(igraph_strvector_t *sv) { + long int i; + assert(sv != 0); + if (sv->data != 0) { + for (i = 0; i < sv->len; i++) { + if (sv->data[i] != 0) { + igraph_Free(sv->data[i]); + } + } + igraph_Free(sv->data); + } +} + +/** + * \ingroup strvector + * \function igraph_strvector_get + * \brief Indexing + * + * Query an element of a string vector. See also the \ref STR macro + * for an easier way. + * \param sv The input string vector. + * \param idx The index of the element to query. + * \param Pointer to a char*, the address of the string + * is stored here. + * + * Time complexity: O(1). + */ + +void igraph_strvector_get(const igraph_strvector_t *sv, long int idx, + char **value) { + assert(sv != 0); + assert(sv->data != 0); + assert(sv->data[idx] != 0); + *value = sv->data[idx]; +} + +/** + * \ingroup strvector + * \function igraph_strvector_set + * \brief Set an element + * + * The provided \p value is copied into the \p idx position in the + * string vector. + * \param sv The string vector. + * \param idx The position to set. + * \param value The new value. + * \return Error code. + * + * Time complexity: O(l), the length of the new string. Maybe more, + * depending on the memory management, if reallocation is needed. + */ + +int igraph_strvector_set(igraph_strvector_t *sv, long int idx, + const char *value) { + assert(sv != 0); + assert(sv->data != 0); + if (sv->data[idx] == 0) { + sv->data[idx] = igraph_Calloc(strlen(value) + 1, char); + if (sv->data[idx] == 0) { + IGRAPH_ERROR("strvector set failed", IGRAPH_ENOMEM); + } + } else { + char *tmp = igraph_Realloc(sv->data[idx], strlen(value) + 1, char); + if (tmp == 0) { + IGRAPH_ERROR("strvector set failed", IGRAPH_ENOMEM); + } + sv->data[idx] = tmp; + } + strcpy(sv->data[idx], value); + + return 0; +} + +/** + * \ingroup strvector + * \function igraph_strvector_set2 + * \brief Sets an element + * + * This is almost the same as \ref igraph_strvector_set, but the new + * value is not a zero terminated string, but its length is given. + * \param sv The string vector. + * \param idx The position to set. + * \param value The new value. + * \param len The length of the new value. + * \return Error code. + * + * Time complexity: O(l), the length of the new string. Maybe more, + * depending on the memory management, if reallocation is needed. + */ +int igraph_strvector_set2(igraph_strvector_t *sv, long int idx, + const char *value, int len) { + assert(sv != 0); + assert(sv->data != 0); + if (sv->data[idx] == 0) { + sv->data[idx] = igraph_Calloc(len + 1, char); + if (sv->data[idx] == 0) { + IGRAPH_ERROR("strvector set failed", IGRAPH_ENOMEM); + } + } else { + char *tmp = igraph_Realloc(sv->data[idx], (size_t) len + 1, char); + if (tmp == 0) { + IGRAPH_ERROR("strvector set failed", IGRAPH_ENOMEM); + } + sv->data[idx] = tmp; + } + memcpy(sv->data[idx], value, (size_t) len * sizeof(char)); + sv->data[idx][len] = '\0'; + + return 0; +} + +/** + * \ingroup strvector + * \function igraph_strvector_remove_section + * \brief Removes a section from a string vector. + * \todo repair realloc + */ + +void igraph_strvector_remove_section(igraph_strvector_t *v, long int from, + long int to) { + long int i; + /* char **tmp; */ + + assert(v != 0); + assert(v->data != 0); + + for (i = from; i < to; i++) { + if (v->data[i] != 0) { + igraph_Free(v->data[i]); + } + } + for (i = 0; i < v->len - to; i++) { + v->data[from + i] = v->data[to + i]; + } + + v->len -= (to - from); + + /* try to make it smaller */ + /* tmp=igraph_Realloc(v->data, v->len, char*); */ + /* if (tmp!=0) { */ + /* v->data=tmp; */ + /* } */ +} + +/** + * \ingroup strvector + * \function igraph_strvector_remove + * \brief Removes a single element from a string vector. + * + * The string will be one shorter. + * \param The string vector. + * \param elem The index of the element to remove. + * + * Time complexity: O(n), the length of the string. + */ + +void igraph_strvector_remove(igraph_strvector_t *v, long int elem) { + assert(v != 0); + assert(v->data != 0); + igraph_strvector_remove_section(v, elem, elem + 1); +} + +/** + * \ingroup strvector + * \function igraph_strvector_move_interval + * \brief Copies an interval of a string vector. + */ + +void igraph_strvector_move_interval(igraph_strvector_t *v, long int begin, + long int end, long int to) { + long int i; + assert(v != 0); + assert(v->data != 0); + for (i = to; i < to + end - begin; i++) { + if (v->data[i] != 0) { + igraph_Free(v->data[i]); + } + } + for (i = 0; i < end - begin; i++) { + if (v->data[begin + i] != 0) { + size_t len = strlen(v->data[begin + i]) + 1; + v->data[to + i] = igraph_Calloc(len, char); + memcpy(v->data[to + i], v->data[begin + i], sizeof(char)*len); + } + } +} + +/** + * \ingroup strvector + * \function igraph_strvector_copy + * \brief Initialization by copying. + * + * Initializes a string vector by copying another string vector. + * \param to Pointer to an uninitialized string vector. + * \param from The other string vector, to be copied. + * \return Error code. + * + * Time complexity: O(l), the total length of the strings in \p from. + */ + +int igraph_strvector_copy(igraph_strvector_t *to, + const igraph_strvector_t *from) { + long int i; + char *str; + assert(from != 0); + /* assert(from->data != 0); */ + to->data = igraph_Calloc(from->len, char*); + if (to->data == 0) { + IGRAPH_ERROR("Cannot copy string vector", IGRAPH_ENOMEM); + } + to->len = from->len; + + for (i = 0; i < from->len; i++) { + int ret; + igraph_strvector_get(from, i, &str); + ret = igraph_strvector_set(to, i, str); + if (ret != 0) { + igraph_strvector_destroy(to); + IGRAPH_ERROR("cannot copy string vector", ret); + } + } + + return 0; +} + +/** + * \function igraph_strvector_append + * Concatenate two string vectors. + * + * \param to The first string vector, the result is stored here. + * \param from The second string vector, it is kept unchanged. + * \return Error code. + * + * Time complexity: O(n+l2), n is the number of strings in the new + * string vector, l2 is the total length of strings in the \p from + * string vector. + */ + +int igraph_strvector_append(igraph_strvector_t *to, + const igraph_strvector_t *from) { + long int len1 = igraph_strvector_size(to), len2 = igraph_strvector_size(from); + long int i; + igraph_bool_t error = 0; + IGRAPH_CHECK(igraph_strvector_resize(to, len1 + len2)); + for (i = 0; i < len2; i++) { + if (from->data[i][0] != '\0') { + igraph_Free(to->data[len1 + i]); + to->data[len1 + i] = strdup(from->data[i]); + if (!to->data[len1 + i]) { + error = 1; + break; + } + } + } + if (error) { + igraph_strvector_resize(to, len1); + IGRAPH_ERROR("Cannot append string vector", IGRAPH_ENOMEM); + } + return 0; +} + +/** + * \function igraph_strvector_clear + * Remove all elements + * + * After this operation the string vector will be empty. + * \param sv The string vector. + * + * Time complexity: O(l), the total length of strings, maybe less, + * depending on the memory manager. + */ + +void igraph_strvector_clear(igraph_strvector_t *sv) { + long int i, n = igraph_strvector_size(sv); + char **tmp; + + for (i = 0; i < n; i++) { + igraph_Free(sv->data[i]); + } + sv->len = 0; + /* try to give back some memory */ + tmp = igraph_Realloc(sv->data, 1, char*); + if (tmp != 0) { + sv->data = tmp; + } +} + +/** + * \ingroup strvector + * \function igraph_strvector_resize + * \brief Resize + * + * If the new size is bigger then empty strings are added, if it is + * smaller then the unneeded elements are removed. + * \param v The string vector. + * \param newsize The new size. + * \return Error code. + * + * Time complexity: O(n), the number of strings if the vector is made + * bigger, O(l), the total length of the deleted strings if it is made + * smaller, maybe less, depending on memory management. + */ + +int igraph_strvector_resize(igraph_strvector_t* v, long int newsize) { + long int toadd = newsize - v->len, i, j; + char **tmp; + long int reallocsize = newsize; + if (reallocsize == 0) { + reallocsize = 1; + } + + assert(v != 0); + assert(v->data != 0); + /* printf("resize %li to %li\n", v->len, newsize); */ + if (newsize < v->len) { + for (i = newsize; i < v->len; i++) { + igraph_Free(v->data[i]); + } + /* try to give back some space */ + tmp = igraph_Realloc(v->data, (size_t) reallocsize, char*); + /* printf("resize %li to %li, %p\n", v->len, newsize, tmp); */ + if (tmp != 0) { + v->data = tmp; + } + } else if (newsize > v->len) { + igraph_bool_t error = 0; + tmp = igraph_Realloc(v->data, (size_t) reallocsize, char*); + if (tmp == 0) { + IGRAPH_ERROR("cannot resize string vector", IGRAPH_ENOMEM); + } + v->data = tmp; + + for (i = 0; i < toadd; i++) { + v->data[v->len + i] = igraph_Calloc(1, char); + if (v->data[v->len + i] == 0) { + error = 1; + break; + } + v->data[v->len + i][0] = '\0'; + } + if (error) { + /* There was an error, free everything we've allocated so far */ + for (j = 0; j < i; j++) { + if (v->data[v->len + i] != 0) { + igraph_Free(v->data[v->len + i]); + } + } + /* Try to give back space */ + tmp = igraph_Realloc(v->data, (size_t) (v->len), char*); + if (tmp != 0) { + v->data = tmp; + } + IGRAPH_ERROR("Cannot resize string vector", IGRAPH_ENOMEM); + } + } + v->len = newsize; + + return 0; +} + +/** + * \ingroup strvector + * \function igraph_strvector_size + * \brief Gives the size of a string vector. + * + * \param sv The string vector. + * \return The length of the string vector. + * + * Time complexity: O(1). + */ + +long int igraph_strvector_size(const igraph_strvector_t *sv) { + assert(sv != 0); + assert(sv->data != 0); + return sv->len; +} + +/** + * \ingroup strvector + * \function igraph_strvector_add + * \brief Adds an element to the back of a string vector. + * + * \param v The string vector. + * \param value The string to add, it will be copied. + * \return Error code. + * + * Time complexity: O(n+l), n is the total number of strings, l is the + * length of the new string. + */ + +int igraph_strvector_add(igraph_strvector_t *v, const char *value) { + long int s = igraph_strvector_size(v); + char **tmp; + assert(v != 0); + assert(v->data != 0); + tmp = igraph_Realloc(v->data, (size_t) s + 1, char*); + if (tmp == 0) { + IGRAPH_ERROR("cannot add string to string vector", IGRAPH_ENOMEM); + } + v->data = tmp; + v->data[s] = igraph_Calloc(strlen(value) + 1, char); + if (v->data[s] == 0) { + IGRAPH_ERROR("cannot add string to string vector", IGRAPH_ENOMEM); + } + strcpy(v->data[s], value); + v->len += 1; + + return 0; +} + +/** + * \ingroup strvector + * \function igraph_strvector_permdelete + * \brief Removes elements from a string vector (for internal use) + */ + +void igraph_strvector_permdelete(igraph_strvector_t *v, const igraph_vector_t *index, + long int nremove) { + long int i; + char **tmp; + assert(v != 0); + assert(v->data != 0); + + for (i = 0; i < igraph_strvector_size(v); i++) { + if (VECTOR(*index)[i] != 0) { + v->data[ (long int) VECTOR(*index)[i] - 1 ] = v->data[i]; + } else { + igraph_Free(v->data[i]); + } + } + /* Try to make it shorter */ + tmp = igraph_Realloc(v->data, v->len - nremove ? + (size_t) (v->len - nremove) : 1, char*); + if (tmp != 0) { + v->data = tmp; + } + v->len -= nremove; +} + +/** + * \ingroup strvector + * \function igraph_strvector_remove_negidx + * \brief Removes elements from a string vector (for internal use) + */ + +void igraph_strvector_remove_negidx(igraph_strvector_t *v, const igraph_vector_t *neg, + long int nremove) { + long int i, idx = 0; + char **tmp; + assert(v != 0); + assert(v->data != 0); + for (i = 0; i < igraph_strvector_size(v); i++) { + if (VECTOR(*neg)[i] >= 0) { + v->data[idx++] = v->data[i]; + } else { + igraph_Free(v->data[i]); + } + } + /* Try to give back some memory */ + tmp = igraph_Realloc(v->data, v->len - nremove ? + (size_t) (v->len - nremove) : 1, char*); + if (tmp != 0) { + v->data = tmp; + } + v->len -= nremove; +} + +int igraph_strvector_print(const igraph_strvector_t *v, FILE *file, + const char *sep) { + + long int i, n = igraph_strvector_size(v); + if (n != 0) { + fprintf(file, "%s", STR(*v, 0)); + } + for (i = 1; i < n; i++) { + fprintf(file, "%s%s", sep, STR(*v, i)); + } + return 0; + +} + +int igraph_strvector_index(const igraph_strvector_t *v, + igraph_strvector_t *newv, + const igraph_vector_t *idx) { + + long int i, newlen = igraph_vector_size(idx); + IGRAPH_CHECK(igraph_strvector_resize(newv, newlen)); + + for (i = 0; i < newlen; i++) { + long int j = (long int) VECTOR(*idx)[i]; + char *str; + igraph_strvector_get(v, j, &str); + igraph_strvector_set(newv, i, str); + } + + return 0; +} diff --git a/src/igraph_trie.c b/src/igraph_trie.c new file mode 100644 index 0000000..224b5d1 --- /dev/null +++ b/src/igraph_trie.c @@ -0,0 +1,391 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_types_internal.h" +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_error.h" +#include "config.h" + +#include +#include /* memcpy & co. */ +#include + +/** + * \ingroup igraphtrie + * \brief Creates a trie node (not to be called directly) + * \return Error code: errors by igraph_strvector_init(), + * igraph_vector_ptr_init() and igraph_vector_init() might be returned. + */ + +static int igraph_i_trie_init_node(igraph_trie_node_t *t) { + IGRAPH_STRVECTOR_INIT_FINALLY(&t->strs, 0); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&t->children, 0); + IGRAPH_VECTOR_INIT_FINALLY(&t->values, 0); + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +static void igraph_i_trie_destroy_node(igraph_trie_node_t *t, igraph_bool_t sfree); + +/** + * \ingroup igraphtrie + * \brief Creates a trie. + * \return Error code: errors by igraph_strvector_init(), + * igraph_vector_ptr_init() and igraph_vector_init() might be returned. + */ + +int igraph_trie_init(igraph_trie_t *t, igraph_bool_t storekeys) { + t->maxvalue = -1; + t->storekeys = storekeys; + IGRAPH_CHECK(igraph_i_trie_init_node( (igraph_trie_node_t *)t )); + IGRAPH_FINALLY(igraph_i_trie_destroy_node, t); + if (storekeys) { + IGRAPH_CHECK(igraph_strvector_init(&t->keys, 0)); + } + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \ingroup igraphtrie + * \brief Destroys a node of a trie (not to be called directly). + */ + +static void igraph_i_trie_destroy_node(igraph_trie_node_t *t, igraph_bool_t sfree) { + long int i; + igraph_strvector_destroy(&t->strs); + for (i = 0; i < igraph_vector_ptr_size(&t->children); i++) { + igraph_trie_node_t *child = VECTOR(t->children)[i]; + if (child != 0) { + igraph_i_trie_destroy_node(child, 1); + } + } + igraph_vector_ptr_destroy(&t->children); + igraph_vector_destroy(&t->values); + if (sfree) { + igraph_Free(t); + } +} + +/** + * \ingroup igraphtrie + * \brief Destroys a trie (frees allocated memory). + */ + +void igraph_trie_destroy(igraph_trie_t *t) { + if (t->storekeys) { + igraph_strvector_destroy(&t->keys); + } + igraph_i_trie_destroy_node( (igraph_trie_node_t*) t, 0); +} + + +/** + * \ingroup igraphtrie + * \brief Internal helping function for igraph_trie_t + */ + +static long int igraph_i_strdiff(const char *str, const char *key) { + + long int diff = 0; + while (key[diff] != '\0' && str[diff] != '\0' && str[diff] == key[diff]) { + diff++; + } + return diff; +} + +/** + * \ingroup igraphtrie + * \brief Search/insert in a trie (not to be called directly). + * + * @return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +int igraph_trie_get_node(igraph_trie_node_t *t, const char *key, + igraph_real_t newvalue, long int *id) { + char *str; + long int i; + igraph_bool_t add; + + /* If newvalue is negative, we don't add the node if nonexistent, only check + * for its existence */ + add = (newvalue >= 0); + + for (i = 0; i < igraph_strvector_size(&t->strs); i++) { + long int diff; + igraph_strvector_get(&t->strs, i, &str); + diff = igraph_i_strdiff(str, key); + + if (diff == 0) { + + /* ------------------------------------ */ + /* No match, next */ + + } else if (str[diff] == '\0' && key[diff] == '\0') { + + /* ------------------------------------ */ + /* They are exactly the same */ + if (VECTOR(t->values)[i] != -1) { + *id = (long int) VECTOR(t->values)[i]; + return 0; + } else { + VECTOR(t->values)[i] = newvalue; + *id = (long int) newvalue; + return 0; + } + + } else if (str[diff] == '\0') { + + /* ------------------------------------ */ + /* str is prefix of key, follow its link if there is one */ + igraph_trie_node_t *node = VECTOR(t->children)[i]; + if (node != 0) { + return igraph_trie_get_node(node, key + diff, newvalue, id); + } else if (add) { + igraph_trie_node_t *node = igraph_Calloc(1, igraph_trie_node_t); + if (node == 0) { + IGRAPH_ERROR("cannot add to trie", IGRAPH_ENOMEM); + } + IGRAPH_STRVECTOR_INIT_FINALLY(&node->strs, 1); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&node->children, 1); + IGRAPH_VECTOR_INIT_FINALLY(&node->values, 1); + IGRAPH_CHECK(igraph_strvector_set(&node->strs, 0, key + diff)); + VECTOR(node->children)[0] = 0; + VECTOR(node->values)[0] = newvalue; + + VECTOR(t->children)[i] = node; + + *id = (long int) newvalue; + IGRAPH_FINALLY_CLEAN(3); + return 0; + } else { + *id = -1; + return 0; + } + + } else if (key[diff] == '\0' && add) { + + /* ------------------------------------ */ + /* key is prefix of str, the node has to be cut */ + char *str2; + + igraph_trie_node_t *node = igraph_Calloc(1, igraph_trie_node_t); + if (node == 0) { + IGRAPH_ERROR("cannot add to trie", IGRAPH_ENOMEM); + } + IGRAPH_STRVECTOR_INIT_FINALLY(&node->strs, 1); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&node->children, 1); + IGRAPH_VECTOR_INIT_FINALLY(&node->values, 1); + IGRAPH_CHECK(igraph_strvector_set(&node->strs, 0, str + diff)); + + VECTOR(node->children)[0] = VECTOR(t->children)[i]; + VECTOR(node->values)[0] = VECTOR(t->values)[i]; + + str2 = strdup(str); + if (str2 == 0) { + IGRAPH_ERROR("cannot add to trie", IGRAPH_ENOMEM); + } + str2[diff] = '\0'; + IGRAPH_FINALLY(igraph_free, str2); + IGRAPH_CHECK(igraph_strvector_set(&t->strs, i, str2)); + igraph_Free(str2); + IGRAPH_FINALLY_CLEAN(4); + + VECTOR(t->values)[i] = newvalue; + VECTOR(t->children)[i] = node; + + *id = (long int) newvalue; + return 0; + + } else if (add) { + + /* ------------------------------------ */ + /* the first diff characters match */ + char *str2; + + igraph_trie_node_t *node = igraph_Calloc(1, igraph_trie_node_t); + if (node == 0) { + IGRAPH_ERROR("cannot add to trie", IGRAPH_ENOMEM); + } + IGRAPH_STRVECTOR_INIT_FINALLY(&node->strs, 2); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&node->children, 2); + IGRAPH_VECTOR_INIT_FINALLY(&node->values, 2); + IGRAPH_CHECK(igraph_strvector_set(&node->strs, 0, str + diff)); + IGRAPH_CHECK(igraph_strvector_set(&node->strs, 1, key + diff)); + VECTOR(node->children)[0] = VECTOR(t->children)[i]; + VECTOR(node->children)[1] = 0; + VECTOR(node->values)[0] = VECTOR(t->values)[i]; + VECTOR(node->values)[1] = newvalue; + + str2 = strdup(str); + if (str2 == 0) { + IGRAPH_ERROR("cannot add to trie", IGRAPH_ENOMEM); + } + str2[diff] = '\0'; + IGRAPH_FINALLY(igraph_free, str2); + IGRAPH_CHECK(igraph_strvector_set(&t->strs, i, str2)); + igraph_Free(str2); + IGRAPH_FINALLY_CLEAN(4); + + VECTOR(t->values)[i] = -1; + VECTOR(t->children)[i] = node; + + *id = (long int) newvalue; + return 0; + } else { + + /* ------------------------------------------------- */ + /* No match, but we requested not to add the new key */ + *id = -1; + return 0; + } + } + + /* ------------------------------------ */ + /* Nothing matches */ + + if (add) { + IGRAPH_CHECK(igraph_vector_ptr_reserve(&t->children, + igraph_vector_ptr_size(&t->children) + 1)); + IGRAPH_CHECK(igraph_vector_reserve(&t->values, igraph_vector_size(&t->values) + 1)); + IGRAPH_CHECK(igraph_strvector_add(&t->strs, key)); + + igraph_vector_ptr_push_back(&t->children, 0); /* allocated */ + igraph_vector_push_back(&t->values, newvalue); /* allocated */ + *id = (long int) newvalue; + } else { + *id = -1; + } + + return 0; +} + +/** + * \ingroup igraphtrie + * \brief Search/insert in a trie. + */ + +int igraph_trie_get(igraph_trie_t *t, const char *key, long int *id) { + if (!t->storekeys) { + IGRAPH_CHECK(igraph_trie_get_node( (igraph_trie_node_t*) t, + key, t->maxvalue + 1, id)); + if (*id > t->maxvalue) { + t->maxvalue = *id; + } + return 0; + } else { + int ret; + igraph_error_handler_t *oldhandler; + oldhandler = igraph_set_error_handler(igraph_error_handler_ignore); + /* Add it to the string vector first, we can undo this later */ + ret = igraph_strvector_add(&t->keys, key); + if (ret != 0) { + igraph_set_error_handler(oldhandler); + IGRAPH_ERROR("cannot get element from trie", ret); + } + ret = igraph_trie_get_node( (igraph_trie_node_t*) t, + key, t->maxvalue + 1, id); + if (ret != 0) { + igraph_strvector_resize(&t->keys, igraph_strvector_size(&t->keys) - 1); + igraph_set_error_handler(oldhandler); + IGRAPH_ERROR("cannot get element from trie", ret); + } + + /* everything is fine */ + if (*id > t->maxvalue) { + t->maxvalue = *id; + } else { + igraph_strvector_resize(&t->keys, igraph_strvector_size(&t->keys) - 1); + } + igraph_set_error_handler(oldhandler); + } + + return 0; +} + +/** + * \ingroup igraphtrie + * \brief Search/insert in a trie (for internal use). + * + * @return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +int igraph_trie_get2(igraph_trie_t *t, const char *key, long int length, + long int *id) { + char *tmp = igraph_Calloc(length + 1, char); + + if (tmp == 0) { + IGRAPH_ERROR("Cannot get from trie", IGRAPH_ENOMEM); + } + + strncpy(tmp, key, length); + tmp[length] = '\0'; + IGRAPH_FINALLY(igraph_free, tmp); + IGRAPH_CHECK(igraph_trie_get(t, tmp, id)); + igraph_Free(tmp); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \ingroup igraphtrie + * \brief Search in a trie. + * This variant does not add \c key to the trie if it does not exist. + * In this case, a negative id is returned. + */ + +int igraph_trie_check(igraph_trie_t *t, const char *key, long int *id) { + IGRAPH_CHECK(igraph_trie_get_node( (igraph_trie_node_t*) t, + key, -1, id)); + return 0; +} + +/** + * \ingroup igraphtrie + * \brief Get an element of a trie based on its index. + */ + +void igraph_trie_idx(igraph_trie_t *t, long int idx, char **str) { + igraph_strvector_get(&t->keys, idx, str); +} + +/** + * \ingroup igraphtrie + * \brief Returns the size of a trie. + */ + +long int igraph_trie_size(igraph_trie_t *t) { + return t->maxvalue + 1; +} + +/* Hmmm, very dirty.... */ + +int igraph_trie_getkeys(igraph_trie_t *t, const igraph_strvector_t **strv) { + *strv = &t->keys; + return 0; +} diff --git a/src/igraph_types_internal.h b/src/igraph_types_internal.h new file mode 100644 index 0000000..cec225c --- /dev/null +++ b/src/igraph_types_internal.h @@ -0,0 +1,395 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_TYPES_INTERNAL_H +#define IGRAPH_TYPES_INTERNAL_H + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus + #define __BEGIN_DECLS extern "C" { + #define __END_DECLS } +#else + #define __BEGIN_DECLS /* empty */ + #define __END_DECLS /* empty */ +#endif + +#include "igraph_types.h" +#include "igraph_matrix.h" +#include "igraph_stack.h" +#include "igraph_strvector.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" + +__BEGIN_DECLS + +/* -------------------------------------------------- */ +/* Indexed heap */ +/* -------------------------------------------------- */ + +/** + * Indexed heap data type. + * \ingroup internal + */ + +typedef struct s_indheap { + igraph_real_t* stor_begin; + igraph_real_t* stor_end; + igraph_real_t* end; + int destroy; + long int* index_begin; +} igraph_indheap_t; + +#define IGRAPH_INDHEAP_NULL { 0,0,0,0,0 } + +int igraph_indheap_init (igraph_indheap_t* h, long int size); +int igraph_indheap_init_array (igraph_indheap_t *t, igraph_real_t* data, long int len); +void igraph_indheap_destroy (igraph_indheap_t* h); +int igraph_indheap_clear(igraph_indheap_t *h); +igraph_bool_t igraph_indheap_empty (igraph_indheap_t* h); +int igraph_indheap_push (igraph_indheap_t* h, igraph_real_t elem); +int igraph_indheap_push_with_index(igraph_indheap_t* h, long int idx, igraph_real_t elem); +int igraph_indheap_modify(igraph_indheap_t* h, long int idx, igraph_real_t elem); +igraph_real_t igraph_indheap_max (igraph_indheap_t* h); +igraph_real_t igraph_indheap_delete_max(igraph_indheap_t* h); +long int igraph_indheap_size (igraph_indheap_t* h); +int igraph_indheap_reserve (igraph_indheap_t* h, long int size); +long int igraph_indheap_max_index(igraph_indheap_t *h); + +void igraph_indheap_i_build(igraph_indheap_t* h, long int head); +void igraph_indheap_i_shift_up(igraph_indheap_t* h, long int elem); +void igraph_indheap_i_sink(igraph_indheap_t* h, long int head); +void igraph_indheap_i_switch(igraph_indheap_t* h, long int e1, long int e2); + +/* -------------------------------------------------- */ +/* Doubly indexed heap */ +/* -------------------------------------------------- */ + +/* This is a heap containing double elements and + two indices, its intended usage is the storage of + weighted edges. +*/ + +/** + * Doubly indexed heap data type. + * \ingroup internal + */ + +typedef struct s_indheap_d { + igraph_real_t* stor_begin; + igraph_real_t* stor_end; + igraph_real_t* end; + int destroy; + long int* index_begin; + long int* index2_begin; +} igraph_d_indheap_t; + + +#define IGRAPH_D_INDHEAP_NULL { 0,0,0,0,0,0 } + +int igraph_d_indheap_init (igraph_d_indheap_t* h, long int size); +void igraph_d_indheap_destroy (igraph_d_indheap_t* h); +igraph_bool_t igraph_d_indheap_empty (igraph_d_indheap_t* h); +int igraph_d_indheap_push (igraph_d_indheap_t* h, igraph_real_t elem, + long int idx, long int idx2); +igraph_real_t igraph_d_indheap_max (igraph_d_indheap_t* h); +igraph_real_t igraph_d_indheap_delete_max(igraph_d_indheap_t* h); +long int igraph_d_indheap_size (igraph_d_indheap_t* h); +int igraph_d_indheap_reserve (igraph_d_indheap_t* h, long int size); +void igraph_d_indheap_max_index(igraph_d_indheap_t *h, long int *idx, long int *idx2); + +void igraph_d_indheap_i_build(igraph_d_indheap_t* h, long int head); +void igraph_d_indheap_i_shift_up(igraph_d_indheap_t* h, long int elem); +void igraph_d_indheap_i_sink(igraph_d_indheap_t* h, long int head); +void igraph_d_indheap_i_switch(igraph_d_indheap_t* h, long int e1, long int e2); + +/* -------------------------------------------------- */ +/* Two-way indexed heap */ +/* -------------------------------------------------- */ + +/* This is a smart indexed heap. In addition to the "normal" indexed heap + it allows to access every element through its index in O(1) time. + In other words, for this heap the _modify operation is O(1), the + normal heap does this in O(n) time.... */ + +typedef struct igraph_2wheap_t { + long int size; + igraph_vector_t data; + igraph_vector_long_t index; + igraph_vector_long_t index2; +} igraph_2wheap_t; + +int igraph_2wheap_init(igraph_2wheap_t *h, long int size); +void igraph_2wheap_destroy(igraph_2wheap_t *h); +int igraph_2wheap_clear(igraph_2wheap_t *h); +int igraph_2wheap_push_with_index(igraph_2wheap_t *h, + long int idx, igraph_real_t elem); +igraph_bool_t igraph_2wheap_empty(const igraph_2wheap_t *h); +long int igraph_2wheap_size(const igraph_2wheap_t *h); +long int igraph_2wheap_max_size(const igraph_2wheap_t *h); +igraph_real_t igraph_2wheap_max(const igraph_2wheap_t *h); +long int igraph_2wheap_max_index(const igraph_2wheap_t *h); +igraph_real_t igraph_2wheap_deactivate_max(igraph_2wheap_t *h); +igraph_bool_t igraph_2wheap_has_elem(const igraph_2wheap_t *h, long int idx); +igraph_bool_t igraph_2wheap_has_active(const igraph_2wheap_t *h, long int idx); +igraph_real_t igraph_2wheap_get(const igraph_2wheap_t *h, long int idx); +igraph_real_t igraph_2wheap_delete_max(igraph_2wheap_t *h); +igraph_real_t igraph_2wheap_delete_max_index(igraph_2wheap_t *h, long int *idx); +int igraph_2wheap_modify(igraph_2wheap_t *h, long int idx, igraph_real_t elem); +int igraph_2wheap_check(igraph_2wheap_t *h); + +/** + * Trie data type + * \ingroup internal + */ + +typedef struct s_igraph_trie_node { + igraph_strvector_t strs; + igraph_vector_ptr_t children; + igraph_vector_t values; +} igraph_trie_node_t; + +typedef struct s_igraph_trie { + igraph_strvector_t strs; + igraph_vector_ptr_t children; + igraph_vector_t values; + long int maxvalue; + igraph_bool_t storekeys; + igraph_strvector_t keys; +} igraph_trie_t; + +#define IGRAPH_TRIE_NULL { IGRAPH_STRVECTOR_NULL, IGRAPH_VECTOR_PTR_NULL, \ + IGRAPH_VECTOR_NULL, 0, 0, IGRAPH_STRVECTOR_NULL } +#define IGRAPH_TRIE_INIT_FINALLY(tr, sk) \ + do { IGRAPH_CHECK(igraph_trie_init(tr, sk)); \ + IGRAPH_FINALLY(igraph_trie_destroy, tr); } while (0) + +int igraph_trie_init(igraph_trie_t *t, igraph_bool_t storekeys); +void igraph_trie_destroy(igraph_trie_t *t); +int igraph_trie_get(igraph_trie_t *t, const char *key, long int *id); +int igraph_trie_check(igraph_trie_t *t, const char *key, long int *id); +int igraph_trie_get2(igraph_trie_t *t, const char *key, long int length, + long int *id); +void igraph_trie_idx(igraph_trie_t *t, long int idx, char **str); +int igraph_trie_getkeys(igraph_trie_t *t, const igraph_strvector_t **strv); +long int igraph_trie_size(igraph_trie_t *t); + +/** + * 2d grid containing points + */ + +typedef struct igraph_2dgrid_t { + igraph_matrix_t *coords; + igraph_real_t minx, maxx, deltax; + igraph_real_t miny, maxy, deltay; + long int stepsx, stepsy; + igraph_matrix_t startidx; + igraph_vector_t next; + igraph_vector_t prev; + igraph_real_t massx, massy; /* The sum of the coordinates */ + long int vertices; /* Number of active vertices */ +} igraph_2dgrid_t; + +int igraph_2dgrid_init(igraph_2dgrid_t *grid, igraph_matrix_t *coords, + igraph_real_t minx, igraph_real_t maxx, igraph_real_t deltax, + igraph_real_t miny, igraph_real_t maxy, igraph_real_t deltay); +void igraph_2dgrid_destroy(igraph_2dgrid_t *grid); +void igraph_2dgrid_add(igraph_2dgrid_t *grid, long int elem, + igraph_real_t xc, igraph_real_t yc); +void igraph_2dgrid_add2(igraph_2dgrid_t *grid, long int elem); +void igraph_2dgrid_move(igraph_2dgrid_t *grid, long int elem, + igraph_real_t xc, igraph_real_t yc); +void igraph_2dgrid_getcenter(const igraph_2dgrid_t *grid, + igraph_real_t *massx, igraph_real_t *massy); +igraph_bool_t igraph_2dgrid_in(const igraph_2dgrid_t *grid, long int elem); +igraph_real_t igraph_2dgrid_dist(const igraph_2dgrid_t *grid, + long int e1, long int e2); +int igraph_2dgrid_neighbors(igraph_2dgrid_t *grid, igraph_vector_t *eids, + igraph_integer_t vid, igraph_real_t r); + +typedef struct igraph_2dgrid_iterator_t { + long int vid, x, y; + long int nei; + long int nx[4], ny[4], ncells; +} igraph_2dgrid_iterator_t; + +void igraph_2dgrid_reset(igraph_2dgrid_t *grid, igraph_2dgrid_iterator_t *it); +igraph_integer_t igraph_2dgrid_next(igraph_2dgrid_t *grid, + igraph_2dgrid_iterator_t *it); +igraph_integer_t igraph_2dgrid_next_nei(igraph_2dgrid_t *grid, + igraph_2dgrid_iterator_t *it); + +/* Another type of grid, each cell is owned by exactly one graph */ + +typedef struct igraph_i_layout_mergegrid_t { + long int *data; + long int stepsx, stepsy; + igraph_real_t minx, maxx, deltax; + igraph_real_t miny, maxy, deltay; +} igraph_i_layout_mergegrid_t; + +int igraph_i_layout_mergegrid_init(igraph_i_layout_mergegrid_t *grid, + igraph_real_t minx, igraph_real_t maxx, long int stepsx, + igraph_real_t miny, igraph_real_t maxy, long int stepsy); +void igraph_i_layout_mergegrid_destroy(igraph_i_layout_mergegrid_t *grid); + +int igraph_i_layout_merge_place_sphere(igraph_i_layout_mergegrid_t *grid, + igraph_real_t x, igraph_real_t y, igraph_real_t r, + long int id); + +long int igraph_i_layout_mergegrid_get(igraph_i_layout_mergegrid_t *grid, + igraph_real_t x, igraph_real_t y); + +long int igraph_i_layout_mergegrid_get_sphere(igraph_i_layout_mergegrid_t *g, + igraph_real_t x, igraph_real_t y, igraph_real_t r); + +/* string -> string hash table */ + +typedef struct igraph_hashtable_t { + igraph_trie_t keys; + igraph_strvector_t elements; + igraph_strvector_t defaults; +} igraph_hashtable_t; + +int igraph_hashtable_init(igraph_hashtable_t *ht); +void igraph_hashtable_destroy(igraph_hashtable_t *ht); +int igraph_hashtable_addset(igraph_hashtable_t *ht, + const char *key, const char *def, + const char *elem); +int igraph_hashtable_addset2(igraph_hashtable_t *ht, + const char *key, const char *def, + const char *elem, int elemlen); +int igraph_hashtable_get(igraph_hashtable_t *ht, + const char *key, char **elem); +int igraph_hashtable_getkeys(igraph_hashtable_t *ht, + const igraph_strvector_t **sv); +int igraph_hashtable_reset(igraph_hashtable_t *ht); + +/* Buckets, needed for the maximum flow algorithm */ + +typedef struct igraph_buckets_t { + igraph_vector_long_t bptr; + igraph_vector_long_t buckets; + igraph_integer_t max, no; +} igraph_buckets_t; + +int igraph_buckets_init(igraph_buckets_t *b, long int bsize, long int size); +void igraph_buckets_destroy(igraph_buckets_t *b); +void igraph_buckets_clear(igraph_buckets_t *b); +long int igraph_buckets_popmax(igraph_buckets_t *b); +long int igraph_buckets_pop(igraph_buckets_t *b, long int bucket); +igraph_bool_t igraph_buckets_empty(const igraph_buckets_t *b); +igraph_bool_t igraph_buckets_empty_bucket(const igraph_buckets_t *b, + long int bucket); +void igraph_buckets_add(igraph_buckets_t *b, long int bucket, + long int elem); + +typedef struct igraph_dbuckets_t { + igraph_vector_long_t bptr; + igraph_vector_long_t next, prev; + igraph_integer_t max, no; +} igraph_dbuckets_t; + +int igraph_dbuckets_init(igraph_dbuckets_t *b, long int bsize, long int size); +void igraph_dbuckets_destroy(igraph_dbuckets_t *b); +void igraph_dbuckets_clear(igraph_dbuckets_t *b); +long int igraph_dbuckets_popmax(igraph_dbuckets_t *b); +long int igraph_dbuckets_pop(igraph_dbuckets_t *b, long int bucket); +igraph_bool_t igraph_dbuckets_empty(const igraph_dbuckets_t *b); +igraph_bool_t igraph_dbuckets_empty_bucket(const igraph_dbuckets_t *b, + long int bucket); +void igraph_dbuckets_add(igraph_dbuckets_t *b, long int bucket, + long int elem); +void igraph_dbuckets_delete(igraph_dbuckets_t *b, long int bucket, + long int elem); + +/* Special maximum heap, needed for the minimum cut algorithm */ + +typedef struct igraph_i_cutheap_t { + igraph_vector_t heap; + igraph_vector_t index; + igraph_vector_t hptr; + long int dnodes; +} igraph_i_cutheap_t; + +int igraph_i_cutheap_init(igraph_i_cutheap_t *ch, igraph_integer_t nodes); +void igraph_i_cutheap_destroy(igraph_i_cutheap_t *ch); +igraph_bool_t igraph_i_cutheap_empty(igraph_i_cutheap_t *ch); +igraph_integer_t igraph_i_cutheap_active_size(igraph_i_cutheap_t *ch); +igraph_integer_t igraph_i_cutheap_size(igraph_i_cutheap_t *ch); +igraph_real_t igraph_i_cutheap_maxvalue(igraph_i_cutheap_t *ch); +igraph_integer_t igraph_i_cutheap_popmax(igraph_i_cutheap_t *ch); +int igraph_i_cutheap_update(igraph_i_cutheap_t *ch, igraph_integer_t index, + igraph_real_t add); +int igraph_i_cutheap_reset_undefine(igraph_i_cutheap_t *ch, long int vertex); + +/* -------------------------------------------------- */ +/* Flexible set */ +/* -------------------------------------------------- */ + +/** + * Set containing integer numbers regardless of the order + * \ingroup types + */ + +typedef struct s_set { + igraph_integer_t* stor_begin; + igraph_integer_t* stor_end; + igraph_integer_t* end; +} igraph_set_t; + +#define IGRAPH_SET_NULL { 0,0,0 } +#define IGRAPH_SET_INIT_FINALLY(v, size) \ + do { IGRAPH_CHECK(igraph_set_init(v, size)); \ + IGRAPH_FINALLY(igraph_set_destroy, v); } while (0) + +int igraph_set_init (igraph_set_t* set, long int size); +void igraph_set_destroy (igraph_set_t* set); +igraph_bool_t igraph_set_inited (igraph_set_t* set); +int igraph_set_reserve (igraph_set_t* set, long int size); +igraph_bool_t igraph_set_empty (const igraph_set_t* set); +void igraph_set_clear (igraph_set_t* set); +long int igraph_set_size (const igraph_set_t* set); +int igraph_set_add (igraph_set_t* v, igraph_integer_t e); +igraph_bool_t igraph_set_contains (igraph_set_t* set, igraph_integer_t e); +igraph_bool_t igraph_set_iterate (igraph_set_t* set, long int* state, + igraph_integer_t* element); + +/* -------------------------------------------------- */ +/* Vectorlist, fixed length */ +/* -------------------------------------------------- */ + +typedef struct igraph_fixed_vectorlist_t { + igraph_vector_t *vecs; + igraph_vector_ptr_t v; + long int length; +} igraph_fixed_vectorlist_t; + +void igraph_fixed_vectorlist_destroy(igraph_fixed_vectorlist_t *l); +int igraph_fixed_vectorlist_convert(igraph_fixed_vectorlist_t *l, + const igraph_vector_t *from, + long int size); + +__END_DECLS + +#endif diff --git a/src/infomap.cc b/src/infomap.cc new file mode 100644 index 0000000..5d38cbd --- /dev/null +++ b/src/infomap.cc @@ -0,0 +1,322 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + ---- + The original version of this file was written by Martin Rosvall + email: martin.rosvall@physics.umu.se + homePage: http://www.tp.umu.se/~rosvall/ + + It was integrated in igraph by Emmanuel Navarro + email: navarro@irit.fr + homePage: http://www.irit.fr/~Emmanuel.Navarro/ +*/ + +#include +#include "igraph_interface.h" +#include "igraph_community.h" +#include "igraph_interrupt_internal.h" + + +#include "infomap_Node.h" +#include "infomap_Greedy.h" + +/****************************************************************************/ +int infomap_partition(FlowGraph * fgraph, bool rcall) { + Greedy * greedy; + + // save the original graph + FlowGraph * cpy_fgraph = new FlowGraph(fgraph); + IGRAPH_FINALLY(delete_FlowGraph, cpy_fgraph); + + int Nnode = cpy_fgraph->Nnode; + // "real" number of vertex, ie. number of vertex of the graph + + int iteration = 0; + double outer_oldCodeLength, newCodeLength; + + int *initial_move = NULL; + bool initial_move_done = true; + + do { // Main loop + outer_oldCodeLength = fgraph->codeLength; + + if (iteration > 0) { + /**********************************************************************/ + // FIRST PART: re-split the network (if need) + // =========================================== + + // intial_move indicate current clustering + initial_move = new int[Nnode]; + // new_cluster_id --> old_cluster_id (save curent clustering state) + + IGRAPH_FINALLY(operator delete [], initial_move); + initial_move_done = false; + + int *subMoveTo = NULL; // enventual new partitionment of original graph + + if ((iteration % 2 == 0) && (fgraph->Nnode > 1)) { + // 0/ Submodule movements : partition each module of the + // current partition (rec. call) + + subMoveTo = new int[Nnode]; + // vid_cpy_fgraph --> new_cluster_id (new partition) + + IGRAPH_FINALLY(operator delete [], subMoveTo); + + int subModIndex = 0; + + for (int i = 0 ; i < fgraph->Nnode ; i++) { + // partition each non trivial module + int sub_Nnode = fgraph->node[i]->members.size(); + if (sub_Nnode > 1) { // If the module is not trivial + int *sub_members = new int[sub_Nnode]; // id_sub --> id + IGRAPH_FINALLY(operator delete [], sub_members); + + for (int j = 0 ; j < sub_Nnode ; j++) { + sub_members[j] = fgraph->node[i]->members[j]; + } + + // extraction of the subgraph + FlowGraph *sub_fgraph = new FlowGraph(cpy_fgraph, sub_Nnode, + sub_members); + IGRAPH_FINALLY(delete_FlowGraph, sub_fgraph); + sub_fgraph->initiate(); + + // recursif call of partitionment on the subgraph + infomap_partition(sub_fgraph, true); + + // Record membership changes + for (int j = 0; j < sub_fgraph->Nnode; j++) { + int Nmembers = sub_fgraph->node[j]->members.size(); + for (int k = 0; k < Nmembers; k++) { + subMoveTo[sub_members[sub_fgraph->node[j]->members[k]]] = + subModIndex; + } + initial_move[subModIndex] = i; + subModIndex++; + } + + delete sub_fgraph; + IGRAPH_FINALLY_CLEAN(1); + delete [] sub_members; + IGRAPH_FINALLY_CLEAN(1); + } else { + subMoveTo[fgraph->node[i]->members[0]] = subModIndex; + initial_move[subModIndex] = i; + subModIndex++; + } + } + } else { + // 1/ Single-node movements : allows each node to move (again) + // save current modules + for (int i = 0; i < fgraph->Nnode; i++) { // for each module + int Nmembers = fgraph->node[i]->members.size(); // Module size + for (int j = 0; j < Nmembers; j++) { // for each vertex (of the module) + initial_move[fgraph->node[i]->members[j]] = i; + } + } + } + + fgraph->back_to(cpy_fgraph); + if (subMoveTo) { + Greedy *cpy_greedy = new Greedy(fgraph); + IGRAPH_FINALLY(delete_Greedy, cpy_greedy); + + cpy_greedy->setMove(subMoveTo); + cpy_greedy->apply(false); + + delete_Greedy(cpy_greedy); + IGRAPH_FINALLY_CLEAN(1); + delete [] subMoveTo; + IGRAPH_FINALLY_CLEAN(1); + } + } + /**********************************************************************/ + // SECOND PART: greedy optimizing it self + // =========================================== + double oldCodeLength; + + do { + // greedy optimizing object creation + greedy = new Greedy(fgraph); + IGRAPH_FINALLY(delete_Greedy, greedy); + + // Initial move to apply ? + if (!initial_move_done && initial_move) { + initial_move_done = true; + greedy->setMove(initial_move); + } + + oldCodeLength = greedy->codeLength; + bool moved = true; + int Nloops = 0; + //int count = 0; + double inner_oldCodeLength = 1000; + + while (moved) { // main greedy optimizing loop + inner_oldCodeLength = greedy->codeLength; + moved = greedy->optimize(); + + Nloops++; + //count++; + + if (fabs(greedy->codeLength - inner_oldCodeLength) < 1.0e-10) + // if the move does'n reduce the codelenght -> exit ! + { + moved = false; + } + + //if (count == 10) { + // greedy->tune(); + // count = 0; + //} + } + + // transform the network to network of modules: + greedy->apply(true); + newCodeLength = greedy->codeLength; + + // destroy greedy object + delete greedy; + IGRAPH_FINALLY_CLEAN(1); + + } while (oldCodeLength - newCodeLength > 1.0e-10); + // while there is some improvement + + if (iteration > 0) { + delete [] initial_move; + IGRAPH_FINALLY_CLEAN(1); + } + + iteration++; + if (!rcall) { + IGRAPH_ALLOW_INTERRUPTION(); + } + } while (outer_oldCodeLength - newCodeLength > 1.0e-10); + + delete cpy_fgraph; + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + + +/** + * \function igraph_community_infomap + * \brief Find community structure that minimizes the expected + * description length of a random walker trajectory. + * + * Implementation of the InfoMap community detection algorithm.of + * Martin Rosvall and Carl T. Bergstrom. + * + * See : + * Visualization of the math and the map generator: www.mapequation.org + * [2] The original paper: M. Rosvall and C. T. Bergstrom, Maps of + * information flow reveal community structure in complex networks, PNAS + * 105, 1118 (2008) [http://dx.doi.org/10.1073/pnas.0706851105 , + * http://arxiv.org/abs/0707.0609 ] + * [3] A more detailed paper: M. Rosvall, D. Axelsson, and C. T. Bergstrom, + * The map equation, Eur. Phys. J. Special Topics 178, 13 (2009). + * [http://dx.doi.org/10.1140/epjst/e2010-01179-1 , + * http://arxiv.org/abs/0906.1405 ] + + * + * The original C++ implementation of Martin Rosvall is used, + * see http://www.tp.umu.se/~rosvall/downloads/infomap_undir.tgz . + * Intergation in igraph has be done by Emmanuel Navarro (who is grateful to + * Martin Rosvall and Carl T. Bergstrom for providing this source code.) + * + * + * Note that the graph must not contain isolated vertices. + * + * + * If you want to specify a random seed (as in original + * implementation) you can use \ref igraph_rng_seed(). + * + * \param graph The input graph. + * \param e_weights Numeric vector giving the weights of the edges. + * If it is a NULL pointer then all edges will have equal + * weights. The weights are expected to be positive. + * \param v_weights Numeric vector giving the weights of the vertices. + * If it is a NULL pointer then all vertices will have equal + * weights. The weights are expected to be positive. + * \param nb_trials The number of attempts to partition the network + * (can be any integer value equal or larger than 1). + * \param membership Pointer to a vector. The membership vector is + * stored here. + * \param codelength Pointer to a real. If not NULL the code length of the + * partition is stored here. + * \return Error code. + * + * \sa \ref igraph_community_spinglass(), \ref + * igraph_community_edge_betweenness(), \ref igraph_community_walktrap(). + * + * Time complexity: TODO. + */ +int igraph_community_infomap(const igraph_t * graph, + const igraph_vector_t *e_weights, + const igraph_vector_t *v_weights, + int nb_trials, + igraph_vector_t *membership, + igraph_real_t *codelength) { + + FlowGraph * fgraph = new FlowGraph(graph, e_weights, v_weights); + IGRAPH_FINALLY(delete_FlowGraph, fgraph); + + // compute stationary distribution + fgraph->initiate(); + + FlowGraph * cpy_fgraph ; + double shortestCodeLength = 1000.0; + + // create membership vector + int Nnode = fgraph->Nnode; + IGRAPH_CHECK(igraph_vector_resize(membership, Nnode)); + + for (int trial = 0; trial < nb_trials; trial++) { + cpy_fgraph = new FlowGraph(fgraph); + IGRAPH_FINALLY(delete_FlowGraph, cpy_fgraph); + + //partition the network + IGRAPH_CHECK(infomap_partition(cpy_fgraph, false)); + + // if better than the better... + if (cpy_fgraph->codeLength < shortestCodeLength) { + shortestCodeLength = cpy_fgraph->codeLength; + // ... store the partition + for (int i = 0 ; i < cpy_fgraph->Nnode ; i++) { + int Nmembers = cpy_fgraph->node[i]->members.size(); + for (int k = 0; k < Nmembers; k++) { + //cluster[ cpy_fgraph->node[i]->members[k] ] = i; + VECTOR(*membership)[cpy_fgraph->node[i]->members[k]] = i; + } + } + } + + delete_FlowGraph(cpy_fgraph); + IGRAPH_FINALLY_CLEAN(1); + } + + *codelength = (igraph_real_t) shortestCodeLength / log(2.0); + + delete fgraph; + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} diff --git a/src/infomap_FlowGraph.cc b/src/infomap_FlowGraph.cc new file mode 100644 index 0000000..57fa32c --- /dev/null +++ b/src/infomap_FlowGraph.cc @@ -0,0 +1,422 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "infomap_FlowGraph.h" + +#define plogp( x ) ( (x) > 0.0 ? (x)*log(x) : 0.0 ) + +using namespace std; + +void FlowGraph::init(int n, const igraph_vector_t *v_weights) { + alpha = 0.15; + beta = 1.0 - alpha; + Nnode = n; + node = new Node*[Nnode]; + if (v_weights) { + for (int i = 0; i < Nnode; i++) { + node[i] = new Node(i, (double)VECTOR(*v_weights)[i]); + } + } else { + for (int i = 0; i < Nnode; i++) { + node[i] = new Node(i, 1.0); + } + } +} + +FlowGraph::FlowGraph(int n) { + init(n, NULL); +} + +FlowGraph::FlowGraph(int n, const igraph_vector_t *v_weights) { + init(n, v_weights); +} + +/* Build the graph from igraph_t object + */ +FlowGraph::FlowGraph(const igraph_t * graph, + const igraph_vector_t *e_weights, + const igraph_vector_t *v_weights) { + + int n = (int)igraph_vcount(graph); + init(n, v_weights); + + int directed = (int) igraph_is_directed(graph); + + double linkWeight = 1.0; + igraph_integer_t from, to; + + long int Nlinks = (long int) igraph_ecount(graph); + if (!directed) { + Nlinks = Nlinks * 2 ; + } + for (int i = 0; i < Nlinks; i++) { + if (!directed) { // not directed + if (i % 2 == 0) { + linkWeight = e_weights ? (double)VECTOR(*e_weights)[i / 2] : 1.0; + igraph_edge(graph, i / 2, &from, &to); + } else { + igraph_edge(graph, (i - 1) / 2, &to, &from); + } + } else { // directed + linkWeight = e_weights ? (double)VECTOR(*e_weights)[i] : 1.0; + igraph_edge(graph, i, &from, &to); + } + + // Populate node from igraph_graph + if (linkWeight > 0.0) { + if (from != to) { + node[(int) from]->outLinks.push_back(make_pair((int)to, linkWeight)); + node[(int) to]->inLinks.push_back(make_pair((int) from, linkWeight)); + } + } + } +} + +FlowGraph::FlowGraph(FlowGraph * fgraph) { + int n = fgraph->Nnode; + init(n, NULL); + for (int i = 0; i < n; i++) { + cpyNode(node[i], fgraph->node[i]); + } + + //XXX: quid de danglings et Ndanglings? + + alpha = fgraph->alpha ; + beta = fgraph->beta ; + + exit = fgraph->exit; + exitFlow = fgraph->exitFlow; + exit_log_exit = fgraph->exit_log_exit; + size_log_size = fgraph->size_log_size ; + nodeSize_log_nodeSize = fgraph->nodeSize_log_nodeSize; + + codeLength = fgraph->codeLength; +} + +/** construct a graph by extracting a subgraph from the given graph + */ +FlowGraph::FlowGraph(FlowGraph * fgraph, int sub_Nnode, int * sub_members) { + init(sub_Nnode, NULL); + + //XXX: use set of integer to ensure that elements are sorted + set sub_mem; + for (int j = 0 ; j < sub_Nnode ; j++) { + sub_mem.insert(sub_members[j]); + } + set::iterator it_mem = sub_mem.begin(); + + vector sub_renumber = vector(fgraph->Nnode); + // id --> sub_id + + for (int j = 0; j < fgraph->Nnode; j++) { + sub_renumber[j] = -1; + } + + + for (int j = 0; j < sub_Nnode; j++) { + //int orig_nr = sub_members[j]; + int orig_nr = (*it_mem); + + node[j]->teleportWeight = fgraph->node[orig_nr]->teleportWeight; + node[j]->selfLink = fgraph->node[orig_nr]->selfLink; + // Take care of self-link + + int orig_NoutLinks = fgraph->node[orig_nr]->outLinks.size(); + int orig_NinLinks = fgraph->node[orig_nr]->inLinks.size(); + + sub_renumber[orig_nr] = j; + + for (int k = 0; k < orig_NoutLinks; k++) { + int to = fgraph->node[orig_nr]->outLinks[k].first; + int to_newnr = sub_renumber[to]; + double link_weight = fgraph->node[orig_nr]->outLinks[k].second; + + if (to < orig_nr) { + // we add links if the destination (to) has already be seen + // (ie. smaller than current id) => orig + + if (sub_mem.find(to) != sub_mem.end()) { + // printf("%2d | %4d to %4d\n", j, orig_nr, to); + // printf("from %4d (%4d:%1.5f) to %4d (%4d)\n", j, orig_nr, + // node[j]->selfLink, to_newnr, to); + node[j]->outLinks.push_back(make_pair(to_newnr, link_weight)); + node[to_newnr]->inLinks.push_back(make_pair(j, link_weight)); + } + } + } + + for (int k = 0; k < orig_NinLinks; k++) { + int to = fgraph->node[orig_nr]->inLinks[k].first; + int to_newnr = sub_renumber[to]; + double link_weight = fgraph->node[orig_nr]->inLinks[k].second; + if (to < orig_nr) { + if (sub_mem.find(to) != sub_mem.end()) { + node[j]->inLinks.push_back(make_pair(to_newnr, link_weight)); + node[to_newnr]->outLinks.push_back(make_pair(j, link_weight)); + } + } + } + it_mem++; + } +} + + +FlowGraph::~FlowGraph() { + //printf("delete FlowGraph !\n"); + for (int i = 0; i < Nnode; i++) { + delete node[i]; + } + delete [] node; +} + +void delete_FlowGraph(FlowGraph *fgraph) { + delete fgraph; +} + + +/** Swap the graph with the one given + the graph is "re" calibrate + but NOT the given one. + */ +void FlowGraph::swap(FlowGraph * fgraph) { + Node ** node_tmp = fgraph->node; + int Nnode_tmp = fgraph->Nnode; + + fgraph->node = node; + fgraph->Nnode = Nnode; + + node = node_tmp; + Nnode = Nnode_tmp; + + calibrate(); +} + +/** Initialisation of the graph, compute the flow inside the graph + * - count danglings nodes + * - normalized edge weights + * - Call eigenvector() to compute steady state distribution + * - call calibrate to compute codelenght + */ +void FlowGraph::initiate() { + // Take care of dangling nodes, normalize outLinks, and calculate + // total teleport weight + Ndanglings = 0; + double totTeleportWeight = 0.0; + for (int i = 0; i < Nnode; i++) { + totTeleportWeight += node[i]->teleportWeight; + } + + for (int i = 0; i < Nnode; i++) { + node[i]->teleportWeight /= totTeleportWeight; + // normalize teleportation weight + + if (node[i]->outLinks.empty() && (node[i]->selfLink <= 0.0)) { + danglings.push_back(i); + Ndanglings++; + } else { // Normalize the weights + int NoutLinks = node[i]->outLinks.size(); + double sum = node[i]->selfLink; // Take care of self-links + for (int j = 0; j < NoutLinks; j++) { + sum += node[i]->outLinks[j].second; + } + node[i]->selfLink /= sum; + for (int j = 0; j < NoutLinks; j++) { + node[i]->outLinks[j].second /= sum; + } + } + } + + // Calculate steady state matrix + eigenvector(); + + // Update links to represent flow + for (int i = 0; i < Nnode; i++) { + node[i]->selfLink = beta * node[i]->size * node[i]->selfLink; + // (1 - \tau) * \pi_i * P_{ii} + + if (!node[i]->outLinks.empty()) { + int NoutLinks = node[i]->outLinks.size(); + for (int j = 0; j < NoutLinks; j++) { + node[i]->outLinks[j].second = beta * node[i]->size * + node[i]->outLinks[j].second; + // (1 - \tau) * \pi_i * P_{ij} + } + + // Update values for corresponding inlink + for (int j = 0; j < NoutLinks; j++) { + int NinLinks = node[node[i]->outLinks[j].first]->inLinks.size(); + for (int k = 0; k < NinLinks; k++) { + if (node[node[i]->outLinks[j].first]->inLinks[k].first == i) { + node[node[i]->outLinks[j].first]->inLinks[k].second = + node[i]->outLinks[j].second; + k = NinLinks; + } + } + } + } + } + + // To be able to handle dangling nodes efficiently + for (int i = 0; i < Nnode; i++) + if (node[i]->outLinks.empty() && (node[i]->selfLink <= 0.0)) { + node[i]->danglingSize = node[i]->size; + } else { + node[i]->danglingSize = 0.0; + } + + nodeSize_log_nodeSize = 0.0 ; + // The exit flow from each node at initiation + for (int i = 0; i < Nnode; i++) { + node[i]->exit = node[i]->size // Proba to be on i + - (alpha * node[i]->size + beta * node[i]->danglingSize) * + node[i]->teleportWeight // Proba teleport back to i + - node[i]->selfLink; // Proba stay on i + + // node[i]->exit == q_{i\exit} + nodeSize_log_nodeSize += plogp(node[i]->size); + } + + calibrate(); +} + + +/* Compute steady state distribution (ie. PageRank) over the network + * (for all i update node[i]->size) + */ +void FlowGraph::eigenvector() { + vector size_tmp = vector(Nnode, 1.0 / Nnode); + + int Niterations = 0; + double danglingSize; + + double sqdiff = 1.0; + double sqdiff_old; + double sum; + do { + // Calculate dangling size + danglingSize = 0.0; + for (int i = 0; i < Ndanglings; i++) { + danglingSize += size_tmp[danglings[i]]; + } + + // Flow from teleportation + for (int i = 0; i < Nnode; i++) { + node[i]->size = (alpha + beta * danglingSize) * node[i]->teleportWeight; + } + + // Flow from network steps + for (int i = 0; i < Nnode; i++) { + node[i]->size += beta * node[i]->selfLink * size_tmp[i]; + int Nlinks = node[i]->outLinks.size(); + for (int j = 0; j < Nlinks; j++) + node[node[i]->outLinks[j].first]->size += beta * + node[i]->outLinks[j].second * size_tmp[i]; + } + + // Normalize + sum = 0.0; + for (int i = 0; i < Nnode; i++) { + sum += node[i]->size; + } + sqdiff_old = sqdiff; + sqdiff = 0.0; + for (int i = 0; i < Nnode; i++) { + node[i]->size /= sum; + sqdiff += fabs(node[i]->size - size_tmp[i]); + size_tmp[i] = node[i]->size; + } + Niterations++; + + if (sqdiff == sqdiff_old) { + alpha += 1.0e-10; + beta = 1.0 - alpha; + } + + } while ((Niterations < 200) && (sqdiff > 1.0e-15 || Niterations < 50)); + + danglingSize = 0.0; + for (int i = 0; i < Ndanglings; i++) { + danglingSize += size_tmp[danglings[i]]; + } + // cout << "done! (the error is " << sqdiff << " after " << Niterations + // << " iterations)" << endl; +} + + +/* Compute the codeLength of the given network + * note: (in **node, one node == one module) + */ +void FlowGraph::calibrate() { + exit_log_exit = 0.0; + exitFlow = 0.0; + size_log_size = 0.0; + + for (int i = 0; i < Nnode; i++) { // For each module + // own node/module codebook + size_log_size += plogp(node[i]->exit + node[i]->size); + + // use of index codebook + exitFlow += node[i]->exit; + exit_log_exit += plogp(node[i]->exit); + } + + exit = plogp(exitFlow); + + codeLength = exit - 2.0 * exit_log_exit + size_log_size - + nodeSize_log_nodeSize; +} + + +/* Restore the data from the given FlowGraph object + */ +void FlowGraph::back_to(FlowGraph * fgraph) { + // delete current nodes + for (int i = 0 ; i < Nnode ; i++) { + delete node[i]; + } + delete [] node; + + Nnode = fgraph->Nnode; + + // copy original ones + node = new Node*[Nnode]; + for (int i = 0; i < Nnode; i++) { + node[i] = new Node(); + cpyNode(node[i], fgraph->node[i]); + } + + // restore atributs + alpha = fgraph->alpha ; + beta = fgraph->beta ; + + exit = fgraph->exit; + exitFlow = fgraph->exitFlow; + exit_log_exit = fgraph->exit_log_exit; + size_log_size = fgraph->size_log_size ; + nodeSize_log_nodeSize = fgraph->nodeSize_log_nodeSize; + + codeLength = fgraph->codeLength; +} + + diff --git a/src/infomap_FlowGraph.h b/src/infomap_FlowGraph.h new file mode 100644 index 0000000..937347c --- /dev/null +++ b/src/infomap_FlowGraph.h @@ -0,0 +1,78 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef FLOWGRAPH_H +#define FLOWGRAPH_H + +#include +#include + +#include "igraph_interface.h" + +#include "infomap_Node.h" + +class FlowGraph { +private: + void init(int n, const igraph_vector_t *nodeWeights); + +public: + FlowGraph(int n); + FlowGraph(int n, const igraph_vector_t *nodeWeights); + FlowGraph(FlowGraph * fgraph); + FlowGraph(FlowGraph * fgraph, int sub_Nnode, int * sub_members); + + FlowGraph(const igraph_t * graph, const igraph_vector_t *e_weights, + const igraph_vector_t *v_weights); + + ~FlowGraph(); + + void swap(FlowGraph * fgraph); + + void initiate(); + void eigenvector(); + void calibrate(); + + void back_to(FlowGraph * fgraph); + + /*************************************************************************/ + Node **node; + int Nnode; + + double alpha, beta; + + int Ndanglings; + std::vector danglings; // id of dangling nodes + + double exit; // + double exitFlow; // + double exit_log_exit; // + double size_log_size; // + double nodeSize_log_nodeSize; // \sum_{v in V} p log(p) + + double codeLength; +}; + +void delete_FlowGraph(FlowGraph *fgraph); + +#endif diff --git a/src/infomap_Greedy.cc b/src/infomap_Greedy.cc new file mode 100644 index 0000000..0fe1f16 --- /dev/null +++ b/src/infomap_Greedy.cc @@ -0,0 +1,614 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "infomap_Greedy.h" +#include +#define plogp( x ) ( (x) > 0.0 ? (x)*log(x) : 0.0 ) + +using namespace std; + +Greedy::Greedy(FlowGraph * fgraph) { + graph = fgraph; + Nnode = graph->Nnode; + + alpha = graph->alpha;// teleportation probability + beta = 1.0 - alpha; // probability to take normal step + + Nempty = 0; + vector(Nnode).swap(mod_empty); + + vector(Nnode).swap(node_index); + vector(Nnode).swap(mod_exit); + vector(Nnode).swap(mod_size); + vector(Nnode).swap(mod_danglingSize); + vector(Nnode).swap(mod_teleportWeight); + vector(Nnode).swap(mod_members); + + nodeSize_log_nodeSize = graph->nodeSize_log_nodeSize; + exit_log_exit = graph->exit_log_exit; + size_log_size = graph->size_log_size; + exitFlow = graph->exitFlow; + + Node ** node = graph->node; + for (int i = 0; i < Nnode; i++) { // For each module + node_index[i] = i; + mod_exit[i] = node[i]->exit; + mod_size[i] = node[i]->size; + + mod_danglingSize[i] = node[i]->danglingSize; + mod_teleportWeight[i] = node[i]->teleportWeight; + mod_members[i] = node[i]->members.size(); + } + + exit = plogp(exitFlow); + + codeLength = exit - 2.0 * exit_log_exit + size_log_size - + nodeSize_log_nodeSize; +} + +Greedy::~Greedy() { +} + +void delete_Greedy(Greedy *greedy) { + delete greedy; +} + + +/** Greedy optimizing (as in Blodel and Al.) : + * for each vertex (selected in a random order) compute the best possible move within neighborhood + */ +bool Greedy::optimize() { + bool moved = false; + Node ** node = graph->node; + + RNG_BEGIN(); + + // Generate random enumeration of nodes + vector randomOrder(Nnode); + for (int i = 0; i < Nnode; i++) { + randomOrder[i] = i; + } + + for (int i = 0; i < Nnode - 1; i++) { + //int randPos = i ; //XXX + int randPos = RNG_INTEGER(i, Nnode - 1); + // swap i & randPos + int tmp = randomOrder[i]; + randomOrder[i] = randomOrder[randPos]; + randomOrder[randPos] = tmp; + } + + unsigned int offset = 1; + vector redirect(Nnode, 0); + vector > > flowNtoM(Nnode); + + for (int k = 0; k < Nnode; k++) { + + // Pick nodes in random order + int flip = randomOrder[k]; + int oldM = node_index[flip]; + + // Reset offset when int overflows + if (offset > INT_MAX) { + for (int j = 0; j < Nnode; j++) { + redirect[j] = 0; + } + offset = 1; + } + // Size of vector with module links + int NmodLinks = 0; + // For all outLinks + int NoutLinks = node[flip]->outLinks.size(); + if (NoutLinks == 0) { //dangling node, add node to calculate flow below + redirect[oldM] = offset + NmodLinks; + flowNtoM[NmodLinks].first = oldM; + flowNtoM[NmodLinks].second.first = 0.0; + flowNtoM[NmodLinks].second.second = 0.0; + NmodLinks++; + } else { + for (int j = 0; j < NoutLinks; j++) { + int nb_M = node_index[node[flip]->outLinks[j].first]; + // index destination du lien + double nb_flow = node[flip]->outLinks[j].second; + // wgt du lien + if (redirect[nb_M] >= offset) { + flowNtoM[redirect[nb_M] - offset].second.first += nb_flow; + } else { + redirect[nb_M] = offset + NmodLinks; + flowNtoM[NmodLinks].first = nb_M; + flowNtoM[NmodLinks].second.first = nb_flow; + flowNtoM[NmodLinks].second.second = 0.0; + NmodLinks++; + } + } + } + // For all inLinks + int NinLinks = node[flip]->inLinks.size(); + for (int j = 0; j < NinLinks; j++) { + int nb_M = node_index[node[flip]->inLinks[j].first]; + double nb_flow = node[flip]->inLinks[j].second; + + if (redirect[nb_M] >= offset) { + flowNtoM[redirect[nb_M] - offset].second.second += nb_flow; + } else { + redirect[nb_M] = offset + NmodLinks; + flowNtoM[NmodLinks].first = nb_M; + flowNtoM[NmodLinks].second.first = 0.0; + flowNtoM[NmodLinks].second.second = nb_flow; + NmodLinks++; + } + } + + // For teleportation and dangling nodes + for (int j = 0; j < NmodLinks; j++) { + int newM = flowNtoM[j].first; + if (newM == oldM) { + flowNtoM[j].second.first += + (alpha * node[flip]->size + beta * node[flip]->danglingSize) * + (mod_teleportWeight[oldM] - node[flip]->teleportWeight); + flowNtoM[j].second.second += + (alpha * (mod_size[oldM] - node[flip]->size) + + beta * (mod_danglingSize[oldM] - node[flip]->danglingSize)) * + node[flip]->teleportWeight; + } else { + flowNtoM[j].second.first += + (alpha * node[flip]->size + beta * node[flip]->danglingSize) * + mod_teleportWeight[newM]; + flowNtoM[j].second.second += + (alpha * mod_size[newM] + beta * mod_danglingSize[newM] ) * + node[flip]->teleportWeight; + } + } + + // Calculate flow to/from own module (default value if no link to + // own module) + double outFlowOldM = + (alpha * node[flip]->size + beta * node[flip]->danglingSize) * + (mod_teleportWeight[oldM] - node[flip]->teleportWeight) ; + double inFlowOldM = + (alpha * (mod_size[oldM] - node[flip]->size) + + beta * (mod_danglingSize[oldM] - node[flip]->danglingSize)) * + node[flip]->teleportWeight; + if (redirect[oldM] >= offset) { + outFlowOldM = flowNtoM[redirect[oldM] - offset].second.first; + inFlowOldM = flowNtoM[redirect[oldM] - offset].second.second; + } + + // Option to move to empty module (if node not already alone) + if (mod_members[oldM] > static_cast(node[flip]->members.size())) { + if (Nempty > 0) { + flowNtoM[NmodLinks].first = mod_empty[Nempty - 1]; + flowNtoM[NmodLinks].second.first = 0.0; + flowNtoM[NmodLinks].second.second = 0.0; + NmodLinks++; + } + } + + // Randomize link order for optimized search + for (int j = 0; j < NmodLinks - 1; j++) { + //int randPos = j ; // XXX + int randPos = RNG_INTEGER(j, NmodLinks - 1); + int tmp_M = flowNtoM[j].first; + double tmp_outFlow = flowNtoM[j].second.first; + double tmp_inFlow = flowNtoM[j].second.second; + flowNtoM[j].first = flowNtoM[randPos].first; + flowNtoM[j].second.first = flowNtoM[randPos].second.first; + flowNtoM[j].second.second = flowNtoM[randPos].second.second; + flowNtoM[randPos].first = tmp_M; + flowNtoM[randPos].second.first = tmp_outFlow; + flowNtoM[randPos].second.second = tmp_inFlow; + } + + int bestM = oldM; + double best_outFlow = 0.0; + double best_inFlow = 0.0; + double best_delta = 0.0; + + // Find the move that minimizes the description length + for (int j = 0; j < NmodLinks; j++) { + + int newM = flowNtoM[j].first; + double outFlowNewM = flowNtoM[j].second.first; + double inFlowNewM = flowNtoM[j].second.second; + + if (newM != oldM) { + + double delta_exit = plogp(exitFlow + outFlowOldM + inFlowOldM - + outFlowNewM - inFlowNewM) - exit; + + double delta_exit_log_exit = - plogp(mod_exit[oldM]) - + plogp(mod_exit[newM]) + + plogp(mod_exit[oldM] - node[flip]->exit + outFlowOldM + inFlowOldM) + + plogp(mod_exit[newM] + node[flip]->exit - outFlowNewM - + inFlowNewM); + + double delta_size_log_size = - plogp(mod_exit[oldM] + mod_size[oldM]) + - plogp(mod_exit[newM] + mod_size[newM]) + + plogp(mod_exit[oldM] + mod_size[oldM] - node[flip]->exit - + node[flip]->size + outFlowOldM + inFlowOldM) + + plogp(mod_exit[newM] + mod_size[newM] + node[flip]->exit + + node[flip]->size - outFlowNewM - inFlowNewM); + + double deltaL = delta_exit - 2.0 * delta_exit_log_exit + + delta_size_log_size; + + if (deltaL - best_delta < -1e-10) { + bestM = newM; + best_outFlow = outFlowNewM; + best_inFlow = inFlowNewM; + best_delta = deltaL; + } + } + } + + // Make best possible move + if (bestM != oldM) { + //Update empty module vector + if (mod_members[bestM] == 0) { + Nempty--; + } + if (mod_members[oldM] == static_cast(node[flip]->members.size())) { + mod_empty[Nempty] = oldM; + Nempty++; + } + + exitFlow -= mod_exit[oldM] + mod_exit[bestM]; + + exit_log_exit -= plogp(mod_exit[oldM]) + plogp(mod_exit[bestM]); + size_log_size -= plogp(mod_exit[oldM] + mod_size[oldM]) + + plogp(mod_exit[bestM] + mod_size[bestM]); + + mod_exit[oldM] -= node[flip]->exit - outFlowOldM - + inFlowOldM; + mod_size[oldM] -= node[flip]->size; + mod_danglingSize[oldM] -= node[flip]->danglingSize; + mod_teleportWeight[oldM] -= node[flip]->teleportWeight; + mod_members[oldM] -= node[flip]->members.size(); + + mod_exit[bestM] += node[flip]->exit - best_outFlow - + best_inFlow; + mod_size[bestM] += node[flip]->size; + mod_danglingSize[bestM] += node[flip]->danglingSize; + mod_teleportWeight[bestM] += node[flip]->teleportWeight; + mod_members[bestM] += node[flip]->members.size(); + + exitFlow += mod_exit[oldM] + mod_exit[bestM]; + + // Update terms in map equation + + exit_log_exit += plogp(mod_exit[oldM]) + plogp(mod_exit[bestM]); + size_log_size += plogp(mod_exit[oldM] + mod_size[oldM]) + + plogp(mod_exit[bestM] + mod_size[bestM]); + exit = plogp(exitFlow); + + // Update code length + + codeLength = exit - 2.0 * exit_log_exit + size_log_size - + nodeSize_log_nodeSize; + + node_index[flip] = bestM; + moved = true; + } + offset += Nnode; + } + + RNG_END(); + + return moved; +} + +/** Apply the move to the given network + */ +void Greedy::apply(bool sort) { +//void Greedy::level(Node ***node_tmp, bool sort) { + + //old fct prepare(sort) + vector modSnode; // will give ids of no-empty modules (nodes) + int Nmod = 0; + if (sort) { + multimap Msize; + for (int i = 0; i < Nnode; i++) { + if (mod_members[i] > 0) { + Nmod++; + Msize.insert(pair(mod_size[i], i)); + } + } + for (multimap::reverse_iterator it = Msize.rbegin(); + it != Msize.rend(); it++) { + modSnode.push_back(it->second); + } + } else { + for (int i = 0; i < Nnode; i++) { + if (mod_members[i] > 0) { + Nmod++; + modSnode.push_back(i); + } + } + } + //modSnode[id_when_no_empty_node] = id_in_mod_tbl + + // Create the new graph + FlowGraph * tmp_fgraph = new FlowGraph(Nmod); + IGRAPH_FINALLY(delete_FlowGraph, tmp_fgraph); + Node ** node_tmp = tmp_fgraph->node ; + + Node ** node = graph->node; + + vector nodeInMod = vector(Nnode); + + // creation of new nodes + for (int i = 0; i < Nmod; i++) { + //node_tmp[i] = new Node(); + vector().swap(node_tmp[i]->members); // clear membership + node_tmp[i]->exit = mod_exit[modSnode[i]]; + node_tmp[i]->size = mod_size[modSnode[i]]; + node_tmp[i]->danglingSize = mod_danglingSize[modSnode[i]]; + node_tmp[i]->teleportWeight = mod_teleportWeight[modSnode[i]]; + + nodeInMod[modSnode[i]] = i; + } + //nodeInMode[id_in_mod_tbl] = id_when_no_empty_node + + // Calculate outflow of links to different modules + vector > outFlowNtoM(Nmod); + map::iterator it_M; + + for (int i = 0; i < Nnode; i++) { + int i_M = nodeInMod[node_index[i]]; //final id of the module of the node i + // add node members to the module + copy( node[i]->members.begin(), node[i]->members.end(), + back_inserter( node_tmp[i_M]->members ) ); + + int NoutLinks = node[i]->outLinks.size(); + for (int j = 0; j < NoutLinks; j++) { + int nb = node[i]->outLinks[j].first; + int nb_M = nodeInMod[node_index[nb]]; + double nb_flow = node[i]->outLinks[j].second; + if (nb != i) { + it_M = outFlowNtoM[i_M].find(nb_M); + if (it_M != outFlowNtoM[i_M].end()) { + it_M->second += nb_flow; + } else { + outFlowNtoM[i_M].insert(make_pair(nb_M, nb_flow)); + } + } + } + } + + // Create outLinks at new level + for (int i = 0; i < Nmod; i++) { + for (it_M = outFlowNtoM[i].begin(); it_M != outFlowNtoM[i].end(); it_M++) { + if (it_M->first != i) { + node_tmp[i]->outLinks.push_back(make_pair(it_M->first, it_M->second)); + } + } + } + + // Calculate inflow of links from different modules + vector > inFlowNtoM(Nmod); + + for (int i = 0; i < Nnode; i++) { + int i_M = nodeInMod[node_index[i]]; + int NinLinks = node[i]->inLinks.size(); + for (int j = 0; j < NinLinks; j++) { + int nb = node[i]->inLinks[j].first; + int nb_M = nodeInMod[node_index[nb]]; + double nb_flow = node[i]->inLinks[j].second; + if (nb != i) { + it_M = inFlowNtoM[i_M].find(nb_M); + if (it_M != inFlowNtoM[i_M].end()) { + it_M->second += nb_flow; + } else { + inFlowNtoM[i_M].insert(make_pair(nb_M, nb_flow)); + } + } + } + } + + // Create inLinks at new level + for (int i = 0; i < Nmod; i++) { + for (it_M = inFlowNtoM[i].begin(); it_M != inFlowNtoM[i].end(); it_M++) { + if (it_M->first != i) { + node_tmp[i]->inLinks.push_back(make_pair(it_M->first, it_M->second)); + } + } + } + + // Option to move to empty module + vector().swap(mod_empty); + Nempty = 0; + + //swap node between tmp_graph and graph, then destroy tmp_fgraph + graph->swap(tmp_fgraph); + Nnode = Nmod; + + delete tmp_fgraph; + IGRAPH_FINALLY_CLEAN(1); +} + + +/** + * RAZ et recalcul : + * - mod_exit + * - mod_size + * - mod_danglingSize + * - mod_teleportWeight + * - mod_members + * and + * - exit_log_exit + * - size_log_size + * - exitFlow + * - exit + * - codeLength + * according to **node / node[i]->index + */ +void Greedy::tune(void) { + + exit_log_exit = 0.0; + size_log_size = 0.0; + exitFlow = 0.0; + + for (int i = 0; i < Nnode; i++) { + mod_exit[i] = 0.0; + mod_size[i] = 0.0; + mod_danglingSize[i] = 0.0; + mod_teleportWeight[i] = 0.0; + mod_members[i] = 0; + } + + Node ** node = graph->node; + // Update all values except contribution from teleportation + for (int i = 0; i < Nnode; i++) { + int i_M = node_index[i]; // module id of node i + int Nlinks = node[i]->outLinks.size(); + + mod_size[i_M] += node[i]->size; + mod_danglingSize[i_M] += node[i]->danglingSize; + mod_teleportWeight[i_M] += node[i]->teleportWeight; + mod_members[i_M]++; + + for (int j = 0; j < Nlinks; j++) { + int neighbor = node[i]->outLinks[j].first; + double neighbor_w = node[i]->outLinks[j].second; + int neighbor_M = node_index[neighbor]; + if (i_M != neighbor_M) { // neighbor in an other module + mod_exit[i_M] += neighbor_w; + } + } + } + + // Update contribution from teleportation + for (int i = 0; i < Nnode; i++) { + mod_exit[i] += (alpha * mod_size[i] + beta * mod_danglingSize[i]) * + (1.0 - mod_teleportWeight[i]); + } + + for (int i = 0; i < Nnode; i++) { + exit_log_exit += plogp(mod_exit[i]); + size_log_size += plogp(mod_exit[i] + mod_size[i]); + exitFlow += mod_exit[i]; + } + exit = plogp(exitFlow); + + codeLength = exit - 2.0 * exit_log_exit + size_log_size - + nodeSize_log_nodeSize; +} + + +/* Compute the new CodeSize if modules are merged as indicated by moveTo + */ +void Greedy::setMove(int *moveTo) { + //void Greedy::determMove(int *moveTo) { + Node ** node = graph->node; + //printf("setMove nNode:%d \n", Nnode); + for (int i = 0 ; i < Nnode ; i++) { // pour chaque module + int oldM = i; + int newM = moveTo[i]; + //printf("old -> new : %d -> %d \n", oldM, newM); + if (newM != oldM) { + + // Si je comprend bien : + // outFlow... : c'est le "flow" de i-> autre sommet du meme module + // inFlow... : c'est le "flow" depuis un autre sommet du meme module --> i + double outFlowOldM = (alpha * node[i]->size + beta * node[i]->danglingSize) * + (mod_teleportWeight[oldM] - node[i]->teleportWeight); + double inFlowOldM = (alpha * (mod_size[oldM] - node[i]->size) + + beta * (mod_danglingSize[oldM] - + node[i]->danglingSize)) * + node[i]->teleportWeight; + double outFlowNewM = (alpha * node[i]->size + beta * node[i]->danglingSize) + * mod_teleportWeight[newM]; + double inFlowNewM = (alpha * mod_size[newM] + + beta * mod_danglingSize[newM]) * + node[i]->teleportWeight; + + // For all outLinks + int NoutLinks = node[i]->outLinks.size(); + for (int j = 0; j < NoutLinks; j++) { + int nb_M = node_index[node[i]->outLinks[j].first]; + double nb_flow = node[i]->outLinks[j].second; + if (nb_M == oldM) { + outFlowOldM += nb_flow; + } else if (nb_M == newM) { + outFlowNewM += nb_flow; + } + } + + // For all inLinks + int NinLinks = node[i]->inLinks.size(); + for (int j = 0; j < NinLinks; j++) { + int nb_M = node_index[node[i]->inLinks[j].first]; + double nb_flow = node[i]->inLinks[j].second; + if (nb_M == oldM) { + inFlowOldM += nb_flow; + } else if (nb_M == newM) { + inFlowNewM += nb_flow; + } + } + + // Update empty module vector + // RAZ de mod_empty et Nempty ds calibrate() + if (mod_members[newM] == 0) { + // si le nouveau etait vide, on a un vide de moins... + Nempty--; + } + if (mod_members[oldM] == static_cast(node[i]->members.size())) { + // si l'ancien avait la taille de celui qui bouge, un vide de plus + mod_empty[Nempty] = oldM; + Nempty++; + } + + exitFlow -= mod_exit[oldM] + mod_exit[newM]; + exit_log_exit -= plogp(mod_exit[oldM]) + plogp(mod_exit[newM]); + size_log_size -= plogp(mod_exit[oldM] + mod_size[oldM]) + + plogp(mod_exit[newM] + mod_size[newM]); + + mod_exit[oldM] -= node[i]->exit - outFlowOldM - inFlowOldM; + mod_size[oldM] -= node[i]->size; + mod_danglingSize[oldM] -= node[i]->danglingSize; + mod_teleportWeight[oldM] -= node[i]->teleportWeight; + mod_members[oldM] -= node[i]->members.size(); + mod_exit[newM] += node[i]->exit - outFlowNewM - inFlowNewM; + mod_size[newM] += node[i]->size; + mod_danglingSize[newM] += node[i]->danglingSize; + mod_teleportWeight[newM] += node[i]->teleportWeight; + mod_members[newM] += node[i]->members.size(); + + exitFlow += mod_exit[oldM] + mod_exit[newM]; + exit_log_exit += plogp(mod_exit[oldM]) + plogp(mod_exit[newM]); + size_log_size += plogp(mod_exit[oldM] + mod_size[oldM]) + + plogp(mod_exit[newM] + mod_size[newM]); + exit = plogp(exitFlow); + + codeLength = exit - 2.0 * exit_log_exit + size_log_size - + nodeSize_log_nodeSize; + + node_index[i] = newM; + + } + + } +} + + diff --git a/src/infomap_Greedy.h b/src/infomap_Greedy.h new file mode 100644 index 0000000..9769d1d --- /dev/null +++ b/src/infomap_Greedy.h @@ -0,0 +1,85 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef GREEDY_H +#define GREEDY_H + +#include +#include +#include +#include + +#include "igraph_random.h" + +#include "infomap_Node.h" +#include "infomap_FlowGraph.h" + +class Greedy { +public: + Greedy(FlowGraph * fgraph); + // initialise les attributs par rapport au graph + + ~Greedy(); + + void setMove(int *moveTo); + //virtual void determMove(int *moveTo); + + bool optimize(); + //virtual void move(bool &moved); + + void apply(bool sort); + //virtual void level(Node ***, bool sort); + + void tune(void); + + /**************************************************************************/ + + FlowGraph * graph; + int Nnode; + + double exit; + double exitFlow; + double exit_log_exit; + double size_log_size; + double nodeSize_log_nodeSize; + + double codeLength; + + double alpha, beta; + // local copy of fgraph alpha, beta (=alpha - Nnode = graph->Nnode;1) + + std::vector node_index; // module number of each node + + int Nempty; + std::vector mod_empty; + + std::vector mod_exit; // version tmp de node + std::vector mod_size; + std::vector mod_danglingSize; + std::vector mod_teleportWeight; + std::vector mod_members; +}; + +void delete_Greedy(Greedy *greedy); +#endif diff --git a/src/infomap_Node.cc b/src/infomap_Node.cc new file mode 100644 index 0000000..8633a11 --- /dev/null +++ b/src/infomap_Node.cc @@ -0,0 +1,72 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "infomap_Node.h" + +using namespace std; + +Node::Node() { + exit = 0.0; + size = 0.0; + selfLink = 0.0; +} + +Node::Node(int nodenr, double tpweight) { + teleportWeight = tpweight; + exit = 0.0; + size = 0.0; + selfLink = 0.0; + members.push_back(nodenr); // members = [nodenr] +} + +void cpyNode(Node *newNode, Node *oldNode) { + newNode->exit = oldNode->exit; + newNode->size = oldNode->size; + newNode->teleportWeight = oldNode->teleportWeight; + newNode->danglingSize = oldNode->danglingSize; + + int Nmembers = oldNode->members.size(); + newNode->members = vector(Nmembers); + for (int i = 0; i < Nmembers; i++) { + newNode->members[i] = oldNode->members[i]; + } + + newNode->selfLink = oldNode->selfLink; + + int NoutLinks = oldNode->outLinks.size(); + newNode->outLinks = vector >(NoutLinks); + for (int i = 0; i < NoutLinks; i++) { + newNode->outLinks[i].first = oldNode->outLinks[i].first; + newNode->outLinks[i].second = oldNode->outLinks[i].second; + } + + int NinLinks = oldNode->inLinks.size(); + newNode->inLinks = vector >(NinLinks); + for (int i = 0; i < NinLinks; i++) { + newNode->inLinks[i].first = oldNode->inLinks[i].first; + newNode->inLinks[i].second = oldNode->inLinks[i].second; + } + +} + diff --git a/src/infomap_Node.h b/src/infomap_Node.h new file mode 100644 index 0000000..5cd0407 --- /dev/null +++ b/src/infomap_Node.h @@ -0,0 +1,52 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef NODE_H +#define NODE_H + +#include +#include + +#include "igraph_interface.h" + +class Node { +public: + + Node(); + Node(int modulenr, double tpweight); + + std::vector members; + std::vector< std::pair > inLinks; + std::vector< std::pair > outLinks; + double selfLink; + + double teleportWeight; + double danglingSize; + double exit; + double size; +}; + +void cpyNode(Node *newNode, Node *oldNode); + +#endif diff --git a/src/interrupt.c b/src/interrupt.c new file mode 100644 index 0000000..20f7eec --- /dev/null +++ b/src/interrupt.c @@ -0,0 +1,46 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_interrupt.h" +#include "config.h" + +#include +#include +#include + +IGRAPH_THREAD_LOCAL igraph_interruption_handler_t +*igraph_i_interruption_handler = 0; + +int igraph_allow_interruption(void* data) { + if (igraph_i_interruption_handler) { + return igraph_i_interruption_handler(data); + } + return IGRAPH_SUCCESS; +} + +igraph_interruption_handler_t * +igraph_set_interruption_handler (igraph_interruption_handler_t * new_handler) { + igraph_interruption_handler_t * previous_handler = igraph_i_interruption_handler; + igraph_i_interruption_handler = new_handler; + return previous_handler; +} diff --git a/src/iterators.c b/src/iterators.c new file mode 100644 index 0000000..c413b22 --- /dev/null +++ b/src/iterators.c @@ -0,0 +1,1916 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_iterators.h" +#include "igraph_memory.h" +#include "igraph_interface.h" +#include "config.h" + +#include +#include + +/** + * \section about_iterators About selectors, iterators + * + * Everything about vertices and vertex selectors also applies + * to edges and edge selectors unless explicitly noted otherwise. + * + * The vertex (and edge) selector notion was introduced in igraph 0.2. + * It is a way to reference a sequence of vertices or edges + * independently of the graph. + * + * While this might sound quite mysterious, it is actually very + * simple. For example, all vertices of a graph can be selected by + * \ref igraph_vs_all() and the graph independence means that + * \ref igraph_vs_all() is not parametrized by a graph object. That is, + * \ref igraph_vs_all() is the general \em concept of selecting all vertices + * of a graph. A vertex selector is then a way to specify the class of vertices + * to be visited. The selector might specify that all vertices of a graph or + * all the neighbours of a vertex are to be visited. A vertex selector is a + * way of saying that you want to visit a bunch of vertices, as opposed to a + * vertex iterator which is a concrete plan for visiting each of the + * chosen vertices of a specific graph. + * + * To determine the actual vertex IDs implied by a vertex selector, you + * need to apply the concept of selecting vertices to a specific graph object. + * This can be accomplished by instantiating a vertex iterator using a + * specific vertex selection concept and a specific graph object. The notion + * of vertex iterators can be thought of in the following way. Given a + * specific graph object and the class of vertices to be visited, a vertex + * iterator is a road map, plan or route for how to visit the chosen + * vertices. + * + * Some vertex selectors have \em immediate versions. These have the + * prefix \c igraph_vss instead of \c igraph_vs, e.g. \ref igraph_vss_all() + * instead of \ref igraph_vs_all(). The immediate versions are to be used in + * the parameter list of the igraph functions, such as \ref igraph_degree(). + * These functions are not associated with any \type igraph_vs_t object, so + * they have no separate constructors and destructors + * (destroy functions). + */ + +/** + * \section about_vertex_selectors + * + * Vertex selectors are created by vertex selector constructors, + * can be instantiated with \ref igraph_vit_create(), and are + * destroyed with \ref igraph_vs_destroy(). + */ + +/** + * \function igraph_vs_all + * \brief Vertex set, all vertices of a graph. + * + * \param vs Pointer to an uninitialized \type igraph_vs_t object. + * \return Error code. + * \sa \ref igraph_vss_all(), \ref igraph_vs_destroy() + * + * This selector includes all vertices of a given graph in + * increasing vertex id order. + * + * + * Time complexity: O(1). + */ + +int igraph_vs_all(igraph_vs_t *vs) { + vs->type = IGRAPH_VS_ALL; + return 0; +} + +/** + * \function igraph_vss_all + * \brief All vertices of a graph (immediate version). + * + * Immediate vertex selector for all vertices in a graph. It can + * be used conveniently when some vertex property (eg. betweenness, + * degree, etc.) should be calculated for all vertices. + * + * \return A vertex selector for all vertices in a graph. + * \sa \ref igraph_vs_all() + * + * Time complexity: O(1). + */ + +igraph_vs_t igraph_vss_all(void) { + igraph_vs_t allvs; + allvs.type = IGRAPH_VS_ALL; + return allvs; +} + +/** + * \function igraph_vs_adj + * \brief Adjacent vertices of a vertex. + * + * All neighboring vertices of a given vertex are selected by this + * selector. The \c mode argument controls the type of the neighboring + * vertices to be selected. The vertices are visited in increasing vertex + * ID order, as of igraph version 0.4. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \param vid Vertex ID, the center of the neighborhood. + * \param mode Decides the type of the neighborhood for directed + * graphs. This parameter is ignored for undirected graphs. + * Possible values: + * \clist + * \cli IGRAPH_OUT + * All vertices to which there is a directed edge from \c vid. That + * is, all the out-neighbors of \c vid. + * \cli IGRAPH_IN + * All vertices from which there is a directed edge to \c vid. In + * other words, all the in-neighbors of \c vid. + * \cli IGRAPH_ALL + * All vertices to which or from which there is a directed edge + * from/to \c vid. That is, all the neighbors of \c vid considered + * as if the graph is undirected. + * \endclist + * \return Error code. + * \sa \ref igraph_vs_destroy() + * + * Time complexity: O(1). + */ + +int igraph_vs_adj(igraph_vs_t *vs, + igraph_integer_t vid, igraph_neimode_t mode) { + vs->type = IGRAPH_VS_ADJ; + vs->data.adj.vid = vid; + vs->data.adj.mode = mode; + return 0; +} + +/** + * \function igraph_vs_nonadj + * \brief Non-adjacent vertices of a vertex. + * + * All non-neighboring vertices of a given vertex. The \p mode + * argument controls the type of neighboring vertices \em not to + * select. Instead of selecting immediate neighbors of \c vid as is done by + * \ref igraph_vs_adj(), the current function selects vertices that are \em not + * immediate neighbors of \c vid. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \param vid Vertex ID, the \quote center \endquote of the + * non-neighborhood. + * \param mode The type of neighborhood not to select in directed + * graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * All vertices will be selected except those to which there is a + * directed edge from \c vid. That is, we select all vertices + * excluding the out-neighbors of \c vid. + * \cli IGRAPH_IN + * All vertices will be selected except those from which there is a + * directed edge to \c vid. In other words, we select all vertices + * but the in-neighbors of \c vid. + * \cli IGRAPH_ALL + * All vertices will be selected except those from or to which there + * is a directed edge to or from \c vid. That is, we select all + * vertices of \c vid except for its immediate neighbors. + * \endclist + * \return Error code. + * \sa \ref igraph_vs_destroy() + * + * Time complexity: O(1). + * + * \example examples/simple/igraph_vs_nonadj.c + */ + +int igraph_vs_nonadj(igraph_vs_t *vs, igraph_integer_t vid, + igraph_neimode_t mode) { + vs->type = IGRAPH_VS_NONADJ; + vs->data.adj.vid = vid; + vs->data.adj.mode = mode; + return 0; +} + +/** + * \function igraph_vs_none + * \brief Empty vertex set. + * + * Creates an empty vertex selector. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \return Error code. + * \sa \ref igraph_vss_none(), \ref igraph_vs_destroy() + * + * Time complexity: O(1). + */ + +int igraph_vs_none(igraph_vs_t *vs) { + vs->type = IGRAPH_VS_NONE; + return 0; +} + +/** + * \function igraph_vss_none + * \brief Empty vertex set (immediate version). + * + * The immediate version of the empty vertex selector. + * + * \return An empty vertex selector. + * \sa \ref igraph_vs_none() + * + * Time complexity: O(1). + */ + +igraph_vs_t igraph_vss_none(void) { + igraph_vs_t nonevs; + nonevs.type = IGRAPH_VS_NONE; + return nonevs; +} + +/** + * \function igraph_vs_1 + * \brief Vertex set with a single vertex. + * + * This vertex selector selects a single vertex. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \param vid The vertex id to be selected. + * \return Error Code. + * \sa \ref igraph_vss_1(), \ref igraph_vs_destroy() + * + * Time complexity: O(1). + */ + +int igraph_vs_1(igraph_vs_t *vs, igraph_integer_t vid) { + vs->type = IGRAPH_VS_1; + vs->data.vid = vid; + return 0; +} + +/** + * \function igraph_vss_1 + * \brief Vertex set with a single vertex (immediate version). + * + * The immediate version of the single-vertex selector. + * + * \param vid The vertex to be selected. + * \return A vertex selector containing a single vertex. + * \sa \ref igraph_vs_1() + * + * Time complexity: O(1). + */ + +igraph_vs_t igraph_vss_1(igraph_integer_t vid) { + igraph_vs_t onevs; + onevs.type = IGRAPH_VS_1; + onevs.data.vid = vid; + return onevs; +} + +/** + * \function igraph_vs_vector + * \brief Vertex set based on a vector. + * + * This function makes it possible to handle a \type vector_t + * temporarily as a vertex selector. The vertex selector should be + * thought of like a \em view to the vector. If you make changes to + * the vector that also affects the vertex selector. Destroying the + * vertex selector does not destroy the vector. (Of course.) Do not + * destroy the vector before destroying the vertex selector, or you + * might get strange behavior. + * + * \param vs Pointer to an uninitialized vertex selector. + * \param v Pointer to a \type igraph_vector_t object. + * \return Error code. + * \sa \ref igraph_vss_vector(), \ref igraph_vs_destroy() + * + * Time complexity: O(1). + * + * \example examples/simple/igraph_vs_vector.c + */ + +int igraph_vs_vector(igraph_vs_t *vs, + const igraph_vector_t *v) { + vs->type = IGRAPH_VS_VECTORPTR; + vs->data.vecptr = v; + return 0; +} + +/** + * \function igraph_vss_vector + * \brief Vertex set based on a vector (immediate version). + * + * This is the immediate version of \ref igraph_vs_vector. + * + * \param v Pointer to a \type igraph_vector_t object. + * \return A vertex selector object containing the vertices in the + * vector. + * \sa \ref igraph_vs_vector() + * + * Time complexity: O(1). + */ + +igraph_vs_t igraph_vss_vector(const igraph_vector_t *v) { + igraph_vs_t vecvs; + vecvs.type = IGRAPH_VS_VECTORPTR; + vecvs.data.vecptr = v; + return vecvs; +} + +/** + * \function igraph_vs_vector_small + * \brief Create a vertex set by giving its elements. + * + * This function can be used to create a vertex selector with a couple + * of vertices. Do not forget to include a -1 after the + * last vertex id. The behavior of the function is undefined if you + * don't use a -1 properly. + * + * + * Note that the vertex ids supplied will be parsed as + * int's so you cannot supply arbitrarily large (too + * large for int) vertex ids here. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \param ... Additional parameters, these will be the vertex ids to + * be included in the vertex selector. Supply a -1 + * after the last vertex id. + * \return Error code. + * \sa \ref igraph_vs_destroy() + * + * Time complexity: O(n), the number of vertex ids supplied. + */ + +int igraph_vs_vector_small(igraph_vs_t *vs, ...) { + va_list ap; + long int i, n = 0; + vs->type = IGRAPH_VS_VECTOR; + vs->data.vecptr = igraph_Calloc(1, igraph_vector_t); + if (vs->data.vecptr == 0) { + IGRAPH_ERROR("Cannot create vertex selector", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*)vs->data.vecptr); + + va_start(ap, vs); + while (1) { + int num = va_arg(ap, int); + if (num == -1) { + break; + } + n++; + } + va_end(ap); + + IGRAPH_VECTOR_INIT_FINALLY((igraph_vector_t*)vs->data.vecptr, n); + + va_start(ap, vs); + for (i = 0; i < n; i++) { + VECTOR(*vs->data.vecptr)[i] = (igraph_real_t) va_arg(ap, int); + } + va_end(ap); + + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/** + * \function igraph_vs_vector_copy + * \brief Vertex set based on a vector, with copying. + * + * This function makes it possible to handle a \type vector_t + * permanently as a vertex selector. The vertex selector creates a + * copy of the original vector, so the vector can safely be destroyed + * after creating the vertex selector. Changing the original vector + * will not affect the vertex selector. The vertex selector is + * responsible for deleting the copy made by itself. + * + * \param vs Pointer to an uninitialized vertex selector. + * \param v Pointer to a \type igraph_vector_t object. + * \return Error code. + * \sa \ref igraph_vs_destroy() + * + * Time complexity: O(1). + */ + +int igraph_vs_vector_copy(igraph_vs_t *vs, + const igraph_vector_t *v) { + vs->type = IGRAPH_VS_VECTOR; + vs->data.vecptr = igraph_Calloc(1, igraph_vector_t); + if (vs->data.vecptr == 0) { + IGRAPH_ERROR("Cannot create vertex selector", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*)vs->data.vecptr); + IGRAPH_CHECK(igraph_vector_copy((igraph_vector_t*)vs->data.vecptr, v)); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_vs_seq + * \brief Vertex set, an interval of vertices. + * + * Creates a vertex selector containing all vertices with vertex id + * equal to or bigger than \c from and equal to or smaller than \c + * to. + * + * \param vs Pointer to an uninitialized vertex selector object. + * \param from The first vertex id to be included in the vertex + * selector. + * \param to The last vertex id to be included in the vertex + * selector. + * \return Error code. + * \sa \ref igraph_vss_seq(), \ref igraph_vs_destroy() + * + * Time complexity: O(1). + * + * \example examples/simple/igraph_vs_seq.c + */ + +int igraph_vs_seq(igraph_vs_t *vs, + igraph_integer_t from, igraph_integer_t to) { + vs->type = IGRAPH_VS_SEQ; + vs->data.seq.from = from; + vs->data.seq.to = to + 1; + return 0; +} + +/** + * \function igraph_vss_seq + * \brief An interval of vertices (immediate version). + * + * The immediate version of \ref igraph_vs_seq(). + * + * \param from The first vertex id to be included in the vertex + * selector. + * \param to The last vertex id to be included in the vertex + * selector. + * \return Error code. + * \sa \ref igraph_vs_seq() + * + * Time complexity: O(1). + */ + +igraph_vs_t igraph_vss_seq(igraph_integer_t from, igraph_integer_t to) { + igraph_vs_t vs; + vs.type = IGRAPH_VS_SEQ; + vs.data.seq.from = from; + vs.data.seq.to = to + 1; + return vs; +} + +/** + * \function igraph_vs_destroy + * \brief Destroy a vertex set. + * + * This function should be called for all vertex selectors when they + * are not needed. The memory allocated for the vertex selector will + * be deallocated. Do not call this function on vertex selectors + * created with the immediate versions of the vertex selector + * constructors (starting with igraph_vss). + * + * \param vs Pointer to a vertex selector object. + * + * Time complexity: operating system dependent, usually O(1). + */ + +void igraph_vs_destroy(igraph_vs_t *vs) { + switch (vs->type) { + case IGRAPH_VS_ALL: + case IGRAPH_VS_ADJ: + case IGRAPH_VS_NONE: + case IGRAPH_VS_1: + case IGRAPH_VS_VECTORPTR: + case IGRAPH_VS_SEQ: + case IGRAPH_VS_NONADJ: + break; + case IGRAPH_VS_VECTOR: + igraph_vector_destroy((igraph_vector_t*)vs->data.vecptr); + igraph_Free(vs->data.vecptr); + break; + default: + break; + } +} + +/** + * \function igraph_vs_is_all + * \brief Check whether all vertices are included. + * + * This function checks whether the vertex selector object was created + * by \ref igraph_vs_all() or \ref igraph_vss_all(). Note that the + * vertex selector might contain all vertices in a given graph but if + * it wasn't created by the two constructors mentioned here the return + * value will be FALSE. + * + * \param vs Pointer to a vertex selector object. + * \return TRUE (1) if the vertex selector contains all vertices and + * FALSE (0) otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t igraph_vs_is_all(const igraph_vs_t *vs) { + return vs->type == IGRAPH_VS_ALL; +} + +int igraph_vs_as_vector(const igraph_t *graph, igraph_vs_t vs, + igraph_vector_t *v) { + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vit_create(graph, vs, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_CHECK(igraph_vit_as_vector(&vit, v)); + + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_vs_copy + * \brief Creates a copy of a vertex selector. + * \param src The selector being copied. + * \param dest An uninitialized selector that will contain the copy. + */ +int igraph_vs_copy(igraph_vs_t* dest, const igraph_vs_t* src) { + memcpy(dest, src, sizeof(igraph_vs_t)); + switch (dest->type) { + case IGRAPH_VS_VECTOR: + dest->data.vecptr = igraph_Calloc(1, igraph_vector_t); + if (!dest->data.vecptr) { + IGRAPH_ERROR("Cannot copy vertex selector", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_copy((igraph_vector_t*)dest->data.vecptr, + (igraph_vector_t*)src->data.vecptr)); + break; + } + return 0; +} + +/** + * \function igraph_vs_type + * \brief Returns the type of the vertex selector. + */ +int igraph_vs_type(const igraph_vs_t *vs) { + return vs->type; +} + +/** + * \function igraph_vs_size + * \brief Returns the size of the vertex selector. + * + * The size of the vertex selector is the number of vertices it will + * yield when it is iterated over. + * + * \param graph The graph over which we will iterate. + * \param result The result will be returned here. + */ +int igraph_vs_size(const igraph_t *graph, const igraph_vs_t *vs, + igraph_integer_t *result) { + igraph_vector_t vec; + igraph_bool_t *seen; + long i; + + switch (vs->type) { + case IGRAPH_VS_NONE: + *result = 0; return 0; + + case IGRAPH_VS_1: + *result = 0; + if (vs->data.vid < igraph_vcount(graph) && vs->data.vid >= 0) { + *result = 1; + } + return 0; + + case IGRAPH_VS_SEQ: + *result = vs->data.seq.to - vs->data.seq.from; + return 0; + + case IGRAPH_VS_ALL: + *result = igraph_vcount(graph); return 0; + + case IGRAPH_VS_ADJ: + IGRAPH_VECTOR_INIT_FINALLY(&vec, 0); + IGRAPH_CHECK(igraph_neighbors(graph, &vec, vs->data.adj.vid, vs->data.adj.mode)); + *result = (igraph_integer_t) igraph_vector_size(&vec); + igraph_vector_destroy(&vec); + IGRAPH_FINALLY_CLEAN(1); + return 0; + + case IGRAPH_VS_NONADJ: + IGRAPH_VECTOR_INIT_FINALLY(&vec, 0); + IGRAPH_CHECK(igraph_neighbors(graph, &vec, vs->data.adj.vid, vs->data.adj.mode)); + *result = igraph_vcount(graph); + seen = igraph_Calloc(*result, igraph_bool_t); + if (seen == 0) { + IGRAPH_ERROR("Cannot calculate selector length", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, seen); + for (i = 0; i < igraph_vector_size(&vec); i++) { + if (!seen[(long int)VECTOR(vec)[i]]) { + (*result)--; + seen[(long int)VECTOR(vec)[i]] = 1; + } + } + igraph_free(seen); + igraph_vector_destroy(&vec); + IGRAPH_FINALLY_CLEAN(2); + return 0; + + case IGRAPH_VS_VECTOR: + case IGRAPH_VS_VECTORPTR: + *result = (igraph_integer_t) igraph_vector_size((igraph_vector_t*)vs->data.vecptr); + return 0; + } + + IGRAPH_ERROR("Cannot calculate selector length, invalid selector type", + IGRAPH_EINVAL); +} + +/***************************************************/ + +/** + * \function igraph_vit_create + * \brief Creates a vertex iterator from a vertex selector. + * + * This function instantiates a vertex selector object with a given + * graph. This is the step when the actual vertex ids are created from + * the \em logical notion of the vertex selector based on the graph. + * Eg. a vertex selector created with \ref igraph_vs_all() contains + * knowledge that \em all vertices are included in a (yet indefinite) + * graph. When instantiating it a vertex iterator object is created, + * this contains the actual vertex ids in the graph supplied as a + * parameter. + * + * + * The same vertex selector object can be used to instantiate any + * number vertex iterators. + * + * \param graph An \type igraph_t object, a graph. + * \param vs A vertex selector object. + * \param vit Pointer to an uninitialized vertex iterator object. + * \return Error code. + * \sa \ref igraph_vit_destroy(). + * + * Time complexity: it depends on the vertex selector type. O(1) for + * vertex selectors created with \ref igraph_vs_all(), \ref + * igraph_vs_none(), \ref igraph_vs_1, \ref igraph_vs_vector, \ref + * igraph_vs_seq(), \ref igraph_vs_vector(), \ref + * igraph_vs_vector_small(). O(d) for \ref igraph_vs_adj(), d is the + * number of vertex ids to be included in the iterator. O(|V|) for + * \ref igraph_vs_nonadj(), |V| is the number of vertices in the graph. + */ + +int igraph_vit_create(const igraph_t *graph, + igraph_vs_t vs, igraph_vit_t *vit) { + igraph_vector_t vec; + igraph_bool_t *seen; + long int i, j, n; + + switch (vs.type) { + case IGRAPH_VS_ALL: + vit->type = IGRAPH_VIT_SEQ; + vit->pos = 0; + vit->start = 0; + vit->end = igraph_vcount(graph); + break; + case IGRAPH_VS_ADJ: + vit->type = IGRAPH_VIT_VECTOR; + vit->pos = 0; + vit->start = 0; + vit->vec = igraph_Calloc(1, igraph_vector_t); + if (vit->vec == 0) { + IGRAPH_ERROR("Cannot create iterator", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*) vit->vec); + IGRAPH_VECTOR_INIT_FINALLY((igraph_vector_t*)vit->vec, 0); + IGRAPH_CHECK(igraph_neighbors(graph, (igraph_vector_t*)vit->vec, + vs.data.adj.vid, vs.data.adj.mode)); + vit->end = igraph_vector_size(vit->vec); + IGRAPH_FINALLY_CLEAN(2); + break; + case IGRAPH_VS_NONADJ: + vit->type = IGRAPH_VIT_VECTOR; + vit->pos = 0; + vit->start = 0; + vit->vec = igraph_Calloc(1, igraph_vector_t); + if (vit->vec == 0) { + IGRAPH_ERROR("Cannot create iterator", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*) vit->vec); + IGRAPH_VECTOR_INIT_FINALLY((igraph_vector_t *) vit->vec, 0); + IGRAPH_VECTOR_INIT_FINALLY(&vec, 0); + IGRAPH_CHECK(igraph_neighbors(graph, &vec, + vs.data.adj.vid, vs.data.adj.mode)); + n = igraph_vcount(graph); + seen = igraph_Calloc(n, igraph_bool_t); + if (seen == 0) { + IGRAPH_ERROR("Cannot create iterator", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, seen); + for (i = 0; i < igraph_vector_size(&vec); i++) { + if (! seen [ (long int) VECTOR(vec)[i] ] ) { + n--; + seen[ (long int) VECTOR(vec)[i] ] = 1; + } + } + IGRAPH_CHECK(igraph_vector_resize((igraph_vector_t*)vit->vec, n)); + for (i = 0, j = 0; j < n; i++) { + if (!seen[i]) { + VECTOR(*vit->vec)[j++] = i; + } + } + + igraph_Free(seen); + igraph_vector_destroy(&vec); + vit->end = n; + IGRAPH_FINALLY_CLEAN(4); + break; + case IGRAPH_VS_NONE: + vit->type = IGRAPH_VIT_SEQ; + vit->pos = 0; + vit->start = 0; + vit->end = 0; + break; + case IGRAPH_VS_1: + vit->type = IGRAPH_VIT_SEQ; + vit->pos = vs.data.vid; + vit->start = vs.data.vid; + vit->end = vs.data.vid + 1; + if (vit->pos >= igraph_vcount(graph)) { + IGRAPH_ERROR("Cannot create iterator, invalid vertex id", IGRAPH_EINVVID); + } + break; + case IGRAPH_VS_VECTORPTR: + case IGRAPH_VS_VECTOR: + vit->type = IGRAPH_VIT_VECTORPTR; + vit->pos = 0; + vit->start = 0; + vit->vec = vs.data.vecptr; + vit->end = igraph_vector_size(vit->vec); + if (!igraph_vector_isininterval(vit->vec, 0, igraph_vcount(graph) - 1)) { + IGRAPH_ERROR("Cannot create iterator, invalid vertex id", IGRAPH_EINVVID); + } + break; + case IGRAPH_VS_SEQ: + vit->type = IGRAPH_VIT_SEQ; + vit->pos = vs.data.seq.from; + vit->start = vs.data.seq.from; + vit->end = vs.data.seq.to; + break; + default: + IGRAPH_ERROR("Cannot create iterator, invalid selector", IGRAPH_EINVAL); + break; + } + return 0; +} + +/** + * \function igraph_vit_destroy + * \brief Destroys a vertex iterator. + * + * + * Deallocates memory allocated for a vertex iterator. + * + * \param vit Pointer to an initialized vertex iterator object. + * \sa \ref igraph_vit_create() + * + * Time complexity: operating system dependent, usually O(1). + */ + +void igraph_vit_destroy(const igraph_vit_t *vit) { + switch (vit->type) { + case IGRAPH_VIT_SEQ: + case IGRAPH_VIT_VECTORPTR: + break; + case IGRAPH_VIT_VECTOR: + igraph_vector_destroy((igraph_vector_t*)vit->vec); + igraph_free((igraph_vector_t*)vit->vec); + break; + default: + /* IGRAPH_ERROR("Cannot destroy iterator, unknown type", IGRAPH_EINVAL); */ + break; + } +} + +int igraph_vit_as_vector(const igraph_vit_t *vit, igraph_vector_t *v) { + + long int i; + + IGRAPH_CHECK(igraph_vector_resize(v, IGRAPH_VIT_SIZE(*vit))); + + switch (vit->type) { + case IGRAPH_VIT_SEQ: + for (i = 0; i < IGRAPH_VIT_SIZE(*vit); i++) { + VECTOR(*v)[i] = vit->start + i; + } + break; + case IGRAPH_VIT_VECTOR: + case IGRAPH_VIT_VECTORPTR: + for (i = 0; i < IGRAPH_VIT_SIZE(*vit); i++) { + VECTOR(*v)[i] = VECTOR(*vit->vec)[i]; + } + break; + default: + IGRAPH_ERROR("Cannot convert to vector, unknown iterator type", + IGRAPH_EINVAL); + break; + } + + return 0; +} + +/*******************************************************/ + +/** + * \function igraph_es_all + * \brief Edge set, all edges. + * + * \param es Pointer to an uninitialized edge selector object. + * \param order Constant giving the order in which the edges will be + * included in the selector. Possible values: + * \c IGRAPH_EDGEORDER_ID, edge id order. + * \c IGRAPH_EDGEORDER_FROM, vertex id order, the id of the + * \em source vertex counts for directed graphs. The order + * of the incident edges of a given vertex is arbitrary. + * \c IGRAPH_EDGEORDER_TO, vertex id order, the id of the \em + * target vertex counts for directed graphs. The order + * of the incident edges of a given vertex is arbitrary. + * For undirected graph the latter two is the same. + * \return Error code. + * \sa \ref igraph_ess_all(), \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +int igraph_es_all(igraph_es_t *es, + igraph_edgeorder_type_t order) { + switch (order) { + case IGRAPH_EDGEORDER_ID: + es->type = IGRAPH_ES_ALL; + break; + case IGRAPH_EDGEORDER_FROM: + es->type = IGRAPH_ES_ALLFROM; + break; + case IGRAPH_EDGEORDER_TO: + es->type = IGRAPH_ES_ALLTO; + break; + default: + IGRAPH_ERROR("Invalid edge order, cannot create selector", IGRAPH_EINVAL); + break; + } + return 0; +} + +/** + * \function igraph_ess_all + * \brief Edge set, all edges (immediate version) + * + * The immediate version of the all-vertices selector. + * + * \param order Constant giving the order of the edges in the edge + * selector. See \ref igraph_es_all() for the possible values. + * \return The edge selector. + * \sa \ref igraph_es_all() + * + * Time complexity: O(1). + */ + +igraph_es_t igraph_ess_all(igraph_edgeorder_type_t order) { + igraph_es_t es; + igraph_es_all(&es, order); /* cannot fail */ + return es; +} + +/** + * \function igraph_es_adj + * \brief Adjacent edges of a vertex. + * + * This function was superseded by \ref igraph_es_incident() in igraph 0.6. + * Please use \ref igraph_es_incident() instead of this function. + * + * + * Deprecated in version 0.6. + */ +int igraph_es_adj(igraph_es_t *es, + igraph_integer_t vid, igraph_neimode_t mode) { + IGRAPH_WARNING("igraph_es_adj is deprecated, use igraph_es_incident"); + return igraph_es_incident(es, vid, mode); +} + +/** + * \function igraph_es_incident + * \brief Edges incident on a given vertex. + * + * \param es Pointer to an uninitialized edge selector object. + * \param vid Vertex id, of which the incident edges will be + * selected. + * \param mode Constant giving the type of the incident edges to + * select. This is ignored for undirected graphs. Possible values: + * \c IGRAPH_OUT, outgoing edges; + * \c IGRAPH_IN, incoming edges; + * \c IGRAPH_ALL, all edges. + * \return Error code. + * \sa \ref igraph_es_destroy() + * + * Time complexity: O(1). + * + * \example examples/simple/igraph_es_adj.c + */ + +int igraph_es_incident(igraph_es_t *es, + igraph_integer_t vid, igraph_neimode_t mode) { + es->type = IGRAPH_ES_INCIDENT; + es->data.incident.vid = vid; + es->data.incident.mode = mode; + return 0; +} + +/** + * \function igraph_es_none + * \brief Empty edge selector. + * + * \param es Pointer to an uninitialized edge selector object to + * initialize. + * \return Error code. + * \sa \ref igraph_ess_none(), \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +int igraph_es_none(igraph_es_t *es) { + es->type = IGRAPH_ES_NONE; + return 0; +} + +/** + * \function igraph_ess_none + * \brief Immediate empty edge selector. + * + * + * Immediate version of the empty edge selector. + * + * \return Initialized empty edge selector. + * \sa \ref igraph_es_none() + * + * Time complexity: O(1). + */ + +igraph_es_t igraph_ess_none(void) { + igraph_es_t es; + es.type = IGRAPH_ES_NONE; + return es; +} + +/** + * \function igraph_es_1 + * \brief Edge selector containing a single edge. + * + * \param es Pointer to an uninitialized edge selector object. + * \param eid Edge id of the edge to select. + * \return Error code. + * \sa \ref igraph_ess_1(), \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +int igraph_es_1(igraph_es_t *es, igraph_integer_t eid) { + es->type = IGRAPH_ES_1; + es->data.eid = eid; + return 0; +} + +/** + * \function igraph_ess_1 + * \brief Immediate version of the single edge edge selector. + * + * \param eid The id of the edge. + * \return The edge selector. + * \sa \ref igraph_es_1() + * + * Time complexity: O(1). + */ + +igraph_es_t igraph_ess_1(igraph_integer_t eid) { + igraph_es_t es; + es.type = IGRAPH_ES_1; + es.data.eid = eid; + return es; +} + +/** + * \function igraph_es_vector + * \brief Handle a vector as an edge selector. + * + * + * Creates an edge selector which serves as a view to a vector + * containing edge ids. Do not destroy the vector before destroying + * the view. + * + * Many views can be created to the same vector. + * + * \param es Pointer to an uninitialized edge selector. + * \param v Vector containing edge ids. + * \return Error code. + * \sa \ref igraph_ess_vector(), \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +int igraph_es_vector(igraph_es_t *es, + const igraph_vector_t *v) { + es->type = IGRAPH_ES_VECTORPTR; + es->data.vecptr = v; + return 0; +} + +/** + * \function igraph_es_vector_copy + * \brief Edge set, based on a vector, with copying. + * + * + * This function makes it possible to handle a \type vector_t + * permanently as an edge selector. The edge selector creates a + * copy of the original vector, so the vector can safely be destroyed + * after creating the edge selector. Changing the original vector + * will not affect the edge selector. The edge selector is + * responsible for deleting the copy made by itself. + * + * \param es Pointer to an uninitialized edge selector. + * \param v Pointer to a \type igraph_vector_t object. + * \return Error code. + * \sa \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +int igraph_es_vector_copy(igraph_es_t *es, const igraph_vector_t *v) { + es->type = IGRAPH_ES_VECTOR; + es->data.vecptr = igraph_Calloc(1, igraph_vector_t); + if (es->data.vecptr == 0) { + IGRAPH_ERROR("Cannot create edge selector", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*)es->data.vecptr); + IGRAPH_CHECK(igraph_vector_copy((igraph_vector_t*)es->data.vecptr, v)); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_ess_vector + * \brief Immediate vector view edge selector. + * + * + * This is the immediate version of the vector of edge ids edge + * selector. + * + * \param v The vector of edge ids. + * \return Edge selector, initialized. + * \sa \ref igraph_es_vector() + * + * Time complexity: O(1). + */ + +igraph_es_t igraph_ess_vector(const igraph_vector_t *v) { + igraph_es_t es; + es.type = IGRAPH_ES_VECTORPTR; + es.data.vecptr = v; + return es; +} + +/** + * \function igraph_es_fromto + * \brief Edge selector, all edges between two vertex sets. + * + * + * This function is not implemented yet. + * + * \param es Pointer to an uninitialized edge selector. + * \param from Vertex selector, their outgoing edges will be + * selected. + * \param to Vertex selector, their incoming edges will be selected + * from the previous selection. + * \return Error code. + * \sa \ref igraph_es_destroy() + * + * Time complexity: O(1). + * + * \example examples/simple/igraph_es_fromto.c + */ + +int igraph_es_fromto(igraph_es_t *es, + igraph_vs_t from, igraph_vs_t to) { + + IGRAPH_UNUSED(es); IGRAPH_UNUSED(from); IGRAPH_UNUSED(to); + IGRAPH_ERROR("igraph_es_fromto not implemented yet", IGRAPH_UNIMPLEMENTED); + /* TODO */ + return 0; +} + +/** + * \function igraph_es_seq + * \brief Edge selector, a sequence of edge ids. + * + * All edge ids between from and to will be + * included in the edge selection. + * + * \param es Pointer to an uninitialized edge selector object. + * \param from The first edge id to be included. + * \param to The last edge id to be included. + * \return Error code. + * \sa \ref igraph_ess_seq(), \ref igraph_es_destroy() + * + * Time complexity: O(1). + */ + +int igraph_es_seq(igraph_es_t *es, + igraph_integer_t from, igraph_integer_t to) { + es->type = IGRAPH_ES_SEQ; + es->data.seq.from = from; + es->data.seq.to = to; + return 0; +} + +/** + * \function igraph_ess_seq + * \brief Immediate version of the sequence edge selector. + * + * \param from The first edge id to include. + * \param to The last edge id to include. + * \return The initialized edge selector. + * \sa \ref igraph_es_seq() + * + * Time complexity: O(1). + */ + +igraph_es_t igraph_ess_seq(igraph_integer_t from, igraph_integer_t to) { + igraph_es_t es; + es.type = IGRAPH_ES_SEQ; + es.data.seq.from = from; + es.data.seq.to = to; + return es; +} + +/** + * \function igraph_es_pairs + * \brief Edge selector, multiple edges defined by their endpoints in a vector. + * + * The edges between the given pairs of vertices will be included in the + * edge selection. The vertex pairs must be defined in the vector v, + * the first element of the vector is the first vertex of the first edge + * to be selected, the second element is the second vertex of the first + * edge, the third element is the first vertex of the second edge and + * so on. + * + * \param es Pointer to an uninitialized edge selector object. + * \param v The vector containing the endpoints of the edges. + * \param directed Whether the graph is directed or not. + * \return Error code. + * \sa \ref igraph_es_pairs_small(), \ref igraph_es_destroy() + * + * Time complexity: O(n), the number of edges being selected. + * + * \example examples/simple/igraph_es_pairs.c + */ + +int igraph_es_pairs(igraph_es_t *es, const igraph_vector_t *v, + igraph_bool_t directed) { + es->type = IGRAPH_ES_PAIRS; + es->data.path.mode = directed; + es->data.path.ptr = igraph_Calloc(1, igraph_vector_t); + if (es->data.path.ptr == 0) { + IGRAPH_ERROR("Cannot create edge selector", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*) es->data.path.ptr); + + IGRAPH_CHECK(igraph_vector_copy((igraph_vector_t*) es->data.path.ptr, v)); + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_es_pairs_small + * \brief Edge selector, multiple edges defined by their endpoints as arguments. + * + * The edges between the given pairs of vertices will be included in the + * edge selection. The vertex pairs must be given as the arguments of the + * function call, the third argument is the first vertex of the first edge, + * the fourth argument is the second vertex of the first edge, the fifth + * is the first vertex of the second edge and so on. The last element of the + * argument list must be -1 to denote the end of the argument list. + * + * \param es Pointer to an uninitialized edge selector object. + * \param directed Whether the graph is directed or not. + * \return Error code. + * \sa \ref igraph_es_pairs(), \ref igraph_es_destroy() + * + * Time complexity: O(n), the number of edges being selected. + */ + +int igraph_es_pairs_small(igraph_es_t *es, igraph_bool_t directed, ...) { + va_list ap; + long int i, n = 0; + es->type = IGRAPH_ES_PAIRS; + es->data.path.mode = directed; + es->data.path.ptr = igraph_Calloc(1, igraph_vector_t); + if (es->data.path.ptr == 0) { + IGRAPH_ERROR("Cannot create edge selector", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*)es->data.path.ptr); + + va_start(ap, directed); + while (1) { + int num = va_arg(ap, int); + if (num == -1) { + break; + } + n++; + } + va_end(ap); + + IGRAPH_VECTOR_INIT_FINALLY( (igraph_vector_t*) es->data.path.ptr, n); + + va_start(ap, directed); + for (i = 0; i < n; i++) { + VECTOR(*es->data.path.ptr)[i] = (igraph_real_t) va_arg(ap, int); + } + va_end(ap); + + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +int igraph_es_multipairs(igraph_es_t *es, const igraph_vector_t *v, + igraph_bool_t directed) { + es->type = IGRAPH_ES_MULTIPAIRS; + es->data.path.mode = directed; + es->data.path.ptr = igraph_Calloc(1, igraph_vector_t); + if (es->data.path.ptr == 0) { + IGRAPH_ERROR("Cannot create edge selector", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*) es->data.path.ptr); + + IGRAPH_CHECK(igraph_vector_copy((igraph_vector_t*) es->data.path.ptr, v)); + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \example examples/simple/igraph_es_path.c + */ + +int igraph_es_path(igraph_es_t *es, const igraph_vector_t *v, + igraph_bool_t directed) { + es->type = IGRAPH_ES_PATH; + es->data.path.mode = directed; + es->data.path.ptr = igraph_Calloc(1, igraph_vector_t); + if (es->data.path.ptr == 0) { + IGRAPH_ERROR("Cannot create edge selector", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*) es->data.path.ptr); + + IGRAPH_CHECK(igraph_vector_copy((igraph_vector_t*) es->data.path.ptr, v)); + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +int igraph_es_path_small(igraph_es_t *es, igraph_bool_t directed, ...) { + va_list ap; + long int i, n = 0; + es->type = IGRAPH_ES_PATH; + es->data.path.mode = directed; + es->data.path.ptr = igraph_Calloc(1, igraph_vector_t); + if (es->data.path.ptr == 0) { + IGRAPH_ERROR("Cannot create edge selector", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*)es->data.path.ptr); + + va_start(ap, directed); + while (1) { + int num = va_arg(ap, int); + if (num == -1) { + break; + } + n++; + } + va_end(ap); + + IGRAPH_VECTOR_INIT_FINALLY( (igraph_vector_t*) es->data.path.ptr, n); + + va_start(ap, directed); + for (i = 0; i < n; i++) { + VECTOR(*es->data.path.ptr)[i] = (igraph_real_t) va_arg(ap, int); + } + va_end(ap); + + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/** + * \function igraph_es_destroy + * \brief Destroys an edge selector object. + * + * + * Call this function on an edge selector when it is not needed any + * more. Do \em not call this function on edge selectors created by + * immediate constructors, those don't need to be destroyed. + * + * \param es Pointer to an edge selector object. + * + * Time complexity: operating system dependent, usually O(1). + */ + +void igraph_es_destroy(igraph_es_t *es) { + switch (es->type) { + case IGRAPH_ES_ALL: + case IGRAPH_ES_ALLFROM: + case IGRAPH_ES_ALLTO: + case IGRAPH_ES_INCIDENT: + case IGRAPH_ES_NONE: + case IGRAPH_ES_1: + case IGRAPH_ES_VECTORPTR: + case IGRAPH_ES_SEQ: + break; + case IGRAPH_ES_VECTOR: + igraph_vector_destroy((igraph_vector_t*)es->data.vecptr); + igraph_Free(es->data.vecptr); + break; + case IGRAPH_ES_PAIRS: + case IGRAPH_ES_PATH: + case IGRAPH_ES_MULTIPAIRS: + igraph_vector_destroy((igraph_vector_t*)es->data.path.ptr); + igraph_Free(es->data.path.ptr); + break; + default: + break; + } +} + +/** + * \function igraph_es_is_all + * \brief Check whether an edge selector includes all edges. + * + * \param es Pointer to an edge selector object. + * \return TRUE (1) if es was created with \ref + * igraph_es_all() or \ref igraph_ess_all(), and FALSE (0) otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t igraph_es_is_all(const igraph_es_t *es) { + return es->type == IGRAPH_ES_ALL; +} + +/** + * \function igraph_es_copy + * \brief Creates a copy of an edge selector. + * \param src The selector being copied. + * \param dest An uninitialized selector that will contain the copy. + * \sa \ref igraph_es_destroy() + */ +int igraph_es_copy(igraph_es_t* dest, const igraph_es_t* src) { + memcpy(dest, src, sizeof(igraph_es_t)); + switch (dest->type) { + case IGRAPH_ES_VECTOR: + dest->data.vecptr = igraph_Calloc(1, igraph_vector_t); + if (!dest->data.vecptr) { + IGRAPH_ERROR("Cannot copy edge selector", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_copy((igraph_vector_t*)dest->data.vecptr, + (igraph_vector_t*)src->data.vecptr)); + break; + case IGRAPH_ES_PATH: + case IGRAPH_ES_PAIRS: + case IGRAPH_ES_MULTIPAIRS: + dest->data.path.ptr = igraph_Calloc(1, igraph_vector_t); + if (!dest->data.path.ptr) { + IGRAPH_ERROR("Cannot copy edge selector", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_copy((igraph_vector_t*)dest->data.path.ptr, + (igraph_vector_t*)src->data.path.ptr)); + break; + } + return 0; +} + +int igraph_es_as_vector(const igraph_t *graph, igraph_es_t es, + igraph_vector_t *v) { + igraph_eit_t eit; + + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + IGRAPH_CHECK(igraph_eit_as_vector(&eit, v)); + + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_es_type + * \brief Returns the type of the edge selector. + */ +int igraph_es_type(const igraph_es_t *es) { + return es->type; +} + +static int igraph_i_es_pairs_size(const igraph_t *graph, + const igraph_es_t *es, igraph_integer_t *result); +static int igraph_i_es_path_size(const igraph_t *graph, + const igraph_es_t *es, igraph_integer_t *result); +static int igraph_i_es_multipairs_size(const igraph_t *graph, + const igraph_es_t *es, igraph_integer_t *result); + +/** + * \function igraph_es_size + * \brief Returns the size of the edge selector. + * + * The size of the edge selector is the number of edges it will + * yield when it is iterated over. + * + * \param graph The graph over which we will iterate. + * \param result The result will be returned here. + */ +int igraph_es_size(const igraph_t *graph, const igraph_es_t *es, + igraph_integer_t *result) { + igraph_vector_t v; + + switch (es->type) { + case IGRAPH_ES_ALL: + *result = igraph_ecount(graph); + return 0; + + case IGRAPH_ES_ALLFROM: + *result = igraph_ecount(graph); + return 0; + + case IGRAPH_ES_ALLTO: + *result = igraph_ecount(graph); + return 0; + + case IGRAPH_ES_INCIDENT: + IGRAPH_VECTOR_INIT_FINALLY(&v, 0); + IGRAPH_CHECK(igraph_incident(graph, &v, + es->data.incident.vid, es->data.incident.mode)); + *result = (igraph_integer_t) igraph_vector_size(&v); + igraph_vector_destroy(&v); + IGRAPH_FINALLY_CLEAN(1); + return 0; + + case IGRAPH_ES_NONE: + *result = 0; + return 0; + + case IGRAPH_ES_1: + if (es->data.eid < igraph_ecount(graph) && es->data.eid >= 0) { + *result = 1; + } else { + *result = 0; + } + return 0; + + case IGRAPH_ES_VECTOR: + case IGRAPH_ES_VECTORPTR: + *result = (igraph_integer_t) igraph_vector_size((igraph_vector_t*)es->data.vecptr); + return 0; + + case IGRAPH_ES_SEQ: + *result = es->data.seq.to - es->data.seq.from; + return 0; + + case IGRAPH_ES_PAIRS: + IGRAPH_CHECK(igraph_i_es_pairs_size(graph, es, result)); + return 0; + + case IGRAPH_ES_PATH: + IGRAPH_CHECK(igraph_i_es_path_size(graph, es, result)); + return 0; + + case IGRAPH_ES_MULTIPAIRS: + IGRAPH_CHECK(igraph_i_es_multipairs_size(graph, es, result)); + return 0; + + default: + IGRAPH_ERROR("Cannot calculate selector length, invalid selector type", + IGRAPH_EINVAL); + } + + return 0; +} + +static int igraph_i_es_pairs_size(const igraph_t *graph, + const igraph_es_t *es, igraph_integer_t *result) { + long int n = igraph_vector_size(es->data.path.ptr); + long int no_of_nodes = igraph_vcount(graph); + long int i; + + if (n % 2 != 0) { + IGRAPH_ERROR("Cannot calculate edge selector length from odd number of vertices", + IGRAPH_EINVAL); + } + if (!igraph_vector_isininterval(es->data.path.ptr, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot calculate edge selector length", IGRAPH_EINVVID); + } + + *result = (igraph_integer_t) (n / 2); + /* Check for the existence of all edges */ + for (i = 0; i < *result; i++) { + long int from = (long int) VECTOR(*es->data.path.ptr)[2 * i]; + long int to = (long int) VECTOR(*es->data.path.ptr)[2 * i + 1]; + igraph_integer_t eid; + IGRAPH_CHECK(igraph_get_eid(graph, &eid, (igraph_integer_t) from, + (igraph_integer_t) to, es->data.path.mode, + /*error=*/ 1)); + } + + return 0; +} + +static int igraph_i_es_path_size(const igraph_t *graph, + const igraph_es_t *es, igraph_integer_t *result) { + long int n = igraph_vector_size(es->data.path.ptr); + long int no_of_nodes = igraph_vcount(graph); + long int i; + + if (!igraph_vector_isininterval(es->data.path.ptr, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot calculate selector length", IGRAPH_EINVVID); + } + + if (n <= 1) { + *result = 0; + } else { + *result = (igraph_integer_t) (n - 1); + } + for (i = 0; i < *result; i++) { + long int from = (long int) VECTOR(*es->data.path.ptr)[i]; + long int to = (long int) VECTOR(*es->data.path.ptr)[i + 1]; + igraph_integer_t eid; + IGRAPH_CHECK(igraph_get_eid(graph, &eid, (igraph_integer_t) from, + (igraph_integer_t) to, es->data.path.mode, + /*error=*/ 1)); + } + + return 0; +} + +static int igraph_i_es_multipairs_size(const igraph_t *graph, + const igraph_es_t *es, igraph_integer_t *result) { + IGRAPH_UNUSED(graph); IGRAPH_UNUSED(es); IGRAPH_UNUSED(result); + IGRAPH_ERROR("Cannot calculate edge selector length", IGRAPH_UNIMPLEMENTED); +} + +/**************************************************/ + +static int igraph_i_eit_create_allfromto(const igraph_t *graph, + igraph_eit_t *eit, + igraph_neimode_t mode); +static int igraph_i_eit_pairs(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit); +static int igraph_i_eit_multipairs(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit); +static int igraph_i_eit_path(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit); + +static int igraph_i_eit_create_allfromto(const igraph_t *graph, + igraph_eit_t *eit, + igraph_neimode_t mode) { + igraph_vector_t *vec; + long int no_of_nodes = igraph_vcount(graph); + long int i; + + vec = igraph_Calloc(1, igraph_vector_t); + if (vec == 0) { + IGRAPH_ERROR("Cannot create edge iterator", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_VECTOR_INIT_FINALLY(vec, 0); + IGRAPH_CHECK(igraph_vector_reserve(vec, igraph_ecount(graph))); + + if (igraph_is_directed(graph)) { + igraph_vector_t adj; + IGRAPH_VECTOR_INIT_FINALLY(&adj, 0); + for (i = 0; i < no_of_nodes; i++) { + igraph_incident(graph, &adj, (igraph_integer_t) i, mode); + igraph_vector_append(vec, &adj); + } + igraph_vector_destroy(&adj); + IGRAPH_FINALLY_CLEAN(1); + + } else { + + igraph_vector_t adj; + igraph_bool_t *added; + long int j; + IGRAPH_VECTOR_INIT_FINALLY(&adj, 0); + added = igraph_Calloc(igraph_ecount(graph), igraph_bool_t); + if (added == 0) { + IGRAPH_ERROR("Cannot create edge iterator", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + for (i = 0; i < no_of_nodes; i++) { + igraph_incident(graph, &adj, (igraph_integer_t) i, IGRAPH_ALL); + for (j = 0; j < igraph_vector_size(&adj); j++) { + if (!added[ (long int)VECTOR(adj)[j] ]) { + igraph_vector_push_back(vec, VECTOR(adj)[j]); + added[ (long int)VECTOR(adj)[j] ] += 1; + } + } + } + igraph_vector_destroy(&adj); + igraph_Free(added); + IGRAPH_FINALLY_CLEAN(2); + } + + eit->type = IGRAPH_EIT_VECTOR; + eit->pos = 0; + eit->start = 0; + eit->vec = vec; + eit->end = igraph_vector_size(eit->vec); + + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +static int igraph_i_eit_pairs(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit) { + long int n = igraph_vector_size(es.data.path.ptr); + long int no_of_nodes = igraph_vcount(graph); + long int i; + + if (n % 2 != 0) { + IGRAPH_ERROR("Cannot create edge iterator from odd number of vertices", + IGRAPH_EINVAL); + } + if (!igraph_vector_isininterval(es.data.path.ptr, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot create edge iterator", IGRAPH_EINVVID); + } + + eit->type = IGRAPH_EIT_VECTOR; + eit->pos = 0; + eit->start = 0; + eit->end = n / 2; + eit->vec = igraph_Calloc(1, igraph_vector_t); + if (eit->vec == 0) { + IGRAPH_ERROR("Cannot create edge iterator", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*)eit->vec); + IGRAPH_VECTOR_INIT_FINALLY((igraph_vector_t*)eit->vec, n / 2); + + for (i = 0; i < igraph_vector_size(eit->vec); i++) { + long int from = (long int) VECTOR(*es.data.path.ptr)[2 * i]; + long int to = (long int) VECTOR(*es.data.path.ptr)[2 * i + 1]; + igraph_integer_t eid; + IGRAPH_CHECK(igraph_get_eid(graph, &eid, (igraph_integer_t) from, + (igraph_integer_t) to, es.data.path.mode, + /*error=*/ 1)); + VECTOR(*eit->vec)[i] = eid; + } + + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +static int igraph_i_eit_multipairs(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit) { + long int n = igraph_vector_size(es.data.path.ptr); + long int no_of_nodes = igraph_vcount(graph); + + if (n % 2 != 0) { + IGRAPH_ERROR("Cannot create edge iterator from odd number of vertices", + IGRAPH_EINVAL); + } + if (!igraph_vector_isininterval(es.data.path.ptr, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot create edge iterator", IGRAPH_EINVVID); + } + + eit->type = IGRAPH_EIT_VECTOR; + eit->pos = 0; + eit->start = 0; + eit->end = n / 2; + eit->vec = igraph_Calloc(1, igraph_vector_t); + if (eit->vec == 0) { + IGRAPH_ERROR("Cannot create edge iterator", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*)eit->vec); + IGRAPH_VECTOR_INIT_FINALLY((igraph_vector_t*)eit->vec, n / 2); + + IGRAPH_CHECK(igraph_get_eids_multi(graph, (igraph_vector_t *) eit->vec, + /*pairs=*/ es.data.path.ptr, /*path=*/ 0, + es.data.path.mode, /*error=*/ 1)); + + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +static int igraph_i_eit_path(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit) { + long int n = igraph_vector_size(es.data.path.ptr); + long int no_of_nodes = igraph_vcount(graph); + long int i, len; + + if (!igraph_vector_isininterval(es.data.path.ptr, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot create edge iterator", IGRAPH_EINVVID); + } + + if (n <= 1) { + len = 0; + } else { + len = n - 1; + } + + eit->type = IGRAPH_EIT_VECTOR; + eit->pos = 0; + eit->start = 0; + eit->end = len; + eit->vec = igraph_Calloc(1, igraph_vector_t); + if (eit->vec == 0) { + IGRAPH_ERROR("Cannot create edge iterator", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*)eit->vec); + + IGRAPH_VECTOR_INIT_FINALLY((igraph_vector_t *)eit->vec, len); + + for (i = 0; i < len; i++) { + long int from = (long int) VECTOR(*es.data.path.ptr)[i]; + long int to = (long int) VECTOR(*es.data.path.ptr)[i + 1]; + igraph_integer_t eid; + IGRAPH_CHECK(igraph_get_eid(graph, &eid, (igraph_integer_t) from, + (igraph_integer_t) to, es.data.path.mode, + /*error=*/ 1)); + VECTOR(*eit->vec)[i] = eid; + } + + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/** + * \function igraph_eit_create + * \brief Creates an edge iterator from an edge selector. + * + * + * This function creates an edge iterator based on an edge selector + * and a graph. + * + * + * The same edge selector can be used to create many edge iterators, + * also for different graphs. + * + * \param graph An \type igraph_t object for which the edge selector + * will be instantiated. + * \param es The edge selector to instantiate. + * \param eit Pointer to an uninitialized edge iterator. + * \return Error code. + * \sa \ref igraph_eit_destroy() + * + * Time complexity: depends on the type of the edge selector. For edge + * selectors created by \ref igraph_es_all(), \ref igraph_es_none(), + * \ref igraph_es_1(), igraph_es_vector(), igraph_es_seq() it is + * O(1). For \ref igraph_es_incident() it is O(d) where d is the number of + * incident edges of the vertex. + */ + +int igraph_eit_create(const igraph_t *graph, + igraph_es_t es, igraph_eit_t *eit) { + switch (es.type) { + case IGRAPH_ES_ALL: + eit->type = IGRAPH_EIT_SEQ; + eit->pos = 0; + eit->start = 0; + eit->end = igraph_ecount(graph); + break; + case IGRAPH_ES_ALLFROM: + IGRAPH_CHECK(igraph_i_eit_create_allfromto(graph, eit, IGRAPH_OUT)); + break; + case IGRAPH_ES_ALLTO: + IGRAPH_CHECK(igraph_i_eit_create_allfromto(graph, eit, IGRAPH_IN)); + break; + case IGRAPH_ES_INCIDENT: + eit->type = IGRAPH_EIT_VECTOR; + eit->pos = 0; + eit->start = 0; + eit->vec = igraph_Calloc(1, igraph_vector_t); + if (eit->vec == 0) { + IGRAPH_ERROR("Cannot create iterator", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, (igraph_vector_t*) eit->vec); + IGRAPH_VECTOR_INIT_FINALLY((igraph_vector_t*)eit->vec, 0); + IGRAPH_CHECK(igraph_incident(graph, (igraph_vector_t*)eit->vec, + es.data.incident.vid, es.data.incident.mode)); + eit->end = igraph_vector_size(eit->vec); + IGRAPH_FINALLY_CLEAN(2); + break; + case IGRAPH_ES_NONE: + eit->type = IGRAPH_EIT_SEQ; + eit->pos = 0; + eit->start = 0; + eit->end = 0; + break; + case IGRAPH_ES_1: + eit->type = IGRAPH_EIT_SEQ; + eit->pos = es.data.eid; + eit->start = es.data.eid; + eit->end = es.data.eid + 1; + if (eit->pos >= igraph_ecount(graph)) { + IGRAPH_ERROR("Cannot create iterator, invalid edge id", IGRAPH_EINVVID); + } + break; + case IGRAPH_ES_VECTOR: + case IGRAPH_ES_VECTORPTR: + eit->type = IGRAPH_EIT_VECTORPTR; + eit->pos = 0; + eit->start = 0; + eit->vec = es.data.vecptr; + eit->end = igraph_vector_size(eit->vec); + if (!igraph_vector_isininterval(eit->vec, 0, igraph_ecount(graph) - 1)) { + IGRAPH_ERROR("Cannot create iterator, invalid edge id", IGRAPH_EINVVID); + } + break; + case IGRAPH_ES_SEQ: + eit->type = IGRAPH_EIT_SEQ; + eit->pos = es.data.seq.from; + eit->start = es.data.seq.from; + eit->end = es.data.seq.to; + break; + case IGRAPH_ES_PAIRS: + IGRAPH_CHECK(igraph_i_eit_pairs(graph, es, eit)); + break; + case IGRAPH_ES_MULTIPAIRS: + IGRAPH_CHECK(igraph_i_eit_multipairs(graph, es, eit)); + break; + case IGRAPH_ES_PATH: + IGRAPH_CHECK(igraph_i_eit_path(graph, es, eit)); + break; + default: + IGRAPH_ERROR("Cannot create iterator, invalid selector", IGRAPH_EINVAL); + break; + } + return 0; +} + +/** + * \function igraph_eit_destroy + * \brief Destroys an edge iterator. + * + * \param eit Pointer to an edge iterator to destroy. + * \sa \ref igraph_eit_create() + * + * Time complexity: operating system dependent, usually O(1). + */ + +void igraph_eit_destroy(const igraph_eit_t *eit) { + switch (eit->type) { + case IGRAPH_EIT_SEQ: + case IGRAPH_EIT_VECTORPTR: + break; + case IGRAPH_EIT_VECTOR: + igraph_vector_destroy((igraph_vector_t*)eit->vec); + igraph_free((igraph_vector_t*)eit->vec); + break; + default: + /* IGRAPH_ERROR("Cannot destroy iterator, unknown type", IGRAPH_EINVAL); */ + break; + } +} + +int igraph_eit_as_vector(const igraph_eit_t *eit, igraph_vector_t *v) { + + long int i; + + IGRAPH_CHECK(igraph_vector_resize(v, IGRAPH_EIT_SIZE(*eit))); + + switch (eit->type) { + case IGRAPH_EIT_SEQ: + for (i = 0; i < IGRAPH_EIT_SIZE(*eit); i++) { + VECTOR(*v)[i] = eit->start + i; + } + break; + case IGRAPH_EIT_VECTOR: + case IGRAPH_EIT_VECTORPTR: + for (i = 0; i < IGRAPH_EIT_SIZE(*eit); i++) { + VECTOR(*v)[i] = VECTOR(*eit->vec)[i]; + } + break; + default: + IGRAPH_ERROR("Cannot convert to vector, unknown iterator type", + IGRAPH_EINVAL); + break; + } + + return 0; +} diff --git a/src/lad.c b/src/lad.c new file mode 100644 index 0000000..80bcb09 --- /dev/null +++ b/src/lad.c @@ -0,0 +1,1664 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + The contents of this file was originally taken from the LAD + homepage: http://liris.cnrs.fr/csolnon/LAD.html and then + modified to fit better into igraph. + + Unfortunately LAD seems to have no version numbers. The files + were apparently last changed on the 29th of June, 2010. + + The original copyright message follows here. The CeCILL-B V1 license + is GPL compatible, because instead of V1, one can freely choose to + use V2, and V2 is explicitly GPL compatible. +*/ + +/* This software has been written by Christine Solnon. + It is distributed under the CeCILL-B FREE SOFTWARE LICENSE + see http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html + for more details +*/ + +/* Several modifications had to be made to the original LAD implementation + to make it compile with non-C99-compliant compilers such as MSVC. In + particular, I had to remove all the variable-sized arrays. + -- Tamas Nepusz, 11 July 2013 +*/ + +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include "igraph_vector.h" +#include "igraph_vector_ptr.h" +#include "igraph_memory.h" +#include "igraph_matrix.h" +#include "igraph_interrupt_internal.h" + +#include +#include +#include +#include + + +/* define boolean type as char */ +#define true 1 +#define false 0 +#define bool char + +/* helper to allocate an array of given size and free it using IGRAPH_FINALLY + * when needed */ +#define ALLOC_ARRAY(VAR, SIZE, TYPE) { \ + VAR = igraph_Calloc(SIZE, TYPE); \ + if (VAR == 0) { \ + IGRAPH_ERROR("cannot allocate '" #VAR "' array in LAD isomorphism search", IGRAPH_ENOMEM); \ + } \ + IGRAPH_FINALLY(igraph_free, VAR); \ + } + +/* helper to allocate an array of given size and store its address in a + * pointer array */ +#define ALLOC_ARRAY_IN_HISTORY(VAR, SIZE, TYPE, HISTORY) { \ + VAR = igraph_Calloc(SIZE, TYPE); \ + if (VAR == 0) { \ + IGRAPH_ERROR("cannot allocate '" #VAR "' array in LAD isomorphism search", IGRAPH_ENOMEM); \ + } \ + IGRAPH_FINALLY(igraph_free, VAR); \ + IGRAPH_CHECK(igraph_vector_ptr_push_back(HISTORY, VAR)); \ + IGRAPH_FINALLY_CLEAN(1); \ + } + +/* ---------------------------------------------------------*/ +/* Coming from graph.c */ +/* ---------------------------------------------------------*/ + +typedef struct { + long int nbVertices; /* Number of vertices */ + igraph_vector_t nbSucc; + igraph_adjlist_t succ; + igraph_matrix_char_t isEdge; +} Tgraph; + +int igraph_i_lad_createGraph(const igraph_t *igraph, Tgraph* graph) { + long int i, j, n; + long int no_of_nodes = igraph_vcount(igraph); + igraph_vector_int_t *neis; + + IGRAPH_VECTOR_INIT_FINALLY(&graph->nbSucc, no_of_nodes); + IGRAPH_CHECK(igraph_degree(igraph, &graph->nbSucc, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + + graph->nbVertices = no_of_nodes; + + IGRAPH_CHECK(igraph_adjlist_init(igraph, &graph->succ, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &graph->succ); + IGRAPH_CHECK(igraph_matrix_char_init(&graph->isEdge, + no_of_nodes, no_of_nodes)); + IGRAPH_FINALLY(igraph_matrix_char_destroy, &graph->isEdge); + + for (i = 0; i < no_of_nodes; i++) { + neis = igraph_adjlist_get(&graph->succ, i); + n = igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + int v = (int)VECTOR(*neis)[j]; + if (MATRIX(graph->isEdge, i, v)) { + IGRAPH_ERROR("LAD functions only work on simple graphs, " + "simplify your graph", IGRAPH_EINVAL); + } + MATRIX(graph->isEdge, i, v) = 1; + } + } + + return 0; +} + +/* ---------------------------------------------------------*/ +/* Coming from domains.c */ +/* ---------------------------------------------------------*/ + +typedef struct { + igraph_vector_int_t nbVal; /* nbVal[u] = number of values in D[u] */ + igraph_vector_int_t firstVal; /* firstVal[u] = pos in val of the + first value of D[u] */ + igraph_vector_int_t val; /* val[firstVal[u]..firstVal[u]+nbVal[u]-1] = + values of D[u] */ + igraph_matrix_int_t posInVal; + /* If v in D[u] then firstVal[u] <= posInVal[u][v] < firstVal[u]+nbVal[u] + and val[posInVal[u][v]] = v + otherwise posInVal[u][v] >= firstVal[u]+nbVal[u] */ + int valSize; /* size of val */ + igraph_matrix_int_t firstMatch; + /* firstMatch[u][v] = pos in match of the first vertex + of the covering matching of G_(u, v) */ + igraph_vector_int_t matching; + /* matching[firstMatch[u][v]..firstMatch[u][v]+nbSucc[u]-1] + = covering matching of G_(u, v) */ + int nextOutToFilter; /* position in toFilter of the next pattern node whose + domain should be filtered (-1 if no domain to + filter) */ + int lastInToFilter; /* position in toFilter of the last pattern node whose + domain should be filtered */ + igraph_vector_int_t toFilter; /* contain all pattern nodes whose + domain should be filtered */ + igraph_vector_char_t markedToFilter; /* markedToFilter[u]=true if u + is in toFilter; false otherwise */ + igraph_vector_int_t globalMatchingP; /* globalMatchingP[u] = node of Gt + matched to u in globalAllDiff(Np) */ + igraph_vector_int_t globalMatchingT; + /* globalMatchingT[v] = node of Gp matched to v in globalAllDiff(Np) + or -1 if v is not matched */ +} Tdomain; + +static bool igraph_i_lad_toFilterEmpty(Tdomain* D) { + /* return true if there is no more nodes in toFilter */ + return (D->nextOutToFilter < 0); +} + +static void igraph_i_lad_resetToFilter(Tdomain *D) { + /* empty to filter and unmark the vertices that are marked to be filtered */ + igraph_vector_char_null(&D->markedToFilter); + D->nextOutToFilter = -1; +} + + +static int igraph_i_lad_nextToFilter(Tdomain* D, int size) { + /* precondition: emptyToFilter = false + remove a node from toFilter (FIFO) + unmark this node and return it */ + int u = VECTOR(D->toFilter)[D->nextOutToFilter]; + VECTOR(D->markedToFilter)[u] = false; + if (D->nextOutToFilter == D->lastInToFilter) { + /* u was the last node in tofilter */ + D->nextOutToFilter = -1; + } else if (D->nextOutToFilter == size - 1) { + D->nextOutToFilter = 0; + } else { + D->nextOutToFilter++; + } + return u; +} + +static void igraph_i_lad_addToFilter(int u, Tdomain* D, int size) { + /* if u is not marked, then add it to toFilter and mark it */ + if (VECTOR(D->markedToFilter)[u]) { + return; + } + VECTOR(D->markedToFilter)[u] = true; + if (D->nextOutToFilter < 0) { + D->lastInToFilter = 0; + D->nextOutToFilter = 0; + } else if (D->lastInToFilter == size - 1) { + D->lastInToFilter = 0; + } else { + D->lastInToFilter++; + } + VECTOR(D->toFilter)[D->lastInToFilter] = u; +} + +static bool igraph_i_lad_isInD(int u, int v, Tdomain* D) { + /* returns true if v belongs to D(u); false otherwise */ + return (MATRIX(D->posInVal, u, v) < + VECTOR(D->firstVal)[u] + VECTOR(D->nbVal)[u]); +} + +static int igraph_i_lad_augmentingPath(int u, Tdomain* D, int nbV, bool* result) { + /* return true if there exists an augmenting path starting from u and + ending on a free vertex v in the bipartite directed graph G=(U, + V, E) such that U=pattern nodes, V=target nodes, and + E={(u, v), v in D(u)} U {(v, u), D->globalMatchingP[u]=v} + update D-globalMatchingP and D->globalMatchingT consequently */ + int *fifo, *pred; + bool *marked; + int nextIn = 0; + int nextOut = 0; + int i, v, v2, u2; + + *result = false; + + /* Allocate memory */ + ALLOC_ARRAY(fifo, nbV, int); + ALLOC_ARRAY(pred, nbV, int); + ALLOC_ARRAY(marked, nbV, bool); + + for (i = 0; i < VECTOR(D->nbVal)[u]; i++) { + v = VECTOR(D->val)[ VECTOR(D->firstVal)[u] + i ]; /* v in D(u) */ + if (VECTOR(D->globalMatchingT)[v] < 0) { + /* v is free => augmenting path found */ + VECTOR(D->globalMatchingP)[u] = v; + VECTOR(D->globalMatchingT)[v] = u; + *result = true; + goto cleanup; + } + /* v is not free => add it to fifo */ + pred[v] = u; + fifo[nextIn++] = v; + marked[v] = true; + } + while (nextOut < nextIn) { + u2 = VECTOR(D->globalMatchingT)[fifo[nextOut++]]; + for (i = 0; i < VECTOR(D->nbVal)[u2]; i++) { + v = VECTOR(D->val)[ VECTOR(D->firstVal)[u2] + i ]; /* v in D(u2) */ + if (VECTOR(D->globalMatchingT)[v] < 0) { + /* v is free => augmenting path found */ + while (u2 != u) { /* update global matching wrt path */ + v2 = VECTOR(D->globalMatchingP)[u2]; + VECTOR(D->globalMatchingP)[u2] = v; + VECTOR(D->globalMatchingT)[v] = u2; + v = v2; + u2 = pred[v]; + } + VECTOR(D->globalMatchingP)[u] = v; + VECTOR(D->globalMatchingT)[v] = u; + *result = true; + goto cleanup; + } + if (!marked[v]) { /* v is not free and not marked => add it to fifo */ + pred[v] = u2; + fifo[nextIn++] = v; + marked[v] = true; + } + } + } + +cleanup: + igraph_free(fifo); + igraph_free(pred); + igraph_free(marked); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +static int igraph_i_lad_removeAllValuesButOne(int u, int v, Tdomain* D, Tgraph* Gp, + Tgraph* Gt, bool* result) { + /* remove all values but v from D(u) and add all successors of u in + toFilter return false if an inconsistency is detected wrt to + global all diff */ + int j, oldPos, newPos; + igraph_vector_int_t *uneis = igraph_adjlist_get(&Gp->succ, u); + int n = (int) igraph_vector_int_size(uneis); + /* add all successors of u in toFilter */ + for (j = 0; j < n; j++) { + igraph_i_lad_addToFilter((int) VECTOR(*uneis)[j], D, + (int) (Gp->nbVertices)); + } + /* remove all values but v from D[u] */ + oldPos = MATRIX(D->posInVal, u, v); + newPos = VECTOR(D->firstVal)[u]; + VECTOR(D->val)[oldPos] = VECTOR(D->val)[newPos]; + VECTOR(D->val)[newPos] = v; + MATRIX(D->posInVal, u, VECTOR(D->val)[newPos]) = newPos; + MATRIX(D->posInVal, u, VECTOR(D->val)[oldPos]) = oldPos; + VECTOR(D->nbVal)[u] = 1; + /* update global matchings that support the global all different + constraint */ + if (VECTOR(D->globalMatchingP)[u] != v) { + VECTOR(D->globalMatchingT)[ VECTOR(D->globalMatchingP)[u] ] = -1; + VECTOR(D->globalMatchingP)[u] = -1; + IGRAPH_CHECK(igraph_i_lad_augmentingPath(u, D, (int) (Gt->nbVertices), result)); + } else { + *result = true; + } + return 0; +} + + +static int igraph_i_lad_removeValue(int u, int v, Tdomain* D, Tgraph* Gp, + Tgraph* Gt, bool* result) { + /* remove v from D(u) and add all successors of u in toFilter + return false if an inconsistency is detected wrt global all diff */ + int j; + igraph_vector_int_t *uneis = igraph_adjlist_get(&Gp->succ, u); + int n = (int) igraph_vector_int_size(uneis); + int oldPos, newPos; + + /* add all successors of u in toFilter */ + for (j = 0; j < n; j++) { + igraph_i_lad_addToFilter((int) VECTOR(*uneis)[j], D, + (int) (Gp->nbVertices)); + } + /* remove v from D[u] */ + oldPos = MATRIX(D->posInVal, u, v); + VECTOR(D->nbVal)[u]--; + newPos = VECTOR(D->firstVal)[u] + VECTOR(D->nbVal)[u]; + VECTOR(D->val)[oldPos] = VECTOR(D->val)[newPos]; + VECTOR(D->val)[newPos] = v; + MATRIX(D->posInVal, u, VECTOR(D->val)[oldPos]) = oldPos; + MATRIX(D->posInVal, u, VECTOR(D->val)[newPos]) = newPos; + /* update global matchings that support the global all different + constraint */ + if (VECTOR(D->globalMatchingP)[u] == v) { + VECTOR(D->globalMatchingP)[u] = -1; + VECTOR(D->globalMatchingT)[v] = -1; + IGRAPH_CHECK(igraph_i_lad_augmentingPath(u, D, (int) (Gt->nbVertices), result)); + } else { + *result = true; + } + return 0; +} + + +static int igraph_i_lad_matchVertices(int nb, igraph_vector_int_t* toBeMatched, + bool induced, Tdomain* D, Tgraph* Gp, + Tgraph* Gt, int *invalid) { + /* for each u in toBeMatched[0..nb-1], match u to + D->val[D->firstVal[u] and filter domains of other non matched + vertices wrt FC(Edges) and FC(diff) (this is not mandatory, as + LAD is stronger than FC(Edges) and GAC(allDiff) is stronger than + FC(diff), but this speeds up the solution process). + return false if an inconsistency is detected by FC(Edges) or + FC(diff); true otherwise; */ + int j, u, v, u2, oldNbVal; + igraph_vector_int_t *vneis; + bool result = false; + + while (nb > 0) { + u = VECTOR(*toBeMatched)[--nb]; + v = VECTOR(D->val)[ VECTOR(D->firstVal)[u] ]; + vneis = igraph_adjlist_get(&Gt->succ, v); + /* match u to v */ + for (u2 = 0; u2 < Gp->nbVertices; u2++) { + if (u != u2) { + oldNbVal = VECTOR(D->nbVal)[u2]; + if (igraph_i_lad_isInD(u2, v, D)) { + IGRAPH_CHECK(igraph_i_lad_removeValue(u2, v, D, Gp, Gt, &result)); + if (!result) { + *invalid = 1 ; return 0; + } + } + if (MATRIX(Gp->isEdge, u, u2)) { + /* remove from D[u2] vertices which are not adjacent to v */ + j = VECTOR(D->firstVal)[u2]; + while (j < VECTOR(D->firstVal)[u2] + VECTOR(D->nbVal)[u2]) { + if (MATRIX(Gt->isEdge, v, VECTOR(D->val)[j])) { + j++; + } else { + IGRAPH_CHECK(igraph_i_lad_removeValue(u2, VECTOR(D->val)[j], D, Gp, Gt, &result)); + if (!result) { + *invalid = 1; return 0; + } + } + } + } else if (induced) { + /* (u, u2) is not an edge => remove neighbors of v from D[u2] */ + if (VECTOR(D->nbVal)[u2] < VECTOR(Gt->nbSucc)[v]) { + j = VECTOR(D->firstVal)[u2]; + while (j < VECTOR(D->firstVal)[u2] + VECTOR(D->nbVal)[u2]) { + if (!MATRIX(Gt->isEdge, v, VECTOR(D->val)[j])) { + j++; + } else { + IGRAPH_CHECK(igraph_i_lad_removeValue(u2, VECTOR(D->val)[j], D, Gp, Gt, &result)); + if (!result) { + *invalid = 1; return 0; + } + } + } + } else { + for (j = 0; j < VECTOR(Gt->nbSucc)[v]; j++) { + if (igraph_i_lad_isInD(u2, (int) VECTOR(*vneis)[j], D)) { + IGRAPH_CHECK(igraph_i_lad_removeValue(u2, (int) VECTOR(*vneis)[j], D, Gp, Gt, &result)); + if (!result) { + *invalid = 1; return 0; + } + } + } + } + } + if (VECTOR(D->nbVal)[u2] == 0) { + *invalid = 1; /* D[u2] is empty */ + return 0; + } + if ((VECTOR(D->nbVal)[u2] == 1) && (oldNbVal > 1)) { + VECTOR(*toBeMatched)[nb++] = u2; + } + } + } + } + *invalid = 0; + return 0; +} + + +static bool igraph_i_lad_matchVertex(int u, bool induced, Tdomain* D, Tgraph* Gp, + Tgraph *Gt) { + int invalid; + /* match u to D->val[D->firstVal[u]] and filter domains of other non + matched vertices wrt FC(Edges) and FC(diff) (this is not + mandatory, as LAD is stronger than FC(Edges) and GAC(allDiff) + is stronger than FC(diff), but this speeds up the solution process). + return false if an inconsistency is detected by FC(Edges) or + FC(diff); true otherwise; */ + igraph_vector_int_t toBeMatched; + igraph_vector_int_init(&toBeMatched, Gp->nbVertices); + IGRAPH_FINALLY(igraph_vector_int_destroy, &toBeMatched); + VECTOR(toBeMatched)[0] = u; + igraph_i_lad_matchVertices(1, &toBeMatched, induced, D, Gp, Gt, + &invalid); + igraph_vector_int_destroy(&toBeMatched); + IGRAPH_FINALLY_CLEAN(1); + + return invalid ? false : true; +} + + +static int igraph_i_lad_qcompare (void const *a, void const *b) { + /* function used by the qsort function */ + int pa = *((int*)a) - *((int*)b); + return pa; +} + +static bool igraph_i_lad_compare(int size_mu, int* mu, int size_mv, int* mv) { + /* return true if for every element u of mu there exists + a different element v of mv such that u <= v; + return false otherwise */ + int i, j; + qsort(mu, (size_t) size_mu, sizeof(int), igraph_i_lad_qcompare); + qsort(mv, (size_t) size_mv, sizeof(int), igraph_i_lad_qcompare); + i = size_mv - 1; + for (j = size_mu - 1; j >= 0; j--) { + if (mu[j] > mv[i]) { + return false; + } + i--; + } + return true; +} + +static int igraph_i_lad_initDomains(bool initialDomains, + igraph_vector_ptr_t *domains, Tdomain* D, + Tgraph* Gp, Tgraph* Gt, int *empty) { + /* for every pattern node u, initialize D(u) with every vertex v + such that for every neighbor u' of u there exists a different + neighbor v' of v such that degree(u) <= degree(v) + if initialDomains, then filter initial domains wrt + compatibilities given in file + return false if a domain is empty and true otherwise */ + int *val; + bool *dom; + int *mu, *mv; + int matchingSize, u, v, i, j; + igraph_vector_t *vec; + igraph_vector_t *Gp_uneis; + igraph_vector_t *Gt_vneis; + + val = igraph_Calloc(Gp->nbVertices * Gt->nbVertices, int); + if (val == 0) { + IGRAPH_ERROR("cannot allocated 'val' array in igraph_i_lad_initDomains", IGRAPH_ENOMEM); + } + + dom = igraph_Calloc(Gt->nbVertices, bool); + if (dom == 0) { + igraph_free(val); + IGRAPH_ERROR("cannot allocated 'dom' array in igraph_i_lad_initDomains", IGRAPH_ENOMEM); + } + + IGRAPH_CHECK(igraph_vector_int_init(&D->globalMatchingP, Gp->nbVertices)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &D->globalMatchingP); + igraph_vector_int_fill(&D->globalMatchingP, -1L); + + IGRAPH_CHECK(igraph_vector_int_init(&D->globalMatchingT, Gt->nbVertices)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &D->globalMatchingT); + igraph_vector_int_fill(&D->globalMatchingT, -1L); + + IGRAPH_CHECK(igraph_vector_int_init(&D->nbVal, Gp->nbVertices)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &D->nbVal); + + IGRAPH_CHECK(igraph_vector_int_init(&D->firstVal, Gp->nbVertices)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &D->firstVal); + + IGRAPH_CHECK(igraph_matrix_int_init(&D->posInVal, + Gp->nbVertices, Gt->nbVertices)); + IGRAPH_FINALLY(igraph_matrix_int_destroy, &D->posInVal); + + IGRAPH_CHECK(igraph_matrix_int_init(&D->firstMatch, + Gp->nbVertices, Gt->nbVertices)); + IGRAPH_FINALLY(igraph_matrix_int_destroy, &D->firstMatch); + + IGRAPH_CHECK(igraph_vector_char_init(&D->markedToFilter, Gp->nbVertices)); + IGRAPH_FINALLY(igraph_vector_char_destroy, &D->markedToFilter); + + IGRAPH_CHECK(igraph_vector_int_init(&D->toFilter, Gp->nbVertices)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &D->toFilter); + + D->valSize = 0; + matchingSize = 0; + + for (u = 0; u < Gp->nbVertices; u++) { + igraph_vector_int_t *Gp_uneis = igraph_adjlist_get(&Gp->succ, u); + if (initialDomains) { + /* read the list of target vertices which are compatible with u */ + vec = VECTOR(*domains)[u]; + i = (int) igraph_vector_size(vec); + memset(dom, false, sizeof(bool) * (size_t)(Gt->nbVertices)); + for (j = 0; j < i; j++) { + v = (int) VECTOR(*vec)[j]; + dom[v] = true; + } + } + VECTOR(D->markedToFilter)[u] = true; + VECTOR(D->toFilter)[u] = u; + VECTOR(D->nbVal)[u] = 0; + VECTOR(D->firstVal)[u] = D->valSize; + for (v = 0; v < Gt->nbVertices; v++) { + igraph_vector_int_t *Gt_vneis = igraph_adjlist_get(&Gt->succ, v); + if ((initialDomains) && (!dom[v])) { /* v not in D(u) */ + MATRIX(D->posInVal, u, v) = (int) (VECTOR(D->firstVal)[u] + + Gt->nbVertices); + } else { + MATRIX(D->firstMatch, u, v) = matchingSize; + matchingSize += VECTOR(Gp->nbSucc)[u]; + if (VECTOR(Gp->nbSucc)[u] <= VECTOR(Gt->nbSucc)[v]) { + mu = igraph_Calloc((long int) VECTOR(Gp->nbSucc)[u], int); + if (mu == 0) { + igraph_free(val); igraph_free(dom); + IGRAPH_ERROR("cannot allocate 'mu' array in igraph_i_lad_initDomains", IGRAPH_ENOMEM); + } + mv = igraph_Calloc((long int) VECTOR(Gt->nbSucc)[v], int); + if (mv == 0) { + igraph_free(mu); igraph_free(val); igraph_free(dom); + IGRAPH_ERROR("cannot allocate 'mv' array in igraph_i_lad_initDomains", IGRAPH_ENOMEM); + } + for (i = 0; i < VECTOR(Gp->nbSucc)[u]; i++) { + mu[i] = (int) VECTOR(Gp->nbSucc)[(long int) VECTOR(*Gp_uneis)[i]]; + } + for (i = 0; i < VECTOR(Gt->nbSucc)[v]; i++) { + mv[i] = (int) VECTOR(Gt->nbSucc)[(long int) VECTOR(*Gt_vneis)[i]]; + } + if (igraph_i_lad_compare((int) VECTOR(Gp->nbSucc)[u], mu, + (int) VECTOR(Gt->nbSucc)[v], mv) == 1) { + val[D->valSize] = v; + VECTOR(D->nbVal)[u]++; + MATRIX(D->posInVal, u, v) = D->valSize++; + } else { /* v not in D(u) */ + MATRIX(D->posInVal, u, v) = + (int)(VECTOR(D->firstVal)[u] + Gt->nbVertices); + } + igraph_free(mu); mu = 0; + igraph_free(mv); mv = 0; + } else { /* v not in D(u) */ + MATRIX(D->posInVal, u, v) = + (int) (VECTOR(D->firstVal)[u] + Gt->nbVertices); + } + } + } + if (VECTOR(D->nbVal)[u] == 0) { + *empty = 1; /* empty domain */ + igraph_free(val); + igraph_free(dom); + return 0; + } + } + IGRAPH_CHECK(igraph_vector_int_init(&D->val, D->valSize)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &D->val); + for (i = 0; i < D->valSize; i++) { + VECTOR(D->val)[i] = val[i]; + } + + IGRAPH_CHECK(igraph_vector_int_init(&D->matching, matchingSize)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &D->matching); + igraph_vector_int_fill(&D->matching, -1); + + D->nextOutToFilter = 0; + D->lastInToFilter = (int) (Gp->nbVertices - 1); + *empty = 0; + + igraph_free(val); + igraph_free(dom); + return 0; +} + +/* ---------------------------------------------------------*/ +/* Coming from allDiff.c */ +/* ---------------------------------------------------------*/ + +#define white 0 +#define grey 1 +#define black 2 +#define toBeDeleted 3 +#define deleted 4 + +static void igraph_i_lad_addToDelete(int u, int* list, int* nb, int* marked) { + if (marked[u] < toBeDeleted) { + list[(*nb)++] = u; + marked[u] = toBeDeleted; + } +} + +static int igraph_i_lad_updateMatching(int sizeOfU, int sizeOfV, + igraph_vector_int_t *degree, + igraph_vector_int_t *firstAdj, + igraph_vector_int_t *adj, + igraph_vector_int_t * matchedWithU, + int *invalid) { + /* input: + sizeOfU = number of vertices in U + sizeOfV = number of vertices in V + degree[u] = number of vertices of V which are adjacent to u + firstAdj[u] = pos in adj of the first vertex of V adjacent to u + adj[firstAdj[u]..firstAdj[u]+sizeOfU[u]-1] = vertices of V adjacent to u + + input/output: + matchedWithU[u] = vertex of V matched with u + + returns true if there exists a matching that covers U, i.e., if + for every u in 0..nbU-1, there exists a different v in 0..nb-1 + such that v is adjacent to u; returns false otherwise */ + + int *matchedWithV; /* matchedWithV[matchedWithU[u]]=u */ + int *nbPred; /* nbPred[i] = nb of predecessors of the ith + vertex of V in the DAG */ + int *pred; /* pred[i][j] = jth predecessor the ith + vertex of V in the DAG */ + int *nbSucc; /* nbSucc[i] = nb of successors of the ith + vertex of U in the DAG */ + int *succ; /* succ[i][j] = jth successor of the ith + vertex of U in the DAG */ + int *listV, *listU, *listDV, *listDU; + int nbV, nbU, nbDV, nbDU; + int i, j, k, stop, u, v, w; + int *markedV, *markedU; + /* markedX[i]=white if X[i] is not in the DAG + markedX[i]=grey if X[i] has been added to the DAG, but not its successors + markedX[i]=black if X[i] and its successors have been added to the DAG + markedX[i]=toBeDeleted if X[i] must be deleted from the DAG + markedX[i]=deleted if X[i] has been deleted from the DAG */ + int nbUnmatched = 0; /* number of vertices of U that are not matched */ + int *unmatched; /* vertices of U that are not matched */ + int *posInUnmatched; /* unmatched[posInUnmatched[u]]=u */ + igraph_vector_int_t path; + + if (sizeOfU > sizeOfV) { + *invalid = 1; /* trivial case of infeasibility */ + return 0; + } + + ALLOC_ARRAY(matchedWithV, sizeOfV, int); + ALLOC_ARRAY(nbPred, sizeOfV, int); + ALLOC_ARRAY(pred, sizeOfV * sizeOfU, int); + ALLOC_ARRAY(nbSucc, sizeOfU, int); + ALLOC_ARRAY(succ, sizeOfU * sizeOfV, int); + ALLOC_ARRAY(listV, sizeOfV, int); + ALLOC_ARRAY(listU, sizeOfU, int); + ALLOC_ARRAY(listDV, sizeOfV, int); + ALLOC_ARRAY(listDU, sizeOfU, int); + ALLOC_ARRAY(markedV, sizeOfV, int); + ALLOC_ARRAY(markedU, sizeOfU, int); + ALLOC_ARRAY(unmatched, sizeOfU, int); + ALLOC_ARRAY(posInUnmatched, sizeOfU, int); + + IGRAPH_CHECK(igraph_vector_int_init(&path, 0)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &path); + + /* initialize matchedWithV and unmatched */ + memset(matchedWithV, -1, (size_t)sizeOfV * sizeof(int)); + for (u = 0; u < sizeOfU; u++) { + if (VECTOR(*matchedWithU)[u] >= 0) { + matchedWithV[VECTOR(*matchedWithU)[u]] = u; + } else { + posInUnmatched[u] = nbUnmatched; + unmatched[nbUnmatched++] = u; + } + } + /* try to match unmatched vertices of U with free vertices of V */ + j = 0; + while (j < nbUnmatched) { + u = unmatched[j]; + for (i = VECTOR(*firstAdj)[u]; + ((i < VECTOR(*firstAdj)[u] + VECTOR(*degree)[u]) && + (matchedWithV[VECTOR(*adj)[i]] >= 0)); i++) { } + if (i == VECTOR(*firstAdj)[u] + VECTOR(*degree)[u]) { + j++; /* no free vertex for u */ + } else { + v = VECTOR(*adj)[i]; /* v is free => match u with v */ + VECTOR(*matchedWithU)[u] = v; + matchedWithV[v] = u; + unmatched[j] = unmatched[--nbUnmatched]; + posInUnmatched[unmatched[j]] = j; + } + } + + while (nbUnmatched > 0) { + /* Try to increase the number of matched vertices */ + /* step 1 : build the DAG */ + memset(markedU, white, (size_t) sizeOfU * sizeof(int)); + memset(nbSucc, 0, (size_t) sizeOfU * sizeof(int)); + memset(markedV, white, (size_t) sizeOfV * sizeof(int)); + memset(nbPred, 0, (size_t) sizeOfV * sizeof(int)); + /* first layer of the DAG from the free nodes of U */ + nbV = 0; + for (j = 0; j < nbUnmatched; j++) { + u = unmatched[j]; /* u is a free node of U */ + markedU[u] = black; + for (i = VECTOR(*firstAdj)[u]; + i < VECTOR(*firstAdj)[u] + VECTOR(*degree)[u]; i++) { + v = VECTOR(*adj)[i]; /* add edge (u, v) to the DAG */ + pred[v * sizeOfU + (nbPred[v]++)] = u; + succ[u * sizeOfV + (nbSucc[u]++)] = v; + if (markedV[v] == white) { /* first time v is added to the DAG*/ + markedV[v] = grey; + listV[nbV++] = v; + } + } + } + stop = 0; + while ((stop == 0) && (nbV > 0)) { + /* build next layer from nodes of V to nodes of U */ + nbU = 0; + for (i = 0; i < nbV; i++) { + v = listV[i]; + markedV[v] = black; + u = matchedWithV[v]; + if (markedU[u] == white) { /* edge (v, u) belongs to the DAG */ + markedU[u] = grey; + listU[nbU++] = u; + } + } + /* build next layer from nodes of U to nodes of V */ + nbV = 0; + for (j = 0; j < nbU; j++) { + u = listU[j]; + markedU[u] = black; + for (i = VECTOR(*firstAdj)[u]; + i < VECTOR(*firstAdj)[u] + VECTOR(*degree)[u]; i++) { + v = VECTOR(*adj)[i]; + if (markedV[v] != black) { /* add edge (u, v) to the DAG */ + pred[v * sizeOfU + (nbPred[v]++)] = u; + succ[u * sizeOfV + (nbSucc[u]++)] = v; + if (markedV[v] == white) { /* first time v is added to the DAG */ + markedV[v] = grey; + listV[nbV++] = v; + } + if (matchedWithV[v] == -1) { /* we have found a free node ! */ + stop = 1; + } + } + } + } + } + if (nbV == 0) { + *invalid = 1; + /* I know it's ugly. */ + goto cleanup; + } + + /* step 2: look for augmenting paths */ + for (k = 0; k < nbV; k++) { + v = listV[k]; + if ((matchedWithV[v] == -1) && (nbPred[v] > 0)) { + /* v is the final node of an augmenting path */ + IGRAPH_CHECK(igraph_vector_int_resize(&path, 1)); + VECTOR(path)[0] = v; + nbDV = 0; + nbDU = 0; + igraph_i_lad_addToDelete(v, listDV, &nbDV, markedV); + do { + u = pred[v * sizeOfU + 0]; /* (u, v) belongs to the augmenting path */ + IGRAPH_CHECK(igraph_vector_int_push_back(&path, u)); + igraph_i_lad_addToDelete(u, listDU, &nbDU, markedU); + if (VECTOR(*matchedWithU)[u] != -1) { + /* u is not the initial node of the augmenting path */ + v = VECTOR(*matchedWithU)[u]; /* (v, u) belongs to the + augmenting path */ + IGRAPH_CHECK(igraph_vector_int_push_back(&path, v)); + igraph_i_lad_addToDelete(v, listDV, &nbDV, markedV); + } + } while (VECTOR(*matchedWithU)[u] != -1); + + /* delete nodes of listDV and listDU */ + while ((nbDV > 0) || (nbDU > 0)) { + while (nbDV > 0) { /* delete v */ + v = listDV[--nbDV]; markedV[v] = deleted; + u = matchedWithV[v]; + if (u != -1) { + igraph_i_lad_addToDelete(u, listDU, &nbDU, markedU); + } + for (i = 0; i < nbPred[v]; i++) { + u = pred[v * sizeOfU + i]; /* delete edge (u, v) */ + for (j = 0; ((j < nbSucc[u]) && (v != succ[u * sizeOfV + j])); j++) { } + succ[u * sizeOfV + j] = succ[u * sizeOfV + (--nbSucc[u])]; + if (nbSucc[u] == 0) { + igraph_i_lad_addToDelete(u, listDU, &nbDU, markedU); + } + } + } + while (nbDU > 0) { /* delete u */ + u = listDU[--nbDU]; markedU[u] = deleted; + v = VECTOR(*matchedWithU)[u]; + if (v != -1) { + igraph_i_lad_addToDelete(v, listDV, &nbDV, markedV); + } + j = 0; + for (i = 0; i < nbSucc[u]; i++) { /* delete edge (u, v) */ + v = succ[u * sizeOfV + i]; + for (j = 0; ((j < nbPred[v]) && (u != pred[v * sizeOfU + j])); j++) { } + pred[v * sizeOfU + j] = pred[v * sizeOfU + (--nbPred[v])]; + if (nbPred[v] == 0) { + igraph_i_lad_addToDelete(v, listDV, &nbDV, markedV); + } + } + } + } + /* Remove the last node of the augmenting path from the set of + unmatched vertices */ + u = VECTOR(path)[igraph_vector_int_size(&path) - 1]; + i = posInUnmatched[u]; + unmatched[i] = unmatched[--nbUnmatched]; + posInUnmatched[unmatched[i]] = i; + /* Update the matching wrt the augmenting path */ + while (igraph_vector_int_size(&path) > 1) { + u = igraph_vector_int_pop_back(&path); + v = igraph_vector_int_pop_back(&path); + w = matchedWithV[v]; /* match v with u instead of v with w */ + VECTOR(*matchedWithU)[u] = v; + matchedWithV[v] = u; + } + } + } + } + *invalid = 0; + +cleanup: + /* Free the allocated arrays */ + igraph_vector_int_destroy(&path); + igraph_free(posInUnmatched); + igraph_free(unmatched); + igraph_free(markedU); + igraph_free(markedV); + igraph_free(listDU); + igraph_free(listDV); + igraph_free(listU); + igraph_free(listV); + igraph_free(succ); + igraph_free(nbSucc); + igraph_free(pred); + igraph_free(nbPred); + igraph_free(matchedWithV); + IGRAPH_FINALLY_CLEAN(14); + return 0; +} + +static void igraph_i_lad_DFS(int nbU, int nbV, int u, bool* marked, int* nbSucc, + int* succ, igraph_vector_int_t * matchedWithU, + int* order, int* nb) { + /* perform a depth first search, starting from u, in the bipartite + graph Go=(U, V, E) such that + U = vertices of Gp + V = vertices of Gt + E = { (u, matchedWithU[u]) / u is a vertex of Gp } U + { (v, u) / v is a vertex of D[u] which is not matched to v} + + Given a vertex v of Gt, nbSucc[v]=number of successors of v and + succ[v]=list of successors of v. order[nb^out+1..nb^in] contains + the vertices discovered by the DFS */ + int i; + int v = VECTOR(*matchedWithU)[u]; /* the only one predecessor of v is u */ + marked[u] = true; + if (v >= 0) { + for (i = 0; i < nbSucc[v]; i++) { + if (!marked[succ[v * nbU + i]]) { + igraph_i_lad_DFS(nbU, nbV, succ[v * nbU + i], marked, nbSucc, succ, + matchedWithU, order, nb); + } + } + } + /* we have finished with u => number it */ + order[*nb] = u; (*nb)--; +} + +static int igraph_i_lad_SCC(int nbU, int nbV, int* numV, int* numU, + int* nbSucc, int* succ, + int* nbPred, int* pred, + igraph_vector_int_t * matchedWithU, + igraph_vector_int_t * matchedWithV) { + /* postrelation: numV[v]==numU[u] iff they belong to the same + strongly connected component in the bipartite graph Go=(U, V, E) + such that + U = vertices of Gp + V = vertices of Gt + E = { (u, matchedWithU[u]) / u is a vertex of Gp } U + { (v, u) / v is a vertex of D[u] which is not matched to v} + + Given a vertex v of Gt, nbSucc[v]=number of sucessors of v and + succ[v]=list of successors of v */ + int *order; + bool *marked; + int *fifo; + int u, v, i, j, k, nbSCC, nb; + + /* Allocate memory */ + ALLOC_ARRAY(order, nbU, int); + ALLOC_ARRAY(marked, nbU, bool); + ALLOC_ARRAY(fifo, nbV, int); + + /* Order vertices of Gp wrt DFS */ + nb = nbU - 1; + for (u = 0; u < nbU; u++) { + if (!marked[u]) { + igraph_i_lad_DFS(nbU, nbV, u, marked, nbSucc, succ, matchedWithU, + order, &nb); + } + } + + /* traversal starting from order[0], then order[1], ... */ + nbSCC = 0; + memset(numU, -1, (size_t) nbU * sizeof(int)); + memset(numV, -1, (size_t) nbV * sizeof(int)); + for (i = 0; i < nbU; i++) { + u = order[i]; + v = VECTOR(*matchedWithU)[u]; + if (v == -1) { + continue; + } + if (numV[v] == -1) { /* v belongs to a new SCC */ + nbSCC++; + k = 1; fifo[0] = v; + numV[v] = nbSCC; + while (k > 0) { + v = fifo[--k]; + u = VECTOR(*matchedWithV)[v]; + if (u != -1) { + numU[u] = nbSCC; + for (j = 0; j < nbPred[u]; j++) { + v = pred[u * nbV + j]; + if (numV[v] == -1) { + numV[v] = nbSCC; + fifo[k++] = v; + } + } + } + } + } + } + + /* Free memory */ + igraph_free(fifo); + igraph_free(marked); + igraph_free(order); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + + +static int igraph_i_lad_ensureGACallDiff(bool induced, Tgraph* Gp, Tgraph* Gt, + Tdomain* D, int *invalid) { + /* precondition: D->globalMatchingP is an all different matching of + the pattern vertices + postcondition: filter domains wrt GAC(allDiff) + return false if an inconsistency is detected; true otherwise + + Build the bipartite directed graph Go=(U, V, E) such that + E = { (u, v) / u is a vertex of Gp which is matched to v (i.e., + v=D->globalMatchingP[u])} U + { (v, u) / v is a vertex of Gt which is in D(u) but is not + matched to u} */ + int *nbPred; /* nbPred[u] = nb of predecessors of u in Go */ + int *pred; /* pred[u][i] = ith + predecessor of u in Go */ + int *nbSucc; /* nbSucc[v] = nb of successors of v in Go */ + int *succ; /* succ[v][i] = ith + successor of v in Go */ + int u, v, i, w, oldNbVal, nbToMatch; + int *numV, *numU; + igraph_vector_int_t toMatch; + bool *used; + int *list; + int nb = 0; + bool result; + + /* Allocate memory */ + ALLOC_ARRAY(nbPred, Gp->nbVertices, int); + ALLOC_ARRAY(pred, Gp->nbVertices * Gt->nbVertices, int); + ALLOC_ARRAY(nbSucc, Gt->nbVertices, int); + ALLOC_ARRAY(succ, Gt->nbVertices * Gp->nbVertices, int); + ALLOC_ARRAY(numV, Gt->nbVertices, int); + ALLOC_ARRAY(numU, Gp->nbVertices, int); + ALLOC_ARRAY(used, Gp->nbVertices * Gt->nbVertices, bool); + ALLOC_ARRAY(list, Gt->nbVertices, int); + IGRAPH_CHECK(igraph_vector_int_init(&toMatch, Gp->nbVertices)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &toMatch); + + for (u = 0; u < Gp->nbVertices; u++) { + for (i = 0; i < VECTOR(D->nbVal)[u]; i++) { + v = VECTOR(D->val)[ VECTOR(D->firstVal)[u] + i ]; /* v in D(u) */ + used[u * Gt->nbVertices + v] = false; + if (v != VECTOR(D->globalMatchingP)[u]) { + pred[u * Gt->nbVertices + (nbPred[u]++)] = v; + succ[v * Gp->nbVertices + (nbSucc[v]++)] = u; + } + } + } + + /* mark as used all edges of paths starting from free vertices */ + for (v = 0; v < Gt->nbVertices; v++) { + if (VECTOR(D->globalMatchingT)[v] < 0) { /* v is free */ + list[nb++] = v; + numV[v] = true; + } + } + while (nb > 0) { + v = list[--nb]; + for (i = 0; i < nbSucc[v]; i++) { + u = succ[v * Gp->nbVertices + i]; + used[u * Gt->nbVertices + v] = true; + if (numU[u] == false) { + numU[u] = true; + w = VECTOR(D->globalMatchingP)[u]; + used[u * Gt->nbVertices + w] = true; + if (numV[w] == false) { + list[nb++] = w; + numV[w] = true; + } + } + } + } + + /* look for strongly connected components in Go */ + IGRAPH_CHECK( + igraph_i_lad_SCC((int)(Gp->nbVertices), (int)(Gt->nbVertices), numV, numU, + nbSucc, succ, nbPred, pred, &D->globalMatchingP, &D->globalMatchingT)); + + /* remove v from D[u] if (u, v) is not marked as used + and u and v are not in the same SCC + and D->globalMatchingP[u] != v */ + nbToMatch = 0; + for (u = 0; u < Gp->nbVertices; u++) { + oldNbVal = VECTOR(D->nbVal)[u]; + for (i = 0; i < VECTOR(D->nbVal)[u]; i++) { + v = VECTOR(D->val)[ VECTOR(D->firstVal)[u] + i ]; /* v in D(u) */ + if ((!used[u * Gt->nbVertices + v]) && (numV[v] != numU[u]) && + (VECTOR(D->globalMatchingP)[u] != v)) { + IGRAPH_CHECK(igraph_i_lad_removeValue(u, v, D, Gp, Gt, &result)); + if (!result) { + *invalid = 1; + /* Yes, this is ugly. */ + goto cleanup; + } + } + } + if (VECTOR(D->nbVal)[u] == 0) { + *invalid = 1; + /* Yes, this is ugly. */ + goto cleanup; + } + if ((oldNbVal > 1) && (VECTOR(D->nbVal)[u] == 1)) { + VECTOR(toMatch)[nbToMatch++] = u; + } + } + IGRAPH_CHECK(igraph_i_lad_matchVertices(nbToMatch, &toMatch, induced, + D, Gp, Gt, invalid)); + +cleanup: + igraph_vector_int_destroy(&toMatch); + igraph_free(list); + igraph_free(used); + igraph_free(numU); + igraph_free(numV); + igraph_free(succ); + igraph_free(nbSucc); + igraph_free(pred); + igraph_free(nbPred); + IGRAPH_FINALLY_CLEAN(9); + + return 0; +} + +/* ---------------------------------------------------------*/ +/* Coming from lad.c */ +/* ---------------------------------------------------------*/ + +static int igraph_i_lad_checkLAD(int u, int v, Tdomain* D, Tgraph* Gp, Tgraph* Gt, + bool *result) { + /* return true if G_(u, v) has a adj(u)-covering matching; false + otherwise */ + int u2, v2, i, j; + int nbMatched = 0; + igraph_vector_int_t *Gp_uneis = igraph_adjlist_get(&Gp->succ, u); + + int *num, *numInv; + igraph_vector_int_t nbComp; + igraph_vector_int_t firstComp; + igraph_vector_int_t comp; + int nbNum = 0; + int posInComp = 0; + igraph_vector_int_t matchedWithU; + int invalid; + + /* special case when u has only 1 adjacent node => no need to call + Hopcroft and Karp */ + if (VECTOR(Gp->nbSucc)[u] == 1) { + u2 = (int) VECTOR(*Gp_uneis)[0]; /* u2 is the only node adjacent to u */ + v2 = VECTOR(D->matching)[ MATRIX(D->firstMatch, u, v) ]; + if ((v2 != -1) && (igraph_i_lad_isInD(u2, v2, D))) { + *result = true; + return 0; + } + /* look for a support of edge (u, u2) for v */ + for (i = VECTOR(D->firstVal)[u2]; + i < VECTOR(D->firstVal)[u2] + VECTOR(D->nbVal)[u2]; i++) { + if (MATRIX(Gt->isEdge, v, VECTOR(D->val)[i])) { + VECTOR(D->matching)[ MATRIX(D->firstMatch, u, v) ] = + VECTOR(D->val)[i]; + *result = true; + return 0; + } + } + *result = false; + return 0; + } + + /* general case (when u has more than 1 adjacent node) */ + for (i = 0; i < VECTOR(Gp->nbSucc)[u]; i++) { + /* remove from the matching of G_(u, v) edges which no longer + belong to G_(u, v) */ + u2 = (int) VECTOR(*Gp_uneis)[i]; + v2 = VECTOR(D->matching)[ MATRIX(D->firstMatch, u, v) + i]; + if ((v2 != -1) && (igraph_i_lad_isInD(u2, v2, D))) { + nbMatched++; + } + } + if (nbMatched == VECTOR(Gp->nbSucc)[u]) { + *result = true; + return 0; + } /* The matching still covers adj(u) */ + + /* Allocate memory */ + ALLOC_ARRAY(num, Gt->nbVertices, int); + ALLOC_ARRAY(numInv, Gt->nbVertices, int); + + /* Build the bipartite graph + let U be the set of nodes adjacent to u + let V be the set of nodes that are adjacent to v, and that belong + to domains of nodes of U */ + /* nbComp[u]=number of elements of V that are compatible with u */ + IGRAPH_CHECK(igraph_vector_int_init(&nbComp, (long int) VECTOR(Gp->nbSucc)[u])); + IGRAPH_FINALLY(igraph_vector_int_destroy, &nbComp); + IGRAPH_CHECK(igraph_vector_int_init(&firstComp, (long int) VECTOR(Gp->nbSucc)[u])); + IGRAPH_FINALLY(igraph_vector_int_destroy, &firstComp); + /* comp[firstComp[u]..firstComp[u]+nbComp[u]-1] = nodes of Gt that + are compatible with u */ + IGRAPH_CHECK(igraph_vector_int_init(&comp, (long int) (VECTOR(Gp->nbSucc)[u] * + Gt->nbVertices))); + IGRAPH_FINALLY(igraph_vector_int_destroy, &comp); + IGRAPH_CHECK(igraph_vector_int_init(&matchedWithU, (long int) VECTOR(Gp->nbSucc)[u])); + IGRAPH_FINALLY(igraph_vector_int_destroy, &matchedWithU); + memset(num, -1, (size_t) (Gt->nbVertices) * sizeof(int)); + for (i = 0; i < VECTOR(Gp->nbSucc)[u]; i++) { + u2 = (int) VECTOR(*Gp_uneis)[i]; /* u2 is adjacent to u */ + /* search for all nodes v2 in D[u2] which are adjacent to v */ + VECTOR(nbComp)[i] = 0; + VECTOR(firstComp)[i] = posInComp; + if (VECTOR(D->nbVal)[u2] > VECTOR(Gt->nbSucc)[v]) { + for (j = VECTOR(D->firstVal)[u2]; + j < VECTOR(D->firstVal)[u2] + VECTOR(D->nbVal)[u2]; j++) { + v2 = VECTOR(D->val)[j]; /* v2 belongs to D[u2] */ + if (MATRIX(Gt->isEdge, v, v2)) { /* v2 is a successor of v */ + if (num[v2] < 0) { /* v2 has not yet been added to V */ + num[v2] = nbNum; + numInv[nbNum++] = v2; + } + VECTOR(comp)[posInComp++] = num[v2]; + VECTOR(nbComp)[i]++; + } + } + } else { + igraph_vector_int_t *Gt_vneis = igraph_adjlist_get(&Gt->succ, v); + for (j = 0; j < VECTOR(Gt->nbSucc)[v]; j++) { + v2 = (int) VECTOR(*Gt_vneis)[j]; /* v2 is a successor of v */ + if (igraph_i_lad_isInD(u2, v2, D)) { /* v2 belongs to D[u2] */ + if (num[v2] < 0) { /* v2 has not yet been added to V */ + num[v2] = nbNum; + numInv[nbNum++] = v2; + } + VECTOR(comp)[posInComp++] = num[v2]; + VECTOR(nbComp)[i]++; + } + } + } + if (VECTOR(nbComp)[i] == 0) { + *result = false; /* u2 has no compatible vertex in succ[v] */ + goto cleanup; + } + /* u2 is matched to v2 in the matching that supports (u, v) */ + v2 = VECTOR(D->matching)[ MATRIX(D->firstMatch, u, v) + i]; + if ((v2 != -1) && (igraph_i_lad_isInD(u2, v2, D))) { + VECTOR(matchedWithU)[i] = num[v2]; + } else { + VECTOR(matchedWithU)[i] = -1; + } + } + /* Call Hopcroft Karp to update the matching */ + IGRAPH_CHECK( + igraph_i_lad_updateMatching((int) VECTOR(Gp->nbSucc)[u], nbNum, &nbComp, + &firstComp, &comp, &matchedWithU, &invalid) + ); + if (invalid) { + *result = false; + goto cleanup; + } + for (i = 0; i < VECTOR(Gp->nbSucc)[u]; i++) { + VECTOR(D->matching)[ MATRIX(D->firstMatch, u, v) + i] = + numInv[ VECTOR(matchedWithU)[i] ]; + } + *result = true; + +cleanup: + igraph_free(numInv); + igraph_free(num); + igraph_vector_int_destroy(&matchedWithU); + igraph_vector_int_destroy(&comp); + igraph_vector_int_destroy(&firstComp); + igraph_vector_int_destroy(&nbComp); + IGRAPH_FINALLY_CLEAN(6); + + return 0; +} + +/* ---------------------------------------------------------*/ +/* Coming from main.c */ +/* ---------------------------------------------------------*/ + +static int igraph_i_lad_filter(bool induced, Tdomain* D, Tgraph* Gp, Tgraph* Gt, + bool *result) { + /* filter domains of all vertices in D->toFilter wrt LAD and ensure + GAC(allDiff) + return false if some domain becomes empty; true otherwise */ + int u, v, i, oldNbVal; + int invalid; + bool result2; + while (!igraph_i_lad_toFilterEmpty(D)) { + while (!igraph_i_lad_toFilterEmpty(D)) { + u = igraph_i_lad_nextToFilter(D, (int) (Gp->nbVertices)); + oldNbVal = VECTOR(D->nbVal)[u]; + i = VECTOR(D->firstVal)[u]; + while (i < VECTOR(D->firstVal)[u] + VECTOR(D->nbVal)[u]) { + /* for every target node v in D(u), check if G_(u, v) has a + covering matching */ + v = VECTOR(D->val)[i]; + IGRAPH_CHECK(igraph_i_lad_checkLAD(u, v, D, Gp, Gt, &result2)); + if (result2) { + i++; + } else { + IGRAPH_CHECK(igraph_i_lad_removeValue(u, v, D, Gp, Gt, &result2)); + if (!result2) { + *result = false; + return 0; + } + } + } + if ((VECTOR(D->nbVal)[u] == 1) && (oldNbVal > 1) && + (!igraph_i_lad_matchVertex(u, induced, D, Gp, Gt))) { + *result = false; return 0; + } + if (VECTOR(D->nbVal)[u] == 0) { + *result = false; + return 0; + } + } + igraph_i_lad_ensureGACallDiff(induced, Gp, Gt, D, &invalid); + if (invalid) { + *result = false; + return 0; + } + } + *result = true; + return 0; +} + + + +static int igraph_i_lad_solve(int timeLimit, bool firstSol, bool induced, + Tdomain* D, Tgraph* Gp, Tgraph* Gt, + int *invalid, igraph_bool_t *iso, + igraph_vector_t *map, igraph_vector_ptr_t *maps, + int *nbNodes, int *nbFail, int *nbSol, + clock_t *begin, igraph_vector_ptr_t *alloc_history) { + /* if firstSol then search for the first solution; otherwise search + for all solutions if induced then search for induced subgraphs; + otherwise search for partial subgraphs + return false if CPU time limit exceeded before the search is + completed, return true otherwise */ + + int u, v, minDom, i; + int* nbVal; + int* globalMatching; + clock_t end = clock(); + igraph_vector_t *vec; + int* val; + bool result; + + (*nbNodes)++; + + if ( (double)(end - *begin) / CLOCKS_PER_SEC >= timeLimit) { + /* CPU time limit exceeded */ + IGRAPH_ERROR("LAD CPU time exceeded", IGRAPH_CPUTIME); + } + + /* Allocate memory */ + ALLOC_ARRAY_IN_HISTORY(nbVal, Gp->nbVertices, int, alloc_history); + ALLOC_ARRAY_IN_HISTORY(globalMatching, Gp->nbVertices, int, alloc_history); + + IGRAPH_CHECK(igraph_i_lad_filter(induced, D, Gp, Gt, &result)); + if (!result) { + /* filtering has detected an inconsistency */ + (*nbFail)++; + igraph_i_lad_resetToFilter(D); + *invalid = 0; + goto cleanup; + } + + /* The current node of the search tree is consistent wrt to LAD and + GAC(allDiff) Save domain sizes and global all different matching + and search for the non matched vertex minDom with smallest domain */ + minDom = -1; + for (u = 0; u < Gp->nbVertices; u++) { + nbVal[u] = VECTOR(D->nbVal)[u]; + if ((nbVal[u] > 1) && ((minDom < 0) || (nbVal[u] < nbVal[minDom]))) { + minDom = u; + } + globalMatching[u] = VECTOR(D->globalMatchingP)[u]; + } + + if (minDom == -1) { + /* All vertices are matched => Solution found */ + if (iso) { + *iso = 1; + } + (*nbSol)++; + if (map && igraph_vector_size(map) == 0) { + IGRAPH_CHECK(igraph_vector_resize(map, Gp->nbVertices)); + for (u = 0; u < Gp->nbVertices; u++) { + VECTOR(*map)[u] = VECTOR(D->val)[ VECTOR(D->firstVal)[u] ]; + } + } + if (maps) { + vec = igraph_Calloc(1, igraph_vector_t); + if (!vec) { + IGRAPH_ERROR("LAD failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, vec); + IGRAPH_CHECK(igraph_vector_init(vec, Gp->nbVertices)); + IGRAPH_FINALLY(igraph_vector_destroy, vec); + for (u = 0; u < Gp->nbVertices; u++) { + VECTOR(*vec)[u] = VECTOR(D->val)[ VECTOR(D->firstVal)[u] ]; + } + IGRAPH_CHECK(igraph_vector_ptr_push_back(maps, vec)); + IGRAPH_FINALLY_CLEAN(2); + } + igraph_i_lad_resetToFilter(D); + *invalid = 0; + goto cleanup; + } + + /* save the domain of minDom to iterate on its values */ + ALLOC_ARRAY_IN_HISTORY(val, VECTOR(D->nbVal)[minDom], int, alloc_history); + for (i = 0; i < VECTOR(D->nbVal)[minDom]; i++) { + val[i] = VECTOR(D->val)[ VECTOR(D->firstVal)[minDom] + i ]; + } + + /* branch on minDom=v, for every target node v in D(u) */ + for (i = 0; ((i < nbVal[minDom]) && ((firstSol == 0) || (*nbSol == 0))); i++) { + IGRAPH_ALLOW_INTERRUPTION(); + v = val[i]; + IGRAPH_CHECK(igraph_i_lad_removeAllValuesButOne(minDom, v, D, Gp, Gt, &result)); + if (!result || (!igraph_i_lad_matchVertex(minDom, induced, D, Gp, Gt))) { + (*nbFail)++; + (*nbNodes)++; + igraph_i_lad_resetToFilter(D); + } else { + IGRAPH_CHECK(igraph_i_lad_solve(timeLimit, firstSol, induced, + D, Gp, Gt, invalid, iso, map, maps, + nbNodes, nbFail, nbSol, begin, + alloc_history)); + } + /* restore domain sizes and global all different matching */ + igraph_vector_int_fill(&D->globalMatchingT, -1); + for (u = 0; u < Gp->nbVertices; u++) { + VECTOR(D->nbVal)[u] = nbVal[u]; + VECTOR(D->globalMatchingP)[u] = globalMatching[u]; + VECTOR(D->globalMatchingT)[globalMatching[u]] = u; + } + } + *invalid = 0; + + igraph_free(val); + igraph_vector_ptr_pop_back(alloc_history); + +cleanup: + igraph_free(globalMatching); + igraph_vector_ptr_pop_back(alloc_history); + igraph_free(nbVal); + igraph_vector_ptr_pop_back(alloc_history); + + return 0; +} + +/** + * \section about_lad + * + * + * The LAD algorithm can search for a subgraph in a larger graph, or check + * if two graphs are isomorphic. + * See Christine Solnon: AllDifferent-based Filtering for Subgraph + * Isomorphism. Artificial Intelligence, 174(12-13):850-864, 2010. + * https://doi.org/10.1016/j.artint.2010.05.002 + * as well as the homepage of the LAD library at http://liris.cnrs.fr/csolnon/LAD.html + * The implementation in igraph is based on LADv1, but it is + * modified to use igraph's own memory allocation and error handling. + * + * + * + * LAD uses the concept of domains to indicate vertex compatibility when matching the + * pattern graph. Domains can be used to implement matching of colored vertices. + * + * + * + * LAD works with both directed and undirected graphs. Only simple graphs are supported. + * + */ + +/** + * \function igraph_subisomorphic_lad + * Check subgraph isomorphism with the LAD algorithm + * + * Check whether \p pattern is isomorphic to a subgraph os \p target. + * The original LAD implementation by Christine Solnon was used as the + * basis of this code. + * + * + * See more about LAD at http://liris.cnrs.fr/csolnon/LAD.html and in + * Christine Solnon: AllDifferent-based Filtering for Subgraph + * Isomorphism. Artificial Intelligence, 174(12-13):850-864, 2010. + * https://doi.org/10.1016/j.artint.2010.05.002 + * + * \param pattern The smaller graph, it can be directed or undirected. + * \param target The bigger graph, it can be directed or undirected. + * \param domains A pointer vector, or a null pointer. If a pointer + * vector, then it must contain pointers to \c igraph_vector_t + * objects and the length of the vector must match the number of + * vertices in the \p pattern graph. For each vertex, the ids of + * the compatible vertices in the target graph are listed. + * \param iso Pointer to a boolean, or a null pointer. If not a null + * pointer, then the boolean is set to TRUE (1) if a subgraph + * isomorphism is found, and to FALSE (0) otherwise. + * \param map Pointer to a vector or a null pointer. If not a null + * pointer and a subgraph isomorphism is found, the matching + * vertices from the target graph are listed here, for each vertex + * (in vertex id order) from the pattern graph. + * \param maps Pointer vector or a null pointer. If not a null + * pointer, then all subgraph isomorphisms are stored in the + * pointer vector, in \c igraph_vector_t objects. + * \param induced Boolean, whether to search for induced matching + * subgraphs. + * \param time_limit Processor time limit in seconds. Supply zero + * here for no limit. If the time limit is over, then the function + * signals an error. + * \return Error code + * + * \sa \ref igraph_subisomorphic_vf2() for the VF2 algorithm. + * + * Time complexity: exponential. + * + * \example examples/simple/igraph_subisomorphic_lad.c + */ + +int igraph_subisomorphic_lad(const igraph_t *pattern, const igraph_t *target, + igraph_vector_ptr_t *domains, + igraph_bool_t *iso, igraph_vector_t *map, + igraph_vector_ptr_t *maps, + igraph_bool_t induced, int time_limit) { + + bool firstSol = maps == 0; + bool initialDomains = domains != 0; + Tgraph Gp, Gt; + Tdomain D; + int invalidDomain; + int u, nbToMatch = 0; + igraph_vector_int_t toMatch; + /* Number of nodes in the search tree */ + int nbNodes = 0; + /* number of failed nodes in the search tree */ + int nbFail = 0; + /* number of solutions found */ + int nbSol = 0; + /* reusable structure to get CPU time usage */ + clock_t begin = clock(); + /* Stack to store memory blocks that are allocated during igraph_i_lad_solve */ + igraph_vector_ptr_t alloc_history; + + if (!iso && !map && !maps) { + IGRAPH_ERROR("Please give least one of `iso', `map' or `maps'", + IGRAPH_EINVAL); + } + + if (igraph_is_directed(pattern) != igraph_is_directed(target)) { + IGRAPH_ERROR("Cannot search for a directed pattern in an undirected target " + "or vice versa", IGRAPH_EINVAL); + } + if (time_limit <= 0) { + time_limit = INT_MAX; + } + + if (iso) { + *iso = (igraph_vcount(pattern) == 0); + } + if (map) { + igraph_vector_clear(map); + } + if (maps) { + igraph_vector_ptr_clear(maps); + } + + if (igraph_vcount(pattern) == 0) { + /* Special case for empty graphs */ + return IGRAPH_SUCCESS; + } + + IGRAPH_CHECK(igraph_i_lad_createGraph(pattern, &Gp)); + IGRAPH_CHECK(igraph_i_lad_createGraph(target, &Gt)); + + if (Gp.nbVertices > Gt.nbVertices) { + goto exit3; + } + + IGRAPH_CHECK(igraph_i_lad_initDomains(initialDomains, domains, &D, &Gp, + &Gt, &invalidDomain)); + if (invalidDomain) { + goto exit2; + } + + IGRAPH_CHECK(igraph_i_lad_updateMatching((int) (Gp.nbVertices), + (int) (Gt.nbVertices), + &D.nbVal, &D.firstVal, &D.val, + &D.globalMatchingP, + &invalidDomain)); + if (invalidDomain) { + goto exit; + } + + IGRAPH_CHECK(igraph_i_lad_ensureGACallDiff((char) induced, &Gp, &Gt, &D, + &invalidDomain)); + if (invalidDomain) { + goto exit; + } + + for (u = 0; u < Gp.nbVertices; u++) { + VECTOR(D.globalMatchingT)[ VECTOR(D.globalMatchingP)[u] ] = u; + } + + IGRAPH_CHECK(igraph_vector_int_init(&toMatch, Gp.nbVertices)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &toMatch); + + for (u = 0; u < Gp.nbVertices; u++) { + if (VECTOR(D.nbVal)[u] == 1) { + VECTOR(toMatch)[nbToMatch++] = u; + } + } + IGRAPH_CHECK(igraph_i_lad_matchVertices(nbToMatch, &toMatch, (char) induced, + &D, &Gp, &Gt, &invalidDomain)); + igraph_vector_int_destroy(&toMatch); + IGRAPH_FINALLY_CLEAN(1); + if (invalidDomain) { + goto exit; + } + + IGRAPH_CHECK(igraph_vector_ptr_init(&alloc_history, 0)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &alloc_history); + + IGRAPH_CHECK(igraph_i_lad_solve(time_limit, firstSol, (char) induced, &D, + &Gp, &Gt, &invalidDomain, iso, map, maps, + &nbNodes, &nbFail, &nbSol, &begin, + &alloc_history)); + + igraph_vector_ptr_destroy_all(&alloc_history); + IGRAPH_FINALLY_CLEAN(1); + +exit: + + igraph_vector_int_destroy(&D.val); + igraph_vector_int_destroy(&D.matching); + IGRAPH_FINALLY_CLEAN(2); + +exit2: + + igraph_vector_int_destroy(&D.globalMatchingP); + igraph_vector_int_destroy(&D.globalMatchingT); + igraph_vector_int_destroy(&D.nbVal); + igraph_vector_int_destroy(&D.firstVal); + igraph_matrix_int_destroy(&D.posInVal); + igraph_matrix_int_destroy(&D.firstMatch); + igraph_vector_char_destroy(&D.markedToFilter); + igraph_vector_int_destroy(&D.toFilter); + IGRAPH_FINALLY_CLEAN(8); + +exit3: + + igraph_matrix_char_destroy(&Gt.isEdge); + igraph_adjlist_destroy(&Gt.succ); + igraph_vector_destroy(&Gt.nbSucc); + igraph_matrix_char_destroy(&Gp.isEdge); + igraph_adjlist_destroy(&Gp.succ); + igraph_vector_destroy(&Gp.nbSucc); + IGRAPH_FINALLY_CLEAN(6); + + return 0; +} diff --git a/src/lapack.c b/src/lapack.c new file mode 100644 index 0000000..4a46e79 --- /dev/null +++ b/src/lapack.c @@ -0,0 +1,954 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_lapack.h" +#include "igraph_lapack_internal.h" + +/** + * \function igraph_lapack_dgetrf + * LU factorization of a general M-by-N matrix + * + * The factorization has the form + * A = P * L * U + * where P is a permutation matrix, L is lower triangular with unit + * diagonal elements (lower trapezoidal if m > n), and U is upper + * triangular (upper trapezoidal if m < n). + * \param a The input/output matrix. On entry, the M-by-N matrix to be + * factored. On exit, the factors L and U from the factorization + * A = P * L * U; the unit diagonal elements of L are not + * stored. + * \param ipiv An integer vector, the pivot indices are stored here, + * unless it is a null pointer. Row i of the matrix was + * interchanged with row ipiv[i]. + * \param info LAPACK error code. Zero on successful exit. If positive + * and i, then U(i,i) is exactly zero. The factorization has been + * completed, but the factor U is exactly singular, and division + * by zero will occur if it is used to solve a system of + * equations. If LAPACK returns an error, i.e. a negative info + * value, then an igraph error is generated as well. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_lapack_dgetrf(igraph_matrix_t *a, igraph_vector_int_t *ipiv, + int *info) { + int m = (int) igraph_matrix_nrow(a); + int n = (int) igraph_matrix_ncol(a); + int lda = m > 0 ? m : 1; + igraph_vector_int_t *myipiv = ipiv, vipiv; + + if (!ipiv) { + IGRAPH_CHECK(igraph_vector_int_init(&vipiv, m < n ? m : n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &vipiv); + myipiv = &vipiv; + } + + igraphdgetrf_(&m, &n, VECTOR(a->data), &lda, VECTOR(*myipiv), info); + + if (*info > 0) { + IGRAPH_WARNING("LU: factor is exactly singular"); + } else if (*info < 0) { + switch (*info) { + case -1: + IGRAPH_ERROR("Invalid number of rows", IGRAPH_ELAPACK); + break; + case -2: + IGRAPH_ERROR("Invalid number of columns", IGRAPH_ELAPACK); + break; + case -3: + IGRAPH_ERROR("Invalid input matrix", IGRAPH_ELAPACK); + break; + case -4: + IGRAPH_ERROR("Invalid LDA parameter", IGRAPH_ELAPACK); + break; + case -5: + IGRAPH_ERROR("Invalid pivot vector", IGRAPH_ELAPACK); + break; + case -6: + IGRAPH_ERROR("Invalid info argument", IGRAPH_ELAPACK); + break; + default: + IGRAPH_ERROR("Unknown LAPACK error", IGRAPH_ELAPACK); + break; + } + } + + if (!ipiv) { + igraph_vector_int_destroy(&vipiv); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_lapack_dgetrs + * Solve general system of linear equations using LU factorization + * + * This function calls LAPACK to solve a system of linear equations + * A * X = B or A' * X = B + * with a general N-by-N matrix A using the LU factorization + * computed by \ref igraph_lapack_dgetrf. + * \param transpose Logical scalar, whether to transpose the input + * matrix. + * \param a A matrix containing the L and U factors from the + * factorization A = P*L*U. + * \param ipiv An integer vector, the pivot indices from \ref + * igraph_lapack_dgetrf must be given here. + * \param b The right hand side matrix must be given here. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_lapack_dgetrs(igraph_bool_t transpose, const igraph_matrix_t *a, + igraph_vector_int_t *ipiv, igraph_matrix_t *b) { + char trans = transpose ? 'T' : 'N'; + int n = (int) igraph_matrix_nrow(a); + int nrhs = (int) igraph_matrix_ncol(b); + int lda = n > 0 ? n : 1; + int ldb = n > 0 ? n : 1; + int info; + + if (n != igraph_matrix_ncol(a)) { + IGRAPH_ERROR("Cannot LU solve matrix", IGRAPH_NONSQUARE); + } + if (n != igraph_matrix_nrow(b)) { + IGRAPH_ERROR("Cannot LU solve matrix, RHS of wrong size", IGRAPH_EINVAL); + } + + igraphdgetrs_(&trans, &n, &nrhs, VECTOR(a->data), &lda, VECTOR(*ipiv), + VECTOR(b->data), &ldb, &info); + + if (info < 0) { + switch (info) { + case -1: + IGRAPH_ERROR("Invalid transpose argument", IGRAPH_ELAPACK); + break; + case -2: + IGRAPH_ERROR("Invalid number of rows/columns", IGRAPH_ELAPACK); + break; + case -3: + IGRAPH_ERROR("Invalid number of RHS vectors", IGRAPH_ELAPACK); + break; + case -4: + IGRAPH_ERROR("Invalid LU matrix", IGRAPH_ELAPACK); + break; + case -5: + IGRAPH_ERROR("Invalid LDA parameter", IGRAPH_ELAPACK); + break; + case -6: + IGRAPH_ERROR("Invalid pivot vector", IGRAPH_ELAPACK); + break; + case -7: + IGRAPH_ERROR("Invalid RHS matrix", IGRAPH_ELAPACK); + break; + case -8: + IGRAPH_ERROR("Invalid LDB parameter", IGRAPH_ELAPACK); + break; + case -9: + IGRAPH_ERROR("Invalid info argument", IGRAPH_ELAPACK); + break; + default: + IGRAPH_ERROR("Unknown LAPACK error", IGRAPH_ELAPACK); + break; + } + } + + return 0; +} + +/** + * \function igraph_lapack_dgesv + * Solve system of linear equations with LU factorization + * + * This function computes the solution to a real system of linear + * equations A * X = B, where A is an N-by-N matrix and X and B are + * N-by-NRHS matrices. + * + * The LU decomposition with partial pivoting and row + * interchanges is used to factor A as + * A = P * L * U, + * where P is a permutation matrix, L is unit lower triangular, and U is + * upper triangular. The factored form of A is then used to solve the + * system of equations A * X = B. + * \param a Matrix. On entry the N-by-N coefficient matrix, on exit, + * the factors L and U from the factorization A=P*L*U; the unit + * diagonal elements of L are not stored. + * \param ipiv An integer vector or a null pointer. If not a null + * pointer, then the pivot indices that define the permutation + * matrix P, are stored here. Row i of the matrix was + * interchanged with row IPIV(i). + * \param b Matrix, on entry the right hand side matrix should be + * stored here. On exit, if there was no error, and the info + * argument is zero, then it contains the solution matrix X. + * \param info The LAPACK info code. If it is positive, then + * U(info,info) is exactly zero. In this case the factorization + * has been completed, but the factor U is exactly + * singular, so the solution could not be computed. + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_lapack_dgesv.c + */ + +int igraph_lapack_dgesv(igraph_matrix_t *a, igraph_vector_int_t *ipiv, + igraph_matrix_t *b, int *info) { + + int n = (int) igraph_matrix_nrow(a); + int nrhs = (int) igraph_matrix_ncol(b); + int lda = n > 0 ? n : 1; + int ldb = n > 0 ? n : 1; + igraph_vector_int_t *myipiv = ipiv, vipiv; + + if (n != igraph_matrix_ncol(a)) { + IGRAPH_ERROR("Cannot LU solve matrix", IGRAPH_NONSQUARE); + } + if (n != igraph_matrix_nrow(b)) { + IGRAPH_ERROR("Cannot LU solve matrix, RHS of wrong size", IGRAPH_EINVAL); + } + + if (!ipiv) { + IGRAPH_CHECK(igraph_vector_int_init(&vipiv, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &vipiv); + myipiv = &vipiv; + } + + igraphdgesv_(&n, &nrhs, VECTOR(a->data), &lda, VECTOR(*myipiv), + VECTOR(b->data), &ldb, info); + + if (*info > 0) { + IGRAPH_WARNING("LU: factor is exactly singular"); + } else if (*info < 0) { + switch (*info) { + case -1: + IGRAPH_ERROR("Invalid number of rows/column", IGRAPH_ELAPACK); + break; + case -2: + IGRAPH_ERROR("Invalid number of RHS vectors", IGRAPH_ELAPACK); + break; + case -3: + IGRAPH_ERROR("Invalid input matrix", IGRAPH_ELAPACK); + break; + case -4: + IGRAPH_ERROR("Invalid LDA parameter", IGRAPH_ELAPACK); + break; + case -5: + IGRAPH_ERROR("Invalid pivot vector", IGRAPH_ELAPACK); + break; + case -6: + IGRAPH_ERROR("Invalid RHS matrix", IGRAPH_ELAPACK); + break; + case -7: + IGRAPH_ERROR("Invalid LDB parameter", IGRAPH_ELAPACK); + break; + case -8: + IGRAPH_ERROR("Invalid info argument", IGRAPH_ELAPACK); + break; + default: + IGRAPH_ERROR("Unknown LAPACK error", IGRAPH_ELAPACK); + break; + } + } + + if (!ipiv) { + igraph_vector_int_destroy(&vipiv); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_lapack_dsyevr + * Selected eigenvalues and optionally eigenvectors of a symmetric matrix + * + * Calls the DSYEVR LAPACK function to compute selected eigenvalues + * and, optionally, eigenvectors of a real symmetric matrix A. + * Eigenvalues and eigenvectors can be selected by specifying either + * a range of values or a range of indices for the desired eigenvalues. + * + * See more in the LAPACK documentation. + * \param A Matrix, on entry it contains the symmetric input + * matrix. Only the leading N-by-N upper triangular part is + * used for the computation. + * \param which Constant that gives which eigenvalues (and possibly + * the corresponding eigenvectors) to calculate. Possible + * values are \c IGRAPH_LAPACK_DSYEV_ALL, all eigenvalues; + * \c IGRAPH_LAPACK_DSYEV_INTERVAL, all eigenvalues in the + * half-open interval (vl,vu]; + * \c IGRAPH_LAPACK_DSYEV_SELECT, the il-th through iu-th + * eigenvalues. + * \param vl If \p which is \c IGRAPH_LAPACK_DSYEV_INTERVAL, then + * this is the lower bound of the interval to be searched for + * eigenvalues. See also the \p vestimate argument. + * \param vu If \p which is \c IGRAPH_LAPACK_DSYEV_INTERVAL, then + * this is the upper bound of the interval to be searched for + * eigenvalues. See also the \p vestimate argument. + * \param vestimate An upper bound for the number of eigenvalues in + * the (vl,vu] interval, if \p which is \c + * IGRAPH_LAPACK_DSYEV_INTERVAL. Memory is allocated only for + * the given number of eigenvalues (and eigenvectors), so this + * upper bound must be correct. + * \param il The index of the smallest eigenvalue to return, if \p + * which is \c IGRAPH_LAPACK_DSYEV_SELECT. + * \param iu The index of the largets eigenvalue to return, if \p + * which is \c IGRAPH_LAPACK_DSYEV_SELECT. + * \param abstol The absolute error tolerance for the eigevalues. An + * approximate eigenvalue is accepted as converged when it is + * determined to lie in an interval [a,b] of width less than or + * equal to abstol + EPS * max(|a|,|b|), where EPS is the + * machine precision. + * \param values An initialized vector, the eigenvalues are stored + * here, unless it is a null pointer. It will be resized as + * needed. + * \param vectors An initialized matrix, the eigenvectors are stored + * in its columns, unless it is a null pointer. It will be + * resized as needed. + * \param support An integer vector. If not a null pointer, then it + * will be resized to (2*max(1,M)) (M is a the total number of + * eigenvalues found). Then the support of the eigenvectors in + * \p vectors is stored here, i.e., the indices + * indicating the nonzero elements in \p vectors. + * The i-th eigenvector is nonzero only in elements + * support(2*i-1) through support(2*i). + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_lapack_dsyevr.c + */ + +int igraph_lapack_dsyevr(const igraph_matrix_t *A, + igraph_lapack_dsyev_which_t which, + igraph_real_t vl, igraph_real_t vu, int vestimate, + int il, int iu, igraph_real_t abstol, + igraph_vector_t *values, igraph_matrix_t *vectors, + igraph_vector_int_t *support) { + + igraph_matrix_t Acopy; + char jobz = vectors ? 'V' : 'N', range, uplo = 'U'; + int n = (int) igraph_matrix_nrow(A), lda = n, ldz = n; + int m, info; + igraph_vector_t *myvalues = values, vvalues; + igraph_vector_int_t *mysupport = support, vsupport; + igraph_vector_t work; + igraph_vector_int_t iwork; + int lwork = -1, liwork = -1; + + if (n != igraph_matrix_ncol(A)) { + IGRAPH_ERROR("Cannot find eigenvalues/vectors", IGRAPH_NONSQUARE); + } + if (which == IGRAPH_LAPACK_DSYEV_INTERVAL && + (vestimate < 1 || vestimate > n)) { + IGRAPH_ERROR("Estimated (upper bound) number of eigenvalues must be " + "between 1 and n", IGRAPH_EINVAL); + } + if (which == IGRAPH_LAPACK_DSYEV_SELECT && iu - il < 0) { + IGRAPH_ERROR("Invalid 'il' and/or 'iu' values", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_copy(&Acopy, A)); + IGRAPH_FINALLY(igraph_matrix_destroy, &Acopy); + + IGRAPH_VECTOR_INIT_FINALLY(&work, 1); + IGRAPH_CHECK(igraph_vector_int_init(&iwork, 1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &iwork); + + if (!values) { + IGRAPH_VECTOR_INIT_FINALLY(&vvalues, 0); + myvalues = &vvalues; + } + if (!support) { + IGRAPH_CHECK(igraph_vector_int_init(&vsupport, 0)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &vsupport); + mysupport = &vsupport; + } + + IGRAPH_CHECK(igraph_vector_resize(myvalues, n)); + + switch (which) { + case IGRAPH_LAPACK_DSYEV_ALL: + range = 'A'; + IGRAPH_CHECK(igraph_vector_int_resize(mysupport, 2 * n)); + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, n)); + } + break; + case IGRAPH_LAPACK_DSYEV_INTERVAL: + range = 'V'; + IGRAPH_CHECK(igraph_vector_int_resize(mysupport, 2 * vestimate)); + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, vestimate)); + } + break; + case IGRAPH_LAPACK_DSYEV_SELECT: + range = 'I'; + IGRAPH_CHECK(igraph_vector_int_resize(mysupport, 2 * (iu - il + 1))); + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, iu - il + 1)); + } + break; + } + + igraphdsyevr_(&jobz, &range, &uplo, &n, &MATRIX(Acopy, 0, 0), &lda, + &vl, &vu, &il, &iu, &abstol, &m, VECTOR(*myvalues), + vectors ? &MATRIX(*vectors, 0, 0) : 0, &ldz, VECTOR(*mysupport), + VECTOR(work), &lwork, VECTOR(iwork), &liwork, &info); + + if (info != 0) { + IGRAPH_ERROR("Invalid argument to dsyevr in workspace query", IGRAPH_EINVAL); + } + + lwork = (int) VECTOR(work)[0]; + liwork = VECTOR(iwork)[0]; + IGRAPH_CHECK(igraph_vector_resize(&work, lwork)); + IGRAPH_CHECK(igraph_vector_int_resize(&iwork, liwork)); + + igraphdsyevr_(&jobz, &range, &uplo, &n, &MATRIX(Acopy, 0, 0), &lda, + &vl, &vu, &il, &iu, &abstol, &m, VECTOR(*myvalues), + vectors ? &MATRIX(*vectors, 0, 0) : 0, &ldz, VECTOR(*mysupport), + VECTOR(work), &lwork, VECTOR(iwork), &liwork, &info); + + if (info != 0) { + IGRAPH_ERROR("Invalid argument to dsyevr in calculation", IGRAPH_EINVAL); + } + + if (values) { + IGRAPH_CHECK(igraph_vector_resize(values, m)); + } + if (vectors) { + IGRAPH_CHECK(igraph_matrix_resize(vectors, n, m)); + } + if (support) { + IGRAPH_CHECK(igraph_vector_int_resize(support, m)); + } + + if (!support) { + igraph_vector_int_destroy(&vsupport); + IGRAPH_FINALLY_CLEAN(1); + } + if (!values) { + igraph_vector_destroy(&vvalues); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_destroy(&iwork); + igraph_vector_destroy(&work); + igraph_matrix_destroy(&Acopy); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_lapack_dgeev + * Eigenvalues and optionally eigenvectors of a non-symmetric matrix + * + * This function calls LAPACK to compute, for an N-by-N real + * nonsymmetric matrix A, the eigenvalues and, optionally, the left + * and/or right eigenvectors. + * + * + * The right eigenvector v(j) of A satisfies + * A * v(j) = lambda(j) * v(j) + * where lambda(j) is its eigenvalue. + * The left eigenvector u(j) of A satisfies + * u(j)**H * A = lambda(j) * u(j)**H + * where u(j)**H denotes the conjugate transpose of u(j). + * + * + * The computed eigenvectors are normalized to have Euclidean norm + * equal to 1 and largest component real. + * + * \param A matrix. On entry it contains the N-by-N input matrix. + * \param valuesreal Pointer to an initialized vector, or a null + * pointer. If not a null pointer, then the real parts of the + * eigenvalues are stored here. The vector will be resized as + * needed. + * \param valuesimag Pointer to an initialized vector, or a null + * pointer. If not a null pointer, then the imaginary parts of + * the eigenvalues are stored here. The vector will be resized + * as needed. + * \param vectorsleft Pointer to an initialized matrix, or a null + * pointer. If not a null pointer, then the left eigenvectors + * are stored in the columns of the matrix. The matrix will be + * resized as needed. + * \param vectorsright Pointer to an initialized matrix, or a null + * pointer. If not a null pointer, then the right eigenvectors + * are stored in the columns of the matrix. The matrix will be + * resized as needed. + * \param info This argument is used for two purposes. As an input + * argument it gives whether an igraph error should be + * generated if the QR algorithm fails to compute all + * eigenvalues. If \p info is non-zero, then an error is + * generated, otherwise only a warning is given. + * On exit it contains the LAPACK error code. + * Zero means successful exit. + * A negative values means that some of the arguments had an + * illegal value, this always triggers an igraph error. An i + * positive value means that the QR algorithm failed to + * compute all the eigenvalues, and no eigenvectors have been + * computed; element i+1:N of \p valuesreal and \p valuesimag + * contain eigenvalues which have converged. This case only + * generates an igraph error, if \p info was non-zero on entry. + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_lapack_dgeev.c + */ + +int igraph_lapack_dgeev(const igraph_matrix_t *A, + igraph_vector_t *valuesreal, + igraph_vector_t *valuesimag, + igraph_matrix_t *vectorsleft, + igraph_matrix_t *vectorsright, + int *info) { + + char jobvl = vectorsleft ? 'V' : 'N'; + char jobvr = vectorsright ? 'V' : 'N'; + int n = (int) igraph_matrix_nrow(A); + int lda = n, ldvl = n, ldvr = n, lwork = -1; + igraph_vector_t work; + igraph_vector_t *myreal = valuesreal, *myimag = valuesimag, vreal, vimag; + igraph_matrix_t Acopy; + int error = *info; + + if (igraph_matrix_ncol(A) != n) { + IGRAPH_ERROR("Cannot calculate eigenvalues (dgeev)", IGRAPH_NONSQUARE); + } + + IGRAPH_CHECK(igraph_matrix_copy(&Acopy, A)); + IGRAPH_FINALLY(igraph_matrix_destroy, &Acopy); + + IGRAPH_VECTOR_INIT_FINALLY(&work, 1); + + if (!valuesreal) { + IGRAPH_VECTOR_INIT_FINALLY(&vreal, n); + myreal = &vreal; + } else { + IGRAPH_CHECK(igraph_vector_resize(myreal, n)); + } + if (!valuesimag) { + IGRAPH_VECTOR_INIT_FINALLY(&vimag, n); + myimag = &vimag; + } else { + IGRAPH_CHECK(igraph_vector_resize(myimag, n)); + } + if (vectorsleft) { + IGRAPH_CHECK(igraph_matrix_resize(vectorsleft, n, n)); + } + if (vectorsright) { + IGRAPH_CHECK(igraph_matrix_resize(vectorsright, n, n)); + } + + igraphdgeev_(&jobvl, &jobvr, &n, &MATRIX(Acopy, 0, 0), &lda, + VECTOR(*myreal), VECTOR(*myimag), + vectorsleft ? &MATRIX(*vectorsleft, 0, 0) : 0, &ldvl, + vectorsright ? &MATRIX(*vectorsright, 0, 0) : 0, &ldvr, + VECTOR(work), &lwork, info); + + lwork = (int) VECTOR(work)[0]; + IGRAPH_CHECK(igraph_vector_resize(&work, lwork)); + + igraphdgeev_(&jobvl, &jobvr, &n, &MATRIX(Acopy, 0, 0), &lda, + VECTOR(*myreal), VECTOR(*myimag), + vectorsleft ? &MATRIX(*vectorsleft, 0, 0) : 0, &ldvl, + vectorsright ? &MATRIX(*vectorsright, 0, 0) : 0, &ldvr, + VECTOR(work), &lwork, info); + + if (*info < 0) { + IGRAPH_ERROR("Cannot calculate eigenvalues (dgeev)", IGRAPH_ELAPACK); + } else if (*info > 0) { + if (error) { + IGRAPH_ERROR("Cannot calculate eigenvalues (dgeev)", IGRAPH_ELAPACK); + } else { + IGRAPH_WARNING("Cannot calculate eigenvalues (dgeev)"); + } + } + + if (!valuesimag) { + igraph_vector_destroy(&vimag); + IGRAPH_FINALLY_CLEAN(1); + } + if (!valuesreal) { + igraph_vector_destroy(&vreal); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&work); + igraph_matrix_destroy(&Acopy); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_lapack_dgeevx + * Eigenvalues/vectors of nonsymmetric matrices, expert mode + * + * This function calculates the eigenvalues and optionally the left + * and/or right eigenvectors of a nonsymmetric N-by-N real matrix. + * + * + * Optionally also, it computes a balancing transformation to improve + * the conditioning of the eigenvalues and eigenvectors (\p ilo, \pihi, + * \p scale, and \p abnrm), reciprocal condition numbers for the + * eigenvalues (\p rconde), and reciprocal condition numbers for the + * right eigenvectors (\p rcondv). + * + * + * The right eigenvector v(j) of A satisfies + * A * v(j) = lambda(j) * v(j) + * where lambda(j) is its eigenvalue. + * The left eigenvector u(j) of A satisfies + * u(j)**H * A = lambda(j) * u(j)**H + * where u(j)**H denotes the conjugate transpose of u(j). + * + * + * The computed eigenvectors are normalized to have Euclidean norm + * equal to 1 and largest component real. + * + * + * Balancing a matrix means permuting the rows and columns to make it + * more nearly upper triangular, and applying a diagonal similarity + * transformation D * A * D**(-1), where D is a diagonal matrix, to + * make its rows and columns closer in norm and the condition numbers + * of its eigenvalues and eigenvectors smaller. The computed + * reciprocal condition numbers correspond to the balanced matrix. + * Permuting rows and columns will not change the condition numbers + * (in exact arithmetic) but diagonal scaling will. For further + * explanation of balancing, see section 4.10.2 of the LAPACK + * Users' Guide. + * + * \param balance Scalar that indicated, whether the input matrix + * should be balanced. Possible values: + * \clist + * \cli IGRAPH_LAPACK_DGEEVX_BALANCE_NONE + * no not diagonally scale or permute. + * \cli IGRAPH_LAPACK_DGEEVX_BALANCE_PERM + * perform permutations to make the matrix more nearly upper + * triangular. Do not diagonally scale. + * \cli IGRAPH_LAPACK_DGEEVX_BALANCE_SCALE + * diagonally scale the matrix, i.e. replace A by + * D*A*D**(-1), where D is a diagonal matrix, chosen to make + * the rows and columns of A more equal in norm. Do not + * permute. + * \cli IGRAPH_LAPACK_DGEEVX_BALANCE_BOTH + * both diagonally scale and permute A. + * \endclist + * \param A The input matrix, must be square. + * \param valuesreal An initialized vector, or a NULL pointer. If not + * a NULL pointer, then the real parts of the eigenvalues are stored + * here. The vector will be resized, as needed. + * \param valuesimag An initialized vector, or a NULL pointer. If not + * a NULL pointer, then the imaginary parts of the eigenvalues are stored + * here. The vector will be resized, as needed. + * \param vectorsleft An initialized matrix or a NULL pointer. If not + * a null pointer, then the left eigenvectors are stored here. The + * order corresponds to the eigenvalues and the eigenvectors are + * stored in a compressed form. If the j-th eigenvalue is real then + * column j contains the corresponding eigenvector. If the j-th and + * (j+1)-th eigenvalues form a complex conjugate pair, then the j-th + * and (j+1)-th columns contain their corresponding eigenvectors. + * \param vectorsright An initialized matrix or a NULL pointer. If not + * a null pointer, then the right eigenvectors are stored here. The + * format is the same, as for the \p vectorsleft argument. + * \param ilo + * \param ihi \p ilo and \p ihi are integer values determined when A was + * balanced. The balanced A(i,j) = 0 if I>J and + * J=1,...,ilo-1 or I=ihi+1,...,N. + * \param scale Pointer to an initialized vector or a NULL pointer. If + * not a NULL pointer, then details of the permutations and scaling + * factors applied when balancing \param A, are stored here. + * If P(j) is the index of the row and column + * interchanged with row and column j, and D(j) is the scaling + * factor applied to row and column j, then + * \clist + * \cli scale(J) = P(J), for J = 1,...,ilo-1 + * \cli scale(J) = D(J), for J = ilo,...,ihi + * \cli scale(J) = P(J) for J = ihi+1,...,N. + * \endclist + * The order in which the interchanges are made is N to \p ihi+1, + * then 1 to \p ilo-1. + * \param abnrm Pointer to a real variable, the one-norm of the + * balanced matrix is stored here. (The one-norm is the maximum of + * the sum of absolute values of elements in any column.) + * \param rconde An initialized vector or a NULL pointer. If not a + * null pointer, then the reciprocal condition numbers of the + * eigenvalues are stored here. + * \param rcondv An initialized vector or a NULL pointer. If not a + * null pointer, then the reciprocal condition numbers of the right + * eigenvectors are stored here. + * \param info This argument is used for two purposes. As an input + * argument it gives whether an igraph error should be + * generated if the QR algorithm fails to compute all + * eigenvalues. If \p info is non-zero, then an error is + * generated, otherwise only a warning is given. + * On exit it contains the LAPACK error code. + * Zero means successful exit. + * A negative values means that some of the arguments had an + * illegal value, this always triggers an igraph error. An i + * positive value means that the QR algorithm failed to + * compute all the eigenvalues, and no eigenvectors have been + * computed; element i+1:N of \p valuesreal and \p valuesimag + * contain eigenvalues which have converged. This case only + * generated an igraph error, if \p info was non-zero on entry. + * \return Error code. + * + * Time complexity: TODO + * + * \example examples/simple/igraph_lapack_dgeevx.c + */ + +int igraph_lapack_dgeevx(igraph_lapack_dgeevx_balance_t balance, + const igraph_matrix_t *A, + igraph_vector_t *valuesreal, + igraph_vector_t *valuesimag, + igraph_matrix_t *vectorsleft, + igraph_matrix_t *vectorsright, + int *ilo, int *ihi, igraph_vector_t *scale, + igraph_real_t *abnrm, + igraph_vector_t *rconde, + igraph_vector_t *rcondv, + int *info) { + + char balanc; + char jobvl = vectorsleft ? 'V' : 'N'; + char jobvr = vectorsright ? 'V' : 'N'; + char sense; + int n = (int) igraph_matrix_nrow(A); + int lda = n, ldvl = n, ldvr = n, lwork = -1; + igraph_vector_t work; + igraph_vector_int_t iwork; + igraph_matrix_t Acopy; + int error = *info; + igraph_vector_t *myreal = valuesreal, *myimag = valuesimag, vreal, vimag; + igraph_vector_t *myscale = scale, vscale; + + if (igraph_matrix_ncol(A) != n) { + IGRAPH_ERROR("Cannot calculate eigenvalues (dgeevx)", IGRAPH_NONSQUARE); + } + + switch (balance) { + case IGRAPH_LAPACK_DGEEVX_BALANCE_NONE: + balanc = 'N'; + break; + case IGRAPH_LAPACK_DGEEVX_BALANCE_PERM: + balanc = 'P'; + break; + case IGRAPH_LAPACK_DGEEVX_BALANCE_SCALE: + balanc = 'S'; + break; + case IGRAPH_LAPACK_DGEEVX_BALANCE_BOTH: + balanc = 'B'; + break; + default: + IGRAPH_ERROR("Invalid 'balance' argument", IGRAPH_EINVAL); + break; + } + + if (!rconde && !rcondv) { + sense = 'N'; + } else if (rconde && !rcondv) { + sense = 'E'; + } else if (!rconde && rcondv) { + sense = 'V'; + } else { + sense = 'B'; + } + + IGRAPH_CHECK(igraph_matrix_copy(&Acopy, A)); + IGRAPH_FINALLY(igraph_matrix_destroy, &Acopy); + + IGRAPH_VECTOR_INIT_FINALLY(&work, 1); + IGRAPH_CHECK(igraph_vector_int_init(&iwork, n)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &iwork); + + if (!valuesreal) { + IGRAPH_VECTOR_INIT_FINALLY(&vreal, n); + myreal = &vreal; + } else { + IGRAPH_CHECK(igraph_vector_resize(myreal, n)); + } + if (!valuesimag) { + IGRAPH_VECTOR_INIT_FINALLY(&vimag, n); + myimag = &vimag; + } else { + IGRAPH_CHECK(igraph_vector_resize(myimag, n)); + } + if (!scale) { + IGRAPH_VECTOR_INIT_FINALLY(&vscale, n); + myscale = &vscale; + } else { + IGRAPH_CHECK(igraph_vector_resize(scale, n)); + } + if (vectorsleft) { + IGRAPH_CHECK(igraph_matrix_resize(vectorsleft, n, n)); + } + if (vectorsright) { + IGRAPH_CHECK(igraph_matrix_resize(vectorsright, n, n)); + } + + igraphdgeevx_(&balanc, &jobvl, &jobvr, &sense, &n, &MATRIX(Acopy, 0, 0), + &lda, VECTOR(*myreal), VECTOR(*myimag), + vectorsleft ? &MATRIX(*vectorsleft, 0, 0) : 0, &ldvl, + vectorsright ? &MATRIX(*vectorsright, 0, 0) : 0, &ldvr, + ilo, ihi, VECTOR(*myscale), abnrm, + rconde ? VECTOR(*rconde) : 0, + rcondv ? VECTOR(*rcondv) : 0, + VECTOR(work), &lwork, VECTOR(iwork), info); + + lwork = (int) VECTOR(work)[0]; + IGRAPH_CHECK(igraph_vector_resize(&work, lwork)); + + igraphdgeevx_(&balanc, &jobvl, &jobvr, &sense, &n, &MATRIX(Acopy, 0, 0), + &lda, VECTOR(*myreal), VECTOR(*myimag), + vectorsleft ? &MATRIX(*vectorsleft, 0, 0) : 0, &ldvl, + vectorsright ? &MATRIX(*vectorsright, 0, 0) : 0, &ldvr, + ilo, ihi, VECTOR(*myscale), abnrm, + rconde ? VECTOR(*rconde) : 0, + rcondv ? VECTOR(*rcondv) : 0, + VECTOR(work), &lwork, VECTOR(iwork), info); + + if (*info < 0) { + IGRAPH_ERROR("Cannot calculate eigenvalues (dgeev)", IGRAPH_ELAPACK); + } else if (*info > 0) { + if (error) { + IGRAPH_ERROR("Cannot calculate eigenvalues (dgeev)", IGRAPH_ELAPACK); + } else { + IGRAPH_WARNING("Cannot calculate eigenvalues (dgeev)"); + } + } + + if (!scale) { + igraph_vector_destroy(&vscale); + IGRAPH_FINALLY_CLEAN(1); + } + + if (!valuesimag) { + igraph_vector_destroy(&vimag); + IGRAPH_FINALLY_CLEAN(1); + } + + if (!valuesreal) { + igraph_vector_destroy(&vreal); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_int_destroy(&iwork); + igraph_vector_destroy(&work); + igraph_matrix_destroy(&Acopy); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +int igraph_lapack_dgehrd(const igraph_matrix_t *A, + int ilo, int ihi, + igraph_matrix_t *result) { + + int n = (int) igraph_matrix_nrow(A); + int lda = n; + int lwork = -1; + igraph_vector_t work; + igraph_real_t optwork; + igraph_vector_t tau; + igraph_matrix_t Acopy; + int info = 0; + int i; + + if (igraph_matrix_ncol(A) != n) { + IGRAPH_ERROR("Hessenberg reduction failed", IGRAPH_NONSQUARE); + } + + if (ilo < 1 || ihi > n || ilo > ihi) { + IGRAPH_ERROR("Invalid `ilo' and/or `ihi'", IGRAPH_EINVAL); + } + + if (n <= 1) { + IGRAPH_CHECK(igraph_matrix_update(result, A)); + return 0; + } + + IGRAPH_CHECK(igraph_matrix_copy(&Acopy, A)); + IGRAPH_FINALLY(igraph_matrix_destroy, &Acopy); + IGRAPH_VECTOR_INIT_FINALLY(&tau, n - 1); + + igraphdgehrd_(&n, &ilo, &ihi, &MATRIX(Acopy, 0, 0), &lda, VECTOR(tau), + &optwork, &lwork, &info); + + if (info != 0) { + IGRAPH_ERROR("Internal Hessenberg transformation error", + IGRAPH_EINTERNAL); + } + + lwork = (int) optwork; + IGRAPH_VECTOR_INIT_FINALLY(&work, lwork); + + igraphdgehrd_(&n, &ilo, &ihi, &MATRIX(Acopy, 0, 0), &lda, VECTOR(tau), + VECTOR(work), &lwork, &info); + + if (info != 0) { + IGRAPH_ERROR("Internal Hessenberg transformation error", + IGRAPH_EINTERNAL); + } + + igraph_vector_destroy(&work); + igraph_vector_destroy(&tau); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_matrix_update(result, &Acopy)); + + igraph_matrix_destroy(&Acopy); + IGRAPH_FINALLY_CLEAN(1); + + for (i = 0; i < n - 2; i++) { + int j; + for (j = i + 2; j < n; j++) { + MATRIX(*result, j, i) = 0.0; + } + } + + return 0; +} + +int igraph_lapack_ddot(const igraph_vector_t *v1, const igraph_vector_t *v2, + igraph_real_t *res) { + + int n = igraph_vector_size(v1); + int one = 1; + + if (igraph_vector_size(v2) != n) { + IGRAPH_ERROR("Dot product of vectors with different dimensions", + IGRAPH_EINVAL); + } + + *res = igraphddot_(&n, VECTOR(*v1), &one, VECTOR(*v2), &one); + + return 0; +} + diff --git a/src/layout.c b/src/layout.c new file mode 100644 index 0000000..e9d2ee8 --- /dev/null +++ b/src/layout.c @@ -0,0 +1,2425 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph R package. + Copyright (C) 2003-2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" +#include "igraph_random.h" +#include "igraph_memory.h" +#include "igraph_iterators.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include "igraph_progress.h" +#include "igraph_interrupt_internal.h" +#include "igraph_paths.h" +#include "igraph_structural.h" +#include "igraph_visitor.h" +#include "igraph_topology.h" +#include "igraph_components.h" +#include "igraph_types_internal.h" +#include "igraph_dqueue.h" +#include "igraph_arpack.h" +#include "igraph_blas.h" +#include "igraph_centrality.h" +#include "igraph_eigen.h" +#include "config.h" +#include +#include "igraph_math.h" + + +/** + * \section about_layouts + * + * Layout generator functions (or at least most of them) try to place the + * vertices and edges of a graph on a 2D plane or in 3D space in a way + * which visually pleases the human eye. + * + * They take a graph object and a number of parameters as arguments + * and return an \type igraph_matrix_t, in which each row gives the + * coordinates of a vertex. + */ + +/** + * \ingroup layout + * \function igraph_layout_random + * \brief Places the vertices uniform randomly on a plane. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \return Error code. The current implementation always returns with + * success. + * + * Time complexity: O(|V|), the + * number of vertices. + */ + +int igraph_layout_random(const igraph_t *graph, igraph_matrix_t *res) { + + long int no_of_nodes = igraph_vcount(graph); + long int i; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + + RNG_BEGIN(); + + for (i = 0; i < no_of_nodes; i++) { + MATRIX(*res, i, 0) = RNG_UNIF(-1, 1); + MATRIX(*res, i, 1) = RNG_UNIF(-1, 1); + } + + RNG_END(); + + return 0; +} + +/** + * \function igraph_layout_random_3d + * \brief Random layout in 3D + * + * \param graph The graph to place. + * \param res Pointer to an initialized matrix object. It will be + * resized to hold the result. + * \return Error code. The current implementation always returns with + * success. + * + * Added in version 0.2. + * + * Time complexity: O(|V|), the number of vertices. + */ + +int igraph_layout_random_3d(const igraph_t *graph, igraph_matrix_t *res) { + + long int no_of_nodes = igraph_vcount(graph); + long int i; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 3)); + + RNG_BEGIN(); + + for (i = 0; i < no_of_nodes; i++) { + MATRIX(*res, i, 0) = RNG_UNIF(-1, 1); + MATRIX(*res, i, 1) = RNG_UNIF(-1, 1); + MATRIX(*res, i, 2) = RNG_UNIF(-1, 1); + } + + RNG_END(); + + return 0; +} + +/** + * \ingroup layout + * \function igraph_layout_circle + * \brief Places the vertices uniformly on a circle, in the order of vertex ids. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param order The order of the vertices on the circle. The vertices + * not included here, will be placed at (0,0). Supply + * \ref igraph_vss_all() here for all vertices, in the order of + * their vertex ids. + * \return Error code. + * + * Time complexity: O(|V|), the + * number of vertices. + */ + +int igraph_layout_circle(const igraph_t *graph, igraph_matrix_t *res, + igraph_vs_t order) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_integer_t vs_size; + long int i; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vs_size(graph, &order, &vs_size)); + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + igraph_matrix_null(res); + + igraph_vit_create(graph, order, &vit); + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_real_t phi = 2 * M_PI / vs_size * i; + int idx = IGRAPH_VIT_GET(vit); + MATRIX(*res, idx, 0) = cos(phi); + MATRIX(*res, idx, 1) = sin(phi); + } + igraph_vit_destroy(&vit); + + return 0; +} + +/** + * \function igraph_layout_star + * Generate a star-like layout + * + * \param graph The input graph. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param center The id of the vertex to put in the center. + * \param order A numeric vector giving the order of the vertices + * (including the center vertex!). If a null pointer, then the + * vertices are placed in increasing vertex id order. + * \return Error code. + * + * Time complexity: O(|V|), linear in the number of vertices. + * + * \sa \ref igraph_layout_circle() and other layout generators. + */ + +int igraph_layout_star(const igraph_t *graph, igraph_matrix_t *res, + igraph_integer_t center, const igraph_vector_t *order) { + + long int no_of_nodes = igraph_vcount(graph); + long int c = center; + long int i; + igraph_real_t step; + igraph_real_t phi; + + if (order && igraph_vector_size(order) != no_of_nodes) { + IGRAPH_ERROR("Invalid order vector length", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + + if (no_of_nodes == 1) { + MATRIX(*res, 0, 0) = MATRIX(*res, 0, 1) = 0.0; + } else { + for (i = 0, step = 2 * M_PI / (no_of_nodes - 1), phi = 0; + i < no_of_nodes; i++) { + long int node = order ? (long int) VECTOR(*order)[i] : i; + if (node != c) { + MATRIX(*res, node, 0) = cos(phi); + MATRIX(*res, node, 1) = sin(phi); + phi += step; + } else { + MATRIX(*res, node, 0) = MATRIX(*res, node, 1) = 0.0; + } + } + } + + return 0; +} + +/** + * \function igraph_layout_sphere + * \brief Places vertices (more or less) uniformly on a sphere. + * + * + * The algorithm was described in the following paper: + * Distributing many points on a sphere by E.B. Saff and + * A.B.J. Kuijlaars, \emb Mathematical Intelligencer \eme 19.1 (1997) + * 5--11. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \return Error code. The current implementation always returns with + * success. + * + * Added in version 0.2. + * + * Time complexity: O(|V|), the number of vertices in the graph. + */ + +int igraph_layout_sphere(const igraph_t *graph, igraph_matrix_t *res) { + + long int no_of_nodes = igraph_vcount(graph); + long int i; + igraph_real_t h; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 3)); + + if (no_of_nodes != 0) { + MATRIX(*res, 0, 0) = M_PI; + MATRIX(*res, 0, 1) = 0; + } + for (i = 1; i < no_of_nodes - 1; i++) { + h = -1 + 2 * i / (double)(no_of_nodes - 1); + MATRIX(*res, i, 0) = acos(h); + MATRIX(*res, i, 1) = fmod((MATRIX(*res, i - 1, 1) + + 3.6 / sqrt(no_of_nodes * (1 - h * h))), 2 * M_PI); + IGRAPH_ALLOW_INTERRUPTION(); + } + if (no_of_nodes >= 2) { + MATRIX(*res, no_of_nodes - 1, 0) = 0; + MATRIX(*res, no_of_nodes - 1, 1) = 0; + } + + for (i = 0; i < no_of_nodes; i++) { + igraph_real_t x = cos(MATRIX(*res, i, 1)) * sin(MATRIX(*res, i, 0)); + igraph_real_t y = sin(MATRIX(*res, i, 1)) * sin(MATRIX(*res, i, 0)); + igraph_real_t z = cos(MATRIX(*res, i, 0)); + MATRIX(*res, i, 0) = x; + MATRIX(*res, i, 1) = y; + MATRIX(*res, i, 2) = z; + IGRAPH_ALLOW_INTERRUPTION(); + } + + return 0; +} + +/** + * \ingroup layout + * \function igraph_layout_grid + * \brief Places the vertices on a regular grid on the plane. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param width The number of vertices in a single row of the grid. + * When zero or negative, the width of the grid will be the + * square root of the number of vertices, rounded up if needed. + * \return Error code. The current implementation always returns with + * success. + * + * Time complexity: O(|V|), the number of vertices. + */ +int igraph_layout_grid(const igraph_t *graph, igraph_matrix_t *res, long int width) { + long int i, no_of_nodes = igraph_vcount(graph); + igraph_real_t x, y; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + + if (width <= 0) { + width = (long int) ceil(sqrt(no_of_nodes)); + } + + x = y = 0; + for (i = 0; i < no_of_nodes; i++) { + MATRIX(*res, i, 0) = x++; + MATRIX(*res, i, 1) = y; + if (x == width) { + x = 0; y++; + } + } + + return 0; +} + +/** + * \ingroup layout + * \function igraph_layout_grid_3d + * \brief Places the vertices on a regular grid in the 3D space. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param width The number of vertices in a single row of the grid. When + * zero or negative, the width is determined automatically. + * \param height The number of vertices in a single column of the grid. When + * zero or negative, the height is determined automatically. + * + * \return Error code. The current implementation always returns with + * success. + * + * Time complexity: O(|V|), the number of vertices. + */ +int igraph_layout_grid_3d(const igraph_t *graph, igraph_matrix_t *res, + long int width, long int height) { + long int i, no_of_nodes = igraph_vcount(graph); + igraph_real_t x, y, z; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 3)); + + if (width <= 0 && height <= 0) { + width = height = (long int) ceil(pow(no_of_nodes, 1.0 / 3)); + } else if (width <= 0) { + width = (long int) ceil(sqrt(no_of_nodes / (double)height)); + } else if (height <= 0) { + height = (long int) ceil(sqrt(no_of_nodes / (double)width)); + } + + x = y = z = 0; + for (i = 0; i < no_of_nodes; i++) { + MATRIX(*res, i, 0) = x++; + MATRIX(*res, i, 1) = y; + MATRIX(*res, i, 2) = z; + if (x == width) { + x = 0; y++; + if (y == height) { + y = 0; z++; + } + } + } + + return 0; +} + +int igraph_layout_springs(const igraph_t *graph, igraph_matrix_t *res, + igraph_real_t mass, igraph_real_t equil, igraph_real_t k, + igraph_real_t repeqdis, igraph_real_t kfr, igraph_bool_t repulse) { + + IGRAPH_UNUSED(graph); IGRAPH_UNUSED(res); IGRAPH_UNUSED(mass); + IGRAPH_UNUSED(equil); IGRAPH_UNUSED(k); IGRAPH_UNUSED(repeqdis); + IGRAPH_UNUSED(kfr); IGRAPH_UNUSED(repulse); + IGRAPH_ERROR("Springs layout not implemented", IGRAPH_UNIMPLEMENTED); + /* TODO */ + return 0; +} + +static void igraph_i_norm2d(igraph_real_t *x, igraph_real_t *y) { + igraph_real_t len = sqrt((*x) * (*x) + (*y) * (*y)); + if (len != 0) { + *x /= len; + *y /= len; + } +} + +/** + * \function igraph_layout_lgl + * \brief Force based layout algorithm for large graphs. + * + * + * This is a layout generator similar to the Large Graph Layout + * algorithm and program + * (http://lgl.sourceforge.net/). But unlike LGL, this + * version uses a Fruchterman-Reingold style simulated annealing + * algorithm for placing the vertices. The speedup is achieved by + * placing the vertices on a grid and calculating the repulsion only + * for vertices which are closer to each other than a limit. + * + * \param graph The (initialized) graph object to place. + * \param res Pointer to an initialized matrix object to hold the + * result. It will be resized if needed. + * \param maxit The maximum number of cooling iterations to perform + * for each layout step. A reasonable default is 150. + * \param maxdelta The maximum length of the move allowed for a vertex + * in a single iteration. A reasonable default is the number of + * vertices. + * \param area This parameter gives the area of the square on which + * the vertices will be placed. A reasonable default value is the + * number of vertices squared. + * \param coolexp The cooling exponent. A reasonable default value is + * 1.5. + * \param repulserad Determines the radius at which vertex-vertex + * repulsion cancels out attraction of adjacent vertices. A + * reasonable default value is \p area times the number of vertices. + * \param cellsize The size of the grid cells, one side of the + * square. A reasonable default value is the fourth root of + * \p area (or the square root of the number of vertices if \p area + * is also left at its default value). + * \param proot The root vertex, this is placed first, its neighbors + * in the first iteration, second neighbors in the second, etc. If + * negative then a random vertex is chosen. + * \return Error code. + * + * Added in version 0.2. + * + * Time complexity: ideally O(dia*maxit*(|V|+|E|)), |V| is the number + * of vertices, + * dia is the diameter of the graph, worst case complexity is still + * O(dia*maxit*(|V|^2+|E|)), this is the case when all vertices happen to be + * in the same grid cell. + */ + +int igraph_layout_lgl(const igraph_t *graph, igraph_matrix_t *res, + igraph_integer_t maxit, igraph_real_t maxdelta, + igraph_real_t area, igraph_real_t coolexp, + igraph_real_t repulserad, igraph_real_t cellsize, + igraph_integer_t proot) { + + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_t mst; + long int root; + long int no_of_layers, actlayer = 0; + igraph_vector_t vids; + igraph_vector_t layers; + igraph_vector_t parents; + igraph_vector_t edges; + igraph_2dgrid_t grid; + igraph_vector_t eids; + igraph_vector_t forcex; + igraph_vector_t forcey; + + igraph_real_t frk = sqrt(area / no_of_nodes); + igraph_real_t H_n = 0; + + IGRAPH_CHECK(igraph_minimum_spanning_tree_unweighted(graph, &mst)); + IGRAPH_FINALLY(igraph_destroy, &mst); + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + + /* Determine the root vertex, random pick right now */ + if (proot < 0) { + root = RNG_INTEGER(0, no_of_nodes - 1); + } else { + root = proot; + } + + /* Assign the layers */ + IGRAPH_VECTOR_INIT_FINALLY(&vids, 0); + IGRAPH_VECTOR_INIT_FINALLY(&layers, 0); + IGRAPH_VECTOR_INIT_FINALLY(&parents, 0); + IGRAPH_CHECK(igraph_i_bfs(&mst, (igraph_integer_t) root, IGRAPH_ALL, &vids, + &layers, &parents)); + no_of_layers = igraph_vector_size(&layers) - 1; + + /* We don't need the mst any more */ + igraph_destroy(&mst); + igraph_empty(&mst, 0, IGRAPH_UNDIRECTED); /* to make finalization work */ + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges)); + IGRAPH_VECTOR_INIT_FINALLY(&eids, 0); + IGRAPH_VECTOR_INIT_FINALLY(&forcex, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&forcey, no_of_nodes); + + /* Place the vertices randomly */ + IGRAPH_CHECK(igraph_layout_random(graph, res)); + igraph_matrix_scale(res, 1e6); + + /* This is the grid for calculating the vertices near to a given vertex */ + IGRAPH_CHECK(igraph_2dgrid_init(&grid, res, + -sqrt(area / M_PI), sqrt(area / M_PI), cellsize, + -sqrt(area / M_PI), sqrt(area / M_PI), cellsize)); + IGRAPH_FINALLY(igraph_2dgrid_destroy, &grid); + + /* Place the root vertex */ + igraph_2dgrid_add(&grid, root, 0, 0); + + for (actlayer = 1; actlayer < no_of_layers; actlayer++) { + H_n += 1.0 / actlayer; + } + + for (actlayer = 1; actlayer < no_of_layers; actlayer++) { + + igraph_real_t c = 1; + long int i, j; + igraph_real_t massx, massy; + igraph_real_t px, py; + igraph_real_t sx, sy; + + long int it = 0; + igraph_real_t epsilon = 10e-6; + igraph_real_t maxchange = epsilon + 1; + long int pairs; + igraph_real_t sconst = sqrt(area / M_PI) / H_n; + igraph_2dgrid_iterator_t vidit; + + /* printf("Layer %li:\n", actlayer); */ + + /*-----------------------------------------*/ + /* Step 1: place the next layer on spheres */ + /*-----------------------------------------*/ + + RNG_BEGIN(); + + j = (long int) VECTOR(layers)[actlayer]; + for (i = (long int) VECTOR(layers)[actlayer - 1]; + i < VECTOR(layers)[actlayer]; i++) { + + long int vid = (long int) VECTOR(vids)[i]; + long int par = (long int) VECTOR(parents)[vid]; + IGRAPH_ALLOW_INTERRUPTION(); + igraph_2dgrid_getcenter(&grid, &massx, &massy); + igraph_i_norm2d(&massx, &massy); + px = MATRIX(*res, vid, 0) - MATRIX(*res, par, 0); + py = MATRIX(*res, vid, 1) - MATRIX(*res, par, 1); + igraph_i_norm2d(&px, &py); + sx = c * (massx + px) + MATRIX(*res, vid, 0); + sy = c * (massy + py) + MATRIX(*res, vid, 1); + + /* The neighbors of 'vid' */ + while (j < VECTOR(layers)[actlayer + 1] && + VECTOR(parents)[(long int)VECTOR(vids)[j]] == vid) { + igraph_real_t rx, ry; + if (actlayer == 1) { + igraph_real_t phi = 2 * M_PI / (VECTOR(layers)[2] - 1) * (j - 1); + rx = cos(phi); + ry = sin(phi); + } else { + rx = RNG_UNIF(-1, 1); + ry = RNG_UNIF(-1, 1); + } + igraph_i_norm2d(&rx, &ry); + rx = rx / actlayer * sconst; + ry = ry / actlayer * sconst; + igraph_2dgrid_add(&grid, (long int) VECTOR(vids)[j], sx + rx, sy + ry); + j++; + } + } + + RNG_END(); + + /*-----------------------------------------*/ + /* Step 2: add the edges of the next layer */ + /*-----------------------------------------*/ + + for (j = (long int) VECTOR(layers)[actlayer]; + j < VECTOR(layers)[actlayer + 1]; j++) { + long int vid = (long int) VECTOR(vids)[j]; + long int k; + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_CHECK(igraph_incident(graph, &eids, (igraph_integer_t) vid, + IGRAPH_ALL)); + for (k = 0; k < igraph_vector_size(&eids); k++) { + long int eid = (long int) VECTOR(eids)[k]; + igraph_integer_t from, to; + igraph_edge(graph, (igraph_integer_t) eid, &from, &to); + if ((from != vid && igraph_2dgrid_in(&grid, from)) || + (to != vid && igraph_2dgrid_in(&grid, to))) { + igraph_vector_push_back(&edges, eid); + } + } + } + + /*-----------------------------------------*/ + /* Step 3: let the springs spring */ + /*-----------------------------------------*/ + + maxchange = epsilon + 1; + while (it < maxit && maxchange > epsilon) { + long int jj; + igraph_real_t t = maxdelta * pow((maxit - it) / (double)maxit, coolexp); + long int vid, nei; + + IGRAPH_PROGRESS("Large graph layout", + 100.0 * ((actlayer - 1.0) / (no_of_layers - 1.0) + ((float)it) / (maxit * (no_of_layers - 1.0))), + 0); + + /* init */ + igraph_vector_null(&forcex); + igraph_vector_null(&forcey); + maxchange = 0; + + /* attractive "forces" along the edges */ + for (jj = 0; jj < igraph_vector_size(&edges); jj++) { + igraph_integer_t from, to; + igraph_real_t xd, yd, dist, force; + IGRAPH_ALLOW_INTERRUPTION(); + igraph_edge(graph, (igraph_integer_t) VECTOR(edges)[jj], &from, &to); + xd = MATRIX(*res, (long int)from, 0) - MATRIX(*res, (long int)to, 0); + yd = MATRIX(*res, (long int)from, 1) - MATRIX(*res, (long int)to, 1); + dist = sqrt(xd * xd + yd * yd); + if (dist != 0) { + xd /= dist; + yd /= dist; + } + force = dist * dist / frk; + VECTOR(forcex)[(long int)from] -= xd * force; + VECTOR(forcex)[(long int)to] += xd * force; + VECTOR(forcey)[(long int)from] -= yd * force; + VECTOR(forcey)[(long int)to] += yd * force; + } + + /* repulsive "forces" of the vertices nearby */ + pairs = 0; + igraph_2dgrid_reset(&grid, &vidit); + while ( (vid = igraph_2dgrid_next(&grid, &vidit) - 1) != -1) { + while ( (nei = igraph_2dgrid_next_nei(&grid, &vidit) - 1) != -1) { + igraph_real_t xd = MATRIX(*res, (long int)vid, 0) - + MATRIX(*res, (long int)nei, 0); + igraph_real_t yd = MATRIX(*res, (long int)vid, 1) - + MATRIX(*res, (long int)nei, 1); + igraph_real_t dist = sqrt(xd * xd + yd * yd); + igraph_real_t force; + if (dist < cellsize) { + pairs++; + if (dist == 0) { + dist = epsilon; + }; + xd /= dist; yd /= dist; + force = frk * frk * (1.0 / dist - dist * dist / repulserad); + VECTOR(forcex)[(long int)vid] += xd * force; + VECTOR(forcex)[(long int)nei] -= xd * force; + VECTOR(forcey)[(long int)vid] += yd * force; + VECTOR(forcey)[(long int)nei] -= yd * force; + } + } + } + + /* printf("verties: %li iterations: %li\n", */ + /* (long int) VECTOR(layers)[actlayer+1], pairs); */ + + /* apply the changes */ + for (jj = 0; jj < VECTOR(layers)[actlayer + 1]; jj++) { + long int vvid = (long int) VECTOR(vids)[jj]; + igraph_real_t fx = VECTOR(forcex)[vvid]; + igraph_real_t fy = VECTOR(forcey)[vvid]; + igraph_real_t ded = sqrt(fx * fx + fy * fy); + if (ded > t) { + ded = t / ded; + fx *= ded; fy *= ded; + } + igraph_2dgrid_move(&grid, vvid, fx, fy); + if (fx > maxchange) { + maxchange = fx; + } + if (fy > maxchange) { + maxchange = fy; + } + } + it++; + /* printf("%li iterations, maxchange: %f\n", it, (double)maxchange); */ + } + } + + IGRAPH_PROGRESS("Large graph layout", 100.0, 0); + igraph_destroy(&mst); + igraph_vector_destroy(&vids); + igraph_vector_destroy(&layers); + igraph_vector_destroy(&parents); + igraph_vector_destroy(&edges); + igraph_2dgrid_destroy(&grid); + igraph_vector_destroy(&eids); + igraph_vector_destroy(&forcex); + igraph_vector_destroy(&forcey); + IGRAPH_FINALLY_CLEAN(9); + return 0; + +} + +static int igraph_i_layout_reingold_tilford_unreachable( + const igraph_t *graph, + igraph_neimode_t mode, + long int real_root, + long int no_of_nodes, + igraph_vector_t *pnewedges) { + + long int no_of_newedges; + igraph_vector_t visited; + long int i, j, n; + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + igraph_adjlist_t allneis; + igraph_vector_int_t *neis; + + igraph_vector_resize(pnewedges, 0); + + /* traverse from real_root and see what nodes you cannot reach */ + no_of_newedges = 0; + IGRAPH_VECTOR_INIT_FINALLY(&visited, no_of_nodes); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, mode)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + /* start from real_root and go BFS */ + IGRAPH_CHECK(igraph_dqueue_push(&q, real_root)); + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + neis = igraph_adjlist_get(&allneis, actnode); + n = igraph_vector_int_size(neis); + VECTOR(visited)[actnode] = 1; + for (j = 0; j < n; j++) { + long int neighbor = (long int) VECTOR(*neis)[j]; + if (!(long int)VECTOR(visited)[neighbor]) { + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + } + } + } + + for (j = 0; j < no_of_nodes; j++) { + no_of_newedges += 1 - VECTOR(visited)[j]; + } + + /* if any nodes are unreachable, add edges between them and real_root */ + if (no_of_newedges != 0) { + + igraph_vector_resize(pnewedges, no_of_newedges * 2); + j = 0; + for (i = 0; i < no_of_nodes; i++) { + if (!VECTOR(visited)[i]) { + if (mode != IGRAPH_IN) { + VECTOR(*pnewedges)[2 * j] = real_root; + VECTOR(*pnewedges)[2 * j + 1] = i; + } else { + VECTOR(*pnewedges)[2 * j] = i; + VECTOR(*pnewedges)[2 * j + 1] = real_root; + } + j++; + } + } + } + + igraph_dqueue_destroy(&q); + igraph_adjlist_destroy(&allneis); + igraph_vector_destroy(&visited); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + + +/* Internal structure for Reingold-Tilford layout */ +struct igraph_i_reingold_tilford_vertex { + long int parent; /* Parent node index */ + long int level; /* Level of the node */ + igraph_real_t offset; /* X offset from parent node */ + long int left_contour; /* Next left node of the contour + of the subtree rooted at this node */ + long int right_contour; /* Next right node of the contour + of the subtree rooted at this node */ + igraph_real_t offset_follow_lc; /* X offset when following the left contour */ + igraph_real_t offset_follow_rc; /* X offset when following the right contour */ +}; + +static int igraph_i_layout_reingold_tilford_postorder(struct igraph_i_reingold_tilford_vertex *vdata, + long int node, long int vcount); +static int igraph_i_layout_reingold_tilford_calc_coords(struct igraph_i_reingold_tilford_vertex *vdata, + igraph_matrix_t *res, long int node, + long int vcount, igraph_real_t xpos); + +static int igraph_i_layout_reingold_tilford(const igraph_t *graph, + igraph_matrix_t *res, + igraph_neimode_t mode, + long int root) { + long int no_of_nodes = igraph_vcount(graph); + long int i, n, j; + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + igraph_adjlist_t allneis; + igraph_vector_int_t *neis; + struct igraph_i_reingold_tilford_vertex *vdata; + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, mode)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + vdata = igraph_Calloc(no_of_nodes, struct igraph_i_reingold_tilford_vertex); + if (vdata == 0) { + IGRAPH_ERROR("igraph_layout_reingold_tilford failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, vdata); + + for (i = 0; i < no_of_nodes; i++) { + vdata[i].parent = -1; + vdata[i].level = -1; + vdata[i].offset = 0.0; + vdata[i].left_contour = -1; + vdata[i].right_contour = -1; + vdata[i].offset_follow_lc = 0.0; + vdata[i].offset_follow_rc = 0.0; + } + vdata[root].parent = root; + vdata[root].level = 0; + MATRIX(*res, root, 1) = 0; + + /* Step 1: assign Y coordinates based on BFS and setup parents vector */ + IGRAPH_CHECK(igraph_dqueue_push(&q, root)); + IGRAPH_CHECK(igraph_dqueue_push(&q, 0)); + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + long int actdist = (long int) igraph_dqueue_pop(&q); + neis = igraph_adjlist_get(&allneis, actnode); + n = igraph_vector_int_size(neis); + + for (j = 0; j < n; j++) { + long int neighbor = (long int) VECTOR(*neis)[j]; + if (vdata[neighbor].parent >= 0) { + continue; + } + MATRIX(*res, neighbor, 1) = actdist + 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1)); + vdata[neighbor].parent = actnode; + vdata[neighbor].level = actdist + 1; + } + } + + /* Step 2: postorder tree traversal, determines the appropriate X + * offsets for every node */ + igraph_i_layout_reingold_tilford_postorder(vdata, root, no_of_nodes); + + /* Step 3: calculate real coordinates based on X offsets */ + igraph_i_layout_reingold_tilford_calc_coords(vdata, res, root, no_of_nodes, vdata[root].offset); + + igraph_dqueue_destroy(&q); + igraph_adjlist_destroy(&allneis); + igraph_free(vdata); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_PROGRESS("Reingold-Tilford tree layout", 100.0, NULL); + + return 0; +} + +static int igraph_i_layout_reingold_tilford_calc_coords( + struct igraph_i_reingold_tilford_vertex *vdata, + igraph_matrix_t *res, long int node, + long int vcount, igraph_real_t xpos) { + long int i; + MATRIX(*res, node, 0) = xpos; + for (i = 0; i < vcount; i++) { + if (i == node) { + continue; + } + if (vdata[i].parent == node) { + igraph_i_layout_reingold_tilford_calc_coords(vdata, res, i, vcount, + xpos + vdata[i].offset); + } + } + return 0; +} + +static int igraph_i_layout_reingold_tilford_postorder( + struct igraph_i_reingold_tilford_vertex *vdata, + long int node, long int vcount) { + long int i, j, childcount, leftroot, leftrootidx; + igraph_real_t avg; + + /* printf("Starting visiting node %d\n", node); */ + + /* Check whether this node is a leaf node */ + childcount = 0; + for (i = 0; i < vcount; i++) { + if (i == node) { + continue; + } + if (vdata[i].parent == node) { + /* Node i is a child, so visit it recursively */ + childcount++; + igraph_i_layout_reingold_tilford_postorder(vdata, i, vcount); + } + } + + if (childcount == 0) { + return 0; + } + + /* Here we can assume that all of the subtrees have been placed and their + * left and right contours are calculated. Let's place them next to each + * other as close as we can. + * We will take each subtree in an arbitrary order. The root of the + * first one will be placed at offset 0, the next ones will be placed + * as close to each other as possible. leftroot stores the root of the + * rightmost subtree of the already placed subtrees - its right contour + * will be checked against the left contour of the next subtree */ + leftroot = leftrootidx = -1; + avg = 0.0; + /*printf("Visited node %d and arranged its subtrees\n", node);*/ + for (i = 0, j = 0; i < vcount; i++) { + if (i == node) { + continue; + } + if (vdata[i].parent == node) { + /*printf(" Placing child %d on level %d\n", i, vdata[i].level);*/ + if (leftroot >= 0) { + /* Now we will follow the right contour of leftroot and the + * left contour of the subtree rooted at i */ + long lnode, rnode; + igraph_real_t loffset, roffset, minsep, rootsep; + lnode = leftroot; rnode = i; + minsep = 1; + rootsep = vdata[leftroot].offset + minsep; + loffset = 0; roffset = minsep; + /*printf(" Contour: [%d, %d], offsets: [%lf, %lf], rootsep: %lf\n", + lnode, rnode, loffset, roffset, rootsep);*/ + while ((lnode >= 0) && (rnode >= 0)) { + /* Step to the next level on the right contour of the left subtree */ + if (vdata[lnode].right_contour >= 0) { + loffset += vdata[lnode].offset_follow_rc; + lnode = vdata[lnode].right_contour; + } else { + /* Left subtree ended there. The right contour of the left subtree + * will continue to the next step on the right subtree. */ + if (vdata[rnode].left_contour >= 0) { + /*printf(" Left subtree ended, continuing left subtree's left and right contour on right subtree (node %ld)\n", vdata[rnode].left_contour);*/ + vdata[lnode].left_contour = vdata[rnode].left_contour; + vdata[lnode].right_contour = vdata[rnode].left_contour; + vdata[lnode].offset_follow_lc = vdata[lnode].offset_follow_rc = + (roffset - loffset) + vdata[rnode].offset_follow_lc; + /*printf(" vdata[lnode].offset_follow_* = %.4f\n", vdata[lnode].offset_follow_lc);*/ + } + lnode = -1; + } + /* Step to the next level on the left contour of the right subtree */ + if (vdata[rnode].left_contour >= 0) { + roffset += vdata[rnode].offset_follow_lc; + rnode = vdata[rnode].left_contour; + } else { + /* Right subtree ended here. The left contour of the right + * subtree will continue to the next step on the left subtree. + * Note that lnode has already been advanced here */ + if (lnode >= 0) { + /*printf(" Right subtree ended, continuing right subtree's left and right contour on left subtree (node %ld)\n", lnode);*/ + vdata[rnode].left_contour = lnode; + vdata[rnode].right_contour = lnode; + vdata[rnode].offset_follow_lc = vdata[rnode].offset_follow_rc = + (loffset - roffset); /* loffset has also been increased earlier */ + /*printf(" vdata[rnode].offset_follow_* = %.4f\n", vdata[rnode].offset_follow_lc);*/ + } + rnode = -1; + } + /*printf(" Contour: [%d, %d], offsets: [%lf, %lf], rootsep: %lf\n", + lnode, rnode, loffset, roffset, rootsep);*/ + + /* Push subtrees away if necessary */ + if ((lnode >= 0) && (rnode >= 0) && (roffset - loffset < minsep)) { + /*printf(" Pushing right subtree away by %lf\n", minsep-roffset+loffset);*/ + rootsep += minsep - roffset + loffset; + roffset = loffset + minsep; + } + } + + /*printf(" Offset of subtree with root node %d will be %lf\n", i, rootsep);*/ + vdata[i].offset = rootsep; + vdata[node].right_contour = i; + vdata[node].offset_follow_rc = rootsep; + avg = (avg * j) / (j + 1) + rootsep / (j + 1); + leftrootidx = j; + leftroot = i; + } else { + leftrootidx = j; + leftroot = i; + vdata[node].left_contour = i; + vdata[node].right_contour = i; + vdata[node].offset_follow_lc = 0.0; + vdata[node].offset_follow_rc = 0.0; + avg = vdata[i].offset; + } + j++; + } + } + /*printf("Shifting node to be centered above children. Shift amount: %lf\n", avg);*/ + vdata[node].offset_follow_lc -= avg; + vdata[node].offset_follow_rc -= avg; + for (i = 0, j = 0; i < vcount; i++) { + if (i == node) { + continue; + } + if (vdata[i].parent == node) { + vdata[i].offset -= avg; + } + } + + return 0; +} + +/** + * \function igraph_layout_reingold_tilford + * \brief Reingold-Tilford layout for tree graphs + * + * + * Arranges the nodes in a tree where the given node is used as the root. + * The tree is directed downwards and the parents are centered above its + * children. For the exact algorithm, see: + * + * + * Reingold, E and Tilford, J: Tidier drawing of trees. + * IEEE Trans. Softw. Eng., SE-7(2):223--228, 1981 + * + * + * If the given graph is not a tree, a breadth-first search is executed + * first to obtain a possible spanning tree. + * + * \param graph The graph object. + * \param res The result, the coordinates in a matrix. The parameter + * should point to an initialized matrix object and will be resized. + * \param mode Specifies which edges to consider when building the tree. + * If it is \c IGRAPH_OUT then only the outgoing, if it is \c IGRAPH_IN + * then only the incoming edges of a parent are considered. If it is + * \c IGRAPH_ALL then all edges are used (this was the behavior in + * igraph 0.5 and before). This parameter also influences how the root + * vertices are calculated, if they are not given. See the \p roots parameter. + * \param roots The index of the root vertex or root vertices. + * If this is a non-empty vector then the supplied vertex ids are used + * as the roots of the trees (or a single tree if the graph is connected). + * If it is a null pointer of a pointer to an empty vector, then the root + * vertices are automatically calculated based on topological sorting, + * performed with the opposite mode than the \p mode argument. + * After the vertices have been sorted, one is selected from each component. + * \param rootlevel This argument can be useful when drawing forests which are + * not trees (i.e. they are unconnected and have tree components). It specifies + * the level of the root vertices for every tree in the forest. It is only + * considered if not a null pointer and the \p roots argument is also given + * (and it is not a null pointer of an empty vector). + * \return Error code. + * + * Added in version 0.2. + * + * \sa \ref igraph_layout_reingold_tilford_circular(). + * + * \example examples/simple/igraph_layout_reingold_tilford.c + */ + +int igraph_layout_reingold_tilford(const igraph_t *graph, + igraph_matrix_t *res, + igraph_neimode_t mode, + const igraph_vector_t *roots, + const igraph_vector_t *rootlevel) { + + long int no_of_nodes_orig = igraph_vcount(graph); + long int no_of_nodes = no_of_nodes_orig; + long int real_root; + igraph_t extended; + const igraph_t *pextended = graph; + igraph_vector_t myroots; + const igraph_vector_t *proots = roots; + igraph_neimode_t mode2; + long int i; + igraph_vector_t newedges; + + /* TODO: possible speedup could be achieved if we use a table for storing + * the children of each node in the tree. (Now the implementation uses a + * single array containing the parent of each node and a node's children + * are determined by looking for other nodes that have this node as parent) + */ + + /* at various steps it might be necessary to add edges to the graph */ + IGRAPH_VECTOR_INIT_FINALLY(&newedges, 0); + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if ( (!roots || igraph_vector_size(roots) == 0) && + rootlevel && igraph_vector_size(rootlevel) != 0 ) { + IGRAPH_WARNING("Reingold-Tilford layout: 'rootlevel' ignored"); + } + + /* ----------------------------------------------------------------------- */ + /* If root vertices are not given, then do a topological sort and take + the last element from every component for directed graphs and mode == out, + or the first element from every component for directed graphs and mode == + in,or select the vertex with the maximum degree from each component for + undirected graphs */ + + if (!roots || igraph_vector_size(roots) == 0) { + + igraph_vector_t order, membership; + igraph_integer_t no_comps; + long int i, noseen = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&myroots, 0); + IGRAPH_VECTOR_INIT_FINALLY(&order, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&membership, no_of_nodes); + + if (mode != IGRAPH_ALL) { + /* look for roots by swimming against the stream */ + mode2 = (mode == IGRAPH_IN) ? IGRAPH_OUT : IGRAPH_IN; + + IGRAPH_CHECK(igraph_topological_sorting(graph, &order, mode2)); + IGRAPH_CHECK(igraph_clusters(graph, &membership, /*csize=*/ 0, + &no_comps, IGRAPH_WEAK)); + } else { + IGRAPH_CHECK(igraph_sort_vertex_ids_by_degree(graph, &order, + igraph_vss_all(), IGRAPH_ALL, 0, IGRAPH_ASCENDING, 0)); + IGRAPH_CHECK(igraph_clusters(graph, &membership, /*csize=*/ 0, + &no_comps, IGRAPH_WEAK)); + } + + IGRAPH_CHECK(igraph_vector_resize(&myroots, no_comps)); + + /* go backwards and fill the roots vector with indices [1, no_of_nodes] + The index 0 is used to signal this root has not been found yet: + all indices are then decreased by one to [0, no_of_nodes - 1] */ + igraph_vector_null(&myroots); + proots = &myroots; + for (i = no_of_nodes - 1; noseen < no_comps && i >= 0; i--) { + long int v = (long int) VECTOR(order)[i]; + long int mem = (long int) VECTOR(membership)[v]; + if (VECTOR(myroots)[mem] == 0) { + noseen += 1; + VECTOR(myroots)[mem] = v + 1; + } + } + for (i = 0; i < no_comps; i++) { + VECTOR(myroots)[i] -= 1; + } + + igraph_vector_destroy(&membership); + igraph_vector_destroy(&order); + IGRAPH_FINALLY_CLEAN(2); + + } else if (rootlevel && igraph_vector_size(rootlevel) > 0 && + igraph_vector_size(roots) > 1) { + + /* ----------------------------------------------------------------------- */ + /* Many roots were given to us, check 'rootlevel' */ + + long int plus_levels = 0; + long int i; + + if (igraph_vector_size(roots) != igraph_vector_size(rootlevel)) { + IGRAPH_ERROR("Reingold-Tilford: 'roots' and 'rootlevel' lengths differ", + IGRAPH_EINVAL); + } + + /* count the rootlevels that are not zero */ + for (i = 0; i < igraph_vector_size(roots); i++) { + plus_levels += VECTOR(*rootlevel)[i]; + } + + /* make copy of graph, add vertices/edges */ + if (plus_levels != 0) { + long int edgeptr = 0; + + pextended = &extended; + IGRAPH_CHECK(igraph_copy(&extended, graph)); + IGRAPH_FINALLY(igraph_destroy, &extended); + IGRAPH_CHECK(igraph_add_vertices(&extended, + (igraph_integer_t) plus_levels, 0)); + + igraph_vector_resize(&newedges, plus_levels * 2); + + for (i = 0; i < igraph_vector_size(roots); i++) { + long int rl = (long int) VECTOR(*rootlevel)[i]; + long int rn = (long int) VECTOR(*roots)[i]; + long int j; + + /* zero-level roots don't get anything special */ + if (rl == 0) { + continue; + } + + /* for each nonzero-level root, add vertices + and edges at all levels [1, 2, .., rl] + piercing through the graph. If mode=="in" + they pierce the other way */ + if (mode != IGRAPH_IN) { + VECTOR(newedges)[edgeptr++] = no_of_nodes; + VECTOR(newedges)[edgeptr++] = rn; + for (j = 0; j < rl - 1; j++) { + VECTOR(newedges)[edgeptr++] = no_of_nodes + 1; + VECTOR(newedges)[edgeptr++] = no_of_nodes; + no_of_nodes++; + } + } else { + VECTOR(newedges)[edgeptr++] = rn; + VECTOR(newedges)[edgeptr++] = no_of_nodes; + for (j = 0; j < rl - 1; j++) { + VECTOR(newedges)[edgeptr++] = no_of_nodes; + VECTOR(newedges)[edgeptr++] = no_of_nodes + 1; + no_of_nodes++; + } + } + + /* move on to the next root */ + VECTOR(*roots)[i] = no_of_nodes++; + } + + /* actually add the edges to the graph */ + IGRAPH_CHECK(igraph_add_edges(&extended, &newedges, 0)); + } + } + + /* We have root vertices now. If one or more nonzero-level roots were + chosen by the user, we have copied the graph and added a few vertices + and (directed) edges to connect those floating roots to nonfloating, + zero-level equivalent roots. + + Below, the function + + igraph_i_layout_reingold_tilford(pextended, res, mode, real_root) + + calculates the actual rt coordinates of the graph. However, for + simplicity that function requires a connected graph and a single root. + For directed graphs, it needs not be strongly connected, however all + nodes must be reachable from the root following the stream (i.e. the + root must be a "mother vertex"). + + So before we call that function we have to make sure the (copied) graph + satisfies that condition. That requires: + 1. if there is more than one root, defining a single real_root + 2. if a real_root is defined, adding edges to connect all roots to it + 3. ensure real_root is mother of the whole graph. If it is not, + add shortcut edges from real_root to any disconnected node for now. + + NOTE: 3. could be done better, e.g. by topological sorting of some kind. + But for now it's ok like this. + */ + /* if there is only one root, no need for real_root */ + if (igraph_vector_size(proots) == 1) { + real_root = (long int) VECTOR(*proots)[0]; + if (real_root < 0 || real_root >= no_of_nodes) { + IGRAPH_ERROR("invalid vertex id", IGRAPH_EINVVID); + } + + /* else, we need to make real_root */ + } else { + long int no_of_newedges; + + /* Make copy of the graph unless it exists already */ + if (pextended == graph) { + pextended = &extended; + IGRAPH_CHECK(igraph_copy(&extended, graph)); + IGRAPH_FINALLY(igraph_destroy, &extended); + } + + /* add real_root to the vertices */ + real_root = no_of_nodes; + IGRAPH_CHECK(igraph_add_vertices(&extended, 1, 0)); + no_of_nodes++; + + /* add edges from the roots to real_root */ + no_of_newedges = igraph_vector_size(proots); + igraph_vector_resize(&newedges, no_of_newedges * 2); + for (i = 0; i < no_of_newedges; i++) { + VECTOR(newedges)[2 * i] = no_of_nodes - 1; + VECTOR(newedges)[2 * i + 1] = VECTOR(*proots)[i]; + } + + IGRAPH_CHECK(igraph_add_edges(&extended, &newedges, 0)); + } + + /* prepare edges to unreachable parts of the graph */ + IGRAPH_CHECK(igraph_i_layout_reingold_tilford_unreachable(pextended, mode, real_root, no_of_nodes, &newedges)); + + if (igraph_vector_size(&newedges) != 0) { + /* Make copy of the graph unless it exists already */ + if (pextended == graph) { + pextended = &extended; + IGRAPH_CHECK(igraph_copy(&extended, graph)); + IGRAPH_FINALLY(igraph_destroy, &extended); + } + + IGRAPH_CHECK(igraph_add_edges(&extended, &newedges, 0)); + } + igraph_vector_destroy(&newedges); + IGRAPH_FINALLY_CLEAN(1); + + /* ----------------------------------------------------------------------- */ + /* Layout */ + IGRAPH_CHECK(igraph_i_layout_reingold_tilford(pextended, res, mode, real_root)); + + /* Remove the new vertices from the layout */ + if (no_of_nodes != no_of_nodes_orig) { + if (no_of_nodes - 1 == no_of_nodes_orig) { + IGRAPH_CHECK(igraph_matrix_remove_row(res, no_of_nodes_orig)); + } else { + igraph_matrix_t tmp; + long int i; + IGRAPH_MATRIX_INIT_FINALLY(&tmp, no_of_nodes_orig, 2); + for (i = 0; i < no_of_nodes_orig; i++) { + MATRIX(tmp, i, 0) = MATRIX(*res, i, 0); + MATRIX(tmp, i, 1) = MATRIX(*res, i, 1); + } + IGRAPH_CHECK(igraph_matrix_update(res, &tmp)); + igraph_matrix_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + } + } + + if (pextended != graph) { + igraph_destroy(&extended); + IGRAPH_FINALLY_CLEAN(1); + } + + /* Remove the roots vector if it was created by us */ + if (proots != roots) { + igraph_vector_destroy(&myroots); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_layout_reingold_tilford_circular + * \brief Circular Reingold-Tilford layout for trees + * + * + * This layout is almost the same as \ref igraph_layout_reingold_tilford(), but + * the tree is drawn in a circular way, with the root vertex in the center. + * + * \param graph The graph object. + * \param res The result, the coordinates in a matrix. The parameter + * should point to an initialized matrix object and will be resized. + * \param mode Specifies which edges to consider when building the tree. + * If it is \c IGRAPH_OUT then only the outgoing, if it is \c IGRAPH_IN + * then only the incoming edges of a parent are considered. If it is + * \c IGRAPH_ALL then all edges are used (this was the behavior in + * igraph 0.5 and before). This parameter also influences how the root + * vertices are calculated, if they are not given. See the \p roots parameter. + * \param roots The index of the root vertex or root vertices. + * If this is a non-empty vector then the supplied vertex ids are used + * as the roots of the trees (or a single tree if the graph is connected). + * If it is a null pointer of a pointer to an empty vector, then the root + * vertices are automatically calculated based on topological sorting, + * performed with the opposite mode than the \p mode argument. + * After the vertices have been sorted, one is selected from each component. + * \param rootlevel This argument can be useful when drawing forests which are + * not trees (i.e. they are unconnected and have tree components). It specifies + * the level of the root vertices for every tree in the forest. It is only + * considered if not a null pointer and the \p roots argument is also given + * (and it is not a null pointer of an empty vector). Note that if you supply + * a null pointer here and the graph has multiple components, all of the root + * vertices will be mapped to the origin of the coordinate system, which does + * not really make sense. + * \return Error code. + * + * \sa \ref igraph_layout_reingold_tilford(). + */ + +int igraph_layout_reingold_tilford_circular(const igraph_t *graph, + igraph_matrix_t *res, + igraph_neimode_t mode, + const igraph_vector_t *roots, + const igraph_vector_t *rootlevel) { + + long int no_of_nodes = igraph_vcount(graph); + long int i; + igraph_real_t ratio = 2 * M_PI * (no_of_nodes - 1.0) / no_of_nodes; + igraph_real_t minx, maxx; + + IGRAPH_CHECK(igraph_layout_reingold_tilford(graph, res, mode, roots, rootlevel)); + + if (no_of_nodes == 0) { + return 0; + } + + minx = maxx = MATRIX(*res, 0, 0); + for (i = 1; i < no_of_nodes; i++) { + if (MATRIX(*res, i, 0) > maxx) { + maxx = MATRIX(*res, i, 0); + } + if (MATRIX(*res, i, 0) < minx) { + minx = MATRIX(*res, i, 0); + } + } + if (maxx > minx) { + ratio /= (maxx - minx); + } + for (i = 0; i < no_of_nodes; i++) { + igraph_real_t phi = (MATRIX(*res, i, 0) - minx) * ratio; + igraph_real_t r = MATRIX(*res, i, 1); + MATRIX(*res, i, 0) = r * cos(phi); + MATRIX(*res, i, 1) = r * sin(phi); + } + + return 0; +} + +#define COULOMBS_CONSTANT 8987500000.0 + + +static igraph_real_t igraph_i_distance_between( + const igraph_matrix_t *c, + long int a, long int b); + +static int igraph_i_determine_electric_axal_forces( + const igraph_matrix_t *pos, + igraph_real_t *x, + igraph_real_t *y, + igraph_real_t directed_force, + igraph_real_t distance, + long int other_node, + long int this_node); + +static int igraph_i_apply_electrical_force( + const igraph_matrix_t *pos, + igraph_vector_t *pending_forces_x, + igraph_vector_t *pending_forces_y, + long int other_node, long int this_node, + igraph_real_t node_charge, + igraph_real_t distance); + +static int igraph_i_determine_spring_axal_forces( + const igraph_matrix_t *pos, + igraph_real_t *x, igraph_real_t *y, + igraph_real_t directed_force, + igraph_real_t distance, + int spring_length, + long int other_node, + long int this_node); + +static int igraph_i_apply_spring_force( + const igraph_matrix_t *pos, + igraph_vector_t *pending_forces_x, + igraph_vector_t *pending_forces_y, + long int other_node, + long int this_node, int spring_length, + igraph_real_t spring_constant); + +static int igraph_i_move_nodes( + igraph_matrix_t *pos, + const igraph_vector_t *pending_forces_x, + const igraph_vector_t *pending_forces_y, + igraph_real_t node_mass, + igraph_real_t max_sa_movement); + +static igraph_real_t igraph_i_distance_between( + const igraph_matrix_t *c, + long int a, long int b) { + igraph_real_t diffx = MATRIX(*c, a, 0) - MATRIX(*c, b, 0); + igraph_real_t diffy = MATRIX(*c, a, 1) - MATRIX(*c, b, 1); + return sqrt( diffx * diffx + diffy * diffy ); +} + +static int igraph_i_determine_electric_axal_forces(const igraph_matrix_t *pos, + igraph_real_t *x, + igraph_real_t *y, + igraph_real_t directed_force, + igraph_real_t distance, + long int other_node, + long int this_node) { + + // We know what the directed force is. We now need to translate it + // into the appropriate x and y components. + // First, assume: + // other_node + // /| + // directed_force / | + // / | y + // /______| + // this_node x + // + // other_node.x > this_node.x + // other_node.y > this_node.y + // the force will be on this_node away from other_node + + // the proportion (distance/y_distance) is equal to the proportion + // (directed_force/y_force), as the two triangles are similar. + // therefore, the magnitude of y_force = (directed_force*y_distance)/distance + // the sign of y_force is negative, away from other_node + + igraph_real_t x_distance, y_distance; + y_distance = MATRIX(*pos, other_node, 1) - MATRIX(*pos, this_node, 1); + if (y_distance < 0) { + y_distance = -y_distance; + } + *y = -1 * ((directed_force * y_distance) / distance); + + // the x component works in exactly the same way. + x_distance = MATRIX(*pos, other_node, 0) - MATRIX(*pos, this_node, 0); + if (x_distance < 0) { + x_distance = -x_distance; + } + *x = -1 * ((directed_force * x_distance) / distance); + + // Now we need to reverse the polarity of our answers based on the falsness + // of our assumptions. + if (MATRIX(*pos, other_node, 0) < MATRIX(*pos, this_node, 0)) { + *x = *x * -1; + } + if (MATRIX(*pos, other_node, 1) < MATRIX(*pos, this_node, 1)) { + *y = *y * -1; + } + + return 0; +} + +static int igraph_i_apply_electrical_force( + const igraph_matrix_t *pos, + igraph_vector_t *pending_forces_x, + igraph_vector_t *pending_forces_y, + long int other_node, long int this_node, + igraph_real_t node_charge, + igraph_real_t distance) { + + igraph_real_t directed_force = COULOMBS_CONSTANT * + ((node_charge * node_charge) / (distance * distance)); + + igraph_real_t x_force, y_force; + igraph_i_determine_electric_axal_forces(pos, &x_force, &y_force, + directed_force, distance, + other_node, this_node); + + VECTOR(*pending_forces_x)[this_node] += x_force; + VECTOR(*pending_forces_y)[this_node] += y_force; + VECTOR(*pending_forces_x)[other_node] -= x_force; + VECTOR(*pending_forces_y)[other_node] -= y_force; + + return 0; +} + +static int igraph_i_determine_spring_axal_forces( + const igraph_matrix_t *pos, + igraph_real_t *x, igraph_real_t *y, + igraph_real_t directed_force, + igraph_real_t distance, + int spring_length, + long int other_node, long int this_node) { + + // if the spring is just the right size, the forces will be 0, so we can + // skip the computation. + // + // if the spring is too long, our forces will be identical to those computed + // by determine_electrical_axal_forces() (this_node will be pulled toward + // other_node). + // + // if the spring is too short, our forces will be the opposite of those + // computed by determine_electrical_axal_forces() (this_node will be pushed + // away from other_node) + // + // finally, since both nodes are movable, only one-half of the total force + // should be applied to each node, so half the forces for our answer. + + if (distance == spring_length) { + *x = 0.0; + *y = 0.0; + } else { + igraph_i_determine_electric_axal_forces(pos, x, y, directed_force, distance, + other_node, this_node); + if (distance < spring_length) { + *x = -1 * *x; + *y = -1 * *y; + } + *x = 0.5 * *x; + *y = 0.5 * *y; + } + + return 0; +} + +static int igraph_i_apply_spring_force( + const igraph_matrix_t *pos, + igraph_vector_t *pending_forces_x, + igraph_vector_t *pending_forces_y, + long int other_node, + long int this_node, int spring_length, + igraph_real_t spring_constant) { + + // determined using Hooke's Law: + // force = -kx + // where: + // k = spring constant + // x = displacement from ideal length in meters + + igraph_real_t distance, displacement, directed_force, x_force, y_force; + distance = igraph_i_distance_between(pos, other_node, this_node); + // let's protect ourselves from division by zero by ignoring two nodes that + // happen to be in the same place. Since we separate all nodes before we + // work on any of them, this will only happen in extremely rare circumstances, + // and when it does, electrical force will probably push one or both of them + // one way or another anyway. + if (distance == 0.0) { + return 0; + } + + displacement = distance - spring_length; + if (displacement < 0) { + displacement = -displacement; + } + directed_force = -1 * spring_constant * displacement; + // remember, this is force directed away from the spring; + // a negative number is back towards the spring (or, in our case, back towards + // the other node) + + // get the force that should be applied to >this< node + igraph_i_determine_spring_axal_forces(pos, &x_force, &y_force, + directed_force, distance, spring_length, + other_node, this_node); + + VECTOR(*pending_forces_x)[this_node] += x_force; + VECTOR(*pending_forces_y)[this_node] += y_force; + VECTOR(*pending_forces_x)[other_node] -= x_force; + VECTOR(*pending_forces_y)[other_node] -= y_force; + + return 0; +} + +static int igraph_i_move_nodes( + igraph_matrix_t *pos, + const igraph_vector_t *pending_forces_x, + const igraph_vector_t *pending_forces_y, + igraph_real_t node_mass, + igraph_real_t max_sa_movement) { + + // Since each iteration is isolated, time is constant at 1. + // Therefore: + // Force effects acceleration. + // acceleration (d(velocity)/time) = velocity + // velocity (d(displacement)/time) = displacement + // displacement = acceleration + + // determined using Newton's second law: + // sum(F) = ma + // therefore: + // acceleration = force / mass + // velocity = force / mass + // displacement = force / mass + + long int this_node, no_of_nodes = igraph_vector_size(pending_forces_x); + + for (this_node = 0; this_node < no_of_nodes; this_node++) { + + igraph_real_t x_movement, y_movement; + + x_movement = VECTOR(*pending_forces_x)[this_node] / node_mass; + if (x_movement > max_sa_movement) { + x_movement = max_sa_movement; + } else if (x_movement < -max_sa_movement) { + x_movement = -max_sa_movement; + } + + y_movement = VECTOR(*pending_forces_y)[this_node] / node_mass; + if (y_movement > max_sa_movement) { + y_movement = max_sa_movement; + } else if (y_movement < -max_sa_movement) { + y_movement = -max_sa_movement; + } + + MATRIX(*pos, this_node, 0) += x_movement; + MATRIX(*pos, this_node, 1) += y_movement; + + } + return 0; +} + +/** + * \function igraph_layout_graphopt + * \brief Optimizes vertex layout via the graphopt algorithm. + * + * + * This is a port of the graphopt layout algorithm by Michael Schmuhl. + * graphopt version 0.4.1 was rewritten in C and the support for + * layers was removed (might be added later) and a code was a bit + * reorganized to avoid some unnecessary steps is the node charge (see below) + * is zero. + * + * + * graphopt uses physical analogies for defining attracting and repelling + * forces among the vertices and then the physical system is simulated + * until it reaches an equilibrium. (There is no simulated annealing or + * anything like that, so a stable fixed point is not guaranteed.) + * + * + * See also http://www.schmuhl.org/graphopt/ for the original graphopt. + * \param graph The input graph. + * \param res Pointer to an initialized matrix, the result will be stored here + * and its initial contents is used the starting point of the simulation + * if the \p use_seed argument is true. Note that in this case the + * matrix should have the proper size, otherwise a warning is issued and + * the supplied values are ignored. If no starting positions are given + * (or they are invalid) then a random staring position is used. + * The matrix will be resized if needed. + * \param niter Integer constant, the number of iterations to perform. + * Should be a couple of hundred in general. If you have a large graph + * then you might want to only do a few iterations and then check the + * result. If it is not good enough you can feed it in again in + * the \p res argument. The original graphopt default if 500. + * \param node_charge The charge of the vertices, used to calculate electric + * repulsion. The original graphopt default is 0.001. + * \param node_mass The mass of the vertices, used for the spring forces. + * The original graphopt defaults to 30. + * \param spring_length The length of the springs, an integer number. + * The original graphopt defaults to zero. + * \param spring_constant The spring constant, the original graphopt defaults + * to one. + * \param max_sa_movement Real constant, it gives the maximum amount of movement + * allowed in a single step along a single axis. The original graphopt + * default is 5. + * \param use_seed Logical scalar, whether to use the positions in \p res as + * a starting configuration. See also \p res above. + * \return Error code. + * + * Time complexity: O(n (|V|^2+|E|) ), n is the number of iterations, + * |V| is the number of vertices, |E| the number + * of edges. If \p node_charge is zero then it is only O(n|E|). + */ + +int igraph_layout_graphopt(const igraph_t *graph, igraph_matrix_t *res, + igraph_integer_t niter, + igraph_real_t node_charge, igraph_real_t node_mass, + igraph_real_t spring_length, + igraph_real_t spring_constant, + igraph_real_t max_sa_movement, + igraph_bool_t use_seed) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + int my_spring_length = (int) spring_length; + igraph_vector_t pending_forces_x, pending_forces_y; + /* Set a flag to calculate (or not) the electrical forces that the nodes */ + /* apply on each other based on if both node types' charges are zero. */ + igraph_bool_t apply_electric_charges = (node_charge != 0); + + long int this_node, other_node, edge; + igraph_real_t distance; + long int i; + + IGRAPH_VECTOR_INIT_FINALLY(&pending_forces_x, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&pending_forces_y, no_of_nodes); + + if (use_seed) { + if (igraph_matrix_nrow(res) != no_of_nodes || + igraph_matrix_ncol(res) != 2) { + IGRAPH_WARNING("Invalid size for initial matrix, starting from random layout"); + IGRAPH_CHECK(igraph_layout_random(graph, res)); + } + } else { + IGRAPH_CHECK(igraph_layout_random(graph, res)); + } + + IGRAPH_PROGRESS("Graphopt layout", 0, NULL); + for (i = niter; i > 0; i--) { + /* Report progress in approx. every 100th step */ + if (i % 10 == 0) { + IGRAPH_PROGRESS("Graphopt layout", 100.0 - 100.0 * i / niter, NULL); + } + + /* Clear pending forces on all nodes */ + igraph_vector_null(&pending_forces_x); + igraph_vector_null(&pending_forces_y); + + // Apply electrical force applied by all other nodes + if (apply_electric_charges) { + // Iterate through all nodes + for (this_node = 0; this_node < no_of_nodes; this_node++) { + IGRAPH_ALLOW_INTERRUPTION(); + for (other_node = this_node + 1; + other_node < no_of_nodes; + other_node++) { + distance = igraph_i_distance_between(res, this_node, other_node); + // let's protect ourselves from division by zero by ignoring + // two nodes that happen to be in the same place. Since we + // separate all nodes before we work on any of them, this + // will only happen in extremely rare circumstances, and when + // it does, springs will probably pull them apart anyway. + // also, if we are more than 50 away, the electric force + // will be negligible. + // ***** may not always be desirable **** + if ((distance != 0.0) && (distance < 500.0)) { + // if (distance != 0.0) { + // Apply electrical force from node(counter2) on + // node(counter) + igraph_i_apply_electrical_force(res, &pending_forces_x, + &pending_forces_y, + other_node, this_node, + node_charge, + distance); + } + } + } + } + + // Apply force from springs + for (edge = 0; edge < no_of_edges; edge++) { + long int tthis_node = IGRAPH_FROM(graph, edge); + long int oother_node = IGRAPH_TO(graph, edge); + // Apply spring force on both nodes + igraph_i_apply_spring_force(res, &pending_forces_x, &pending_forces_y, + oother_node, tthis_node, my_spring_length, + spring_constant); + } + + // Effect the movement of the nodes based on all pending forces + igraph_i_move_nodes(res, &pending_forces_x, &pending_forces_y, node_mass, + max_sa_movement); + } + IGRAPH_PROGRESS("Graphopt layout", 100, NULL); + + igraph_vector_destroy(&pending_forces_y); + igraph_vector_destroy(&pending_forces_x); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/* not 'static', used in tests */ +int igraph_i_layout_merge_dla(igraph_i_layout_mergegrid_t *grid, + long int actg, igraph_real_t *x, igraph_real_t *y, igraph_real_t r, + igraph_real_t cx, igraph_real_t cy, igraph_real_t startr, + igraph_real_t killr); + +/* TODO: not 'static' because used in tests */ +int igraph_i_layout_sphere_2d(igraph_matrix_t *coords, igraph_real_t *x, + igraph_real_t *y, igraph_real_t *r); +int igraph_i_layout_sphere_3d(igraph_matrix_t *coords, igraph_real_t *x, + igraph_real_t *y, igraph_real_t *z, + igraph_real_t *r); + +/** + * \function igraph_layout_merge_dla + * \brief Merge multiple layouts by using a DLA algorithm + * + * + * First each layout is covered by a circle. Then the layout of the + * largest graph is placed at the origin. Then the other layouts are + * placed by the DLA algorithm, larger ones first and smaller ones + * last. + * \param thegraphs Pointer vector containing the graph object of + * which the layouts will be merged. + * \param coords Pointer vector containing matrix objects with the 2d + * layouts of the graphs in \p thegraphs. + * \param res Pointer to an initialized matrix object, the result will + * be stored here. It will be resized if needed. + * \return Error code. + * + * Added in version 0.2. This function is experimental. + * + * + * Time complexity: TODO. + */ + +int igraph_layout_merge_dla(igraph_vector_ptr_t *thegraphs, + igraph_vector_ptr_t *coords, + igraph_matrix_t *res) { + long int graphs = igraph_vector_ptr_size(coords); + igraph_vector_t sizes; + igraph_vector_t x, y, r; + igraph_vector_t nx, ny, nr; + long int allnodes = 0; + long int i, j; + long int actg; + igraph_i_layout_mergegrid_t grid; + long int jpos = 0; + igraph_real_t minx, maxx, miny, maxy; + igraph_real_t area = 0; + igraph_real_t maxr = 0; + long int respos; + + /* Graphs are currently not used, only the coordinates */ + IGRAPH_UNUSED(thegraphs); + + IGRAPH_VECTOR_INIT_FINALLY(&sizes, graphs); + IGRAPH_VECTOR_INIT_FINALLY(&x, graphs); + IGRAPH_VECTOR_INIT_FINALLY(&y, graphs); + IGRAPH_VECTOR_INIT_FINALLY(&r, graphs); + IGRAPH_VECTOR_INIT_FINALLY(&nx, graphs); + IGRAPH_VECTOR_INIT_FINALLY(&ny, graphs); + IGRAPH_VECTOR_INIT_FINALLY(&nr, graphs); + + RNG_BEGIN(); + + for (i = 0; i < igraph_vector_ptr_size(coords); i++) { + igraph_matrix_t *mat = VECTOR(*coords)[i]; + long int size = igraph_matrix_nrow(mat); + + if (igraph_matrix_ncol(mat) != 2) { + IGRAPH_ERROR("igraph_layout_merge_dla works for 2D layouts only", + IGRAPH_EINVAL); + } + + IGRAPH_ALLOW_INTERRUPTION(); + allnodes += size; + VECTOR(sizes)[i] = size; + VECTOR(r)[i] = pow(size, .75); + area += VECTOR(r)[i] * VECTOR(r)[i]; + if (VECTOR(r)[i] > maxr) { + maxr = VECTOR(r)[i]; + } + + igraph_i_layout_sphere_2d(mat, + igraph_vector_e_ptr(&nx, i), + igraph_vector_e_ptr(&ny, i), + igraph_vector_e_ptr(&nr, i)); + + } + igraph_vector_order2(&sizes); /* largest first */ + + /* 0. create grid */ + minx = miny = -sqrt(5 * area); + maxx = maxy = sqrt(5 * area); + igraph_i_layout_mergegrid_init(&grid, minx, maxx, 200, + miny, maxy, 200); + IGRAPH_FINALLY(igraph_i_layout_mergegrid_destroy, &grid); + + /* fprintf(stderr, "Ok, starting DLA\n"); */ + + /* 1. place the largest */ + actg = (long int) VECTOR(sizes)[jpos++]; + igraph_i_layout_merge_place_sphere(&grid, 0, 0, VECTOR(r)[actg], actg); + + IGRAPH_PROGRESS("Merging layouts via DLA", 0.0, NULL); + while (jpos < graphs) { + IGRAPH_ALLOW_INTERRUPTION(); + /* fprintf(stderr, "comp: %li", jpos); */ + IGRAPH_PROGRESS("Merging layouts via DLA", (100.0 * jpos) / graphs, NULL); + + actg = (long int) VECTOR(sizes)[jpos++]; + /* 2. random walk, TODO: tune parameters */ + igraph_i_layout_merge_dla(&grid, actg, + igraph_vector_e_ptr(&x, actg), + igraph_vector_e_ptr(&y, actg), + VECTOR(r)[actg], 0, 0, + maxx, maxx + 5); + + /* 3. place sphere */ + igraph_i_layout_merge_place_sphere(&grid, VECTOR(x)[actg], VECTOR(y)[actg], + VECTOR(r)[actg], actg); + } + IGRAPH_PROGRESS("Merging layouts via DLA", 100.0, NULL); + + /* Create the result */ + IGRAPH_CHECK(igraph_matrix_resize(res, allnodes, 2)); + respos = 0; + for (i = 0; i < graphs; i++) { + long int size = igraph_matrix_nrow(VECTOR(*coords)[i]); + igraph_real_t xx = VECTOR(x)[i]; + igraph_real_t yy = VECTOR(y)[i]; + igraph_real_t rr = VECTOR(r)[i] / VECTOR(nr)[i]; + igraph_matrix_t *mat = VECTOR(*coords)[i]; + IGRAPH_ALLOW_INTERRUPTION(); + if (VECTOR(nr)[i] == 0) { + rr = 1; + } + for (j = 0; j < size; j++) { + MATRIX(*res, respos, 0) = rr * (MATRIX(*mat, j, 0) - VECTOR(nx)[i]); + MATRIX(*res, respos, 1) = rr * (MATRIX(*mat, j, 1) - VECTOR(ny)[i]); + MATRIX(*res, respos, 0) += xx; + MATRIX(*res, respos, 1) += yy; + ++respos; + } + } + + RNG_END(); + + igraph_i_layout_mergegrid_destroy(&grid); + igraph_vector_destroy(&sizes); + igraph_vector_destroy(&x); + igraph_vector_destroy(&y); + igraph_vector_destroy(&r); + igraph_vector_destroy(&nx); + igraph_vector_destroy(&ny); + igraph_vector_destroy(&nr); + IGRAPH_FINALLY_CLEAN(8); + return 0; +} + +int igraph_i_layout_sphere_2d(igraph_matrix_t *coords, + igraph_real_t *x, igraph_real_t *y, + igraph_real_t *r) { + long int nodes = igraph_matrix_nrow(coords); + long int i; + igraph_real_t xmin, xmax, ymin, ymax; + + xmin = xmax = MATRIX(*coords, 0, 0); + ymin = ymax = MATRIX(*coords, 0, 1); + for (i = 1; i < nodes; i++) { + + if (MATRIX(*coords, i, 0) < xmin) { + xmin = MATRIX(*coords, i, 0); + } else if (MATRIX(*coords, i, 0) > xmax) { + xmax = MATRIX(*coords, i, 0); + } + + if (MATRIX(*coords, i, 1) < ymin) { + ymin = MATRIX(*coords, i, 1); + } else if (MATRIX(*coords, i, 1) > ymax) { + ymax = MATRIX(*coords, i, 1); + } + + } + + *x = (xmin + xmax) / 2; + *y = (ymin + ymax) / 2; + *r = sqrt( (xmax - xmin) * (xmax - xmin) + (ymax - ymin) * (ymax - ymin) ) / 2; + + return 0; +} + +int igraph_i_layout_sphere_3d(igraph_matrix_t *coords, + igraph_real_t *x, igraph_real_t *y, + igraph_real_t *z, igraph_real_t *r) { + long int nodes = igraph_matrix_nrow(coords); + long int i; + igraph_real_t xmin, xmax, ymin, ymax, zmin, zmax; + + xmin = xmax = MATRIX(*coords, 0, 0); + ymin = ymax = MATRIX(*coords, 0, 1); + zmin = zmax = MATRIX(*coords, 0, 2); + for (i = 1; i < nodes; i++) { + + if (MATRIX(*coords, i, 0) < xmin) { + xmin = MATRIX(*coords, i, 0); + } else if (MATRIX(*coords, i, 0) > xmax) { + xmax = MATRIX(*coords, i, 0); + } + + if (MATRIX(*coords, i, 1) < ymin) { + ymin = MATRIX(*coords, i, 1); + } else if (MATRIX(*coords, i, 1) > ymax) { + ymax = MATRIX(*coords, i, 1); + } + + if (MATRIX(*coords, i, 2) < zmin) { + zmin = MATRIX(*coords, i, 2); + } else if (MATRIX(*coords, i, 2) > zmax) { + zmax = MATRIX(*coords, i, 2); + } + + } + + *x = (xmin + xmax) / 2; + *y = (ymin + ymax) / 2; + *z = (zmin + zmax) / 2; + *r = sqrt( (xmax - xmin) * (xmax - xmin) + (ymax - ymin) * (ymax - ymin) + + (zmax - zmin) * (zmax - zmin) ) / 2; + + return 0; +} + +#define DIST(x,y) (sqrt(pow((x)-cx,2)+pow((y)-cy,2))) + +int igraph_i_layout_merge_dla(igraph_i_layout_mergegrid_t *grid, + long int actg, igraph_real_t *x, igraph_real_t *y, igraph_real_t r, + igraph_real_t cx, igraph_real_t cy, igraph_real_t startr, + igraph_real_t killr) { + long int sp = -1; + igraph_real_t angle, len; + long int steps = 0; + + /* The graph is not used, only its coordinates */ + IGRAPH_UNUSED(actg); + + while (sp < 0) { + /* start particle */ + do { + steps++; + angle = RNG_UNIF(0, 2 * M_PI); + len = RNG_UNIF(.5 * startr, startr); + *x = cx + len * cos(angle); + *y = cy + len * sin(angle); + sp = igraph_i_layout_mergegrid_get_sphere(grid, *x, *y, r); + } while (sp >= 0); + + while (sp < 0 && DIST(*x, *y) < killr) { + igraph_real_t nx, ny; + steps++; + angle = RNG_UNIF(0, 2 * M_PI); + len = RNG_UNIF(0, startr / 100); + nx = *x + len * cos(angle); + ny = *y + len * sin(angle); + sp = igraph_i_layout_mergegrid_get_sphere(grid, nx, ny, r); + if (sp < 0) { + *x = nx; *y = ny; + } + } + } + + /* fprintf(stderr, "%li ", steps); */ + return 0; +} + +static int igraph_i_layout_mds_step(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra); + +static int igraph_i_layout_mds_single(const igraph_t* graph, igraph_matrix_t *res, + igraph_matrix_t *dist, long int dim); + +static int igraph_i_layout_mds_step(igraph_real_t *to, const igraph_real_t *from, + int n, void *extra) { + igraph_matrix_t* matrix = (igraph_matrix_t*)extra; + IGRAPH_UNUSED(n); + igraph_blas_dgemv_array(0, 1, matrix, from, 0, to); + return 0; +} + +/* MDS layout for a connected graph, with no error checking on the + * input parameters. The distance matrix will be modified in-place. */ +int igraph_i_layout_mds_single(const igraph_t* graph, igraph_matrix_t *res, + igraph_matrix_t *dist, long int dim) { + + long int no_of_nodes = igraph_vcount(graph); + long int nev = dim; + igraph_matrix_t vectors; + igraph_vector_t values, row_means; + igraph_real_t grand_mean; + long int i, j, k; + igraph_eigen_which_t which; + + /* Handle the trivial cases */ + if (no_of_nodes == 1) { + IGRAPH_CHECK(igraph_matrix_resize(res, 1, dim)); + igraph_matrix_fill(res, 0); + return IGRAPH_SUCCESS; + } + if (no_of_nodes == 2) { + IGRAPH_CHECK(igraph_matrix_resize(res, 2, dim)); + igraph_matrix_fill(res, 0); + for (j = 0; j < dim; j++) { + MATRIX(*res, 1, j) = 1; + } + return IGRAPH_SUCCESS; + } + + /* Initialize some stuff */ + IGRAPH_VECTOR_INIT_FINALLY(&values, no_of_nodes); + IGRAPH_CHECK(igraph_matrix_init(&vectors, no_of_nodes, dim)); + IGRAPH_FINALLY(igraph_matrix_destroy, &vectors); + + /* Take the square of the distance matrix */ + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < no_of_nodes; j++) { + MATRIX(*dist, i, j) *= MATRIX(*dist, i, j); + } + } + + /* Double centering of the distance matrix */ + IGRAPH_VECTOR_INIT_FINALLY(&row_means, no_of_nodes); + igraph_vector_fill(&values, 1.0 / no_of_nodes); + igraph_blas_dgemv(0, 1, dist, &values, 0, &row_means); + grand_mean = igraph_vector_sum(&row_means) / no_of_nodes; + igraph_matrix_add_constant(dist, grand_mean); + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < no_of_nodes; j++) { + MATRIX(*dist, i, j) -= VECTOR(row_means)[i] + VECTOR(row_means)[j]; + MATRIX(*dist, i, j) *= -0.5; + } + } + igraph_vector_destroy(&row_means); + IGRAPH_FINALLY_CLEAN(1); + + /* Calculate the top `dim` eigenvectors. */ + which.pos = IGRAPH_EIGEN_LA; + which.howmany = (int) nev; + IGRAPH_CHECK(igraph_eigen_matrix_symmetric(/*A=*/ 0, /*sA=*/ 0, + /*fun=*/ igraph_i_layout_mds_step, + /*n=*/ (int) no_of_nodes, /*extra=*/ dist, + /*algorithm=*/ IGRAPH_EIGEN_LAPACK, + &which, /*options=*/ 0, /*storage=*/ 0, + &values, &vectors)); + + /* Calculate and normalize the final coordinates */ + for (j = 0; j < nev; j++) { + VECTOR(values)[j] = sqrt(fabs(VECTOR(values)[j])); + } + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, dim)); + for (i = 0; i < no_of_nodes; i++) { + for (j = 0, k = nev - 1; j < nev; j++, k--) { + MATRIX(*res, i, k) = VECTOR(values)[j] * MATRIX(vectors, i, j); + } + } + + igraph_matrix_destroy(&vectors); + igraph_vector_destroy(&values); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_layout_mds + * \brief Place the vertices on a plane using multidimensional scaling. + * + * + * This layout requires a distance matrix, where the intersection of + * row i and column j specifies the desired distance between vertex i + * and vertex j. The algorithm will try to place the vertices in a + * space having a given number of dimensions in a way that approximates + * the distance relations prescribed in the distance matrix. igraph + * uses the classical multidimensional scaling by Torgerson; for more + * details, see Cox & Cox: Multidimensional Scaling (1994), Chapman + * and Hall, London. + * + * + * If the input graph is disconnected, igraph will decompose it + * first into its subgraphs, lay out the subgraphs one by one + * using the appropriate submatrices of the distance matrix, and + * then merge the layouts using \ref igraph_layout_merge_dla. + * Since \ref igraph_layout_merge_dla works for 2D layouts only, + * you cannot run the MDS layout on disconnected graphs for + * more than two dimensions. + * + * + * Warning: if the graph is symmetric to the exchange of two vertices + * (as is the case with leaves of a tree connecting to the same parent), + * classical multidimensional scaling may assign the same coordinates to + * these vertices. + * + * \param graph A graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized if needed. + * \param dist The distance matrix. It must be symmetric and this + * function does not check whether the matrix is indeed + * symmetric. Results are unspecified if you pass a non-symmetric + * matrix here. You can set this parameter to null; in this + * case, the shortest path lengths between vertices will be + * used as distances. + * \param dim The number of dimensions in the embedding space. For + * 2D layouts, supply 2 here. + * \param options This argument is currently ignored, it was used for + * ARPACK, but LAPACK is used now for calculating the eigenvectors. + * \return Error code. + * + * Added in version 0.6. + * + * + * Time complexity: usually around O(|V|^2 dim). + */ + +int igraph_layout_mds(const igraph_t* graph, igraph_matrix_t *res, + const igraph_matrix_t *dist, long int dim, + igraph_arpack_options_t *options) { + long int i, no_of_nodes = igraph_vcount(graph); + igraph_matrix_t m; + igraph_bool_t conn; + + RNG_BEGIN(); + + /* Check the distance matrix */ + if (dist && (igraph_matrix_nrow(dist) != no_of_nodes || + igraph_matrix_ncol(dist) != no_of_nodes)) { + IGRAPH_ERROR("invalid distance matrix size", IGRAPH_EINVAL); + } + + /* Check the number of dimensions */ + if (dim <= 1) { + IGRAPH_ERROR("dim must be positive", IGRAPH_EINVAL); + } + if (dim > no_of_nodes) { + IGRAPH_ERROR("dim must be less than the number of nodes", IGRAPH_EINVAL); + } + + /* Copy or obtain the distance matrix */ + if (dist == 0) { + IGRAPH_CHECK(igraph_matrix_init(&m, no_of_nodes, no_of_nodes)); + IGRAPH_FINALLY(igraph_matrix_destroy, &m); + IGRAPH_CHECK(igraph_shortest_paths(graph, &m, + igraph_vss_all(), igraph_vss_all(), IGRAPH_ALL)); + } else { + IGRAPH_CHECK(igraph_matrix_copy(&m, dist)); + IGRAPH_FINALLY(igraph_matrix_destroy, &m); + /* Make sure that the diagonal contains zeroes only */ + for (i = 0; i < no_of_nodes; i++) { + MATRIX(m, i, i) = 0.0; + } + } + + /* Check whether the graph is connected */ + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (conn) { + /* Yes, it is, just do the MDS */ + IGRAPH_CHECK(igraph_i_layout_mds_single(graph, res, &m, dim)); + } else { + /* The graph is not connected, lay out the components one by one */ + igraph_vector_ptr_t layouts; + igraph_vector_t comp, vertex_order; + igraph_t subgraph; + igraph_matrix_t *layout; + igraph_matrix_t dist_submatrix; + igraph_bool_t *seen_vertices; + long int j, n, processed_vertex_count = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&comp, 0); + IGRAPH_VECTOR_INIT_FINALLY(&vertex_order, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_ptr_init(&layouts, 0)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &layouts); + igraph_vector_ptr_set_item_destructor(&layouts, (igraph_finally_func_t*)igraph_matrix_destroy); + + IGRAPH_CHECK(igraph_matrix_init(&dist_submatrix, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &dist_submatrix); + + seen_vertices = igraph_Calloc(no_of_nodes, igraph_bool_t); + if (seen_vertices == 0) { + IGRAPH_ERROR("cannot calculate MDS layout", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, seen_vertices); + + for (i = 0; i < no_of_nodes; i++) { + if (seen_vertices[i]) { + continue; + } + + /* This is a vertex whose component we did not lay out so far */ + IGRAPH_CHECK(igraph_subcomponent(graph, &comp, i, IGRAPH_ALL)); + /* Take the subgraph */ + IGRAPH_CHECK(igraph_induced_subgraph(graph, &subgraph, igraph_vss_vector(&comp), + IGRAPH_SUBGRAPH_AUTO)); + IGRAPH_FINALLY(igraph_destroy, &subgraph); + /* Calculate the submatrix of the distances */ + IGRAPH_CHECK(igraph_matrix_select_rows_cols(&m, &dist_submatrix, + &comp, &comp)); + /* Allocate a new matrix for storing the layout */ + layout = igraph_Calloc(1, igraph_matrix_t); + if (layout == 0) { + IGRAPH_ERROR("cannot calculate MDS layout", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, layout); + IGRAPH_CHECK(igraph_matrix_init(layout, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, layout); + /* Lay out the subgraph */ + IGRAPH_CHECK(igraph_i_layout_mds_single(&subgraph, layout, &dist_submatrix, dim)); + /* Store the layout */ + IGRAPH_CHECK(igraph_vector_ptr_push_back(&layouts, layout)); + IGRAPH_FINALLY_CLEAN(2); /* ownership of layout taken by layouts */ + /* Free the newly created subgraph */ + igraph_destroy(&subgraph); + IGRAPH_FINALLY_CLEAN(1); + /* Mark all the vertices in the component as visited */ + n = igraph_vector_size(&comp); + for (j = 0; j < n; j++) { + seen_vertices[(long int)VECTOR(comp)[j]] = 1; + VECTOR(vertex_order)[(long int)VECTOR(comp)[j]] = processed_vertex_count++; + } + } + /* Merge the layouts - reusing dist_submatrix here */ + IGRAPH_CHECK(igraph_layout_merge_dla(0, &layouts, &dist_submatrix)); + /* Reordering the rows of res to match the original graph */ + IGRAPH_CHECK(igraph_matrix_select_rows(&dist_submatrix, res, &vertex_order)); + + igraph_free(seen_vertices); + igraph_matrix_destroy(&dist_submatrix); + igraph_vector_ptr_destroy_all(&layouts); + igraph_vector_destroy(&vertex_order); + igraph_vector_destroy(&comp); + IGRAPH_FINALLY_CLEAN(5); + } + + RNG_END(); + + igraph_matrix_destroy(&m); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_layout_bipartite + * Simple layout for bipartite graphs + * + * The layout is created by first placing the vertices in two rows, + * according to their types. Then the positions within the rows are + * optimized to minimize edge crossings, by calling \ref + * igraph_layout_sugiyama(). + * + * \param graph The input graph. + * \param types A boolean vector containing ones and zeros, the vertex + * types. Its length must match the number of vertices in the graph. + * \param res Pointer to an initialized matrix, the result, the x and + * y coordinates are stored here. + * \param hgap The preferred minimum horizontal gap between vertices + * in the same layer (i.e. vertices of the same type). + * \param vgap The distance between layers. + * \param maxiter Maximum number of iterations in the crossing + * minimization stage. 100 is a reasonable default; if you feel + * that you have too many edge crossings, increase this. + * \return Error code. + * + * \sa \ref igraph_layout_sugiyama(). + */ + +int igraph_layout_bipartite(const igraph_t *graph, + const igraph_vector_bool_t *types, + igraph_matrix_t *res, igraph_real_t hgap, + igraph_real_t vgap, long int maxiter) { + + long int i, no_of_nodes = igraph_vcount(graph); + igraph_vector_t layers; + + if (igraph_vector_bool_size(types) != no_of_nodes) { + IGRAPH_ERROR("Invalid vertex type vector size", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&layers, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(layers)[i] = 1 - VECTOR(*types)[i]; + } + + IGRAPH_CHECK(igraph_layout_sugiyama(graph, res, /*extd_graph=*/ 0, + /*extd_to_orig_eids=*/ 0, &layers, hgap, + vgap, maxiter, /*weights=*/ 0)); + + igraph_vector_destroy(&layers); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} diff --git a/src/layout_dh.c b/src/layout_dh.c new file mode 100644 index 0000000..aad1f27 --- /dev/null +++ b/src/layout_dh.c @@ -0,0 +1,459 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph R package. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" +#include "igraph_interface.h" +#include "igraph_random.h" +#include "igraph_math.h" + +#include + +/* not 'static', used in tests */ +igraph_bool_t igraph_i_segments_intersect(float p0_x, float p0_y, + float p1_x, float p1_y, + float p2_x, float p2_y, + float p3_x, float p3_y) { + float s1_x = p1_x - p0_x; + float s1_y = p1_y - p0_y; + float s2_x = p3_x - p2_x; + float s2_y = p3_y - p2_y; + + float s1, s2, t1, t2, s, t; + s1 = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)); + s2 = (-s2_x * s1_y + s1_x * s2_y); + if (s2 == 0) { + return 0; + } + t1 = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)); + t2 = (-s2_x * s1_y + s1_x * s2_y); + s = s1 / s2; + t = t1 / t2; + + return s >= 0 && s <= 1 && t >= 0 && t <= 1 ? 1 : 0; +} + +/* not 'static', used in tests */ +float igraph_i_point_segment_dist2(float v_x, float v_y, + float u1_x, float u1_y, + float u2_x, float u2_y) { + + float dx = u2_x - u1_x; + float dy = u2_y - u1_y; + float l2 = dx * dx + dy * dy; + float t, p_x, p_y; + if (l2 == 0) { + return (v_x - u1_x) * (v_x - u1_x) + (v_y - u1_y) * (v_y - u1_y); + } + t = ((v_x - u1_x) * dx + (v_y - u1_y) * dy) / l2; + if (t < 0.0) { + return (v_x - u1_x) * (v_x - u1_x) + (v_y - u1_y) * (v_y - u1_y); + } else if (t > 1.0) { + return (v_x - u2_x) * (v_x - u2_x) + (v_y - u2_y) * (v_y - u2_y); + } + p_x = u1_x + t * dx; + p_y = u1_y + t * dy; + return (v_x - p_x) * (v_x - p_x) + (v_y - p_y) * (v_y - p_y); +} + +/** + * \function igraph_layout_davidson_harel + * Davidson-Harel layout algorithm + * + * This function implements the algorithm by Davidson and Harel, + * see Ron Davidson, David Harel: Drawing Graphs Nicely Using + * Simulated Annealing. ACM Transactions on Graphics 15(4), + * pp. 301-331, 1996. + * + * + * The algorithm uses simulated annealing and a sophisticated + * energy function, which is unfortunately hard to parameterize + * for different graphs. The original publication did not disclose any + * parameter values, and the ones below were determined by + * experimentation. + * + * + * The algorithm consists of two phases, an annealing phase, and a + * fine-tuning phase. There is no simulated annealing in the second + * phase. + * + * + * Our implementation tries to follow the original publication, as + * much as possible. The only major difference is that coordinates are + * explicitly kept within the bounds of the rectangle of the layout. + * + * \param graph The input graph, edge directions are ignored. + * \param res A matrix, the result is stored here. It can be used to + * supply start coordinates, see \p use_seed. + * \param use_seed Boolean, whether to use the supplied \p res as + * start coordinates. + * \param maxiter The maximum number of annealing iterations. A + * reasonable value for smaller graphs is 10. + * \param fineiter The number of fine tuning iterations. A reasonable + * value is max(10, log2(n)) where n is the number of vertices. + * \param cool_fact Cooling factor. A reasonable value is 0.75. + * \param weight_node_dist Weight for the node-node distances + * component of the energy function. Reasonable value: 1.0. + * \param weight_border Weight for the distance from the border + * component of the energy function. It can be set to zero, if + * vertices are allowed to sit on the border. + * \param weight_edge_lengths Weight for the edge length component + * of the energy function, a reasonable value is the density of + * the graph divided by 10. + * \param weight_edge_crossings Weight for the edge crossing component + * of the energy function, a reasonable default is 1 minus the + * square root of the density of the graph. + * \param weight_node_edge_dist Weight for the node-edge distance + * component of the energy function. A reasonable value is + * 1 minus the density, divided by 5. + * \return Error code. + * + * Time complexity: one first phase iteration has time complexity + * O(n^2+m^2), one fine tuning iteration has time complexity O(mn). + * Time complexity might be smaller if some of the weights of the + * components of the energy function are set to zero. + * + */ + +int igraph_layout_davidson_harel(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_integer_t maxiter, + igraph_integer_t fineiter, igraph_real_t cool_fact, + igraph_real_t weight_node_dist, igraph_real_t weight_border, + igraph_real_t weight_edge_lengths, + igraph_real_t weight_edge_crossings, + igraph_real_t weight_node_edge_dist) { + + igraph_integer_t no_nodes = igraph_vcount(graph); + igraph_integer_t no_edges = igraph_ecount(graph); + float width = sqrt(no_nodes) * 10, height = width; + igraph_vector_int_t perm; + igraph_bool_t fine_tuning = 0; + igraph_integer_t round, i; + igraph_vector_float_t try_x, try_y; + igraph_vector_int_t try_idx; + float move_radius = width / 2; + float fine_tuning_factor = 0.01; + igraph_vector_t neis; + float min_x = width / 2, max_x = -width / 2, min_y = height / 2, max_y = -height / 2; + + igraph_integer_t no_tries = 30; + float w_node_dist = weight_node_dist ; /* 1.0 */ + float w_borderlines = weight_border; /* 0.0 */ + float w_edge_lengths = weight_edge_lengths; /* 0.0001; */ + float w_edge_crossings = weight_edge_crossings; /* 1.0 */ + float w_node_edge_dist = weight_node_edge_dist; /* 0.2 */ + + if (use_seed && (igraph_matrix_nrow(res) != no_nodes || + igraph_matrix_ncol(res) != 2)) { + IGRAPH_ERROR("Invalid start position matrix size in " + "Davidson-Harel layout", IGRAPH_EINVAL); + } + if (maxiter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negative in " + "Davidson-Harel layout", IGRAPH_EINVAL); + } + if (fineiter < 0) { + IGRAPH_ERROR("Number of fine tuning iterations must be non-negative in " + "Davidson-Harel layout", IGRAPH_EINVAL); + } + if (cool_fact <= 0 || cool_fact >= 1) { + IGRAPH_ERROR("Cooling factor must be in (0,1) in " + "Davidson-Harel layout", IGRAPH_EINVAL); + } + + if (no_nodes == 0) { + return 0; + } + + IGRAPH_CHECK(igraph_vector_int_init_seq(&perm, 0, no_nodes - 1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &perm); + IGRAPH_CHECK(igraph_vector_float_init(&try_x, no_tries)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &try_x); + IGRAPH_CHECK(igraph_vector_float_init(&try_y, no_tries)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &try_y); + IGRAPH_CHECK(igraph_vector_int_init_seq(&try_idx, 0, no_tries - 1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &try_idx); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 100); + + RNG_BEGIN(); + + if (!use_seed) { + IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2)); + for (i = 0; i < no_nodes; i++) { + float x, y; + x = MATRIX(*res, i, 0) = RNG_UNIF(-width / 2, width / 2); + y = MATRIX(*res, i, 1) = RNG_UNIF(-height / 2, height / 2); + if (x < min_x) { + min_x = x; + } else if (x > max_x) { + max_x = x; + } + if (y < min_y) { + min_y = y; + } else if (y > max_y) { + max_y = y; + } + } + } else { + min_x = IGRAPH_INFINITY; max_x = IGRAPH_NEGINFINITY; + min_y = IGRAPH_INFINITY; max_y = IGRAPH_NEGINFINITY; + for (i = 0; i < no_nodes; i++) { + float x = MATRIX(*res, i, 0); + float y = MATRIX(*res, i, 1); + if (x < min_x) { + min_x = x; + } else if (x > max_x) { + max_x = x; + } + if (y < min_y) { + min_y = y; + } else if (y > max_y) { + max_y = y; + } + } + } + + for (i = 0; i < no_tries; i++) { + float phi = 2 * M_PI / no_tries * i; + VECTOR(try_x)[i] = cos(phi); + VECTOR(try_y)[i] = sin(phi); + } + + for (round = 0; round < maxiter + fineiter; round++) { + igraph_integer_t p; + igraph_vector_int_shuffle(&perm); + + fine_tuning = round >= maxiter; + if (fine_tuning) { + float fx = fine_tuning_factor * (max_x - min_x); + float fy = fine_tuning_factor * (max_y - min_y); + move_radius = fx < fy ? fx : fy; + } + + for (p = 0; p < no_nodes; p++) { + igraph_integer_t t; + igraph_integer_t v = VECTOR(perm)[p]; + igraph_vector_int_shuffle(&try_idx); + + for (t = 0; t < no_tries; t++) { + float diff_energy = 0.0; + int ti = VECTOR(try_idx)[t]; + + /* Try moving it */ + float old_x = MATRIX(*res, v, 0); + float old_y = MATRIX(*res, v, 1); + float new_x = old_x + move_radius * VECTOR(try_x)[ti]; + float new_y = old_y + move_radius * VECTOR(try_y)[ti]; + + if (new_x < -width / 2) { + new_x = -width / 2 - 1e-6; + } + if (new_x > width / 2) { + new_x = width / 2 - 1e-6; + } + if (new_y < -height / 2) { + new_y = -height / 2 - 1e-6; + } + if (new_y > height / 2) { + new_y = height / 2 - 1e-6; + } + + if (w_node_dist != 0) { + igraph_integer_t u; + for (u = 0; u < no_nodes; u++) { + float odx, ody, odist2, dx, dy, dist2; + if (u == v) { + continue; + } + odx = old_x - MATRIX(*res, u, 0); + ody = old_y - MATRIX(*res, u, 1); + dx = new_x - MATRIX(*res, u, 0); + dy = new_y - MATRIX(*res, u, 1); + odist2 = odx * odx + ody * ody; + dist2 = dx * dx + dy * dy; + diff_energy += w_node_dist / dist2 - w_node_dist / odist2; + } + } + + if (w_borderlines != 0) { + float odx1 = width / 2 - old_x, odx2 = old_x + width / 2; + float ody1 = height / 2 - old_y, ody2 = old_y + height / 2; + float dx1 = width / 2 - new_x, dx2 = new_x + width / 2; + float dy1 = height / 2 - new_y, dy2 = new_y + height / 2; + if (odx1 < 0) { + odx1 = 2; + } if (odx2 < 0) { + odx2 = 2; + } + if (ody1 < 0) { + ody1 = 2; + } if (ody2 < 0) { + ody2 = 2; + } + if (dx1 < 0) { + dx1 = 2; + } if (dx2 < 0) { + dx2 = 2; + } + if (dy1 < 0) { + dy1 = 2; + } if (dy2 < 0) { + dy2 = 2; + } + diff_energy -= w_borderlines * + (1.0 / (odx1 * odx1) + 1.0 / (odx2 * odx2) + + 1.0 / (ody1 * ody1) + 1.0 / (ody2 * ody2)); + diff_energy += w_borderlines * + (1.0 / (dx1 * dx1) + 1.0 / (dx2 * dx2) + + 1.0 / (dy1 * dy1) + 1.0 / (dy2 * dy2)); + } + + if (w_edge_lengths != 0) { + igraph_integer_t len, j; + igraph_neighbors(graph, &neis, v, IGRAPH_ALL); + len = igraph_vector_size(&neis); + for (j = 0; j < len; j++) { + igraph_integer_t u = VECTOR(neis)[j]; + float odx = old_x - MATRIX(*res, u, 0); + float ody = old_y - MATRIX(*res, u, 1); + float odist2 = odx * odx + ody * ody; + float dx = new_x - MATRIX(*res, u, 0); + float dy = new_y - MATRIX(*res, u, 1); + float dist2 = dx * dx + dy * dy; + diff_energy += w_edge_lengths * (dist2 - odist2); + } + } + + if (w_edge_crossings != 0) { + igraph_integer_t len, j, no = 0; + igraph_neighbors(graph, &neis, v, IGRAPH_ALL); + len = igraph_vector_size(&neis); + for (j = 0; j < len; j++) { + igraph_integer_t u = VECTOR(neis)[j]; + float u_x = MATRIX(*res, u, 0); + float u_y = MATRIX(*res, u, 1); + igraph_integer_t e; + for (e = 0; e < no_edges; e++) { + igraph_integer_t u1 = IGRAPH_FROM(graph, e); + igraph_integer_t u2 = IGRAPH_TO(graph, e); + float u1_x, u1_y, u2_x, u2_y; + if (u1 == v || u2 == v || u1 == u || u2 == u) { + continue; + } + u1_x = MATRIX(*res, u1, 0); + u1_y = MATRIX(*res, u1, 1); + u2_x = MATRIX(*res, u2, 0); + u2_y = MATRIX(*res, u2, 1); + no -= igraph_i_segments_intersect(old_x, old_y, u_x, u_y, + u1_x, u1_y, u2_x, u2_y); + no += igraph_i_segments_intersect(new_x, new_y, u_x, u_y, + u1_x, u1_y, u2_x, u2_y); + } + } + diff_energy += w_edge_crossings * no; + } + + if (w_node_edge_dist != 0 && fine_tuning) { + igraph_integer_t e, no; + + /* All non-incident edges from the moved 'v' */ + for (e = 0; e < no_edges; e++) { + igraph_integer_t u1 = IGRAPH_FROM(graph, e); + igraph_integer_t u2 = IGRAPH_TO(graph, e); + float u1_x, u1_y, u2_x, u2_y, d_ev; + if (u1 == v || u2 == v) { + continue; + } + u1_x = MATRIX(*res, u1, 0); + u1_y = MATRIX(*res, u1, 1); + u2_x = MATRIX(*res, u2, 0); + u2_y = MATRIX(*res, u2, 1); + d_ev = igraph_i_point_segment_dist2(old_x, old_y, u1_x, u1_y, + u2_x, u2_y); + diff_energy -= w_node_edge_dist / d_ev; + d_ev = igraph_i_point_segment_dist2(new_x, new_y, u1_x, u1_y, + u2_x, u2_y); + diff_energy += w_node_edge_dist / d_ev; + } + + /* All other nodes from all of v's incident edges */ + igraph_incident(graph, &neis, v, IGRAPH_ALL); + no = igraph_vector_size(&neis); + for (e = 0; e < no; e++) { + igraph_integer_t mye = VECTOR(neis)[e]; + igraph_integer_t u = IGRAPH_OTHER(graph, mye, v); + float u_x = MATRIX(*res, u, 0); + float u_y = MATRIX(*res, u, 1); + igraph_integer_t w; + for (w = 0; w < no_nodes; w++) { + float w_x, w_y, d_ev; + if (w == v || w == u) { + continue; + } + w_x = MATRIX(*res, w, 0); + w_y = MATRIX(*res, w, 1); + d_ev = igraph_i_point_segment_dist2(w_x, w_y, old_x, + old_y, u_x, u_y); + diff_energy -= w_node_edge_dist / d_ev; + d_ev = igraph_i_point_segment_dist2(w_x, w_y, new_x, new_y, + u_x, u_y); + diff_energy += w_node_edge_dist / d_ev; + } + } + } /* w_node_edge_dist != 0 && fine_tuning */ + + if (diff_energy < 0 || + (!fine_tuning && RNG_UNIF01() < exp(-diff_energy / move_radius))) { + MATRIX(*res, v, 0) = new_x; + MATRIX(*res, v, 1) = new_y; + if (new_x < min_x) { + min_x = new_x; + } else if (new_x > max_x) { + max_x = new_x; + } + if (new_y < min_y) { + min_y = new_y; + } else if (new_y > max_y) { + max_y = new_y; + } + } + + } /* t < no_tries */ + + } /* p < no_nodes */ + + move_radius *= cool_fact; + + } /* round < maxiter */ + + RNG_END(); + + igraph_vector_destroy(&neis); + igraph_vector_int_destroy(&try_idx); + igraph_vector_float_destroy(&try_x); + igraph_vector_float_destroy(&try_y); + igraph_vector_int_destroy(&perm); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} diff --git a/src/layout_fr.c b/src/layout_fr.c new file mode 100644 index 0000000..6b2cfec --- /dev/null +++ b/src/layout_fr.c @@ -0,0 +1,701 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph R package. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" +#include "igraph_random.h" +#include "igraph_interface.h" +#include "igraph_components.h" +#include "igraph_types_internal.h" + +static int igraph_layout_i_fr(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_integer_t niter, + igraph_real_t start_temp, + const igraph_vector_t *weight, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy) { + + igraph_integer_t no_nodes = igraph_vcount(graph); + igraph_integer_t no_edges = igraph_ecount(graph); + igraph_integer_t i; + igraph_vector_float_t dispx, dispy; + igraph_real_t temp = start_temp; + igraph_real_t difftemp = start_temp / niter; + float width = sqrtf(no_nodes), height = width; + igraph_bool_t conn = 1; + float C; + + igraph_is_connected(graph, &conn, IGRAPH_WEAK); + if (!conn) { + C = no_nodes * sqrtf(no_nodes); + } + + RNG_BEGIN(); + + if (!use_seed) { + IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2)); + for (i = 0; i < no_nodes; i++) { + igraph_real_t x1 = minx ? VECTOR(*minx)[i] : -width / 2; + igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] : width / 2; + igraph_real_t y1 = miny ? VECTOR(*miny)[i] : -height / 2; + igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] : height / 2; + if (!igraph_finite(x1)) { + x1 = -sqrt(no_nodes) / 2; + } + if (!igraph_finite(x2)) { + x2 = sqrt(no_nodes) / 2; + } + if (!igraph_finite(y1)) { + y1 = -sqrt(no_nodes) / 2; + } + if (!igraph_finite(y2)) { + y2 = sqrt(no_nodes) / 2; + } + MATRIX(*res, i, 0) = RNG_UNIF(x1, x2); + MATRIX(*res, i, 1) = RNG_UNIF(y1, y2); + } + } + + IGRAPH_CHECK(igraph_vector_float_init(&dispx, no_nodes)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &dispx); + IGRAPH_CHECK(igraph_vector_float_init(&dispy, no_nodes)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &dispy); + + for (i = 0; i < niter; i++) { + igraph_integer_t v, u, e; + + /* calculate repulsive forces, we have a special version + for unconnected graphs */ + igraph_vector_float_null(&dispx); + igraph_vector_float_null(&dispy); + if (conn) { + for (v = 0; v < no_nodes; v++) { + for (u = v + 1; u < no_nodes; u++) { + float dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + float dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + float dlen = dx * dx + dy * dy; + + if (dlen == 0) { + dx = RNG_UNIF01() * 1e-9; + dy = RNG_UNIF01() * 1e-9; + dlen = dx * dx + dy * dy; + } + + VECTOR(dispx)[v] += dx / dlen; + VECTOR(dispy)[v] += dy / dlen; + VECTOR(dispx)[u] -= dx / dlen; + VECTOR(dispy)[u] -= dy / dlen; + } + } + } else { + for (v = 0; v < no_nodes; v++) { + for (u = v + 1; u < no_nodes; u++) { + float dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + float dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + float dlen, rdlen; + + dlen = dx * dx + dy * dy; + if (dlen == 0) { + dx = RNG_UNIF(0, 1e-6); + dy = RNG_UNIF(0, 1e-6); + dlen = dx * dx + dy * dy; + } + + rdlen = sqrt(dlen); + + VECTOR(dispx)[v] += dx * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispy)[v] += dy * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispx)[u] -= dx * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispy)[u] -= dy * (C - dlen * rdlen) / (dlen * C); + } + } + } + + /* calculate attractive forces */ + for (e = 0; e < no_edges; e++) { + /* each edges is an ordered pair of vertices v and u */ + igraph_integer_t v = IGRAPH_FROM(graph, e); + igraph_integer_t u = IGRAPH_TO(graph, e); + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t w = weight ? VECTOR(*weight)[e] : 1.0; + igraph_real_t dlen = sqrt(dx * dx + dy * dy) * w; + VECTOR(dispx)[v] -= (dx * dlen); + VECTOR(dispy)[v] -= (dy * dlen); + VECTOR(dispx)[u] += (dx * dlen); + VECTOR(dispy)[u] += (dy * dlen); + } + + /* limit max displacement to temperature t and prevent from + displacement outside frame */ + for (v = 0; v < no_nodes; v++) { + igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF01() * 1e-9; + igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF01() * 1e-9; + igraph_real_t displen = sqrt(dx * dx + dy * dy); + igraph_real_t mx = fabs(dx) < temp ? dx : temp; + igraph_real_t my = fabs(dy) < temp ? dy : temp; + if (displen > 0) { + MATRIX(*res, v, 0) += (dx / displen) * mx; + MATRIX(*res, v, 1) += (dy / displen) * my; + } + if (minx && MATRIX(*res, v, 0) < VECTOR(*minx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*minx)[v]; + } + if (maxx && MATRIX(*res, v, 0) > VECTOR(*maxx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*maxx)[v]; + } + if (miny && MATRIX(*res, v, 1) < VECTOR(*miny)[v]) { + MATRIX(*res, v, 1) = VECTOR(*miny)[v]; + } + if (maxy && MATRIX(*res, v, 1) > VECTOR(*maxy)[v]) { + MATRIX(*res, v, 1) = VECTOR(*maxy)[v]; + } + } + + temp -= difftemp; + } + + RNG_END(); + + igraph_vector_float_destroy(&dispx); + igraph_vector_float_destroy(&dispy); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +static int igraph_layout_i_grid_fr( + const igraph_t *graph, + igraph_matrix_t *res, igraph_bool_t use_seed, + igraph_integer_t niter, igraph_real_t start_temp, + const igraph_vector_t *weight, const igraph_vector_t *minx, + const igraph_vector_t *maxx, const igraph_vector_t *miny, + const igraph_vector_t *maxy) { + + igraph_integer_t no_nodes = igraph_vcount(graph); + igraph_integer_t no_edges = igraph_ecount(graph); + float width = sqrtf(no_nodes), height = width; + igraph_2dgrid_t grid; + igraph_vector_float_t dispx, dispy; + igraph_real_t temp = start_temp; + igraph_real_t difftemp = start_temp / niter; + igraph_2dgrid_iterator_t vidit; + igraph_integer_t i; + const float cellsize = 2.0; + + RNG_BEGIN(); + + if (!use_seed) { + IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2)); + for (i = 0; i < no_nodes; i++) { + igraph_real_t x1 = minx ? VECTOR(*minx)[i] : -width / 2; + igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] : width / 2; + igraph_real_t y1 = miny ? VECTOR(*miny)[i] : -height / 2; + igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] : height / 2; + if (!igraph_finite(x1)) { + x1 = -sqrt(no_nodes) / 2; + } + if (!igraph_finite(x2)) { + x2 = sqrt(no_nodes) / 2; + } + if (!igraph_finite(y1)) { + y1 = -sqrt(no_nodes) / 2; + } + if (!igraph_finite(y2)) { + y2 = sqrt(no_nodes) / 2; + } + MATRIX(*res, i, 0) = RNG_UNIF(x1, x2); + MATRIX(*res, i, 1) = RNG_UNIF(y1, y2); + } + } + + /* make grid */ + IGRAPH_CHECK(igraph_2dgrid_init(&grid, res, -width / 2, width / 2, cellsize, + -height / 2, height / 2, cellsize)); + IGRAPH_FINALLY(igraph_2dgrid_destroy, &grid); + + /* place vertices on grid */ + for (i = 0; i < no_nodes; i++) { + igraph_2dgrid_add2(&grid, i); + } + + IGRAPH_CHECK(igraph_vector_float_init(&dispx, no_nodes)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &dispx); + IGRAPH_CHECK(igraph_vector_float_init(&dispy, no_nodes)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &dispy); + + for (i = 0; i < niter; i++) { + igraph_integer_t v, u, e; + + igraph_vector_float_null(&dispx); + igraph_vector_float_null(&dispy); + + /* repulsion */ + igraph_2dgrid_reset(&grid, &vidit); + while ( (v = igraph_2dgrid_next(&grid, &vidit) - 1) != -1) { + while ( (u = igraph_2dgrid_next_nei(&grid, &vidit) - 1) != -1) { + float dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + float dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + float dlen = dx * dx + dy * dy; + if (dlen < cellsize * cellsize) { + VECTOR(dispx)[v] += dx / dlen; + VECTOR(dispy)[v] += dy / dlen; + VECTOR(dispx)[u] -= dx / dlen; + VECTOR(dispy)[u] -= dy / dlen; + } + } + } + + /* attraction */ + for (e = 0; e < no_edges; e++) { + igraph_integer_t v = IGRAPH_FROM(graph, e); + igraph_integer_t u = IGRAPH_TO(graph, e); + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t w = weight ? VECTOR(*weight)[e] : 1.0; + igraph_real_t dlen = sqrt(dx * dx + dy * dy) * w; + VECTOR(dispx)[v] -= (dx * dlen); + VECTOR(dispy)[v] -= (dy * dlen); + VECTOR(dispx)[u] += (dx * dlen); + VECTOR(dispy)[u] += (dy * dlen); + } + + /* update */ + for (v = 0; v < no_nodes; v++) { + igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF01() * 1e-9; + igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF01() * 1e-9; + igraph_real_t displen = sqrt(dx * dx + dy * dy); + igraph_real_t mx = fabs(dx) < temp ? dx : temp; + igraph_real_t my = fabs(dy) < temp ? dy : temp; + if (displen > 0) { + MATRIX(*res, v, 0) += (dx / displen) * mx; + MATRIX(*res, v, 1) += (dy / displen) * my; + } + if (minx && MATRIX(*res, v, 0) < VECTOR(*minx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*minx)[v]; + } + if (maxx && MATRIX(*res, v, 0) > VECTOR(*maxx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*maxx)[v]; + } + if (miny && MATRIX(*res, v, 1) < VECTOR(*miny)[v]) { + MATRIX(*res, v, 1) = VECTOR(*miny)[v]; + } + if (maxy && MATRIX(*res, v, 1) > VECTOR(*maxy)[v]) { + MATRIX(*res, v, 1) = VECTOR(*maxy)[v]; + } + } + + temp -= difftemp; + } + + igraph_vector_float_destroy(&dispx); + igraph_vector_float_destroy(&dispy); + igraph_2dgrid_destroy(&grid); + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +/** + * \ingroup layout + * \function igraph_layout_fruchterman_reingold + * \brief Places the vertices on a plane according to the Fruchterman-Reingold algorithm. + * + * + * This is a force-directed layout, see Fruchterman, T.M.J. and + * Reingold, E.M.: Graph Drawing by Force-directed Placement. + * Software -- Practice and Experience, 21/11, 1129--1164, + * 1991. + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param use_seed Logical, if true the supplied values in the + * \p res argument are used as an initial layout, if + * false a random initial layout is used. + * \param niter The number of iterations to do. A reasonable + * default value is 500. + * \param start_temp Start temperature. This is the maximum amount + * of movement alloved along one axis, within one step, for a + * vertex. Currently it is decreased linearly to zero during + * the iteration. + * \param grid Whether to use the (fast but less accurate) grid based + * version of the algorithm. Possible values: \c + * IGRAPH_LAYOUT_GRID, \c IGRAPH_LAYOUT_NOGRID, \c + * IGRAPH_LAYOUT_AUTOGRID. The last one uses the grid based + * version only for large graphs, currently the ones with + * more than 1000 vertices. + * \param weight Pointer to a vector containing edge weights, + * the attraction along the edges will be multiplied by these. + * It will be ignored if it is a null-pointer. + * \param minx Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote x \endquote coordinate for every vertex. + * \param maxx Same as \p minx, but the maximum \quote x \endquote + * coordinates. + * \param miny Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote y \endquote coordinate for every vertex. + * \param maxy Same as \p miny, but the maximum \quote y \endquote + * coordinates. + * \return Error code. + * + * Time complexity: O(|V|^2) in each + * iteration, |V| is the number of + * vertices in the graph. + */ + +int igraph_layout_fruchterman_reingold(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_integer_t niter, + igraph_real_t start_temp, + igraph_layout_grid_t grid, + const igraph_vector_t *weight, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy) { + + igraph_integer_t no_nodes = igraph_vcount(graph); + + if (niter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negative in " + "Fruchterman-Reingold layout", IGRAPH_EINVAL); + } + + if (use_seed && (igraph_matrix_nrow(res) != no_nodes || + igraph_matrix_ncol(res) != 2)) { + IGRAPH_ERROR("Invalid start position matrix size in " + "Fruchterman-Reingold layout", IGRAPH_EINVAL); + } + + if (weight && igraph_vector_size(weight) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + if (minx && igraph_vector_size(minx) != no_nodes) { + IGRAPH_ERROR("Invalid minx vector length", IGRAPH_EINVAL); + } + if (maxx && igraph_vector_size(maxx) != no_nodes) { + IGRAPH_ERROR("Invalid maxx vector length", IGRAPH_EINVAL); + } + if (minx && maxx && !igraph_vector_all_le(minx, maxx)) { + IGRAPH_ERROR("minx must not be greater than maxx", IGRAPH_EINVAL); + } + if (miny && igraph_vector_size(miny) != no_nodes) { + IGRAPH_ERROR("Invalid miny vector length", IGRAPH_EINVAL); + } + if (maxy && igraph_vector_size(maxy) != no_nodes) { + IGRAPH_ERROR("Invalid maxy vector length", IGRAPH_EINVAL); + } + if (miny && maxy && !igraph_vector_all_le(miny, maxy)) { + IGRAPH_ERROR("miny must not be greater than maxy", IGRAPH_EINVAL); + } + + if (grid == IGRAPH_LAYOUT_AUTOGRID) { + if (no_nodes > 1000) { + grid = IGRAPH_LAYOUT_GRID; + } else { + grid = IGRAPH_LAYOUT_NOGRID; + } + } + + if (grid == IGRAPH_LAYOUT_GRID) { + return igraph_layout_i_grid_fr(graph, res, use_seed, niter, start_temp, + weight, minx, maxx, miny, maxy); + } else { + return igraph_layout_i_fr(graph, res, use_seed, niter, start_temp, + weight, minx, maxx, miny, maxy); + } +} + +/** + * \function igraph_layout_fruchterman_reingold_3d + * \brief 3D Fruchterman-Reingold algorithm. + * + * This is the 3D version of the force based + * Fruchterman-Reingold layout (see \ref + * igraph_layout_fruchterman_reingold for the 2D version + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result and will be resized as needed. + * \param use_seed Logical, if true the supplied values in the + * \p res argument are used as an initial layout, if + * false a random initial layout is used. + * \param niter The number of iterations to do. A reasonable + * default value is 500. + * \param start_temp Start temperature. This is the maximum amount + * of movement alloved along one axis, within one step, for a + * vertex. Currently it is decreased linearly to zero during + * the iteration. + * \param weight Pointer to a vector containing edge weights, + * the attraction along the edges will be multiplied by these. + * It will be ignored if it is a null-pointer. + * \param minx Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote x \endquote coordinate for every vertex. + * \param maxx Same as \p minx, but the maximum \quote x \endquote + * coordinates. + * \param miny Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote y \endquote coordinate for every vertex. + * \param maxy Same as \p miny, but the maximum \quote y \endquote + * coordinates. + * \param minz Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote z \endquote coordinate for every vertex. + * \param maxz Same as \p minz, but the maximum \quote z \endquote + * coordinates. + * \return Error code. + * + * Added in version 0.2. + * + * Time complexity: O(|V|^2) in each + * iteration, |V| is the number of + * vertices in the graph. + * + */ + +int igraph_layout_fruchterman_reingold_3d(const igraph_t *graph, + igraph_matrix_t *res, + igraph_bool_t use_seed, + igraph_integer_t niter, + igraph_real_t start_temp, + const igraph_vector_t *weight, + const igraph_vector_t *minx, + const igraph_vector_t *maxx, + const igraph_vector_t *miny, + const igraph_vector_t *maxy, + const igraph_vector_t *minz, + const igraph_vector_t *maxz) { + + igraph_integer_t no_nodes = igraph_vcount(graph); + igraph_integer_t no_edges = igraph_ecount(graph); + igraph_integer_t i; + igraph_vector_float_t dispx, dispy, dispz; + igraph_real_t temp = start_temp; + igraph_real_t difftemp = start_temp / niter; + float width = sqrtf(no_nodes), height = width, depth = width; + igraph_bool_t conn = 1; + float C; + + if (niter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negative in " + "Fruchterman-Reingold layout", IGRAPH_EINVAL); + } + + if (use_seed && (igraph_matrix_nrow(res) != no_nodes || + igraph_matrix_ncol(res) != 3)) { + IGRAPH_ERROR("Invalid start position matrix size in " + "Fruchterman-Reingold layout", IGRAPH_EINVAL); + } + + if (weight && igraph_vector_size(weight) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + if (minx && igraph_vector_size(minx) != no_nodes) { + IGRAPH_ERROR("Invalid minx vector length", IGRAPH_EINVAL); + } + if (maxx && igraph_vector_size(maxx) != no_nodes) { + IGRAPH_ERROR("Invalid maxx vector length", IGRAPH_EINVAL); + } + if (minx && maxx && !igraph_vector_all_le(minx, maxx)) { + IGRAPH_ERROR("minx must not be greater than maxx", IGRAPH_EINVAL); + } + if (miny && igraph_vector_size(miny) != no_nodes) { + IGRAPH_ERROR("Invalid miny vector length", IGRAPH_EINVAL); + } + if (maxy && igraph_vector_size(maxy) != no_nodes) { + IGRAPH_ERROR("Invalid maxy vector length", IGRAPH_EINVAL); + } + if (miny && maxy && !igraph_vector_all_le(miny, maxy)) { + IGRAPH_ERROR("miny must not be greater than maxy", IGRAPH_EINVAL); + } + if (minz && igraph_vector_size(minz) != no_nodes) { + IGRAPH_ERROR("Invalid minz vector length", IGRAPH_EINVAL); + } + if (maxz && igraph_vector_size(maxz) != no_nodes) { + IGRAPH_ERROR("Invalid maxz vector length", IGRAPH_EINVAL); + } + if (minz && maxz && !igraph_vector_all_le(minz, maxz)) { + IGRAPH_ERROR("minz must not be greater than maxz", IGRAPH_EINVAL); + } + + igraph_is_connected(graph, &conn, IGRAPH_WEAK); + if (!conn) { + C = no_nodes * sqrtf(no_nodes); + } + + RNG_BEGIN(); + + if (!use_seed) { + IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 3)); + for (i = 0; i < no_nodes; i++) { + igraph_real_t x1 = minx ? VECTOR(*minx)[i] : -width / 2; + igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] : width / 2; + igraph_real_t y1 = miny ? VECTOR(*miny)[i] : -height / 2; + igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] : height / 2; + igraph_real_t z1 = minz ? VECTOR(*minz)[i] : -depth / 2; + igraph_real_t z2 = maxz ? VECTOR(*maxz)[i] : depth / 2; + MATRIX(*res, i, 0) = RNG_UNIF(x1, x2); + MATRIX(*res, i, 1) = RNG_UNIF(y1, y2); + MATRIX(*res, i, 2) = RNG_UNIF(z1, z2); + } + } + + IGRAPH_CHECK(igraph_vector_float_init(&dispx, no_nodes)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &dispx); + IGRAPH_CHECK(igraph_vector_float_init(&dispy, no_nodes)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &dispy); + IGRAPH_CHECK(igraph_vector_float_init(&dispz, no_nodes)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &dispz); + + for (i = 0; i < niter; i++) { + igraph_integer_t v, u, e; + + /* calculate repulsive forces, we have a special version + for unconnected graphs */ + igraph_vector_float_null(&dispx); + igraph_vector_float_null(&dispy); + igraph_vector_float_null(&dispz); + if (conn) { + for (v = 0; v < no_nodes; v++) { + for (u = v + 1; u < no_nodes; u++) { + float dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + float dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + float dz = MATRIX(*res, v, 2) - MATRIX(*res, u, 2); + float dlen = dx * dx + dy * dy + dz * dz; + + if (dlen == 0) { + dx = RNG_UNIF01() * 1e-9; + dy = RNG_UNIF01() * 1e-9; + dz = RNG_UNIF01() * 1e-9; + dlen = dx * dx + dy * dy + dz * dz; + } + + VECTOR(dispx)[v] += dx / dlen; + VECTOR(dispy)[v] += dy / dlen; + VECTOR(dispz)[v] += dz / dlen; + VECTOR(dispx)[u] -= dx / dlen; + VECTOR(dispy)[u] -= dy / dlen; + VECTOR(dispz)[u] -= dz / dlen; + } + } + } else { + for (v = 0; v < no_nodes; v++) { + for (u = v + 1; u < no_nodes; u++) { + float dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + float dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + float dz = MATRIX(*res, v, 2) - MATRIX(*res, u, 2); + float dlen, rdlen; + + dlen = dx * dx + dy * dy + dz * dz; + if (dlen == 0) { + dx = RNG_UNIF01() * 1e-9; + dy = RNG_UNIF01() * 1e-9; + dz = RNG_UNIF01() * 1e-9; + dlen = dx * dx + dy * dy + dz * dz; + } + + rdlen = sqrt(dlen); + + VECTOR(dispx)[v] += dx * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispy)[v] += dy * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispy)[v] += dz * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispx)[u] -= dx * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispy)[u] -= dy * (C - dlen * rdlen) / (dlen * C); + VECTOR(dispz)[u] -= dz * (C - dlen * rdlen) / (dlen * C); + } + } + } + + /* calculate attractive forces */ + for (e = 0; e < no_edges; e++) { + /* each edges is an ordered pair of vertices v and u */ + igraph_integer_t v = IGRAPH_FROM(graph, e); + igraph_integer_t u = IGRAPH_TO(graph, e); + igraph_real_t dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + igraph_real_t dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + igraph_real_t dz = MATRIX(*res, v, 2) - MATRIX(*res, u, 2); + igraph_real_t w = weight ? VECTOR(*weight)[e] : 1.0; + igraph_real_t dlen = sqrt(dx * dx + dy * dy + dz * dz) * w; + VECTOR(dispx)[v] -= (dx * dlen); + VECTOR(dispy)[v] -= (dy * dlen); + VECTOR(dispz)[v] -= (dz * dlen); + VECTOR(dispx)[u] += (dx * dlen); + VECTOR(dispy)[u] += (dy * dlen); + VECTOR(dispz)[u] += (dz * dlen); + } + + /* limit max displacement to temperature t and prevent from + displacement outside frame */ + for (v = 0; v < no_nodes; v++) { + igraph_real_t dx = VECTOR(dispx)[v] + RNG_UNIF01() * 1e-9; + igraph_real_t dy = VECTOR(dispy)[v] + RNG_UNIF01() * 1e-9; + igraph_real_t dz = VECTOR(dispz)[v] + RNG_UNIF01() * 1e-9; + igraph_real_t displen = sqrt(dx * dx + dy * dy + dz * dz); + igraph_real_t mx = fabs(dx) < temp ? dx : temp; + igraph_real_t my = fabs(dy) < temp ? dy : temp; + igraph_real_t mz = fabs(dz) < temp ? dz : temp; + if (displen > 0) { + MATRIX(*res, v, 0) += (dx / displen) * mx; + MATRIX(*res, v, 1) += (dy / displen) * my; + MATRIX(*res, v, 2) += (dz / displen) * mz; + } + if (minx && MATRIX(*res, v, 0) < VECTOR(*minx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*minx)[v]; + } + if (maxx && MATRIX(*res, v, 0) > VECTOR(*maxx)[v]) { + MATRIX(*res, v, 0) = VECTOR(*maxx)[v]; + } + if (miny && MATRIX(*res, v, 1) < VECTOR(*miny)[v]) { + MATRIX(*res, v, 1) = VECTOR(*miny)[v]; + } + if (maxy && MATRIX(*res, v, 1) > VECTOR(*maxy)[v]) { + MATRIX(*res, v, 1) = VECTOR(*maxy)[v]; + } + if (minz && MATRIX(*res, v, 2) < VECTOR(*minz)[v]) { + MATRIX(*res, v, 2) = VECTOR(*minz)[v]; + } + if (maxz && MATRIX(*res, v, 2) > VECTOR(*maxz)[v]) { + MATRIX(*res, v, 2) = VECTOR(*maxz)[v]; + } + } + + temp -= difftemp; + } + + RNG_END(); + + igraph_vector_float_destroy(&dispx); + igraph_vector_float_destroy(&dispy); + igraph_vector_float_destroy(&dispz); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} diff --git a/src/layout_gem.c b/src/layout_gem.c new file mode 100644 index 0000000..e2b6cf3 --- /dev/null +++ b/src/layout_gem.c @@ -0,0 +1,246 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph R package. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" +#include "igraph_interface.h" +#include "igraph_random.h" +#include "igraph_math.h" + +/** + * \ingroup layout + * \function igraph_layout_gem + * + * The GEM layout algorithm, as described in Arne Frick, Andreas Ludwig, + * Heiko Mehldau: A Fast Adaptive Layout Algorithm for Undirected Graphs, + * Proc. Graph Drawing 1994, LNCS 894, pp. 388-403, 1995. + * \param graph The input graph. Edge directions are ignored in + * directed graphs. + * \param res The result is stored here. If the \p use_seed argument + * is true (non-zero), then this matrix is also used as the + * starting point of the algorithm. + * \param use_seed Boolean, whether to use the supplied coordinates in + * \p res as the starting point. If false (zero), then a + * uniform random starting point is used. + * \param maxiter The maximum number of iterations to + * perform. Updating a single vertex counts as an iteration. + * A reasonable default is 40 * n * n, where n is the number of + * vertices. The original paper suggests 4 * n * n, but this + * usually only works if the other parameters are set up carefully. + * \param temp_max The maximum allowed local temperature. A reasonable + * default is the number of vertices. + * \param temp_min The global temperature at which the algorithm + * terminates (even before reaching \p maxiter iterations). A + * reasonable default is 1/10. + * \param temp_init Initial local temperature of all vertices. A + * reasonable default is the square root of the number of + * vertices. + * \return Error code. + * + * Time complexity: O(t * n * (n+e)), where n is the number of vertices, + * e is the number of edges and t is the number of time steps + * performed. + */ + +int igraph_layout_gem(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_integer_t maxiter, + igraph_real_t temp_max, igraph_real_t temp_min, + igraph_real_t temp_init) { + + igraph_integer_t no_nodes = igraph_vcount(graph); + igraph_vector_int_t perm; + igraph_vector_float_t impulse_x, impulse_y, temp, skew_gauge; + igraph_integer_t i; + float temp_global; + igraph_integer_t perm_pointer = 0; + float barycenter_x = 0.0, barycenter_y = 0.0; + igraph_vector_t phi; + igraph_vector_t neis; + const float elen_des2 = 128 * 128; + const float gamma = 1 / 16.0; + const float alpha_o = M_PI; + const float alpha_r = M_PI / 3.0; + const float sigma_o = 1.0 / 3.0; + const float sigma_r = 1.0 / 2.0 / no_nodes; + + if (maxiter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negative in GEM layout", + IGRAPH_EINVAL); + } + if (use_seed && (igraph_matrix_nrow(res) != no_nodes || + igraph_matrix_ncol(res) != 2)) { + IGRAPH_ERROR("Invalid start position matrix size in GEM layout", + IGRAPH_EINVAL); + } + if (temp_max <= 0) { + IGRAPH_ERROR("Maximum temperature should be positive in GEM layout", + IGRAPH_EINVAL); + } + if (temp_min <= 0) { + IGRAPH_ERROR("Minimum temperature should be positive in GEM layout", + IGRAPH_EINVAL); + } + if (temp_init <= 0) { + IGRAPH_ERROR("Initial temperature should be positive in GEM layout", + IGRAPH_EINVAL); + } + if (temp_max < temp_init || temp_init < temp_min) { + IGRAPH_ERROR("Minimum <= Initial <= Maximum temperature is required " + "in GEM layout", IGRAPH_EINVAL); + } + + if (no_nodes == 0) { + return 0; + } + + IGRAPH_CHECK(igraph_vector_float_init(&impulse_x, no_nodes)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &impulse_x); + IGRAPH_CHECK(igraph_vector_float_init(&impulse_y, no_nodes)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &impulse_y); + IGRAPH_CHECK(igraph_vector_float_init(&temp, no_nodes)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &temp); + IGRAPH_CHECK(igraph_vector_float_init(&skew_gauge, no_nodes)); + IGRAPH_FINALLY(igraph_vector_float_destroy, &skew_gauge); + IGRAPH_CHECK(igraph_vector_int_init_seq(&perm, 0, no_nodes - 1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &perm); + IGRAPH_VECTOR_INIT_FINALLY(&phi, no_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 10); + + RNG_BEGIN(); + + /* Initialization */ + igraph_degree(graph, &phi, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS); + if (!use_seed) { + const igraph_real_t width_half = no_nodes * 100, height_half = width_half; + IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2)); + for (i = 0; i < no_nodes; i++) { + MATRIX(*res, i, 0) = RNG_UNIF(-width_half, width_half); + MATRIX(*res, i, 1) = RNG_UNIF(-height_half, height_half); + barycenter_x += MATRIX(*res, i, 0); + barycenter_y += MATRIX(*res, i, 1); + VECTOR(phi)[i] *= (VECTOR(phi)[i] / 2.0 + 1.0); + } + } else { + for (i = 0; i < no_nodes; i++) { + barycenter_x += MATRIX(*res, i, 0); + barycenter_y += MATRIX(*res, i, 1); + VECTOR(phi)[i] *= (VECTOR(phi)[i] / 2.0 + 1.0); + } + } + igraph_vector_float_fill(&temp, temp_init); + temp_global = temp_init * no_nodes; + + while (temp_global > temp_min * no_nodes && maxiter > 0) { + + /* choose a vertex v to update */ + igraph_integer_t u, v, nlen, j; + float px, py, pvx, pvy; + if (!perm_pointer) { + igraph_vector_int_shuffle(&perm); + perm_pointer = no_nodes - 1; + } + v = VECTOR(perm)[perm_pointer--]; + + /* compute v's impulse */ + px = (barycenter_x / no_nodes - MATRIX(*res, v, 0)) * gamma * VECTOR(phi)[v]; + py = (barycenter_y / no_nodes - MATRIX(*res, v, 1)) * gamma * VECTOR(phi)[v]; + px += RNG_UNIF(-32.0, 32.0); + py += RNG_UNIF(-32.0, 32.0); + + for (u = 0; u < no_nodes; u++) { + float dx, dy, dist2; + if (u == v) { + continue; + } + dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + dist2 = dx * dx + dy * dy; + if (dist2 != 0) { + px += dx * elen_des2 / dist2; + py += dy * elen_des2 / dist2; + } + } + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, v, IGRAPH_ALL)); + nlen = igraph_vector_size(&neis); + for (j = 0; j < nlen; j++) { + igraph_integer_t u = VECTOR(neis)[j]; + float dx = MATRIX(*res, v, 0) - MATRIX(*res, u, 0); + float dy = MATRIX(*res, v, 1) - MATRIX(*res, u, 1); + float dist2 = dx * dx + dy * dy; + px -= dx * dist2 / (elen_des2 * VECTOR(phi)[v]); + py -= dy * dist2 / (elen_des2 * VECTOR(phi)[v]); + } + + /* update v's position and temperature */ + if (px != 0 || py != 0) { + float plen = sqrtf(px * px + py * py); + px *= VECTOR(temp)[v] / plen; + py *= VECTOR(temp)[v] / plen; + MATRIX(*res, v, 0) += px; + MATRIX(*res, v, 1) += py; + barycenter_x += px; + barycenter_y += py; + } + + pvx = VECTOR(impulse_x)[v]; pvy = VECTOR(impulse_y)[v]; + if (pvx != 0 || pvy != 0) { + float beta = atan2f(pvy - py, pvx - px); + float sin_beta = sinf(beta); + float sign_sin_beta = (sin_beta > 0) ? 1 : ((sin_beta < 0) ? -1 : 0); + float cos_beta = cosf(beta); + float abs_cos_beta = fabsf(cos_beta); + float old_temp = VECTOR(temp)[v]; + if (sin(beta) >= sin(M_PI_2 + alpha_r / 2.0)) { + VECTOR(skew_gauge)[v] += sigma_r * sign_sin_beta; + } + if (abs_cos_beta >= cosf(alpha_o / 2.0)) { + VECTOR(temp)[v] *= sigma_o * cos_beta; + } + VECTOR(temp)[v] *= (1 - fabsf(VECTOR(skew_gauge)[v])); + if (VECTOR(temp)[v] > temp_max) { + VECTOR(temp)[v] = temp_max; + } + VECTOR(impulse_x)[v] = px; + VECTOR(impulse_y)[v] = py; + temp_global += VECTOR(temp)[v] - old_temp; + } + + maxiter--; + + } /* while temp && iter */ + + + RNG_END(); + + igraph_vector_destroy(&neis); + igraph_vector_destroy(&phi); + igraph_vector_int_destroy(&perm); + igraph_vector_float_destroy(&skew_gauge); + igraph_vector_float_destroy(&temp); + igraph_vector_float_destroy(&impulse_y); + igraph_vector_float_destroy(&impulse_x); + IGRAPH_FINALLY_CLEAN(7); + + return 0; +} diff --git a/src/layout_kk.c b/src/layout_kk.c new file mode 100644 index 0000000..7f451b5 --- /dev/null +++ b/src/layout_kk.c @@ -0,0 +1,680 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph R package. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_layout.h" +#include "igraph_interface.h" +#include "igraph_paths.h" +#include "igraph_random.h" + +/** + * \ingroup layout + * \function igraph_layout_kamada_kawai + * \brief Places the vertices on a plane according the Kamada-Kawai algorithm. + * + * + * This is a force directed layout, see Kamada, T. and Kawai, S.: An + * Algorithm for Drawing General Undirected Graphs. Information + * Processing Letters, 31/1, 7--15, 1989. + * \param graph A graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result (x-positions in column zero and + * y-positions in column one) and will be resized if needed. + * \param use_seed Boolean, whether to use the values supplied in the + * \p res argument as the initial configuration. If zero then a + * random initial configuration is used. + * \param maxiter The maximum number of iterations to perform. A reasonable + * default value is at least ten (or more) times the number of + * vertices. + * \param epsilon Stop the iteration, if the maximum delta value of the + * algorithm is smaller than still. It is safe to leave it at zero, + * and then \p maxiter iterations are performed. + * \param kkconst The Kamada-Kawai vertex attraction constant. + * Typical value: number of vertices. + * \param weights Edge weights, larger values will result longer edges. + * \param minx Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote x \endquote coordinate for every vertex. + * \param maxx Same as \p minx, but the maximum \quote x \endquote + * coordinates. + * \param miny Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote y \endquote coordinate for every vertex. + * \param maxy Same as \p miny, but the maximum \quote y \endquote + * coordinates. + * \return Error code. + * + * Time complexity: O(|V|) for each iteration, after an O(|V|^2 + * log|V|) initialization step. |V| is the number of vertices in the + * graph. + */ + +int igraph_layout_kamada_kawai(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_integer_t maxiter, + igraph_real_t epsilon, igraph_real_t kkconst, + const igraph_vector_t *weights, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy) { + + igraph_integer_t no_nodes = igraph_vcount(graph); + igraph_integer_t no_edges = igraph_ecount(graph); + igraph_real_t L, L0 = sqrt(no_nodes); + igraph_matrix_t dij, lij, kij; + igraph_real_t max_dij; + igraph_vector_t D1, D2; + igraph_integer_t i, j, m; + + if (maxiter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negatice in " + "Kamada-Kawai layout", IGRAPH_EINVAL); + } + if (kkconst <= 0) { + IGRAPH_ERROR("`K' constant must be positive in Kamada-Kawai layout", + IGRAPH_EINVAL); + } + + if (use_seed && (igraph_matrix_nrow(res) != no_nodes || + igraph_matrix_ncol(res) != 2)) { + IGRAPH_ERROR("Invalid start position matrix size in " + "Kamada-Kawai layout", IGRAPH_EINVAL); + } + if (weights && igraph_vector_size(weights) != no_edges) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + if (minx && igraph_vector_size(minx) != no_nodes) { + IGRAPH_ERROR("Invalid minx vector length", IGRAPH_EINVAL); + } + if (maxx && igraph_vector_size(maxx) != no_nodes) { + IGRAPH_ERROR("Invalid maxx vector length", IGRAPH_EINVAL); + } + if (minx && maxx && !igraph_vector_all_le(minx, maxx)) { + IGRAPH_ERROR("minx must not be greater than maxx", IGRAPH_EINVAL); + } + if (miny && igraph_vector_size(miny) != no_nodes) { + IGRAPH_ERROR("Invalid miny vector length", IGRAPH_EINVAL); + } + if (maxy && igraph_vector_size(maxy) != no_nodes) { + IGRAPH_ERROR("Invalid maxy vector length", IGRAPH_EINVAL); + } + if (miny && maxy && !igraph_vector_all_le(miny, maxy)) { + IGRAPH_ERROR("miny must not be greater than maxy", IGRAPH_EINVAL); + } + + if (!use_seed) { + if (minx || maxx || miny || maxy) { + const igraph_real_t width = sqrt(no_nodes), height = width; + IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 2)); + RNG_BEGIN(); + for (i = 0; i < no_nodes; i++) { + igraph_real_t x1 = minx ? VECTOR(*minx)[i] : -width / 2; + igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] : width / 2; + igraph_real_t y1 = miny ? VECTOR(*miny)[i] : -height / 2; + igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] : height / 2; + if (!igraph_finite(x1)) { + x1 = -width / 2; + } + if (!igraph_finite(x2)) { + x2 = width / 2; + } + if (!igraph_finite(y1)) { + y1 = -height / 2; + } + if (!igraph_finite(y2)) { + y2 = height / 2; + } + MATRIX(*res, i, 0) = RNG_UNIF(x1, x2); + MATRIX(*res, i, 1) = RNG_UNIF(y1, y2); + } + RNG_END(); + } else { + igraph_layout_circle(graph, res, /* order= */ igraph_vss_all()); + } + } + + if (no_nodes <= 1) { + return 0; + } + + IGRAPH_MATRIX_INIT_FINALLY(&dij, no_nodes, no_nodes); + IGRAPH_MATRIX_INIT_FINALLY(&kij, no_nodes, no_nodes); + IGRAPH_MATRIX_INIT_FINALLY(&lij, no_nodes, no_nodes); + + if (weights && igraph_vector_min(weights) < 0) { + IGRAPH_CHECK(igraph_shortest_paths_bellman_ford(graph, &dij, igraph_vss_all(), + igraph_vss_all(), weights, + IGRAPH_ALL)); + } else { + + IGRAPH_CHECK(igraph_shortest_paths_dijkstra(graph, &dij, igraph_vss_all(), + igraph_vss_all(), weights, + IGRAPH_ALL)); + } + + max_dij = 0.0; + for (i = 0; i < no_nodes; i++) { + for (j = i + 1; j < no_nodes; j++) { + if (!igraph_finite(MATRIX(dij, i, j))) { + continue; + } + if (MATRIX(dij, i, j) > max_dij) { + max_dij = MATRIX(dij, i, j); + } + } + } + for (i = 0; i < no_nodes; i++) { + for (j = 0; j < no_nodes; j++) { + if (MATRIX(dij, i, j) > max_dij) { + MATRIX(dij, i, j) = max_dij; + } + } + } + + L = L0 / max_dij; + for (i = 0; i < no_nodes; i++) { + for (j = 0; j < no_nodes; j++) { + igraph_real_t tmp = MATRIX(dij, i, j) * MATRIX(dij, i, j); + if (i == j) { + continue; + } + MATRIX(kij, i, j) = kkconst / tmp; + MATRIX(lij, i, j) = L * MATRIX(dij, i, j); + } + } + + /* Initialize delta */ + IGRAPH_VECTOR_INIT_FINALLY(&D1, no_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&D2, no_nodes); + for (m = 0; m < no_nodes; m++) { + igraph_real_t myD1 = 0.0, myD2 = 0.0; + for (i = 0; i < no_nodes; i++) { + igraph_real_t dx, dy, mi_dist; + if (i == m) { + continue; + } + dx = MATRIX(*res, m, 0) - MATRIX(*res, i, 0); + dy = MATRIX(*res, m, 1) - MATRIX(*res, i, 1); + mi_dist = sqrt(dx * dx + dy * dy); + myD1 += MATRIX(kij, m, i) * (dx - MATRIX(lij, m, i) * dx / mi_dist); + myD2 += MATRIX(kij, m, i) * (dy - MATRIX(lij, m, i) * dy / mi_dist); + } + VECTOR(D1)[m] = myD1; + VECTOR(D2)[m] = myD2; + } + + for (j = 0; j < maxiter; j++) { + igraph_real_t myD1, myD2, A, B, C; + igraph_real_t max_delta, delta_x, delta_y; + igraph_real_t old_x, old_y, new_x, new_y; + + myD1 = 0.0, myD2 = 0.0, A = 0.0, B = 0.0, C = 0.0; + + /* Select maximal delta */ + m = 0; max_delta = -1; + for (i = 0; i < no_nodes; i++) { + igraph_real_t delta = (VECTOR(D1)[i] * VECTOR(D1)[i] + + VECTOR(D2)[i] * VECTOR(D2)[i]); + if (delta > max_delta) { + m = i; max_delta = delta; + } + } + if (max_delta < epsilon) { + break; + } + old_x = MATRIX(*res, m, 0); + old_y = MATRIX(*res, m, 1); + + /* Calculate D1 and D2, A, B, C */ + for (i = 0; i < no_nodes; i++) { + igraph_real_t dx, dy, dist, den; + if (i == m) { + continue; + } + dx = old_x - MATRIX(*res, i, 0); + dy = old_y - MATRIX(*res, i, 1); + dist = sqrt(dx * dx + dy * dy); + den = dist * (dx * dx + dy * dy); + A += MATRIX(kij, m, i) * (1 - MATRIX(lij, m, i) * dy * dy / den); + B += MATRIX(kij, m, i) * MATRIX(lij, m, i) * dx * dy / den; + C += MATRIX(kij, m, i) * (1 - MATRIX(lij, m, i) * dx * dx / den); + } + myD1 = VECTOR(D1)[m]; + myD2 = VECTOR(D2)[m]; + + /* Need to solve some linear equations */ + delta_y = (B * myD1 - myD2 * A) / (C * A - B * B); + delta_x = - (myD1 + B * delta_y) / A; + + new_x = old_x + delta_x; + new_y = old_y + delta_y; + + /* Limits, if given */ + if (minx && new_x < VECTOR(*minx)[m]) { + new_x = VECTOR(*minx)[m]; + } + if (maxx && new_x > VECTOR(*maxx)[m]) { + new_x = VECTOR(*maxx)[m]; + } + if (miny && new_y < VECTOR(*miny)[m]) { + new_y = VECTOR(*miny)[m]; + } + if (maxy && new_y > VECTOR(*maxy)[m]) { + new_y = VECTOR(*maxy)[m]; + } + + /* Update delta, only with/for the affected node */ + VECTOR(D1)[m] = VECTOR(D2)[m] = 0.0; + for (i = 0; i < no_nodes; i++) { + igraph_real_t old_dx, old_dy, old_mi, new_dx, new_dy, new_mi_dist, old_mi_dist; + if (i == m) { + continue; + } + old_dx = old_x - MATRIX(*res, i, 0); + old_dy = old_y - MATRIX(*res, i, 1); + old_mi_dist = sqrt(old_dx * old_dx + old_dy * old_dy); + new_dx = new_x - MATRIX(*res, i, 0); + new_dy = new_y - MATRIX(*res, i, 1); + new_mi_dist = sqrt(new_dx * new_dx + new_dy * new_dy); + + VECTOR(D1)[i] -= MATRIX(kij, m, i) * + (-old_dx + MATRIX(lij, m, i) * old_dx / old_mi_dist); + VECTOR(D2)[i] -= MATRIX(kij, m, i) * + (-old_dy + MATRIX(lij, m, i) * old_dy / old_mi_dist); + VECTOR(D1)[i] += MATRIX(kij, m, i) * + (-new_dx + MATRIX(lij, m, i) * new_dx / new_mi_dist); + VECTOR(D2)[i] += MATRIX(kij, m, i) * + (-new_dy + MATRIX(lij, m, i) * new_dy / new_mi_dist); + + VECTOR(D1)[m] += MATRIX(kij, m, i) * + (new_dx - MATRIX(lij, m, i) * new_dx / new_mi_dist); + VECTOR(D2)[m] += MATRIX(kij, m, i) * + (new_dy - MATRIX(lij, m, i) * new_dy / new_mi_dist); + } + + /* Update coordinates*/ + MATRIX(*res, m, 0) = new_x; + MATRIX(*res, m, 1) = new_y; + } + + igraph_vector_destroy(&D2); + igraph_vector_destroy(&D1); + igraph_matrix_destroy(&lij); + igraph_matrix_destroy(&kij); + igraph_matrix_destroy(&dij); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +/** + * \ingroup layout + * \function igraph_layout_kamada_kawai_3d + * \brief 3D version of the Kamada-Kawai layout generator + * + * + * This is a force directed layout, see Kamada, T. and Kawai, S.: An + * Algorithm for Drawing General Undirected Graphs. Information + * Processing Letters, 31/1, 7--15, 1989. + * \param graph A graph object. + * \param res Pointer to an initialized matrix object. This will + * contain the result (x-positions in column zero and + * y-positions in column one) and will be resized if needed. + * \param use_seed Boolean, whether to use the values supplied in the + * \p res argument as the initial configuration. If zero then a + * random initial configuration is used. + * \param maxiter The maximum number of iterations to perform. A reasonable + * default value is at least ten (or more) times the number of + * vertices. + * \param epsilon Stop the iteration, if the maximum delta value of the + * algorithm is smaller than still. It is safe to leave it at zero, + * and then \p maxiter iterations are performed. + * \param kkconst The Kamada-Kawai vertex attraction constant. + * Typical value: number of vertices. + * \param weights Edge weights, larger values will result longer edges. + * \param minx Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote x \endquote coordinate for every vertex. + * \param maxx Same as \p minx, but the maximum \quote x \endquote + * coordinates. + * \param miny Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote y \endquote coordinate for every vertex. + * \param maxy Same as \p miny, but the maximum \quote y \endquote + * coordinates. + * \param minz Pointer to a vector, or a \c NULL pointer. If not a + * \c NULL pointer then the vector gives the minimum + * \quote z \endquote coordinate for every vertex. + * \param maxz Same as \p minz, but the maximum \quote z \endquote + * coordinates. + * \return Error code. + * + * Time complexity: O(|V|) for each iteration, after an O(|V|^2 + * log|V|) initialization step. |V| is the number of vertices in the + * graph. + */ + +int igraph_layout_kamada_kawai_3d(const igraph_t *graph, igraph_matrix_t *res, + igraph_bool_t use_seed, igraph_integer_t maxiter, + igraph_real_t epsilon, igraph_real_t kkconst, + const igraph_vector_t *weights, + const igraph_vector_t *minx, const igraph_vector_t *maxx, + const igraph_vector_t *miny, const igraph_vector_t *maxy, + const igraph_vector_t *minz, const igraph_vector_t *maxz) { + + igraph_integer_t no_nodes = igraph_vcount(graph); + igraph_integer_t no_edges = igraph_ecount(graph); + igraph_real_t L, L0 = sqrt(no_nodes); + igraph_matrix_t dij, lij, kij; + igraph_real_t max_dij; + igraph_vector_t D1, D2, D3; + igraph_integer_t i, j, m; + + if (maxiter < 0) { + IGRAPH_ERROR("Number of iterations must be non-negatice in " + "Kamada-Kawai layout", IGRAPH_EINVAL); + } + if (kkconst <= 0) { + IGRAPH_ERROR("`K' constant must be positive in Kamada-Kawai layout", + IGRAPH_EINVAL); + } + + if (use_seed && (igraph_matrix_nrow(res) != no_nodes || + igraph_matrix_ncol(res) != 3)) { + IGRAPH_ERROR("Invalid start position matrix size in " + "3d Kamada-Kawai layout", IGRAPH_EINVAL); + } + if (weights && igraph_vector_size(weights) != no_edges) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + if (minx && igraph_vector_size(minx) != no_nodes) { + IGRAPH_ERROR("Invalid minx vector length", IGRAPH_EINVAL); + } + if (maxx && igraph_vector_size(maxx) != no_nodes) { + IGRAPH_ERROR("Invalid maxx vector length", IGRAPH_EINVAL); + } + if (minx && maxx && !igraph_vector_all_le(minx, maxx)) { + IGRAPH_ERROR("minx must not be greater than maxx", IGRAPH_EINVAL); + } + if (miny && igraph_vector_size(miny) != no_nodes) { + IGRAPH_ERROR("Invalid miny vector length", IGRAPH_EINVAL); + } + if (maxy && igraph_vector_size(maxy) != no_nodes) { + IGRAPH_ERROR("Invalid maxy vector length", IGRAPH_EINVAL); + } + if (miny && maxy && !igraph_vector_all_le(miny, maxy)) { + IGRAPH_ERROR("miny must not be greater than maxy", IGRAPH_EINVAL); + } + if (minz && igraph_vector_size(minz) != no_nodes) { + IGRAPH_ERROR("Invalid minz vector length", IGRAPH_EINVAL); + } + if (maxz && igraph_vector_size(maxz) != no_nodes) { + IGRAPH_ERROR("Invalid maxz vector length", IGRAPH_EINVAL); + } + if (minz && maxz && !igraph_vector_all_le(minz, maxz)) { + IGRAPH_ERROR("minz must not be greater than maxz", IGRAPH_EINVAL); + } + + if (!use_seed) { + if (minx || maxx || miny || maxy || minz || maxz) { + const igraph_real_t width = sqrt(no_nodes), height = width, depth = width; + IGRAPH_CHECK(igraph_matrix_resize(res, no_nodes, 3)); + RNG_BEGIN(); + for (i = 0; i < no_nodes; i++) { + igraph_real_t x1 = minx ? VECTOR(*minx)[i] : -width / 2; + igraph_real_t x2 = maxx ? VECTOR(*maxx)[i] : width / 2; + igraph_real_t y1 = miny ? VECTOR(*miny)[i] : -height / 2; + igraph_real_t y2 = maxy ? VECTOR(*maxy)[i] : height / 2; + igraph_real_t z1 = minz ? VECTOR(*minz)[i] : -depth / 2; + igraph_real_t z2 = maxz ? VECTOR(*maxz)[i] : depth / 2; + if (!igraph_finite(x1)) { + x1 = -width / 2; + } + if (!igraph_finite(x2)) { + x2 = width / 2; + } + if (!igraph_finite(y1)) { + y1 = -height / 2; + } + if (!igraph_finite(y2)) { + y2 = height / 2; + } + if (!igraph_finite(z1)) { + z1 = -depth / 2; + } + if (!igraph_finite(z2)) { + z2 = depth / 2; + } + MATRIX(*res, i, 0) = RNG_UNIF(x1, x2); + MATRIX(*res, i, 1) = RNG_UNIF(y1, y2); + MATRIX(*res, i, 2) = RNG_UNIF(z1, z2); + } + RNG_END(); + } else { + igraph_layout_sphere(graph, res); + } + } + + if (no_nodes <= 1) { + return 0; + } + + IGRAPH_MATRIX_INIT_FINALLY(&dij, no_nodes, no_nodes); + IGRAPH_MATRIX_INIT_FINALLY(&kij, no_nodes, no_nodes); + IGRAPH_MATRIX_INIT_FINALLY(&lij, no_nodes, no_nodes); + IGRAPH_CHECK(igraph_shortest_paths_dijkstra(graph, &dij, igraph_vss_all(), + igraph_vss_all(), weights, + IGRAPH_ALL)); + + max_dij = 0.0; + for (i = 0; i < no_nodes; i++) { + for (j = i + 1; j < no_nodes; j++) { + if (!igraph_finite(MATRIX(dij, i, j))) { + continue; + } + if (MATRIX(dij, i, j) > max_dij) { + max_dij = MATRIX(dij, i, j); + } + } + } + for (i = 0; i < no_nodes; i++) { + for (j = 0; j < no_nodes; j++) { + if (MATRIX(dij, i, j) > max_dij) { + MATRIX(dij, i, j) = max_dij; + } + } + } + + L = L0 / max_dij; + for (i = 0; i < no_nodes; i++) { + for (j = 0; j < no_nodes; j++) { + igraph_real_t tmp = MATRIX(dij, i, j) * MATRIX(dij, i, j); + if (i == j) { + continue; + } + MATRIX(kij, i, j) = kkconst / tmp; + MATRIX(lij, i, j) = L * MATRIX(dij, i, j); + } + } + + /* Initialize delta */ + IGRAPH_VECTOR_INIT_FINALLY(&D1, no_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&D2, no_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&D3, no_nodes); + for (m = 0; m < no_nodes; m++) { + igraph_real_t dx, dy, dz, mi_dist; + igraph_real_t myD1 = 0.0, myD2 = 0.0, myD3 = 0.0; + for (i = 0; i < no_nodes; i++) { + if (i == m) { + continue; + } + dx = MATRIX(*res, m, 0) - MATRIX(*res, i, 0); + dy = MATRIX(*res, m, 1) - MATRIX(*res, i, 1); + dz = MATRIX(*res, m, 2) - MATRIX(*res, i, 2); + mi_dist = sqrt(dx * dx + dy * dy + dz * dz); + myD1 += MATRIX(kij, m, i) * (dx - MATRIX(lij, m, i) * dx / mi_dist); + myD2 += MATRIX(kij, m, i) * (dy - MATRIX(lij, m, i) * dy / mi_dist); + myD3 += MATRIX(kij, m, i) * (dz - MATRIX(lij, m, i) * dz / mi_dist); + } + VECTOR(D1)[m] = myD1; + VECTOR(D2)[m] = myD2; + VECTOR(D3)[m] = myD3; + } + + for (j = 0; j < maxiter; j++) { + + igraph_real_t Ax = 0.0, Ay = 0.0, Az = 0.0; + igraph_real_t Axx = 0.0, Axy = 0.0, Axz = 0.0, Ayy = 0.0, Ayz = 0.0, Azz = 0.0; + igraph_real_t max_delta, delta_x, delta_y, delta_z; + igraph_real_t old_x, old_y, old_z, new_x, new_y, new_z; + igraph_real_t detnum; + + /* Select maximal delta */ + m = 0; max_delta = -1; + for (i = 0; i < no_nodes; i++) { + igraph_real_t delta = (VECTOR(D1)[i] * VECTOR(D1)[i] + + VECTOR(D2)[i] * VECTOR(D2)[i] + + VECTOR(D3)[i] * VECTOR(D3)[i]); + if (delta > max_delta) { + m = i; max_delta = delta; + } + } + if (max_delta < epsilon) { + break; + } + old_x = MATRIX(*res, m, 0); + old_y = MATRIX(*res, m, 1); + old_z = MATRIX(*res, m, 2); + + /* Calculate D1, D2 and D3, and other coefficients */ + for (i = 0; i < no_nodes; i++) { + igraph_real_t dx, dy, dz, dist, den, k_mi, l_mi; + if (i == m) { + continue; + } + dx = old_x - MATRIX(*res, i, 0); + dy = old_y - MATRIX(*res, i, 1); + dz = old_z - MATRIX(*res, i, 2); + dist = sqrt(dx * dx + dy * dy + dz * dz); + den = dist * (dx * dx + dy * dy + dz * dz); + k_mi = MATRIX(kij, m, i); + l_mi = MATRIX(lij, m, i); + Axx += k_mi * (1 - l_mi * (dy * dy + dz * dz) / den); + Ayy += k_mi * (1 - l_mi * (dx * dx + dz * dz) / den); + Azz += k_mi * (1 - l_mi * (dx * dx + dy * dy) / den); + Axy += k_mi * l_mi * dx * dy / den; + Axz += k_mi * l_mi * dx * dz / den; + Ayz += k_mi * l_mi * dy * dz / den; + } + Ax = -VECTOR(D1)[m]; + Ay = -VECTOR(D2)[m]; + Az = -VECTOR(D3)[m]; + + /* Need to solve some linear equations, we just use Cramer's rule */ +#define DET(a,b,c,d,e,f,g,h,i) ((a*e*i+b*f*g+c*d*h)-(c*e*g+b*d*i+a*f*h)) + + detnum = DET(Axx, Axy, Axz, Axy, Ayy, Ayz, Axz, Ayz, Azz); + delta_x = DET(Ax, Ay, Az, Axy, Ayy, Ayz, Axz, Ayz, Azz) / detnum; + delta_y = DET(Axx, Axy, Axz, Ax, Ay, Az, Axz, Ayz, Azz) / detnum; + delta_z = DET(Axx, Axy, Axz, Axy, Ayy, Ayz, Ax, Ay, Az ) / detnum; + + new_x = old_x + delta_x; + new_y = old_y + delta_y; + new_z = old_z + delta_z; + + /* Limits, if given */ + if (minx && new_x < VECTOR(*minx)[m]) { + new_x = VECTOR(*minx)[m]; + } + if (maxx && new_x > VECTOR(*maxx)[m]) { + new_x = VECTOR(*maxx)[m]; + } + if (miny && new_y < VECTOR(*miny)[m]) { + new_y = VECTOR(*miny)[m]; + } + if (maxy && new_y > VECTOR(*maxy)[m]) { + new_y = VECTOR(*maxy)[m]; + } + if (minz && new_z < VECTOR(*minz)[m]) { + new_z = VECTOR(*minz)[m]; + } + if (maxz && new_z > VECTOR(*maxz)[m]) { + new_z = VECTOR(*maxz)[m]; + } + + /* Update delta, only with/for the affected node */ + VECTOR(D1)[m] = VECTOR(D2)[m] = VECTOR(D3)[m] = 0.0; + for (i = 0; i < no_nodes; i++) { + igraph_real_t old_dx, old_dy, old_dz, old_mi_dist, new_dx, new_dy, new_dz, new_mi_dist; + if (i == m) { + continue; + } + old_dx = old_x - MATRIX(*res, i, 0); + old_dy = old_y - MATRIX(*res, i, 1); + old_dz = old_z - MATRIX(*res, i, 2); + old_mi_dist = sqrt(old_dx * old_dx + old_dy * old_dy + + old_dz * old_dz); + new_dx = new_x - MATRIX(*res, i, 0); + new_dy = new_y - MATRIX(*res, i, 1); + new_dz = new_z - MATRIX(*res, i, 2); + new_mi_dist = sqrt(new_dx * new_dx + new_dy * new_dy + + new_dz * new_dz); + + VECTOR(D1)[i] -= MATRIX(kij, m, i) * + (-old_dx + MATRIX(lij, m, i) * old_dx / old_mi_dist); + VECTOR(D2)[i] -= MATRIX(kij, m, i) * + (-old_dy + MATRIX(lij, m, i) * old_dy / old_mi_dist); + VECTOR(D3)[i] -= MATRIX(kij, m, i) * + (-old_dz + MATRIX(lij, m, i) * old_dz / old_mi_dist); + + VECTOR(D1)[i] += MATRIX(kij, m, i) * + (-new_dx + MATRIX(lij, m, i) * new_dx / new_mi_dist); + VECTOR(D2)[i] += MATRIX(kij, m, i) * + (-new_dy + MATRIX(lij, m, i) * new_dy / new_mi_dist); + VECTOR(D3)[i] += MATRIX(kij, m, i) * + (-new_dz + MATRIX(lij, m, i) * new_dz / new_mi_dist); + + VECTOR(D1)[m] += MATRIX(kij, m, i) * + (new_dx - MATRIX(lij, m, i) * new_dx / new_mi_dist); + VECTOR(D2)[m] += MATRIX(kij, m, i) * + (new_dy - MATRIX(lij, m, i) * new_dy / new_mi_dist); + VECTOR(D3)[m] += MATRIX(kij, m, i) * + (new_dz - MATRIX(lij, m, i) * new_dz / new_mi_dist); + } + + /* Update coordinates*/ + MATRIX(*res, m, 0) = new_x; + MATRIX(*res, m, 1) = new_y; + MATRIX(*res, m, 2) = new_z; + } + + igraph_vector_destroy(&D3); + igraph_vector_destroy(&D2); + igraph_vector_destroy(&D1); + igraph_matrix_destroy(&lij); + igraph_matrix_destroy(&kij); + igraph_matrix_destroy(&dij); + IGRAPH_FINALLY_CLEAN(6); + + return 0; +} diff --git a/src/lsap.c b/src/lsap.c new file mode 100644 index 0000000..a49c882 --- /dev/null +++ b/src/lsap.c @@ -0,0 +1,631 @@ + +#include "igraph_lsap.h" +#include "igraph_error.h" + +/* #include */ +#include +#include +#include /* INT_MAX */ +#include /* DBL_MAX */ +#include + +/* constants used for improving readability of code */ + +#define COVERED 1 +#define UNCOVERED 0 +#define ASSIGNED 1 +#define UNASSIGNED 0 +#define TRUE 1 +#define FALSE 0 + +#define MARKED 1 +#define UNMARKED 0 + +#define REDUCE 1 +#define NOREDUCE 0 + +typedef struct { + int n; /* order of problem */ + double **C; /* cost matrix */ + double **c; /* reduced cost matrix */ + int *s; /* assignment */ + int *f; /* column i is assigned to f[i] */ + int na; /* number of assigned items; */ + int runs; /* number of iterations */ + double cost; /* minimum cost */ + time_t rtime; /* time */ +} AP; + +/* public interface */ + +/* constructors and destructor */ +static AP *ap_create_problem(double *t, int n); +static AP *ap_create_problem_from_matrix(double **t, int n); +static AP *ap_read_problem(char *file); +static void ap_free(AP *p); + +static int ap_assignment(AP *p, int *res); +static int ap_costmatrix(AP *p, double **m); +static int ap_datamatrix(AP *p, double **m); +static int ap_iterations(AP *p); +static int ap_hungarian(AP *p); +static double ap_mincost(AP *p); +/* static void ap_print_solution(AP *p); */ +/* static void ap_show_data(AP *p); */ +static int ap_size(AP *p); +static int ap_time(AP *p); + +/* error reporting */ +/* static void ap_error(char *message); */ + +/* private functions */ +static void preprocess(AP *p); +static void preassign(AP *p); +static int cover(AP *p, int *ri, int *ci); +static void reduce(AP *p, int *ri, int *ci); + +int ap_hungarian(AP *p) { + int n; /* size of problem */ + int *ri; /* covered rows */ + int *ci; /* covered columns */ + time_t start, end; /* timer */ + int i, j, ok; + + start = time(0); + + n = p->n; + p->runs = 0; + + /* allocate memory */ + p->s = calloc(1 + n, sizeof(int)); + p->f = calloc(1 + n, sizeof(int)); + + ri = calloc(1 + n, sizeof(int)); + ci = calloc(1 + n, sizeof(int)); + + if (ri == NULL || ci == NULL || p->s == NULL || p->f == NULL) { + IGRAPH_ERROR("ap_hungarian: could not allocate memory", IGRAPH_ENOMEM); + } + + preprocess(p); + preassign(p); + + while (p->na < n) { + if (REDUCE == cover(p, ri, ci)) { + reduce(p, ri, ci); + } + ++p->runs; + } + + end = time(0); + + p->rtime = end - start; + + /* check if assignment is a permutation of (1..n) */ + for (i = 1; i <= n; i++) { + ok = 0; + for (j = 1; j <= n; j++) + if (p->s[j] == i) { + ++ok; + } + if (ok != 1) + IGRAPH_ERROR("ap_hungarian: error in assigment, is not a permutation", + IGRAPH_EINVAL); + } + + /* calculate cost of assignment */ + p->cost = 0; + for (i = 1; i <= n; i++) { + p->cost += p->C[i][p->s[i]]; + } + + /* reset result back to base-0 indexing */ + for (i = 1; i <= n; i++) { + p->s[i - 1] = p->s[i] - 1; + } + + /* free memory */ + + free(ri); + free(ci); + + return 0; +} + +/* abbreviated interface */ +int ap_assignment(AP *p, int *res) { + int i; + + if (p->s == NULL) { + ap_hungarian(p); + } + + for (i = 0; i < p->n; i++) { + res[i] = p->s[i]; + } + + return p->n; +} + + +/*******************************************************************/ +/* constructors */ +/* read data from file */ +/*******************************************************************/ + +AP *ap_read_problem(char *file) { + FILE *f; + int i, j, c; + int m, n; + double x; + double **t; + int nrow, ncol; + AP *p; + + f = fopen(file, "r"); + if (f == NULL) { + return NULL; + } + + t = (double **)malloc(sizeof(double*)); + + m = 0; + n = 0; + + nrow = 0; + ncol = 0; + + while (EOF != (i = fscanf(f, "%lf", &x))) { + if (i == 1) { + if (n == 0) { + t = (double **) realloc(t, (m + 1) * sizeof(double *)); + t[m] = (double *) malloc(sizeof(double)); + } else { + t[m] = (double *) realloc(t[m], (n + 1) * sizeof(double)); + } + + t[m][n++] = x; + + ncol = (ncol < n) ? n : ncol; + c = fgetc(f); + if (c == '\n') { + n = 0; + ++m; + nrow = (nrow < m) ? m : nrow; + } + } + } + fclose(f); + + /* prepare data */ + + if (nrow != ncol) { + /* + fprintf(stderr,"ap_read_problem: problem not quadratic\nrows =%d, cols = %d\n",nrow,ncol); + */ + igraph_warningf("ap_read_problem: problem not quadratic\nrows = %d, cols = %d\n", + __FILE__, __LINE__, -1, nrow, ncol); + return NULL; + } + + p = (AP*) malloc(sizeof(AP)); + p->n = ncol; + + p->C = (double **) malloc((1 + nrow) * sizeof(double *)); + p->c = (double **) malloc((1 + nrow) * sizeof(double *)); + if (p->C == NULL || p->c == NULL) { + return NULL; + } + + for (i = 1; i <= nrow; i++) { + p->C[i] = (double *) calloc(ncol + 1, sizeof(double)); + p->c[i] = (double *) calloc(ncol + 1, sizeof(double)); + if (p->C[i] == NULL || p->c[i] == NULL) { + return NULL; + } + } + + for (i = 1; i <= nrow; i++) + for ( j = 1; j <= ncol; j++) { + p->C[i][j] = t[i - 1][j - 1]; + p->c[i][j] = t[i - 1][j - 1]; + } + + for (i = 0; i < nrow; i++) { + free(t[i]); + } + free(t); + + p->cost = 0; + p->s = NULL; + p->f = NULL; + return p; +} + +AP *ap_create_problem_from_matrix(double **t, int n) { + int i, j; + AP *p; + + p = (AP*) malloc(sizeof(AP)); + if (p == NULL) { + return NULL; + } + + p->n = n; + + p->C = (double **) malloc((n + 1) * sizeof(double *)); + p->c = (double **) malloc((n + 1) * sizeof(double *)); + if (p->C == NULL || p->c == NULL) { + return NULL; + } + + for (i = 1; i <= n; i++) { + p->C[i] = (double *) calloc(n + 1, sizeof(double)); + p->c[i] = (double *) calloc(n + 1, sizeof(double)); + if (p->C[i] == NULL || p->c[i] == NULL) { + return NULL; + } + } + + + for (i = 1; i <= n; i++) + for ( j = 1; j <= n; j++) { + p->C[i][j] = t[i - 1][j - 1]; + p->c[i][j] = t[i - 1][j - 1]; + } + p->cost = 0; + p->s = NULL; + p->f = NULL; + return p; +} + +/* read data from vector */ +AP *ap_create_problem(double *t, int n) { + int i, j; + AP *p; + + p = (AP*) malloc(sizeof(AP)); + if (p == NULL) { + return NULL; + } + + p->n = n; + + p->C = (double **) malloc((n + 1) * sizeof(double *)); + p->c = (double **) malloc((n + 1) * sizeof(double *)); + if (p->C == NULL || p->c == NULL) { + return NULL; + } + + for (i = 1; i <= n; i++) { + p->C[i] = (double *) calloc(n + 1, sizeof(double)); + p->c[i] = (double *) calloc(n + 1, sizeof(double)); + if (p->C[i] == NULL || p->c[i] == NULL) { + return NULL; + } + } + + + for (i = 1; i <= n; i++) + for ( j = 1; j <= n; j++) { + p->C[i][j] = t[n * (j - 1) + i - 1]; + p->c[i][j] = t[n * (j - 1) + i - 1]; + } + p->cost = 0; + p->s = NULL; + p->f = NULL; + return p; +} + +/* destructor */ +void ap_free(AP *p) { + int i; + + free(p->s); + free(p->f); + + for (i = 1; i <= p->n; i++) { + free(p->C[i]); + free(p->c[i]); + } + + free(p->C); + free(p->c); + free(p); +} + +/* set + get functions */ + +/* +void ap_show_data(AP *p) +{ + int i, j; + + for(i = 1; i <= p->n; i++){ + for(j = 1; j <= p->n; j++) + printf("%6.2f ", p->c[i][j]); + printf("\n"); + } +} +*/ + +double ap_mincost(AP *p) { + if (p->s == NULL) { + ap_hungarian(p); + } + + return p->cost; +} + +int ap_size(AP *p) { + return p->n; +} + +int ap_time(AP *p) { + return (int) p->rtime; +} + +int ap_iterations(AP *p) { + return p->runs; +} + +/* +void ap_print_solution(AP *p) +{ + int i; + + printf("%d itertations, %d secs.\n",p->runs, (int)p->rtime); + printf("Min Cost: %10.4f\n",p->cost); + + for(i = 0; i < p->n; i++) + printf("%4d",p->s[i]); + printf("\n"); +} +*/ + +int ap_costmatrix(AP *p, double **m) { + int i, j; + + for (i = 0; i < p->n; i++) + for (j = 0; j < p->n; j++) { + m[i][j] = p->C[i + 1][j + 1]; + } + + return p->n; +} + +int ap_datamatrix(AP *p, double **m) { + int i, j; + + for (i = 0; i < p->n; i++) + for (j = 0; j < p->n; j++) { + m[i][j] = p->c[i + 1][j + 1]; + } + + return p->n; +} + +/* error reporting */ + +/* +void ap_error(char *message) +{ + fprintf(stderr,"%s\n",message); + exit(1); +} +*/ + +/*************************************************************/ +/* these functions are used internally */ +/* by ap_hungarian */ +/*************************************************************/ + +int cover(AP *p, int *ri, int *ci) { + int *mr, i, r; + int n; + + n = p->n; + mr = calloc(1 + p->n, sizeof(int)); + + /* reset cover indices */ + for (i = 1; i <= n; i++) { + if (p->s[i] == UNASSIGNED) { + ri[i] = UNCOVERED; + mr[i] = MARKED; + } else { + ri[i] = COVERED; + } + ci[i] = UNCOVERED; + } + + while (TRUE) { + /* find marked row */ + r = 0; + for (i = 1; i <= n; i++) + if (mr[i] == MARKED) { + r = i; + break; + } + + if (r == 0) { + break; + } + for (i = 1; i <= n; i++) + if (p->c[r][i] == 0 && ci[i] == UNCOVERED) { + if (p->f[i]) { + ri[p->f[i]] = UNCOVERED; + mr[p->f[i]] = MARKED; + ci[i] = COVERED; + } else { + if (p->s[r] == UNASSIGNED) { + ++p->na; + } + + p->f[p->s[r]] = 0; + p->f[i] = r; + p->s[r] = i; + + free(mr); + return NOREDUCE; + } + } + mr[r] = UNMARKED; + } + free(mr); + return REDUCE; +} + +void reduce(AP *p, int *ri, int *ci) { + int i, j, n; + double min; + + n = p->n; + + /* find minimum in uncovered c-matrix */ + min = DBL_MAX; + for (i = 1; i <= n; i++) + for (j = 1; j <= n; j++) + if (ri[i] == UNCOVERED && ci[j] == UNCOVERED) { + if (p->c[i][j] < min) { + min = p->c[i][j]; + } + } + + /* subtract min from each uncovered element and add it to each element */ + /* which is covered twice */ + for (i = 1; i <= n; i++) + for (j = 1; j <= n; j++) { + if (ri[i] == UNCOVERED && ci[j] == UNCOVERED) { + p->c[i][j] -= min; + } + if (ri[i] == COVERED && ci[j] == COVERED) { + p->c[i][j] += min; + } + } +} + +void preassign(AP *p) { + int i, j, min, r, c, n, count; + int *ri, *ci, *rz, *cz; + + n = p->n; + p->na = 0; + + /* row and column markers */ + ri = calloc(1 + n, sizeof(int)); + ci = calloc(1 + n, sizeof(int)); + + /* row and column counts of zeroes */ + rz = calloc(1 + n, sizeof(int)); + cz = calloc(1 + n, sizeof(int)); + + for (i = 1; i <= n; i++) { + count = 0; + for (j = 1; j <= n; j++) + if (p->c[i][j] == 0) { + ++count; + } + rz[i] = count; + } + + for (i = 1; i <= n; i++) { + count = 0; + for (j = 1; j <= n; j++) + if (p->c[j][i] == 0) { + ++count; + } + cz[i] = count; + } + + while (TRUE) { + /* find unassigned row with least number of zeroes > 0 */ + min = INT_MAX; + r = 0; + for (i = 1; i <= n; i++) + if (rz[i] > 0 && rz[i] < min && ri[i] == UNASSIGNED) { + min = rz[i]; + r = i; + } + /* check if we are done */ + if (r == 0) { + break; + } + + /* find unassigned column in row r with least number of zeroes */ + c = 0; + min = INT_MAX; + for (i = 1; i <= n; i++) + if (p->c[r][i] == 0 && cz[i] < min && ci[i] == UNASSIGNED) { + min = cz[i]; + c = i; + } + + if (c) { + ++p->na; + p->s[r] = c; + p->f[c] = r; + + ri[r] = ASSIGNED; + ci[c] = ASSIGNED; + + /* adjust zero counts */ + cz[c] = 0; + for (i = 1; i <= n; i++) + if (p->c[i][c] == 0) { + --rz[i]; + } + } + } + + /* free memory */ + free(ri); + free(ci); + free(rz); + free(cz); +} + +void preprocess(AP *p) { + int i, j, n; + double min; + + n = p->n; + + /* subtract column minima in each row */ + for (i = 1; i <= n; i++) { + min = p->c[i][1]; + for (j = 2; j <= n; j++) + if (p->c[i][j] < min) { + min = p->c[i][j]; + } + for (j = 1; j <= n; j++) { + p->c[i][j] -= min; + } + } + + /* subtract row minima in each column */ + for (i = 1; i <= n; i++) { + min = p->c[1][i]; + for (j = 2; j <= n; j++) + if (p->c[j][i] < min) { + min = p->c[j][i]; + } + for (j = 1; j <= n; j++) { + p->c[j][i] -= min; + } + } +} + +int igraph_solve_lsap(igraph_matrix_t *c, igraph_integer_t n, + igraph_vector_int_t *p) { + AP *ap; + + IGRAPH_CHECK(igraph_vector_int_resize(p, n)); + igraph_vector_int_null(p); + + ap = ap_create_problem(&MATRIX(*c, 0, 0), n); + ap_hungarian(ap); + ap_assignment(ap, VECTOR(*p)); + ap_free(ap); + + return 0; +} diff --git a/src/matching.c b/src/matching.c new file mode 100644 index 0000000..c8c6f01 --- /dev/null +++ b/src/matching.c @@ -0,0 +1,1029 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2012 Tamas Nepusz + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_adjlist.h" +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_matching.h" +#include "igraph_structural.h" +#include "config.h" +#include + +/* #define MATCHING_DEBUG */ + +#ifdef _MSC_VER +/* MSVC does not support variadic macros */ +#include +static void debug(const char* fmt, ...) { + va_list args; + va_start(args, fmt); +#ifdef MATCHING_DEBUG + vfprintf(stderr, fmt, args); +#endif + va_end(args); +} +#else +#ifdef MATCHING_DEBUG + #define debug(...) fprintf(stderr, __VA_ARGS__) +#else + #define debug(...) +#endif +#endif + +/** + * \function igraph_is_matching + * Checks whether the given matching is valid for the given graph. + * + * This function checks a matching vector and verifies whether its length + * matches the number of vertices in the given graph, its values are between + * -1 (inclusive) and the number of vertices (exclusive), and whether there + * exists a corresponding edge in the graph for every matched vertex pair. + * For bipartite graphs, it also verifies whether the matched vertices are + * in different parts of the graph. + * + * \param graph The input graph. It can be directed but the edge directions + * will be ignored. + * \param types If the graph is bipartite and you are interested in bipartite + * matchings only, pass the vertex types here. If the graph is + * non-bipartite, simply pass \c NULL. + * \param matching The matching itself. It must be a vector where element i + * contains the ID of the vertex that vertex i is matched to, + * or -1 if vertex i is unmatched. + * \param result Pointer to a boolean variable, the result will be returned + * here. + * + * \sa \ref igraph_is_maximal_matching() if you are also interested in whether + * the matching is maximal (i.e. non-extendable). + * + * Time complexity: O(|V|+|E|) where |V| is the number of vertices and + * |E| is the number of edges. + * + * \example examples/simple/igraph_maximum_bipartite_matching.c + */ +int igraph_is_matching(const igraph_t* graph, + const igraph_vector_bool_t* types, const igraph_vector_long_t* matching, + igraph_bool_t* result) { + long int i, j, no_of_nodes = igraph_vcount(graph); + igraph_bool_t conn; + + /* Checking match vector length */ + if (igraph_vector_long_size(matching) != no_of_nodes) { + *result = 0; return IGRAPH_SUCCESS; + } + + for (i = 0; i < no_of_nodes; i++) { + j = VECTOR(*matching)[i]; + + /* Checking range of each element in the match vector */ + if (j < -1 || j >= no_of_nodes) { + *result = 0; return IGRAPH_SUCCESS; + } + /* When i is unmatched, we're done */ + if (j == -1) { + continue; + } + /* Matches must be mutual */ + if (VECTOR(*matching)[j] != i) { + *result = 0; return IGRAPH_SUCCESS; + } + /* Matched vertices must be connected */ + IGRAPH_CHECK(igraph_are_connected(graph, (igraph_integer_t) i, + (igraph_integer_t) j, &conn)); + if (!conn) { + /* Try the other direction -- for directed graphs */ + IGRAPH_CHECK(igraph_are_connected(graph, (igraph_integer_t) j, + (igraph_integer_t) i, &conn)); + if (!conn) { + *result = 0; return IGRAPH_SUCCESS; + } + } + } + + if (types != 0) { + /* Matched vertices must be of different types */ + for (i = 0; i < no_of_nodes; i++) { + j = VECTOR(*matching)[i]; + if (j == -1) { + continue; + } + if (VECTOR(*types)[i] == VECTOR(*types)[j]) { + *result = 0; return IGRAPH_SUCCESS; + } + } + } + + *result = 1; + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_maximal_matching + * Checks whether a matching in a graph is maximal. + * + * A matching is maximal if and only if there exists no unmatched vertex in a + * graph such that one of its neighbors is also unmatched. + * + * \param graph The input graph. It can be directed but the edge directions + * will be ignored. + * \param types If the graph is bipartite and you are interested in bipartite + * matchings only, pass the vertex types here. If the graph is + * non-bipartite, simply pass \c NULL. + * \param matching The matching itself. It must be a vector where element i + * contains the ID of the vertex that vertex i is matched to, + * or -1 if vertex i is unmatched. + * \param result Pointer to a boolean variable, the result will be returned + * here. + * + * \sa \ref igraph_is_matching() if you are only interested in whether a + * matching vector is valid for a given graph. + * + * Time complexity: O(|V|+|E|) where |V| is the number of vertices and + * |E| is the number of edges. + * + * \example examples/simple/igraph_maximum_bipartite_matching.c + */ +int igraph_is_maximal_matching(const igraph_t* graph, + const igraph_vector_bool_t* types, const igraph_vector_long_t* matching, + igraph_bool_t* result) { + long int i, j, n, no_of_nodes = igraph_vcount(graph); + igraph_vector_t neis; + igraph_bool_t valid; + + IGRAPH_CHECK(igraph_is_matching(graph, types, matching, &valid)); + if (!valid) { + *result = 0; return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + valid = 1; + for (i = 0; i < no_of_nodes; i++) { + j = VECTOR(*matching)[i]; + if (j != -1) { + continue; + } + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) i, + IGRAPH_ALL)); + n = igraph_vector_size(&neis); + for (j = 0; j < n; j++) { + if (VECTOR(*matching)[(long int)VECTOR(neis)[j]] == -1) { + if (types == 0 || + VECTOR(*types)[i] != VECTOR(*types)[(long int)VECTOR(neis)[j]]) { + valid = 0; break; + } + } + } + } + + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + + *result = valid; + return IGRAPH_SUCCESS; +} + +static int igraph_i_maximum_bipartite_matching_unweighted( + const igraph_t* graph, + const igraph_vector_bool_t* types, igraph_integer_t* matching_size, + igraph_vector_long_t* matching); +static int igraph_i_maximum_bipartite_matching_weighted( + const igraph_t* graph, + const igraph_vector_bool_t* types, igraph_integer_t* matching_size, + igraph_real_t* matching_weight, igraph_vector_long_t* matching, + const igraph_vector_t* weights, igraph_real_t eps); + +#define MATCHED(v) (VECTOR(match)[v] != -1) +#define UNMATCHED(v) (!MATCHED(v)) + +/** + * \function igraph_maximum_bipartite_matching + * Calculates a maximum matching in a bipartite graph. + * + * A matching in a bipartite graph is a partial assignment of vertices + * of the first kind to vertices of the second kind such that each vertex of + * the first kind is matched to at most one vertex of the second kind and + * vice versa, and matched vertices must be connected by an edge in the graph. + * The size (or cardinality) of a matching is the number of edges. + * A matching is a maximum matching if there exists no other matching with + * larger cardinality. For weighted graphs, a maximum matching is a matching + * whose edges have the largest possible total weight among all possible + * matchings. + * + * + * Maximum matchings in bipartite graphs are found by the push-relabel algorithm + * with greedy initialization and a global relabeling after every n/2 steps where + * n is the number of vertices in the graph. + * + * + * References: Cherkassky BV, Goldberg AV, Martin P, Setubal JC and Stolfi J: + * Augment or push: A computational study of bipartite matching and + * unit-capacity flow algorithms. ACM Journal of Experimental Algorithmics 3, + * 1998. + * + * + * Kaya K, Langguth J, Manne F and Ucar B: Experiments on push-relabel-based + * maximum cardinality matching algorithms for bipartite graphs. Technical + * Report TR/PA/11/33 of the Centre Europeen de Recherche et de Formation + * Avancee en Calcul Scientifique, 2011. + * + * \param graph The input graph. It can be directed but the edge directions + * will be ignored. + * \param types Boolean vector giving the vertex types of the graph. + * \param matching_size The size of the matching (i.e. the number of matched + * vertex pairs will be returned here). It may be \c NULL + * if you don't need this. + * \param matching_weight The weight of the matching if the edges are weighted, + * or the size of the matching again if the edges are + * unweighted. It may be \c NULL if you don't need this. + * \param matching The matching itself. It must be a vector where element i + * contains the ID of the vertex that vertex i is matched to, + * or -1 if vertex i is unmatched. + * \param weights A null pointer (=no edge weights), or a vector giving the + * weights of the edges. Note that the algorithm is stable + * only for integer weights. + * \param eps A small real number used in equality tests in the weighted + * bipartite matching algorithm. Two real numbers are considered + * equal in the algorithm if their difference is smaller than + * \c eps. This is required to avoid the accumulation of numerical + * errors. It is advised to pass a value derived from the + * \c DBL_EPSILON constant in \c float.h here. If you are + * running the algorithm with no \c weights vector, this argument + * is ignored. + * \return Error code. + * + * Time complexity: O(sqrt(|V|) |E|) for unweighted graphs (according to the + * technical report referenced above), O(|V||E|) for weighted graphs. + * + * \example examples/simple/igraph_maximum_bipartite_matching.c + */ +int igraph_maximum_bipartite_matching(const igraph_t* graph, + const igraph_vector_bool_t* types, igraph_integer_t* matching_size, + igraph_real_t* matching_weight, igraph_vector_long_t* matching, + const igraph_vector_t* weights, igraph_real_t eps) { + + /* Sanity checks */ + if (igraph_vector_bool_size(types) < igraph_vcount(graph)) { + IGRAPH_ERROR("types vector too short", IGRAPH_EINVAL); + } + if (weights && igraph_vector_size(weights) < igraph_ecount(graph)) { + IGRAPH_ERROR("weights vector too short", IGRAPH_EINVAL); + } + + if (weights == 0) { + IGRAPH_CHECK(igraph_i_maximum_bipartite_matching_unweighted(graph, types, + matching_size, matching)); + if (matching_weight != 0) { + *matching_weight = *matching_size; + } + return IGRAPH_SUCCESS; + } else { + IGRAPH_CHECK(igraph_i_maximum_bipartite_matching_weighted(graph, types, + matching_size, matching_weight, matching, weights, eps)); + return IGRAPH_SUCCESS; + } +} + +static int igraph_i_maximum_bipartite_matching_unweighted_relabel( + const igraph_t* graph, + const igraph_vector_bool_t* types, igraph_vector_t* labels, + igraph_vector_long_t* matching, igraph_bool_t smaller_set); + +/** + * Finding maximum bipartite matchings on bipartite graphs using the + * push-relabel algorithm. + * + * The implementation follows the pseudocode in Algorithm 1 of the + * following paper: + * + * Kaya K, Langguth J, Manne F and Ucar B: Experiments on push-relabel-based + * maximum cardinality matching algorithms for bipartite graphs. Technical + * Report TR/PA/11/33 of CERFACS (Centre Européen de Recherche et de Formation + * Avancée en Calcul Scientifique). + * http://www.cerfacs.fr/algor/reports/2011/TR_PA_11_33.pdf + */ +static int igraph_i_maximum_bipartite_matching_unweighted( + const igraph_t* graph, + const igraph_vector_bool_t* types, igraph_integer_t* matching_size, + igraph_vector_long_t* matching) { + long int i, j, k, n, no_of_nodes = igraph_vcount(graph); + long int num_matched; /* number of matched vertex pairs */ + igraph_vector_long_t match; /* will store the matching */ + igraph_vector_t labels; /* will store the labels */ + igraph_vector_t neis; /* used to retrieve the neighbors of a node */ + igraph_dqueue_long_t q; /* a FIFO for push ordering */ + igraph_bool_t smaller_set; /* denotes which part of the bipartite graph is smaller */ + long int label_changed = 0; /* Counter to decide when to run a global relabeling */ + long int relabeling_freq = no_of_nodes / 2; + + /* We will use: + * - FIFO push ordering + * - global relabeling frequency: n/2 steps where n is the number of nodes + * - simple greedy matching for initialization + */ + + /* (1) Initialize data structures */ + IGRAPH_CHECK(igraph_vector_long_init(&match, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &match); + IGRAPH_VECTOR_INIT_FINALLY(&labels, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_dqueue_long_init(&q, 0)); + IGRAPH_FINALLY(igraph_dqueue_long_destroy, &q); + + /* (2) Initially, every node is unmatched */ + igraph_vector_long_fill(&match, -1); + + /* (3) Find an initial matching in a greedy manner. + * At the same time, find which side of the graph is smaller. */ + num_matched = 0; j = 0; + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i]) { + j++; + } + if (MATCHED(i)) { + continue; + } + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) i, + IGRAPH_ALL)); + n = igraph_vector_size(&neis); + for (j = 0; j < n; j++) { + k = (long int) VECTOR(neis)[j]; + if (VECTOR(*types)[k] == VECTOR(*types)[i]) { + IGRAPH_ERROR("Graph is not bipartite with supplied types vector", IGRAPH_EINVAL); + } + if (UNMATCHED(k)) { + /* We match vertex i to vertex VECTOR(neis)[j] */ + VECTOR(match)[k] = i; + VECTOR(match)[i] = k; + num_matched++; + break; + } + } + } + smaller_set = (j <= no_of_nodes / 2); + + /* (4) Set the initial labeling -- lines 1 and 2 in the tech report */ + IGRAPH_CHECK(igraph_i_maximum_bipartite_matching_unweighted_relabel( + graph, types, &labels, &match, smaller_set)); + + /* (5) Fill the push queue with the unmatched nodes from the smaller set. */ + for (i = 0; i < no_of_nodes; i++) { + if (UNMATCHED(i) && VECTOR(*types)[i] == smaller_set) { + IGRAPH_CHECK(igraph_dqueue_long_push(&q, i)); + } + } + + /* (6) Main loop from the referenced tech report -- lines 4--13 */ + label_changed = 0; + while (!igraph_dqueue_long_empty(&q)) { + long int v = igraph_dqueue_long_pop(&q); /* Line 13 */ + long int u = -1, label_u = 2 * no_of_nodes; + long int w; + + if (label_changed >= relabeling_freq) { + /* Run global relabeling */ + IGRAPH_CHECK(igraph_i_maximum_bipartite_matching_unweighted_relabel( + graph, types, &labels, &match, smaller_set)); + label_changed = 0; + } + + debug("Considering vertex %ld\n", v); + + /* Line 5: find row u among the neighbors of v s.t. label(u) is minimal */ + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) v, + IGRAPH_ALL)); + n = igraph_vector_size(&neis); + for (i = 0; i < n; i++) { + if (VECTOR(labels)[(long int)VECTOR(neis)[i]] < label_u) { + u = (long int) VECTOR(neis)[i]; + label_u = (long int) VECTOR(labels)[u]; + label_changed++; + } + } + + debug(" Neighbor with smallest label: %ld (label=%ld)\n", u, label_u); + + if (label_u < no_of_nodes) { /* Line 6 */ + VECTOR(labels)[v] = VECTOR(labels)[u] + 1; /* Line 7 */ + if (MATCHED(u)) { /* Line 8 */ + w = VECTOR(match)[u]; + debug(" Vertex %ld is matched to %ld, performing a double push\n", u, w); + if (w != v) { + VECTOR(match)[u] = -1; VECTOR(match)[w] = -1; /* Line 9 */ + IGRAPH_CHECK(igraph_dqueue_long_push(&q, w)); /* Line 10 */ + debug(" Unmatching & activating vertex %ld\n", w); + num_matched--; + } + } + VECTOR(match)[u] = v; VECTOR(match)[v] = u; /* Line 11 */ + num_matched++; + VECTOR(labels)[u] += 2; /* Line 12 */ + label_changed++; + } + } + + /* Fill the output parameters */ + if (matching != 0) { + IGRAPH_CHECK(igraph_vector_long_update(matching, &match)); + } + if (matching_size != 0) { + *matching_size = (igraph_integer_t) num_matched; + } + + /* Release everything */ + igraph_dqueue_long_destroy(&q); + igraph_vector_destroy(&neis); + igraph_vector_destroy(&labels); + igraph_vector_long_destroy(&match); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +static int igraph_i_maximum_bipartite_matching_unweighted_relabel( + const igraph_t* graph, + const igraph_vector_bool_t* types, igraph_vector_t* labels, + igraph_vector_long_t* match, igraph_bool_t smaller_set) { + long int i, j, n, no_of_nodes = igraph_vcount(graph), matched_to; + igraph_dqueue_long_t q; + igraph_vector_t neis; + + debug("Running global relabeling.\n"); + + /* Set all the labels to no_of_nodes first */ + igraph_vector_fill(labels, no_of_nodes); + + /* Allocate vector for neighbors */ + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + /* Create a FIFO for the BFS and initialize it with the unmatched rows + * (i.e. members of the larger set) */ + IGRAPH_CHECK(igraph_dqueue_long_init(&q, 0)); + IGRAPH_FINALLY(igraph_dqueue_long_destroy, &q); + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i] != smaller_set && VECTOR(*match)[i] == -1) { + IGRAPH_CHECK(igraph_dqueue_long_push(&q, i)); + VECTOR(*labels)[i] = 0; + } + } + + /* Run the BFS */ + while (!igraph_dqueue_long_empty(&q)) { + long int v = igraph_dqueue_long_pop(&q); + long int w; + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) v, + IGRAPH_ALL)); + + n = igraph_vector_size(&neis); + for (j = 0; j < n; j++) { + w = (long int) VECTOR(neis)[j]; + if (VECTOR(*labels)[w] == no_of_nodes) { + VECTOR(*labels)[w] = VECTOR(*labels)[v] + 1; + matched_to = VECTOR(*match)[w]; + if (matched_to != -1 && VECTOR(*labels)[matched_to] == no_of_nodes) { + IGRAPH_CHECK(igraph_dqueue_long_push(&q, matched_to)); + VECTOR(*labels)[matched_to] = VECTOR(*labels)[w] + 1; + } + } + } + } + + igraph_dqueue_long_destroy(&q); + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + +/** + * Finding maximum bipartite matchings on bipartite graphs using the + * Hungarian algorithm (a.k.a. Kuhn-Munkres algorithm). + * + * The algorithm uses a maximum cardinality matching on a subset of + * tight edges as a starting point. This is achieved by + * \c igraph_i_maximum_bipartite_matching_unweighted on the restricted + * graph. + * + * The algorithm works reliably only if the weights are integers. The + * \c eps parameter should specity a very small number; if the slack on + * an edge falls below \c eps, it will be considered tight. If all your + * weights are integers, you can safely set \c eps to zero. + */ +static int igraph_i_maximum_bipartite_matching_weighted( + const igraph_t* graph, + const igraph_vector_bool_t* types, igraph_integer_t* matching_size, + igraph_real_t* matching_weight, igraph_vector_long_t* matching, + const igraph_vector_t* weights, igraph_real_t eps) { + long int i, j, k, n, no_of_nodes, no_of_edges; + igraph_integer_t u, v, w, msize; + igraph_t newgraph; + igraph_vector_long_t match; /* will store the matching */ + igraph_vector_t slack; /* will store the slack on each edge */ + igraph_vector_t parent; /* parent vertices during a BFS */ + igraph_vector_t vec1, vec2; /* general temporary vectors */ + igraph_vector_t labels; /* will store the labels */ + igraph_dqueue_long_t q; /* a FIFO for BST */ + igraph_bool_t smaller_set_type; /* denotes which part of the bipartite graph is smaller */ + igraph_vector_t smaller_set; /* stores the vertex IDs of the smaller set */ + igraph_vector_t larger_set; /* stores the vertex IDs of the larger set */ + long int smaller_set_size; /* size of the smaller set */ + long int larger_set_size; /* size of the larger set */ + igraph_real_t dual; /* solution of the dual problem */ + igraph_adjlist_t tight_phantom_edges; /* adjacency list to manage tight phantom edges */ + igraph_integer_t alternating_path_endpoint; + igraph_vector_int_t* neis; + igraph_vector_int_t *neis2; + igraph_inclist_t inclist; /* incidence list of the original graph */ + + /* The Hungarian algorithm is originally for complete bipartite graphs. + * For non-complete bipartite graphs, a phantom edge of weight zero must be + * added between every pair of non-connected vertices. We don't do this + * explicitly of course. See the comments below about how phantom edges + * are taken into account. */ + + no_of_nodes = igraph_vcount(graph); + no_of_edges = igraph_ecount(graph); + if (eps < 0) { + IGRAPH_WARNING("negative epsilon given, clamping to zero"); + eps = 0; + } + + /* (1) Initialize data structures */ + IGRAPH_CHECK(igraph_vector_long_init(&match, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &match); + IGRAPH_CHECK(igraph_vector_init(&slack, no_of_edges)); + IGRAPH_FINALLY(igraph_vector_destroy, &slack); + IGRAPH_VECTOR_INIT_FINALLY(&vec1, 0); + IGRAPH_VECTOR_INIT_FINALLY(&vec2, 0); + IGRAPH_VECTOR_INIT_FINALLY(&labels, no_of_nodes); + IGRAPH_CHECK(igraph_dqueue_long_init(&q, 0)); + IGRAPH_FINALLY(igraph_dqueue_long_destroy, &q); + IGRAPH_VECTOR_INIT_FINALLY(&parent, no_of_nodes); + IGRAPH_CHECK(igraph_adjlist_init_empty(&tight_phantom_edges, + (igraph_integer_t) no_of_nodes)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &tight_phantom_edges); + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + IGRAPH_VECTOR_INIT_FINALLY(&smaller_set, 0); + IGRAPH_VECTOR_INIT_FINALLY(&larger_set, 0); + + /* (2) Find which set is the smaller one */ + j = 0; + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i] == 0) { + j++; + } + } + smaller_set_type = (j > no_of_nodes / 2); + smaller_set_size = smaller_set_type ? (no_of_nodes - j) : j; + larger_set_size = no_of_nodes - smaller_set_size; + IGRAPH_CHECK(igraph_vector_reserve(&smaller_set, smaller_set_size)); + IGRAPH_CHECK(igraph_vector_reserve(&larger_set, larger_set_size)); + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*types)[i] == smaller_set_type) { + IGRAPH_CHECK(igraph_vector_push_back(&smaller_set, i)); + } else { + IGRAPH_CHECK(igraph_vector_push_back(&larger_set, i)); + } + } + + /* (3) Calculate the initial labeling and the set of tight edges. Use the + * smaller set only. Here we can assume that there are no phantom edges + * among the tight ones. */ + dual = 0; + for (i = 0; i < no_of_nodes; i++) { + igraph_real_t max_weight = 0; + + if (VECTOR(*types)[i] != smaller_set_type) { + VECTOR(labels)[i] = 0; + continue; + } + + neis = igraph_inclist_get(&inclist, i); + n = igraph_vector_int_size(neis); + for (j = 0, k = 0; j < n; j++) { + k = (long int) VECTOR(*neis)[j]; + u = IGRAPH_OTHER(graph, k, i); + if (VECTOR(*types)[u] == VECTOR(*types)[i]) { + IGRAPH_ERROR("Graph is not bipartite with supplied types vector", IGRAPH_EINVAL); + } + if (VECTOR(*weights)[k] > max_weight) { + max_weight = VECTOR(*weights)[k]; + } + } + + VECTOR(labels)[i] = max_weight; + dual += max_weight; + } + + igraph_vector_clear(&vec1); + IGRAPH_CHECK(igraph_get_edgelist(graph, &vec2, 0)); +#define IS_TIGHT(i) (VECTOR(slack)[i] <= eps) + for (i = 0, j = 0; i < no_of_edges; i++, j += 2) { + u = (igraph_integer_t) VECTOR(vec2)[j]; + v = (igraph_integer_t) VECTOR(vec2)[j + 1]; + VECTOR(slack)[i] = VECTOR(labels)[u] + VECTOR(labels)[v] - VECTOR(*weights)[i]; + if (IS_TIGHT(i)) { + IGRAPH_CHECK(igraph_vector_push_back(&vec1, u)); + IGRAPH_CHECK(igraph_vector_push_back(&vec1, v)); + } + } + igraph_vector_clear(&vec2); + + /* (4) Construct a temporary graph on which the initial maximum matching + * will be calculated (only on the subset of tight edges) */ + IGRAPH_CHECK(igraph_create(&newgraph, &vec1, + (igraph_integer_t) no_of_nodes, 0)); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + IGRAPH_CHECK(igraph_maximum_bipartite_matching(&newgraph, types, &msize, 0, &match, 0, 0)); + igraph_destroy(&newgraph); + IGRAPH_FINALLY_CLEAN(1); + + /* (5) Main loop until the matching becomes maximal */ + while (msize < smaller_set_size) { + igraph_real_t min_slack, min_slack_2; + igraph_integer_t min_slack_u, min_slack_v; + + /* (7) Fill the push queue with the unmatched nodes from the smaller set. */ + igraph_vector_clear(&vec1); + igraph_vector_clear(&vec2); + igraph_vector_fill(&parent, -1); + for (j = 0; j < smaller_set_size; j++) { + i = VECTOR(smaller_set)[j]; + if (UNMATCHED(i)) { + IGRAPH_CHECK(igraph_dqueue_long_push(&q, i)); + VECTOR(parent)[i] = i; + IGRAPH_CHECK(igraph_vector_push_back(&vec1, i)); + } + } + +#ifdef MATCHING_DEBUG + debug("Matching:"); + igraph_vector_long_print(&match); + debug("Unmatched vertices are marked by non-negative numbers:\n"); + igraph_vector_print(&parent); + debug("Labeling:"); + igraph_vector_print(&labels); + debug("Slacks:"); + igraph_vector_print(&slack); +#endif + + /* (8) Run the BFS */ + alternating_path_endpoint = -1; + while (!igraph_dqueue_long_empty(&q)) { + v = (int) igraph_dqueue_long_pop(&q); + + debug("Considering vertex %ld\n", (long int)v); + + /* v is always in the smaller set. Find the neighbors of v, which + * are all in the larger set. Find the pairs of these nodes in + * the smaller set and push them to the queue. Mark the traversed + * nodes as seen. + * + * Here we have to be careful as there are two types of incident + * edges on v: real edges and phantom ones. Real edges are + * given by igraph_inclist_get. Phantom edges are not given so we + * (ab)use an adjacency list data structure that lists the + * vertices connected to v by phantom edges only. */ + neis = igraph_inclist_get(&inclist, v); + n = igraph_vector_int_size(neis); + for (i = 0; i < n; i++) { + j = (long int) VECTOR(*neis)[i]; + /* We only care about tight edges */ + if (!IS_TIGHT(j)) { + continue; + } + /* Have we seen the other endpoint already? */ + u = IGRAPH_OTHER(graph, j, v); + if (VECTOR(parent)[u] >= 0) { + continue; + } + debug(" Reached vertex %ld via edge %ld\n", (long)u, (long)j); + VECTOR(parent)[u] = v; + IGRAPH_CHECK(igraph_vector_push_back(&vec2, u)); + w = (int) VECTOR(match)[u]; + if (w == -1) { + /* u is unmatched and it is in the larger set. Therefore, we + * could improve the matching by following the parents back + * from u to the root. + */ + alternating_path_endpoint = u; + break; /* since we don't need any more endpoints that come from v */ + } else { + IGRAPH_CHECK(igraph_dqueue_long_push(&q, w)); + VECTOR(parent)[w] = u; + } + IGRAPH_CHECK(igraph_vector_push_back(&vec1, w)); + } + + /* Now do the same with the phantom edges */ + neis2 = igraph_adjlist_get(&tight_phantom_edges, v); + n = igraph_vector_int_size(neis2); + for (i = 0; i < n; i++) { + u = (igraph_integer_t) VECTOR(*neis2)[i]; + /* Have we seen u already? */ + if (VECTOR(parent)[u] >= 0) { + continue; + } + /* Check if the edge is really tight; it might have happened that the + * edge became non-tight in the meanwhile. We do not remove these from + * tight_phantom_edges at the moment, so we check them once again here. + */ + if (fabs(VECTOR(labels)[(long int)v] + VECTOR(labels)[(long int)u]) > eps) { + continue; + } + debug(" Reached vertex %ld via tight phantom edge\n", (long)u); + VECTOR(parent)[u] = v; + IGRAPH_CHECK(igraph_vector_push_back(&vec2, u)); + w = (int) VECTOR(match)[u]; + if (w == -1) { + /* u is unmatched and it is in the larger set. Therefore, we + * could improve the matching by following the parents back + * from u to the root. + */ + alternating_path_endpoint = u; + break; /* since we don't need any more endpoints that come from v */ + } else { + IGRAPH_CHECK(igraph_dqueue_long_push(&q, w)); + VECTOR(parent)[w] = u; + } + IGRAPH_CHECK(igraph_vector_push_back(&vec1, w)); + } + } + + /* Okay; did we have an alternating path? */ + if (alternating_path_endpoint != -1) { +#ifdef MATCHING_DEBUG + debug("BFS parent tree:"); + igraph_vector_print(&parent); +#endif + /* Increase the size of the matching with the alternating path. */ + v = alternating_path_endpoint; + u = (igraph_integer_t) VECTOR(parent)[v]; + debug("Extending matching with alternating path ending in %ld.\n", (long int)v); + + while (u != v) { + w = (int) VECTOR(match)[v]; + if (w != -1) { + VECTOR(match)[w] = -1; + } + VECTOR(match)[v] = u; + + VECTOR(match)[v] = u; + w = (int) VECTOR(match)[u]; + if (w != -1) { + VECTOR(match)[w] = -1; + } + VECTOR(match)[u] = v; + + v = (igraph_integer_t) VECTOR(parent)[u]; + u = (igraph_integer_t) VECTOR(parent)[v]; + } + + msize++; + +#ifdef MATCHING_DEBUG + debug("New matching after update:"); + igraph_vector_long_print(&match); + debug("Matching size is now: %ld\n", (long)msize); +#endif + continue; + } + +#ifdef MATCHING_DEBUG + debug("Vertices reachable from unmatched ones via tight edges:\n"); + igraph_vector_print(&vec1); + igraph_vector_print(&vec2); +#endif + + /* At this point, vec1 contains the nodes in the smaller set (A) + * reachable from unmatched nodes in A via tight edges only, while vec2 + * contains the nodes in the larger set (B) reachable from unmatched + * nodes in A via tight edges only. Also, parent[i] >= 0 if node i + * is reachable */ + + /* Check the edges between reachable nodes in A and unreachable + * nodes in B, and find the minimum slack on them. + * + * Since the weights are positive, we do no harm if we first + * assume that there are no "real" edges between the two sets + * mentioned above and determine an upper bound for min_slack + * based on this. */ + min_slack = IGRAPH_INFINITY; + min_slack_u = min_slack_v = 0; + n = igraph_vector_size(&vec1); + for (j = 0; j < larger_set_size; j++) { + i = VECTOR(larger_set)[j]; + if (VECTOR(labels)[i] < min_slack) { + min_slack = VECTOR(labels)[i]; + min_slack_v = (igraph_integer_t) i; + } + } + min_slack_2 = IGRAPH_INFINITY; + for (i = 0; i < n; i++) { + u = (igraph_integer_t) VECTOR(vec1)[i]; + /* u is surely from the smaller set, but we are interested in it + * only if it is reachable from an unmatched vertex */ + if (VECTOR(parent)[u] < 0) { + continue; + } + if (VECTOR(labels)[u] < min_slack_2) { + min_slack_2 = VECTOR(labels)[u]; + min_slack_u = u; + } + } + min_slack += min_slack_2; + debug("Starting approximation for min_slack = %.4f (based on vertex pair %ld--%ld)\n", + min_slack, (long int)min_slack_u, (long int)min_slack_v); + + n = igraph_vector_size(&vec1); + for (i = 0; i < n; i++) { + u = (igraph_integer_t) VECTOR(vec1)[i]; + /* u is a reachable node in A; get its incident edges. + * + * There are two types of incident edges: 1) real edges, + * 2) phantom edges. Phantom edges were treated earlier + * when we determined the initial value for min_slack. */ + debug("Trying to expand along vertex %ld\n", (long int)u); + neis = igraph_inclist_get(&inclist, u); + k = igraph_vector_int_size(neis); + for (j = 0; j < k; j++) { + /* v is the vertex sitting at the other end of an edge incident + * on u; check whether it was reached */ + v = IGRAPH_OTHER(graph, VECTOR(*neis)[j], u); + debug(" Edge %ld -- %ld (ID=%ld)\n", (long int)u, (long int)v, (long int)VECTOR(*neis)[j]); + if (VECTOR(parent)[v] >= 0) { + /* v was reached, so we are not interested in it */ + debug(" %ld was reached, so we are not interested in it\n", (long int)v); + continue; + } + /* v is the ID of the edge from now on */ + v = (igraph_integer_t) VECTOR(*neis)[j]; + if (VECTOR(slack)[v] < min_slack) { + min_slack = VECTOR(slack)[v]; + min_slack_u = u; + min_slack_v = IGRAPH_OTHER(graph, v, u); + } + debug(" Slack of this edge: %.4f, min slack is now: %.4f\n", + VECTOR(slack)[v], min_slack); + } + } + debug("Minimum slack: %.4f on edge %d--%d\n", min_slack, (int)min_slack_u, (int)min_slack_v); + + if (min_slack > 0) { + /* Decrease the label of reachable nodes in A by min_slack. + * Also update the dual solution */ + n = igraph_vector_size(&vec1); + for (i = 0; i < n; i++) { + u = (igraph_integer_t) VECTOR(vec1)[i]; + VECTOR(labels)[u] -= min_slack; + neis = igraph_inclist_get(&inclist, u); + k = igraph_vector_int_size(neis); + for (j = 0; j < k; j++) { + debug(" Decreasing slack of edge %ld (%ld--%ld) by %.4f\n", + (long)VECTOR(*neis)[j], (long)u, + (long)IGRAPH_OTHER(graph, VECTOR(*neis)[j], u), min_slack); + VECTOR(slack)[(long int)VECTOR(*neis)[j]] -= min_slack; + } + dual -= min_slack; + } + + /* Increase the label of reachable nodes in B by min_slack. + * Also update the dual solution */ + n = igraph_vector_size(&vec2); + for (i = 0; i < n; i++) { + u = (igraph_integer_t) VECTOR(vec2)[i]; + VECTOR(labels)[u] += min_slack; + neis = igraph_inclist_get(&inclist, u); + k = igraph_vector_int_size(neis); + for (j = 0; j < k; j++) { + debug(" Increasing slack of edge %ld (%ld--%ld) by %.4f\n", + (long)VECTOR(*neis)[j], (long)u, + (long)IGRAPH_OTHER(graph, (long)VECTOR(*neis)[j], u), min_slack); + VECTOR(slack)[(long int)VECTOR(*neis)[j]] += min_slack; + } + dual += min_slack; + } + } + + /* Update the set of tight phantom edges. + * Note that we must do it even if min_slack is zero; the reason is that + * it can happen that min_slack is zero in the first step if there are + * isolated nodes in the input graph. + * + * TODO: this is O(n^2) here. Can we do it faster? */ + for (i = 0; i < smaller_set_size; i++) { + u = VECTOR(smaller_set)[i]; + for (j = 0; j < larger_set_size; j++) { + v = VECTOR(larger_set)[j]; + if (VECTOR(labels)[(long int)u] + VECTOR(labels)[(long int)v] <= eps) { + /* Tight phantom edge found. Note that we don't have to check whether + * u and v are connected; if they were, then the slack of this edge + * would be negative. */ + neis2 = igraph_adjlist_get(&tight_phantom_edges, u); + if (!igraph_vector_int_binsearch(neis2, v, &k)) { + debug("New tight phantom edge: %ld -- %ld\n", (long)u, (long)v); + IGRAPH_CHECK(igraph_vector_int_insert(neis2, k, v)); + } + } + } + } + +#ifdef MATCHING_DEBUG + debug("New labels:"); + igraph_vector_print(&labels); + debug("Slacks after updating with min_slack:"); + igraph_vector_print(&slack); +#endif + } + + /* Cleanup: remove phantom edges from the matching */ + for (i = 0; i < smaller_set_size; i++) { + u = VECTOR(smaller_set)[i]; + v = VECTOR(match)[u]; + if (v != -1) { + neis2 = igraph_adjlist_get(&tight_phantom_edges, u); + if (igraph_vector_int_binsearch(neis2, v, 0)) { + VECTOR(match)[u] = VECTOR(match)[v] = -1; + msize--; + } + } + } + + /* Fill the output parameters */ + if (matching != 0) { + IGRAPH_CHECK(igraph_vector_long_update(matching, &match)); + } + if (matching_size != 0) { + *matching_size = msize; + } + if (matching_weight != 0) { + *matching_weight = 0; + for (i = 0; i < no_of_edges; i++) { + if (IS_TIGHT(i)) { + IGRAPH_CHECK(igraph_edge(graph, (igraph_integer_t) i, &u, &v)); + if (VECTOR(match)[u] == v) { + *matching_weight += VECTOR(*weights)[i]; + } + } + } + } + + /* Release everything */ +#undef IS_TIGHT + igraph_vector_destroy(&larger_set); + igraph_vector_destroy(&smaller_set); + igraph_inclist_destroy(&inclist); + igraph_adjlist_destroy(&tight_phantom_edges); + igraph_vector_destroy(&parent); + igraph_dqueue_long_destroy(&q); + igraph_vector_destroy(&labels); + igraph_vector_destroy(&vec1); + igraph_vector_destroy(&vec2); + igraph_vector_destroy(&slack); + igraph_vector_long_destroy(&match); + IGRAPH_FINALLY_CLEAN(11); + + return IGRAPH_SUCCESS; +} + +int igraph_maximum_matching(const igraph_t* graph, igraph_integer_t* matching_size, + igraph_real_t* matching_weight, igraph_vector_long_t* matching, + const igraph_vector_t* weights) { + IGRAPH_UNUSED(graph); + IGRAPH_UNUSED(matching_size); + IGRAPH_UNUSED(matching_weight); + IGRAPH_UNUSED(matching); + IGRAPH_UNUSED(weights); + IGRAPH_ERROR("maximum matching on general graphs not implemented yet", + IGRAPH_UNIMPLEMENTED); +} + +#ifdef MATCHING_DEBUG + #undef MATCHING_DEBUG +#endif + + diff --git a/src/math.c b/src/math.c new file mode 100644 index 0000000..701fc35 --- /dev/null +++ b/src/math.c @@ -0,0 +1,324 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include +#include +#include +#include "config.h" +#include "igraph_math.h" +#include "igraph_types.h" + +#ifdef _MSC_VER + #define isinf(x) (!_finite(x) && !_isnan(x)) +#endif + +int igraph_finite(double x) { +#if HAVE_DECL_ISFINITE + return isfinite(x); +#elif HAVE_FINITE == 1 + return finite(x); +#else + /* neither finite nor isfinite work. Do we really need the AIX exception? */ +# ifdef _AIX +# include + return FINITE(x); +# else + return (!isnan(x) & (x != IGRAPH_POSINFINITY) & (x != IGRAPH_NEGINFINITY)); +# endif +#endif +} + +double igraph_log2(const double a) { + return log(a) / log(2.0); +} + +int igraph_chebyshev_init(const double *dos, int nos, double eta) { + int i, ii; + double err; + + if (nos < 1) { + return 0; + } + + err = 0.0; + i = 0; /* just to avoid compiler warnings */ + for (ii = 1; ii <= nos; ii++) { + i = nos - ii; + err += fabs(dos[i]); + if (err > eta) { + return i; + } + } + return i; +} + +double igraph_chebyshev_eval(double x, const double *a, const int n) { + double b0, b1, b2, twox; + int i; + + if (n < 1 || n > 1000) { + IGRAPH_NAN; + } + + if (x < -1.1 || x > 1.1) { + IGRAPH_NAN; + } + + twox = x * 2; + b2 = b1 = 0; + b0 = 0; + for (i = 1; i <= n; i++) { + b2 = b1; + b1 = b0; + b0 = twox * b1 - b2 + a[n - i]; + } + return (b0 - b2) * 0.5; +} + +double igraph_log1p(double x) { + /* series for log1p on the interval -.375 to .375 + * with weighted error 6.35e-32 + * log weighted error 31.20 + * significant figures required 30.93 + * decimal places required 32.01 + */ + static const double alnrcs[43] = { + +.10378693562743769800686267719098e+1, + -.13364301504908918098766041553133e+0, + +.19408249135520563357926199374750e-1, + -.30107551127535777690376537776592e-2, + +.48694614797154850090456366509137e-3, + -.81054881893175356066809943008622e-4, + +.13778847799559524782938251496059e-4, + -.23802210894358970251369992914935e-5, + +.41640416213865183476391859901989e-6, + -.73595828378075994984266837031998e-7, + +.13117611876241674949152294345011e-7, + -.23546709317742425136696092330175e-8, + +.42522773276034997775638052962567e-9, + -.77190894134840796826108107493300e-10, + +.14075746481359069909215356472191e-10, + -.25769072058024680627537078627584e-11, + +.47342406666294421849154395005938e-12, + -.87249012674742641745301263292675e-13, + +.16124614902740551465739833119115e-13, + -.29875652015665773006710792416815e-14, + +.55480701209082887983041321697279e-15, + -.10324619158271569595141333961932e-15, + +.19250239203049851177878503244868e-16, + -.35955073465265150011189707844266e-17, + +.67264542537876857892194574226773e-18, + -.12602624168735219252082425637546e-18, + +.23644884408606210044916158955519e-19, + -.44419377050807936898878389179733e-20, + +.83546594464034259016241293994666e-21, + -.15731559416479562574899253521066e-21, + +.29653128740247422686154369706666e-22, + -.55949583481815947292156013226666e-23, + +.10566354268835681048187284138666e-23, + -.19972483680670204548314999466666e-24, + +.37782977818839361421049855999999e-25, + -.71531586889081740345038165333333e-26, + +.13552488463674213646502024533333e-26, + -.25694673048487567430079829333333e-27, + +.48747756066216949076459519999999e-28, + -.92542112530849715321132373333333e-29, + +.17578597841760239233269760000000e-29, + -.33410026677731010351377066666666e-30, + +.63533936180236187354180266666666e-31, + }; + + static IGRAPH_THREAD_LOCAL int nlnrel = 0; + static IGRAPH_THREAD_LOCAL double xmin = 0.0; + + if (xmin == 0.0) { + xmin = -1 + sqrt(DBL_EPSILON); /*was sqrt(d1mach(4)); */ + } + if (nlnrel == 0) { /* initialize chebychev coefficients */ + nlnrel = igraph_chebyshev_init(alnrcs, 43, DBL_EPSILON / 20); /*was .1*d1mach(3)*/ + } + + if (x == 0.) { + return 0.; /* speed */ + } + if (x == -1) { + return (IGRAPH_NEGINFINITY); + } + if (x < -1) { + return (IGRAPH_NAN); + } + + if (fabs(x) <= .375) { + /* Improve on speed (only); + again give result accurate to IEEE double precision: */ + if (fabs(x) < .5 * DBL_EPSILON) { + return x; + } + + if ( (0 < x && x < 1e-8) || (-1e-9 < x && x < 0)) { + return x * (1 - .5 * x); + } + /* else */ + return x * (1 - x * igraph_chebyshev_eval(x / .375, alnrcs, nlnrel)); + } + /* else */ + /* if (x < xmin) { */ + /* /\* answer less than half precision because x too near -1 *\/ */ + /* ML_ERROR(ME_PRECISION, "log1p"); */ + /* } */ + return log(1 + x); +} + +long double igraph_fabsl(long double a) { + if (a < 0) { + return -a; + } else { + return a; + } +} + +double igraph_fmin(double a, double b) { + if (b < a) { + return b; + } else { + return a; + } +} + +double igraph_i_round(double X) { + + /* NaN */ + if (X != X) { + return X; + } + + if (X < 0.0) { + return floor(X); + } + + return ceil(X); +} + +#ifdef _MSC_VER +/** + * Internal function, replacement for snprintf + * Used only in case of the Microsoft Visual C compiler which does not + * provide a proper sprintf implementation. + * + * This implementation differs from the standard in the value returned + * when the number of characters needed by the output, excluding the + * terminating '\0' is larger than count + */ +int igraph_i_snprintf(char *buffer, size_t count, const char *format, ...) { + int n; + va_list args; + if (count > 0) { + va_start(args, format); + n = _vsnprintf(buffer, count, format, args); + buffer[count - 1] = 0; + va_end(args); + } else { + n = 0; + } + return n; +} + +#endif + +int igraph_is_nan(double x) { + return isnan(x); +} + +int igraph_is_inf(double x) { + return isinf(x) != 0; +} + +int igraph_is_posinf(double x) { + return isinf(x) == 1; +} + +int igraph_is_neginf(double x) { + return isinf(x) == -1; +} + +/** + * \function igraph_almost_equals + * Compare two double-precision floats with a tolerance + * + * Determines whether two double-precision floats are "almost equal" + * to each other with a given level of tolerance on the relative error. + * + * \param a the first float + * \param b the second float + * \param eps the level of tolerance on the relative error. The relative + * error is defined as \c "abs(a-b) / (abs(a) + abs(b))". The + * two numbers are considered equal if this is less than \c eps. + * + * \return nonzero if the two floats are nearly equal to each other within + * the given level of tolerance, zero otherwise + */ +int igraph_almost_equals(double a, double b, double eps) { + return igraph_cmp_epsilon(a, b, eps) == 0 ? 1 : 0; +} + + +/** + * \function igraph_cmp_epsilon + * Compare two double-precision floats with a tolerance + * + * Determines whether two double-precision floats are "almost equal" + * to each other with a given level of tolerance on the relative error. + * + * \param a the first float + * \param b the second float + * \param eps the level of tolerance on the relative error. The relative + * error is defined as \c "abs(a-b) / (abs(a) + abs(b))". The + * two numbers are considered equal if this is less than \c eps. + * + * \return zero if the two floats are nearly equal to each other within + * the given level of tolerance, positive number if the first float is + * larger, negative number if the second float is larger + */ +int igraph_cmp_epsilon(double a, double b, double eps) { + double diff; + double abs_diff; + + if (a == b) { + /* shortcut, handles infinities */ + return 0; + } + + diff = a - b; + abs_diff = fabs(diff); + + if (a == 0 || b == 0 || diff < DBL_MIN) { + /* a or b is zero or both are extremely close to it; relative + * error is less meaningful here so just compare it with + * epsilon */ + return abs_diff < (eps * DBL_MIN) ? 0 : (diff < 0 ? -1 : 1); + } else { + /* use relative error */ + return (abs_diff / (fabs(a) + fabs(b)) < eps) ? 0 : (diff < 0 ? -1 : 1); + } +} + diff --git a/src/matrix.c b/src/matrix.c new file mode 100644 index 0000000..80bd31e --- /dev/null +++ b/src/matrix.c @@ -0,0 +1,158 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_matrix.h" + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "matrix.pmt" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_INT +#include "igraph_pmt.h" +#include "matrix.pmt" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_LONG +#include "igraph_pmt.h" +#include "matrix.pmt" +#include "igraph_pmt_off.h" +#undef BASE_LONG + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "matrix.pmt" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "matrix.pmt" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_COMPLEX +#include "igraph_pmt.h" +#include "matrix.pmt" +#include "igraph_pmt_off.h" +#undef BASE_COMPLEX + +#ifndef USING_R +int igraph_matrix_complex_print(const igraph_matrix_complex_t *m) { + + long int nr = igraph_matrix_complex_nrow(m); + long int nc = igraph_matrix_complex_ncol(m); + long int i, j; + for (i = 0; i < nr; i++) { + for (j = 0; j < nc; j++) { + igraph_complex_t z = MATRIX(*m, i, j); + if (j != 0) { + putchar(' '); + } + printf("%g%+gi", IGRAPH_REAL(z), IGRAPH_IMAG(z)); + } + printf("\n"); + } + + return 0; +} +#endif + +int igraph_matrix_complex_fprint(const igraph_matrix_complex_t *m, + FILE *file) { + + long int nr = igraph_matrix_complex_nrow(m); + long int nc = igraph_matrix_complex_ncol(m); + long int i, j; + for (i = 0; i < nr; i++) { + for (j = 0; j < nc; j++) { + igraph_complex_t z = MATRIX(*m, i, j); + if (j != 0) { + fputc(' ', file); + } + fprintf(file, "%g%+gi", IGRAPH_REAL(z), IGRAPH_IMAG(z)); + } + fprintf(file, "\n"); + } + + return 0; +} + +int igraph_matrix_complex_real(const igraph_matrix_complex_t *v, + igraph_matrix_t *real) { + long int nrow = igraph_matrix_complex_nrow(v); + long int ncol = igraph_matrix_complex_ncol(v); + IGRAPH_CHECK(igraph_matrix_resize(real, nrow, ncol)); + IGRAPH_CHECK(igraph_vector_complex_real(&v->data, &real->data)); + return 0; +} + +int igraph_matrix_complex_imag(const igraph_matrix_complex_t *v, + igraph_matrix_t *imag) { + long int nrow = igraph_matrix_complex_nrow(v); + long int ncol = igraph_matrix_complex_ncol(v); + IGRAPH_CHECK(igraph_matrix_resize(imag, nrow, ncol)); + IGRAPH_CHECK(igraph_vector_complex_imag(&v->data, &imag->data)); + return 0; +} + +int igraph_matrix_complex_realimag(const igraph_matrix_complex_t *v, + igraph_matrix_t *real, + igraph_matrix_t *imag) { + long int nrow = igraph_matrix_complex_nrow(v); + long int ncol = igraph_matrix_complex_ncol(v); + IGRAPH_CHECK(igraph_matrix_resize(real, nrow, ncol)); + IGRAPH_CHECK(igraph_matrix_resize(imag, nrow, ncol)); + IGRAPH_CHECK(igraph_vector_complex_realimag(&v->data, &real->data, + &imag->data)); + return 0; +} + +int igraph_matrix_complex_create(igraph_matrix_complex_t *v, + const igraph_matrix_t *real, + const igraph_matrix_t *imag) { + IGRAPH_CHECK(igraph_vector_complex_create(&v->data, &real->data, + &imag->data)); + return 0; +} + +int igraph_matrix_complex_create_polar(igraph_matrix_complex_t *v, + const igraph_matrix_t *r, + const igraph_matrix_t *theta) { + IGRAPH_CHECK(igraph_vector_complex_create_polar(&v->data, &r->data, + &theta->data)); + return 0; +} + +igraph_bool_t igraph_matrix_all_e_tol(const igraph_matrix_t *lhs, + const igraph_matrix_t *rhs, + igraph_real_t tol) { + return igraph_vector_e_tol(&lhs->data, &rhs->data, tol); +} + +int igraph_matrix_zapsmall(igraph_matrix_t *m, igraph_real_t tol) { + return igraph_vector_zapsmall(&m->data, tol); +} diff --git a/src/matrix.pmt b/src/matrix.pmt new file mode 100644 index 0000000..00119f7 --- /dev/null +++ b/src/matrix.pmt @@ -0,0 +1,1634 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_error.h" + +#include +#include /* memcpy & co. */ +#include + +/** + * \section about_igraph_matrix_t_objects About \type igraph_matrix_t objects + * + * This type is just an interface to \type igraph_vector_t. + * + * The \type igraph_matrix_t type usually stores n + * elements in O(n) space, but not always. See the documentation of + * the vector type. + */ + +/** + * \section igraph_matrix_constructor_and_destructor Matrix constructors and + * destructors + */ + +/** + * \ingroup matrix + * \function igraph_matrix_init + * \brief Initializes a matrix. + * + * + * Every matrix needs to be initialized before using it. This is done + * by calling this function. A matrix has to be destroyed if it is not + * needed any more; see \ref igraph_matrix_destroy(). + * \param m Pointer to a not yet initialized matrix object to be + * initialized. + * \param nrow The number of rows in the matrix. + * \param ncol The number of columns in the matrix. + * \return Error code. + * + * Time complexity: usually O(n), + * n is the + * number of elements in the matrix. + */ + +int FUNCTION(igraph_matrix, init)(TYPE(igraph_matrix) *m, long int nrow, long int ncol) { + int ret1; + ret1 = FUNCTION(igraph_vector, init)(&m->data, nrow * ncol); + m->nrow = nrow; + m->ncol = ncol; + return ret1; +} + +const TYPE(igraph_matrix) *FUNCTION(igraph_matrix, view)(const TYPE(igraph_matrix) *m, + const BASE *data, + long int nrow, + long int ncol) { + TYPE(igraph_matrix) *m2 = (TYPE(igraph_matrix)*)m; + FUNCTION(igraph_vector, view)(&m2->data, data, nrow * ncol); + m2->nrow = nrow; + m2->ncol = ncol; + return m; +} + +/** + * \ingroup matrix + * \function igraph_matrix_destroy + * \brief Destroys a matrix object. + * + * + * This function frees all the memory allocated for a matrix + * object. The destroyed object needs to be reinitialized before using + * it again. + * \param m The matrix to destroy. + * + * Time complexity: operating system dependent. + */ + +void FUNCTION(igraph_matrix, destroy)(TYPE(igraph_matrix) *m) { + FUNCTION(igraph_vector, destroy)(&m->data); +} + +/** + * \ingroup matrix + * \function igraph_matrix_capacity + * \brief Returns the number of elements allocated for a matrix. + * + * Note that this might be different from the size of the matrix (as + * queried by \ref igraph_matrix_size(), and specifies how many elements + * the matrix can hold, without reallocation. + * \param v Pointer to the (previously initialized) matrix object + * to query. + * \return The allocated capacity. + * + * \sa \ref igraph_matrix_size(), \ref igraph_matrix_nrow(), + * \ref igraph_matrix_ncol(). + * + * Time complexity: O(1). + */ + +long int FUNCTION(igraph_matrix, capacity)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, capacity)(&m->data); +} + + +/** + * \section igraph_matrix_accessing_elements Accessing elements of a matrix + */ + +/** + * \ingroup matrix + * \function igraph_matrix_resize + * \brief Resizes a matrix. + * + * + * This function resizes a matrix by adding more elements to it. + * The matrix contains arbitrary data after resizing it. + * That is, after calling this function you cannot expect that element + * (i,j) in the matrix remains the + * same as before. + * \param m Pointer to an already initialized matrix object. + * \param nrow The number of rows in the resized matrix. + * \param ncol The number of columns in the resized matrix. + * \return Error code. + * + * Time complexity: O(1) if the + * matrix gets smaller, usually O(n) + * if it gets larger, n is the + * number of elements in the resized matrix. + */ + +int FUNCTION(igraph_matrix, resize)(TYPE(igraph_matrix) *m, long int nrow, long int ncol) { + FUNCTION(igraph_vector, resize)(&m->data, nrow * ncol); + m->nrow = nrow; + m->ncol = ncol; + return 0; +} + +/** + * \ingroup matrix + * \function igraph_matrix_resize_min + * \brief Deallocates unused memory for a matrix. + * + * + * Note that this function might fail if there is not enough memory + * available. + * + * + * Also note, that this function leaves the matrix intact, i.e. + * it does not destroy any of the elements. However, usually it involves + * copying the matrix in memory. + * \param m Pointer to an initialized matrix. + * \return Error code. + * + * \sa \ref igraph_matrix_resize(). + * + * Time complexity: operating system dependent. + */ + +int FUNCTION(igraph_matrix, resize_min)(TYPE(igraph_matrix) *m) { + TYPE(igraph_vector) tmp; + long int size = FUNCTION(igraph_matrix, size)(m); + long int capacity = FUNCTION(igraph_matrix, capacity)(m); + if (size == capacity) { + return 0; + } + + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(&tmp, size)); + FUNCTION(igraph_vector, update)(&tmp, &m->data); + FUNCTION(igraph_vector, destroy)(&m->data); + m->data = tmp; + + return 0; +} + + +/** + * \ingroup matrix + * \function igraph_matrix_size + * \brief The number of elements in a matrix. + * + * \param m Pointer to an initialized matrix object. + * \return The size of the matrix. + * + * Time complexity: O(1). + */ + +long int FUNCTION(igraph_matrix, size)(const TYPE(igraph_matrix) *m) { + return (m->nrow) * (m->ncol); +} + +/** + * \ingroup matrix + * \function igraph_matrix_nrow + * \brief The number of rows in a matrix. + * + * \param m Pointer to an initialized matrix object. + * \return The number of rows in the matrix. + * + * Time complexity: O(1). + */ + +long int FUNCTION(igraph_matrix, nrow)(const TYPE(igraph_matrix) *m) { + return m->nrow; +} + +/** + * \ingroup matrix + * \function igraph_matrix_ncol + * \brief The number of columns in a matrix. + * + * \param m Pointer to an initialized matrix object. + * \return The number of columns in the matrix. + * + * Time complexity: O(1). + */ + +long int FUNCTION(igraph_matrix, ncol)(const TYPE(igraph_matrix) *m) { + return m->ncol; +} + +/** + * \ingroup matrix + * \function igraph_matrix_copy_to + * \brief Copies a matrix to a regular C array. + * + * + * The matrix is copied columnwise, as this is the format most + * programs and languages use. + * The C array should be of sufficient size; there are (of course) no + * range checks. + * \param m Pointer to an initialized matrix object. + * \param to Pointer to a C array; the place to copy the data to. + * \return Error code. + * + * Time complexity: O(n), + * n is the number of + * elements in the matrix. + */ + +void FUNCTION(igraph_matrix, copy_to)(const TYPE(igraph_matrix) *m, BASE *to) { + FUNCTION(igraph_vector, copy_to)(&m->data, to); +} + +/** + * \ingroup matrix + * \function igraph_matrix_null + * \brief Sets all elements in a matrix to zero. + * + * \param m Pointer to an initialized matrix object. + * + * Time complexity: O(n), + * n is the number of elements in + * the matrix. + */ + +void FUNCTION(igraph_matrix, null)(TYPE(igraph_matrix) *m) { + FUNCTION(igraph_vector, null)(&m->data); +} + +/** + * \ingroup matrix + * \function igraph_matrix_add_cols + * \brief Adds columns to a matrix. + * \param m The matrix object. + * \param n The number of columns to add. + * \return Error code, \c IGRAPH_ENOMEM if there is + * not enough memory to perform the operation. + * + * Time complexity: linear with the number of elements of the new, + * resized matrix. + */ + +int FUNCTION(igraph_matrix, add_cols)(TYPE(igraph_matrix) *m, long int n) { + FUNCTION(igraph_matrix, resize)(m, m->nrow, m->ncol + n); + return 0; +} + +/** + * \ingroup matrix + * \function igraph_matrix_add_rows + * \brief Adds rows to a matrix. + * \param m The matrix object. + * \param n The number of rows to add. + * \return Error code, \c IGRAPH_ENOMEM if there + * isn't enough memory for the operation. + * + * Time complexity: linear with the number of elements of the new, + * resized matrix. + */ + +int FUNCTION(igraph_matrix, add_rows)(TYPE(igraph_matrix) *m, long int n) { + long int i; + FUNCTION(igraph_vector, resize)(&m->data, (m->ncol) * (m->nrow + n)); + for (i = m->ncol - 1; i >= 0; i--) { + FUNCTION(igraph_vector, move_interval2)(&m->data, (m->nrow)*i, (m->nrow) * (i + 1), + (m->nrow + n)*i); + } + m->nrow += n; + return 0; +} + +/** + * \ingroup matrix + * \function igraph_matrix_remove_col + * \brief Removes a column from a matrix. + * + * \param m The matrix object. + * \param col The column to remove. + * \return Error code, always returns with success. + * + * Time complexity: linear with the number of elements of the new, + * resized matrix. + */ + +int FUNCTION(igraph_matrix, remove_col)(TYPE(igraph_matrix) *m, long int col) { + FUNCTION(igraph_vector, remove_section)(&m->data, (m->nrow)*col, (m->nrow) * (col + 1)); + m->ncol--; + return 0; +} + +/** + * \ingroup matrix + * \function igraph_matrix_permdelete_rows + * \brief Removes rows from a matrix (for internal use). + * + * Time complexity: linear with the number of elements of the original + * matrix. + */ + +int FUNCTION(igraph_matrix, permdelete_rows)(TYPE(igraph_matrix) *m, long int *index, long int nremove) { + long int i, j; + for (j = 0; j < m->nrow; j++) { + if (index[j] != 0) { + for (i = 0; i < m->ncol; i++) { + MATRIX(*m, index[j] - 1, i) = MATRIX(*m, j, i); + } + } + } + /* Remove unnecessary elements from the end of each column */ + for (i = 0; i < m->ncol; i++) + FUNCTION(igraph_vector, remove_section)(&m->data, + (i + 1) * (m->nrow - nremove), (i + 1) * (m->nrow - nremove) + nremove); + FUNCTION(igraph_matrix, resize)(m, m->nrow - nremove, m->ncol); + + return 0; +} + +/** + * \ingroup matrix + * \function igraph_matrix_delete_rows_neg + * \brief Removes columns from a matrix (for internal use). + * + * Time complexity: linear with the number of elements of the original + * matrix. + */ + +int FUNCTION(igraph_matrix, delete_rows_neg)(TYPE(igraph_matrix) *m, + const igraph_vector_t *neg, long int nremove) { + long int i, j, idx = 0; + for (i = 0; i < m->ncol; i++) { + for (j = 0; j < m->nrow; j++) { + if (VECTOR(*neg)[j] >= 0) { + MATRIX(*m, idx++, i) = MATRIX(*m, j, i); + } + } + idx = 0; + } + FUNCTION(igraph_matrix, resize)(m, m->nrow - nremove, m->ncol); + + return 0; +} + +/** + * \ingroup matrix + * \function igraph_matrix_copy + * \brief Copies a matrix. + * + * + * Creates a matrix object by copying from an existing matrix. + * \param to Pointer to an uninitialized matrix object. + * \param from The initialized matrix object to copy. + * \return Error code, \c IGRAPH_ENOMEM if there + * isn't enough memory to allocate the new matrix. + * + * Time complexity: O(n), the number + * of elements in the matrix. + */ + +int FUNCTION(igraph_matrix, copy)(TYPE(igraph_matrix) *to, const TYPE(igraph_matrix) *from) { + to->nrow = from->nrow; + to->ncol = from->ncol; + return FUNCTION(igraph_vector, copy)(&to->data, &from->data); +} + +#ifndef NOTORDERED + +/** + * \function igraph_matrix_max + * + * Returns the maximal element of a matrix. + * \param m The matrix object. + * \return The maximum element. For empty matrix the returned value is + * undefined. + * + * Added in version 0.2. + * + * Time complexity: O(n), the number of elements in the matrix. + */ + +igraph_real_t FUNCTION(igraph_matrix, max)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, max)(&m->data); +} + +#endif + +/** + * \function igraph_matrix_scale + * + * Multiplies each element of the matrix by a constant. + * \param m The matrix. + * \param by The constant. + * + * Added in version 0.2. + * + * Time complexity: O(n), the number of elements in the matrix. + */ + +void FUNCTION(igraph_matrix, scale)(TYPE(igraph_matrix) *m, BASE by) { + FUNCTION(igraph_vector, scale)(&m->data, by); +} + +/** + * \function igraph_matrix_select_rows + * \brief Select some rows of a matrix. + * + * This function selects some rows of a matrix and returns them in a + * new matrix. The result matrix should be initialized before calling + * the function. + * \param m The input matrix. + * \param res The result matrix. It should be initialized and will be + * resized as needed. + * \param rows Vector; it contains the row indices (starting with + * zero) to extract. Note that no range checking is performed. + * \return Error code. + * + * Time complexity: O(nm), n is the number of rows, m the number of + * columns of the result matrix. + */ + +int FUNCTION(igraph_matrix, select_rows)(const TYPE(igraph_matrix) *m, + TYPE(igraph_matrix) *res, + const igraph_vector_t *rows) { + long int norows = igraph_vector_size(rows); + long int i, j, ncols = FUNCTION(igraph_matrix, ncol)(m); + + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(res, norows, ncols)); + for (i = 0; i < norows; i++) { + for (j = 0; j < ncols; j++) { + MATRIX(*res, i, j) = MATRIX(*m, (long int)VECTOR(*rows)[i], j); + } + } + + return 0; +} + +/** + * \function igraph_matrix_select_rows_cols + * \brief Select some rows and columns of a matrix. + * + * This function selects some rows and columns of a matrix and returns + * them in a new matrix. The result matrix should be initialized before + * calling the function. + * \param m The input matrix. + * \param res The result matrix. It should be initialized and will be + * resized as needed. + * \param rows Vector; it contains the row indices (starting with + * zero) to extract. Note that no range checking is performed. + * \param cols Vector; it contains the column indices (starting with + * zero) to extract. Note that no range checking is performed. + * \return Error code. + * + * Time complexity: O(nm), n is the number of rows, m the number of + * columns of the result matrix. + */ + +int FUNCTION(igraph_matrix, select_rows_cols)(const TYPE(igraph_matrix) *m, + TYPE(igraph_matrix) *res, + const igraph_vector_t *rows, + const igraph_vector_t *cols) { + long int nrows = igraph_vector_size(rows); + long int ncols = igraph_vector_size(cols); + long int i, j; + + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(res, nrows, ncols)); + for (i = 0; i < nrows; i++) { + for (j = 0; j < ncols; j++) { + MATRIX(*res, i, j) = MATRIX(*m, (long int)VECTOR(*rows)[i], + (long int)VECTOR(*cols)[j]); + } + } + + return 0; +} + +/** + * \function igraph_matrix_get_col + * \brief Select a column. + * + * Extract a column of a matrix and return it as a vector. + * \param m The input matrix. + * \param res The result will we stored in this vector. It should be + * initialized and will be resized as needed. + * \param index The index of the column to select. + * \return Error code. + * + * Time complexity: O(n), the number of rows in the matrix. + */ + +int FUNCTION(igraph_matrix, get_col)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res, + long int index) { + long int nrow = FUNCTION(igraph_matrix, nrow)(m); + + if (index >= m->ncol) { + IGRAPH_ERROR("Index out of range for selecting matrix column", IGRAPH_EINVAL); + } + IGRAPH_CHECK(FUNCTION(igraph_vector, get_interval)(&m->data, res, + nrow * index, nrow * (index + 1))); + return 0; +} + +/** + * \function igraph_matrix_sum + * \brief Sum of elements. + * + * Returns the sum of the elements of a matrix. + * \param m The input matrix. + * \return The sum of the elements. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +BASE FUNCTION(igraph_matrix, sum)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, sum)(&m->data); +} + +/** + * \function igraph_matrix_all_e + * \brief Are all elements equal? + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \return Positive integer (=true) if the elements in the \p lhs are all + * equal to the corresponding elements in \p rhs. Returns \c 0 + * (=false) if the dimensions of the matrices don't match. + * + * Time complexity: O(nm), the size of the matrices. + */ + +igraph_bool_t FUNCTION(igraph_matrix, all_e)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + FUNCTION(igraph_vector, all_e)(&lhs->data, &rhs->data); +} + +igraph_bool_t +FUNCTION(igraph_matrix, is_equal)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return FUNCTION(igraph_matrix, all_e)(lhs, rhs); +} + +#ifndef NOTORDERED + +/** + * \function igraph_matrix_all_l + * \brief Are all elements less? + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \return Positive integer (=true) if the elements in the \p lhs are all + * less than the corresponding elements in \p rhs. Returns \c 0 + * (=false) if the dimensions of the matrices don't match. + * + * Time complexity: O(nm), the size of the matrices. + */ + +igraph_bool_t FUNCTION(igraph_matrix, all_l)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + FUNCTION(igraph_vector, all_l)(&lhs->data, &rhs->data); +} + +/** + * \function igraph_matrix_all_g + * \brief Are all elements greater? + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \return Positive integer (=true) if the elements in the \p lhs are all + * greater than the corresponding elements in \p rhs. Returns \c 0 + * (=false) if the dimensions of the matrices don't match. + * + * Time complexity: O(nm), the size of the matrices. + */ + +igraph_bool_t FUNCTION(igraph_matrix, all_g)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + FUNCTION(igraph_vector, all_g)(&lhs->data, &rhs->data); +} + +/** + * \function igraph_matrix_all_le + * \brief Are all elements less or equal? + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \return Positive integer (=true) if the elements in the \p lhs are all + * less than or equal to the corresponding elements in \p + * rhs. Returns \c 0 (=false) if the dimensions of the matrices + * don't match. + * + * Time complexity: O(nm), the size of the matrices. + */ + +igraph_bool_t +FUNCTION(igraph_matrix, all_le)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + FUNCTION(igraph_vector, all_le)(&lhs->data, &rhs->data); +} + +/** + * \function igraph_matrix_all_ge + * \brief Are all elements greater or equal? + * + * \param lhs The first matrix. + * \param rhs The second matrix. + * \return Positive integer (=true) if the elements in the \p lhs are all + * greater than or equal to the corresponding elements in \p + * rhs. Returns \c 0 (=false) if the dimensions of the matrices + * don't match. + * + * Time complexity: O(nm), the size of the matrices. + */ + +igraph_bool_t +FUNCTION(igraph_matrix, all_ge)(const TYPE(igraph_matrix) *lhs, + const TYPE(igraph_matrix) *rhs) { + return lhs->ncol == rhs->ncol && lhs->nrow == rhs->nrow && + FUNCTION(igraph_vector, all_ge)(&lhs->data, &rhs->data); +} + +#endif + +#ifndef NOTORDERED + +/** + * \function igraph_matrix_maxdifference + * \brief Maximum absolute difference between two matrices. + * + * Calculate the maximum absolute difference of two matrices. Both matrices + * must be non-empty. If their dimensions differ then a warning is given and + * the comparison is performed by vectors columnwise from both matrices. + * The remaining elements in the larger vector are ignored. + * \param m1 The first matrix. + * \param m2 The second matrix. + * \return The element with the largest absolute value in \c m1 - \c m2. + * + * Time complexity: O(mn), the elements in the smaller matrix. + */ + +igraph_real_t FUNCTION(igraph_matrix, maxdifference)(const TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2) { + long int col1 = FUNCTION(igraph_matrix, ncol)(m1); + long int col2 = FUNCTION(igraph_matrix, ncol)(m2); + long int row1 = FUNCTION(igraph_matrix, nrow)(m1); + long int row2 = FUNCTION(igraph_matrix, nrow)(m2); + if (col1 != col2 || row1 != row2) { + IGRAPH_WARNING("Comparing non-conformant matrices"); + } + return FUNCTION(igraph_vector, maxdifference)(&m1->data, &m2->data); +} + +#endif + +/** + * \function igraph_matrix_transpose + * \brief Transpose a matrix. + * + * Calculate the transpose of a matrix. Note that the function + * reallocates the memory used for the matrix. + * \param m The input (and output) matrix. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +int FUNCTION(igraph_matrix, transpose)(TYPE(igraph_matrix) *m) { + long int nrow = m->nrow; + long int ncol = m->ncol; + if (nrow > 1 && ncol > 1) { + TYPE(igraph_vector) newdata; + long int i, size = nrow * ncol, mod = size - 1; + FUNCTION(igraph_vector, init)(&newdata, size); + IGRAPH_FINALLY(FUNCTION(igraph_vector, destroy), &newdata); + for (i = 0; i < size; i++) { + VECTOR(newdata)[i] = VECTOR(m->data)[ (i * nrow) % mod ]; + } + VECTOR(newdata)[size - 1] = VECTOR(m->data)[size - 1]; + FUNCTION(igraph_vector, destroy)(&m->data); + IGRAPH_FINALLY_CLEAN(1); + m->data = newdata; + } + m->nrow = ncol; + m->ncol = nrow; + + return 0; +} + +/** + * \function igraph_matrix_e + * Extract an element from a matrix. + * + * Use this if you need a function for some reason and cannot use the + * \ref MATRIX macro. Note that no range checking is performed. + * \param m The input matrix. + * \param row The row index. + * \param col The column index. + * \return The element in the given row and column. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_matrix, e)(const TYPE(igraph_matrix) *m, + long int row, long int col) { + return MATRIX(*m, row, col); +} + +/** + * \function igraph_matrix_e_ptr + * Pointer to an element of a matrix. + * + * The function returns a pointer to an element. No range checking is + * performed. + * \param m The input matrix. + * \param row The row index. + * \param col The column index. + * \return Pointer to the element in the given row and column. + * + * Time complexity: O(1). + */ + +BASE* FUNCTION(igraph_matrix, e_ptr)(const TYPE(igraph_matrix) *m, + long int row, long int col) { + return &MATRIX(*m, row, col); +} + +/** + * \function igraph_matrix_set + * Set an element. + * + * Set an element of a matrix. No range checking is performed. + * \param m The input matrix. + * \param row The row index. + * \param col The column index. + * \param value The new value of the element. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_matrix, set)(TYPE(igraph_matrix)* m, long int row, long int col, + BASE value) { + MATRIX(*m, row, col) = value; +} + +/** + * \function igraph_matrix_fill + * Fill with an element. + * + * Set the matrix to a constant matrix. + * \param m The input matrix. + * \param e The element to set. + * + * Time complexity: O(mn), the number of elements. + */ + +void FUNCTION(igraph_matrix, fill)(TYPE(igraph_matrix) *m, BASE e) { + FUNCTION(igraph_vector, fill)(&m->data, e); +} + +/** + * \function igraph_matrix_update + * Update from another matrix. + * + * This function replicates \p from in the matrix \p to. + * Note that \p to must be already initialized. + * \param to The result matrix. + * \param from The matrix to replicate; it is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +int FUNCTION(igraph_matrix, update)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from) { + + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(to, from->nrow, from->ncol)); + FUNCTION(igraph_vector, update)(&to->data, &from->data); + return 0; +} + +/** + * \function igraph_matrix_rbind + * Combine two matrices rowwise. + * + * This function places the rows of \p from below the rows of \c to + * and stores the result in \p to. The number of columns in the two + * matrices must match. + * \param to The upper matrix; the result is also stored here. + * \param from The lower matrix. It is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the newly created + * matrix. + */ + +int FUNCTION(igraph_matrix, rbind)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from) { + long int tocols = to->ncol, fromcols = from->ncol; + long int torows = to->nrow, fromrows = from->nrow; + long int offset, c, r, index, offset2; + if (tocols != fromcols) { + IGRAPH_ERROR("Cannot do rbind, number of columns do not match", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(&to->data, + tocols * (fromrows + torows))); + to->nrow += fromrows; + + offset = (tocols - 1) * fromrows; + index = tocols * torows - 1; + for (c = tocols - 1; c > 0; c--) { + for (r = 0; r < torows; r++, index--) { + VECTOR(to->data)[index + offset] = VECTOR(to->data)[index]; + } + offset -= fromrows; + } + + offset = torows; offset2 = 0; + for (c = 0; c < tocols; c++) { + memcpy(VECTOR(to->data) + offset, VECTOR(from->data) + offset2, + sizeof(BASE) * (size_t) fromrows); + offset += fromrows + torows; + offset2 += fromrows; + } + return 0; +} + +/** + * \function igraph_matrix_cbind + * Combine matrices columnwise. + * + * This function places the columns of \p from on the right of \p to, + * and stores the result in \p to. + * \param to The left matrix; the result is stored here too. + * \param from The right matrix. It is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements on the new matrix. + */ + +int FUNCTION(igraph_matrix, cbind)(TYPE(igraph_matrix) *to, + const TYPE(igraph_matrix) *from) { + + long int tocols = to->ncol, fromcols = from->ncol; + long int torows = to->nrow, fromrows = from->nrow; + if (torows != fromrows) { + IGRAPH_ERROR("Cannot do rbind, number of rows do not match", IGRAPH_EINVAL); + } + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(to, torows, tocols + fromcols)); + FUNCTION(igraph_vector, copy_to)(&from->data, VECTOR(to->data) + tocols * torows); + return 0; +} + +/** + * \function igraph_matrix_swap + * Swap two matrices. + * + * The contents of the two matrices will be swapped. They must have the + * same dimensions. + * \param m1 The first matrix. + * \param m2 The second matrix. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the matrices. + */ + +int FUNCTION(igraph_matrix, swap)(TYPE(igraph_matrix) *m1, TYPE(igraph_matrix) *m2) { + if (m1->nrow != m2->nrow || m1->ncol != m2->ncol) { + IGRAPH_ERROR("Cannot swap non-conformant matrices", IGRAPH_EINVAL); + } + return FUNCTION(igraph_vector, swap)(&m1->data, &m2->data); +} + +/** + * \function igraph_matrix_get_row + * Extract a row. + * + * Extract a row from a matrix and return it as a vector. + * \param m The input matrix. + * \param res Pointer to an initialized vector; it will be resized if + * needed. + * \param index The index of the row to select. + * \return Error code. + * + * Time complexity: O(n), the number of columns in the matrix. + */ + +int FUNCTION(igraph_matrix, get_row)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res, long int index) { + long int rows = m->nrow, cols = m->ncol; + long int i, j; + + if (index >= rows) { + IGRAPH_ERROR("Index out of range for selecting matrix row", IGRAPH_EINVAL); + } + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(res, cols)); + + for (i = index, j = 0; j < cols; i += rows, j++) { + VECTOR(*res)[j] = VECTOR(m->data)[i]; + } + return 0; +} + +/** + * \function igraph_matrix_set_row + * Set a row from a vector. + * + * Sets the elements of a row with the given vector. This has the effect of + * setting row \c index to have the elements in the vector \c v. The length of + * the vector and the number of columns in the matrix must match, + * otherwise an error is triggered. + * \param m The input matrix. + * \param v The vector containing the new elements of the row. + * \param index Index of the row to set. + * \return Error code. + * + * Time complexity: O(n), the number of columns in the matrix. + */ + +int FUNCTION(igraph_matrix, set_row)(TYPE(igraph_matrix) *m, + const TYPE(igraph_vector) *v, long int index) { + long int rows = m->nrow, cols = m->ncol; + long int i, j; + + if (index >= rows) { + IGRAPH_ERROR("Index out of range for selecting matrix row", IGRAPH_EINVAL); + } + if (FUNCTION(igraph_vector, size)(v) != cols) { + IGRAPH_ERROR("Cannot set matrix row, invalid vector length", IGRAPH_EINVAL); + } + for (i = index, j = 0; j < cols; i += rows, j++) { + VECTOR(m->data)[i] = VECTOR(*v)[j]; + } + return 0; +} + +/** + * \function igraph_matrix_set_col + * Set a column from a vector. + * + * Sets the elements of a column with the given vector. In effect, column + * \c index will be set with elements from the vector \c v. The length of + * the vector and the number of rows in the matrix must match, + * otherwise an error is triggered. + * \param m The input matrix. + * \param v The vector containing the new elements of the column. + * \param index Index of the column to set. + * \return Error code. + * + * Time complexity: O(m), the number of rows in the matrix. + */ + +int FUNCTION(igraph_matrix, set_col)(TYPE(igraph_matrix) *m, + const TYPE(igraph_vector) *v, long int index) { + long int rows = m->nrow, cols = m->ncol; + long int i, j; + + if (index >= cols) { + IGRAPH_ERROR("Index out of range for setting matrix column", IGRAPH_EINVAL); + } + if (FUNCTION(igraph_vector, size)(v) != rows) { + IGRAPH_ERROR("Cannot set matrix column, invalid vector length", IGRAPH_EINVAL); + } + for (i = index * rows, j = 0; j < rows; i++, j++) { + VECTOR(m->data)[i] = VECTOR(*v)[j]; + } + return 0; +} + +/** + * \function igraph_matrix_swap_rows + * Swap two rows. + * + * Swap two rows in the matrix. + * \param m The input matrix. + * \param i The index of the first row. + * \param j The index of the second row. + * \return Error code. + * + * Time complexity: O(n), the number of columns. + */ + +int FUNCTION(igraph_matrix, swap_rows)(TYPE(igraph_matrix) *m, + long int i, long int j) { + long int ncol = m->ncol, nrow = m->nrow; + long int n = nrow * ncol; + long int index1, index2; + if (i >= nrow || j >= nrow) { + IGRAPH_ERROR("Cannot swap rows, index out of range", IGRAPH_EINVAL); + } + if (i == j) { + return 0; + } + for (index1 = i, index2 = j; index1 < n; index1 += nrow, index2 += nrow) { + BASE tmp; + tmp = VECTOR(m->data)[index1]; + VECTOR(m->data)[index1] = VECTOR(m->data)[index2]; + VECTOR(m->data)[index2] = tmp; + } + return 0; +} + +/** + * \function igraph_matrix_swap_cols + * Swap two columns. + * + * Swap two columns in the matrix. + * \param m The input matrix. + * \param i The index of the first column. + * \param j The index of the second column. + * \return Error code. + * + * Time complexity: O(m), the number of rows. + */ + +int FUNCTION(igraph_matrix, swap_cols)(TYPE(igraph_matrix) *m, + long int i, long int j) { + long int ncol = m->ncol, nrow = m->nrow; + long int k, index1, index2; + if (i >= ncol || j >= ncol) { + IGRAPH_ERROR("Cannot swap columns, index out of range", IGRAPH_EINVAL); + } + if (i == j) { + return 0; + } + for (index1 = i * nrow, index2 = j * nrow, k = 0; k < nrow; k++, index1++, index2++) { + BASE tmp = VECTOR(m->data)[index1]; + VECTOR(m->data)[index1] = VECTOR(m->data)[index2]; + VECTOR(m->data)[index2] = tmp; + } + return 0; +} + +/** + * \function igraph_matrix_add_constant + * Add a constant to every element. + * + * \param m The input matrix. + * \param plud The constant to add. + * + * Time complexity: O(mn), the number of elements. + */ + +void FUNCTION(igraph_matrix, add_constant)(TYPE(igraph_matrix) *m, BASE plus) { + FUNCTION(igraph_vector, add_constant)(&m->data, plus); +} + +/** + * \function igraph_matrix_add + * Add two matrices. + * + * Add \p m2 to \p m1, and store the result in \p m1. The dimensions of the + * matrices must match. + * \param m1 The first matrix; the result will be stored here. + * \param m2 The second matrix; it is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +int FUNCTION(igraph_matrix, add)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2) { + if (m1->nrow != m2->nrow || m1->ncol != m2->ncol) { + IGRAPH_ERROR("Cannot add non-conformant matrices", IGRAPH_EINVAL); + } + return FUNCTION(igraph_vector, add)(&m1->data, &m2->data); +} + +/** + * \function igraph_matrix_sub + * Difference of two matrices. + * + * Subtract \p m2 from \p m1 and store the result in \p m1. + * The dimensions of the two matrices must match. + * \param m1 The first matrix; the result is stored here. + * \param m2 The second matrix; it is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +int FUNCTION(igraph_matrix, sub)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2) { + if (m1->nrow != m2->nrow || m1->ncol != m2->ncol) { + IGRAPH_ERROR("Cannot subtract non-conformant matrices", IGRAPH_EINVAL); + } + return FUNCTION(igraph_vector, sub)(&m1->data, &m2->data); +} + +/** + * \function igraph_matrix_mul_elements + * Elementwise multiplication. + * + * Multiply \p m1 by \p m2 elementwise and store the result in \p m1. + * The dimensions of the two matrices must match. + * \param m1 The first matrix; the result is stored here. + * \param m2 The second matrix; it is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +int FUNCTION(igraph_matrix, mul_elements)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2) { + if (m1->nrow != m2->nrow || m1->ncol != m2->ncol) { + IGRAPH_ERROR("Cannot multiply non-conformant matrices", IGRAPH_EINVAL); + } + return FUNCTION(igraph_vector, mul)(&m1->data, &m2->data); +} + +/** + * \function igraph_matrix_div_elements + * Elementwise division. + * + * Divide \p m1 by \p m2 elementwise and store the result in \p m1. + * The dimensions of the two matrices must match. + * \param m1 The dividend. The result is store here. + * \param m2 The divisor. It is left unchanged. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +int FUNCTION(igraph_matrix, div_elements)(TYPE(igraph_matrix) *m1, + const TYPE(igraph_matrix) *m2) { + if (m1->nrow != m2->nrow || m1->ncol != m2->ncol) { + IGRAPH_ERROR("Cannot divide non-conformant matrices", IGRAPH_EINVAL); + } + return FUNCTION(igraph_vector, div)(&m1->data, &m2->data); +} + +#ifndef NOTORDERED + +/** + * \function igraph_matrix_min + * Minimum element. + * + * Returns the smallest element of a non-empty matrix. + * \param m The input matrix. + * \return The smallest element. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_real_t FUNCTION(igraph_matrix, min)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, min)(&m->data); +} + +/** + * \function igraph_matrix_which_min + * Indices of the minimum. + * + * Gives the indices of the (first) smallest element in a non-empty + * matrix. + * \param m The matrix. + * \param i Pointer to a long int. The row index of the + * minimum is stored here. + * \param j Pointer to a long int. The column index of + * the minimum is stored here. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +int FUNCTION(igraph_matrix, which_min)(const TYPE(igraph_matrix) *m, + long int *i, long int *j) { + long int vmin = FUNCTION(igraph_vector, which_min)(&m->data); + *i = vmin % m->nrow; + *j = vmin / m->nrow; + return 0; +} + +/** + * \function igraph_matrix_which_max + * Indices of the maximum. + * + * Gives the indices of the (first) largest element in a non-empty + * matrix. + * \param m The matrix. + * \param i Pointer to a long int. The row index of the + * maximum is stored here. + * \param j Pointer to a long int. The column index of + * the maximum is stored here. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +int FUNCTION(igraph_matrix, which_max)(const TYPE(igraph_matrix) *m, + long int *i, long int *j) { + long int vmax = FUNCTION(igraph_vector, which_max)(&m->data); + *i = vmax % m->nrow; + *j = vmax / m->nrow; + return 0; +} + +/** + * \function igraph_matrix_minmax + * Minimum and maximum + * + * The maximum and minimum elements of a non-empty matrix. + * \param m The input matrix. + * \param min Pointer to a base type. The minimum is stored here. + * \param max Pointer to a base type. The maximum is stored here. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +int FUNCTION(igraph_matrix, minmax)(const TYPE(igraph_matrix) *m, + BASE *min, BASE *max) { + return FUNCTION(igraph_vector, minmax)(&m->data, min, max); +} + +/** + * \function igraph_matrix_which_minmax + * Indices of the minimum and maximum + * + * Find the positions of the smallest and largest elements of a + * non-empty matrix. + * \param m The input matrix. + * \param imin Pointer to a long int, the row index of + * the minimum is stored here. + * \param jmin Pointer to a long int, the column index of + * the minimum is stored here. + * \param imax Pointer to a long int, the row index of + * the maximum is stored here. + * \param jmax Pointer to a long int, the column index of + * the maximum is stored here. + * \return Error code. + * + * Time complexity: O(mn), the number of elements. + */ + +int FUNCTION(igraph_matrix, which_minmax)(const TYPE(igraph_matrix) *m, + long int *imin, long int *jmin, + long int *imax, long int *jmax) { + long int vmin, vmax; + FUNCTION(igraph_vector, which_minmax)(&m->data, &vmin, &vmax); + *imin = vmin % m->nrow; + *jmin = vmin / m->nrow; + *imax = vmax % m->nrow; + *jmax = vmax / m->nrow; + return 0; +} + +#endif + +/** + * \function igraph_matrix_isnull + * Check for a null matrix. + * + * Checks whether all elements are zero. + * \param m The input matrix. + * \return Boolean, \c TRUE is \p m contains only zeros and \c FALSE + * otherwise. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_bool_t FUNCTION(igraph_matrix, isnull)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, isnull)(&m->data); +} + +/** + * \function igraph_matrix_empty + * Check for an empty matrix. + * + * It is possible to have a matrix with zero rows or zero columns, or + * even both. This functions checks for these. + * \param m The input matrix. + * \return Boolean, \c TRUE if the matrix contains zero elements, and + * \c FALSE otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_matrix, empty)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, empty)(&m->data); +} + +/** + * \function igraph_matrix_is_symmetric + * Check for symmetric matrix. + * + * A non-square matrix is not symmetric by definition. + * \param m The input matrix. + * \return Boolean, \c TRUE if the matrix is square and symmetric, \c + * FALSE otherwise. + * + * Time complexity: O(mn), the number of elements. O(1) for non-square + * matrices. + */ + +igraph_bool_t FUNCTION(igraph_matrix, is_symmetric)(const TYPE(igraph_matrix) *m) { + + long int n = m->nrow; + long int r, c; + if (m->ncol != n) { + return 0; + } + for (r = 1; r < n; r++) { + for (c = 0; c < r; c++) { + BASE a1 = MATRIX(*m, r, c); + BASE a2 = MATRIX(*m, c, r); +#ifdef EQ + if (!EQ(a1, a2)) { + return 0; + } +#else + if (a1 != a2) { + return 0; + } +#endif + } + } + return 1; +} + +/** + * \function igraph_matrix_prod + * Product of the elements. + * + * Note this function can result in overflow easily, even for not too + * big matrices. + * \param m The input matrix. + * \return The product of the elements. + * + * Time complexity: O(mn), the number of elements. + */ + +BASE FUNCTION(igraph_matrix, prod)(const TYPE(igraph_matrix) *m) { + return FUNCTION(igraph_vector, prod)(&m->data); +} + +/** + * \function igraph_matrix_rowsum + * Rowwise sum. + * + * Calculate the sum of the elements in each row. + * \param m The input matrix. + * \param res Pointer to an initialized vector; the result is stored + * here. It will be resized if necessary. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +int FUNCTION(igraph_matrix, rowsum)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res) { + long int nrow = m->nrow, ncol = m->ncol; + long int r, c; + BASE sum; + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(res, nrow)); + for (r = 0; r < nrow; r++) { + sum = ZERO; + for (c = 0; c < ncol; c++) { +#ifdef SUM + SUM(sum, sum, MATRIX(*m, r, c)); +#else + sum += MATRIX(*m, r, c); +#endif + } + VECTOR(*res)[r] = sum; + } + return 0; +} + +/** + * \function igraph_matrix_colsum + * Columnwise sum. + * + * Calculate the sum of the elements in each column. + * \param m The input matrix. + * \param res Pointer to an initialized vector; the result is stored + * here. It will be resized if necessary. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +int FUNCTION(igraph_matrix, colsum)(const TYPE(igraph_matrix) *m, + TYPE(igraph_vector) *res) { + long int nrow = m->nrow, ncol = m->ncol; + long int r, c; + BASE sum; + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(res, ncol)); + for (c = 0; c < ncol; c++) { + sum = ZERO; + for (r = 0; r < nrow; r++) { +#ifdef SUM + SUM(sum, sum, MATRIX(*m, r, c)); +#else + sum += MATRIX(*m, r, c); +#endif + } + VECTOR(*res)[c] = sum; + } + return 0; +} + +/** + * \function igraph_matrix_contains + * Search for an element. + * + * Search for the given element in the matrix. + * \param m The input matrix. + * \param e The element to search for. + * \return Boolean, \c TRUE if the matrix contains \p e, \c FALSE + * otherwise. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_bool_t FUNCTION(igraph_matrix, contains)(const TYPE(igraph_matrix) *m, + BASE e) { + return FUNCTION(igraph_vector, contains)(&m->data, e); +} + +/** + * \function igraph_matrix_search + * Search from a given position. + * + * Search for an element in a matrix and start the search from the + * given position. The search is performed columnwise. + * \param m The input matrix. + * \param from The position to search from, the positions are + * enumerated columnwise. + * \param what The element to search for. + * \param pos Pointer to a long int. If the element is + * found, then this is set to the position of its first appearance. + * \param row Pointer to a long int. If the element is + * found, then this is set to its row index. + * \param col Pointer to a long int. If the element is + * found, then this is set to its column index. + * \return Boolean, \c TRUE if the element is found, \c FALSE + * otherwise. + * + * Time complexity: O(mn), the number of elements. + */ + +igraph_bool_t FUNCTION(igraph_matrix, search)(const TYPE(igraph_matrix) *m, + long int from, BASE what, + long int *pos, + long int *row, long int *col) { + igraph_bool_t find = FUNCTION(igraph_vector, search)(&m->data, from, what, pos); + if (find) { + *row = *pos % m->nrow; + *col = *pos / m->nrow; + } + return find; +} + +/** + * \function igraph_matrix_remove_row + * Remove a row. + * + * A row is removed from the matrix. + * \param m The input matrix. + * \param row The index of the row to remove. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the matrix. + */ + +int FUNCTION(igraph_matrix, remove_row)(TYPE(igraph_matrix) *m, long int row) { + + long int c, r, index = row + 1, leap = 1, n = m->nrow * m->ncol; + if (row >= m->nrow) { + IGRAPH_ERROR("Cannot remove row, index out of range", IGRAPH_EINVAL); + } + + for (c = 0; c < m->ncol; c++) { + for (r = 0; r < m->nrow - 1 && index < n; r++) { + VECTOR(m->data)[index - leap] = VECTOR(m->data)[index]; + index++; + } + leap++; + index++; + } + m->nrow--; + FUNCTION(igraph_vector, resize)(&m->data, m->nrow * m->ncol); + return 0; +} + +/** + * \function igraph_matrix_select_cols + * \brief Select some columns of a matrix. + * + * This function selects some columns of a matrix and returns them in a + * new matrix. The result matrix should be initialized before calling + * the function. + * \param m The input matrix. + * \param res The result matrix. It should be initialized and will be + * resized as needed. + * \param cols Vector; it contains the column indices (starting with + * zero) to extract. Note that no range checking is performed. + * \return Error code. + * + * Time complexity: O(nm), n is the number of rows, m the number of + * columns of the result matrix. + */ + +int FUNCTION(igraph_matrix, select_cols)(const TYPE(igraph_matrix) *m, + TYPE(igraph_matrix) *res, + const igraph_vector_t *cols) { + long int ncols = igraph_vector_size(cols); + long int nrows = m->nrow; + long int i, j; + + IGRAPH_CHECK(FUNCTION(igraph_matrix, resize)(res, nrows, ncols)); + for (i = 0; i < nrows; i++) { + for (j = 0; j < ncols; j++) { + MATRIX(*res, i, j) = MATRIX(*m, i, (long int)VECTOR(*cols)[j]); + } + } + return 0; +} + +#ifdef OUT_FORMAT + +#ifndef USING_R +int FUNCTION(igraph_matrix, print)(const TYPE(igraph_matrix) *m) { + + long int nr = FUNCTION(igraph_matrix, nrow)(m); + long int nc = FUNCTION(igraph_matrix, ncol)(m); + long int i, j; + for (i = 0; i < nr; i++) { + for (j = 0; j < nc; j++) { + if (j != 0) { + putchar(' '); + } + printf(OUT_FORMAT, MATRIX(*m, i, j)); + } + printf("\n"); + } + + return 0; +} + +int FUNCTION(igraph_matrix, printf)(const TYPE(igraph_matrix) *m, + const char *format) { + long int nr = FUNCTION(igraph_matrix, nrow)(m); + long int nc = FUNCTION(igraph_matrix, ncol)(m); + long int i, j; + for (i = 0; i < nr; i++) { + for (j = 0; j < nc; j++) { + if (j != 0) { + putchar(' '); + } + printf(format, MATRIX(*m, i, j)); + } + printf("\n"); + } + + return 0; +} + +#endif + +int FUNCTION(igraph_matrix, fprint)(const TYPE(igraph_matrix) *m, + FILE *file) { + + long int nr = FUNCTION(igraph_matrix, nrow)(m); + long int nc = FUNCTION(igraph_matrix, ncol)(m); + long int i, j; + for (i = 0; i < nr; i++) { + for (j = 0; j < nc; j++) { + if (j != 0) { + fputc(' ', file); + } + fprintf(file, OUT_FORMAT, MATRIX(*m, i, j)); + } + fprintf(file, "\n"); + } + + return 0; +} + +#endif diff --git a/src/maximal_cliques.c b/src/maximal_cliques.c new file mode 100644 index 0000000..a2584b4 --- /dev/null +++ b/src/maximal_cliques.c @@ -0,0 +1,501 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_cliques.h" +#include "igraph_constants.h" +#include "igraph_interface.h" +#include "igraph_community.h" +#include "igraph_adjlist.h" +#include "igraph_interrupt_internal.h" +#include "igraph_memory.h" +#include "igraph_progress.h" +#include "igraph_math.h" + +#define CONCAT2x(a,b) a ## b +#define CONCAT2(a,b) CONCAT2x(a,b) +#define FUNCTION(name,sfx) CONCAT2(name,sfx) + +static int igraph_i_maximal_cliques_reorder_adjlists( + const igraph_vector_int_t *PX, + int PS, int PE, int XS, int XE, + const igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist); + +static int igraph_i_maximal_cliques_select_pivot( + const igraph_vector_int_t *PX, + int PS, int PE, int XS, int XE, + const igraph_vector_int_t *pos, + const igraph_adjlist_t *adjlist, + int *pivot, + igraph_vector_int_t *nextv, + int oldPS, int oldXE); + +static int igraph_i_maximal_cliques_down( + igraph_vector_int_t *PX, + int PS, int PE, int XS, int XE, + igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist, int mynextv, + igraph_vector_int_t *R, + int *newPS, int *newXE); + +static int igraph_i_maximal_cliques_PX( + igraph_vector_int_t *PX, int PS, int *PE, + int *XS, int XE, igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist, int v, + igraph_vector_int_t *H); + +static int igraph_i_maximal_cliques_up( + igraph_vector_int_t *PX, int PS, int PE, + int XS, int XE, igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist, + igraph_vector_int_t *R, + igraph_vector_int_t *H); + +#define PRINT_PX do { \ + int j; \ + printf("PX="); \ + for (j=0; j= sPS && avneipos <= sPE) { + if (pp != avnei) { + int tmp = *avnei; + *avnei = *pp; + *pp = tmp; + } + pp++; + } + } + } + return 0; +} + +static int igraph_i_maximal_cliques_select_pivot( + const igraph_vector_int_t *PX, + int PS, int PE, int XS, int XE, + const igraph_vector_int_t *pos, + const igraph_adjlist_t *adjlist, + int *pivot, + igraph_vector_int_t *nextv, + int oldPS, int oldXE) { + igraph_vector_int_t *pivotvectneis; + int i, pivotvectlen, j, usize = -1; + int soldPS = oldPS + 1, soldXE = oldXE + 1, sPS = PS + 1, sPE = PE + 1; + + /* Choose a pivotvect, and bring up P vertices at the same time */ + for (i = PS; i <= XE; i++) { + int av = VECTOR(*PX)[i]; + igraph_vector_int_t *avneis = igraph_adjlist_get(adjlist, av); + int *avp = VECTOR(*avneis); + int avlen = igraph_vector_int_size(avneis); + int *ave = avp + avlen; + int *avnei = avp, *pp = avp; + + for (; avnei < ave; avnei++) { + int avneipos = VECTOR(*pos)[(int)(*avnei)]; + if (avneipos < soldPS || avneipos > soldXE) { + break; + } + if (avneipos >= sPS && avneipos <= sPE) { + if (pp != avnei) { + int tmp = *avnei; + *avnei = *pp; + *pp = tmp; + } + pp++; + } + } + if ((j = pp - avp) > usize) { + *pivot = av; + usize = j; + } + } + + igraph_vector_int_push_back(nextv, -1); + pivotvectneis = igraph_adjlist_get(adjlist, *pivot); + pivotvectlen = igraph_vector_int_size(pivotvectneis); + + for (j = PS; j <= PE; j++) { + int vcand = VECTOR(*PX)[j]; + igraph_bool_t nei = 0; + int k = 0; + for (k = 0; k < pivotvectlen; k++) { + int unv = VECTOR(*pivotvectneis)[k]; + int unvpos = VECTOR(*pos)[unv]; + if (unvpos < sPS || unvpos > sPE) { + break; + } + if (unv == vcand) { + nei = 1; + break; + } + } + if (!nei) { + igraph_vector_int_push_back(nextv, vcand); + } + } + + return 0; +} + +#define SWAP(p1,p2) do { \ + int v1=VECTOR(*PX)[p1]; \ + int v2=VECTOR(*PX)[p2]; \ + VECTOR(*PX)[p1] = v2; \ + VECTOR(*PX)[p2] = v1; \ + VECTOR(*pos)[v1] = (p2)+1; \ + VECTOR(*pos)[v2] = (p1)+1; \ + } while (0) + +static int igraph_i_maximal_cliques_down(igraph_vector_int_t *PX, + int PS, int PE, int XS, int XE, + igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist, int mynextv, + igraph_vector_int_t *R, + int *newPS, int *newXE) { + + igraph_vector_int_t *vneis = igraph_adjlist_get(adjlist, mynextv); + int j, vneislen = igraph_vector_int_size(vneis); + int sPS = PS + 1, sPE = PE + 1, sXS = XS + 1, sXE = XE + 1; + + *newPS = PE + 1; *newXE = XS - 1; + for (j = 0; j < vneislen; j++) { + int vnei = VECTOR(*vneis)[j]; + int vneipos = VECTOR(*pos)[vnei]; + if (vneipos >= sPS && vneipos <= sPE) { + (*newPS)--; + SWAP(vneipos - 1, *newPS); + } else if (vneipos >= sXS && vneipos <= sXE) { + (*newXE)++; + SWAP(vneipos - 1, *newXE); + } + } + + igraph_vector_int_push_back(R, mynextv); + + return 0; +} + +#undef SWAP + +static int igraph_i_maximal_cliques_PX(igraph_vector_int_t *PX, int PS, int *PE, + int *XS, int XE, igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist, int v, + igraph_vector_int_t *H) { + + int vpos = VECTOR(*pos)[v] - 1; + int tmp = VECTOR(*PX)[*PE]; + VECTOR(*PX)[vpos] = tmp; + VECTOR(*PX)[*PE] = v; + VECTOR(*pos)[v] = (*PE) + 1; + VECTOR(*pos)[tmp] = vpos + 1; + (*PE)--; (*XS)--; + igraph_vector_int_push_back(H, v); + + return 0; +} + +static int igraph_i_maximal_cliques_up(igraph_vector_int_t *PX, int PS, int PE, + int XS, int XE, igraph_vector_int_t *pos, + igraph_adjlist_t *adjlist, + igraph_vector_int_t *R, + igraph_vector_int_t *H) { + int vv; + igraph_vector_int_pop_back(R); + + while ((vv = igraph_vector_int_pop_back(H)) != -1) { + int vvpos = VECTOR(*pos)[vv]; + int tmp = VECTOR(*PX)[XS]; + VECTOR(*PX)[XS] = vv; + VECTOR(*PX)[vvpos - 1] = tmp; + VECTOR(*pos)[vv] = XS + 1; + VECTOR(*pos)[tmp] = vvpos; + PE++; XS++; + } + + return 0; +} + +/** + * \function igraph_maximal_cliques + * \brief Find all maximal cliques of a graph + * + * + * A maximal clique is a clique which can't be extended any more by + * adding a new vertex to it. + * + * + * If you are only interested in the size of the largest clique in the + * graph, use \ref igraph_clique_number() instead. + * + * + * The current implementation uses a modified Bron-Kerbosch + * algorithm to find the maximal cliques, see: David Eppstein, + * Maarten Löffler, Darren Strash: Listing All Maximal Cliques in + * Sparse Graphs in Near-Optimal Time. Algorithms and Computation, + * Lecture Notes in Computer Science Volume 6506, 2010, pp 403-414. + * + * The implementation of this function changed between + * igraph 0.5 and 0.6 and also between 0.6 and 0.7, so the order of + * the cliques and the order of vertices within the cliques will + * almost surely be different between these three versions. + * + * \param graph The input graph. + * \param res Pointer to a pointer vector, the result will be stored + * here, ie. \c res will contain pointers to \c igraph_vector_t + * objects which contain the indices of vertices involved in a clique. + * The pointer vector will be resized if needed but note that the + * objects in the pointer vector will not be freed. Note that vertices + * of a clique may be returned in arbitrary order. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \return Error code. + * + * \sa \ref igraph_maximal_independent_vertex_sets(), \ref + * igraph_clique_number() + * + * Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy + * of the graph, this is typically small for sparse graphs. + * + * \example examples/simple/igraph_maximal_cliques.c + */ + +int igraph_maximal_cliques(const igraph_t *graph, + igraph_vector_ptr_t *res, + igraph_integer_t min_size, + igraph_integer_t max_size); + +#define IGRAPH_MC_ORIG +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_ORIG + +/** + * \function igraph_maximal_cliques_count + * Count the number of maximal cliques in a graph + * + * + * The current implementation uses a modified Bron-Kerbosch + * algorithm to find the maximal cliques, see: David Eppstein, + * Maarten Löffler, Darren Strash: Listing All Maximal Cliques in + * Sparse Graphs in Near-Optimal Time. Algorithms and Computation, + * Lecture Notes in Computer Science Volume 6506, 2010, pp 403-414. + * + * \param graph The input graph. + * \param res Pointer to an \c igraph_integer_t; the number of maximal + * cliques will be stored here. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \return Error code. + * + * \sa \ref igraph_maximal_cliques(). + * + * Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy + * of the graph, this is typically small for sparse graphs. + * + * \example examples/simple/igraph_maximal_cliques.c + */ + +int igraph_maximal_cliques_count(const igraph_t *graph, + igraph_integer_t *res, + igraph_integer_t min_size, + igraph_integer_t max_size); + +#define IGRAPH_MC_COUNT +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_COUNT + +/** + * \function igraph_maximal_cliques_file + * Find maximal cliques and write them to a file + * + * TODO + */ + +int igraph_maximal_cliques_file(const igraph_t *graph, + FILE *outfile, + igraph_integer_t min_size, + igraph_integer_t max_size); + +#define IGRAPH_MC_FILE +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_FILE + +/** + * \function igraph_maximal_cliques_subset + * Maximal cliques for a subset of initial vertices + * + * TODO + */ + +int igraph_maximal_cliques_subset(const igraph_t *graph, + igraph_vector_int_t *subset, + igraph_vector_ptr_t *res, + igraph_integer_t *no, + FILE *outfile, + igraph_integer_t min_size, + igraph_integer_t max_size); + +#define IGRAPH_MC_FULL +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_FULL + + +/** + * \function igraph_maximal_cliques_callback + * \brief Finds maximal cliques in a graph and calls a function for each one + * + * This function enumerates all maximal cliques within the given size range + * and calls \p cliquehandler_fn for each of them. The cliques are passed to the + * callback function as an igraph_vector_t *. Destroying and + * freeing this vector is left up to the user. Use \ref igraph_vector_destroy() + * to destroy it first, then free it using \ref igraph_free(). + * + * + * + * Edge directions are ignored. + * + * + * + * \param graph The input graph. + * \param cliquehandler_fn Callback function to be called for each clique. + * See also \ref igraph_clique_handler_t. + * \param arg Extra argument to supply to \p cliquehandler_fn. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \return Error code. + * + * \sa \ref igraph_maximal_cliques(). + * + * Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy + * of the graph, this is typically small for sparse graphs. + * + */ + +int igraph_maximal_cliques_callback(const igraph_t *graph, + igraph_clique_handler_t *cliquehandler_fn, void *arg, + igraph_integer_t min_size, igraph_integer_t max_size); + +#define IGRAPH_MC_CALLBACK +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_CALLBACK + + +/** + * \function igraph_maximal_cliques_hist + * \brief Count the number of maximal cliques of each size in a graph. + * + * This function counts how many maximal cliques of each size are present in + * the graph. Size-1 maximal cliques are simply isolated vertices. + * + * + * + * Edge directions are ignored. + * + * + * + * \param graph The input graph. + * \param hist Pointer to an initialized vector. The result will be stored + * here. The first element will store the number of size-1 maximal cliques, + * the second element the number of size-2 maximal cliques, etc. + * For cliques smaller than \c min_size, zero counts will be returned. + * \param min_size Integer giving the minimum size of the cliques to be + * returned. If negative or zero, no lower bound will be used. + * \param max_size Integer giving the maximum size of the cliques to be + * returned. If negative or zero, no upper bound will be used. + * \return Error code. + * + * \sa \ref igraph_maximal_cliques(). + * + * Time complexity: O(d(n-d)3^(d/3)) worst case, d is the degeneracy + * of the graph, this is typically small for sparse graphs. + * + */ + +int igraph_maximal_cliques_hist(const igraph_t *graph, + igraph_vector_t *hist, + igraph_integer_t min_size, + igraph_integer_t max_size); + +#define IGRAPH_MC_HIST +#include "maximal_cliques_template.h" +#undef IGRAPH_MC_HIST diff --git a/src/maximal_cliques_template.h b/src/maximal_cliques_template.h new file mode 100644 index 0000000..04c0931 --- /dev/null +++ b/src/maximal_cliques_template.h @@ -0,0 +1,409 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifdef IGRAPH_MC_ORIG +#define RESTYPE igraph_vector_ptr_t *res +#define RESNAME res +#define SUFFIX +#define RECORD do { \ + igraph_vector_t *cl=igraph_Calloc(1, igraph_vector_t); \ + int j; \ + if (!cl) { \ + IGRAPH_ERROR("Cannot list maximal cliques", IGRAPH_ENOMEM); \ + } \ + IGRAPH_CHECK(igraph_vector_ptr_push_back(res, cl)); \ + IGRAPH_CHECK(igraph_vector_init(cl, clsize)); \ + for (j=0; j hsize) { \ + long hcapacity = igraph_vector_capacity(hist); \ + long j; \ + int err; \ + if (hcapacity < clsize && clsize < 2*hcapacity) \ + err = igraph_vector_reserve(hist, 2*hcapacity); \ + err = igraph_vector_resize(hist, clsize); \ + if (err != IGRAPH_SUCCESS) \ + IGRAPH_ERROR("Cannot count maximal cliques", IGRAPH_ENOMEM); \ + for (j=hsize; j < clsize; j++) \ + VECTOR(*hist)[j] = 0; \ + } \ + VECTOR(*hist)[clsize-1] += 1; \ + } while (0) +#define FINALLY \ + igraph_vector_clear(hist); \ + igraph_vector_reserve(hist, 50); /* initially reserve space for 50 elements */ +#define FOR_LOOP_OVER_VERTICES for (i=0; i PE && XS > XE) { + /* Found a maximum clique, report it */ + int clsize = igraph_vector_int_size(R); + if (min_size <= clsize && (clsize <= max_size || max_size <= 0)) { + RECORD; + } + } else if (PS <= PE) { + /* Select a pivot element */ + int pivot, mynextv; + igraph_i_maximal_cliques_select_pivot(PX, PS, PE, XS, XE, pos, + adjlist, &pivot, nextv, + oldPS, oldXE); + while ((mynextv = igraph_vector_int_pop_back(nextv)) != -1) { + int newPS, newXE; + + /* Going down, prepare */ + igraph_i_maximal_cliques_down(PX, PS, PE, XS, XE, pos, adjlist, + mynextv, R, &newPS, &newXE); + /* Recursive call */ + err = FUNCTION(igraph_i_maximal_cliques_bk, SUFFIX)( + PX, newPS, PE, XS, newXE, PS, XE, R, + pos, adjlist, RESNAME, nextv, H, + min_size, max_size); + + if (err == IGRAPH_STOP) { + return err; + } else { + IGRAPH_CHECK(err); + } + /* Putting v from P to X */ + if (igraph_vector_int_tail(nextv) != -1) { + igraph_i_maximal_cliques_PX(PX, PS, &PE, &XS, XE, pos, adjlist, + mynextv, H); + } + } + } + + /* Putting back vertices from X to P, see notes in H */ + igraph_i_maximal_cliques_up(PX, PS, PE, XS, XE, pos, adjlist, R, H); + + return 0; +} + +int FUNCTION(igraph_maximal_cliques, SUFFIX)( + const igraph_t *graph, + RESTYPE, + igraph_integer_t min_size, + igraph_integer_t max_size) { + + /* Implementation details. TODO */ + + igraph_vector_int_t PX, R, H, pos, nextv; + igraph_vector_t coreness, order; + igraph_vector_int_t rank; /* TODO: this is not needed */ + int i, ii, nn, no_of_nodes = igraph_vcount(graph); + igraph_adjlist_t adjlist, fulladjlist; + igraph_real_t pgreset = round(no_of_nodes / 100.0), pg = pgreset, pgc = 0; + int err; + IGRAPH_UNUSED(nn); + + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Edge directions are ignored for maximal clique " + "calculation"); + } + + igraph_vector_init(&order, no_of_nodes); + IGRAPH_FINALLY(igraph_vector_destroy, &order); + igraph_vector_int_init(&rank, no_of_nodes); + IGRAPH_FINALLY(igraph_vector_int_destroy, &rank); + igraph_vector_init(&coreness, no_of_nodes); + igraph_coreness(graph, &coreness, /*mode=*/ IGRAPH_ALL); + IGRAPH_FINALLY(igraph_vector_destroy, &coreness); + igraph_vector_qsort_ind(&coreness, &order, /*descending=*/ 0); + for (ii = 0; ii < no_of_nodes; ii++) { + int v = VECTOR(order)[ii]; + VECTOR(rank)[v] = ii; + } + + igraph_vector_destroy(&coreness); + IGRAPH_FINALLY_CLEAN(1); + + igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL); + + igraph_adjlist_simplify(&adjlist); + igraph_adjlist_init(graph, &fulladjlist, IGRAPH_ALL); + IGRAPH_FINALLY(igraph_adjlist_destroy, &fulladjlist); + igraph_adjlist_simplify(&fulladjlist); + igraph_vector_int_init(&PX, 20); + IGRAPH_FINALLY(igraph_vector_int_destroy, &PX); + igraph_vector_int_init(&R, 20); + IGRAPH_FINALLY(igraph_vector_int_destroy, &R); + igraph_vector_int_init(&H, 100); + IGRAPH_FINALLY(igraph_vector_int_destroy, &H); + igraph_vector_int_init(&pos, no_of_nodes); + IGRAPH_FINALLY(igraph_vector_int_destroy, &pos); + igraph_vector_int_init(&nextv, 100); + IGRAPH_FINALLY(igraph_vector_int_destroy, &nextv); + + FINALLY; + + FOR_LOOP_OVER_VERTICES + int v; + int vrank; + igraph_vector_int_t *vneis; + int vdeg; + int Pptr, Xptr, PS, PE, XS, XE; + int j; + + FOR_LOOP_OVER_VERTICES_PREPARE; + + v = VECTOR(order)[i]; + vrank = VECTOR(rank)[v]; + vneis = igraph_adjlist_get(&fulladjlist, v); + vdeg = igraph_vector_int_size(vneis); + Pptr = 0; Xptr = vdeg - 1; PS = 0; XE = vdeg - 1; + + pg--; + if (pg <= 0) { + IGRAPH_PROGRESS("Maximal cliques: ", pgc++, NULL); + pg = pgreset; + } + + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_vector_int_resize(&PX, vdeg); + igraph_vector_int_resize(&R, 1); + igraph_vector_int_resize(&H, 1); + igraph_vector_int_null(&pos); /* TODO: makes it quadratic? */ + igraph_vector_int_resize(&nextv, 1); + + VECTOR(H)[0] = -1; /* marks the end of the recursion */ + VECTOR(nextv)[0] = -1; + + /* ================================================================*/ + /* P <- G(v[i]) intersect { v[i+1], ..., v[n-1] } + X <- G(v[i]) intersect { v[0], ..., v[i-1] } */ + + VECTOR(R)[0] = v; + for (j = 0; j < vdeg; j++) { + int vx = VECTOR(*vneis)[j]; + if (VECTOR(rank)[vx] > vrank) { + VECTOR(PX)[Pptr] = vx; + VECTOR(pos)[vx] = Pptr + 1; + Pptr++; + } else if (VECTOR(rank)[vx] < vrank) { + VECTOR(PX)[Xptr] = vx; + VECTOR(pos)[vx] = Xptr + 1; + Xptr--; + } + } + + PE = Pptr - 1; XS = Xptr + 1; /* end of P, start of X in PX */ + + /* Create an adjacency list that is specific to the + v vertex. It only contains 'v' and its neighbors. Moreover, we + only deal with the vertices in P and X (and R). */ + igraph_vector_int_update(igraph_adjlist_get(&adjlist, v), + igraph_adjlist_get(&fulladjlist, v)); + for (j = 0; j <= vdeg - 1; j++) { + int vv = VECTOR(PX)[j]; + igraph_vector_int_t *fadj = igraph_adjlist_get(&fulladjlist, vv); + igraph_vector_int_t *radj = igraph_adjlist_get(&adjlist, vv); + int k, fn = igraph_vector_int_size(fadj); + igraph_vector_int_clear(radj); + for (k = 0; k < fn; k++) { + int nei = VECTOR(*fadj)[k]; + int neipos = VECTOR(pos)[nei] - 1; + if (neipos >= PS && neipos <= XE) { + igraph_vector_int_push_back(radj, nei); + } + } + } + + /* Reorder the adjacency lists, according to P and X. */ + igraph_i_maximal_cliques_reorder_adjlists(&PX, PS, PE, XS, XE, &pos, + &adjlist); + + err = FUNCTION(igraph_i_maximal_cliques_bk, SUFFIX)( + &PX, PS, PE, XS, XE, PS, XE, &R, &pos, + &adjlist, RESNAME, &nextv, &H, min_size, + max_size); + if (err == IGRAPH_STOP) { + break; + } else { + IGRAPH_CHECK(err); + } +} + +IGRAPH_PROGRESS("Maximal cliques: ", 100.0, NULL); + +igraph_vector_int_destroy(&nextv); +igraph_vector_int_destroy(&pos); +igraph_vector_int_destroy(&H); +igraph_vector_int_destroy(&R); +igraph_vector_int_destroy(&PX); +igraph_adjlist_destroy(&fulladjlist); +igraph_adjlist_destroy(&adjlist); +igraph_vector_int_destroy(&rank); +igraph_vector_destroy(&order); +IGRAPH_FINALLY_CLEAN(10); /* + res */ + +return 0; +} + +#undef RESTYPE +#undef RESNAME +#undef SUFFIX +#undef RECORD +#undef FINALLY +#undef FOR_LOOP_OVER_VERTICES +#undef FOR_LOOP_OVER_VERTICES_PREPARE diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..52c7c19 --- /dev/null +++ b/src/memory.c @@ -0,0 +1,99 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_memory.h" +#include "config.h" + +/** + * \function igraph_free + * Deallocate memory that was allocated by igraph functions + * + * Some igraph functions return a pointer vector (igraph_vector_ptr_t) + * containing pointers to other igraph or other data types. These data + * types are dynamically allocated and have to be deallocated + * manually, if the user does not need them any more. This can be done + * by calling igraph_free on them. + * + * + * Here is a complete example on how to use \c igraph_free properly. + * + * + * + * int main(void) + * { + * igraph_t graph; + * igraph_vector_ptr_t seps; + * long int i; + * + * igraph_famous(&graph, "tutte"); + * igraph_vector_ptr_init(&seps, 0); + * igraph_minimum_size_separators(&graph, &seps); + * + * for (i=0; i + * + * + * + * \param p Pointer to the piece of memory to be deallocated. + * \return Error code, currently always zero, meaning success. + * + * Time complexity: platform dependent, ideally it should be O(1). + * + * \sa \ref igraph_malloc() + */ + +int igraph_free(void *p) { + igraph_Free(p); + return 0; +} + + +/** + * \function igraph_malloc + * Allocate memory that can be safely deallocated by igraph functions + * + * Some igraph functions, such as \ref igraph_vector_ptr_free_all() and + * \ref igraph_vector_ptr_destroy_all() can free memory that may have been + * allocated by the user. \c igraph_malloc() works exactly like \c malloc() + * from the C standard library, but it is guaranteed that it can be safely + * paired with the \c free() function used by igraph internally (which is + * also user-accessible through \ref igraph_free()). + * + * \param n Number of bytes to be allocated. + * \return Pointer to the piece of allocated memory. + * + * \sa \ref igraph_free() + */ + +void *igraph_malloc(size_t n) { + return malloc(n); +} diff --git a/src/microscopic_update.c b/src/microscopic_update.c new file mode 100644 index 0000000..5d6a8d7 --- /dev/null +++ b/src/microscopic_update.c @@ -0,0 +1,1209 @@ +/* -*- mode: C -*- */ +/* + Microscopic update rules for dealing with agent-level strategy revision. + Copyright (C) 2011 Minh Van Nguyen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "igraph_iterators.h" +#include "igraph_interface.h" +#include "igraph_microscopic_update.h" +#include "igraph_nongraph.h" +#include "igraph_random.h" + +#include + +/* + * Internal use only. + * Compute the cumulative proportionate values of a vector. The vector is + * assumed to hold values associated with edges. + * + * \param graph The graph object representing the game network. No error + * checks will be performed on this graph. You are responsible for + * ensuring that this is a valid graph for the particular + * microscopic update rule at hand. + * \param U A vector of edge values for which we want to compute cumulative + * proportionate values. So U[i] is the value of the edge with ID i. + * With a local perspective, we would only compute cumulative + * proportionate values for some combination of U. This vector could + * be, for example, a vector of weights for edges in \p graph. It is + * assumed that each value of U is nonnegative; it is your + * responsibility to ensure this. Furthermore, this vector must have a + * length the same as the number of edges in \p graph; you are + * responsible for ensuring this condition holds. + * \param V Pointer to an uninitialized vector. The cumulative proportionate + * values will be computed and stored here. No error checks will be + * performed on this parameter. + * \param islocal Boolean; this flag controls which perspective to use. If + * true then we use the local perspective; otherwise we use the global + * perspective. In the context of this function, the local perspective + * for a vertex v consists of all edges incident on v. In contrast, the + * global perspective for v consists of all edges in \p graph. + * \param vid The vertex to use if we are considering a local perspective, + * i.e. if \p islocal is true. This vertex will be ignored if + * \p islocal is false. That is, if \p islocal is false then it is safe + * pass the value -1 here. On the other hand, if \p islocal is true then + * it is assumed that this is indeed a vertex of \p graph. + * \param mode Defines the sort of neighbourhood to consider for \p vid. This + * is only relevant if we are considering the local perspective, i.e. if + * \p islocal is true. If we are considering the global perspective, + * then this parameter would be ignored. In other words, if \p islocal + * is false then it is safe to pass the value \p IGRAPH_ALL here. If + * \p graph is undirected, then we use all the immediate neighbours of + * \p vid. Thus if you know that \p graph is undirected, then it is + * safe to pass the value \p IGRAPH_ALL here. Supported values are: + * \clist + * \cli IGRAPH_OUT + * Use the out-neighbours of \p vid. This option is only relevant + * when \p graph is a digraph and we are considering the local + * perspective. + * \cli IGRAPH_IN + * Use the in-neighbours of \p vid. Again this option is only relevant + * when \p graph is a directed graph and we are considering the local + * perspective. + * \cli IGRAPH_ALL + * Use both the in- and out-neighbours of \p vid. This option is only + * relevant if \p graph is a digraph and we are considering a local + * perspective. Also use this value if \p graph is undirected or we + * are considering the global perspective. + * \endclist + * \return Codes: + * \clist + * \cli IGRAPH_EINVAL + * This error code is returned in the following case: The vector + * \p U, or some combination of its values, sums to zero. + * \cli IGRAPH_SUCCESS + * This signal is returned if the cumulative proportionate values + * were successfully computed. + * \endclist + * + * Time complexity: O(2n) where n is the number of edges in the perspective + * of \p vid. + */ + +int igraph_ecumulative_proportionate_values(const igraph_t *graph, + const igraph_vector_t *U, + igraph_vector_t *V, + igraph_bool_t islocal, + igraph_integer_t vid, + igraph_neimode_t mode) { + igraph_eit_t A; /* all edges in v's perspective */ + igraph_es_t es; + igraph_integer_t e; + igraph_real_t C; /* cumulative probability */ + igraph_real_t P; /* probability */ + igraph_real_t S; /* sum of values */ + long int i; + + /* Set the perspective. Let v be the vertex under consideration. The local */ + /* perspective for v consists of edges incident on it. In contrast, the */ + /* global perspective for v are all edges in the given graph. Hence in the */ + /* global perspective, we will ignore the given vertex and the given */ + /* neighbourhood type, but instead consider all edges in the given graph. */ + if (islocal) { + IGRAPH_CHECK(igraph_es_incident(&es, vid, mode)); + } else { + IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_ID)); + } + IGRAPH_FINALLY(igraph_es_destroy, &es); + + /* Sum up all the values of vector U in the perspective for v. This sum */ + /* will be used in normalizing each value. */ + /* NOTE: Here we assume that each value to be summed is nonnegative, */ + /* and at least one of the values is nonzero. The behaviour resulting */ + /* from all values being zero would be division by zero later on when */ + /* we normalize each value. We check to see that the values sum to zero. */ + /* NOTE: In this function, the order in which we iterate through the */ + /* edges of interest should be the same as the order in which we do so */ + /* in the caller function. If the caller function doesn't care about the */ + /* order of values in the resulting vector V, then there's no need to take */ + /* special notice of that order. But in some cases the order of values in */ + /* V is taken into account, for example, in the Moran process. */ + S = 0.0; + IGRAPH_CHECK(igraph_eit_create(graph, es, &A)); + IGRAPH_FINALLY(igraph_eit_destroy, &A); + while (!IGRAPH_EIT_END(A)) { + e = (igraph_integer_t)IGRAPH_EIT_GET(A); + S += (igraph_real_t)VECTOR(*U)[e]; + IGRAPH_EIT_NEXT(A); + } + /* avoid division by zero later on */ + if (S == (igraph_real_t)0.0) { + igraph_eit_destroy(&A); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_ERROR("Vector of values sums to zero", IGRAPH_EINVAL); + } + + /* Get cumulative probability and relative value for each edge in the */ + /* perspective of v. The vector V holds the cumulative proportionate */ + /* values of all edges in v's perspective. The value V[0] is the */ + /* cumulative proportionate value of the first edge in the edge iterator */ + /* A. The value V[1] is the cumulative proportionate value of the second */ + /* edge in the iterator A. And so on. */ + C = 0.0; + i = 0; + IGRAPH_EIT_RESET(A); + IGRAPH_VECTOR_INIT_FINALLY(V, IGRAPH_EIT_SIZE(A)); + while (!IGRAPH_EIT_END(A)) { + e = (igraph_integer_t)IGRAPH_EIT_GET(A); + /* NOTE: Beware of division by zero here. This can happen if the vector */ + /* of values, or the combination of interest, sums to zero. */ + P = (igraph_real_t)VECTOR(*U)[e] / S; + C += P; + VECTOR(*V)[i] = C; + i++; + IGRAPH_EIT_NEXT(A); + } + + igraph_eit_destroy(&A); + igraph_es_destroy(&es); + + /* Pop V, A and es from the finally stack -- that's three items */ + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/* + * Internal use only. + * Compute the cumulative proportionate values of a vector. The vector is + * assumed to hold values associated with vertices. + * + * \param graph The graph object representing the game network. No error + * checks will be performed on this graph. You are responsible for + * ensuring that this is a valid graph for the particular + * microscopic update rule at hand. + * \param U A vector of vertex values for which we want to compute cumulative + * proportionate values. The vector could be, for example, a vector of + * fitness for vertices of \p graph. It is assumed that each value of U + * is nonnegative; it is your responsibility to ensure this. Also U, or + * a combination of interest, is assumed to sum to a positive value; + * this condition will be checked. + * \param V Pointer to an uninitialized vector. The cumulative proportionate + * values will be computed and stored here. No error checks will be + * performed on this parameter. + * \param islocal Boolean; this flag controls which perspective to use. If + * true then we use the local perspective; otherwise we use the global + * perspective. The local perspective for a vertex v is the set of all + * immediate neighbours of v. In contrast, the global perspective + * for v is the vertex set of \p graph. + * \param vid The vertex to use if we are considering a local perspective, + * i.e. if \p islocal is true. This vertex will be ignored if + * \p islocal is false. That is, if \p islocal is false then it is safe + * pass the value -1 here. On the other hand, if \p islocal is true then + * it is assumed that this is indeed a vertex of \p graph. + * \param mode Defines the sort of neighbourhood to consider for \p vid. This + * is only relevant if we are considering the local perspective, i.e. if + * \p islocal is true. If we are considering the global perspective, + * then this parameter would be ignored. In other words, if \p islocal + * is false then it is safe to pass the value \p IGRAPH_ALL here. If + * \p graph is undirected, then we use all the immediate neighbours of + * \p vid. Thus if you know that \p graph is undirected, then it is + * safe to pass the value \p IGRAPH_ALL here. Supported values are: + * \clist + * \cli IGRAPH_OUT + * Use the out-neighbours of \p vid. This option is only relevant + * when \p graph is a digraph and we are considering the local + * perspective. + * \cli IGRAPH_IN + * Use the in-neighbours of \p vid. Again this option is only relevant + * when \p graph is a directed graph and we are considering the local + * perspective. + * \cli IGRAPH_ALL + * Use both the in- and out-neighbours of \p vid. This option is only + * relevant if \p graph is a digraph and we are considering a local + * perspective. Also use this value if \p graph is undirected or we + * are considering the global perspective. + * \endclist + * \return Codes: + * \clist + * \cli IGRAPH_EINVAL + * This error code is returned in the following case: The vector + * \p U, or some combination of its values, sums to zero. + * \cli IGRAPH_SUCCESS + * This signal is returned if the cumulative proportionate values + * were successfully computed. + * \endclist + * + * Time complexity: O(2n) where n is the number of vertices in the + * perspective of vid. + */ + +int igraph_vcumulative_proportionate_values(const igraph_t *graph, + const igraph_vector_t *U, + igraph_vector_t *V, + igraph_bool_t islocal, + igraph_integer_t vid, + igraph_neimode_t mode) { + igraph_integer_t v; + igraph_real_t C; /* cumulative probability */ + igraph_real_t P; /* probability */ + igraph_real_t S; /* sum of values */ + igraph_vit_t A; /* all vertices in v's perspective */ + igraph_vs_t vs; + long int i; + + /* Set the perspective. Let v be the vertex under consideration; it might */ + /* be that we want to update v's strategy. The local perspective for v */ + /* consists of its immediate neighbours. In contrast, the global */ + /* perspective for v are all the vertices in the given graph. Hence in the */ + /* global perspective, we will ignore the given vertex and the given */ + /* neighbourhood type, but instead consider all vertices in the given */ + /* graph. */ + if (islocal) { + IGRAPH_CHECK(igraph_vs_adj(&vs, vid, mode)); + } else { + IGRAPH_CHECK(igraph_vs_all(&vs)); + } + IGRAPH_FINALLY(igraph_vs_destroy, &vs); + + /* Sum up all the values of vector U in the perspective for v. This */ + /* sum will be used in normalizing each value. If we are using a local */ + /* perspective, then we also need to consider the quantity of v in */ + /* computing the sum. */ + /* NOTE: Here we assume that each value to be summed is nonnegative, */ + /* and at least one of the values is nonzero. The behaviour resulting */ + /* from all values being zero would be division by zero later on when */ + /* we normalize each value. We check to see that the values sum to zero. */ + /* NOTE: In this function, the order in which we iterate through the */ + /* vertices of interest should be the same as the order in which we do so */ + /* in the caller function. If the caller function doesn't care about the */ + /* order of values in the resulting vector V, then there's no need to take */ + /* special notice of that order. But in some cases the order of values in */ + /* V is taken into account, for example, in roulette wheel selection. */ + S = 0.0; + IGRAPH_CHECK(igraph_vit_create(graph, vs, &A)); + IGRAPH_FINALLY(igraph_vit_destroy, &A); + while (!IGRAPH_VIT_END(A)) { + v = (igraph_integer_t)IGRAPH_VIT_GET(A); + S += (igraph_real_t)VECTOR(*U)[v]; + IGRAPH_VIT_NEXT(A); + } + if (islocal) { + S += (igraph_real_t)VECTOR(*U)[vid]; + } + /* avoid division by zero later on */ + if (S == (igraph_real_t)0.0) { + igraph_vit_destroy(&A); + igraph_vs_destroy(&vs); + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_ERROR("Vector of values sums to zero", IGRAPH_EINVAL); + } + + /* Get cumulative probability and relative value for each vertex in the */ + /* perspective of v. The vector V holds the cumulative proportionate */ + /* values of all vertices in v's perspective. The value V[0] is the */ + /* cumulative proportionate value of the first vertex in the vertex */ + /* iterator A. The value V[1] is the cumulative proportionate value of */ + /* the second vertex in the iterator A. And so on. If we are using the */ + /* local perspective, then we also need to consider the cumulative */ + /* proportionate value of v. In the case of the local perspective, we */ + /* don't need to compute and store v's cumulative proportionate value, */ + /* but we pretend that such value is appended to the vector V. */ + C = 0.0; + i = 0; + IGRAPH_VIT_RESET(A); + IGRAPH_VECTOR_INIT_FINALLY(V, IGRAPH_VIT_SIZE(A)); + while (!IGRAPH_VIT_END(A)) { + v = (igraph_integer_t)IGRAPH_VIT_GET(A); + /* NOTE: Beware of division by zero here. This can happen if the vector */ + /* of values, or a combination of interest, sums to zero. */ + P = (igraph_real_t)VECTOR(*U)[v] / S; + C += P; + VECTOR(*V)[i] = C; + i++; + IGRAPH_VIT_NEXT(A); + } + + igraph_vit_destroy(&A); + igraph_vs_destroy(&vs); + + /* Pop V, A and vs from the finally stack -- that's three items */ + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/* + * Internal use only. + * A set of standard tests to be performed prior to strategy updates. The + * tests contained in this function are common to many strategy revision + * functions in this file. This function is meant to be invoked from within + * a specific strategy update function in order to perform certain common + * tests, including sanity checks and conditions under which no strategy + * updates are necessary. + * + * \param graph The graph object representing the game network. This cannot + * be the empty or trivial graph, but must have at least two vertices + * and one edge. If \p graph has one vertex, then no strategy update + * would take place. Furthermore, if \p graph has at least two vertices + * but zero edges, then strategy update would also not take place. + * \param vid The vertex whose strategy is to be updated. It is assumed that + * \p vid represents a vertex in \p graph. No checking is performed and + * it is your responsibility to ensure that \p vid is indeed a vertex + * of \p graph. If an isolated vertex is provided, i.e. the input + * vertex has degree 0, then no strategy update would take place and + * \p vid would retain its current strategy. Strategy update would also + * not take place if the local neighbourhood of \p vid are its + * in-neighbours (respectively out-neighbours), but \p vid has zero + * in-neighbours (respectively out-neighbours). Loops are ignored in + * computing the degree (in, out, all) of \p vid. + * \param quantities A vector of quantities providing the quantity of each + * vertex in \p graph. Think of each entry of the vector as being + * generated by a function such as the fitness function for the game. + * So if the vector represents fitness quantities, then each vector + * entry is the fitness of some vertex. The length of this vector must + * be the same as the number of vertices in the vertex set of \p graph. + * \param strategies A vector of the current strategies for the vertex + * population. Each strategy is identified with a nonnegative integer, + * whose interpretation depends on the payoff matrix of the game. + * Generally we use the strategy ID as a row or column index of the + * payoff matrix. The length of this vector must be the same as the + * number of vertices in the vertex set of \p graph. + * \param mode Defines the sort of neighbourhood to consider for \p vid. If + * \p graph is undirected, then we use all the immediate neighbours of + * \p vid. Thus if you know that \p graph is undirected, then it is safe + * to pass the value \p IGRAPH_ALL here. Supported values are: + * \clist + * \cli IGRAPH_OUT + * Use the out-neighbours of \p vid. This option is only relevant + * when \p graph is a directed graph. + * \cli IGRAPH_IN + * Use the in-neighbours of \p vid. Again this option is only relevant + * when \p graph is a directed graph. + * \cli IGRAPH_ALL + * Use both the in- and out-neighbours of \p vid. This option is only + * relevant if \p graph is a digraph. Also use this value if + * \p graph is undirected. + * \endclist + * \param updates Boolean; at the end of this test suite, this flag + * indicates whether to proceed with strategy revision. If true then + * strategy revision should proceed; otherwise there is no need to + * continue with revising a vertex's strategy. A caller function that + * invokes this function would use the value of \p updates to + * determine whether to proceed with strategy revision. + * \param islocal Boolean; this flag controls which perspective to use. If + * true then we use the local perspective; otherwise we use the global + * perspective. The local perspective for \p vid is the set of all + * immediate neighbours of \p vid. In contrast, the global perspective + * for \p vid is the vertex set of \p graph. + * \return Codes: + * \clist + * \cli IGRAPH_EINVAL + * This error code is returned in each of the following cases: + * (1) Any of the parameters \p graph, \p quantities, or + * \p strategies is a null pointer. (2) The vector \p quantities + * or \p strategies has a length different from the number of + * vertices in \p graph. (3) The parameter \p graph is the empty + * or null graph, i.e. the graph with zero vertices and edges. + * \cli IGRAPH_SUCCESS + * This signal is returned if no errors were raised. You should use + * the value of the boolean \p updates to decide whether to go + * ahead with updating a vertex's strategy. + * \endclist + */ + +int igraph_microscopic_standard_tests(const igraph_t *graph, + igraph_integer_t vid, + const igraph_vector_t *quantities, + const igraph_vector_t *strategies, + igraph_neimode_t mode, + igraph_bool_t *updates, + igraph_bool_t islocal) { + + igraph_integer_t nvert; + igraph_vector_t degv; + *updates = 1; + + /* sanity checks */ + if (graph == NULL) { + IGRAPH_ERROR("Graph is a null pointer", IGRAPH_EINVAL); + } + if (quantities == NULL) { + IGRAPH_ERROR("Quantities vector is a null pointer", IGRAPH_EINVAL); + } + if (strategies == NULL) { + IGRAPH_ERROR("Strategies vector is a null pointer", IGRAPH_EINVAL); + } + + /* the empty graph */ + nvert = igraph_vcount(graph); + if (nvert < 1) { + IGRAPH_ERROR("Graph cannot be the empty graph", IGRAPH_EINVAL); + } + /* invalid vector length */ + if (nvert != (igraph_integer_t)igraph_vector_size(quantities)) { + IGRAPH_ERROR("Size of quantities vector different from number of vertices", + IGRAPH_EINVAL); + } + if (nvert != (igraph_integer_t)igraph_vector_size(strategies)) { + IGRAPH_ERROR("Size of strategies vector different from number of vertices", + IGRAPH_EINVAL); + } + + /* Various conditions under which no strategy updates will take place. That + * is, the vertex retains its current strategy. + */ + /* given graph has < 2 vertices */ + if (nvert < 2) { + *updates = 0; + } + /* graph has >= 2 vertices, but no edges */ + if (igraph_ecount(graph) < 1) { + *updates = 0; + } + + /* Test for vertex isolation, depending on the perspective given. For + * undirected graphs, a given vertex v is isolated if its degree is zero. + * If we are considering in-neighbours (respectively out-neighbours), then + * we say that v is isolated if its in-degree (respectively out-degree) is + * zero. In general, this vertex isolation test is only relevant if we are + * using a local perspective, i.e. if we only consider the immediate + * neighbours (local perspective) of v as opposed to all vertices in the + * vertex set of the graph (global perspective). + */ + if (islocal) { + /* Moving on ahead with vertex isolation test, since local perspective */ + /* is requested. */ + IGRAPH_VECTOR_INIT_FINALLY(°v, 1); + IGRAPH_CHECK(igraph_degree(graph, °v, igraph_vss_1(vid), + mode, IGRAPH_NO_LOOPS)); + if (VECTOR(degv)[0] < 1) { + *updates = 0; + } + igraph_vector_destroy(°v); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup spatialgames + * \function igraph_deterministic_optimal_imitation + * \brief Adopt a strategy via deterministic optimal imitation. + * + * A simple deterministic imitation strategy where a vertex revises its + * strategy to that which yields a local optimal. Here "local" is with + * respect to the immediate neighbours of the vertex. The vertex retains its + * current strategy where this strategy yields a locally optimal quantity. + * The quantity in this case could be a measure such as fitness. + * + * \param graph The graph object representing the game network. This cannot + * be the empty or trivial graph, but must have at least two vertices + * and one edge. If \p graph has one vertex, then no strategy update + * would take place. Furthermore, if \p graph has at least two vertices + * but zero edges, then strategy update would also not take place. + * \param vid The vertex whose strategy is to be updated. It is assumed that + * \p vid represents a vertex in \p graph. No checking is performed and + * it is your responsibility to ensure that \p vid is indeed a vertex + * of \p graph. If an isolated vertex is provided, i.e. the input + * vertex has degree 0, then no strategy update would take place and + * \p vid would retain its current strategy. Strategy update would also + * not take place if the local neighbourhood of \p vid are its + * in-neighbours (respectively out-neighbours), but \p vid has zero + * in-neighbours (respectively out-neighbours). Loops are ignored in + * computing the degree (in, out, all) of \p vid. + * \param optimality Logical; controls the type of optimality to be used. + * Supported values are: + * \clist + * \cli IGRAPH_MAXIMUM + * Use maximum deterministic imitation, where the strategy of the + * vertex with maximum quantity (e.g. fitness) would be adopted. We + * update the strategy of \p vid to that which yields a local + * maximum. + * \cli IGRAPH_MINIMUM + * Use minimum deterministic imitation. That is, the strategy of the + * vertex with minimum quantity would be imitated. In other words, + * update to the strategy that yields a local minimum. + * \endclist + * \param quantities A vector of quantities providing the quantity of each + * vertex in \p graph. Think of each entry of the vector as being + * generated by a function such as the fitness function for the game. + * So if the vector represents fitness quantities, then each vector + * entry is the fitness of some vertex. The length of this vector must + * be the same as the number of vertices in the vertex set of \p graph. + * \param strategies A vector of the current strategies for the vertex + * population. The updated strategy for \p vid would be stored here. + * Each strategy is identified with a nonnegative integer, whose + * interpretation depends on the payoff matrix of the game. Generally + * we use the strategy ID as a row or column index of the payoff + * matrix. The length of this vector must be the same as the number of + * vertices in the vertex set of \p graph. + * \param mode Defines the sort of neighbourhood to consider for \p vid. If + * \p graph is undirected, then we use all the immediate neighbours of + * \p vid. Thus if you know that \p graph is undirected, then it is safe + * to pass the value \p IGRAPH_ALL here. Supported values are: + * \clist + * \cli IGRAPH_OUT + * Use the out-neighbours of \p vid. This option is only relevant + * when \p graph is a directed graph. + * \cli IGRAPH_IN + * Use the in-neighbours of \p vid. Again this option is only relevant + * when \p graph is a directed graph. + * \cli IGRAPH_ALL + * Use both the in- and out-neighbours of \p vid. This option is only + * relevant if \p graph is a digraph. Also use this value if + * \p graph is undirected. + * \endclist + * \return The error code \p IGRAPH_EINVAL is returned in each of the + * following cases: (1) Any of the parameters \p graph, \p quantities, + * or \p strategies is a null pointer. (2) The vector \p quantities + * or \p strategies has a length different from the number of vertices + * in \p graph. (3) The parameter \p graph is the empty or null graph, + * i.e. the graph with zero vertices and edges. + * + * Time complexity: O(2d), where d is the degree of the vertex \p vid. + * + * \example examples/simple/igraph_deterministic_optimal_imitation.c + */ + +int igraph_deterministic_optimal_imitation(const igraph_t *graph, + igraph_integer_t vid, + igraph_optimal_t optimality, + const igraph_vector_t *quantities, + igraph_vector_t *strategies, + igraph_neimode_t mode) { + igraph_integer_t i, k, v; + igraph_real_t q; + igraph_vector_t adj; + igraph_bool_t updates; + + IGRAPH_CHECK(igraph_microscopic_standard_tests(graph, vid, quantities, + strategies, mode, &updates, + /*is local?*/ 1)); + if (!updates) { + return IGRAPH_SUCCESS; /* Nothing to do */ + } + + /* Choose a locally optimal strategy to imitate. This can be either maximum + * or minimum deterministic imitation. By now we know that the given vertex v + * has degree >= 1 and at least 1 edge. Then within its immediate + * neighbourhood adj(v) and including v itself, there exists a vertex whose + * strategy yields a local optimal quantity. + */ + /* Random permutation of adj(v). This ensures that if there are multiple */ + /* candidates with an optimal strategy, then we choose one such candidate */ + /* at random. */ + IGRAPH_VECTOR_INIT_FINALLY(&adj, 0); + IGRAPH_CHECK(igraph_neighbors(graph, &adj, vid, mode)); + IGRAPH_CHECK(igraph_vector_shuffle(&adj)); + /* maximum deterministic imitation */ + i = vid; + q = (igraph_real_t)VECTOR(*quantities)[vid]; + if (optimality == IGRAPH_MAXIMUM) { + for (k = 0; k < igraph_vector_size(&adj); k++) { + v = (igraph_integer_t) VECTOR(adj)[k]; + if ((igraph_real_t)VECTOR(*quantities)[v] > q) { + i = v; + q = (igraph_real_t)VECTOR(*quantities)[v]; + } + } + } else { /* minimum deterministic imitation */ + for (k = 0; k < igraph_vector_size(&adj); k++) { + v = (igraph_integer_t) VECTOR(adj)[k]; + if ((igraph_real_t)VECTOR(*quantities)[v] < q) { + i = v; + q = (igraph_real_t)VECTOR(*quantities)[v]; + } + } + } + /* Now i is a vertex with a locally optimal quantity, the value of which */ + /* is q. Update the strategy of vid to that of i. */ + VECTOR(*strategies)[vid] = VECTOR(*strategies)[i]; + igraph_vector_destroy(&adj); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup spatialgames + * \function igraph_moran_process + * \brief The Moran process in a network setting. + * + * This is an extension of the classic Moran process to a network setting. + * The Moran process is a model of haploid (asexual) reproduction within a + * population having a fixed size. In the network setting, the Moran process + * operates on a weighted graph. At each time step a vertex a is chosen for + * reproduction and another vertex b is chosen for death. Vertex a gives birth + * to an identical clone c, which replaces b. Vertex c is a clone of a in that + * c inherits both the current quantity (e.g. fitness) and current strategy + * of a. + * + * + * The graph G representing the game network is assumed to be simple, + * i.e. free of loops and without multiple edges. If, on the other hand, G has + * a loop incident on some vertex v, then it is possible that when v is chosen + * for reproduction it would forgo this opportunity. In particular, when v is + * chosen for reproduction and v is also chosen for death, the clone of v + * would be v itself with its current vertex ID. In effect v forgoes its + * chance for reproduction. + * + * \param graph The graph object representing the game network. This cannot + * be the empty or trivial graph, but must have at least two vertices + * and one edge. The Moran process will not take place in each of the + * following cases: (1) If \p graph has one vertex. (2) If \p graph has + * at least two vertices but zero edges. + * \param weights A vector of all edge weights for \p graph. Thus weights[i] + * means the weight of the edge with edge ID i. For the purpose of the + * Moran process, each weight is assumed to be positive; it is your + * responsibility to ensure this condition holds. The length of this + * vector must be the same as the number of edges in \p graph. + * \param quantities A vector of quantities providing the quantity of each + * vertex in \p graph. The quantity of the new clone will be stored + * here. Think of each entry of the vector as being generated by a + * function such as the fitness function for the game. So if the vector + * represents fitness quantities, then each vector entry is the fitness + * of some vertex. The length of this vector must be the same as the + * number of vertices in the vertex set of \p graph. For the purpose of + * the Moran process, each vector entry is assumed to be nonnegative; + * no checks will be performed for this. It is your responsibility to + * ensure that at least one entry is positive. Furthermore, this vector + * cannot be a vector of zeros; this condition will be checked. + * \param strategies A vector of the current strategies for the vertex + * population. The strategy of the new clone will be stored here. Each + * strategy is identified with a nonnegative integer, whose + * interpretation depends on the payoff matrix of the game. Generally + * we use the strategy ID as a row or column index of the payoff + * matrix. The length of this vector must be the same as the number of + * vertices in the vertex set of \p graph. + * \param mode Defines the sort of neighbourhood to consider for the vertex a + * chosen for reproduction. This is only relevant if \p graph is + * directed. If \p graph is undirected, then it is safe to pass the + * value \p IGRAPH_ALL here. Supported values are: + * \clist + * \cli IGRAPH_OUT + * Use the out-neighbours of a. This option is only relevant when + * \p graph is directed. + * \cli IGRAPH_IN + * Use the in-neighbours of a. Again this option is only relevant + * when \p graph is directed. + * \cli IGRAPH_ALL + * Use both the in- and out-neighbours of a. This option is only + * relevant if \p graph is directed. Also use this value if + * \p graph is undirected. + * \endclist + * \return The error code \p IGRAPH_EINVAL is returned in each of the following + * cases: (1) Any of the parameters \p graph, \p weights, + * \p quantities or \p strategies is a null pointer. (2) The vector + * \p quantities or \p strategies has a length different from the + * number of vertices in \p graph. (3) The vector \p weights has a + * length different from the number of edges in \p graph. (4) The + * parameter \p graph is the empty or null graph, i.e. the graph with + * zero vertices and edges. (5) The vector \p weights, or the + * combination of interest, sums to zero. (6) The vector \p quantities, + * or the combination of interest, sums to zero. + * + * Time complexity: depends on the random number generator, but is usually + * O(n) where n is the number of vertices in \p graph. + * + * + * References: + * \clist + * \cli (Lieberman et al. 2005) + * E. Lieberman, C. Hauert, and M. A. Nowak. Evolutionary dynamics on + * graphs. \emb Nature, \eme 433(7023):312--316, 2005. + * \cli (Moran 1958) + * P. A. P. Moran. Random processes in genetics. \emb Mathematical + * Proceedings of the Cambridge Philosophical Society, \eme 54(1):60--71, + * 1958. + * \endclist + * + * \example examples/simple/igraph_moran_process.c + */ + +int igraph_moran_process(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_t *quantities, + igraph_vector_t *strategies, + igraph_neimode_t mode) { + igraph_bool_t updates; + igraph_integer_t a = -1; /* vertex chosen for reproduction */ + igraph_integer_t b = -1; /* vertex chosen for death */ + igraph_integer_t e, nedge, u, v; + igraph_real_t r; /* random number */ + igraph_vector_t deg; + igraph_vector_t V; /* vector of cumulative proportionate values */ + igraph_vit_t vA; /* vertex list */ + igraph_eit_t eA; /* edge list */ + igraph_vs_t vs; + igraph_es_t es; + long int i; + + /* don't test for vertex isolation, hence vid = -1 and islocal = 0 */ + IGRAPH_CHECK(igraph_microscopic_standard_tests(graph, /*vid*/ -1, + quantities, strategies, mode, + &updates, /*is local?*/ 0)); + if (!updates) { + return IGRAPH_SUCCESS; /* nothing more to do */ + } + if (weights == NULL) { + IGRAPH_ERROR("Weights vector is a null pointer", IGRAPH_EINVAL); + } + nedge = igraph_ecount(graph); + if (nedge != (igraph_integer_t)igraph_vector_size(weights)) { + IGRAPH_ERROR("Size of weights vector different from number of edges", + IGRAPH_EINVAL); + } + + /* Cumulative proportionate quantities. We are using the global */ + /* perspective, hence islocal = 0, vid = -1 and mode = IGRAPH_ALL. */ + IGRAPH_CHECK(igraph_vcumulative_proportionate_values(graph, quantities, &V, + /*is local?*/ 0, + /*vid*/ -1, + /*mode*/ IGRAPH_ALL)); + + /* Choose a vertex for reproduction from among all vertices in the graph. */ + /* The vertex is chosen proportionate to its quantity and such that its */ + /* degree is >= 1. In case we are considering in-neighbours (respectively */ + /* out-neighbours), the chosen vertex must have in-degree (respectively */ + /* out-degree) >= 1. All loops will be ignored. At this point, we know */ + /* that the graph has at least one edge, which may be directed or not. */ + /* Furthermore the quantities of all vertices sum to a positive value. */ + /* Hence at least one vertex will be chosen for reproduction. */ + IGRAPH_CHECK(igraph_vs_all(&vs)); + IGRAPH_FINALLY(igraph_vs_destroy, &vs); + IGRAPH_CHECK(igraph_vit_create(graph, vs, &vA)); + IGRAPH_FINALLY(igraph_vit_destroy, &vA); + RNG_BEGIN(); + r = RNG_UNIF01(); + RNG_END(); + i = 0; + IGRAPH_VECTOR_INIT_FINALLY(°, 1); + while (!IGRAPH_VIT_END(vA)) { + u = (igraph_integer_t)IGRAPH_VIT_GET(vA); + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_1(u), mode, + IGRAPH_NO_LOOPS)); + if (VECTOR(deg)[0] < 1) { + i++; + IGRAPH_VIT_NEXT(vA); + continue; + } + if (r <= VECTOR(V)[i]) { + /* we have found our candidate vertex for reproduction */ + a = u; + break; + } + i++; + IGRAPH_VIT_NEXT(vA); + } + /* By now we should have chosen a vertex for reproduction. Check this. */ + assert(a >= 0); + + /* Cumulative proportionate weights. We are using the local perspective */ + /* with respect to vertex a, which has been chosen for reproduction. */ + /* The degree of a is deg(a) >= 1 with respect to the mode "mode", which */ + /* can flag either the in-degree, out-degree or all degree of a. But it */ + /* still might happen that the edge weights of interest would sum to zero. */ + /* An error would be raised in that case. */ + igraph_vector_destroy(&V); + IGRAPH_CHECK(igraph_ecumulative_proportionate_values(graph, weights, &V, + /*is local?*/ 1, + /*vertex*/ a, mode)); + + /* Choose a vertex for death from among all vertices in a's perspective. */ + /* Let E be all the edges in the perspective of a. If (u,v) \in E is any */ + /* such edge, then we have a = u or a = v. That is, any edge in E has a */ + /* for one of its endpoints. As G is assumed to be a simple graph, then */ + /* exactly one of u or v is the vertex a. Without loss of generality, we */ + /* assume that each edge in E has the form (a, v_i). Then the vertex v_j */ + /* chosen for death is chosen proportionate to the weight of the edge */ + /* (a, v_j). */ + IGRAPH_CHECK(igraph_es_incident(&es, a, mode)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + IGRAPH_CHECK(igraph_eit_create(graph, es, &eA)); + IGRAPH_FINALLY(igraph_eit_destroy, &eA); + RNG_BEGIN(); + r = RNG_UNIF01(); + RNG_END(); + i = 0; + while (!IGRAPH_EIT_END(eA)) { + e = (igraph_integer_t)IGRAPH_EIT_GET(eA); + if (r <= VECTOR(V)[i]) { + /* We have found our candidate vertex for death; call this vertex b. */ + /* As G is simple, then a =/= b. Check the latter condition. */ + IGRAPH_CHECK(igraph_edge(graph, /*edge ID*/ e, + /*tail vertex*/ &u, /*head vertex*/ &v)); + if (a == u) { + b = v; + } else { + b = u; + } + assert(a != b); /* always true if G is simple */ + break; + } + i++; + IGRAPH_EIT_NEXT(eA); + } + + /* By now a vertex a is chosen for reproduction and a vertex b is chosen */ + /* for death. Check that b has indeed been chosen. Clone vertex a and kill */ + /* vertex b. Let the clone c have the vertex ID of b, and the strategy and */ + /* quantity of a. */ + assert(b >= 0); + VECTOR(*quantities)[b] = VECTOR(*quantities)[a]; + VECTOR(*strategies)[b] = VECTOR(*strategies)[a]; + + igraph_vector_destroy(°); + igraph_vector_destroy(&V); + igraph_vit_destroy(&vA); + igraph_eit_destroy(&eA); + igraph_vs_destroy(&vs); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(6); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup spatialgames + * \function igraph_roulette_wheel_imitation + * \brief Adopt a strategy via roulette wheel selection. + * + * A simple stochastic imitation strategy where a vertex revises its + * strategy to that of a vertex u chosen proportionate to u's quantity + * (e.g. fitness). This is a special case of stochastic imitation, where a + * candidate is not chosen uniformly at random but proportionate to its + * quantity. + * + * \param graph The graph object representing the game network. This cannot + * be the empty or trivial graph, but must have at least two vertices + * and one edge. If \p graph has one vertex, then no strategy update + * would take place. Furthermore, if \p graph has at least two vertices + * but zero edges, then strategy update would also not take place. + * \param vid The vertex whose strategy is to be updated. It is assumed that + * \p vid represents a vertex in \p graph. No checking is performed and + * it is your responsibility to ensure that \p vid is indeed a vertex + * of \p graph. If an isolated vertex is provided, i.e. the input + * vertex has degree 0, then no strategy update would take place and + * \p vid would retain its current strategy. Strategy update would also + * not take place if the local neighbourhood of \p vid are its + * in-neighbours (respectively out-neighbours), but \p vid has zero + * in-neighbours (respectively out-neighbours). Loops are ignored in + * computing the degree (in, out, all) of \p vid. + * \param islocal Boolean; this flag controls which perspective to use in + * computing the relative quantity. If true then we use the local + * perspective; otherwise we use the global perspective. The local + * perspective for \p vid is the set of all immediate neighbours of + * \p vid. In contrast, the global perspective for \p vid is the + * vertex set of \p graph. + * \param quantities A vector of quantities providing the quantity of each + * vertex in \p graph. Think of each entry of the vector as being + * generated by a function such as the fitness function for the game. + * So if the vector represents fitness quantities, then each vector + * entry is the fitness of some vertex. The length of this vector must + * be the same as the number of vertices in the vertex set of \p graph. + * For the purpose of roulette wheel selection, each vector entry is + * assumed to be nonnegative; no checks will be performed for this. It + * is your responsibility to ensure that at least one entry is nonzero. + * Furthermore, this vector cannot be a vector of zeros; this condition + * will be checked. + * \param strategies A vector of the current strategies for the vertex + * population. The updated strategy for \p vid would be stored here. + * Each strategy is identified with a nonnegative integer, whose + * interpretation depends on the payoff matrix of the game. Generally + * we use the strategy ID as a row or column index of the payoff + * matrix. The length of this vector must be the same as the number of + * vertices in the vertex set of \p graph. + * \param mode Defines the sort of neighbourhood to consider for \p vid. This + * is only relevant if we are considering the local perspective, i.e. if + * \p islocal is true. If we are considering the global perspective, + * then it is safe to pass the value \p IGRAPH_ALL here. If \p graph is + * undirected, then we use all the immediate neighbours of \p vid. Thus + * if you know that \p graph is undirected, then it is safe to pass the + * value \p IGRAPH_ALL here. Supported values are: + * \clist + * \cli IGRAPH_OUT + * Use the out-neighbours of \p vid. This option is only relevant + * when \p graph is a digraph and we are considering the local + * perspective. + * \cli IGRAPH_IN + * Use the in-neighbours of \p vid. Again this option is only relevant + * when \p graph is a directed graph and we are considering the local + * perspective. + * \cli IGRAPH_ALL + * Use both the in- and out-neighbours of \p vid. This option is only + * relevant if \p graph is a digraph. Also use this value if + * \p graph is undirected or we are considering the global + * perspective. + * \endclist + * \return The error code \p IGRAPH_EINVAL is returned in each of the following + * cases: (1) Any of the parameters \p graph, \p quantities, or + * \p strategies is a null pointer. (2) The vector \p quantities or + * \p strategies has a length different from the number of vertices + * in \p graph. (3) The parameter \p graph is the empty or null graph, + * i.e. the graph with zero vertices and edges. (4) The vector + * \p quantities sums to zero. + * + * Time complexity: O(n) where n is the number of vertices in the perspective + * to consider. If we consider the global perspective, then n is the number + * of vertices in the vertex set of \p graph. On the other hand, for the local + * perspective n is the degree of \p vid, excluding loops. + * + * + * Reference: + * \clist + * \cli (Yu & Gen 2010) + * X. Yu and M. Gen. \emb Introduction to Evolutionary Algorithms. \eme + * Springer, 2010, pages 18--20. + * \endclist + * + * \example examples/simple/igraph_roulette_wheel_imitation.c + */ + +int igraph_roulette_wheel_imitation(const igraph_t *graph, + igraph_integer_t vid, + igraph_bool_t islocal, + const igraph_vector_t *quantities, + igraph_vector_t *strategies, + igraph_neimode_t mode) { + igraph_bool_t updates; + igraph_integer_t u; + igraph_real_t r; /* random number */ + igraph_vector_t V; /* vector of cumulative proportionate quantities */ + igraph_vit_t A; /* all vertices in v's perspective */ + igraph_vs_t vs; + long int i; + + IGRAPH_CHECK(igraph_microscopic_standard_tests(graph, vid, quantities, + strategies, mode, &updates, + islocal)); + if (!updates) { + return IGRAPH_SUCCESS; /* nothing further to do */ + } + + /* set the perspective */ + if (islocal) { + IGRAPH_CHECK(igraph_vs_adj(&vs, vid, mode)); + } else { + IGRAPH_CHECK(igraph_vs_all(&vs)); + } + IGRAPH_FINALLY(igraph_vs_destroy, &vs); + IGRAPH_CHECK(igraph_vit_create(graph, vs, &A)); + IGRAPH_FINALLY(igraph_vit_destroy, &A); + + IGRAPH_CHECK(igraph_vcumulative_proportionate_values(graph, quantities, &V, + islocal, vid, mode)); + + /* Finally, choose a vertex u to imitate. The vertex u is chosen */ + /* proportionate to its quantity. In the case of a local perspective, we */ + /* pretend that v's cumulative proportionate quantity has been appended to */ + /* the vector V. Let V be of length n so that V[n-1] is the last element */ + /* of V, and let r be a real number chosen uniformly at random from the */ + /* unit interval [0,1]. If r > V[i] for all i < n, then v defaults to */ + /* retaining its current strategy. Similarly in the case of the global */ + /* perspective, if r > V[i] for all i < n - 1 then v would adopt the */ + /* strategy of the vertex whose cumulative proportionate quantity is */ + /* V[n-1]. */ + /* NOTE: Here we assume that the order in which we iterate through the */ + /* vertices in A is the same as the order in which we do so in the */ + /* invoked function igraph_vcumulative_proportionate_values(). */ + /* Otherwise we would incorrectly associate each V[i] with a vertex in A. */ + RNG_BEGIN(); + r = RNG_UNIF01(); + RNG_END(); + i = 0; + while (!IGRAPH_VIT_END(A)) { + if (r <= VECTOR(V)[i]) { + /* We have found our candidate vertex for imitation. Update strategy */ + /* of v to that of u, and exit the selection loop. */ + u = (igraph_integer_t)IGRAPH_VIT_GET(A); + VECTOR(*strategies)[vid] = VECTOR(*strategies)[u]; + break; + } + i++; + IGRAPH_VIT_NEXT(A); + } + + /* By now, vertex v should either retain its current strategy or it has */ + /* adopted the strategy of a vertex in its perspective. Nothing else to */ + /* do, but clean up. */ + igraph_vector_destroy(&V); + igraph_vit_destroy(&A); + igraph_vs_destroy(&vs); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \ingroup spatialgames + * \function igraph_stochastic_imitation + * \brief Adopt a strategy via stochastic imitation with uniform selection. + * + * A simple stochastic imitation strategy where a vertex revises its + * strategy to that of a vertex chosen uniformly at random from its local + * neighbourhood. This is called stochastic imitation via uniform selection, + * where the strategy to imitate is chosen via some random process. For the + * purposes of this function, we use uniform selection from a pool of + * candidates. + * + * \param graph The graph object representing the game network. This cannot + * be the empty or trivial graph, but must have at least two vertices + * and one edge. If \p graph has one vertex, then no strategy update + * would take place. Furthermore, if \p graph has at least two vertices + * but zero edges, then strategy update would also not take place. + * \param vid The vertex whose strategy is to be updated. It is assumed that + * \p vid represents a vertex in \p graph. No checking is performed and + * it is your responsibility to ensure that \p vid is indeed a vertex + * of \p graph. If an isolated vertex is provided, i.e. the input + * vertex has degree 0, then no strategy update would take place and + * \p vid would retain its current strategy. Strategy update would also + * not take place if the local neighbourhood of \p vid are its + * in-neighbours (respectively out-neighbours), but \p vid has zero + * in-neighbours (respectively out-neighbours). Loops are ignored in + * computing the degree (in, out, all) of \p vid. + * \param algo This flag controls which algorithm to use in stochastic + * imitation. Supported values are: + * \clist + * \cli IGRAPH_IMITATE_AUGMENTED + * Augmented imitation. Vertex \p vid imitates the strategy of the + * chosen vertex u provided that doing so would increase the + * quantity (e.g. fitness) of \p vid. Augmented imitation can be + * thought of as "imitate if better". + * \cli IGRAPH_IMITATE_BLIND + * Blind imitation. Vertex \p vid blindly imitates the strategy of + * the chosen vertex u, regardless of whether doing so would + * increase or decrease the quantity of \p vid. + * \cli IGRAPH_IMITATE_CONTRACTED + * Contracted imitation. Here vertex \p vid imitates the strategy of + * the chosen vertex u if doing so would decrease the quantity of + * \p vid. Think of contracted imitation as "imitate if worse". + * \endclist + * \param quantities A vector of quantities providing the quantity of each + * vertex in \p graph. Think of each entry of the vector as being + * generated by a function such as the fitness function for the game. + * So if the vector represents fitness quantities, then each vector + * entry is the fitness of some vertex. The length of this vector must + * be the same as the number of vertices in the vertex set of \p graph. + * \param strategies A vector of the current strategies for the vertex + * population. The updated strategy for \p vid would be stored here. + * Each strategy is identified with a nonnegative integer, whose + * interpretation depends on the payoff matrix of the game. Generally + * we use the strategy ID as a row or column index of the payoff + * matrix. The length of this vector must be the same as the number of + * vertices in the vertex set of \p graph. + * \param mode Defines the sort of neighbourhood to consider for \p vid. If + * \p graph is undirected, then we use all the immediate neighbours of + * \p vid. Thus if you know that \p graph is undirected, then it is safe + * to pass the value \p IGRAPH_ALL here. Supported values are: + * \clist + * \cli IGRAPH_OUT + * Use the out-neighbours of \p vid. This option is only relevant + * when \p graph is a directed graph. + * \cli IGRAPH_IN + * Use the in-neighbours of \p vid. Again this option is only relevant + * when \p graph is a directed graph. + * \cli IGRAPH_ALL + * Use both the in- and out-neighbours of \p vid. This option is only + * relevant if \p graph is a digraph. Also use this value if + * \p graph is undirected. + * \endclist + * \return The error code \p IGRAPH_EINVAL is returned in each of the following + * cases: (1) Any of the parameters \p graph, \p quantities, or + * \p strategies is a null pointer. (2) The vector \p quantities or + * \p strategies has a length different from the number of vertices + * in \p graph. (3) The parameter \p graph is the empty or null graph, + * i.e. the graph with zero vertices and edges. (4) The parameter + * \p algo refers to an unsupported stochastic imitation algorithm. + * + * Time complexity: depends on the uniform random number generator, but should + * usually be O(1). + * + * \example examples/simple/igraph_stochastic_imitation.c + */ + +int igraph_stochastic_imitation(const igraph_t *graph, + igraph_integer_t vid, + igraph_imitate_algorithm_t algo, + const igraph_vector_t *quantities, + igraph_vector_t *strategies, + igraph_neimode_t mode) { + igraph_bool_t updates; + igraph_integer_t u; + igraph_vector_t adj; + int i; + + /* sanity checks */ + if (algo != IGRAPH_IMITATE_AUGMENTED && + algo != IGRAPH_IMITATE_BLIND && + algo != IGRAPH_IMITATE_CONTRACTED) { + IGRAPH_ERROR("Unsupported stochastic imitation algorithm", + IGRAPH_EINVAL); + } + IGRAPH_CHECK(igraph_microscopic_standard_tests(graph, vid, quantities, + strategies, mode, &updates, + /*is local?*/ 1)); + if (!updates) { + return IGRAPH_SUCCESS; /* nothing more to do */ + } + + /* immediate neighbours of v */ + IGRAPH_VECTOR_INIT_FINALLY(&adj, 0); + IGRAPH_CHECK(igraph_neighbors(graph, &adj, vid, mode)); + + /* Blind imitation. Let v be the vertex whose strategy we want to revise. */ + /* Choose a vertex u uniformly at random from the immediate neighbours of */ + /* v, including v itself. Then blindly update the strategy of v to that of */ + /* u, irrespective of whether doing so would increase or decrease the */ + /* quantity (e.g. fitness) of v. Here v retains its current strategy if */ + /* the chosen vertex u is indeed v itself. */ + if (algo == IGRAPH_IMITATE_BLIND) { + IGRAPH_CHECK(igraph_vector_push_back(&adj, vid)); + RNG_BEGIN(); + i = (int) RNG_INTEGER(0, igraph_vector_size(&adj) - 1); + RNG_END(); + u = (igraph_integer_t) VECTOR(adj)[i]; + VECTOR(*strategies)[vid] = VECTOR(*strategies)[u]; + } + /* Augmented imitation. Let v be the vertex whose strategy we want to */ + /* revise. Let f be the quantity function for the game. Choose a vertex u */ + /* uniformly at random from the immediate neighbours of v; do not include */ + /* v. Then v imitates the strategy of u if f(u) > f(v). Otherwise v */ + /* retains its current strategy. */ + else if (algo == IGRAPH_IMITATE_AUGMENTED) { + RNG_BEGIN(); + i = (int) RNG_INTEGER(0, igraph_vector_size(&adj) - 1); + RNG_END(); + u = (igraph_integer_t) VECTOR(adj)[i]; + if (VECTOR(*quantities)[u] > VECTOR(*quantities)[vid]) { + VECTOR(*strategies)[vid] = VECTOR(*strategies)[u]; + } + } + /* Contracted imitation. Let v be the vertex whose strategy we want to */ + /* update and let f be the quantity function for the game. Choose a vertex */ + /* u uniformly at random from the immediate neighbours of v, excluding v */ + /* itself. Then v imitates the strategy of u provided that f(u) < f(v). */ + /* Otherwise v retains its current strategy. */ + else if (algo == IGRAPH_IMITATE_CONTRACTED) { + RNG_BEGIN(); + i = (int) RNG_INTEGER(0, igraph_vector_size(&adj) - 1); + RNG_END(); + u = (igraph_integer_t) VECTOR(adj)[i]; + if (VECTOR(*quantities)[u] < VECTOR(*quantities)[vid]) { + VECTOR(*strategies)[vid] = VECTOR(*strategies)[u]; + } + } + + /* clean up */ + igraph_vector_destroy(&adj); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/mixing.c b/src/mixing.c new file mode 100644 index 0000000..c636900 --- /dev/null +++ b/src/mixing.c @@ -0,0 +1,300 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_mixing.h" +#include "igraph_interface.h" + +/** + * \function igraph_assortativity_nominal + * Assortativity of a graph based on vertex categories + * + * Assuming the vertices of the input graph belong to different + * categories, this function calculates the assortativity coefficient of + * the graph. The assortativity coefficient is between minus one and one + * and it is one if all connections stay within categories, it is + * minus one, if the network is perfectly disassortative. For a + * randomly connected network it is (asymptotically) zero. + * + * See equation (2) in M. E. J. Newman: Mixing patterns + * in networks, Phys. Rev. E 67, 026126 (2003) + * (http://arxiv.org/abs/cond-mat/0209450) for the proper + * definition. + * + * \param graph The input graph, it can be directed or undirected. + * \param types Vector giving the vertex types. They are assumed to be + * integer numbers, starting with zero. + * \param res Pointer to a real variable, the result is stored here. + * \param directed Boolean, it gives whether to consider edge + * directions in a directed graph. It is ignored for undirected + * graphs. + * \return Error code. + * + * Time complexity: O(|E|+t), |E| is the number of edges, t is the + * number of vertex types. + * + * \sa \ref igraph_assortativity if the vertex types are defines by + * numeric values (e.g. vertex degree), instead of categories. + * + * \example examples/simple/assortativity.c + */ + +int igraph_assortativity_nominal(const igraph_t *graph, + const igraph_vector_t *types, + igraph_real_t *res, + igraph_bool_t directed) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + long int no_of_types; + igraph_vector_t ai, bi, eii; + long int e, i; + igraph_real_t sumaibi = 0.0, sumeii = 0.0; + + if (igraph_vector_size(types) != no_of_nodes) { + IGRAPH_ERROR("Invalid `types' vector length", IGRAPH_EINVAL); + } + + if (igraph_vector_min(types) < 0) { + IGRAPH_ERROR("Invalid `types' vector", IGRAPH_EINVAL); + } + + directed = directed && igraph_is_directed(graph); + + no_of_types = (long int) igraph_vector_max(types) + 1; + IGRAPH_VECTOR_INIT_FINALLY(&ai, no_of_types); + IGRAPH_VECTOR_INIT_FINALLY(&bi, no_of_types); + IGRAPH_VECTOR_INIT_FINALLY(&eii, no_of_types); + + for (e = 0; e < no_of_edges; e++) { + long int from = IGRAPH_FROM(graph, e); + long int to = IGRAPH_TO(graph, e); + long int from_type = (long int) VECTOR(*types)[from]; + long int to_type = (long int) VECTOR(*types)[to]; + + VECTOR(ai)[from_type] += 1; + VECTOR(bi)[to_type] += 1; + if (from_type == to_type) { + VECTOR(eii)[from_type] += 1; + } + if (!directed) { + if (from_type == to_type) { + VECTOR(eii)[from_type] += 1; + } + VECTOR(ai)[to_type] += 1; + VECTOR(bi)[from_type] += 1; + } + } + + for (i = 0; i < no_of_types; i++) { + sumaibi += (VECTOR(ai)[i] / no_of_edges) * (VECTOR(bi)[i] / no_of_edges); + sumeii += (VECTOR(eii)[i] / no_of_edges); + } + + if (!directed) { + sumaibi /= 4.0; + sumeii /= 2.0; + } + + *res = (sumeii - sumaibi) / (1.0 - sumaibi); + + igraph_vector_destroy(&eii); + igraph_vector_destroy(&bi); + igraph_vector_destroy(&ai); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_assortativity + * Assortativity based on numeric properties of vertices + * + * This function calculates the assortativity coefficient of the input + * graph. This coefficient is basically the correlation between the + * actual connectivity patterns of the vertices and the pattern + * expected from the distribution of the vertex types. + * + * See equation (21) in M. E. J. Newman: Mixing patterns + * in networks, Phys. Rev. E 67, 026126 (2003) + * (http://arxiv.org/abs/cond-mat/0209450) for the proper + * definition. The actual calculation is performed using equation (26) + * in the same paper for directed graphs, and equation (4) in + * M. E. J. Newman: Assortative mixing in networks, + * Phys. Rev. Lett. 89, 208701 (2002) + * (http://arxiv.org/abs/cond-mat/0205405/) for undirected graphs. + * + * \param graph The input graph, it can be directed or undirected. + * \param types1 The vertex values, these can be arbitrary numeric + * values. + * \param types2 A second value vector to be using for the incoming + * edges when calculating assortativity for a directed graph. + * Supply a null pointer here if you want to use the same values + * for outgoing and incoming edges. This argument is ignored + * (with a warning) if it is not a null pointer and undirected + * assortativity coefficient is being calculated. + * \param res Pointer to a real variable, the result is stored here. + * \param directed Boolean, whether to consider edge directions for + * directed graphs. It is ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|E|), linear in the number of edges of the + * graph. + * + * \sa \ref igraph_assortativity_nominal() if you have discrete vertex + * categories instead of numeric labels, and \ref + * igraph_assortativity_degree() for the special case of assortativity + * based on vertex degree. + * + * \example examples/simple/assortativity.c + */ + +int igraph_assortativity(const igraph_t *graph, + const igraph_vector_t *types1, + const igraph_vector_t *types2, + igraph_real_t *res, + igraph_bool_t directed) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + long int e; + + directed = directed && igraph_is_directed(graph); + + if (!directed && types2) { + IGRAPH_WARNING("Only `types1' is used for undirected case"); + } + + if (igraph_vector_size(types1) != no_of_nodes) { + IGRAPH_ERROR("Invalid `types1' vector length", IGRAPH_EINVAL); + } + + if (types2 && igraph_vector_size(types2) != no_of_nodes) { + IGRAPH_ERROR("Invalid `types2' vector length", IGRAPH_EINVAL); + } + + if (!directed) { + igraph_real_t num1 = 0.0, num2 = 0.0, den1 = 0.0; + + for (e = 0; e < no_of_edges; e++) { + long int from = IGRAPH_FROM(graph, e); + long int to = IGRAPH_TO(graph, e); + igraph_real_t from_type = VECTOR(*types1)[from]; + igraph_real_t to_type = VECTOR(*types1)[to]; + + num1 += from_type * to_type; + num2 += from_type + to_type; + den1 += from_type * from_type + to_type * to_type; + } + + num1 /= no_of_edges; + den1 /= no_of_edges * 2; + num2 /= no_of_edges * 2; + num2 = num2 * num2; + + *res = (num1 - num2) / (den1 - num2); + + } else { + igraph_real_t num1 = 0.0, num2 = 0.0, num3 = 0.0, + den1 = 0.0, den2 = 0.0; + igraph_real_t num, den; + + if (!types2) { + types2 = types1; + } + + for (e = 0; e < no_of_edges; e++) { + long int from = IGRAPH_FROM(graph, e); + long int to = IGRAPH_TO(graph, e); + igraph_real_t from_type = VECTOR(*types1)[from]; + igraph_real_t to_type = VECTOR(*types2)[to]; + + num1 += from_type * to_type; + num2 += from_type; + num3 += to_type; + den1 += from_type * from_type; + den2 += to_type * to_type; + } + + num = num1 - num2 * num3 / no_of_edges; + den = sqrt(den1 - num2 * num2 / no_of_edges) * + sqrt(den2 - num3 * num3 / no_of_edges); + + *res = num / den; + } + + return 0; +} + +/** + * \function igraph_assortativity_degree + * Assortativity of a graph based on vertex degree + * + * Assortativity based on vertex degree, please see the discussion at + * the documentation of \ref igraph_assortativity() for details. + * + * \param graph The input graph, it can be directed or undirected. + * \param res Pointer to a real variable, the result is stored here. + * \param directed Boolean, whether to consider edge directions for + * directed graphs. This argument is ignored for undirected + * graphs. Supply 1 (=TRUE) here to do the natural thing, i.e. use + * directed version of the measure for directed graphs and the + * undirected version for undirected graphs. + * \return Error code. + * + * Time complexity: O(|E|+|V|), |E| is the number of edges, |V| is + * the number of vertices. + * + * \sa \ref igraph_assortativity() for the general function + * calculating assortativity for any kind of numeric vertex values. + * + * \example examples/simple/assortativity.c + */ + +int igraph_assortativity_degree(const igraph_t *graph, + igraph_real_t *res, + igraph_bool_t directed) { + + directed = directed && igraph_is_directed(graph); + + if (directed) { + igraph_vector_t indegree, outdegree; + igraph_vector_init(&indegree, 0); + igraph_vector_init(&outdegree, 0); + igraph_degree(graph, &indegree, igraph_vss_all(), IGRAPH_IN, /*loops=*/ 1); + igraph_degree(graph, &outdegree, igraph_vss_all(), IGRAPH_OUT, /*loops=*/ 1); + igraph_vector_add_constant(&indegree, -1); + igraph_vector_add_constant(&outdegree, -1); + igraph_assortativity(graph, &outdegree, &indegree, res, /*directed=*/ 1); + igraph_vector_destroy(&indegree); + igraph_vector_destroy(&outdegree); + } else { + igraph_vector_t degree; + igraph_vector_init(°ree, 0); + igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, /*loops=*/ 1); + igraph_vector_add_constant(°ree, -1); + igraph_assortativity(graph, °ree, 0, res, /*directed=*/ 0); + igraph_vector_destroy(°ree); + } + + return 0; +} diff --git a/src/motifs.c b/src/motifs.c new file mode 100644 index 0000000..9161461 --- /dev/null +++ b/src/motifs.c @@ -0,0 +1,1125 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_motifs.h" +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_adjlist.h" +#include "igraph_interrupt_internal.h" +#include "igraph_interface.h" +#include "igraph_nongraph.h" +#include "igraph_stack.h" +#include "config.h" + +/* TODO create header for these functions: */ +extern unsigned int igraph_i_isoclass_3[]; +extern unsigned int igraph_i_isoclass_4[]; +extern unsigned int igraph_i_isoclass_3u[]; +extern unsigned int igraph_i_isoclass_4u[]; +extern unsigned int igraph_i_isoclass2_3[]; +extern unsigned int igraph_i_isoclass2_4[]; +extern unsigned int igraph_i_isoclass2_3u[]; +extern unsigned int igraph_i_isoclass2_4u[]; +extern unsigned int igraph_i_isoclass_3_idx[]; +extern unsigned int igraph_i_isoclass_4_idx[]; +extern unsigned int igraph_i_isoclass_3u_idx[]; +extern unsigned int igraph_i_isoclass_4u_idx[]; + +/** + * Callback function for igraph_motifs_randesu that counts the motifs by + * isomorphism class in a histogram. + */ +static igraph_bool_t igraph_i_motifs_randesu_update_hist( + const igraph_t *graph, + igraph_vector_t *vids, int isoclass, void* extra) { + igraph_vector_t *hist = (igraph_vector_t*)extra; + IGRAPH_UNUSED(graph); IGRAPH_UNUSED(vids); + VECTOR(*hist)[isoclass]++; + return 0; +} + +/** + * \function igraph_motifs_randesu + * \brief Count the number of motifs in a graph + * + * + * Motifs are small connected subgraphs of a given structure in a + * graph. It is argued that the motif profile (ie. the number of + * different motifs in the graph) is characteristic for different + * types of networks and network function is related to the motifs in + * the graph. + * + * + * This function is able to find the different motifs of size three + * and four (ie. the number of different subgraphs with three and four + * vertices) in the network. + * + * + * In a big network the total number of motifs can be very large, so + * it takes a lot of time to find all of them, a sampling method can + * be used. This function is capable of doing sampling via the + * \c cut_prob argument. This argument gives the probability that + * a branch of the motif search tree will not be explored. See + * S. Wernicke and F. Rasche: FANMOD: a tool for fast network motif + * detection, Bioinformatics 22(9), 1152--1153, 2006 for details. + * + * + * Set the \c cut_prob argument to a zero vector for finding all + * motifs. + * + * + * Directed motifs will be counted in directed graphs and undirected + * motifs in undirected graphs. + * + * \param graph The graph to find the motifs in. + * \param hist The result of the computation, it gives the number of + * motifs found for each isomorphism class. See + * \ref igraph_isoclass() for help about isomorphism classes. + * Note that this function does \em not count isomorphism + * classes that are not connected and will report NaN (more + * precisely \c IGRAPH_NAN) for them. + * \param size The size of the motifs to search for. Only three and + * four are implemented currently. The limitation is not in the + * motif finding code, but the graph isomorphism code. + * \param cut_prob Vector of probabilities for cutting the search tree + * at a given level. The first element is the first level, etc. + * Supply all zeros here (of length \c size) to find all motifs + * in a graph. + * \return Error code. + * \sa \ref igraph_motifs_randesu_estimate() for estimating the number + * of motifs in a graph, this can help to set the \c cut_prob + * parameter; \ref igraph_motifs_randesu_no() to calculate the total + * number of motifs of a given size in a graph; + * \ref igraph_motifs_randesu_callback() for calling a callback function + * for every motif found; \ref igraph_subisomorphic_lad() for finding + * subgraphs on more than 4 vertices. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_motifs_randesu.c + */ +int igraph_motifs_randesu(const igraph_t *graph, igraph_vector_t *hist, + int size, const igraph_vector_t *cut_prob) { + int histlen; + + if (size != 3 && size != 4) { + IGRAPH_ERROR("Only 3 and 4 vertex motifs are implemented", + IGRAPH_EINVAL); + } + if (size == 3) { + histlen = igraph_is_directed(graph) ? 16 : 4; + } else { + histlen = igraph_is_directed(graph) ? 218 : 11; + } + + IGRAPH_CHECK(igraph_vector_resize(hist, histlen)); + igraph_vector_null(hist); + + IGRAPH_CHECK(igraph_motifs_randesu_callback(graph, size, cut_prob, + &igraph_i_motifs_randesu_update_hist, hist)); + + if (size == 3) { + if (igraph_is_directed(graph)) { + VECTOR(*hist)[0] = VECTOR(*hist)[1] = VECTOR(*hist)[3] = IGRAPH_NAN; + } else { + VECTOR(*hist)[0] = VECTOR(*hist)[1] = IGRAPH_NAN; + } + } else if (size == 4) { + if (igraph_is_directed(graph)) { + int not_connected[] = { 0, 1, 2, 4, 5, 6, 9, 10, 11, 15, 22, 23, 27, + 28, 33, 34, 39, 62, 120 + }; + int i, n = sizeof(not_connected) / sizeof(int); + for (i = 0; i < n; i++) { + VECTOR(*hist)[not_connected[i]] = IGRAPH_NAN; + } + } else { + VECTOR(*hist)[0] = VECTOR(*hist)[1] = VECTOR(*hist)[2] = + VECTOR(*hist)[3] = VECTOR(*hist)[5] = IGRAPH_NAN; + } + } + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_motifs_randesu_callback + * \brief Finds motifs in a graph and calls a function for each of them + * + * + * Similarly to \ref igraph_motifs_randesu(), this function is able to find the + * different motifs of size three and four (ie. the number of different + * subgraphs with three and four vertices) in the network. However, instead of + * counting them, the function will call a callback function for each motif + * found to allow further tests or post-processing. + * + * + * The \c cut_prob argument also allows sampling the motifs, just like for + * \ref igraph_motifs_randesu(). Set the \c cut_prob argument to a zero vector + * for finding all motifs. + * + * \param graph The graph to find the motifs in. + * \param size The size of the motifs to search for. Only three and + * four are implemented currently. The limitation is not in the + * motif finding code, but the graph isomorphism code. + * \param cut_prob Vector of probabilities for cutting the search tree + * at a given level. The first element is the first level, etc. + * Supply all zeros here (of length \c size) to find all motifs + * in a graph. + * \param callback A pointer to a function of type \ref igraph_motifs_handler_t. + * This function will be called whenever a new motif is found. + * \param extra Extra argument to pass to the callback function. + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_motifs_randesu.c + */ + +int igraph_motifs_randesu_callback(const igraph_t *graph, int size, + const igraph_vector_t *cut_prob, igraph_motifs_handler_t *callback, + void* extra) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_adjlist_t allneis, alloutneis; + igraph_vector_int_t *neis; + long int father; + long int i, j, s; + long int motifs = 0; + + igraph_vector_t vids; /* this is G */ + igraph_vector_t adjverts; /* this is V_E */ + igraph_stack_t stack; /* this is S */ + long int *added; + char *subg; + + unsigned int *arr_idx, *arr_code; + int code = 0; + unsigned char mul, idx; + + igraph_bool_t terminate = 0; + + if (size != 3 && size != 4) { + IGRAPH_ERROR("Only 3 and 4 vertex motifs are implemented", + IGRAPH_EINVAL); + } + + if (igraph_vector_size(cut_prob) < size) { + IGRAPH_ERROR("The size of the cut probability vector must not be smaller than the motif size.", + IGRAPH_EINVAL); + } + + if (size == 3) { + mul = 3; + if (igraph_is_directed(graph)) { + arr_idx = igraph_i_isoclass_3_idx; + arr_code = igraph_i_isoclass2_3; + } else { + arr_idx = igraph_i_isoclass_3u_idx; + arr_code = igraph_i_isoclass2_3u; + } + } else { + mul = 4; + if (igraph_is_directed(graph)) { + arr_idx = igraph_i_isoclass_4_idx; + arr_code = igraph_i_isoclass2_4; + } else { + arr_idx = igraph_i_isoclass_4u_idx; + arr_code = igraph_i_isoclass2_4u; + } + } + + added = igraph_Calloc(no_of_nodes, long int); + if (added == 0) { + IGRAPH_ERROR("Cannot find motifs", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + + subg = igraph_Calloc(no_of_nodes, char); + if (subg == 0) { + IGRAPH_ERROR("Cannot find motifs", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, subg); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + IGRAPH_CHECK(igraph_adjlist_init(graph, &alloutneis, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &alloutneis); + + IGRAPH_VECTOR_INIT_FINALLY(&vids, 0); + IGRAPH_VECTOR_INIT_FINALLY(&adjverts, 0); + IGRAPH_CHECK(igraph_stack_init(&stack, 0)); + IGRAPH_FINALLY(igraph_stack_destroy, &stack); + + RNG_BEGIN(); + + for (father = 0; father < no_of_nodes; father++) { + long int level; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (VECTOR(*cut_prob)[0] == 1 || + RNG_UNIF01() < VECTOR(*cut_prob)[0]) { + continue; + } + + /* init G */ + igraph_vector_clear(&vids); level = 0; + IGRAPH_CHECK(igraph_vector_push_back(&vids, father)); + subg[father] = 1; added[father] += 1; level += 1; + + /* init V_E */ + igraph_vector_clear(&adjverts); + neis = igraph_adjlist_get(&allneis, father); + s = igraph_vector_int_size(neis); + for (i = 0; i < s; i++) { + long int nei = (long int) VECTOR(*neis)[i]; + if (!added[nei] && nei > father) { + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, nei)); + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, father)); + } + added[nei] += 1; + } + + /* init S */ + igraph_stack_clear(&stack); + + while (level > 1 || !igraph_vector_empty(&adjverts)) { + igraph_real_t cp = VECTOR(*cut_prob)[level]; + + if (level == size - 1) { + s = igraph_vector_size(&adjverts) / 2; + for (i = 0; i < s; i++) { + long int k, s2; + long int last; + + if (cp != 0 && RNG_UNIF01() < cp) { + continue; + } + motifs += 1; + + last = (long int) VECTOR(adjverts)[2 * i]; + IGRAPH_CHECK(igraph_vector_push_back(&vids, last)); + subg[last] = (char) size; + + code = 0; idx = 0; + for (k = 0; k < size; k++) { + long int from = (long int) VECTOR(vids)[k]; + neis = igraph_adjlist_get(&alloutneis, from); + s2 = igraph_vector_int_size(neis); + for (j = 0; j < s2; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + if (subg[nei] && k != subg[nei] - 1) { + idx = (unsigned char) (mul * k + (subg[nei] - 1)); + code |= arr_idx[idx]; + } + } + } + + if (callback(graph, &vids, (int) arr_code[code], extra)) { + terminate = 1; + break; + } + igraph_vector_pop_back(&vids); + subg[last] = 0; + } + } + + /* did the callback function asked us to terminate the search? */ + if (terminate) { + break; + } + + /* can we step down? */ + if (level < size - 1 && + !igraph_vector_empty(&adjverts)) { + /* we might step down */ + long int neifather = (long int) igraph_vector_pop_back(&adjverts); + long int nei = (long int) igraph_vector_pop_back(&adjverts); + + if (cp == 0 || RNG_UNIF01() > cp) { + /* yes, step down */ + IGRAPH_CHECK(igraph_vector_push_back(&vids, nei)); + subg[nei] = (char) level + 1; added[nei] += 1; level += 1; + + IGRAPH_CHECK(igraph_stack_push(&stack, neifather)); + IGRAPH_CHECK(igraph_stack_push(&stack, nei)); + IGRAPH_CHECK(igraph_stack_push(&stack, level)); + + neis = igraph_adjlist_get(&allneis, nei); + s = igraph_vector_int_size(neis); + for (i = 0; i < s; i++) { + long int nei2 = (long int) VECTOR(*neis)[i]; + if (!added[nei2] && nei2 > father) { + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, nei2)); + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, nei)); + } + added[nei2] += 1; + } + } + } else { + /* no, step back */ + long int nei, neifather; + while (!igraph_stack_empty(&stack) && + level == igraph_stack_top(&stack) - 1) { + igraph_stack_pop(&stack); + nei = (long int) igraph_stack_pop(&stack); + neifather = (long int) igraph_stack_pop(&stack); + igraph_vector_push_back(&adjverts, nei); + igraph_vector_push_back(&adjverts, neifather); + } + + nei = (long int) igraph_vector_pop_back(&vids); + subg[nei] = 0; added[nei] -= 1; level -= 1; + neis = igraph_adjlist_get(&allneis, nei); + s = igraph_vector_int_size(neis); + for (i = 0; i < s; i++) { + added[ (long int) VECTOR(*neis)[i] ] -= 1; + } + while (!igraph_vector_empty(&adjverts) && + igraph_vector_tail(&adjverts) == nei) { + igraph_vector_pop_back(&adjverts); + igraph_vector_pop_back(&adjverts); + } + } + + } /* while */ + + /* did the callback function asked us to terminate the search? */ + if (terminate) { + break; + } + + /* clear the added vector */ + added[father] -= 1; + subg[father] = 0; + neis = igraph_adjlist_get(&allneis, father); + s = igraph_vector_int_size(neis); + for (i = 0; i < s; i++) { + added[ (long int) VECTOR(*neis)[i] ] -= 1; + } + + } /* for father */ + + RNG_END(); + + igraph_Free(added); + igraph_Free(subg); + igraph_vector_destroy(&vids); + igraph_vector_destroy(&adjverts); + igraph_adjlist_destroy(&alloutneis); + igraph_adjlist_destroy(&allneis); + igraph_stack_destroy(&stack); + IGRAPH_FINALLY_CLEAN(7); + return 0; +} + +/** + * \function igraph_motifs_randesu_estimate + * \brief Estimate the total number of motifs in a graph + * + * + * This function is useful for large graphs for which it is not + * feasible to count all the different motifs, because there is very + * many of them. + * + * + * The total number of motifs is estimated by taking a sample of + * vertices and counts all motifs in which these vertices are + * included. (There is also a \c cut_prob parameter which gives the + * probabilities to cut a branch of the search tree.) + * + * + * Directed motifs will be counted in directed graphs and undirected + * motifs in undirected graphs. + * + * \param graph The graph object to study. + * \param est Pointer to an integer type, the result will be stored + * here. + * \param size The size of the motif to look for. + * \param cut_prob Vector giving the probabilities to cut a branch of + * the search tree and omit counting the motifs in that branch. + * It contains a probability for each level. Supply \c size + * zeros here to count all the motifs in the sample. + * \param sample_size The number of vertices to use as the + * sample. This parameter is only used if the \c parsample + * argument is a null pointer. + * \param parsample Either pointer to an initialized vector or a null + * pointer. If a vector then the vertex ids in the vector are + * used as a sample. If a null pointer then the \c sample_size + * argument is used to create a sample of vertices drawn with + * uniform probability. + * \return Error code. + * \sa \ref igraph_motifs_randesu(), \ref igraph_motifs_randesu_no(). + * + * Time complexity: TODO. + */ + +int igraph_motifs_randesu_estimate(const igraph_t *graph, igraph_integer_t *est, + int size, const igraph_vector_t *cut_prob, + igraph_integer_t sample_size, + const igraph_vector_t *parsample) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t neis; + + igraph_vector_t vids; /* this is G */ + igraph_vector_t adjverts; /* this is V_E */ + igraph_stack_t stack; /* this is S */ + long int *added; + igraph_vector_t *sample; + long int sam; + long int i; + + added = igraph_Calloc(no_of_nodes, long int); + if (added == 0) { + IGRAPH_ERROR("Cannot find motifs", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + + IGRAPH_VECTOR_INIT_FINALLY(&vids, 0); + IGRAPH_VECTOR_INIT_FINALLY(&adjverts, 0); + IGRAPH_CHECK(igraph_stack_init(&stack, 0)); + IGRAPH_FINALLY(igraph_stack_destroy, &stack); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + if (parsample == 0) { + sample = igraph_Calloc(1, igraph_vector_t); + if (sample == 0) { + IGRAPH_ERROR("Cannot estimate motifs", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, sample); + IGRAPH_VECTOR_INIT_FINALLY(sample, 0); + IGRAPH_CHECK(igraph_random_sample(sample, 0, no_of_nodes - 1, sample_size)); + } else { + sample = (igraph_vector_t*)parsample; + sample_size = (igraph_integer_t) igraph_vector_size(sample); + } + + *est = 0; + + RNG_BEGIN(); + + for (sam = 0; sam < sample_size; sam++) { + long int father = (long int) VECTOR(*sample)[sam]; + long int level, s; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (VECTOR(*cut_prob)[0] == 1 || + RNG_UNIF01() < VECTOR(*cut_prob)[0]) { + continue; + } + + /* init G */ + igraph_vector_clear(&vids); level = 0; + IGRAPH_CHECK(igraph_vector_push_back(&vids, father)); + added[father] += 1; level += 1; + + /* init V_E */ + igraph_vector_clear(&adjverts); + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) father, + IGRAPH_ALL)); + s = igraph_vector_size(&neis); + for (i = 0; i < s; i++) { + long int nei = (long int) VECTOR(neis)[i]; + if (!added[nei] && nei > father) { + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, nei)); + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, father)); + } + added[nei] += 1; + } + + /* init S */ + igraph_stack_clear(&stack); + + while (level > 1 || !igraph_vector_empty(&adjverts)) { + igraph_real_t cp = VECTOR(*cut_prob)[level]; + + if (level == size - 1) { + s = igraph_vector_size(&adjverts) / 2; + for (i = 0; i < s; i++) { + if (cp != 0 && RNG_UNIF01() < cp) { + continue; + } + (*est) += 1; + } + } + + if (level < size - 1 && + !igraph_vector_empty(&adjverts)) { + /* We might step down */ + long int neifather = (long int) igraph_vector_pop_back(&adjverts); + long int nei = (long int) igraph_vector_pop_back(&adjverts); + + if (cp == 0 || RNG_UNIF01() > cp) { + /* Yes, step down */ + IGRAPH_CHECK(igraph_vector_push_back(&vids, nei)); + added[nei] += 1; level += 1; + + IGRAPH_CHECK(igraph_stack_push(&stack, neifather)); + IGRAPH_CHECK(igraph_stack_push(&stack, nei)); + IGRAPH_CHECK(igraph_stack_push(&stack, level)); + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) nei, + IGRAPH_ALL)); + s = igraph_vector_size(&neis); + for (i = 0; i < s; i++) { + long int nei2 = (long int) VECTOR(neis)[i]; + if (!added[nei2] && nei2 > father) { + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, nei2)); + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, nei)); + } + added[nei2] += 1; + } + } + } else { + /* no, step back */ + long int nei, neifather; + while (!igraph_stack_empty(&stack) && + level == igraph_stack_top(&stack) - 1) { + igraph_stack_pop(&stack); + nei = (long int) igraph_stack_pop(&stack); + neifather = (long int) igraph_stack_pop(&stack); + igraph_vector_push_back(&adjverts, nei); + igraph_vector_push_back(&adjverts, neifather); + } + + nei = (long int) igraph_vector_pop_back(&vids); + added[nei] -= 1; level -= 1; + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) nei, + IGRAPH_ALL)); + s = igraph_vector_size(&neis); + for (i = 0; i < s; i++) { + added[ (long int) VECTOR(neis)[i] ] -= 1; + } + while (!igraph_vector_empty(&adjverts) && + igraph_vector_tail(&adjverts) == nei) { + igraph_vector_pop_back(&adjverts); + igraph_vector_pop_back(&adjverts); + } + } + + } /* while */ + + /* clear the added vector */ + added[father] -= 1; + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) father, + IGRAPH_ALL)); + s = igraph_vector_size(&neis); + for (i = 0; i < s; i++) { + added[ (long int) VECTOR(neis)[i] ] -= 1; + } + + } /* for father */ + + RNG_END(); + + (*est) *= ((double)no_of_nodes / sample_size); + + if (parsample == 0) { + igraph_vector_destroy(sample); + igraph_Free(sample); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_Free(added); + igraph_vector_destroy(&vids); + igraph_vector_destroy(&adjverts); + igraph_stack_destroy(&stack); + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(5); + return 0; +} + +/** + * \function igraph_motifs_randesu_no + * \brief Count the total number of motifs in a graph + * + * + * This function counts the total number of motifs in a graph without + * assigning isomorphism classes to them. + * + * + * Directed motifs will be counted in directed graphs and undirected + * motifs in undirected graphs. + * + * \param graph The graph object to study. + * \param no Pointer to an integer type, the result will be stored + * here. + * \param size The size of the motifs to count. + * \param cut_prob Vector giving the probabilities that a branch of + * the search tree will be cut at a given level. + * \return Error code. + * \sa \ref igraph_motifs_randesu(), \ref + * igraph_motifs_randesu_estimate(). + * + * Time complexity: TODO. + */ + +int igraph_motifs_randesu_no(const igraph_t *graph, igraph_integer_t *no, + int size, const igraph_vector_t *cut_prob) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t neis; + + igraph_vector_t vids; /* this is G */ + igraph_vector_t adjverts; /* this is V_E */ + igraph_stack_t stack; /* this is S */ + long int *added; + long int father; + long int i; + + added = igraph_Calloc(no_of_nodes, long int); + if (added == 0) { + IGRAPH_ERROR("Cannot find motifs", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + + IGRAPH_VECTOR_INIT_FINALLY(&vids, 0); + IGRAPH_VECTOR_INIT_FINALLY(&adjverts, 0); + IGRAPH_CHECK(igraph_stack_init(&stack, 0)); + IGRAPH_FINALLY(igraph_stack_destroy, &stack); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + *no = 0; + + RNG_BEGIN(); + + for (father = 0; father < no_of_nodes; father++) { + long int level, s; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (VECTOR(*cut_prob)[0] == 1 || + RNG_UNIF01() < VECTOR(*cut_prob)[0]) { + continue; + } + + /* init G */ + igraph_vector_clear(&vids); level = 0; + IGRAPH_CHECK(igraph_vector_push_back(&vids, father)); + added[father] += 1; level += 1; + + /* init V_E */ + igraph_vector_clear(&adjverts); + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) father, + IGRAPH_ALL)); + s = igraph_vector_size(&neis); + for (i = 0; i < s; i++) { + long int nei = (long int) VECTOR(neis)[i]; + if (!added[nei] && nei > father) { + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, nei)); + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, father)); + } + added[nei] += 1; + } + + /* init S */ + igraph_stack_clear(&stack); + + while (level > 1 || !igraph_vector_empty(&adjverts)) { + igraph_real_t cp = VECTOR(*cut_prob)[level]; + + if (level == size - 1) { + s = igraph_vector_size(&adjverts) / 2; + for (i = 0; i < s; i++) { + if (cp != 0 && RNG_UNIF01() < cp) { + continue; + } + (*no) += 1; + } + } + + if (level < size - 1 && + !igraph_vector_empty(&adjverts)) { + /* We might step down */ + long int neifather = (long int) igraph_vector_pop_back(&adjverts); + long int nei = (long int) igraph_vector_pop_back(&adjverts); + + if (cp == 0 || RNG_UNIF01() > cp) { + /* Yes, step down */ + IGRAPH_CHECK(igraph_vector_push_back(&vids, nei)); + added[nei] += 1; level += 1; + + IGRAPH_CHECK(igraph_stack_push(&stack, neifather)); + IGRAPH_CHECK(igraph_stack_push(&stack, nei)); + IGRAPH_CHECK(igraph_stack_push(&stack, level)); + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) nei, + IGRAPH_ALL)); + s = igraph_vector_size(&neis); + for (i = 0; i < s; i++) { + long int nei2 = (long int) VECTOR(neis)[i]; + if (!added[nei2] && nei2 > father) { + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, nei2)); + IGRAPH_CHECK(igraph_vector_push_back(&adjverts, nei)); + } + added[nei2] += 1; + } + } + } else { + /* no, step back */ + long int nei, neifather; + while (!igraph_stack_empty(&stack) && + level == igraph_stack_top(&stack) - 1) { + igraph_stack_pop(&stack); + nei = (long int) igraph_stack_pop(&stack); + neifather = (long int) igraph_stack_pop(&stack); + igraph_vector_push_back(&adjverts, nei); + igraph_vector_push_back(&adjverts, neifather); + } + + nei = (long int) igraph_vector_pop_back(&vids); + added[nei] -= 1; level -= 1; + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) nei, + IGRAPH_ALL)); + s = igraph_vector_size(&neis); + for (i = 0; i < s; i++) { + added[ (long int) VECTOR(neis)[i] ] -= 1; + } + while (!igraph_vector_empty(&adjverts) && + igraph_vector_tail(&adjverts) == nei) { + igraph_vector_pop_back(&adjverts); + igraph_vector_pop_back(&adjverts); + } + } + + } /* while */ + + /* clear the added vector */ + added[father] -= 1; + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) father, + IGRAPH_ALL)); + s = igraph_vector_size(&neis); + for (i = 0; i < s; i++) { + added[ (long int) VECTOR(neis)[i] ] -= 1; + } + + } /* for father */ + + RNG_END(); + + igraph_Free(added); + igraph_vector_destroy(&vids); + igraph_vector_destroy(&adjverts); + igraph_stack_destroy(&stack); + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(5); + return 0; +} + +/** + * \function igraph_dyad_census + * \brief Calculating the dyad census as defined by Holland and Leinhardt + * + * + * Dyad census means classifying each pair of vertices of a directed + * graph into three categories: mutual, there is an edge from \c a to + * \c b and also from \c b to \c a; asymmetric, there is an edge + * either from \c a to \c b or from \c b to \c a but not the other way + * and null, no edges between \c a and \c b. + * + * + * Holland, P.W. and Leinhardt, S. (1970). A Method for Detecting + * Structure in Sociometric Data. American Journal of Sociology, + * 70, 492-513. + * \param graph The input graph, a warning is given if undirected as + * the results are undefined for undirected graphs. + * \param mut Pointer to an integer, the number of mutual dyads is + * stored here. + * \param asym Pointer to an integer, the number of asymmetric dyads + * is stored here. + * \param null Pointer to an integer, the number of null dyads is + * stored here. In case of an integer overflow (i.e. too many + * null dyads), -1 will be returned. + * \return Error code. + * + * \sa \ref igraph_reciprocity(), \ref igraph_triad_census(). + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + */ + +int igraph_dyad_census(const igraph_t *graph, igraph_integer_t *mut, + igraph_integer_t *asym, igraph_integer_t *null) { + + igraph_integer_t nonrec = 0, rec = 0; + igraph_vector_t inneis, outneis; + igraph_integer_t vc = igraph_vcount(graph); + long int i; + + if (!igraph_is_directed(graph)) { + IGRAPH_WARNING("Dyad census called on undirected graph"); + } + + IGRAPH_VECTOR_INIT_FINALLY(&inneis, 0); + IGRAPH_VECTOR_INIT_FINALLY(&outneis, 0); + + for (i = 0; i < vc; i++) { + long int ip, op; + igraph_neighbors(graph, &inneis, i, IGRAPH_IN); + igraph_neighbors(graph, &outneis, i, IGRAPH_OUT); + + ip = op = 0; + while (ip < igraph_vector_size(&inneis) && + op < igraph_vector_size(&outneis)) { + if (VECTOR(inneis)[ip] < VECTOR(outneis)[op]) { + nonrec += 1; + ip++; + } else if (VECTOR(inneis)[ip] > VECTOR(outneis)[op]) { + nonrec += 1; + op++; + } else { + rec += 1; + ip++; + op++; + } + } + nonrec += (igraph_vector_size(&inneis) - ip) + + (igraph_vector_size(&outneis) - op); + } + + igraph_vector_destroy(&inneis); + igraph_vector_destroy(&outneis); + IGRAPH_FINALLY_CLEAN(2); + + *mut = rec / 2; + *asym = nonrec / 2; + if (vc % 2) { + *null = vc * ((vc - 1) / 2); + } else { + *null = (vc / 2) * (vc - 1); + } + if (*null < vc) { + IGRAPH_WARNING("Integer overflow, returning -1"); + *null = -1; + } else { + *null = *null - (*mut) - (*asym); + } + + return 0; +} + +/** + * \function igraph_triad_census_24 + * TODO + */ + +int igraph_triad_census_24(const igraph_t *graph, igraph_real_t *res2, + igraph_real_t *res4) { + + long int vc = igraph_vcount(graph); + igraph_vector_long_t seen; + igraph_vector_int_t *neis, *neis2; + long int i, j, k, s, neilen, neilen2, ign; + igraph_adjlist_t adjlist; + + IGRAPH_CHECK(igraph_vector_long_init(&seen, vc)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &seen); + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + *res2 = *res4 = 0; + + for (i = 0; i < vc; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + + neis = igraph_adjlist_get(&adjlist, i); + neilen = igraph_vector_int_size(neis); + /* mark neighbors of i & i itself */ + VECTOR(seen)[i] = i + 1; + ign = 0; + for (j = 0; j < neilen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + if (VECTOR(seen)[nei] == i + 1 || VECTOR(seen)[nei] == -(i + 1)) { + /* multiple edges or loop edge */ + VECTOR(seen)[nei] = -(i + 1); + ign++; + } else { + VECTOR(seen)[nei] = i + 1; + } + } + + for (j = 0; j < neilen; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + if (nei <= i || (j > 0 && nei == VECTOR(*neis)[j - 1])) { + continue; + } + neis2 = igraph_adjlist_get(&adjlist, nei); + neilen2 = igraph_vector_int_size(neis2); + s = 0; + for (k = 0; k < neilen2; k++) { + long int nei2 = (long int) VECTOR(*neis2)[k]; + if (k > 0 && nei2 == VECTOR(*neis2)[k - 1]) { + continue; + } + if (VECTOR(seen)[nei2] != i + 1 && VECTOR(seen)[nei2] != -(i + 1)) { + s++; + } + } + if (VECTOR(seen)[nei] > 0) { + *res2 += vc - s - neilen + ign - 1; + } else { + *res4 += vc - s - neilen + ign - 1; + } + } + } + + igraph_adjlist_destroy(&adjlist); + igraph_vector_long_destroy(&seen); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_triad_census + * \brief Triad census, as defined by Davis and Leinhardt + * + * + * Calculating the triad census means classifying every triple of + * vertices in a directed graph. A triple can be in one of 16 states: + * \clist + * \cli 003 + * A, B, C, the empty graph. + * \cli 012 + * A->B, C, a graph with a single directed edge. + * \cli 102 + * A<->B, C, a graph with a mutual connection between two vertices. + * \cli 021D + * A<-B->C, the binary out-tree. + * \cli 021U + * A->B<-C, the binary in-tree. + * \cli 021C + * A->B->C, the directed line. + * \cli 111D + * A<->B<-C. + * \cli 111U + * A<->B->C. + * \cli 030T + * A->B<-C, A->C. + * \cli 030C + * A<-B<-C, A->C. + * \cli 201 + * A<->B<->C. + * \cli 120D + * A<-B->C, A<->C. + * \cli 120U + * A->B<-C, A<->C. + * \cli 120C + * A->B->C, A<->C. + * \cli 210 + * A->B<->C, A<->C. + * \cli 300 + * A<->B<->C, A<->C, the complete graph. + * \endclist + * + * + * See also Davis, J.A. and Leinhardt, S. (1972). The Structure of + * Positive Interpersonal Relations in Small Groups. In J. Berger + * (Ed.), Sociological Theories in Progress, Volume 2, 218-251. + * Boston: Houghton Mifflin. + * + * + * This function calls \ref igraph_motifs_randesu() which is an + * implementation of the FANMOD motif finder tool, see \ref + * igraph_motifs_randesu() for details. Note that the order of the + * triads is not the same for \ref igraph_triad_census() and \ref + * igraph_motifs_randesu(). + * + * \param graph The input graph. A warning is given for undirected + * graphs, as the result is undefined for those. + * \param res Pointer to an initialized vector, the result is stored + * here in the same order as given in the list above. Note that this + * order is different than the one used by \ref igraph_motifs_randesu(). + * \return Error code. + * + * \sa \ref igraph_motifs_randesu(), \ref igraph_dyad_census(). + * + * Time complexity: TODO. + */ + +int igraph_triad_census(const igraph_t *graph, igraph_vector_t *res) { + + igraph_vector_t cut_prob; + igraph_real_t m2, m4; + igraph_vector_t tmp; + igraph_integer_t vc = igraph_vcount(graph); + igraph_real_t total; + + if (!igraph_is_directed(graph)) { + IGRAPH_WARNING("Triad census called on an undirected graph"); + } + + IGRAPH_VECTOR_INIT_FINALLY(&tmp, 0); + IGRAPH_VECTOR_INIT_FINALLY(&cut_prob, 3); /* all zeros */ + IGRAPH_CHECK(igraph_vector_resize(res, 16)); + igraph_vector_null(res); + IGRAPH_CHECK(igraph_motifs_randesu(graph, &tmp, 3, &cut_prob)); + IGRAPH_CHECK(igraph_triad_census_24(graph, &m2, &m4)); + + total = ((igraph_real_t)vc) * (vc - 1); + total *= (vc - 2); + total /= 6; + + /* Reorder */ + if (igraph_is_directed(graph)) { + VECTOR(tmp)[0] = 0; + VECTOR(tmp)[1] = m2; + VECTOR(tmp)[3] = m4; + VECTOR(tmp)[0] = total - igraph_vector_sum(&tmp); + + VECTOR(*res)[0] = VECTOR(tmp)[0]; + VECTOR(*res)[1] = VECTOR(tmp)[1]; + VECTOR(*res)[2] = VECTOR(tmp)[3]; + VECTOR(*res)[3] = VECTOR(tmp)[6]; + VECTOR(*res)[4] = VECTOR(tmp)[2]; + VECTOR(*res)[5] = VECTOR(tmp)[4]; + VECTOR(*res)[6] = VECTOR(tmp)[5]; + VECTOR(*res)[7] = VECTOR(tmp)[9]; + VECTOR(*res)[8] = VECTOR(tmp)[7]; + VECTOR(*res)[9] = VECTOR(tmp)[11]; + VECTOR(*res)[10] = VECTOR(tmp)[10]; + VECTOR(*res)[11] = VECTOR(tmp)[8]; + VECTOR(*res)[12] = VECTOR(tmp)[13]; + VECTOR(*res)[13] = VECTOR(tmp)[12]; + VECTOR(*res)[14] = VECTOR(tmp)[14]; + VECTOR(*res)[15] = VECTOR(tmp)[15]; + } else { + VECTOR(tmp)[0] = 0; + VECTOR(tmp)[1] = m2; + VECTOR(tmp)[0] = total - igraph_vector_sum(&tmp); + + VECTOR(*res)[0] = VECTOR(tmp)[0]; + VECTOR(*res)[2] = VECTOR(tmp)[1]; + VECTOR(*res)[10] = VECTOR(tmp)[2]; + VECTOR(*res)[15] = VECTOR(tmp)[3]; + } + + igraph_vector_destroy(&cut_prob); + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + diff --git a/src/operators.c b/src/operators.c new file mode 100644 index 0000000..1ef14d5 --- /dev/null +++ b/src/operators.c @@ -0,0 +1,1242 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_operators.h" +#include "igraph_error.h" +#include "igraph_memory.h" +#include "igraph_interrupt_internal.h" +#include "igraph_interface.h" +#include "igraph_constructors.h" +#include "igraph_adjlist.h" +#include "igraph_attributes.h" +#include "igraph_conversion.h" +#include "igraph_qsort.h" +#include "config.h" +#include + +/** + * \function igraph_disjoint_union + * \brief Creates the union of two disjoint graphs + * + * + * First the vertices of the second graph will be relabeled with new + * vertex ids to have two disjoint sets of vertex ids, then the union + * of the two graphs will be formed. + * If the two graphs have |V1| and |V2| vertices and |E1| and |E2| + * edges respectively then the new graph will have |V1|+|V2| vertices + * and |E1|+|E2| edges. + * + * + * Both graphs need to have the same directedness, ie. either both + * directed or both undirected. + * + * + * The current version of this function cannot handle graph, vertex + * and edge attributes, they will be lost. + * + * \param res Pointer to an uninitialized graph object, the result + * will stored here. + * \param left The first graph. + * \param right The second graph. + * \return Error code. + * \sa \ref igraph_disjoint_union_many() for creating the disjoint union + * of more than two graphs, \ref igraph_union() for non-disjoint + * union. + * + * Time complexity: O(|V1|+|V2|+|E1|+|E2|). + * + * \example examples/simple/igraph_disjoint_union.c + */ + +int igraph_disjoint_union(igraph_t *res, const igraph_t *left, + const igraph_t *right) { + + long int no_of_nodes_left = igraph_vcount(left); + long int no_of_nodes_right = igraph_vcount(right); + long int no_of_edges_left = igraph_ecount(left); + long int no_of_edges_right = igraph_ecount(right); + igraph_vector_t edges; + igraph_bool_t directed_left = igraph_is_directed(left); + igraph_integer_t from, to; + long int i; + + if (directed_left != igraph_is_directed(right)) { + IGRAPH_ERROR("Cannot union directed and undirected graphs", + IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, + 2 * (no_of_edges_left + no_of_edges_right))); + for (i = 0; i < no_of_edges_left; i++) { + igraph_edge(left, (igraph_integer_t) i, &from, &to); + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + } + for (i = 0; i < no_of_edges_right; i++) { + igraph_edge(right, (igraph_integer_t) i, &from, &to); + igraph_vector_push_back(&edges, from + no_of_nodes_left); + igraph_vector_push_back(&edges, to + no_of_nodes_left); + } + + IGRAPH_CHECK(igraph_create(res, &edges, (igraph_integer_t) + (no_of_nodes_left + no_of_nodes_right), + directed_left)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_disjoint_union_many + * \brief The disjint union of many graphs. + * + * + * First the vertices in the graphs will be relabeled with new vertex + * ids to have pairwise disjoint vertex id sets and then the union of + * the graphs is formed. + * The number of vertices and edges in the result is the total number + * of vertices and edges in the graphs. + * + * + * Both graphs need to have the same directedness, ie. either both + * directed or both undirected. + * + * + * The current version of this function cannot handle graph, vertex + * and edge attributes, they will be lost. + * + * \param res Pointer to an uninitialized graph object, the result of + * the operation will be stored here. + * \param graphs Pointer vector, contains pointers to initialized + * graph objects. + * \return Error code. + * \sa \ref igraph_disjoint_union() for an easier syntax if you have + * only two graphs, \ref igraph_union_many() for non-disjoint union. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges in the result. + */ + +int igraph_disjoint_union_many(igraph_t *res, + const igraph_vector_ptr_t *graphs) { + long int no_of_graphs = igraph_vector_ptr_size(graphs); + igraph_bool_t directed = 1; + igraph_vector_t edges; + long int no_of_edges = 0; + long int shift = 0; + igraph_t *graph; + long int i, j; + igraph_integer_t from, to; + + if (no_of_graphs != 0) { + graph = VECTOR(*graphs)[0]; + directed = igraph_is_directed(graph); + for (i = 0; i < no_of_graphs; i++) { + graph = VECTOR(*graphs)[i]; + no_of_edges += igraph_ecount(graph); + if (directed != igraph_is_directed(graph)) { + IGRAPH_ERROR("Cannot union directed and undirected graphs", + IGRAPH_EINVAL); + } + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, 2 * no_of_edges)); + + for (i = 0; i < no_of_graphs; i++) { + long int ec; + graph = VECTOR(*graphs)[i]; + ec = igraph_ecount(graph); + for (j = 0; j < ec; j++) { + igraph_edge(graph, (igraph_integer_t) j, &from, &to); + igraph_vector_push_back(&edges, from + shift); + igraph_vector_push_back(&edges, to + shift); + } + shift += igraph_vcount(graph); + } + + IGRAPH_CHECK(igraph_create(res, &edges, (igraph_integer_t) shift, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +static int igraph_i_order_edgelist_cmp(void *edges, const void *e1, const void *e2) { + igraph_vector_t *edgelist = edges; + long int edge1 = (*(const long int*) e1) * 2; + long int edge2 = (*(const long int*) e2) * 2; + long int from1 = VECTOR(*edgelist)[edge1]; + long int from2 = VECTOR(*edgelist)[edge2]; + if (from1 < from2) { + return -1; + } else if (from1 > from2) { + return 1; + } else { + long int to1 = VECTOR(*edgelist)[edge1 + 1]; + long int to2 = VECTOR(*edgelist)[edge2 + 1]; + if (to1 < to2) { + return -1; + } else if (to1 > to2) { + return 1; + } else { + return 0; + } + } +} + +#define IGRAPH_MODE_UNION 1 +#define IGRAPH_MODE_INTERSECTION 2 + +static int igraph_i_merge(igraph_t *res, int mode, + const igraph_t *left, const igraph_t *right, + igraph_vector_t *edge_map1, igraph_vector_t *edge_map2) { + + long int no_of_nodes_left = igraph_vcount(left); + long int no_of_nodes_right = igraph_vcount(right); + long int no_of_nodes; + long int no_edges_left = igraph_ecount(left); + long int no_edges_right = igraph_ecount(right); + igraph_bool_t directed = igraph_is_directed(left); + igraph_vector_t edges; + igraph_vector_t edges1, edges2; + igraph_vector_long_t order1, order2; + long int i, j, eptr = 0; + long int idx1, idx2, edge1 = -1, edge2 = -1, from1 = -1, from2 = -1, to1 = -1, to2 = -1; + igraph_bool_t l; + + if (directed != igraph_is_directed(right)) { + IGRAPH_ERROR("Cannot make union or intersection of directed " + "and undirected graph", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&edges1, no_edges_left * 2); + IGRAPH_VECTOR_INIT_FINALLY(&edges2, no_edges_right * 2); + IGRAPH_CHECK(igraph_vector_long_init(&order1, no_edges_left)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &order1); + IGRAPH_CHECK(igraph_vector_long_init(&order2, no_edges_right)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &order2); + + if (edge_map1) { + switch (mode) { + case IGRAPH_MODE_UNION: + IGRAPH_CHECK(igraph_vector_resize(edge_map1, no_edges_left)); + break; + case IGRAPH_MODE_INTERSECTION: + igraph_vector_clear(edge_map1); + break; + } + } + if (edge_map2) { + switch (mode) { + case IGRAPH_MODE_UNION: + IGRAPH_CHECK(igraph_vector_resize(edge_map2, no_edges_right)); + break; + case IGRAPH_MODE_INTERSECTION: + igraph_vector_clear(edge_map2); + break; + } + } + + no_of_nodes = no_of_nodes_left > no_of_nodes_right ? + no_of_nodes_left : no_of_nodes_right; + + /* We merge the two edge lists. We need to sort them first. + For undirected graphs, we also need to make sure that + for every edge, that larger (non-smaller) vertex id is in the + second column. */ + + IGRAPH_CHECK(igraph_get_edgelist(left, &edges1, /*bycol=*/ 0)); + IGRAPH_CHECK(igraph_get_edgelist(right, &edges2, /*bycol=*/ 0)); + if (!directed) { + for (i = 0, j = 0; i < no_edges_left; i++, j += 2) { + if (VECTOR(edges1)[j] > VECTOR(edges1)[j + 1]) { + long int tmp = VECTOR(edges1)[j]; + VECTOR(edges1)[j] = VECTOR(edges1)[j + 1]; + VECTOR(edges1)[j + 1] = tmp; + } + } + for (i = 0, j = 0; i < no_edges_right; i++, j += 2) { + if (VECTOR(edges2)[j] > VECTOR(edges2)[j + 1]) { + long int tmp = VECTOR(edges2)[j]; + VECTOR(edges2)[j] = VECTOR(edges2)[j + 1]; + VECTOR(edges2)[j + 1] = tmp; + } + } + } + + for (i = 0; i < no_edges_left; i++) { + VECTOR(order1)[i] = i; + } + for (i = 0; i < no_edges_right; i++) { + VECTOR(order2)[i] = i; + } + + igraph_qsort_r(VECTOR(order1), no_edges_left, sizeof(VECTOR(order1)[0]), + &edges1, igraph_i_order_edgelist_cmp); + igraph_qsort_r(VECTOR(order2), no_edges_right, sizeof(VECTOR(order2)[0]), + &edges2, igraph_i_order_edgelist_cmp); + +#define INC1() if ( (++idx1) < no_edges_left) { \ + edge1 = VECTOR(order1)[idx1]; \ + from1 = VECTOR(edges1)[2*edge1]; \ + to1 = VECTOR(edges1)[2*edge1+1]; \ + } +#define INC2() if ( (++idx2) < no_edges_right) { \ + edge2 = VECTOR(order2)[idx2]; \ + from2 = VECTOR(edges2)[2*edge2]; \ + to2 = VECTOR(edges2)[2*edge2+1]; \ + } + + idx1 = idx2 = -1; + INC1(); + INC2(); + +#define CONT() switch (mode) { \ + case IGRAPH_MODE_UNION: \ + l = idx1 < no_edges_left || idx2 < no_edges_right; \ + break; \ + case IGRAPH_MODE_INTERSECTION: \ + l = idx1 < no_edges_left && idx2 < no_edges_right; \ + break; \ + } + + CONT(); + while (l) { + if (idx2 >= no_edges_right || + (idx1 < no_edges_left && from1 < from2) || + (idx1 < no_edges_left && from1 == from2 && to1 < to2)) { + /* Edge from first graph */ + if (mode == IGRAPH_MODE_UNION) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, from1)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to1)); + if (edge_map1) { + VECTOR(*edge_map1)[edge1] = eptr; + } + eptr++; + } + INC1(); + } else if (idx1 >= no_edges_left || + (idx2 < no_edges_right && from2 < from1) || + (idx2 < no_edges_right && from1 == from2 && to2 < to1)) { + /* Edge from second graph */ + if (mode == IGRAPH_MODE_UNION) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, from2)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to2)); + if (edge_map2) { + VECTOR(*edge_map2)[edge2] = eptr; + } + eptr++; + } + INC2(); + } else { + /* Edge from both */ + IGRAPH_CHECK(igraph_vector_push_back(&edges, from1)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, to1)); + if (mode == IGRAPH_MODE_UNION) { + if (edge_map1) { + VECTOR(*edge_map1)[edge1] = eptr; + } + if (edge_map2) { + VECTOR(*edge_map2)[edge2] = eptr; + } + } else if (mode == IGRAPH_MODE_INTERSECTION) { + if (edge_map1) { + IGRAPH_CHECK(igraph_vector_push_back(edge_map1, edge1)); + } + if (edge_map2) { + IGRAPH_CHECK(igraph_vector_push_back(edge_map2, edge2)); + } + } + eptr++; + INC1(); + INC2(); + } + CONT(); + } + +#undef INC1 +#undef INC2 + + igraph_vector_long_destroy(&order2); + igraph_vector_long_destroy(&order1); + igraph_vector_destroy(&edges2); + igraph_vector_destroy(&edges1); + IGRAPH_FINALLY_CLEAN(4); + + IGRAPH_CHECK(igraph_create(res, &edges, no_of_nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_intersection + * \brief Collect the common edges from two graphs. + * + * + * The result graph contains only edges present both in the first and + * the second graph. The number of vertices in the result graph is the + * same as the larger from the two arguments. + * + * \param res Pointer to an uninitialized graph object. This will + * contain the result of the operation. + * \param left The first operand, a graph object. + * \param right The second operand, a graph object. + * \param edge_map1 Null pointer, or an initialized \type igraph_vector_t. + * If the latter, then a mapping from the edges of the result graph, to + * the edges of the \p left input graph is stored here. + * \param edge_map2 Null pointer, or an \type igraph_vector_t. The same + * as \p edge_map1, but for the \p right input graph. + * \return Error code. + * \sa \ref igraph_intersection_many() to calculate the intersection + * of many graphs at once, \ref igraph_union(), \ref + * igraph_difference() for other operators. + * + * Time complexity: O(|V|+|E|), |V| is the number of nodes, |E| + * is the number of edges in the smaller graph of the two. (The one + * containing less vertices is considered smaller.) + * + * \example examples/simple/igraph_intersection.c + */ + +int igraph_intersection(igraph_t *res, + const igraph_t *left, const igraph_t *right, + igraph_vector_t *edge_map1, + igraph_vector_t *edge_map2) { + return igraph_i_merge(res, IGRAPH_MODE_INTERSECTION, left, right, + edge_map1, edge_map2); +} + +static void igraph_i_union_many_free(igraph_vector_ptr_t *v) { + long int i, n = igraph_vector_ptr_size(v); + for (i = 0; i < n; i++) { + if (VECTOR(*v)[i] != 0) { + igraph_vector_destroy(VECTOR(*v)[i]); + igraph_Free(VECTOR(*v)[i]); + } + } + igraph_vector_ptr_destroy(v); +} + +static void igraph_i_union_many_free2(igraph_vector_ptr_t *v) { + long int i, n = igraph_vector_ptr_size(v); + for (i = 0; i < n; i++) { + if (VECTOR(*v)[i] != 0) { + igraph_vector_long_destroy(VECTOR(*v)[i]); + igraph_Free(VECTOR(*v)[i]); + } + } + igraph_vector_ptr_destroy(v); +} + +static void igraph_i_union_many_free3(igraph_vector_ptr_t *v) { + long int i, n = igraph_vector_ptr_size(v); + for (i = 0; i < n; i++) { + if (VECTOR(*v)[i] != 0) { + igraph_vector_destroy(VECTOR(*v)[i]); + igraph_Free(VECTOR(*v)[i]); + } + } +} + +/** + * \function igraph_intersection_many + * \brief The intersection of more than two graphs. + * + * + * This function calculates the intersection of the graphs stored in + * the \c graphs argument. Only those edges will be included in the + * result graph which are part of every graph in \c graphs. + * + * + * The number of vertices in the result graph will be the maximum + * number of vertices in the argument graphs. + * + * \param res Pointer to an uninitialized graph object, the result of + * the operation will be stored here. + * \param graphs Pointer vector, contains pointers to graphs objects, + * the operands of the intersection operator. + * \param edgemaps If not a null pointer, then it must be an initialized + * pointer vector and the mappings of edges from the graphs to the + * result graph will be stored here, in the same order as + * \p graphs. Each mapping is stored in a separate + * \type igraph_vector_t object. For the edges that are not in + * the intersection, -1 is stored. + * \return Error code. + * \sa \ref igraph_intersection() for the intersection of two graphs, + * \ref igraph_union_many(), \ref igraph_union() and \ref + * igraph_difference() for other operators. + * + * Time complexity: O(|V|+|E|), |V| is the number of vertices, + * |E| is the number of edges in the smallest graph (ie. the graph having + * the less vertices). + */ + +int igraph_intersection_many(igraph_t *res, + const igraph_vector_ptr_t *graphs, + igraph_vector_ptr_t *edgemaps) { + + long int no_of_graphs = igraph_vector_ptr_size(graphs); + long int no_of_nodes = 0; + igraph_bool_t directed = 1; + igraph_vector_t edges; + igraph_vector_ptr_t edge_vects, order_vects; + long int i, j, tailfrom = no_of_graphs > 0 ? 0 : -1, tailto = -1; + igraph_vector_long_t no_edges; + igraph_bool_t allne = no_of_graphs == 0 ? 0 : 1, allsame = 0; + long int idx = 0; + + /* Check directedness */ + if (no_of_graphs != 0) { + directed = igraph_is_directed(VECTOR(*graphs)[0]); + } + for (i = 1; i < no_of_graphs; i++) { + if (directed != igraph_is_directed(VECTOR(*graphs)[i])) { + IGRAPH_ERROR("Cannot intersect directed and undirected graphs", + IGRAPH_EINVAL); + } + } + + if (edgemaps) { + IGRAPH_CHECK(igraph_vector_ptr_resize(edgemaps, no_of_graphs)); + igraph_vector_ptr_null(edgemaps); + IGRAPH_FINALLY(igraph_i_union_many_free3, edgemaps); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_long_init(&no_edges, no_of_graphs)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &no_edges); + + /* Calculate number of nodes, query number of edges */ + for (i = 0; i < no_of_graphs; i++) { + long int n = igraph_vcount(VECTOR(*graphs)[i]); + if (n > no_of_nodes) { + no_of_nodes = n; + } + VECTOR(no_edges)[i] = igraph_ecount(VECTOR(*graphs)[i]); + allne = allne && VECTOR(no_edges)[i] > 0; + } + + if (edgemaps) { + for (i = 0; i < no_of_graphs; i++) { + VECTOR(*edgemaps)[i] = igraph_Calloc(1, igraph_vector_t); + if (!VECTOR(*edgemaps)[i]) { + IGRAPH_ERROR("Cannot intersect graphs", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(VECTOR(*edgemaps)[i], + VECTOR(no_edges)[i])); + igraph_vector_fill(VECTOR(*edgemaps)[i], -1); + } + } + + /* Allocate memory for the edge lists and their index vectors */ + if (no_of_graphs != 0) { + IGRAPH_CHECK(igraph_vector_ptr_init(&edge_vects, no_of_graphs)); + IGRAPH_FINALLY(igraph_i_union_many_free, &edge_vects); + IGRAPH_CHECK(igraph_vector_ptr_init(&order_vects, no_of_graphs)); + IGRAPH_FINALLY(igraph_i_union_many_free2, &order_vects); + } + for (i = 0; i < no_of_graphs; i++) { + VECTOR(edge_vects)[i] = igraph_Calloc(1, igraph_vector_t); + VECTOR(order_vects)[i] = igraph_Calloc(1, igraph_vector_long_t); + if (! VECTOR(edge_vects)[i] || ! VECTOR(order_vects)[i]) { + IGRAPH_ERROR("Cannot intersect graphs", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(VECTOR(edge_vects)[i], + 2 * VECTOR(no_edges)[i])); + IGRAPH_CHECK(igraph_vector_long_init(VECTOR(order_vects)[i], + VECTOR(no_edges)[i])); + } + + /* Query and sort the edge lists */ + for (i = 0; i < no_of_graphs; i++) { + long int k, j, n = VECTOR(no_edges)[i]; + igraph_vector_t *edges = VECTOR(edge_vects)[i]; + igraph_vector_long_t *order = VECTOR(order_vects)[i]; + IGRAPH_CHECK(igraph_get_edgelist(VECTOR(*graphs)[i], edges, /*bycol=*/0)); + if (!directed) { + for (k = 0, j = 0; k < n; k++, j += 2) { + if (VECTOR(*edges)[j] > VECTOR(*edges)[j + 1]) { + long int tmp = VECTOR(*edges)[j]; + VECTOR(*edges)[j] = VECTOR(*edges)[j + 1]; + VECTOR(*edges)[j + 1] = tmp; + } + } + } + for (k = 0; k < n; k++) { + VECTOR(*order)[k] = k; + } + igraph_qsort_r(VECTOR(*order), n, sizeof(VECTOR(*order)[0]), edges, + igraph_i_order_edgelist_cmp); + } + + /* Do the merge. We work from the end of the edge lists, + because then we don't have to keep track of where we are right + now in the edge and order lists. We find the "largest" edge, + and if it is present in all graphs, then we copy it to the + result. We remove all instances of this edge. */ + + while (allne) { + + /* Look for the smallest tail element */ + for (j = 0, tailfrom = LONG_MAX, tailto = LONG_MAX; j < no_of_graphs; j++) { + long int edge = igraph_vector_long_tail(VECTOR(order_vects)[j]); + igraph_vector_t *ev = VECTOR(edge_vects)[j]; + long int from = VECTOR(*ev)[2 * edge]; + long int to = VECTOR(*ev)[2 * edge + 1]; + if (from < tailfrom || (from == tailfrom && to < tailto)) { + tailfrom = from; tailto = to; + } + } + + /* OK, now remove all elements from the tail(s) that are bigger + than the smallest tail element. */ + for (j = 0, allsame = 1; j < no_of_graphs; j++) { + long int from = -1, to = -1; + while (1) { + long int edge = igraph_vector_long_tail(VECTOR(order_vects)[j]); + igraph_vector_t *ev = VECTOR(edge_vects)[j]; + from = VECTOR(*ev)[2 * edge]; + to = VECTOR(*ev)[2 * edge + 1]; + if (from > tailfrom || (from == tailfrom && to > tailto)) { + igraph_vector_long_pop_back(VECTOR(order_vects)[j]); + if (igraph_vector_long_empty(VECTOR(order_vects)[j])) { + allne = 0; + break; + } + } else { + break; + } + } + if (from != tailfrom || to != tailto) { + allsame = 0; + } + } + + /* Add the edge, if the smallest tail element was present + in all graphs. */ + if (allsame) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, tailfrom)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, tailto)); + } + + /* Drop edges matching the smalles tail elements + from the order vectors, build edge maps */ + if (allne) { + for (j = 0; j < no_of_graphs; j++) { + long int edge = igraph_vector_long_tail(VECTOR(order_vects)[j]); + igraph_vector_t *ev = VECTOR(edge_vects)[j]; + long int from = VECTOR(*ev)[2 * edge]; + long int to = VECTOR(*ev)[2 * edge + 1]; + if (from == tailfrom && to == tailto) { + igraph_vector_long_pop_back(VECTOR(order_vects)[j]); + if (igraph_vector_long_empty(VECTOR(order_vects)[j])) { + allne = 0; + } + if (edgemaps && allsame) { + igraph_vector_t *map = VECTOR(*edgemaps)[j]; + VECTOR(*map)[edge] = idx; + } + } + } + if (allsame) { + idx++; + } + } + + } /* while allne */ + + if (no_of_graphs > 0) { + igraph_i_union_many_free2(&order_vects); + igraph_i_union_many_free(&edge_vects); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_vector_long_destroy(&no_edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_create(res, &edges, (igraph_integer_t) no_of_nodes, + directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + if (edgemaps) { + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_union + * \brief Calculates the union of two graphs. + * + * + * The number of vertices in the result is that of the larger graph + * from the two arguments. The result graph contains edges which are + * present in at least one of the operand graphs. + * + * \param res Pointer to an uninitialized graph object, the result + * will be stored here. + * \param left The first graph. + * \param right The second graph. + * \param edge_map1 Pointer to an initialized vector or a null pointer. + * If not a null pointer, it will contain a mapping from the edges + * of the first argument graph (\p left) to the edges of the + * result graph. + * \param edge_map2 The same as \p edge_map1, but for the second + * graph, \p right. + * \return Error code. + * \sa \ref igraph_union_many() for the union of many graphs, + * \ref igraph_intersection() and \ref igraph_difference() for other + * operators. + * + * Time complexity: O(|V|+|E|), |V| is the number of + * vertices, |E| the number of edges in the result graph. + * + * \example examples/simple/igraph_union.c + */ + +int igraph_union(igraph_t *res, + const igraph_t *left, const igraph_t *right, + igraph_vector_t *edge_map1, igraph_vector_t *edge_map2) { + return igraph_i_merge(res, IGRAPH_MODE_UNION, left, right, + edge_map1, edge_map2); +} + +/** + * \function igraph_union_many + * \brief Creates the union of many graphs. + * + * + * The result graph will contain as many vertices as the largest graph + * among the arguments does, and an edge will be included in it if it + * is part of at least one operand graph. + * + * + * The directedness of the operand graphs must be the same. + * + * \param res Pointer to an uninitialized graph object, this will + * contain the result. + * \param graphs Pointer vector, contains pointers to the operands of + * the union operator, graph objects of course. + * \param edgemaps If not a null pointer, then it must be an initialized + * pointer vector and the mappings of edges from the graphs to the + * result graph will be stored here, in the same order as + * \p graphs. Each mapping is stored in a separate + * \type igraph_vector_t object. + * \return Error code. + * \sa \ref igraph_union() for the union of two graphs, \ref + * igraph_intersection_many(), \ref igraph_intersection() and \ref + * igraph_difference for other operators. + * + * + * Time complexity: O(|V|+|E|), |V| is the number of vertices + * in largest graph and |E| is the number of edges in the result graph. + * + * \example examples/simple/igraph_union.c + */ + +int igraph_union_many(igraph_t *res, const igraph_vector_ptr_t *graphs, + igraph_vector_ptr_t *edgemaps) { + + long int no_of_graphs = igraph_vector_ptr_size(graphs); + long int no_of_nodes = 0; + igraph_bool_t directed = 1; + igraph_vector_t edges; + igraph_vector_ptr_t edge_vects, order_vects; + igraph_vector_long_t no_edges; + long int i, j, tailfrom = no_of_graphs > 0 ? 0 : -1, tailto = -1; + long int idx = 0; + + /* Check directedness */ + if (no_of_graphs != 0) { + directed = igraph_is_directed(VECTOR(*graphs)[0]); + no_of_nodes = igraph_vcount(VECTOR(*graphs)[0]); + } + for (i = 1; i < no_of_graphs; i++) { + if (directed != igraph_is_directed(VECTOR(*graphs)[i])) { + IGRAPH_ERROR("Cannot union directed and undirected graphs", + IGRAPH_EINVAL); + } + } + + if (edgemaps) { + IGRAPH_CHECK(igraph_vector_ptr_resize(edgemaps, no_of_graphs)); + igraph_vector_ptr_null(edgemaps); + IGRAPH_FINALLY(igraph_i_union_many_free3, edgemaps); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_long_init(&no_edges, no_of_graphs)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &no_edges); + + /* Calculate number of nodes, query number of edges */ + for (i = 0; i < no_of_graphs; i++) { + long int n = igraph_vcount(VECTOR(*graphs)[i]); + if (n > no_of_nodes) { + no_of_nodes = n; + } + VECTOR(no_edges)[i] = igraph_ecount(VECTOR(*graphs)[i]); + } + + if (edgemaps) { + for (i = 0; i < no_of_graphs; i++) { + VECTOR(*edgemaps)[i] = igraph_Calloc(1, igraph_vector_t); + if (!VECTOR(*edgemaps)[i]) { + IGRAPH_ERROR("Cannot union graphs", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(VECTOR(*edgemaps)[i], + VECTOR(no_edges)[i])); + } + } + + /* Allocate memory for the edge lists and their index vectors */ + if (no_of_graphs != 0) { + IGRAPH_CHECK(igraph_vector_ptr_init(&edge_vects, no_of_graphs)); + IGRAPH_FINALLY(igraph_i_union_many_free, &edge_vects); + IGRAPH_CHECK(igraph_vector_ptr_init(&order_vects, no_of_graphs)); + IGRAPH_FINALLY(igraph_i_union_many_free2, &order_vects); + } + for (i = 0; i < no_of_graphs; i++) { + VECTOR(edge_vects)[i] = igraph_Calloc(1, igraph_vector_t); + VECTOR(order_vects)[i] = igraph_Calloc(1, igraph_vector_long_t); + if (! VECTOR(edge_vects)[i] || ! VECTOR(order_vects)[i]) { + IGRAPH_ERROR("Cannot union graphs", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(VECTOR(edge_vects)[i], + 2 * VECTOR(no_edges)[i])); + IGRAPH_CHECK(igraph_vector_long_init(VECTOR(order_vects)[i], + VECTOR(no_edges)[i])); + } + + /* Query and sort the edge lists */ + for (i = 0; i < no_of_graphs; i++) { + long int k, j, n = VECTOR(no_edges)[i]; + igraph_vector_t *edges = VECTOR(edge_vects)[i]; + igraph_vector_long_t *order = VECTOR(order_vects)[i]; + IGRAPH_CHECK(igraph_get_edgelist(VECTOR(*graphs)[i], edges, /*bycol=*/0)); + if (!directed) { + for (k = 0, j = 0; k < n; k++, j += 2) { + if (VECTOR(*edges)[j] > VECTOR(*edges)[j + 1]) { + long int tmp = VECTOR(*edges)[j]; + VECTOR(*edges)[j] = VECTOR(*edges)[j + 1]; + VECTOR(*edges)[j + 1] = tmp; + } + } + } + for (k = 0; k < n; k++) { + VECTOR(*order)[k] = k; + } + igraph_qsort_r(VECTOR(*order), n, sizeof(VECTOR(*order)[0]), edges, + igraph_i_order_edgelist_cmp); + } + + while (tailfrom >= 0) { + + /* Get the largest tail element */ + tailfrom = tailto = -1; + for (j = 0; j < no_of_graphs; j++) { + if (!igraph_vector_long_empty(VECTOR(order_vects)[j])) { + long int edge = igraph_vector_long_tail(VECTOR(order_vects)[j]); + igraph_vector_t *ev = VECTOR(edge_vects)[j]; + long int from = VECTOR(*ev)[2 * edge]; + long int to = VECTOR(*ev)[2 * edge + 1]; + if (from > tailfrom || (from == tailfrom && to > tailto)) { + tailfrom = from; tailto = to; + } + } + } + if (tailfrom < 0) { + continue; + } + + /* add the edge */ + IGRAPH_CHECK(igraph_vector_push_back(&edges, tailfrom)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, tailto)); + + /* update edge lists, we just modify the 'order' vectors */ + for (j = 0; j < no_of_graphs; j++) { + if (!igraph_vector_long_empty(VECTOR(order_vects)[j])) { + long int edge = igraph_vector_long_tail(VECTOR(order_vects)[j]); + igraph_vector_t *ev = VECTOR(edge_vects)[j]; + long int from = VECTOR(*ev)[2 * edge]; + long int to = VECTOR(*ev)[2 * edge + 1]; + if (from == tailfrom && to == tailto) { + igraph_vector_long_pop_back(VECTOR(order_vects)[j]); + if (edgemaps) { + igraph_vector_t *map = VECTOR(*edgemaps)[j]; + VECTOR(*map)[edge] = idx; + } + } + } + } + idx++; + + } + + if (no_of_graphs > 0) { + igraph_i_union_many_free2(&order_vects); + igraph_i_union_many_free(&edge_vects); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_vector_long_destroy(&no_edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_create(res, &edges, (igraph_integer_t) no_of_nodes, + directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + if (edgemaps) { + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_difference + * \brief Calculate the difference of two graphs + * + * + * The number of vertices in the result is the number of vertices in + * the original graph, ie. the left, first operand. In the results + * graph only edges will be included from \c orig which are not + * present in \c sub. + * + * \param res Pointer to an uninitialized graph object, the result + * will be stored here. + * \param orig The left operand of the operator, a graph object. + * \param sub The right operand of the operator, a graph object. + * \return Error code. + * \sa \ref igraph_intersection() and \ref igraph_union() for other + * operators. + * + * Time complexity: O(|V|+|E|), |V| is the number vertices in + * the smaller graph, |E| is the + * number of edges in the result graph. + * + * \example examples/simple/igraph_difference.c + */ + +int igraph_difference(igraph_t *res, + const igraph_t *orig, const igraph_t *sub) { + + /* Quite nasty, but we will use that an edge adjacency list + contains the vertices according to the order of the + vertex ids at the "other" end of the edge. */ + + long int no_of_nodes_orig = igraph_vcount(orig); + long int no_of_nodes_sub = igraph_vcount(sub); + long int no_of_nodes = no_of_nodes_orig; + long int smaller_nodes; + igraph_bool_t directed = igraph_is_directed(orig); + igraph_vector_t edges; + igraph_vector_t edge_ids; + igraph_vector_int_t *nei1, *nei2; + igraph_inclist_t inc_orig, inc_sub; + long int i; + igraph_integer_t v1, v2; + + if (directed != igraph_is_directed(sub)) { + IGRAPH_ERROR("Cannot subtract directed and undirected graphs", + IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edge_ids, 0); + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_inclist_init(orig, &inc_orig, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inc_orig); + IGRAPH_CHECK(igraph_inclist_init(sub, &inc_sub, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inc_sub); + + smaller_nodes = no_of_nodes_orig > no_of_nodes_sub ? + no_of_nodes_sub : no_of_nodes_orig; + + for (i = 0; i < smaller_nodes; i++) { + long int n1, n2, e1, e2; + IGRAPH_ALLOW_INTERRUPTION(); + nei1 = igraph_inclist_get(&inc_orig, i); + nei2 = igraph_inclist_get(&inc_sub, i); + n1 = igraph_vector_int_size(nei1) - 1; + n2 = igraph_vector_int_size(nei2) - 1; + while (n1 >= 0 && n2 >= 0) { + e1 = (long int) VECTOR(*nei1)[n1]; + e2 = (long int) VECTOR(*nei2)[n2]; + v1 = IGRAPH_OTHER(orig, e1, i); + v2 = IGRAPH_OTHER(sub, e2, i); + + if (!directed && v1 < i) { + n1--; + } else if (!directed && v2 < i) { + n2--; + } else if (v1 > v2) { + IGRAPH_CHECK(igraph_vector_push_back(&edge_ids, e1)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, v1)); + n1--; + } else if (v2 > v1) { + n2--; + } else { + n1--; + n2--; + } + } + + /* Copy remaining edges */ + while (n1 >= 0) { + e1 = (long int) VECTOR(*nei1)[n1]; + v1 = IGRAPH_OTHER(orig, e1, i); + if (directed || v1 >= i) { + IGRAPH_CHECK(igraph_vector_push_back(&edge_ids, e1)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, v1)); + } + n1--; + } + } + + /* copy remaining edges, use the previous value of 'i' */ + for (; i < no_of_nodes_orig; i++) { + long int n1, e1; + nei1 = igraph_inclist_get(&inc_orig, i); + n1 = igraph_vector_int_size(nei1) - 1; + while (n1 >= 0) { + e1 = (long int) VECTOR(*nei1)[n1]; + v1 = IGRAPH_OTHER(orig, e1, i); + if (directed || v1 >= i) { + IGRAPH_CHECK(igraph_vector_push_back(&edge_ids, e1)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, v1)); + } + n1--; + } + } + + igraph_inclist_destroy(&inc_sub); + igraph_inclist_destroy(&inc_orig); + IGRAPH_FINALLY_CLEAN(2); + IGRAPH_CHECK(igraph_create(res, &edges, (igraph_integer_t) no_of_nodes, + directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + /* Attributes */ + if (orig->attr) { + IGRAPH_I_ATTRIBUTE_DESTROY(res); + IGRAPH_I_ATTRIBUTE_COPY(res, orig, /*graph=*/1, /*vertex=*/1, /*edge=*/0); + IGRAPH_CHECK(igraph_i_attribute_permute_edges(orig, res, &edge_ids)); + } + + igraph_vector_destroy(&edge_ids); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_complementer + * \brief Create the complementer of a graph + * + * The complementer graph means that all edges which are + * not part of the original graph will be included in the result. + * + * \param res Pointer to an uninitialized graph object. + * \param graph The original graph. + * \param loops Whether to add loop edges to the complementer graph. + * \return Error code. + * \sa \ref igraph_union(), \ref igraph_intersection() and \ref + * igraph_difference(). + * + * Time complexity: O(|V|+|E1|+|E2|), |V| is the number of + * vertices in the graph, |E1| is the number of edges in the original + * and |E2| in the complementer graph. + * + * \example examples/simple/igraph_complementer.c + */ + +int igraph_complementer(igraph_t *res, const igraph_t *graph, + igraph_bool_t loops) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t edges; + igraph_vector_t neis; + long int i, j; + long int zero = 0, *limit; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + if (igraph_is_directed(graph)) { + limit = &zero; + } else { + limit = &i; + } + + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) i, + IGRAPH_OUT)); + if (loops) { + for (j = no_of_nodes - 1; j >= *limit; j--) { + if (igraph_vector_empty(&neis) || j > igraph_vector_tail(&neis)) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, j)); + } else { + igraph_vector_pop_back(&neis); + } + } + } else { + for (j = no_of_nodes - 1; j >= *limit; j--) { + if (igraph_vector_empty(&neis) || j > igraph_vector_tail(&neis)) { + if (i != j) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, j)); + } + } else { + igraph_vector_pop_back(&neis); + } + } + } + } + + IGRAPH_CHECK(igraph_create(res, &edges, (igraph_integer_t) no_of_nodes, + igraph_is_directed(graph))); + igraph_vector_destroy(&edges); + igraph_vector_destroy(&neis); + IGRAPH_I_ATTRIBUTE_DESTROY(res); + IGRAPH_I_ATTRIBUTE_COPY(res, graph, /*graph=*/1, /*vertex=*/1, /*edge=*/0); + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/** + * \function igraph_compose + * \brief Calculates the composition of two graphs + * + * The composition of graphs contains the same number of vertices as + * the bigger graph of the two operands. It contains an (i,j) edge if + * and only if there is a k vertex, such that the first graphs + * contains an (i,k) edge and the second graph a (k,j) edge. + * + * This is of course exactly the composition of two + * binary relations. + * + * Two two graphs must have the same directedness, + * otherwise the function returns with an error message. + * Note that for undirected graphs the two relations are by definition + * symmetric. + * + * \param res Pointer to an uninitialized graph object, the result + * will be stored here. + * \param g1 The firs operand, a graph object. + * \param g2 The second operand, another graph object. + * \param edge_map1 If not a null pointer, then it must be a pointer + * to an initialized vector, and a mapping from the edges of + * the result graph to the edges of the first graph is stored + * here. + * \param edge_map1 If not a null pointer, then it must be a pointer + * to an initialized vector, and a mapping from the edges of + * the result graph to the edges of the second graph is stored + * here. + * \return Error code. + * + * Time complexity: O(|V|*d1*d2), |V| is the number of vertices in the + * first graph, d1 and d2 the average degree in the first and second + * graphs. + * + * \example examples/simple/igraph_compose.c + */ + +int igraph_compose(igraph_t *res, const igraph_t *g1, const igraph_t *g2, + igraph_vector_t *edge_map1, igraph_vector_t *edge_map2) { + + long int no_of_nodes_left = igraph_vcount(g1); + long int no_of_nodes_right = igraph_vcount(g2); + long int no_of_nodes; + igraph_bool_t directed = igraph_is_directed(g1); + igraph_vector_t edges; + igraph_vector_t neis1, neis2; + long int i; + + if (directed != igraph_is_directed(g2)) { + IGRAPH_ERROR("Cannot compose directed and undirected graph", + IGRAPH_EINVAL); + } + + no_of_nodes = no_of_nodes_left > no_of_nodes_right ? + no_of_nodes_left : no_of_nodes_right; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&neis1, 0); + IGRAPH_VECTOR_INIT_FINALLY(&neis2, 0); + + if (edge_map1) { + igraph_vector_clear(edge_map1); + } + if (edge_map2) { + igraph_vector_clear(edge_map2); + } + + for (i = 0; i < no_of_nodes_left; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + IGRAPH_CHECK(igraph_incident(g1, &neis1, (igraph_integer_t) i, + IGRAPH_OUT)); + while (!igraph_vector_empty(&neis1)) { + long int con = (long int) igraph_vector_pop_back(&neis1); + long int v1 = IGRAPH_OTHER(g1, con, i); + if (v1 < no_of_nodes_right) { + IGRAPH_CHECK(igraph_incident(g2, &neis2, (igraph_integer_t) v1, + IGRAPH_OUT)); + } else { + continue; + } + while (!igraph_vector_empty(&neis2)) { + long int con2 = igraph_vector_pop_back(&neis2); + long int v2 = IGRAPH_OTHER(g2, con2, v1); + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, v2)); + if (edge_map1) { + IGRAPH_CHECK(igraph_vector_push_back(edge_map1, con)); + } + if (edge_map2) { + IGRAPH_CHECK(igraph_vector_push_back(edge_map2, con2)); + } + } + } + } + + igraph_vector_destroy(&neis1); + igraph_vector_destroy(&neis2); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(res, &edges, (igraph_integer_t) no_of_nodes, + directed)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} diff --git a/src/optimal_modularity.c b/src/optimal_modularity.c new file mode 100644 index 0000000..2e30e7f --- /dev/null +++ b/src/optimal_modularity.c @@ -0,0 +1,259 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_interface.h" +#include "igraph_community.h" +#include "igraph_error.h" +#include "igraph_glpk_support.h" +#include "igraph_interrupt_internal.h" +#include "igraph_centrality.h" +#include "config.h" + +#ifdef HAVE_GLPK + #include +#endif + +/** + * \function igraph_community_optimal_modularity + * Calculate the community structure with the highest modularity value + * + * This function calculates the optimal community structure for a + * graph, in terms of maximal modularity score. + * + * + * The calculation is done by transforming the modularity maximization + * into an integer programming problem, and then calling the GLPK + * library to solve that. Please see Ulrik Brandes et al.: On + * Modularity Clustering, IEEE Transactions on Knowledge and Data + * Engineering 20(2):172-188, 2008. + * + * + * Note that modularity optimization is an NP-complete problem, and + * all known algorithms for it have exponential time complexity. This + * means that you probably don't want to run this function on larger + * graphs. Graphs with up to fifty vertices should be fine, graphs + * with a couple of hundred vertices might be possible. + * + * \param graph The input graph. It is always treated as undirected. + * \param modularity Pointer to a real number, or a null pointer. + * If it is not a null pointer, then a optimal modularity value + * is returned here. + * \param membership Pointer to a vector, or a null pointer. If not a + * null pointer, then the membership vector of the optimal + * community structure is stored here. + * \param weights Vector giving the weights of the edges. If it is + * \c NULL then each edge is supposed to have the same weight. + * \return Error code. + * + * \sa \ref igraph_modularity(), \ref igraph_community_fastgreedy() + * for an algorithm that finds a local optimum in a greedy way. + * + * Time complexity: exponential in the number of vertices. + * + * \example examples/simple/igraph_community_optimal_modularity.c + */ + +int igraph_community_optimal_modularity(const igraph_t *graph, + igraph_real_t *modularity, + igraph_vector_t *membership, + const igraph_vector_t *weights) { + +#ifndef HAVE_GLPK + IGRAPH_ERROR("GLPK is not available", + IGRAPH_UNIMPLEMENTED); +#else + + igraph_integer_t no_of_nodes = (igraph_integer_t) igraph_vcount(graph); + igraph_integer_t no_of_edges = (igraph_integer_t) igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + int no_of_variables = no_of_nodes * (no_of_nodes + 1) / 2; + int i, j, k, l, st; + int idx[] = { 0, 0, 0, 0 }; + double coef[] = { 0.0, 1.0, 1.0, -2.0 }; + igraph_real_t total_weight; + igraph_vector_t indegree; + igraph_vector_t outdegree; + + glp_prob *ip; + glp_iocp parm; + + if (weights != 0) { + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid length of weight vector", IGRAPH_EINVAL); + } + if (igraph_vector_min(weights) < 0) { + IGRAPH_ERROR("Negative weights are not allowed in weight vector", IGRAPH_EINVAL); + } + } + + if (weights) { + total_weight = igraph_vector_sum(weights); + } else { + total_weight = no_of_edges; + } + if (!directed) { + total_weight *= 2; + } + + /* Special case */ + if (no_of_edges == 0 || total_weight == 0) { + if (modularity) { + *modularity = IGRAPH_NAN; + } + if (membership) { + IGRAPH_CHECK(igraph_vector_resize(membership, no_of_nodes)); + igraph_vector_null(membership); + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&indegree, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&outdegree, no_of_nodes); + IGRAPH_CHECK(igraph_strength(graph, &indegree, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS, weights)); + IGRAPH_CHECK(igraph_strength(graph, &outdegree, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS, weights)); + + glp_term_out(GLP_OFF); + ip = glp_create_prob(); + IGRAPH_FINALLY(glp_delete_prob, ip); + + glp_set_obj_dir(ip, GLP_MAX); + st = glp_add_cols(ip, no_of_variables); + + /* variables are binary */ + for (i = 0; i < no_of_variables; i++) { + glp_set_col_kind(ip, (st + i), GLP_BV); + } + +#define IDX(a,b) ((b)*((b)+1)/2+(a)) + + /* reflexivity */ + for (i = 0; i < no_of_nodes; i++) { + glp_set_col_bnds(ip, (st + IDX(i, i)), GLP_FX, 1.0, 1.0); + } + + /* transitivity */ + for (i = 0; i < no_of_nodes; i++) { + for (j = i + 1; j < no_of_nodes; j++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + for (k = j + 1; k < no_of_nodes; k++) { + int newrow = glp_add_rows(ip, 3); + + glp_set_row_bnds(ip, newrow, GLP_UP, 0.0, 1.0); + idx[1] = (st + IDX(i, j)); idx[2] = (st + IDX(j, k)); + idx[3] = (st + IDX(i, k)); + glp_set_mat_row(ip, newrow, 3, idx, coef); + + glp_set_row_bnds(ip, newrow + 1, GLP_UP, 0.0, 1.0); + idx[1] = st + IDX(i, j); idx[2] = st + IDX(i, k); idx[3] = st + IDX(j, k); + glp_set_mat_row(ip, newrow + 1, 3, idx, coef); + + glp_set_row_bnds(ip, newrow + 2, GLP_UP, 0.0, 1.0); + idx[1] = st + IDX(i, k); idx[2] = st + IDX(j, k); idx[3] = st + IDX(i, j); + glp_set_mat_row(ip, newrow + 2, 3, idx, coef); + + } + } + } + + /* objective function */ + { + igraph_real_t c; + + /* first part: -strength(i)*strength(j)/total_weight for every node pair */ + for (i = 0; i < no_of_nodes; i++) { + for (j = i + 1; j < no_of_nodes; j++) { + c = -VECTOR(indegree)[i] * VECTOR(outdegree)[j] / total_weight \ + -VECTOR(outdegree)[i] * VECTOR(indegree)[j] / total_weight; + glp_set_obj_coef(ip, st + IDX(i, j), c); + } + /* special case for (i,i) */ + c = -VECTOR(indegree)[i] * VECTOR(outdegree)[i] / total_weight; + glp_set_obj_coef(ip, st + IDX(i, i), c); + } + + /* second part: add the weighted adjacency matrix to the coefficient matrix */ + for (k = 0; k < no_of_edges; k++) { + i = IGRAPH_FROM(graph, k); + j = IGRAPH_TO(graph, k); + if (i > j) { + l = i; i = j; j = l; + } + c = weights ? VECTOR(*weights)[k] : 1.0; + if (!directed || i == j) { + c *= 2.0; + } + glp_set_obj_coef(ip, st + IDX(i, j), c + glp_get_obj_coef(ip, st + IDX(i, j))); + } + } + + /* solve it */ + glp_init_iocp(&parm); + parm.br_tech = GLP_BR_DTH; + parm.bt_tech = GLP_BT_BLB; + parm.presolve = GLP_ON; + parm.binarize = GLP_ON; + parm.cb_func = igraph_i_glpk_interruption_hook; + IGRAPH_GLPK_CHECK(glp_intopt(ip, &parm), "Modularity optimization failed"); + + /* store the results */ + if (modularity) { + *modularity = glp_mip_obj_val(ip) / total_weight; + } + + if (membership) { + long int comm = 0; /* id of the last community that was found */ + IGRAPH_CHECK(igraph_vector_resize(membership, no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + for (j = 0; j < i; j++) { + int val = (int) glp_mip_col_val(ip, st + IDX(j, i)); + if (val == 1) { + VECTOR(*membership)[i] = VECTOR(*membership)[j]; + break; + } + } + if (j == i) { /* new community */ + VECTOR(*membership)[i] = comm++; + } + } + } + +#undef IDX + + igraph_vector_destroy(&indegree); + igraph_vector_destroy(&outdegree); + glp_delete_prob(ip); + IGRAPH_FINALLY_CLEAN(3); + + return 0; + +#endif + +} + diff --git a/src/other.c b/src/other.c new file mode 100644 index 0000000..efebb75 --- /dev/null +++ b/src/other.c @@ -0,0 +1,429 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_nongraph.h" +#include "igraph_types.h" +#include "igraph_interrupt_internal.h" +#include "config.h" +#include "plfit/error.h" +#include "plfit/plfit.h" +#include + +/** + * \ingroup nongraph + * \function igraph_running_mean + * \brief Calculates the running mean of a vector. + * + * + * The running mean is defined by the mean of the + * previous \p binwidth values. + * \param data The vector containing the data. + * \param res The vector containing the result. This should be + * initialized before calling this function and will be + * resized. + * \param binwidth Integer giving the width of the bin for the running + * mean calculation. + * \return Error code. + * + * Time complexity: O(n), + * n is the length of + * the data vector. + */ + +int igraph_running_mean(const igraph_vector_t *data, igraph_vector_t *res, + igraph_integer_t binwidth) { + + double sum = 0; + long int i; + + /* Check */ + if (igraph_vector_size(data) < binwidth) { + IGRAPH_ERROR("Vector too short for this binwidth", IGRAPH_EINVAL); + } + + /* Memory for result */ + + IGRAPH_CHECK(igraph_vector_resize(res, (long int)(igraph_vector_size(data) - binwidth + 1))); + + /* Initial bin */ + for (i = 0; i < binwidth; i++) { + sum += VECTOR(*data)[i]; + } + + VECTOR(*res)[0] = sum / binwidth; + + for (i = 1; i < igraph_vector_size(data) - binwidth + 1; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + sum -= VECTOR(*data)[i - 1]; + sum += VECTOR(*data)[ (long int)(i + binwidth - 1)]; + VECTOR(*res)[i] = sum / binwidth; + } + + return 0; +} + + +/** + * \ingroup nongraph + * \function igraph_convex_hull + * \brief Determines the convex hull of a given set of points in the 2D plane + * + * + * The convex hull is determined by the Graham scan algorithm. + * See the following reference for details: + * + * + * Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford + * Stein. Introduction to Algorithms, Second Edition. MIT Press and + * McGraw-Hill, 2001. ISBN 0262032937. Pages 949-955 of section 33.3: + * Finding the convex hull. + * + * \param data vector containing the coordinates. The length of the + * vector must be even, since it contains X-Y coordinate pairs. + * \param resverts the vector containing the result, e.g. the vector of + * vertex indices used as the corners of the convex hull. Supply + * \c NULL here if you are only interested in the coordinates of + * the convex hull corners. + * \param rescoords the matrix containing the coordinates of the selected + * corner vertices. Supply \c NULL here if you are only interested in + * the vertex indices. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory + * + * Time complexity: O(n log(n)) where n is the number of vertices + * + * \example examples/simple/igraph_convex_hull.c + */ +int igraph_convex_hull(const igraph_matrix_t *data, igraph_vector_t *resverts, + igraph_matrix_t *rescoords) { + igraph_integer_t no_of_nodes; + long int i, pivot_idx = 0, last_idx, before_last_idx, next_idx, j; + igraph_vector_t angles, stack, order; + igraph_real_t px, py, cp; + + no_of_nodes = (igraph_integer_t) igraph_matrix_nrow(data); + if (igraph_matrix_ncol(data) != 2) { + IGRAPH_ERROR("matrix must have 2 columns", IGRAPH_EINVAL); + } + if (no_of_nodes == 0) { + if (resverts != 0) { + IGRAPH_CHECK(igraph_vector_resize(resverts, 0)); + } + if (rescoords != 0) { + IGRAPH_CHECK(igraph_matrix_resize(rescoords, 0, 2)); + } + /**************************** this is an exit here *********/ + return 0; + } + + IGRAPH_VECTOR_INIT_FINALLY(&angles, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&stack, 0); + + /* Search for the pivot vertex */ + for (i = 1; i < no_of_nodes; i++) { + if (MATRIX(*data, i, 1) < MATRIX(*data, pivot_idx, 1)) { + pivot_idx = i; + } else if (MATRIX(*data, i, 1) == MATRIX(*data, pivot_idx, 1) && + MATRIX(*data, i, 0) < MATRIX(*data, pivot_idx, 0)) { + pivot_idx = i; + } + } + px = MATRIX(*data, pivot_idx, 0); + py = MATRIX(*data, pivot_idx, 1); + + /* Create angle array */ + for (i = 0; i < no_of_nodes; i++) { + if (i == pivot_idx) { + /* We can't calculate the angle of the pivot point with itself, + * so we use 10 here. This way, after sorting the angle vector, + * the pivot point will always be the first one, since the range + * of atan2 is -3.14..3.14 */ + VECTOR(angles)[i] = 10; + } else { + VECTOR(angles)[i] = atan2(MATRIX(*data, i, 1) - py, MATRIX(*data, i, 0) - px); + } + } + + /* Sort points by angles */ + IGRAPH_VECTOR_INIT_FINALLY(&order, no_of_nodes); + IGRAPH_CHECK(igraph_vector_qsort_ind(&angles, &order, 0)); + + /* Check if two points have the same angle. If so, keep only the point that + * is farthest from the pivot */ + j = 0; + last_idx = (long int) VECTOR(order)[0]; + pivot_idx = (long int) VECTOR(order)[no_of_nodes - 1]; + for (i = 1; i < no_of_nodes; i++) { + next_idx = (long int) VECTOR(order)[i]; + if (VECTOR(angles)[last_idx] == VECTOR(angles)[next_idx]) { + /* Keep the vertex that is farther from the pivot, drop the one that is + * closer */ + px = pow(MATRIX(*data, last_idx, 0) - MATRIX(*data, pivot_idx, 0), 2) + + pow(MATRIX(*data, last_idx, 1) - MATRIX(*data, pivot_idx, 1), 2); + py = pow(MATRIX(*data, next_idx, 0) - MATRIX(*data, pivot_idx, 0), 2) + + pow(MATRIX(*data, next_idx, 1) - MATRIX(*data, pivot_idx, 1), 2); + if (px > py) { + VECTOR(order)[i] = -1; + } else { + VECTOR(order)[j] = -1; + last_idx = next_idx; + j = i; + } + } else { + last_idx = next_idx; + j = i; + } + } + + j = 0; + last_idx = -1; + before_last_idx = -1; + while (!igraph_vector_empty(&order)) { + next_idx = (long int)VECTOR(order)[igraph_vector_size(&order) - 1]; + if (next_idx < 0) { + /* This vertex should be skipped; was excluded in an earlier step */ + igraph_vector_pop_back(&order); + continue; + } + /* Determine whether we are at a left or right turn */ + if (j < 2) { + /* Pretend that we are turning into the right direction if we have less + * than two items in the stack */ + cp = -1; + } else { + cp = (MATRIX(*data, last_idx, 0) - MATRIX(*data, before_last_idx, 0)) * + (MATRIX(*data, next_idx, 1) - MATRIX(*data, before_last_idx, 1)) - + (MATRIX(*data, next_idx, 0) - MATRIX(*data, before_last_idx, 0)) * + (MATRIX(*data, last_idx, 1) - MATRIX(*data, before_last_idx, 1)); + } + /* + printf("B L N cp: %ld, %ld, %ld, %f [", before_last_idx, last_idx, next_idx, (float)cp); + for (int k=0; k= 2) ? (long int) VECTOR(stack)[j - 2] : -1; + } + } + + /* Create result vector */ + if (resverts != 0) { + igraph_vector_clear(resverts); + IGRAPH_CHECK(igraph_vector_append(resverts, &stack)); + } + if (rescoords != 0) { + igraph_matrix_select_rows(data, rescoords, &stack); + } + + /* Free everything */ + igraph_vector_destroy(&order); + igraph_vector_destroy(&stack); + igraph_vector_destroy(&angles); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + + +static const char* igraph_i_plfit_error_message = 0; + +static void igraph_i_plfit_error_handler_store(const char *reason, const char *file, + int line, int plfit_errno) { + igraph_i_plfit_error_message = reason; +} + +/** + * \ingroup nongraph + * \function igraph_power_law_fit + * \brief Fits a power-law distribution to a vector of numbers + * + * This function fits a power-law distribution to a vector containing samples + * from a distribution (that is assumed to follow a power-law of course). In + * a power-law distribution, it is generally assumed that P(X=x) is + * proportional to x-alpha, where x is a positive number and alpha + * is greater than 1. In many real-world cases, the power-law behaviour kicks + * in only above a threshold value \em xmin. The goal of this functions is to + * determine \em alpha if \em xmin is given, or to determine \em xmin and the + * corresponding value of \em alpha. + * + * + * The function uses the maximum likelihood principle to determine \em alpha + * for a given \em xmin; in other words, the function will return the \em alpha + * value for which the probability of drawing the given sample is the highest. + * When \em xmin is not given in advance, the algorithm will attempt to find + * the optimal \em xmin value for which the p-value of a Kolmogorov-Smirnov + * test between the fitted distribution and the original sample is the largest. + * The function uses the method of Clauset, Shalizi and Newman to calculate the + * parameters of the fitted distribution. See the following reference for + * details: + * + * + * Aaron Clauset, Cosma R .Shalizi and Mark E.J. Newman: Power-law + * distributions in empirical data. SIAM Review 51(4):661-703, 2009. + * + * \param data vector containing the samples for which a power-law distribution + * is to be fitted. Note that you have to provide the \em samples, + * not the probability density function or the cumulative + * distribution function. For example, if you wish to fit + * a power-law to the degrees of a graph, you can use the output of + * \ref igraph_degree directly as an input argument to + * \ref igraph_power_law_fit + * \param result the result of the fitting algorithm. See \ref igraph_plfit_result_t + * for more details. + * \param xmin the minimum value in the sample vector where the power-law + * behaviour is expected to kick in. Samples smaller than \c xmin + * will be ignored by the algoritm. Pass zero here if you want to + * include all the samples. If \c xmin is negative, the algorithm + * will attempt to determine its best value automatically. + * \param force_continuous assume that the samples in the \c data argument come + * from a continuous distribution even if the sample vector + * contains integer values only (by chance). If this argument is + * false, igraph will assume a continuous distribution if at least + * one sample is non-integer and assume a discrete distribution + * otherwise. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory + * \c IGRAPH_EINVAL: one of the arguments is invalid + * \c IGRAPH_EOVERFLOW: overflow during the fitting process + * \c IGRAPH_EUNDERFLOW: underflow during the fitting process + * \c IGRAPH_FAILURE: the underlying algorithm signaled a failure + * without returning a more specific error code + * + * Time complexity: in the continuous case, O(n log(n)) if \c xmin is given. + * In the discrete case, the time complexity is dominated by the complexity of + * the underlying L-BFGS algorithm that is used to optimize alpha. If \c xmin + * is not given, the time complexity is multiplied by the number of unique + * samples in the input vector (although it should be faster in practice). + * + * \example examples/simple/igraph_power_law_fit.c + */ +int igraph_power_law_fit(const igraph_vector_t* data, igraph_plfit_result_t* result, + igraph_real_t xmin, igraph_bool_t force_continuous) { + plfit_error_handler_t* plfit_stored_error_handler; + plfit_result_t plfit_result; + plfit_continuous_options_t cont_options; + plfit_discrete_options_t disc_options; + igraph_bool_t discrete = force_continuous ? 0 : 1; + igraph_bool_t finite_size_correction; + int retval; + size_t i, n; + + n = (size_t) igraph_vector_size(data); + finite_size_correction = (n < 50); + + if (discrete) { + /* Does the vector contain discrete values only? */ + for (i = 0; i < n; i++) { + if ((long int)(VECTOR(*data)[i]) != VECTOR(*data)[i]) { + discrete = 0; + break; + } + } + } + + plfit_stored_error_handler = plfit_set_error_handler(igraph_i_plfit_error_handler_store); + if (discrete) { + plfit_discrete_options_init(&disc_options); + /* approximation method should be switched to PLFIT_P_VALUE_EXACT in igraph 0.9 */ + disc_options.p_value_method = PLFIT_P_VALUE_APPROXIMATE; + disc_options.finite_size_correction = (plfit_bool_t) finite_size_correction; + + if (xmin >= 0) { + retval = plfit_estimate_alpha_discrete(VECTOR(*data), n, xmin, + &disc_options, &plfit_result); + } else { + retval = plfit_discrete(VECTOR(*data), n, &disc_options, &plfit_result); + } + } else { + plfit_continuous_options_init(&cont_options); + /* approximation method should be switched to PLFIT_P_VALUE_EXACT in igraph 0.9 */ + cont_options.p_value_method = PLFIT_P_VALUE_APPROXIMATE; + /* xmin method should be switched to PLFIT_STRATIFIED_SAMPLING in igraph 0.9 */ + cont_options.xmin_method = PLFIT_GSS_OR_LINEAR; + cont_options.finite_size_correction = (plfit_bool_t) finite_size_correction; + + if (xmin >= 0) { + retval = plfit_estimate_alpha_continuous(VECTOR(*data), n, xmin, + &cont_options, &plfit_result); + } else { + retval = plfit_continuous(VECTOR(*data), n, &cont_options, &plfit_result); + } + } + plfit_set_error_handler(plfit_stored_error_handler); + + switch (retval) { + case PLFIT_FAILURE: + IGRAPH_ERROR(igraph_i_plfit_error_message, IGRAPH_FAILURE); + break; + + case PLFIT_EINVAL: + IGRAPH_ERROR(igraph_i_plfit_error_message, IGRAPH_EINVAL); + break; + + case PLFIT_UNDRFLOW: + IGRAPH_ERROR(igraph_i_plfit_error_message, IGRAPH_EUNDERFLOW); + break; + + case PLFIT_OVERFLOW: + IGRAPH_ERROR(igraph_i_plfit_error_message, IGRAPH_EOVERFLOW); + break; + + case PLFIT_ENOMEM: + IGRAPH_ERROR(igraph_i_plfit_error_message, IGRAPH_ENOMEM); + break; + + default: + break; + } + + if (result) { + result->continuous = !discrete; + result->alpha = plfit_result.alpha; + result->xmin = plfit_result.xmin; + result->L = plfit_result.L; + result->D = plfit_result.D; + result->p = plfit_result.p; + } + + return 0; +} + +/** + * Internal function, floating point division + * Used only in compilers not supporting INFINITY and HUGE_VAL to create + * infinity values + */ +double igraph_i_fdiv(const double a, const double b) { + return a / b; +} diff --git a/src/paths.c b/src/paths.c new file mode 100644 index 0000000..ab066fd --- /dev/null +++ b/src/paths.c @@ -0,0 +1,175 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_interface.h" +#include "igraph_interrupt_internal.h" +#include "igraph_vector_ptr.h" +#include "igraph_iterators.h" +#include "igraph_adjlist.h" +#include "igraph_stack.h" + +/** + * \function igraph_get_all_simple_paths + * List all simple paths from one source + * + * A path is simple, if its vertices are unique, no vertex + * is visited more than once. + * + * + * Note that potentially there are exponentially many + * paths between two vertices of a graph, and you may + * run out of memory when using this function, if your + * graph is lattice-like. + * + * + * This function currently ignored multiple and loop edges. + * \param graph The input graph. + * \param res Initialized integer vector, all paths are + * returned here, separated by -1 markers. The paths + * are included in arbitrary order, as they are found. + * \param from The start vertex. + * \param to The target vertices. + * \param cutoff Maximum length of path that is considered. If + * negative, paths of all lengths are considered. + * \param mode The type of the paths to consider, it is ignored + * for undirected graphs. + * \return Error code. + * + * Time complexity: O(n!) in the worst case, n is the number of + * vertices. + */ + +int igraph_get_all_simple_paths(const igraph_t *graph, + igraph_vector_int_t *res, + igraph_integer_t from, + const igraph_vs_t to, + igraph_integer_t cutoff, + igraph_neimode_t mode) { + + igraph_integer_t no_nodes = igraph_vcount(graph); + igraph_vit_t vit; + igraph_bool_t toall = igraph_vs_is_all(&to); + igraph_vector_char_t markto; + igraph_lazy_adjlist_t adjlist; + igraph_vector_int_t stack, dist; + igraph_vector_char_t added; + igraph_vector_int_t nptr; + int iteration; + + if (from < 0 || from >= no_nodes) { + IGRAPH_ERROR("Invalid starting vertex", IGRAPH_EINVAL); + } + + if (!toall) { + igraph_vector_char_init(&markto, no_nodes); + IGRAPH_FINALLY(igraph_vector_char_destroy, &markto); + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + for (; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + VECTOR(markto)[ IGRAPH_VIT_GET(vit) ] = 1; + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + } + + IGRAPH_CHECK(igraph_vector_char_init(&added, no_nodes)); + IGRAPH_FINALLY(igraph_vector_char_destroy, &added); + IGRAPH_CHECK(igraph_vector_int_init(&stack, 100)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &stack); + IGRAPH_CHECK(igraph_vector_int_init(&dist, 100)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &dist); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, mode, + /*simplify=*/ 1)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + IGRAPH_CHECK(igraph_vector_int_init(&nptr, no_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &nptr); + + igraph_vector_int_clear(res); + + igraph_vector_int_clear(&stack); + igraph_vector_int_clear(&dist); + igraph_vector_int_push_back(&stack, from); + igraph_vector_int_push_back(&dist, 0); + VECTOR(added)[from] = 1; + while (!igraph_vector_int_empty(&stack)) { + int act = igraph_vector_int_tail(&stack); + int curdist = igraph_vector_int_tail(&dist); + igraph_vector_t *neis = igraph_lazy_adjlist_get(&adjlist, act); + int n = igraph_vector_size(neis); + int *ptr = igraph_vector_int_e_ptr(&nptr, act); + igraph_bool_t any; + igraph_bool_t within_dist; + int nei; + + if (iteration == 0) { + IGRAPH_ALLOW_INTERRUPTION(); + } + + within_dist = (curdist < cutoff || cutoff < 0); + if (within_dist) { + /* Search for a neighbor that was not yet visited */ + any = 0; + while (!any && (*ptr) < n) { + nei = (int) VECTOR(*neis)[(*ptr)]; + any = !VECTOR(added)[nei]; + (*ptr) ++; + } + } + if (within_dist && any) { + /* There is such a neighbor, add it */ + IGRAPH_CHECK(igraph_vector_int_push_back(&stack, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(&dist, curdist + 1)); + VECTOR(added)[nei] = 1; + /* Add to results */ + if (toall || VECTOR(markto)[nei]) { + IGRAPH_CHECK(igraph_vector_int_append(res, &stack)); + IGRAPH_CHECK(igraph_vector_int_push_back(res, -1)); + } + } else { + /* There is no such neighbor, finished with the subtree */ + int up = igraph_vector_int_pop_back(&stack); + igraph_vector_int_pop_back(&dist); + VECTOR(added)[up] = 0; + VECTOR(nptr)[up] = 0; + } + + iteration++; + if (iteration >= 10000) { + iteration = 0; + } + } + + igraph_vector_int_destroy(&nptr); + igraph_lazy_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&dist); + igraph_vector_int_destroy(&stack); + igraph_vector_char_destroy(&added); + IGRAPH_FINALLY_CLEAN(5); + + if (!toall) { + igraph_vector_char_destroy(&markto); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} diff --git a/src/plfit/arithmetic_ansi.h b/src/plfit/arithmetic_ansi.h new file mode 100644 index 0000000..c58c98a --- /dev/null +++ b/src/plfit/arithmetic_ansi.h @@ -0,0 +1,133 @@ +/* + * ANSI C implementation of vector operations. + * + * Copyright (c) 2007-2010 Naoaki Okazaki + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* $Id: arithmetic_ansi.h 65 2010-01-29 12:19:16Z naoaki $ */ + +#include +#include + +#if LBFGS_FLOAT == 32 && LBFGS_IEEE_FLOAT +#define fsigndiff(x, y) (((*(uint32_t*)(x)) ^ (*(uint32_t*)(y))) & 0x80000000U) +#else +#define fsigndiff(x, y) (*(x) * (*(y) / fabs(*(y))) < 0.) +#endif/*LBFGS_IEEE_FLOAT*/ + +inline static void* vecalloc(size_t size) +{ + void *memblock = malloc(size); + if (memblock) { + memset(memblock, 0, size); + } + return memblock; +} + +inline static void vecfree(void *memblock) +{ + free(memblock); +} + +inline static void vecset(lbfgsfloatval_t *x, const lbfgsfloatval_t c, const int n) +{ + int i; + + for (i = 0;i < n;++i) { + x[i] = c; + } +} + +inline static void veccpy(lbfgsfloatval_t *y, const lbfgsfloatval_t *x, const int n) +{ + int i; + + for (i = 0;i < n;++i) { + y[i] = x[i]; + } +} + +inline static void vecncpy(lbfgsfloatval_t *y, const lbfgsfloatval_t *x, const int n) +{ + int i; + + for (i = 0;i < n;++i) { + y[i] = -x[i]; + } +} + +inline static void vecadd(lbfgsfloatval_t *y, const lbfgsfloatval_t *x, const lbfgsfloatval_t c, const int n) +{ + int i; + + for (i = 0;i < n;++i) { + y[i] += c * x[i]; + } +} + +inline static void vecdiff(lbfgsfloatval_t *z, const lbfgsfloatval_t *x, const lbfgsfloatval_t *y, const int n) +{ + int i; + + for (i = 0;i < n;++i) { + z[i] = x[i] - y[i]; + } +} + +inline static void vecscale(lbfgsfloatval_t *y, const lbfgsfloatval_t c, const int n) +{ + int i; + + for (i = 0;i < n;++i) { + y[i] *= c; + } +} + +inline static void vecmul(lbfgsfloatval_t *y, const lbfgsfloatval_t *x, const int n) +{ + int i; + + for (i = 0;i < n;++i) { + y[i] *= x[i]; + } +} + +inline static void vecdot(lbfgsfloatval_t* s, const lbfgsfloatval_t *x, const lbfgsfloatval_t *y, const int n) +{ + int i; + *s = 0.; + for (i = 0;i < n;++i) { + *s += x[i] * y[i]; + } +} + +inline static void vec2norm(lbfgsfloatval_t* s, const lbfgsfloatval_t *x, const int n) +{ + vecdot(s, x, x, n); + *s = (lbfgsfloatval_t)sqrt(*s); +} + +inline static void vec2norminv(lbfgsfloatval_t* s, const lbfgsfloatval_t *x, const int n) +{ + vec2norm(s, x, n); + *s = (lbfgsfloatval_t)(1.0 / *s); +} diff --git a/src/plfit/arithmetic_sse_double.h b/src/plfit/arithmetic_sse_double.h new file mode 100644 index 0000000..a94d89d --- /dev/null +++ b/src/plfit/arithmetic_sse_double.h @@ -0,0 +1,294 @@ +/* + * SSE2 implementation of vector oprations (64bit double). + * + * Copyright (c) 2007-2010 Naoaki Okazaki + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* $Id: arithmetic_sse_double.h 65 2010-01-29 12:19:16Z naoaki $ */ + +#include + +#if !defined(__APPLE__) +#include +#endif + +#include + +#if 1400 <= _MSC_VER +#include +#endif/*1400 <= _MSC_VER*/ + +#if HAVE_EMMINTRIN_H +#include +#endif/*HAVE_EMMINTRIN_H*/ + +inline static void* vecalloc(size_t size) +{ +#ifdef _MSC_VER + void *memblock = _aligned_malloc(size, 16); +#elif defined(__APPLE__) + /* Memory on Mac OS X is already aligned to 16 bytes */ + void *memblock = malloc(size); +#else + void *memblock = memalign(16, size); +#endif + if (memblock != NULL) { + memset(memblock, 0, size); + } + return memblock; +} + +inline static void vecfree(void *memblock) +{ +#ifdef _MSC_VER + _aligned_free(memblock); +#else + free(memblock); +#endif +} + +#define fsigndiff(x, y) \ + ((_mm_movemask_pd(_mm_set_pd(*(x), *(y))) + 1) & 0x002) + +#define vecset(x, c, n) \ +{ \ + int i; \ + __m128d XMM0 = _mm_set1_pd(c); \ + for (i = 0;i < (n);i += 8) { \ + _mm_store_pd((x)+i , XMM0); \ + _mm_store_pd((x)+i+2, XMM0); \ + _mm_store_pd((x)+i+4, XMM0); \ + _mm_store_pd((x)+i+6, XMM0); \ + } \ +} + +#define veccpy(y, x, n) \ +{ \ + int i; \ + for (i = 0;i < (n);i += 8) { \ + __m128d XMM0 = _mm_load_pd((x)+i ); \ + __m128d XMM1 = _mm_load_pd((x)+i+2); \ + __m128d XMM2 = _mm_load_pd((x)+i+4); \ + __m128d XMM3 = _mm_load_pd((x)+i+6); \ + _mm_store_pd((y)+i , XMM0); \ + _mm_store_pd((y)+i+2, XMM1); \ + _mm_store_pd((y)+i+4, XMM2); \ + _mm_store_pd((y)+i+6, XMM3); \ + } \ +} + +#define vecncpy(y, x, n) \ +{ \ + int i; \ + for (i = 0;i < (n);i += 8) { \ + __m128d XMM0 = _mm_setzero_pd(); \ + __m128d XMM1 = _mm_setzero_pd(); \ + __m128d XMM2 = _mm_setzero_pd(); \ + __m128d XMM3 = _mm_setzero_pd(); \ + __m128d XMM4 = _mm_load_pd((x)+i ); \ + __m128d XMM5 = _mm_load_pd((x)+i+2); \ + __m128d XMM6 = _mm_load_pd((x)+i+4); \ + __m128d XMM7 = _mm_load_pd((x)+i+6); \ + XMM0 = _mm_sub_pd(XMM0, XMM4); \ + XMM1 = _mm_sub_pd(XMM1, XMM5); \ + XMM2 = _mm_sub_pd(XMM2, XMM6); \ + XMM3 = _mm_sub_pd(XMM3, XMM7); \ + _mm_store_pd((y)+i , XMM0); \ + _mm_store_pd((y)+i+2, XMM1); \ + _mm_store_pd((y)+i+4, XMM2); \ + _mm_store_pd((y)+i+6, XMM3); \ + } \ +} + +#define vecadd(y, x, c, n) \ +{ \ + int i; \ + __m128d XMM7 = _mm_set1_pd(c); \ + for (i = 0;i < (n);i += 4) { \ + __m128d XMM0 = _mm_load_pd((x)+i ); \ + __m128d XMM1 = _mm_load_pd((x)+i+2); \ + __m128d XMM2 = _mm_load_pd((y)+i ); \ + __m128d XMM3 = _mm_load_pd((y)+i+2); \ + XMM0 = _mm_mul_pd(XMM0, XMM7); \ + XMM1 = _mm_mul_pd(XMM1, XMM7); \ + XMM2 = _mm_add_pd(XMM2, XMM0); \ + XMM3 = _mm_add_pd(XMM3, XMM1); \ + _mm_store_pd((y)+i , XMM2); \ + _mm_store_pd((y)+i+2, XMM3); \ + } \ +} + +#define vecdiff(z, x, y, n) \ +{ \ + int i; \ + for (i = 0;i < (n);i += 8) { \ + __m128d XMM0 = _mm_load_pd((x)+i ); \ + __m128d XMM1 = _mm_load_pd((x)+i+2); \ + __m128d XMM2 = _mm_load_pd((x)+i+4); \ + __m128d XMM3 = _mm_load_pd((x)+i+6); \ + __m128d XMM4 = _mm_load_pd((y)+i ); \ + __m128d XMM5 = _mm_load_pd((y)+i+2); \ + __m128d XMM6 = _mm_load_pd((y)+i+4); \ + __m128d XMM7 = _mm_load_pd((y)+i+6); \ + XMM0 = _mm_sub_pd(XMM0, XMM4); \ + XMM1 = _mm_sub_pd(XMM1, XMM5); \ + XMM2 = _mm_sub_pd(XMM2, XMM6); \ + XMM3 = _mm_sub_pd(XMM3, XMM7); \ + _mm_store_pd((z)+i , XMM0); \ + _mm_store_pd((z)+i+2, XMM1); \ + _mm_store_pd((z)+i+4, XMM2); \ + _mm_store_pd((z)+i+6, XMM3); \ + } \ +} + +#define vecscale(y, c, n) \ +{ \ + int i; \ + __m128d XMM7 = _mm_set1_pd(c); \ + for (i = 0;i < (n);i += 4) { \ + __m128d XMM0 = _mm_load_pd((y)+i ); \ + __m128d XMM1 = _mm_load_pd((y)+i+2); \ + XMM0 = _mm_mul_pd(XMM0, XMM7); \ + XMM1 = _mm_mul_pd(XMM1, XMM7); \ + _mm_store_pd((y)+i , XMM0); \ + _mm_store_pd((y)+i+2, XMM1); \ + } \ +} + +#define vecmul(y, x, n) \ +{ \ + int i; \ + for (i = 0;i < (n);i += 8) { \ + __m128d XMM0 = _mm_load_pd((x)+i ); \ + __m128d XMM1 = _mm_load_pd((x)+i+2); \ + __m128d XMM2 = _mm_load_pd((x)+i+4); \ + __m128d XMM3 = _mm_load_pd((x)+i+6); \ + __m128d XMM4 = _mm_load_pd((y)+i ); \ + __m128d XMM5 = _mm_load_pd((y)+i+2); \ + __m128d XMM6 = _mm_load_pd((y)+i+4); \ + __m128d XMM7 = _mm_load_pd((y)+i+6); \ + XMM4 = _mm_mul_pd(XMM4, XMM0); \ + XMM5 = _mm_mul_pd(XMM5, XMM1); \ + XMM6 = _mm_mul_pd(XMM6, XMM2); \ + XMM7 = _mm_mul_pd(XMM7, XMM3); \ + _mm_store_pd((y)+i , XMM4); \ + _mm_store_pd((y)+i+2, XMM5); \ + _mm_store_pd((y)+i+4, XMM6); \ + _mm_store_pd((y)+i+6, XMM7); \ + } \ +} + + + +#if 3 <= __SSE__ +/* + Horizontal add with haddps SSE3 instruction. The work register (rw) + is unused. + */ +#define __horizontal_sum(r, rw) \ + r = _mm_hadd_ps(r, r); \ + r = _mm_hadd_ps(r, r); + +#else +/* + Horizontal add with SSE instruction. The work register (rw) is used. + */ +#define __horizontal_sum(r, rw) \ + rw = r; \ + r = _mm_shuffle_ps(r, rw, _MM_SHUFFLE(1, 0, 3, 2)); \ + r = _mm_add_ps(r, rw); \ + rw = r; \ + r = _mm_shuffle_ps(r, rw, _MM_SHUFFLE(2, 3, 0, 1)); \ + r = _mm_add_ps(r, rw); + +#endif + +#define vecdot(s, x, y, n) \ +{ \ + int i; \ + __m128d XMM0 = _mm_setzero_pd(); \ + __m128d XMM1 = _mm_setzero_pd(); \ + __m128d XMM2, XMM3, XMM4, XMM5; \ + for (i = 0;i < (n);i += 4) { \ + XMM2 = _mm_load_pd((x)+i ); \ + XMM3 = _mm_load_pd((x)+i+2); \ + XMM4 = _mm_load_pd((y)+i ); \ + XMM5 = _mm_load_pd((y)+i+2); \ + XMM2 = _mm_mul_pd(XMM2, XMM4); \ + XMM3 = _mm_mul_pd(XMM3, XMM5); \ + XMM0 = _mm_add_pd(XMM0, XMM2); \ + XMM1 = _mm_add_pd(XMM1, XMM3); \ + } \ + XMM0 = _mm_add_pd(XMM0, XMM1); \ + XMM1 = _mm_shuffle_pd(XMM0, XMM0, _MM_SHUFFLE2(1, 1)); \ + XMM0 = _mm_add_pd(XMM0, XMM1); \ + _mm_store_sd((s), XMM0); \ +} + +#define vec2norm(s, x, n) \ +{ \ + int i; \ + __m128d XMM0 = _mm_setzero_pd(); \ + __m128d XMM1 = _mm_setzero_pd(); \ + __m128d XMM2, XMM3, XMM4, XMM5; \ + for (i = 0;i < (n);i += 4) { \ + XMM2 = _mm_load_pd((x)+i ); \ + XMM3 = _mm_load_pd((x)+i+2); \ + XMM4 = XMM2; \ + XMM5 = XMM3; \ + XMM2 = _mm_mul_pd(XMM2, XMM4); \ + XMM3 = _mm_mul_pd(XMM3, XMM5); \ + XMM0 = _mm_add_pd(XMM0, XMM2); \ + XMM1 = _mm_add_pd(XMM1, XMM3); \ + } \ + XMM0 = _mm_add_pd(XMM0, XMM1); \ + XMM1 = _mm_shuffle_pd(XMM0, XMM0, _MM_SHUFFLE2(1, 1)); \ + XMM0 = _mm_add_pd(XMM0, XMM1); \ + XMM0 = _mm_sqrt_pd(XMM0); \ + _mm_store_sd((s), XMM0); \ +} + + +#define vec2norminv(s, x, n) \ +{ \ + int i; \ + __m128d XMM0 = _mm_setzero_pd(); \ + __m128d XMM1 = _mm_setzero_pd(); \ + __m128d XMM2, XMM3, XMM4, XMM5; \ + for (i = 0;i < (n);i += 4) { \ + XMM2 = _mm_load_pd((x)+i ); \ + XMM3 = _mm_load_pd((x)+i+2); \ + XMM4 = XMM2; \ + XMM5 = XMM3; \ + XMM2 = _mm_mul_pd(XMM2, XMM4); \ + XMM3 = _mm_mul_pd(XMM3, XMM5); \ + XMM0 = _mm_add_pd(XMM0, XMM2); \ + XMM1 = _mm_add_pd(XMM1, XMM3); \ + } \ + XMM2 = _mm_set1_pd(1.0); \ + XMM0 = _mm_add_pd(XMM0, XMM1); \ + XMM1 = _mm_shuffle_pd(XMM0, XMM0, _MM_SHUFFLE2(1, 1)); \ + XMM0 = _mm_add_pd(XMM0, XMM1); \ + XMM0 = _mm_sqrt_pd(XMM0); \ + XMM2 = _mm_div_pd(XMM2, XMM0); \ + _mm_store_sd((s), XMM2); \ +} diff --git a/src/plfit/arithmetic_sse_float.h b/src/plfit/arithmetic_sse_float.h new file mode 100644 index 0000000..b88f0e2 --- /dev/null +++ b/src/plfit/arithmetic_sse_float.h @@ -0,0 +1,291 @@ +/* + * SSE/SSE3 implementation of vector oprations (32bit float). + * + * Copyright (c) 2007-2010 Naoaki Okazaki + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* $Id: arithmetic_sse_float.h 65 2010-01-29 12:19:16Z naoaki $ */ + +#include + +#if !defined(__APPLE__) +#include +#endif + +#include + +#if 1400 <= _MSC_VER +#include +#endif/*_MSC_VER*/ + +#if HAVE_XMMINTRIN_H +#include +#endif/*HAVE_XMMINTRIN_H*/ + +#if LBFGS_FLOAT == 32 && LBFGS_IEEE_FLOAT +#define fsigndiff(x, y) (((*(uint32_t*)(x)) ^ (*(uint32_t*)(y))) & 0x80000000U) +#else +#define fsigndiff(x, y) (*(x) * (*(y) / fabs(*(y))) < 0.) +#endif/*LBFGS_IEEE_FLOAT*/ + +inline static void* vecalloc(size_t size) +{ + void *memblock = _aligned_malloc(size, 16); + if (memblock != NULL) { + memset(memblock, 0, size); + } + return memblock; +} + +inline static void vecfree(void *memblock) +{ + _aligned_free(memblock); +} + +#define vecset(x, c, n) \ +{ \ + int i; \ + __m128 XMM0 = _mm_set_ps1(c); \ + for (i = 0;i < (n);i += 16) { \ + _mm_store_ps((x)+i , XMM0); \ + _mm_store_ps((x)+i+ 4, XMM0); \ + _mm_store_ps((x)+i+ 8, XMM0); \ + _mm_store_ps((x)+i+12, XMM0); \ + } \ +} + +#define veccpy(y, x, n) \ +{ \ + int i; \ + for (i = 0;i < (n);i += 16) { \ + __m128 XMM0 = _mm_load_ps((x)+i ); \ + __m128 XMM1 = _mm_load_ps((x)+i+ 4); \ + __m128 XMM2 = _mm_load_ps((x)+i+ 8); \ + __m128 XMM3 = _mm_load_ps((x)+i+12); \ + _mm_store_ps((y)+i , XMM0); \ + _mm_store_ps((y)+i+ 4, XMM1); \ + _mm_store_ps((y)+i+ 8, XMM2); \ + _mm_store_ps((y)+i+12, XMM3); \ + } \ +} + +#define vecncpy(y, x, n) \ +{ \ + int i; \ + const uint32_t mask = 0x80000000; \ + __m128 XMM4 = _mm_load_ps1((float*)&mask); \ + for (i = 0;i < (n);i += 16) { \ + __m128 XMM0 = _mm_load_ps((x)+i ); \ + __m128 XMM1 = _mm_load_ps((x)+i+ 4); \ + __m128 XMM2 = _mm_load_ps((x)+i+ 8); \ + __m128 XMM3 = _mm_load_ps((x)+i+12); \ + XMM0 = _mm_xor_ps(XMM0, XMM4); \ + XMM1 = _mm_xor_ps(XMM1, XMM4); \ + XMM2 = _mm_xor_ps(XMM2, XMM4); \ + XMM3 = _mm_xor_ps(XMM3, XMM4); \ + _mm_store_ps((y)+i , XMM0); \ + _mm_store_ps((y)+i+ 4, XMM1); \ + _mm_store_ps((y)+i+ 8, XMM2); \ + _mm_store_ps((y)+i+12, XMM3); \ + } \ +} + +#define vecadd(y, x, c, n) \ +{ \ + int i; \ + __m128 XMM7 = _mm_set_ps1(c); \ + for (i = 0;i < (n);i += 8) { \ + __m128 XMM0 = _mm_load_ps((x)+i ); \ + __m128 XMM1 = _mm_load_ps((x)+i+4); \ + __m128 XMM2 = _mm_load_ps((y)+i ); \ + __m128 XMM3 = _mm_load_ps((y)+i+4); \ + XMM0 = _mm_mul_ps(XMM0, XMM7); \ + XMM1 = _mm_mul_ps(XMM1, XMM7); \ + XMM2 = _mm_add_ps(XMM2, XMM0); \ + XMM3 = _mm_add_ps(XMM3, XMM1); \ + _mm_store_ps((y)+i , XMM2); \ + _mm_store_ps((y)+i+4, XMM3); \ + } \ +} + +#define vecdiff(z, x, y, n) \ +{ \ + int i; \ + for (i = 0;i < (n);i += 16) { \ + __m128 XMM0 = _mm_load_ps((x)+i ); \ + __m128 XMM1 = _mm_load_ps((x)+i+ 4); \ + __m128 XMM2 = _mm_load_ps((x)+i+ 8); \ + __m128 XMM3 = _mm_load_ps((x)+i+12); \ + __m128 XMM4 = _mm_load_ps((y)+i ); \ + __m128 XMM5 = _mm_load_ps((y)+i+ 4); \ + __m128 XMM6 = _mm_load_ps((y)+i+ 8); \ + __m128 XMM7 = _mm_load_ps((y)+i+12); \ + XMM0 = _mm_sub_ps(XMM0, XMM4); \ + XMM1 = _mm_sub_ps(XMM1, XMM5); \ + XMM2 = _mm_sub_ps(XMM2, XMM6); \ + XMM3 = _mm_sub_ps(XMM3, XMM7); \ + _mm_store_ps((z)+i , XMM0); \ + _mm_store_ps((z)+i+ 4, XMM1); \ + _mm_store_ps((z)+i+ 8, XMM2); \ + _mm_store_ps((z)+i+12, XMM3); \ + } \ +} + +#define vecscale(y, c, n) \ +{ \ + int i; \ + __m128 XMM7 = _mm_set_ps1(c); \ + for (i = 0;i < (n);i += 8) { \ + __m128 XMM0 = _mm_load_ps((y)+i ); \ + __m128 XMM1 = _mm_load_ps((y)+i+4); \ + XMM0 = _mm_mul_ps(XMM0, XMM7); \ + XMM1 = _mm_mul_ps(XMM1, XMM7); \ + _mm_store_ps((y)+i , XMM0); \ + _mm_store_ps((y)+i+4, XMM1); \ + } \ +} + +#define vecmul(y, x, n) \ +{ \ + int i; \ + for (i = 0;i < (n);i += 16) { \ + __m128 XMM0 = _mm_load_ps((x)+i ); \ + __m128 XMM1 = _mm_load_ps((x)+i+ 4); \ + __m128 XMM2 = _mm_load_ps((x)+i+ 8); \ + __m128 XMM3 = _mm_load_ps((x)+i+12); \ + __m128 XMM4 = _mm_load_ps((y)+i ); \ + __m128 XMM5 = _mm_load_ps((y)+i+ 4); \ + __m128 XMM6 = _mm_load_ps((y)+i+ 8); \ + __m128 XMM7 = _mm_load_ps((y)+i+12); \ + XMM4 = _mm_mul_ps(XMM4, XMM0); \ + XMM5 = _mm_mul_ps(XMM5, XMM1); \ + XMM6 = _mm_mul_ps(XMM6, XMM2); \ + XMM7 = _mm_mul_ps(XMM7, XMM3); \ + _mm_store_ps((y)+i , XMM4); \ + _mm_store_ps((y)+i+ 4, XMM5); \ + _mm_store_ps((y)+i+ 8, XMM6); \ + _mm_store_ps((y)+i+12, XMM7); \ + } \ +} + + + +#if 3 <= __SSE__ +/* + Horizontal add with haddps SSE3 instruction. The work register (rw) + is unused. + */ +#define __horizontal_sum(r, rw) \ + r = _mm_hadd_ps(r, r); \ + r = _mm_hadd_ps(r, r); + +#else +/* + Horizontal add with SSE instruction. The work register (rw) is used. + */ +#define __horizontal_sum(r, rw) \ + rw = r; \ + r = _mm_shuffle_ps(r, rw, _MM_SHUFFLE(1, 0, 3, 2)); \ + r = _mm_add_ps(r, rw); \ + rw = r; \ + r = _mm_shuffle_ps(r, rw, _MM_SHUFFLE(2, 3, 0, 1)); \ + r = _mm_add_ps(r, rw); + +#endif + +#define vecdot(s, x, y, n) \ +{ \ + int i; \ + __m128 XMM0 = _mm_setzero_ps(); \ + __m128 XMM1 = _mm_setzero_ps(); \ + __m128 XMM2, XMM3, XMM4, XMM5; \ + for (i = 0;i < (n);i += 8) { \ + XMM2 = _mm_load_ps((x)+i ); \ + XMM3 = _mm_load_ps((x)+i+4); \ + XMM4 = _mm_load_ps((y)+i ); \ + XMM5 = _mm_load_ps((y)+i+4); \ + XMM2 = _mm_mul_ps(XMM2, XMM4); \ + XMM3 = _mm_mul_ps(XMM3, XMM5); \ + XMM0 = _mm_add_ps(XMM0, XMM2); \ + XMM1 = _mm_add_ps(XMM1, XMM3); \ + } \ + XMM0 = _mm_add_ps(XMM0, XMM1); \ + __horizontal_sum(XMM0, XMM1); \ + _mm_store_ss((s), XMM0); \ +} + +#define vec2norm(s, x, n) \ +{ \ + int i; \ + __m128 XMM0 = _mm_setzero_ps(); \ + __m128 XMM1 = _mm_setzero_ps(); \ + __m128 XMM2, XMM3; \ + for (i = 0;i < (n);i += 8) { \ + XMM2 = _mm_load_ps((x)+i ); \ + XMM3 = _mm_load_ps((x)+i+4); \ + XMM2 = _mm_mul_ps(XMM2, XMM2); \ + XMM3 = _mm_mul_ps(XMM3, XMM3); \ + XMM0 = _mm_add_ps(XMM0, XMM2); \ + XMM1 = _mm_add_ps(XMM1, XMM3); \ + } \ + XMM0 = _mm_add_ps(XMM0, XMM1); \ + __horizontal_sum(XMM0, XMM1); \ + XMM2 = XMM0; \ + XMM1 = _mm_rsqrt_ss(XMM0); \ + XMM3 = XMM1; \ + XMM1 = _mm_mul_ss(XMM1, XMM1); \ + XMM1 = _mm_mul_ss(XMM1, XMM3); \ + XMM1 = _mm_mul_ss(XMM1, XMM0); \ + XMM1 = _mm_mul_ss(XMM1, _mm_set_ss(-0.5f)); \ + XMM3 = _mm_mul_ss(XMM3, _mm_set_ss(1.5f)); \ + XMM3 = _mm_add_ss(XMM3, XMM1); \ + XMM3 = _mm_mul_ss(XMM3, XMM2); \ + _mm_store_ss((s), XMM3); \ +} + +#define vec2norminv(s, x, n) \ +{ \ + int i; \ + __m128 XMM0 = _mm_setzero_ps(); \ + __m128 XMM1 = _mm_setzero_ps(); \ + __m128 XMM2, XMM3; \ + for (i = 0;i < (n);i += 16) { \ + XMM2 = _mm_load_ps((x)+i ); \ + XMM3 = _mm_load_ps((x)+i+4); \ + XMM2 = _mm_mul_ps(XMM2, XMM2); \ + XMM3 = _mm_mul_ps(XMM3, XMM3); \ + XMM0 = _mm_add_ps(XMM0, XMM2); \ + XMM1 = _mm_add_ps(XMM1, XMM3); \ + } \ + XMM0 = _mm_add_ps(XMM0, XMM1); \ + __horizontal_sum(XMM0, XMM1); \ + XMM2 = XMM0; \ + XMM1 = _mm_rsqrt_ss(XMM0); \ + XMM3 = XMM1; \ + XMM1 = _mm_mul_ss(XMM1, XMM1); \ + XMM1 = _mm_mul_ss(XMM1, XMM3); \ + XMM1 = _mm_mul_ss(XMM1, XMM0); \ + XMM1 = _mm_mul_ss(XMM1, _mm_set_ss(-0.5f)); \ + XMM3 = _mm_mul_ss(XMM3, _mm_set_ss(1.5f)); \ + XMM3 = _mm_add_ss(XMM3, XMM1); \ + _mm_store_ss((s), XMM3); \ +} diff --git a/src/plfit/error.c b/src/plfit/error.c new file mode 100644 index 0000000..4313fc9 --- /dev/null +++ b/src/plfit/error.c @@ -0,0 +1,75 @@ +/* error.c + * + * Copyright (C) 2010-2011 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include "error.h" +#include "platform.h" + +static char *plfit_i_error_strings[] = { + "No error", + "Failed", + "Invalid value", + "Underflow", + "Overflow", + "Not enough memory" +}; + +#ifndef USING_R +static plfit_error_handler_t* plfit_error_handler = plfit_error_handler_abort; +#else +/* This is overwritten, anyway */ +static plfit_error_handler_t* plfit_error_handler = plfit_error_handler_ignore; +#endif + +const char* plfit_strerror(const int plfit_errno) { + return plfit_i_error_strings[plfit_errno]; +} + +plfit_error_handler_t* plfit_set_error_handler(plfit_error_handler_t* new_handler) { + plfit_error_handler_t* old_handler = plfit_error_handler; + plfit_error_handler = new_handler; + return old_handler; +} + +void plfit_error(const char *reason, const char *file, int line, + int plfit_errno) { + plfit_error_handler(reason, file, line, plfit_errno); +} + +#ifndef USING_R +void plfit_error_handler_abort(const char *reason, const char *file, int line, + int plfit_errno) { + fprintf(stderr, "Error at %s:%i : %s, %s\n", file, line, reason, + plfit_strerror(plfit_errno)); + abort(); +} +#endif + +#ifndef USING_R +void plfit_error_handler_printignore(const char *reason, const char *file, int line, + int plfit_errno) { + fprintf(stderr, "Error at %s:%i : %s, %s\n", file, line, reason, + plfit_strerror(plfit_errno)); +} +#endif + +void plfit_error_handler_ignore(const char* reason, const char* file, int line, + int plfit_errno) { +} diff --git a/src/plfit/error.h b/src/plfit/error.h new file mode 100644 index 0000000..a77cb08 --- /dev/null +++ b/src/plfit/error.h @@ -0,0 +1,86 @@ +/* error.h + * + * Copyright (C) 2010-2011 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __ERROR_H__ +#define __ERROR_H__ + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +__BEGIN_DECLS + +enum { + PLFIT_SUCCESS = 0, + PLFIT_FAILURE = 1, + PLFIT_EINVAL = 2, + PLFIT_UNDRFLOW = 3, + PLFIT_OVERFLOW = 4, + PLFIT_ENOMEM = 5 +}; + +#if (defined(__GNUC__) && GCC_VERSION_MAJOR >= 3) +# define PLFIT_UNLIKELY(a) __builtin_expect((a), 0) +# define PLFIT_LIKELY(a) __builtin_expect((a), 1) +#else +# define PLFIT_UNLIKELY(a) a +# define PLFIT_LIKELY(a) a +#endif + +#define PLFIT_CHECK(a) \ + do {\ + int plfit_i_ret=(a); \ + if (PLFIT_UNLIKELY(plfit_i_ret != PLFIT_SUCCESS)) {\ + return plfit_i_ret; \ + } \ + } while(0) + +#define PLFIT_ERROR(reason,plfit_errno) \ + do {\ + plfit_error (reason, __FILE__, __LINE__, plfit_errno) ; \ + return plfit_errno ; \ + } while (0) + +typedef void plfit_error_handler_t(const char*, const char*, int, int); + +extern plfit_error_handler_t plfit_error_handler_abort; +extern plfit_error_handler_t plfit_error_handler_ignore; +extern plfit_error_handler_t plfit_error_handler_printignore; + +plfit_error_handler_t* plfit_set_error_handler(plfit_error_handler_t* new_handler); + +void plfit_error(const char *reason, const char *file, int line, int plfit_errno); +const char* plfit_strerror(const int plfit_errno); + +void plfit_error_handler_abort(const char *reason, const char *file, int line, + int plfit_errno); +void plfit_error_handler_ignore(const char *reason, const char *file, int line, + int plfit_errno); +void plfit_error_handler_printignore(const char *reason, const char *file, int line, + int plfit_errno); + +__END_DECLS + +#endif /* __ERROR_H__ */ diff --git a/src/plfit/gss.c b/src/plfit/gss.c new file mode 100644 index 0000000..4ba6d2d --- /dev/null +++ b/src/plfit/gss.c @@ -0,0 +1,153 @@ +/* gss.c + * + * Copyright (C) 2012 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include "error.h" +#include "gss.h" +#include "platform.h" + +/** + * \def PHI + * + * The golden ratio, i.e. 1+sqrt(5)/2 + */ +#define PHI 1.618033988749895 + +/** + * \def RESPHI + * + * Constant defined as 2 - \c PHI + */ +#define RESPHI 0.3819660112501051 + +/** + * \const _defparam + * + * Default parameters for the GSS algorithm. + */ +static const gss_parameter_t _defparam = { + /* .epsilon = */ DBL_MIN, + /* .on_error = */ GSS_ERROR_STOP +}; + +/** + * Stores whether the last optimization run triggered a warning or not. + */ +static unsigned short int gss_i_warning_flag = 0; + +void gss_parameter_init(gss_parameter_t *param) { + memcpy(param, &_defparam, sizeof(*param)); +} + +unsigned short int gss_get_warning_flag() { + return gss_i_warning_flag; +} + +#define TERMINATE { \ + if (_min) { \ + *(_min) = min; \ + } \ + if (_fmin) { \ + *(_fmin) = fmin; \ + } \ +} + +#define EVALUATE(x, fx) { \ + fx = proc_evaluate(instance, x); \ + if (fmin > fx) { \ + min = x; \ + fmin = fx; \ + } \ + if (proc_progress) { \ + retval = proc_progress(instance, x, fx, min, fmin, \ + (a < b) ? a : b, (a < b) ? b : a, k); \ + if (retval) { \ + TERMINATE; \ + return PLFIT_SUCCESS; \ + } \ + } \ +} + +int gss(double a, double b, double *_min, double *_fmin, + gss_evaluate_t proc_evaluate, gss_progress_t proc_progress, + void* instance, const gss_parameter_t *_param) { + double c, d, min; + double fa, fb, fc, fd, fmin; + int k = 0; + int retval; + unsigned short int successful = 1; + + gss_parameter_t param = _param ? (*_param) : _defparam; + + gss_i_warning_flag = 0; + + if (a > b) { + c = a; a = b; b = c; + } + + min = a; + fmin = proc_evaluate(instance, a); + + c = a + RESPHI*(b-a); + + EVALUATE(a, fa); + EVALUATE(b, fb); + EVALUATE(c, fc); + + if (fc >= fa || fc >= fb) { + if (param.on_error == GSS_ERROR_STOP) { + return PLFIT_FAILURE; + } else { + gss_i_warning_flag = 1; + } + } + + while (fabs(a-b) > param.epsilon) { + k++; + + d = c + RESPHI*(b-c); + EVALUATE(d, fd); + + if (fd >= fa || fd >= fb) { + if (param.on_error == GSS_ERROR_STOP) { + successful = 0; + break; + } else { + gss_i_warning_flag = 1; + } + } + + if (fc <= fd) { + b = a; a = d; + } else { + a = c; c = d; fc = fd; + } + } + + if (successful) { + c = (a+b) / 2.0; + k++; + EVALUATE(c, fc); + TERMINATE; + } + + return successful ? PLFIT_SUCCESS : PLFIT_FAILURE; +} diff --git a/src/plfit/gss.h b/src/plfit/gss.h new file mode 100644 index 0000000..b96b213 --- /dev/null +++ b/src/plfit/gss.h @@ -0,0 +1,146 @@ +/* gss.h + * + * Copyright (C) 2012 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __GSS_H__ +#define __GSS_H__ + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +__BEGIN_DECLS + +/** + * Enum specifying what the search should do when the function is not U-shaped. + */ +typedef enum { + GSS_ERROR_STOP, /**< Stop and return an error code */ + GSS_ERROR_WARN /**< Continue and set the warning flag */ +} gss_error_handling_t; + +/** + * Parameter settings for a golden section search. + */ +typedef struct { + double epsilon; + gss_error_handling_t on_error; +} gss_parameter_t; + +/** + * Callback interface to provide objective function evaluations for the golden + * section search. + * + * The gss() function calls this function to obtain the values of the objective + * function when needed. A client program must implement this function to evaluate + * the value of the objective function, given the location. + * + * @param instance The user data sent for the gss() function by the client. + * @param x The current value of the variable. + * @retval double The value of the objective function for the current + * variable. + */ +typedef double (*gss_evaluate_t)(void *instance, double x); + +/** + * Callback interface to receive the progress of the optimization process for + * the golden section search. + * + * The gss() function calls this function for each iteration. Implementing + * this function, a client program can store or display the current progress + * of the optimization process. + * + * @param instance The user data sent for the gss() function by the client. + * @param x The current value of the variable. + * @param fx The value of the objective function at x. + * @param min The location of the minimum value of the objective + * function found so far. + * @param fmin The minimum value of the objective function found so far. + * @param left The left side of the current bracket. + * @param right The right side of the current bracket. + * @param k The index of the current iteration. + * @retval int Zero to continue the optimization process. Returning a + * non-zero value will cancel the optimization process. + */ +typedef int (*gss_progress_t)(void *instance, double x, double fx, double min, + double fmin, double left, double right, int k); + +/** + * Start a golden section search optimization. + * + * @param a The left side of the bracket to start from + * @param b The right side of the bracket to start from + * @param min The pointer to the variable that receives the location of the + * final value of the objective function. This argument can be set to + * \c NULL if the location of the final value of the objective + * function is unnecessary. + * @param fmin The pointer to the variable that receives the final value of + * the objective function. This argument can be st to \c NULL if the + * final value of the objective function is unnecessary. + * @param proc_evaluate The callback function to evaluate the objective + * function at a given location. + * @param proc_progress The callback function to receive the progress (the + * last evaluated location, the value of the objective + * function at that location, the width of the current + * bracket, the minimum found so far and the step + * count). This argument can be set to \c NULL if + * a progress report is unnecessary. + * @param instance A user data for the client program. The callback + * functions will receive the value of this argument. + * @param param The pointer to a structure representing parameters for + * GSS algorithm. A client program can set this parameter + * to \c NULL to use the default parameters. + * Call the \ref gss_parameter_init() function to fill a + * structure with the default values. + * @retval int The status code. This function returns zero if the + * minimization process terminates without an error. A + * non-zero value indicates an error; in particular, + * \c PLFIT_FAILURE means that the function is not + * U-shaped. + */ +int gss(double a, double b, double *min, double *fmin, + gss_evaluate_t proc_evaluate, gss_progress_t proc_progress, + void* instance, const gss_parameter_t *_param); + +/** + * Return the state of the warning flag. + * + * The warning flag is 1 if the last optimization was run on a function that + * was not U-shaped. + */ +unsigned short int gss_get_warning_flag(); + +/** + * Initialize GSS parameters to the default values. + * + * Call this function to fill a parameter structure with the default values + * and overwrite parameter values if necessary. + * + * @param param The pointer to the parameter structure. + */ +void gss_parameter_init(gss_parameter_t *param); + +__END_DECLS + +#endif /* __GSS_H__ */ diff --git a/src/plfit/hzeta.c b/src/plfit/hzeta.c new file mode 100644 index 0000000..4765066 --- /dev/null +++ b/src/plfit/hzeta.c @@ -0,0 +1,651 @@ +/* vim:set ts=4 sw=2 sts=2 et: */ + +/* This file was imported from a private scientific library + * based on GSL coined Home Scientific Libray (HSL) by its author + * Jerome Benoit; this very material is itself inspired from the + * material written by G. Jungan and distributed by GSL. + * Ultimately, some modifications were done in order to render the + * imported material independent from the rest of GSL. + */ + +/* `hsl/specfunc/hzeta.c' C source file +// HSL - Home Scientific Library +// Copyright (C) 2017-2018 Jerome Benoit +// +// HSL is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +/* +// The material in this file is mainly inspired by the material written by +// G. Jungan and distributed under GPLv2 by the GNU Scientific Library (GSL) +// ( https://www.gnu.org/software/gsl/ [specfunc/zeta.c]), itself inspired by +// the material written by Moshier and distributed in the Cephes Mathematical +// Library ( http://www.moshier.net/ [zeta.c]). +// +// More specifically, hsl_sf_hzeta_e is a slightly modifed clone of +// gsl_sf_hzeta_e as found in GSL 2.4; the remaining is `inspired by'. +// [Sooner or later a _Working_Note_ may be deposited at ResearchGate +// ( https://www.researchgate.net/profile/Jerome_Benoit )] +*/ + +/* Author: Jerome G. Benoit < jgmbenoit _at_ rezozer _dot_ net > */ + +#ifdef _MSC_VER +#define _USE_MATH_DEFINES +#endif + +#include +#include +#include "hzeta.h" +#include "error.h" +#include "platform.h" + +/* imported from gsl_machine.h */ + +#define GSL_LOG_DBL_MIN (-7.0839641853226408e+02) +#define GSL_LOG_DBL_MAX 7.0978271289338397e+02 +#define GSL_DBL_EPSILON 2.2204460492503131e-16 + +/* imported from gsl_math.h */ + +#ifndef M_LOG2E +#define M_LOG2E 1.44269504088896340735992468100 /* log_2 (e) */ +#endif + +/* imported from gsl_sf_result.h */ + +struct gsl_sf_result_struct { + double val; + double err; +}; +typedef struct gsl_sf_result_struct gsl_sf_result; + +/* imported and adapted from hsl/specfunc/specfunc_def.h */ + +#define HSL_SF_EVAL_RESULT(FnE) \ + gsl_sf_result result; \ + FnE ; \ + return (result.val); + +#define HSL_SF_EVAL_TUPLE_RESULT(FnET) \ + gsl_sf_result result0; \ + gsl_sf_result result1; \ + FnET ; \ + *tuple1=result1.val; \ + *tuple0=result0.val; \ + return (result0.val); + +/* */ + + +#define HSL_SF_HZETA_EULERMACLAURIN_SERIES_SHIFT 10 +#define HSL_SF_HZETA_EULERMACLAURIN_SERIES_ORDER 32 + +#define HSL_SF_LNHZETA_EULERMACLAURIN_SERIES_SHIFT_MAX 256 + +// B_{2j}/(2j) +static +double hsl_sf_hzeta_eulermaclaurin_series_coeffs[HSL_SF_HZETA_EULERMACLAURIN_SERIES_ORDER+1]={ + +1.0, + +1.0/12.0, + -1.0/720.0, + +1.0/30240.0, + -1.0/1209600.0, + +1.0/47900160.0, + -691.0/1307674368000.0, + +1.0/74724249600.0, + -3.38968029632258286683019539125e-13, + +8.58606205627784456413590545043e-15, + -2.17486869855806187304151642387e-16, + +5.50900282836022951520265260890e-18, + -1.39544646858125233407076862641e-19, + +3.53470703962946747169322997780e-21, + -8.95351742703754685040261131811e-23, + +2.26795245233768306031095073887e-24, + -5.74479066887220244526388198761e-26, + +1.45517247561486490186626486727e-27, + -3.68599494066531017818178247991e-29, + +9.33673425709504467203255515279e-31, + -2.36502241570062993455963519637e-32, + +5.99067176248213430465991239682e-34, + -1.51745488446829026171081313586e-35, + +3.84375812545418823222944529099e-37, + -9.73635307264669103526762127925e-39, + +2.46624704420068095710640028029e-40, + -6.24707674182074369314875679472e-42, + +1.58240302446449142975108170683e-43, + -4.00827368594893596853001219052e-45, + +1.01530758555695563116307139454e-46, + -2.57180415824187174992481940976e-48, + +6.51445603523381493155843485864e-50, + -1.65013099068965245550609878048e-51 + }; // hsl_sf_hzeta_eulermaclaurin_series_coeffs + +// 4\zeta(2j)/(2\pi)^(2j) +static +double hsl_sf_hzeta_eulermaclaurin_series_majorantratios[HSL_SF_HZETA_EULERMACLAURIN_SERIES_ORDER+1]={ + -2.0, + +1.0/6.0, + +1.0/360.0, + +1.0/15120.0, + +1.0/604800.0, + +1.0/23950080.0, + +691.0/653837184000.0, + +1.0/37362124800.0, + +3617.0/5335311421440000.0, + +1.71721241125556891282718109009e-14, + +4.34973739711612374608303284773e-16, + +1.10180056567204590304053052178e-17, + +2.79089293716250466814153725281e-19, + +7.06941407925893494338645995561e-21, + +1.79070348540750937008052226362e-22, + +4.53590490467536612062190147774e-24, + +1.14895813377444048905277639752e-25, + +2.91034495122972980373252973454e-27, + +7.37198988133062035636356495982e-29, + +1.86734685141900893440651103056e-30, + +4.73004483140125986911927039274e-32, + +1.19813435249642686093198247936e-33, + +3.03490976893658052342162627173e-35, + +7.68751625090837646445889058198e-37, + +1.94727061452933820705352425585e-38, + +4.93249408840136191421280056051e-40, + +1.24941534836414873862975135893e-41, + +3.16480604892898285950216341362e-43, + +8.01654737189787193706002438098e-45, + +2.03061517111391126232614278906e-46, + +5.14360831648374349984963881946e-48, + +1.30289120704676298631168697172e-49, + +3.30026198137930491101219756091e-51 + }; // hsl_sf_hzeta_eulermaclaurin_series_majorantratios + + +extern +int hsl_sf_hzeta_e(const double s, const double q, gsl_sf_result * result) { + + /* CHECK_POINTER(result) */ + + if ((s <= 1.0) || (q <= 0.0)) { + PLFIT_ERROR("s must be larger than 1.0 and q must be larger than zero", PLFIT_EINVAL); + } + else { + const double max_bits=54.0; // max_bits=\lceil{s}\rceil with \zeta(s,2)=\zeta(s)-1=GSL_DBL_EPSILON + const double ln_term0=-s*log(q); + if (ln_term0 < GSL_LOG_DBL_MIN+1.0) { + PLFIT_ERROR("underflow", PLFIT_UNDRFLOW); + } + else if (GSL_LOG_DBL_MAX-1.0 < ln_term0) { + PLFIT_ERROR("overflow", PLFIT_OVERFLOW); + } +#if 1 + else if (((max_bits < s) && (q < 1.0)) || ((0.5*max_bits < s) && (q < 0.25))) { + result->val=pow(q,-s); + result->err=2.0*GSL_DBL_EPSILON*fabs(result->val); + return (PLFIT_SUCCESS); + } + else if ((0.5*max_bits < s) && (q < 1.0)) { + const double a0=pow(q,-s); + const double p1=pow(q/(1.0+q),s); + const double p2=pow(q/(2.0+q),s); + const double ans=a0*(1.0+p1+p2); + result->val=ans; + result->err=GSL_DBL_EPSILON*(2.0+0.5*s)*fabs(result->val); + return (PLFIT_SUCCESS); + } +#endif + else { // Euler-Maclaurin summation formula + const double qshift=HSL_SF_HZETA_EULERMACLAURIN_SERIES_SHIFT+q; + const double inv_qshift=1.0/qshift; + const double sqr_inv_qshift=inv_qshift*inv_qshift; + const double inv_sm1=1.0/(s-1.0); + const double pmax=pow(qshift,-s); + double terms[HSL_SF_HZETA_EULERMACLAURIN_SERIES_SHIFT+HSL_SF_HZETA_EULERMACLAURIN_SERIES_ORDER+1]={NAN}; + double delta=NAN; + double tscp=s; + double scp=tscp; + double pcp=pmax*inv_qshift; + double ratio=scp*pcp; + size_t n=0; + size_t j=0; + double ans=0.0; + double mjr=NAN; + + for(j=0;jval=+ans; + result->err=2.0*((HSL_SF_HZETA_EULERMACLAURIN_SERIES_SHIFT+1.0)*GSL_DBL_EPSILON*fabs(ans)+mjr); + return (PLFIT_SUCCESS); + } + } + + return (PLFIT_SUCCESS); } + +extern +double hsl_sf_hzeta(const double s, const double q) { + HSL_SF_EVAL_RESULT(hsl_sf_hzeta_e(s,q,&result)); } + +extern +int hsl_sf_hzeta_deriv_e(const double s, const double q, gsl_sf_result * result) { + + /* CHECK_POINTER(result) */ + + if ((s <= 1.0) || (q <= 0.0)) { + PLFIT_ERROR("s must be larger than 1.0 and q must be larger than zero", PLFIT_EINVAL); + } + else { + const double ln_hz_term0=-s*log(q); + if (ln_hz_term0 < GSL_LOG_DBL_MIN+1.0) { + PLFIT_ERROR("underflow", PLFIT_UNDRFLOW); + } + else if (GSL_LOG_DBL_MAX-1.0 < ln_hz_term0) { + PLFIT_ERROR("overflow", PLFIT_OVERFLOW); + } + else { // Euler-Maclaurin summation formula + const double qshift=HSL_SF_HZETA_EULERMACLAURIN_SERIES_SHIFT+q; + const double inv_qshift=1.0/qshift; + const double sqr_inv_qshift=inv_qshift*inv_qshift; + const double inv_sm1=1.0/(s-1.0); + const double pmax=pow(qshift,-s); + const double lmax=log(qshift); + double terms[HSL_SF_HZETA_EULERMACLAURIN_SERIES_SHIFT+HSL_SF_HZETA_EULERMACLAURIN_SERIES_ORDER+1]={NAN}; + double delta=NAN; + double tscp=s; + double scp=tscp; + double pcp=pmax*inv_qshift; + double lcp=lmax-1.0/s; + double ratio=scp*pcp*lcp; + double qs=NAN; + size_t n=0; + size_t j=0; + double ans=0.0; + double mjr=NAN; + + for(j=0,qs=q;jval=-ans; + result->err=2.0*((HSL_SF_HZETA_EULERMACLAURIN_SERIES_SHIFT+1.0)*GSL_DBL_EPSILON*fabs(ans)+mjr); + return (PLFIT_SUCCESS); + } + } + + return (PLFIT_SUCCESS); } + +extern +double hsl_sf_hzeta_deriv(const double s, const double q) { + HSL_SF_EVAL_RESULT(hsl_sf_hzeta_deriv_e(s,q,&result)); } + +extern +int hsl_sf_hzeta_deriv2_e(const double s, const double q, gsl_sf_result * result) { + + /* CHECK_POINTER(result) */ + + if ((s <= 1.0) || (q <= 0.0)) { + PLFIT_ERROR("s must be larger than 1.0 and q must be larger than zero", PLFIT_EINVAL); + } + else { + const double ln_hz_term0=-s*log(q); + if (ln_hz_term0 < GSL_LOG_DBL_MIN+1.0) { + PLFIT_ERROR("underflow", PLFIT_UNDRFLOW); + } + else if (GSL_LOG_DBL_MAX-1.0 < ln_hz_term0) { + PLFIT_ERROR("overflow", PLFIT_OVERFLOW); + } + else { // Euler-Maclaurin summation formula + const double qshift=HSL_SF_HZETA_EULERMACLAURIN_SERIES_SHIFT+q; + const double inv_qshift=1.0/qshift; + const double sqr_inv_qshift=inv_qshift*inv_qshift; + const double inv_sm1=1.0/(s-1.0); + const double pmax=pow(qshift,-s); + const double lmax=log(qshift); + const double lmax_p_inv_sm1=lmax+inv_sm1; + const double sqr_inv_sm1=inv_sm1*inv_sm1; + const double sqr_lmax=lmax*lmax; + const double sqr_lmax_p_inv_sm1=lmax_p_inv_sm1*lmax_p_inv_sm1; + double terms[HSL_SF_HZETA_EULERMACLAURIN_SERIES_SHIFT+HSL_SF_HZETA_EULERMACLAURIN_SERIES_ORDER+1]={NAN}; + double delta=NAN; + double tscp=s; + double slcp=NAN; + double plcp=NAN; + double scp=tscp; + double pcp=pmax*inv_qshift; + double lcp=1.0/s-lmax; + double sqr_lcp=lmax*(lmax-2.0/s); + double ratio=scp*pcp*sqr_lcp; + double qs=NAN; + double lqs=NAN; + size_t n=0; + size_t j=0; + double ans=0.0; + double mjr=NAN; + + for(j=0,qs=q;jval=+ans; + result->err=2.0*((HSL_SF_HZETA_EULERMACLAURIN_SERIES_SHIFT+1.0)*GSL_DBL_EPSILON*fabs(ans)+mjr); + return (PLFIT_SUCCESS); + } + } + + return (PLFIT_SUCCESS); } + +extern +double hsl_sf_hzeta_deriv2(const double s, const double q) { + HSL_SF_EVAL_RESULT(hsl_sf_hzeta_deriv2_e(s,q,&result)); } + +static inline +double hsl_sf_hZeta0_zed(const double s, const double q) { +#if 1 + const long double ld_q=(long double)(q); + const long double ld_s=(long double)(s); + const long double ld_log1prq=log1pl(1.0L/ld_q); + const long double ld_epsilon=expm1l(-ld_s*ld_log1prq); + const long double ld_z=ld_s+(ld_q+0.5L*ld_s+0.5L)*ld_epsilon; + const double z=(double)(ld_z); +#else + double z=s+(q+0.5*s+0.5)*expm1(-s*log1p(1.0/q)); +#endif + return (z); } + +// Z_{0}(s,a) = a^s \left(\frac{1}{2}+\frac{a}{s-1}\right)^{-1} \zeta(s,a) - 1 +// Z_{0}(s,a) = O\left(\frac{(s-1)s}{6a^{2}}\right) +static +int hsl_sf_hZeta0(const double s, const double q, double * value, double * abserror) { + const double criterion=ceil(10.0*s-q); + const size_t shift=(criterion<0.0)?0: + (criterionval=log1p(ln_hZeta0_value); + result->err=(2.0*GSL_DBL_EPSILON*ln_hz_coeff+hZeta0_abserror)/(1.0+ln_hZeta0_value); + } + + if (result_deriv) { + const double ld_hz_coeff2=1.0+inv_sm1*M_LOG2E; + const double ld_hz_coeff1=1.0+inv_qsm1*ld_hz_coeff2; + double hZeta1_value=NAN; + double hZeta1_abserror=NAN; + hsl_sf_hZeta1(s,2.0,M_LN2,&hZeta1_value,&hZeta1_abserror,NULL); + hZeta0_value*=hz_coeff1; + hZeta0_value+=hz_coeff0; + hZeta1_value+=1.0; + hZeta1_value*=-M_LN2*ld_hz_coeff1; + result_deriv->val=hZeta1_value/hZeta0_value; + result_deriv->err=2.0*GSL_DBL_EPSILON*fabs(result_deriv->val)+(hZeta0_abserror+hZeta1_abserror); + } + } + else { + const double ln_q=log(q); + double hZeta0_value=NAN; + double hZeta0_abserror=NAN; + hsl_sf_hZeta0(s,q,&hZeta0_value,&hZeta0_abserror); + if (result) { + const double ln_hz_term0=-s*ln_q; + const double ln_hz_term1=log(0.5+q/(s-1.0)); + result->val=ln_hz_term0+ln_hz_term1+log1p(hZeta0_value); + result->err=2.0*GSL_DBL_EPSILON*(fabs(ln_hz_term0)+fabs(ln_hz_term1))+hZeta0_abserror/(1.0+hZeta0_value); + } + if (result_deriv) { + double hZeta1_value=NAN; + double hZeta1_abserror=NAN; + double ld_hz_coeff1=NAN; + hsl_sf_hZeta1(s,q,ln_q,&hZeta1_value,&hZeta1_abserror,&ld_hz_coeff1); + result_deriv->val=-ln_q*ld_hz_coeff1*(1.0+hZeta1_value)/(1.0+hZeta0_value); + result_deriv->err=2.0*GSL_DBL_EPSILON*fabs(result_deriv->val)+(hZeta0_abserror+hZeta1_abserror); + } + } + + return (PLFIT_SUCCESS); } + +extern +double hsl_sf_lnhzeta_deriv_tuple(const double s, const double q, double * tuple0, double * tuple1) { + HSL_SF_EVAL_TUPLE_RESULT(hsl_sf_lnhzeta_deriv_tuple_e(s,q,&result0,&result1)); } + +extern +int hsl_sf_lnhzeta_e(const double s, const double q, gsl_sf_result * result) { + return (hsl_sf_lnhzeta_deriv_tuple_e(s,q,result,NULL)); } + +extern +double hsl_sf_lnhzeta(const double s, const double q) { + HSL_SF_EVAL_RESULT(hsl_sf_lnhzeta_e(s,q,&result)); } + +extern +int hsl_sf_lnhzeta_deriv_e(const double s, const double q, gsl_sf_result * result) { + return (hsl_sf_lnhzeta_deriv_tuple_e(s,q,NULL,result)); } + +extern +double hsl_sf_lnhzeta_deriv(const double s, const double q) { + HSL_SF_EVAL_RESULT(hsl_sf_lnhzeta_deriv_e(s,q,&result)); } + +// +// End of file `hsl/specfunc/hzeta.c'. diff --git a/src/plfit/hzeta.h b/src/plfit/hzeta.h new file mode 100644 index 0000000..31d646e --- /dev/null +++ b/src/plfit/hzeta.h @@ -0,0 +1,96 @@ +/* This file was imported from a private scientific library + * based on GSL coined Home Scientific Libray (HSL) by its author + * Jerome Benoit; this very material is itself inspired from the + * material written by G. Jungan and distributed by GSL. + * Ultimately, some modifications were done in order to render the + * imported material independent from the rest of GSL. + */ + +/* `hsl/hsl_sf_zeta.h' C header file +// HSL - Home Scientific Library +// Copyright (C) 2005-2018 Jerome Benoit +// +// HSL is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +/* For futher details, see its source conterpart src/hzeta.c */ + +/* Author: Jerome G. Benoit < jgmbenoit _at_ rezozer _dot_ net > */ + +#ifndef __HZETA_H__ +#define __HZETA_H__ + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +__BEGIN_DECLS + + +/* Hurwitz Zeta Function + * zeta(s,q) = Sum[ (k+q)^(-s), {k,0,Infinity} ] + * + * s > 1.0, q > 0.0 + */ +double hsl_sf_hzeta(const double s, const double q); + +/* First Derivative of Hurwitz Zeta Function + * zeta'(s,q) = - Sum[ Ln(k+q)/(k+q)^(s), {k,0,Infinity} ] + * + * s > 1.0, q > 0.0 + */ +double hsl_sf_hzeta_deriv(const double s, const double q); + +/* Second Derivative of Hurwitz Zeta Function + * zeta''(s,q) = + Sum[ Ln(k+q)^2/(k+q)^(s), {k,0,Infinity} ] + * + * s > 1.0, q > 0.0 + */ +double hsl_sf_hzeta_deriv2(const double s, const double q); + +/* Logarithm of Hurwitz Zeta Function + * lnzeta(s,q) = ln(zeta(s,q)) + * + * s > 1.0, q > 0.0 (and q >> 1) + */ +double hsl_sf_lnhzeta(const double s, const double q); + +/* Logarithmic Derivative of Hurwitz Zeta Function + * lnzeta'(s,q) = zeta'(s,q)/zeta(s,q) + * + * s > 1.0, q > 0.0 (and q >> 1) + */ +double hsl_sf_lnhzeta_deriv(const double s, const double q); + +/* Logarithm and Logarithmic Derivative of Hurwitz Zeta Function: + * nonredundant computation version: + * - lnzeta(s,q) and lnzeta'(s,q) are stored in *deriv0 and *deriv1, respectively; + * - the return value and the value stored in *deriv0 are the same; + * - deriv0 and deriv1 must be effective pointers, that is, not the NULL pointer. + * + * s > 1.0, q > 0.0 (and q >> 1) + */ +double hsl_sf_lnhzeta_deriv_tuple(const double s, const double q, double * deriv0, double * deriv1); + + +__END_DECLS + +#endif // __HZETA_H__ diff --git a/src/plfit/kolmogorov.c b/src/plfit/kolmogorov.c new file mode 100644 index 0000000..cd0858e --- /dev/null +++ b/src/plfit/kolmogorov.c @@ -0,0 +1,66 @@ +/* kolmogorov.c + * + * Copyright (C) 2010-2011 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "kolmogorov.h" + +double plfit_kolmogorov(double z) { + const double fj[4] = { -2, -8, -18, -32 }; + const double w = 2.50662827; + const double c1 = -1.2337005501361697; /* -pi^2 / 8 */ + const double c2 = -11.103304951225528; /* 9*c1 */ + const double c3 = -30.842513753404244; /* 25*c1 */ + + double u = fabs(z); + double v; + + if (u < 0.2) + return 1; + + if (u < 0.755) { + v = 1.0 / (u*u); + return 1 - w * (exp(c1*v) + exp(c2*v) + exp(c3*v)) / u; + } + + if (u < 6.8116) { + double r[4] = { 0, 0, 0, 0 }; + long int maxj = (long int)(3.0 / u + 0.5); + long int j; + + if (maxj < 1) + maxj = 1; + + v = u*u; + for (j = 0; j < maxj; j++) { + r[j] = exp(fj[j] * v); + } + + return 2*(r[0] - r[1] + r[2] - r[3]); + } + + return 0; +} + +double plfit_ks_test_one_sample_p(double d, size_t n) { + return plfit_kolmogorov(d * sqrt(n)); +} + +double plfit_ks_test_two_sample_p(double d, size_t n1, size_t n2) { + return plfit_kolmogorov(d * sqrt(n1*n2 / ((double)(n1+n2)))); +} diff --git a/src/plfit/kolmogorov.h b/src/plfit/kolmogorov.h new file mode 100644 index 0000000..358e697 --- /dev/null +++ b/src/plfit/kolmogorov.h @@ -0,0 +1,43 @@ +/* kolmogorov.h + * + * Copyright (C) 2010-2011 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __KOLMOGOROV_H__ +#define __KOLMOGOROV_H__ + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +#include + +__BEGIN_DECLS + +double plfit_kolmogorov(double z); +double plfit_ks_test_one_sample_p(double d, size_t n); +double plfit_ks_test_two_sample_p(double d, size_t n1, size_t n2); + +__END_DECLS + +#endif diff --git a/src/plfit/lbfgs.c b/src/plfit/lbfgs.c new file mode 100644 index 0000000..1067d1c --- /dev/null +++ b/src/plfit/lbfgs.c @@ -0,0 +1,1378 @@ +/* + * Limited memory BFGS (L-BFGS). + * + * Copyright (c) 1990, Jorge Nocedal + * Copyright (c) 2007-2010 Naoaki Okazaki + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* $Id: lbfgs.c 65 2010-01-29 12:19:16Z naoaki $ */ + +/* +This library is a C port of the FORTRAN implementation of Limited-memory +Broyden-Fletcher-Goldfarb-Shanno (L-BFGS) method written by Jorge Nocedal. +The original FORTRAN source code is available at: +http://www.ece.northwestern.edu/~nocedal/lbfgs.html + +The L-BFGS algorithm is described in: + - Jorge Nocedal. + Updating Quasi-Newton Matrices with Limited Storage. + Mathematics of Computation, Vol. 35, No. 151, pp. 773--782, 1980. + - Dong C. Liu and Jorge Nocedal. + On the limited memory BFGS method for large scale optimization. + Mathematical Programming B, Vol. 45, No. 3, pp. 503-528, 1989. + +The line search algorithms used in this implementation are described in: + - John E. Dennis and Robert B. Schnabel. + Numerical Methods for Unconstrained Optimization and Nonlinear + Equations, Englewood Cliffs, 1983. + - Jorge J. More and David J. Thuente. + Line search algorithm with guaranteed sufficient decrease. + ACM Transactions on Mathematical Software (TOMS), Vol. 20, No. 3, + pp. 286-307, 1994. + +This library also implements Orthant-Wise Limited-memory Quasi-Newton (OWL-QN) +method presented in: + - Galen Andrew and Jianfeng Gao. + Scalable training of L1-regularized log-linear models. + In Proceedings of the 24th International Conference on Machine + Learning (ICML 2007), pp. 33-40, 2007. + +I would like to thank the original author, Jorge Nocedal, who has been +distributing the effieicnt and explanatory implementation in an open source +licence. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif/*HAVE_CONFIG_H*/ + +#ifndef _MSC_VER +#include +#endif + +#include +#include +#include + +#include "lbfgs.h" + +#ifdef _MSC_VER +#define inline __inline +typedef unsigned int uint32_t; +#endif/*_MSC_VER*/ + +#if defined(USE_SSE) && defined(__SSE2__) && LBFGS_FLOAT == 64 +/* Use SSE2 optimization for 64bit double precision. */ +#include "arithmetic_sse_double.h" + +#elif defined(USE_SSE) && defined(__SSE__) && LBFGS_FLOAT == 32 +/* Use SSE optimization for 32bit float precision. */ +#include "arithmetic_sse_float.h" + +#else +/* No CPU specific optimization. */ +#include "arithmetic_ansi.h" + +#endif + +#define min2(a, b) ((a) <= (b) ? (a) : (b)) +#define max2(a, b) ((a) >= (b) ? (a) : (b)) +#define max3(a, b, c) max2(max2((a), (b)), (c)); + +#define is_aligned(p, bytes) \ + (((uintptr_t)(const void*)(p)) % (bytes) == 0) + +struct tag_callback_data { + int n; + void *instance; + lbfgs_evaluate_t proc_evaluate; + lbfgs_progress_t proc_progress; +}; +typedef struct tag_callback_data callback_data_t; + +struct tag_iteration_data { + lbfgsfloatval_t alpha; + lbfgsfloatval_t *s; /* [n] */ + lbfgsfloatval_t *y; /* [n] */ + lbfgsfloatval_t ys; /* vecdot(y, s) */ +}; +typedef struct tag_iteration_data iteration_data_t; + +static const lbfgs_parameter_t _defparam = { + 6, 1e-5, 0, 1e-5, + 0, LBFGS_LINESEARCH_DEFAULT, 40, + 1e-20, 1e20, 1e-4, 0.9, 0.9, 1.0e-16, + 0.0, 0, -1, +}; + +/* Forward function declarations. */ + +typedef int (*line_search_proc)( + int n, + lbfgsfloatval_t *x, + lbfgsfloatval_t *f, + lbfgsfloatval_t *g, + lbfgsfloatval_t *s, + lbfgsfloatval_t *stp, + const lbfgsfloatval_t* xp, + const lbfgsfloatval_t* gp, + lbfgsfloatval_t *wa, + callback_data_t *cd, + const lbfgs_parameter_t *param + ); + +static int line_search_backtracking( + int n, + lbfgsfloatval_t *x, + lbfgsfloatval_t *f, + lbfgsfloatval_t *g, + lbfgsfloatval_t *s, + lbfgsfloatval_t *stp, + const lbfgsfloatval_t* xp, + const lbfgsfloatval_t* gp, + lbfgsfloatval_t *wa, + callback_data_t *cd, + const lbfgs_parameter_t *param + ); + +static int line_search_backtracking_owlqn( + int n, + lbfgsfloatval_t *x, + lbfgsfloatval_t *f, + lbfgsfloatval_t *g, + lbfgsfloatval_t *s, + lbfgsfloatval_t *stp, + const lbfgsfloatval_t* xp, + const lbfgsfloatval_t* gp, + lbfgsfloatval_t *wp, + callback_data_t *cd, + const lbfgs_parameter_t *param + ); + +static int line_search_morethuente( + int n, + lbfgsfloatval_t *x, + lbfgsfloatval_t *f, + lbfgsfloatval_t *g, + lbfgsfloatval_t *s, + lbfgsfloatval_t *stp, + const lbfgsfloatval_t* xp, + const lbfgsfloatval_t* gp, + lbfgsfloatval_t *wa, + callback_data_t *cd, + const lbfgs_parameter_t *param + ); + +static int update_trial_interval( + lbfgsfloatval_t *x, + lbfgsfloatval_t *fx, + lbfgsfloatval_t *dx, + lbfgsfloatval_t *y, + lbfgsfloatval_t *fy, + lbfgsfloatval_t *dy, + lbfgsfloatval_t *t, + lbfgsfloatval_t *ft, + lbfgsfloatval_t *dt, + const lbfgsfloatval_t tmin, + const lbfgsfloatval_t tmax, + int *brackt + ); + +static lbfgsfloatval_t owlqn_x1norm( + const lbfgsfloatval_t* x, + const int start, + const int n + ); + +static void owlqn_pseudo_gradient( + lbfgsfloatval_t* pg, + const lbfgsfloatval_t* x, + const lbfgsfloatval_t* g, + const int n, + const lbfgsfloatval_t c, + const int start, + const int end + ); + +static void owlqn_project( + lbfgsfloatval_t* d, + const lbfgsfloatval_t* sign, + const int start, + const int end + ); + + +#if defined(USE_SSE) && (defined(__SSE__) || defined(__SSE2__)) +static int round_out_variables(int n) +{ + n += 7; + n /= 8; + n *= 8; + return n; +} +#endif/*defined(USE_SSE)*/ + +lbfgsfloatval_t* lbfgs_malloc(int n) +{ +#if defined(USE_SSE) && (defined(__SSE__) || defined(__SSE2__)) + n = round_out_variables(n); +#endif/*defined(USE_SSE)*/ + return (lbfgsfloatval_t*)vecalloc(sizeof(lbfgsfloatval_t) * (size_t) n); +} + +void lbfgs_free(lbfgsfloatval_t *x) +{ + vecfree(x); +} + +void lbfgs_parameter_init(lbfgs_parameter_t *param) +{ + memcpy(param, &_defparam, sizeof(*param)); +} + +int lbfgs( + int n, + lbfgsfloatval_t *x, + lbfgsfloatval_t *ptr_fx, + lbfgs_evaluate_t proc_evaluate, + lbfgs_progress_t proc_progress, + void *instance, + lbfgs_parameter_t *_param + ) +{ + int ret; + int i, j, k, ls, end, bound; + lbfgsfloatval_t step; + + /* Constant parameters and their default values. */ + lbfgs_parameter_t param = (_param != NULL) ? (*_param) : _defparam; + const int m = param.m; + + lbfgsfloatval_t *xp = NULL; + lbfgsfloatval_t *g = NULL, *gp = NULL, *pg = NULL; + lbfgsfloatval_t *d = NULL, *w = NULL, *pf = NULL; + iteration_data_t *lm = NULL, *it = NULL; + lbfgsfloatval_t ys, yy; + lbfgsfloatval_t xnorm, gnorm, beta; + lbfgsfloatval_t fx = 0.; + lbfgsfloatval_t rate = 0.; + line_search_proc linesearch = line_search_morethuente; + + /* Construct a callback data. */ + callback_data_t cd; + cd.n = n; + cd.instance = instance; + cd.proc_evaluate = proc_evaluate; + cd.proc_progress = proc_progress; + +#if defined(USE_SSE) && (defined(__SSE__) || defined(__SSE2__)) + /* Round out the number of variables. */ + n = round_out_variables(n); +#endif/*defined(USE_SSE)*/ + + /* Check the input parameters for errors. */ + if (n <= 0) { + return LBFGSERR_INVALID_N; + } +#if defined(USE_SSE) && (defined(__SSE__) || defined(__SSE2__)) + if (n % 8 != 0) { + return LBFGSERR_INVALID_N_SSE; + } + if (!is_aligned(x, 16)) { + return LBFGSERR_INVALID_X_SSE; + } +#endif/*defined(USE_SSE)*/ + if (param.epsilon < 0.) { + return LBFGSERR_INVALID_EPSILON; + } + if (param.past < 0) { + return LBFGSERR_INVALID_TESTPERIOD; + } + if (param.delta < 0.) { + return LBFGSERR_INVALID_DELTA; + } + if (param.min_step < 0.) { + return LBFGSERR_INVALID_MINSTEP; + } + if (param.max_step < param.min_step) { + return LBFGSERR_INVALID_MAXSTEP; + } + if (param.ftol < 0.) { + return LBFGSERR_INVALID_FTOL; + } + if (param.linesearch == LBFGS_LINESEARCH_BACKTRACKING_WOLFE || + param.linesearch == LBFGS_LINESEARCH_BACKTRACKING_STRONG_WOLFE) { + if (param.wolfe <= param.ftol || 1. <= param.wolfe) { + return LBFGSERR_INVALID_WOLFE; + } + } + if (param.gtol < 0.) { + return LBFGSERR_INVALID_GTOL; + } + if (param.xtol < 0.) { + return LBFGSERR_INVALID_XTOL; + } + if (param.max_linesearch <= 0) { + return LBFGSERR_INVALID_MAXLINESEARCH; + } + if (param.orthantwise_c < 0.) { + return LBFGSERR_INVALID_ORTHANTWISE; + } + if (param.orthantwise_start < 0 || n < param.orthantwise_start) { + return LBFGSERR_INVALID_ORTHANTWISE_START; + } + if (param.orthantwise_end < 0) { + param.orthantwise_end = n; + } + if (n < param.orthantwise_end) { + return LBFGSERR_INVALID_ORTHANTWISE_END; + } + if (param.orthantwise_c != 0.) { + switch (param.linesearch) { + case LBFGS_LINESEARCH_BACKTRACKING: + linesearch = line_search_backtracking_owlqn; + break; + default: + /* Only the backtracking method is available. */ + return LBFGSERR_INVALID_LINESEARCH; + } + } else { + switch (param.linesearch) { + case LBFGS_LINESEARCH_MORETHUENTE: + linesearch = line_search_morethuente; + break; + case LBFGS_LINESEARCH_BACKTRACKING_ARMIJO: + case LBFGS_LINESEARCH_BACKTRACKING_WOLFE: + case LBFGS_LINESEARCH_BACKTRACKING_STRONG_WOLFE: + linesearch = line_search_backtracking; + break; + default: + return LBFGSERR_INVALID_LINESEARCH; + } + } + + /* Allocate working space. */ + xp = (lbfgsfloatval_t*)vecalloc((size_t) n * sizeof(lbfgsfloatval_t)); + g = (lbfgsfloatval_t*)vecalloc((size_t) n * sizeof(lbfgsfloatval_t)); + gp = (lbfgsfloatval_t*)vecalloc((size_t) n * sizeof(lbfgsfloatval_t)); + d = (lbfgsfloatval_t*)vecalloc((size_t) n * sizeof(lbfgsfloatval_t)); + w = (lbfgsfloatval_t*)vecalloc((size_t) n * sizeof(lbfgsfloatval_t)); + if (xp == NULL || g == NULL || gp == NULL || d == NULL || w == NULL) { + ret = LBFGSERR_OUTOFMEMORY; + goto lbfgs_exit; + } + + if (param.orthantwise_c != 0.) { + /* Allocate working space for OW-LQN. */ + pg = (lbfgsfloatval_t*)vecalloc((size_t) n * sizeof(lbfgsfloatval_t)); + if (pg == NULL) { + ret = LBFGSERR_OUTOFMEMORY; + goto lbfgs_exit; + } + } + + /* Allocate limited memory storage. */ + lm = (iteration_data_t*)vecalloc((size_t) m * sizeof(iteration_data_t)); + if (lm == NULL) { + ret = LBFGSERR_OUTOFMEMORY; + goto lbfgs_exit; + } + + /* Initialize the limited memory. */ + for (i = 0;i < m;++i) { + it = &lm[i]; + it->alpha = 0; + it->ys = 0; + it->s = (lbfgsfloatval_t*)vecalloc((size_t) n * sizeof(lbfgsfloatval_t)); + it->y = (lbfgsfloatval_t*)vecalloc((size_t) n * sizeof(lbfgsfloatval_t)); + if (it->s == NULL || it->y == NULL) { + ret = LBFGSERR_OUTOFMEMORY; + goto lbfgs_exit; + } + } + + /* Allocate an array for storing previous values of the objective function. */ + if (0 < param.past) { + pf = (lbfgsfloatval_t*)vecalloc((size_t) param.past * sizeof(lbfgsfloatval_t)); + } + + /* Evaluate the function value and its gradient. */ + fx = cd.proc_evaluate(cd.instance, x, g, cd.n, 0); + if (0. != param.orthantwise_c) { + /* Compute the L1 norm of the variable and add it to the object value. */ + xnorm = owlqn_x1norm(x, param.orthantwise_start, param.orthantwise_end); + fx += xnorm * param.orthantwise_c; + owlqn_pseudo_gradient( + pg, x, g, n, + param.orthantwise_c, param.orthantwise_start, param.orthantwise_end + ); + } + + /* Store the initial value of the objective function. */ + if (pf != NULL) { + pf[0] = fx; + } + + /* + Compute the direction; + we assume the initial hessian matrix H_0 as the identity matrix. + */ + if (param.orthantwise_c == 0.) { + vecncpy(d, g, n); + } else { + vecncpy(d, pg, n); + } + + /* + Make sure that the initial variables are not a minimizer. + */ + vec2norm(&xnorm, x, n); + if (param.orthantwise_c == 0.) { + vec2norm(&gnorm, g, n); + } else { + vec2norm(&gnorm, pg, n); + } + if (xnorm < 1.0) xnorm = 1.0; + if (gnorm / xnorm <= param.epsilon) { + ret = LBFGS_ALREADY_MINIMIZED; + goto lbfgs_exit; + } + + /* Compute the initial step: + step = 1.0 / sqrt(vecdot(d, d, n)) + */ + vec2norminv(&step, d, n); + + k = 1; + end = 0; + for (;;) { + /* Store the current position and gradient vectors. */ + veccpy(xp, x, n); + veccpy(gp, g, n); + + /* Search for an optimal step. */ + if (param.orthantwise_c == 0.) { + ls = linesearch(n, x, &fx, g, d, &step, xp, gp, w, &cd, ¶m); + } else { + ls = linesearch(n, x, &fx, g, d, &step, xp, pg, w, &cd, ¶m); + owlqn_pseudo_gradient( + pg, x, g, n, + param.orthantwise_c, param.orthantwise_start, param.orthantwise_end + ); + } + if (ls < 0) { + /* Revert to the previous point. */ + veccpy(x, xp, n); + veccpy(g, gp, n); + ret = ls; + goto lbfgs_exit; + } + + /* Compute x and g norms. */ + vec2norm(&xnorm, x, n); + if (param.orthantwise_c == 0.) { + vec2norm(&gnorm, g, n); + } else { + vec2norm(&gnorm, pg, n); + } + + /* Report the progress. */ + if (cd.proc_progress) { + if ((ret = cd.proc_progress(cd.instance, x, g, fx, xnorm, gnorm, step, cd.n, k, ls))) { + goto lbfgs_exit; + } + } + + /* + Convergence test. + The criterion is given by the following formula: + |g(x)| / \max(1, |x|) < \epsilon + */ + if (xnorm < 1.0) xnorm = 1.0; + if (gnorm / xnorm <= param.epsilon) { + /* Convergence. */ + ret = LBFGS_SUCCESS; + break; + } + + /* + Test for stopping criterion. + The criterion is given by the following formula: + (f(past_x) - f(x)) / f(x) < \delta + */ + if (pf != NULL) { + /* We don't test the stopping criterion while k < past. */ + if (param.past <= k) { + /* Compute the relative improvement from the past. */ + rate = (pf[k % param.past] - fx) / fx; + + /* The stopping criterion. */ + if (rate < param.delta) { + ret = LBFGS_STOP; + break; + } + } + + /* Store the current value of the objective function. */ + pf[k % param.past] = fx; + } + + if (param.max_iterations != 0 && param.max_iterations < k+1) { + /* Maximum number of iterations. */ + ret = LBFGSERR_MAXIMUMITERATION; + break; + } + + /* + Update vectors s and y: + s_{k+1} = x_{k+1} - x_{k} = \step * d_{k}. + y_{k+1} = g_{k+1} - g_{k}. + */ + it = &lm[end]; + vecdiff(it->s, x, xp, n); + vecdiff(it->y, g, gp, n); + + /* + Compute scalars ys and yy: + ys = y^t \cdot s = 1 / \rho. + yy = y^t \cdot y. + Notice that yy is used for scaling the hessian matrix H_0 (Cholesky factor). + */ + vecdot(&ys, it->y, it->s, n); + vecdot(&yy, it->y, it->y, n); + it->ys = ys; + + /* + Recursive formula to compute dir = -(H \cdot g). + This is described in page 779 of: + Jorge Nocedal. + Updating Quasi-Newton Matrices with Limited Storage. + Mathematics of Computation, Vol. 35, No. 151, + pp. 773--782, 1980. + */ + bound = (m <= k) ? m : k; + ++k; + end = (end + 1) % m; + + /* Compute the steepest direction. */ + if (param.orthantwise_c == 0.) { + /* Compute the negative of gradients. */ + vecncpy(d, g, n); + } else { + vecncpy(d, pg, n); + } + + j = end; + for (i = 0;i < bound;++i) { + j = (j + m - 1) % m; /* if (--j == -1) j = m-1; */ + it = &lm[j]; + /* \alpha_{j} = \rho_{j} s^{t}_{j} \cdot q_{k+1}. */ + vecdot(&it->alpha, it->s, d, n); + it->alpha /= it->ys; + /* q_{i} = q_{i+1} - \alpha_{i} y_{i}. */ + vecadd(d, it->y, -it->alpha, n); + } + + vecscale(d, ys / yy, n); + + for (i = 0;i < bound;++i) { + it = &lm[j]; + /* \beta_{j} = \rho_{j} y^t_{j} \cdot \gamma_{i}. */ + vecdot(&beta, it->y, d, n); + beta /= it->ys; + /* \gamma_{i+1} = \gamma_{i} + (\alpha_{j} - \beta_{j}) s_{j}. */ + vecadd(d, it->s, it->alpha - beta, n); + j = (j + 1) % m; /* if (++j == m) j = 0; */ + } + + /* + Constrain the search direction for orthant-wise updates. + */ + if (param.orthantwise_c != 0.) { + for (i = param.orthantwise_start;i < param.orthantwise_end;++i) { + if (d[i] * pg[i] >= 0) { + d[i] = 0; + } + } + } + + /* + Now the search direction d is ready. We try step = 1 first. + */ + step = 1.0; + } + +lbfgs_exit: + /* Return the final value of the objective function. */ + if (ptr_fx != NULL) { + *ptr_fx = fx; + } + + vecfree(pf); + + /* Free memory blocks used by this function. */ + if (lm != NULL) { + for (i = 0;i < m;++i) { + vecfree(lm[i].s); + vecfree(lm[i].y); + } + vecfree(lm); + } + vecfree(pg); + vecfree(w); + vecfree(d); + vecfree(gp); + vecfree(g); + vecfree(xp); + + return ret; +} + + + +static int line_search_backtracking( + int n, + lbfgsfloatval_t *x, + lbfgsfloatval_t *f, + lbfgsfloatval_t *g, + lbfgsfloatval_t *s, + lbfgsfloatval_t *stp, + const lbfgsfloatval_t* xp, + const lbfgsfloatval_t* gp, + lbfgsfloatval_t *wp, + callback_data_t *cd, + const lbfgs_parameter_t *param + ) +{ + int count = 0; + lbfgsfloatval_t width, dg; + lbfgsfloatval_t finit, dginit = 0., dgtest; + const lbfgsfloatval_t dec = 0.5, inc = 2.1; + + /* Check the input parameters for errors. */ + if (*stp <= 0.) { + return LBFGSERR_INVALIDPARAMETERS; + } + + /* Compute the initial gradient in the search direction. */ + vecdot(&dginit, g, s, n); + + /* Make sure that s points to a descent direction. */ + if (0 < dginit) { + return LBFGSERR_INCREASEGRADIENT; + } + + /* The initial value of the objective function. */ + finit = *f; + dgtest = param->ftol * dginit; + + for (;;) { + veccpy(x, xp, n); + vecadd(x, s, *stp, n); + + /* Evaluate the function and gradient values. */ + *f = cd->proc_evaluate(cd->instance, x, g, cd->n, *stp); + + ++count; + + if (*f > finit + *stp * dgtest) { + width = dec; + } else { + /* The sufficient decrease condition (Armijo condition). */ + if (param->linesearch == LBFGS_LINESEARCH_BACKTRACKING_ARMIJO) { + /* Exit with the Armijo condition. */ + return count; + } + + /* Check the Wolfe condition. */ + vecdot(&dg, g, s, n); + if (dg < param->wolfe * dginit) { + width = inc; + } else { + if(param->linesearch == LBFGS_LINESEARCH_BACKTRACKING_WOLFE) { + /* Exit with the regular Wolfe condition. */ + return count; + } + + /* Check the strong Wolfe condition. */ + if(dg > -param->wolfe * dginit) { + width = dec; + } else { + /* Exit with the strong Wolfe condition. */ + return count; + } + } + } + + if (*stp < param->min_step) { + /* The step is the minimum value. */ + return LBFGSERR_MINIMUMSTEP; + } + if (*stp > param->max_step) { + /* The step is the maximum value. */ + return LBFGSERR_MAXIMUMSTEP; + } + if (param->max_linesearch <= count) { + /* Maximum number of iteration. */ + return LBFGSERR_MAXIMUMLINESEARCH; + } + + (*stp) *= width; + } +} + + + +static int line_search_backtracking_owlqn( + int n, + lbfgsfloatval_t *x, + lbfgsfloatval_t *f, + lbfgsfloatval_t *g, + lbfgsfloatval_t *s, + lbfgsfloatval_t *stp, + const lbfgsfloatval_t* xp, + const lbfgsfloatval_t* gp, + lbfgsfloatval_t *wp, + callback_data_t *cd, + const lbfgs_parameter_t *param + ) +{ + int i, count = 0; + lbfgsfloatval_t width = 0.5, norm = 0.; + lbfgsfloatval_t finit = *f, dgtest; + + /* Check the input parameters for errors. */ + if (*stp <= 0.) { + return LBFGSERR_INVALIDPARAMETERS; + } + + /* Choose the orthant for the new point. */ + for (i = 0;i < n;++i) { + wp[i] = (xp[i] == 0.) ? -gp[i] : xp[i]; + } + + for (;;) { + /* Update the current point. */ + veccpy(x, xp, n); + vecadd(x, s, *stp, n); + + /* The current point is projected onto the orthant. */ + owlqn_project(x, wp, param->orthantwise_start, param->orthantwise_end); + + /* Evaluate the function and gradient values. */ + *f = cd->proc_evaluate(cd->instance, x, g, cd->n, *stp); + + /* Compute the L1 norm of the variables and add it to the object value. */ + norm = owlqn_x1norm(x, param->orthantwise_start, param->orthantwise_end); + *f += norm * param->orthantwise_c; + + ++count; + + dgtest = 0.; + for (i = 0;i < n;++i) { + dgtest += (x[i] - xp[i]) * gp[i]; + } + + if (*f <= finit + param->ftol * dgtest) { + /* The sufficient decrease condition. */ + return count; + } + + if (*stp < param->min_step) { + /* The step is the minimum value. */ + return LBFGSERR_MINIMUMSTEP; + } + if (*stp > param->max_step) { + /* The step is the maximum value. */ + return LBFGSERR_MAXIMUMSTEP; + } + if (param->max_linesearch <= count) { + /* Maximum number of iteration. */ + return LBFGSERR_MAXIMUMLINESEARCH; + } + + (*stp) *= width; + } +} + + + +static int line_search_morethuente( + int n, + lbfgsfloatval_t *x, + lbfgsfloatval_t *f, + lbfgsfloatval_t *g, + lbfgsfloatval_t *s, + lbfgsfloatval_t *stp, + const lbfgsfloatval_t* xp, + const lbfgsfloatval_t* gp, + lbfgsfloatval_t *wa, + callback_data_t *cd, + const lbfgs_parameter_t *param + ) +{ + int count = 0; + int brackt, stage1, uinfo = 0; + lbfgsfloatval_t dg; + lbfgsfloatval_t stx, fx, dgx; + lbfgsfloatval_t sty, fy, dgy; + lbfgsfloatval_t fxm, dgxm, fym, dgym, fm, dgm; + lbfgsfloatval_t finit, ftest1, dginit, dgtest; + lbfgsfloatval_t width, prev_width; + lbfgsfloatval_t stmin, stmax; + + /* Check the input parameters for errors. */ + if (*stp <= 0.) { + return LBFGSERR_INVALIDPARAMETERS; + } + + /* Compute the initial gradient in the search direction. */ + vecdot(&dginit, g, s, n); + + /* Make sure that s points to a descent direction. */ + if (0 < dginit) { + return LBFGSERR_INCREASEGRADIENT; + } + + /* Initialize local variables. */ + brackt = 0; + stage1 = 1; + finit = *f; + dgtest = param->ftol * dginit; + width = param->max_step - param->min_step; + prev_width = 2.0 * width; + + /* + The variables stx, fx, dgx contain the values of the step, + function, and directional derivative at the best step. + The variables sty, fy, dgy contain the value of the step, + function, and derivative at the other endpoint of + the interval of uncertainty. + The variables stp, f, dg contain the values of the step, + function, and derivative at the current step. + */ + stx = sty = 0.; + fx = fy = finit; + dgx = dgy = dginit; + + for (;;) { + /* + Set the minimum and maximum steps to correspond to the + present interval of uncertainty. + */ + if (brackt) { + stmin = min2(stx, sty); + stmax = max2(stx, sty); + } else { + stmin = stx; + stmax = *stp + 4.0 * (*stp - stx); + } + + /* Clip the step in the range of [stpmin, stpmax]. */ + if (*stp < param->min_step) *stp = param->min_step; + if (param->max_step < *stp) *stp = param->max_step; + + /* + If an unusual termination is to occur then let + stp be the lowest point obtained so far. + */ + if ((brackt && ((*stp <= stmin || stmax <= *stp) || param->max_linesearch <= count + 1 || uinfo != 0)) || (brackt && (stmax - stmin <= param->xtol * stmax))) { + *stp = stx; + } + + /* + Compute the current value of x: + x <- x + (*stp) * s. + */ + veccpy(x, xp, n); + vecadd(x, s, *stp, n); + + /* Evaluate the function and gradient values. */ + *f = cd->proc_evaluate(cd->instance, x, g, cd->n, *stp); + vecdot(&dg, g, s, n); + + ftest1 = finit + *stp * dgtest; + ++count; + + /* Test for errors and convergence. */ + if (brackt && ((*stp <= stmin || stmax <= *stp) || uinfo != 0)) { + /* Rounding errors prevent further progress. */ + return LBFGSERR_ROUNDING_ERROR; + } + if (*stp == param->max_step && *f <= ftest1 && dg <= dgtest) { + /* The step is the maximum value. */ + return LBFGSERR_MAXIMUMSTEP; + } + if (*stp == param->min_step && (ftest1 < *f || dgtest <= dg)) { + /* The step is the minimum value. */ + return LBFGSERR_MINIMUMSTEP; + } + if (brackt && (stmax - stmin) <= param->xtol * stmax) { + /* Relative width of the interval of uncertainty is at most xtol. */ + return LBFGSERR_WIDTHTOOSMALL; + } + if (param->max_linesearch <= count) { + /* Maximum number of iteration. */ + return LBFGSERR_MAXIMUMLINESEARCH; + } + if (*f <= ftest1 && fabs(dg) <= param->gtol * (-dginit)) { + /* The sufficient decrease condition and the directional derivative condition hold. */ + return count; + } + + /* + In the first stage we seek a step for which the modified + function has a nonpositive value and nonnegative derivative. + */ + if (stage1 && *f <= ftest1 && min2(param->ftol, param->gtol) * dginit <= dg) { + stage1 = 0; + } + + /* + A modified function is used to predict the step only if + we have not obtained a step for which the modified + function has a nonpositive function value and nonnegative + derivative, and if a lower function value has been + obtained but the decrease is not sufficient. + */ + if (stage1 && ftest1 < *f && *f <= fx) { + /* Define the modified function and derivative values. */ + fm = *f - *stp * dgtest; + fxm = fx - stx * dgtest; + fym = fy - sty * dgtest; + dgm = dg - dgtest; + dgxm = dgx - dgtest; + dgym = dgy - dgtest; + + /* + Call update_trial_interval() to update the interval of + uncertainty and to compute the new step. + */ + uinfo = update_trial_interval( + &stx, &fxm, &dgxm, + &sty, &fym, &dgym, + stp, &fm, &dgm, + stmin, stmax, &brackt + ); + + /* Reset the function and gradient values for f. */ + fx = fxm + stx * dgtest; + fy = fym + sty * dgtest; + dgx = dgxm + dgtest; + dgy = dgym + dgtest; + } else { + /* + Call update_trial_interval() to update the interval of + uncertainty and to compute the new step. + */ + uinfo = update_trial_interval( + &stx, &fx, &dgx, + &sty, &fy, &dgy, + stp, f, &dg, + stmin, stmax, &brackt + ); + } + + /* + Force a sufficient decrease in the interval of uncertainty. + */ + if (brackt) { + if (0.66 * prev_width <= fabs(sty - stx)) { + *stp = stx + 0.5 * (sty - stx); + } + prev_width = width; + width = fabs(sty - stx); + } + } + + return LBFGSERR_LOGICERROR; +} + + + +/** + * Define the local variables for computing minimizers. + */ +#define USES_MINIMIZER \ + lbfgsfloatval_t a, d, gamma, theta, p, q, r, s; + +/** + * Find a minimizer of an interpolated cubic function. + * @param cm The minimizer of the interpolated cubic. + * @param u The value of one point, u. + * @param fu The value of f(u). + * @param du The value of f'(u). + * @param v The value of another point, v. + * @param fv The value of f(v). + * @param du The value of f'(v). + */ +#define CUBIC_MINIMIZER(cm, u, fu, du, v, fv, dv) \ + d = (v) - (u); \ + theta = ((fu) - (fv)) * 3 / d + (du) + (dv); \ + p = fabs(theta); \ + q = fabs(du); \ + r = fabs(dv); \ + s = max3(p, q, r); \ + /* gamma = s*sqrt((theta/s)**2 - (du/s) * (dv/s)) */ \ + a = theta / s; \ + gamma = s * sqrt(a * a - ((du) / s) * ((dv) / s)); \ + if ((v) < (u)) gamma = -gamma; \ + p = gamma - (du) + theta; \ + q = gamma - (du) + gamma + (dv); \ + r = p / q; \ + (cm) = (u) + r * d; + +/** + * Find a minimizer of an interpolated cubic function. + * @param cm The minimizer of the interpolated cubic. + * @param u The value of one point, u. + * @param fu The value of f(u). + * @param du The value of f'(u). + * @param v The value of another point, v. + * @param fv The value of f(v). + * @param du The value of f'(v). + * @param xmin The maximum value. + * @param xmin The minimum value. + */ +#define CUBIC_MINIMIZER2(cm, u, fu, du, v, fv, dv, xmin, xmax) \ + d = (v) - (u); \ + theta = ((fu) - (fv)) * 3 / d + (du) + (dv); \ + p = fabs(theta); \ + q = fabs(du); \ + r = fabs(dv); \ + s = max3(p, q, r); \ + /* gamma = s*sqrt((theta/s)**2 - (du/s) * (dv/s)) */ \ + a = theta / s; \ + gamma = s * sqrt(max2(0, a * a - ((du) / s) * ((dv) / s))); \ + if ((u) < (v)) gamma = -gamma; \ + p = gamma - (dv) + theta; \ + q = gamma - (dv) + gamma + (du); \ + r = p / q; \ + if (r < 0. && gamma != 0.) { \ + (cm) = (v) - r * d; \ + } else if (a < 0) { \ + (cm) = (xmax); \ + } else { \ + (cm) = (xmin); \ + } + +/** + * Find a minimizer of an interpolated quadratic function. + * @param qm The minimizer of the interpolated quadratic. + * @param u The value of one point, u. + * @param fu The value of f(u). + * @param du The value of f'(u). + * @param v The value of another point, v. + * @param fv The value of f(v). + */ +#define QUARD_MINIMIZER(qm, u, fu, du, v, fv) \ + a = (v) - (u); \ + (qm) = (u) + (du) / (((fu) - (fv)) / a + (du)) / 2 * a; + +/** + * Find a minimizer of an interpolated quadratic function. + * @param qm The minimizer of the interpolated quadratic. + * @param u The value of one point, u. + * @param du The value of f'(u). + * @param v The value of another point, v. + * @param dv The value of f'(v). + */ +#define QUARD_MINIMIZER2(qm, u, du, v, dv) \ + a = (u) - (v); \ + (qm) = (v) + (dv) / ((dv) - (du)) * a; + +/** + * Update a safeguarded trial value and interval for line search. + * + * The parameter x represents the step with the least function value. + * The parameter t represents the current step. This function assumes + * that the derivative at the point of x in the direction of the step. + * If the bracket is set to true, the minimizer has been bracketed in + * an interval of uncertainty with endpoints between x and y. + * + * @param x The pointer to the value of one endpoint. + * @param fx The pointer to the value of f(x). + * @param dx The pointer to the value of f'(x). + * @param y The pointer to the value of another endpoint. + * @param fy The pointer to the value of f(y). + * @param dy The pointer to the value of f'(y). + * @param t The pointer to the value of the trial value, t. + * @param ft The pointer to the value of f(t). + * @param dt The pointer to the value of f'(t). + * @param tmin The minimum value for the trial value, t. + * @param tmax The maximum value for the trial value, t. + * @param brackt The pointer to the predicate if the trial value is + * bracketed. + * @retval int Status value. Zero indicates a normal termination. + * + * @see + * Jorge J. More and David J. Thuente. Line search algorithm with + * guaranteed sufficient decrease. ACM Transactions on Mathematical + * Software (TOMS), Vol 20, No 3, pp. 286-307, 1994. + */ +static int update_trial_interval( + lbfgsfloatval_t *x, + lbfgsfloatval_t *fx, + lbfgsfloatval_t *dx, + lbfgsfloatval_t *y, + lbfgsfloatval_t *fy, + lbfgsfloatval_t *dy, + lbfgsfloatval_t *t, + lbfgsfloatval_t *ft, + lbfgsfloatval_t *dt, + const lbfgsfloatval_t tmin, + const lbfgsfloatval_t tmax, + int *brackt + ) +{ + int bound; + int dsign = fsigndiff(dt, dx); + lbfgsfloatval_t mc; /* minimizer of an interpolated cubic. */ + lbfgsfloatval_t mq; /* minimizer of an interpolated quadratic. */ + lbfgsfloatval_t newt; /* new trial value. */ + USES_MINIMIZER; /* for CUBIC_MINIMIZER and QUARD_MINIMIZER. */ + + /* Check the input parameters for errors. */ + if (*brackt) { + if (*t <= min2(*x, *y) || max2(*x, *y) <= *t) { + /* The trival value t is out of the interval. */ + return LBFGSERR_OUTOFINTERVAL; + } + if (0. <= *dx * (*t - *x)) { + /* The function must decrease from x. */ + return LBFGSERR_INCREASEGRADIENT; + } + if (tmax < tmin) { + /* Incorrect tmin and tmax specified. */ + return LBFGSERR_INCORRECT_TMINMAX; + } + } + + /* + Trial value selection. + */ + if (*fx < *ft) { + /* + Case 1: a higher function value. + The minimum is brackt. If the cubic minimizer is closer + to x than the quadratic one, the cubic one is taken, else + the average of the minimizers is taken. + */ + *brackt = 1; + bound = 1; + CUBIC_MINIMIZER(mc, *x, *fx, *dx, *t, *ft, *dt); + QUARD_MINIMIZER(mq, *x, *fx, *dx, *t, *ft); + if (fabs(mc - *x) < fabs(mq - *x)) { + newt = mc; + } else { + newt = mc + 0.5 * (mq - mc); + } + } else if (dsign) { + /* + Case 2: a lower function value and derivatives of + opposite sign. The minimum is brackt. If the cubic + minimizer is closer to x than the quadratic (secant) one, + the cubic one is taken, else the quadratic one is taken. + */ + *brackt = 1; + bound = 0; + CUBIC_MINIMIZER(mc, *x, *fx, *dx, *t, *ft, *dt); + QUARD_MINIMIZER2(mq, *x, *dx, *t, *dt); + if (fabs(mc - *t) > fabs(mq - *t)) { + newt = mc; + } else { + newt = mq; + } + } else if (fabs(*dt) < fabs(*dx)) { + /* + Case 3: a lower function value, derivatives of the + same sign, and the magnitude of the derivative decreases. + The cubic minimizer is only used if the cubic tends to + infinity in the direction of the minimizer or if the minimum + of the cubic is beyond t. Otherwise the cubic minimizer is + defined to be either tmin or tmax. The quadratic (secant) + minimizer is also computed and if the minimum is brackt + then the the minimizer closest to x is taken, else the one + farthest away is taken. + */ + bound = 1; + CUBIC_MINIMIZER2(mc, *x, *fx, *dx, *t, *ft, *dt, tmin, tmax); + QUARD_MINIMIZER2(mq, *x, *dx, *t, *dt); + if (*brackt) { + if (fabs(*t - mc) < fabs(*t - mq)) { + newt = mc; + } else { + newt = mq; + } + } else { + if (fabs(*t - mc) > fabs(*t - mq)) { + newt = mc; + } else { + newt = mq; + } + } + } else { + /* + Case 4: a lower function value, derivatives of the + same sign, and the magnitude of the derivative does + not decrease. If the minimum is not brackt, the step + is either tmin or tmax, else the cubic minimizer is taken. + */ + bound = 0; + if (*brackt) { + CUBIC_MINIMIZER(newt, *t, *ft, *dt, *y, *fy, *dy); + } else if (*x < *t) { + newt = tmax; + } else { + newt = tmin; + } + } + + /* + Update the interval of uncertainty. This update does not + depend on the new step or the case analysis above. + + - Case a: if f(x) < f(t), + x <- x, y <- t. + - Case b: if f(t) <= f(x) && f'(t)*f'(x) > 0, + x <- t, y <- y. + - Case c: if f(t) <= f(x) && f'(t)*f'(x) < 0, + x <- t, y <- x. + */ + if (*fx < *ft) { + /* Case a */ + *y = *t; + *fy = *ft; + *dy = *dt; + } else { + /* Case c */ + if (dsign) { + *y = *x; + *fy = *fx; + *dy = *dx; + } + /* Cases b and c */ + *x = *t; + *fx = *ft; + *dx = *dt; + } + + /* Clip the new trial value in [tmin, tmax]. */ + if (tmax < newt) newt = tmax; + if (newt < tmin) newt = tmin; + + /* + Redefine the new trial value if it is close to the upper bound + of the interval. + */ + if (*brackt && bound) { + mq = *x + 0.66 * (*y - *x); + if (*x < *y) { + if (mq < newt) newt = mq; + } else { + if (newt < mq) newt = mq; + } + } + + /* Return the new trial value. */ + *t = newt; + return 0; +} + + + + + +static lbfgsfloatval_t owlqn_x1norm( + const lbfgsfloatval_t* x, + const int start, + const int n + ) +{ + int i; + lbfgsfloatval_t norm = 0.; + + for (i = start;i < n;++i) { + norm += fabs(x[i]); + } + + return norm; +} + +static void owlqn_pseudo_gradient( + lbfgsfloatval_t* pg, + const lbfgsfloatval_t* x, + const lbfgsfloatval_t* g, + const int n, + const lbfgsfloatval_t c, + const int start, + const int end + ) +{ + int i; + + /* Compute the negative of gradients. */ + for (i = 0;i < start;++i) { + pg[i] = g[i]; + } + + /* Compute the psuedo-gradients. */ + for (i = start;i < end;++i) { + if (x[i] < 0.) { + /* Differentiable. */ + pg[i] = g[i] - c; + } else if (0. < x[i]) { + /* Differentiable. */ + pg[i] = g[i] + c; + } else { + if (g[i] < -c) { + /* Take the right partial derivative. */ + pg[i] = g[i] + c; + } else if (c < g[i]) { + /* Take the left partial derivative. */ + pg[i] = g[i] - c; + } else { + pg[i] = 0.; + } + } + } + + for (i = end;i < n;++i) { + pg[i] = g[i]; + } +} + +static void owlqn_project( + lbfgsfloatval_t* d, + const lbfgsfloatval_t* sign, + const int start, + const int end + ) +{ + int i; + + for (i = start;i < end;++i) { + if (d[i] * sign[i] <= 0) { + d[i] = 0; + } + } +} diff --git a/src/plfit/lbfgs.h b/src/plfit/lbfgs.h new file mode 100644 index 0000000..f26ae87 --- /dev/null +++ b/src/plfit/lbfgs.h @@ -0,0 +1,736 @@ +/* + * C library of Limited memory BFGS (L-BFGS). + * + * Copyright (c) 1990, Jorge Nocedal + * Copyright (c) 2007-2010 Naoaki Okazaki + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* $Id: lbfgs.h 65 2010-01-29 12:19:16Z naoaki $ */ + +#ifndef __LBFGS_H__ +#define __LBFGS_H__ + +#ifdef __cplusplus +extern "C" { +#endif/*__cplusplus*/ + +/* + * The default precision of floating point values is 64bit (double). + */ +#ifndef LBFGS_FLOAT +#define LBFGS_FLOAT 64 +#endif/*LBFGS_FLOAT*/ + +/* + * Activate optimization routines for IEEE754 floating point values. + */ +#ifndef LBFGS_IEEE_FLOAT +#define LBFGS_IEEE_FLOAT 1 +#endif/*LBFGS_IEEE_FLOAT*/ + +#if LBFGS_FLOAT == 32 +typedef float lbfgsfloatval_t; + +#elif LBFGS_FLOAT == 64 +typedef double lbfgsfloatval_t; + +#else +#error "libLBFGS supports single (float; LBFGS_FLOAT = 32) or double (double; LBFGS_FLOAT=64) precision only." + +#endif + + +/** + * \addtogroup liblbfgs_api libLBFGS API + * @{ + * + * The libLBFGS API. + */ + +/** + * Return values of lbfgs(). + * + * Roughly speaking, a negative value indicates an error. + */ +enum { + /** L-BFGS reaches convergence. */ + LBFGS_SUCCESS = 0, + LBFGS_CONVERGENCE = 0, + LBFGS_STOP, + /** The initial variables already minimize the objective function. */ + LBFGS_ALREADY_MINIMIZED, + + /** Unknown error. */ + LBFGSERR_UNKNOWNERROR = -1024, + /** Logic error. */ + LBFGSERR_LOGICERROR, + /** Insufficient memory. */ + LBFGSERR_OUTOFMEMORY, + /** The minimization process has been canceled. */ + LBFGSERR_CANCELED, + /** Invalid number of variables specified. */ + LBFGSERR_INVALID_N, + /** Invalid number of variables (for SSE) specified. */ + LBFGSERR_INVALID_N_SSE, + /** The array x must be aligned to 16 (for SSE). */ + LBFGSERR_INVALID_X_SSE, + /** Invalid parameter lbfgs_parameter_t::epsilon specified. */ + LBFGSERR_INVALID_EPSILON, + /** Invalid parameter lbfgs_parameter_t::past specified. */ + LBFGSERR_INVALID_TESTPERIOD, + /** Invalid parameter lbfgs_parameter_t::delta specified. */ + LBFGSERR_INVALID_DELTA, + /** Invalid parameter lbfgs_parameter_t::linesearch specified. */ + LBFGSERR_INVALID_LINESEARCH, + /** Invalid parameter lbfgs_parameter_t::max_step specified. */ + LBFGSERR_INVALID_MINSTEP, + /** Invalid parameter lbfgs_parameter_t::max_step specified. */ + LBFGSERR_INVALID_MAXSTEP, + /** Invalid parameter lbfgs_parameter_t::ftol specified. */ + LBFGSERR_INVALID_FTOL, + /** Invalid parameter lbfgs_parameter_t::wolfe specified. */ + LBFGSERR_INVALID_WOLFE, + /** Invalid parameter lbfgs_parameter_t::gtol specified. */ + LBFGSERR_INVALID_GTOL, + /** Invalid parameter lbfgs_parameter_t::xtol specified. */ + LBFGSERR_INVALID_XTOL, + /** Invalid parameter lbfgs_parameter_t::max_linesearch specified. */ + LBFGSERR_INVALID_MAXLINESEARCH, + /** Invalid parameter lbfgs_parameter_t::orthantwise_c specified. */ + LBFGSERR_INVALID_ORTHANTWISE, + /** Invalid parameter lbfgs_parameter_t::orthantwise_start specified. */ + LBFGSERR_INVALID_ORTHANTWISE_START, + /** Invalid parameter lbfgs_parameter_t::orthantwise_end specified. */ + LBFGSERR_INVALID_ORTHANTWISE_END, + /** The line-search step went out of the interval of uncertainty. */ + LBFGSERR_OUTOFINTERVAL, + /** A logic error occurred; alternatively, the interval of uncertainty + became too small. */ + LBFGSERR_INCORRECT_TMINMAX, + /** A rounding error occurred; alternatively, no line-search step + satisfies the sufficient decrease and curvature conditions. */ + LBFGSERR_ROUNDING_ERROR, + /** The line-search step became smaller than lbfgs_parameter_t::min_step. */ + LBFGSERR_MINIMUMSTEP, + /** The line-search step became larger than lbfgs_parameter_t::max_step. */ + LBFGSERR_MAXIMUMSTEP, + /** The line-search routine reaches the maximum number of evaluations. */ + LBFGSERR_MAXIMUMLINESEARCH, + /** The algorithm routine reaches the maximum number of iterations. */ + LBFGSERR_MAXIMUMITERATION, + /** Relative width of the interval of uncertainty is at most + lbfgs_parameter_t::xtol. */ + LBFGSERR_WIDTHTOOSMALL, + /** A logic error (negative line-search step) occurred. */ + LBFGSERR_INVALIDPARAMETERS, + /** The current search direction increases the objective function value. */ + LBFGSERR_INCREASEGRADIENT, +}; + +/** + * Line search algorithms. + */ +enum { + /** The default algorithm (MoreThuente method). */ + LBFGS_LINESEARCH_DEFAULT = 0, + /** MoreThuente method proposd by More and Thuente. */ + LBFGS_LINESEARCH_MORETHUENTE = 0, + /** + * Backtracking method with the Armijo condition. + * The backtracking method finds the step length such that it satisfies + * the sufficient decrease (Armijo) condition, + * - f(x + a * d) <= f(x) + lbfgs_parameter_t::ftol * a * g(x)^T d, + * + * where x is the current point, d is the current search direction, and + * a is the step length. + */ + LBFGS_LINESEARCH_BACKTRACKING_ARMIJO = 1, + /** The backtracking method with the defualt (regular Wolfe) condition. */ + LBFGS_LINESEARCH_BACKTRACKING = 2, + /** + * Backtracking method with regular Wolfe condition. + * The backtracking method finds the step length such that it satisfies + * both the Armijo condition (LBFGS_LINESEARCH_BACKTRACKING_ARMIJO) + * and the curvature condition, + * - g(x + a * d)^T d >= lbfgs_parameter_t::wolfe * g(x)^T d, + * + * where x is the current point, d is the current search direction, and + * a is the step length. + */ + LBFGS_LINESEARCH_BACKTRACKING_WOLFE = 2, + /** + * Backtracking method with strong Wolfe condition. + * The backtracking method finds the step length such that it satisfies + * both the Armijo condition (LBFGS_LINESEARCH_BACKTRACKING_ARMIJO) + * and the following condition, + * - |g(x + a * d)^T d| <= lbfgs_parameter_t::wolfe * |g(x)^T d|, + * + * where x is the current point, d is the current search direction, and + * a is the step length. + */ + LBFGS_LINESEARCH_BACKTRACKING_STRONG_WOLFE = 3, +}; + +/** + * L-BFGS optimization parameters. + * Call lbfgs_parameter_init() function to initialize parameters to the + * default values. + */ +typedef struct { + /** + * The number of corrections to approximate the inverse hessian matrix. + * The L-BFGS routine stores the computation results of previous \ref m + * iterations to approximate the inverse hessian matrix of the current + * iteration. This parameter controls the size of the limited memories + * (corrections). The default value is \c 6. Values less than \c 3 are + * not recommended. Large values will result in excessive computing time. + */ + int m; + + /** + * Epsilon for convergence test. + * This parameter determines the accuracy with which the solution is to + * be found. A minimization terminates when + * ||g|| < \ref epsilon * max(1, ||x||), + * where ||.|| denotes the Euclidean (L2) norm. The default value is + * \c 1e-5. + */ + lbfgsfloatval_t epsilon; + + /** + * Distance for delta-based convergence test. + * This parameter determines the distance, in iterations, to compute + * the rate of decrease of the objective function. If the value of this + * parameter is zero, the library does not perform the delta-based + * convergence test. The default value is \c 0. + */ + int past; + + /** + * Delta for convergence test. + * This parameter determines the minimum rate of decrease of the + * objective function. The library stops iterations when the + * following condition is met: + * (f' - f) / f < \ref delta, + * where f' is the objective value of \ref past iterations ago, and f is + * the objective value of the current iteration. + * The default value is \c 0. + */ + lbfgsfloatval_t delta; + + /** + * The maximum number of iterations. + * The lbfgs() function terminates an optimization process with + * ::LBFGSERR_MAXIMUMITERATION status code when the iteration count + * exceedes this parameter. Setting this parameter to zero continues an + * optimization process until a convergence or error. The default value + * is \c 0. + */ + int max_iterations; + + /** + * The line search algorithm. + * This parameter specifies a line search algorithm to be used by the + * L-BFGS routine. + */ + int linesearch; + + /** + * The maximum number of trials for the line search. + * This parameter controls the number of function and gradients evaluations + * per iteration for the line search routine. The default value is \c 20. + */ + int max_linesearch; + + /** + * The minimum step of the line search routine. + * The default value is \c 1e-20. This value need not be modified unless + * the exponents are too large for the machine being used, or unless the + * problem is extremely badly scaled (in which case the exponents should + * be increased). + */ + lbfgsfloatval_t min_step; + + /** + * The maximum step of the line search. + * The default value is \c 1e+20. This value need not be modified unless + * the exponents are too large for the machine being used, or unless the + * problem is extremely badly scaled (in which case the exponents should + * be increased). + */ + lbfgsfloatval_t max_step; + + /** + * A parameter to control the accuracy of the line search routine. + * The default value is \c 1e-4. This parameter should be greater + * than zero and smaller than \c 0.5. + */ + lbfgsfloatval_t ftol; + + /** + * A coefficient for the Wolfe condition. + * This parameter is valid only when the backtracking line-search + * algorithm is used with the Wolfe condition, + * ::LBFGS_LINESEARCH_BACKTRACKING_STRONG_WOLFE or + * ::LBFGS_LINESEARCH_BACKTRACKING_WOLFE . + * The default value is \c 0.9. This parameter should be greater + * the \ref ftol parameter and smaller than \c 1.0. + */ + lbfgsfloatval_t wolfe; + + /** + * A parameter to control the accuracy of the line search routine. + * The default value is \c 0.9. If the function and gradient + * evaluations are inexpensive with respect to the cost of the + * iteration (which is sometimes the case when solving very large + * problems) it may be advantageous to set this parameter to a small + * value. A typical small value is \c 0.1. This parameter shuold be + * greater than the \ref ftol parameter (\c 1e-4) and smaller than + * \c 1.0. + */ + lbfgsfloatval_t gtol; + + /** + * The machine precision for floating-point values. + * This parameter must be a positive value set by a client program to + * estimate the machine precision. The line search routine will terminate + * with the status code (::LBFGSERR_ROUNDING_ERROR) if the relative width + * of the interval of uncertainty is less than this parameter. + */ + lbfgsfloatval_t xtol; + + /** + * Coeefficient for the L1 norm of variables. + * This parameter should be set to zero for standard minimization + * problems. Setting this parameter to a positive value activates + * Orthant-Wise Limited-memory Quasi-Newton (OWL-QN) method, which + * minimizes the objective function F(x) combined with the L1 norm |x| + * of the variables, {F(x) + C |x|}. This parameter is the coeefficient + * for the |x|, i.e., C. As the L1 norm |x| is not differentiable at + * zero, the library modifies function and gradient evaluations from + * a client program suitably; a client program thus have only to return + * the function value F(x) and gradients G(x) as usual. The default value + * is zero. + */ + lbfgsfloatval_t orthantwise_c; + + /** + * Start index for computing L1 norm of the variables. + * This parameter is valid only for OWL-QN method + * (i.e., \ref orthantwise_c != 0). This parameter b (0 <= b < N) + * specifies the index number from which the library computes the + * L1 norm of the variables x, + * |x| := |x_{b}| + |x_{b+1}| + ... + |x_{N}| . + * In other words, variables x_1, ..., x_{b-1} are not used for + * computing the L1 norm. Setting b (0 < b < N), one can protect + * variables, x_1, ..., x_{b-1} (e.g., a bias term of logistic + * regression) from being regularized. The default value is zero. + */ + int orthantwise_start; + + /** + * End index for computing L1 norm of the variables. + * This parameter is valid only for OWL-QN method + * (i.e., \ref orthantwise_c != 0). This parameter e (0 < e <= N) + * specifies the index number at which the library stops computing the + * L1 norm of the variables x, + */ + int orthantwise_end; +} lbfgs_parameter_t; + + +/** + * Callback interface to provide objective function and gradient evaluations. + * + * The lbfgs() function call this function to obtain the values of objective + * function and its gradients when needed. A client program must implement + * this function to evaluate the values of the objective function and its + * gradients, given current values of variables. + * + * @param instance The user data sent for lbfgs() function by the client. + * @param x The current values of variables. + * @param g The gradient vector. The callback function must compute + * the gradient values for the current variables. + * @param n The number of variables. + * @param step The current step of the line search routine. + * @retval lbfgsfloatval_t The value of the objective function for the current + * variables. + */ +typedef lbfgsfloatval_t (*lbfgs_evaluate_t)( + void *instance, + const lbfgsfloatval_t *x, + lbfgsfloatval_t *g, + const int n, + const lbfgsfloatval_t step + ); + +/** + * Callback interface to receive the progress of the optimization process. + * + * The lbfgs() function call this function for each iteration. Implementing + * this function, a client program can store or display the current progress + * of the optimization process. + * + * @param instance The user data sent for lbfgs() function by the client. + * @param x The current values of variables. + * @param g The current gradient values of variables. + * @param fx The current value of the objective function. + * @param xnorm The Euclidean norm of the variables. + * @param gnorm The Euclidean norm of the gradients. + * @param step The line-search step used for this iteration. + * @param n The number of variables. + * @param k The iteration count. + * @param ls The number of evaluations called for this iteration. + * @retval int Zero to continue the optimization process. Returning a + * non-zero value will cancel the optimization process. + */ +typedef int (*lbfgs_progress_t)( + void *instance, + const lbfgsfloatval_t *x, + const lbfgsfloatval_t *g, + const lbfgsfloatval_t fx, + const lbfgsfloatval_t xnorm, + const lbfgsfloatval_t gnorm, + const lbfgsfloatval_t step, + int n, + int k, + int ls + ); + +/* +A user must implement a function compatible with ::lbfgs_evaluate_t (evaluation +callback) and pass the pointer to the callback function to lbfgs() arguments. +Similarly, a user can implement a function compatible with ::lbfgs_progress_t +(progress callback) to obtain the current progress (e.g., variables, function +value, ||G||, etc) and to cancel the iteration process if necessary. +Implementation of a progress callback is optional: a user can pass \c NULL if +progress notification is not necessary. + +In addition, a user must preserve two requirements: + - The number of variables must be multiples of 16 (this is not 4). + - The memory block of variable array ::x must be aligned to 16. + +This algorithm terminates an optimization +when: + + ||G|| < \epsilon \cdot \max(1, ||x||) . + +In this formula, ||.|| denotes the Euclidean norm. +*/ + +/** + * Start a L-BFGS optimization. + * + * @param n The number of variables. + * @param x The array of variables. A client program can set + * default values for the optimization and receive the + * optimization result through this array. This array + * must be allocated by ::lbfgs_malloc function + * for libLBFGS built with SSE/SSE2 optimization routine + * enabled. The library built without SSE/SSE2 + * optimization does not have such a requirement. + * @param ptr_fx The pointer to the variable that receives the final + * value of the objective function for the variables. + * This argument can be set to \c NULL if the final + * value of the objective function is unnecessary. + * @param proc_evaluate The callback function to provide function and + * gradient evaluations given a current values of + * variables. A client program must implement a + * callback function compatible with \ref + * lbfgs_evaluate_t and pass the pointer to the + * callback function. + * @param proc_progress The callback function to receive the progress + * (the number of iterations, the current value of + * the objective function) of the minimization + * process. This argument can be set to \c NULL if + * a progress report is unnecessary. + * @param instance A user data for the client program. The callback + * functions will receive the value of this argument. + * @param param The pointer to a structure representing parameters for + * L-BFGS optimization. A client program can set this + * parameter to \c NULL to use the default parameters. + * Call lbfgs_parameter_init() function to fill a + * structure with the default values. + * @retval int The status code. This function returns zero if the + * minimization process terminates without an error. A + * non-zero value indicates an error. + */ +int lbfgs( + int n, + lbfgsfloatval_t *x, + lbfgsfloatval_t *ptr_fx, + lbfgs_evaluate_t proc_evaluate, + lbfgs_progress_t proc_progress, + void *instance, + lbfgs_parameter_t *param + ); + +/** + * Initialize L-BFGS parameters to the default values. + * + * Call this function to fill a parameter structure with the default values + * and overwrite parameter values if necessary. + * + * @param param The pointer to the parameter structure. + */ +void lbfgs_parameter_init(lbfgs_parameter_t *param); + +/** + * Allocate an array for variables. + * + * This function allocates an array of variables for the convenience of + * ::lbfgs function; the function has a requreiemt for a variable array + * when libLBFGS is built with SSE/SSE2 optimization routines. A user does + * not have to use this function for libLBFGS built without SSE/SSE2 + * optimization. + * + * @param n The number of variables. + */ +lbfgsfloatval_t* lbfgs_malloc(int n); + +/** + * Free an array of variables. + * + * @param x The array of variables allocated by ::lbfgs_malloc + * function. + */ +void lbfgs_free(lbfgsfloatval_t *x); + +/** @} */ + +#ifdef __cplusplus +} +#endif/*__cplusplus*/ + + + +/** +@mainpage libLBFGS: a library of Limited-memory Broyden-Fletcher-Goldfarb-Shanno (L-BFGS) + +@section intro Introduction + +This library is a C port of the implementation of Limited-memory +Broyden-Fletcher-Goldfarb-Shanno (L-BFGS) method written by Jorge Nocedal. +The original FORTRAN source code is available at: +http://www.ece.northwestern.edu/~nocedal/lbfgs.html + +The L-BFGS method solves the unconstrainted minimization problem, + +
    +    minimize F(x), x = (x1, x2, ..., xN),
    +
    + +only if the objective function F(x) and its gradient G(x) are computable. The +well-known Newton's method requires computation of the inverse of the hessian +matrix of the objective function. However, the computational cost for the +inverse hessian matrix is expensive especially when the objective function +takes a large number of variables. The L-BFGS method iteratively finds a +minimizer by approximating the inverse hessian matrix by information from last +m iterations. This innovation saves the memory storage and computational time +drastically for large-scaled problems. + +Among the various ports of L-BFGS, this library provides several features: +- Optimization with L1-norm (Orthant-Wise Limited-memory Quasi-Newton + (OWL-QN) method): + In addition to standard minimization problems, the library can minimize + a function F(x) combined with L1-norm |x| of the variables, + {F(x) + C |x|}, where C is a constant scalar parameter. This feature is + useful for estimating parameters of sparse log-linear models (e.g., + logistic regression and maximum entropy) with L1-regularization (or + Laplacian prior). +- Clean C code: + Unlike C codes generated automatically by f2c (Fortran 77 into C converter), + this port includes changes based on my interpretations, improvements, + optimizations, and clean-ups so that the ported code would be well-suited + for a C code. In addition to comments inherited from the original code, + a number of comments were added through my interpretations. +- Callback interface: + The library receives function and gradient values via a callback interface. + The library also notifies the progress of the optimization by invoking a + callback function. In the original implementation, a user had to set + function and gradient values every time the function returns for obtaining + updated values. +- Thread safe: + The library is thread-safe, which is the secondary gain from the callback + interface. +- Cross platform. The source code can be compiled on Microsoft Visual + Studio 2005, GNU C Compiler (gcc), etc. +- Configurable precision: A user can choose single-precision (float) + or double-precision (double) accuracy by changing ::LBFGS_FLOAT macro. +- SSE/SSE2 optimization: + This library includes SSE/SSE2 optimization (written in compiler intrinsics) + for vector arithmetic operations on Intel/AMD processors. The library uses + SSE for float values and SSE2 for double values. The SSE/SSE2 optimization + routine is disabled by default. + +This library is used by: +- CRFsuite: A fast implementation of Conditional Random Fields (CRFs) +- Classias: A collection of machine-learning algorithms for classification +- mlegp: an R package for maximum likelihood estimates for Gaussian processes +- imaging2: the imaging2 class library +- Algorithm::LBFGS - Perl extension for L-BFGS +- YAP-LBFGS (an interface to call libLBFGS from YAP Prolog) + +@section download Download + +- Source code + +libLBFGS is distributed under the term of the +MIT license. + +@section changelog History +- Version 1.9 (2010-01-29): + - Fixed a mistake in checking the validity of the parameters "ftol" and + "wolfe"; this was discovered by Kevin S. Van Horn. +- Version 1.8 (2009-07-13): + - Accepted the patch submitted by Takashi Imamichi; + the backtracking method now has three criteria for choosing the step + length: + - ::LBFGS_LINESEARCH_BACKTRACKING_ARMIJO: sufficient decrease (Armijo) + condition only + - ::LBFGS_LINESEARCH_BACKTRACKING_WOLFE: regular Wolfe condition + (sufficient decrease condition + curvature condition) + - ::LBFGS_LINESEARCH_BACKTRACKING_STRONG_WOLFE: strong Wolfe condition + - Updated the documentation to explain the above three criteria. +- Version 1.7 (2009-02-28): + - Improved OWL-QN routines for stability. + - Removed the support of OWL-QN method in MoreThuente algorithm because + it accidentally fails in early stages of iterations for some objectives. + Because of this change, the OW-LQN method must be used with the + backtracking algorithm (::LBFGS_LINESEARCH_BACKTRACKING), or the + library returns ::LBFGSERR_INVALID_LINESEARCH. + - Renamed line search algorithms as follows: + - ::LBFGS_LINESEARCH_BACKTRACKING: regular Wolfe condition. + - ::LBFGS_LINESEARCH_BACKTRACKING_LOOSE: regular Wolfe condition. + - ::LBFGS_LINESEARCH_BACKTRACKING_STRONG: strong Wolfe condition. + - Source code clean-up. +- Version 1.6 (2008-11-02): + - Improved line-search algorithm with strong Wolfe condition, which was + contributed by Takashi Imamichi. This routine is now default for + ::LBFGS_LINESEARCH_BACKTRACKING. The previous line search algorithm + with regular Wolfe condition is still available as + ::LBFGS_LINESEARCH_BACKTRACKING_LOOSE. + - Configurable stop index for L1-norm computation. A member variable + ::lbfgs_parameter_t::orthantwise_end was added to specify the index + number at which the library stops computing the L1 norm of the + variables. This is useful to prevent some variables from being + regularized by the OW-LQN method. + - A sample program written in C++ (sample/sample.cpp). +- Version 1.5 (2008-07-10): + - Configurable starting index for L1-norm computation. A member variable + ::lbfgs_parameter_t::orthantwise_start was added to specify the index + number from which the library computes the L1 norm of the variables. + This is useful to prevent some variables from being regularized by the + OWL-QN method. + - Fixed a zero-division error when the initial variables have already + been a minimizer (reported by Takashi Imamichi). In this case, the + library returns ::LBFGS_ALREADY_MINIMIZED status code. + - Defined ::LBFGS_SUCCESS status code as zero; removed unused constants, + LBFGSFALSE and LBFGSTRUE. + - Fixed a compile error in an implicit down-cast. +- Version 1.4 (2008-04-25): + - Configurable line search algorithms. A member variable + ::lbfgs_parameter_t::linesearch was added to choose either MoreThuente + method (::LBFGS_LINESEARCH_MORETHUENTE) or backtracking algorithm + (::LBFGS_LINESEARCH_BACKTRACKING). + - Fixed a bug: the previous version did not compute psuedo-gradients + properly in the line search routines for OWL-QN. This bug might quit + an iteration process too early when the OWL-QN routine was activated + (0 < ::lbfgs_parameter_t::orthantwise_c). + - Configure script for POSIX environments. + - SSE/SSE2 optimizations with GCC. + - New functions ::lbfgs_malloc and ::lbfgs_free to use SSE/SSE2 routines + transparently. It is uncessary to use these functions for libLBFGS built + without SSE/SSE2 routines; you can still use any memory allocators if + SSE/SSE2 routines are disabled in libLBFGS. +- Version 1.3 (2007-12-16): + - An API change. An argument was added to lbfgs() function to receive the + final value of the objective function. This argument can be set to + \c NULL if the final value is unnecessary. + - Fixed a null-pointer bug in the sample code (reported by Takashi Imamichi). + - Added build scripts for Microsoft Visual Studio 2005 and GCC. + - Added README file. +- Version 1.2 (2007-12-13): + - Fixed a serious bug in orthant-wise L-BFGS. + An important variable was used without initialization. +- Version 1.1 (2007-12-01): + - Implemented orthant-wise L-BFGS. + - Implemented lbfgs_parameter_init() function. + - Fixed several bugs. + - API documentation. +- Version 1.0 (2007-09-20): + - Initial release. + +@section api Documentation + +- @ref liblbfgs_api "libLBFGS API" + +@section sample Sample code + +@include sample.c + +@section ack Acknowledgements + +The L-BFGS algorithm is described in: + - Jorge Nocedal. + Updating Quasi-Newton Matrices with Limited Storage. + Mathematics of Computation, Vol. 35, No. 151, pp. 773--782, 1980. + - Dong C. Liu and Jorge Nocedal. + On the limited memory BFGS method for large scale optimization. + Mathematical Programming B, Vol. 45, No. 3, pp. 503-528, 1989. + +The line search algorithms used in this implementation are described in: + - John E. Dennis and Robert B. Schnabel. + Numerical Methods for Unconstrained Optimization and Nonlinear + Equations, Englewood Cliffs, 1983. + - Jorge J. More and David J. Thuente. + Line search algorithm with guaranteed sufficient decrease. + ACM Transactions on Mathematical Software (TOMS), Vol. 20, No. 3, + pp. 286-307, 1994. + +This library also implements Orthant-Wise Limited-memory Quasi-Newton (OWL-QN) +method presented in: + - Galen Andrew and Jianfeng Gao. + Scalable training of L1-regularized log-linear models. + In Proceedings of the 24th International Conference on Machine + Learning (ICML 2007), pp. 33-40, 2007. + +Special thanks go to: + - Yoshimasa Tsuruoka and Daisuke Okanohara for technical information about + OWL-QN + - Takashi Imamichi for the useful enhancements of the backtracking method + +Finally I would like to thank the original author, Jorge Nocedal, who has been +distributing the effieicnt and explanatory implementation in an open source +licence. + +@section reference Reference + +- L-BFGS by Jorge Nocedal. +- Orthant-Wise Limited-memory Quasi-Newton Optimizer for L1-regularized Objectives by Galen Andrew. +- C port (via f2c) by Taku Kudo. +- C#/C++/Delphi/VisualBasic6 port in ALGLIB. +- Computational Crystallography Toolbox includes + scitbx::lbfgs. +*/ + +#endif/*__LBFGS_H__*/ diff --git a/src/plfit/mt.c b/src/plfit/mt.c new file mode 100644 index 0000000..48f10eb --- /dev/null +++ b/src/plfit/mt.c @@ -0,0 +1,88 @@ +/* mt.c + * + * Mersenne Twister random number generator, based on the implementation of + * Michael Brundage (which has been placed in the public domain). + * + * Author: Tamas Nepusz (original by Michael Brundage) + * + * See the following URL for the original implementation: + * http://www.qbrundage.com/michaelb/pubs/essays/random_number_generation.html + * + * This file has been placed in the public domain. + */ + +#include +#include "mt.h" + +void mt_init(mt_rng_t* rng) { + mt_init_from_rng(rng, 0); +} + +void mt_init_from_rng(mt_rng_t* rng, mt_rng_t* seeder) { + int i; + + if (seeder == 0) { + for (i = 0; i < MT_LEN; i++) { + /* RAND_MAX is guaranteed to be at least 32767, so we can use two + * calls to rand() to produce a random 32-bit number */ + rng->mt_buffer[i] = (rand() << 16) + rand(); + } + } else { + for (i = 0; i < MT_LEN; i++) { + rng->mt_buffer[i] = mt_random(seeder); + } + } + + rng->mt_index = 0; +} + +#define MT_IA 397 +#define MT_IB (MT_LEN - MT_IA) +#define UPPER_MASK 0x80000000 +#define LOWER_MASK 0x7FFFFFFF +#define MATRIX_A 0x9908B0DF +#define TWIST(b,i,j) ((b)[i] & UPPER_MASK) | ((b)[j] & LOWER_MASK) +#define MAGIC(s) (((s)&1)*MATRIX_A) + +uint32_t mt_random(mt_rng_t* rng) { + uint32_t * b = rng->mt_buffer; + int idx = rng->mt_index; + uint32_t s; + int i; + + if (idx == MT_LEN * sizeof(uint32_t)) { + idx = 0; + i = 0; + for (; i < MT_IB; i++) { + s = TWIST(b, i, i+1); + b[i] = b[i + MT_IA] ^ (s >> 1) ^ MAGIC(s); + } + for (; i < MT_LEN-1; i++) { + s = TWIST(b, i, i+1); + b[i] = b[i - MT_IB] ^ (s >> 1) ^ MAGIC(s); + } + + s = TWIST(b, MT_LEN-1, 0); + b[MT_LEN-1] = b[MT_IA-1] ^ (s >> 1) ^ MAGIC(s); + } + + rng->mt_index = idx + sizeof(uint32_t); + return *(uint32_t *)((unsigned char *)b + idx); + /* + Matsumoto and Nishimura additionally confound the bits returned to the caller + but this doesn't increase the randomness, and slows down the generator by + as much as 25%. So I omit these operations here. + + r ^= (r >> 11); + r ^= (r << 7) & 0x9D2C5680; + r ^= (r << 15) & 0xEFC60000; + r ^= (r >> 18); + */ +} + + +double mt_uniform_01(mt_rng_t* rng) { + return ((double)mt_random(rng)) / MT_RAND_MAX; +} + + diff --git a/src/plfit/mt.h b/src/plfit/mt.h new file mode 100644 index 0000000..ce12068 --- /dev/null +++ b/src/plfit/mt.h @@ -0,0 +1,102 @@ +/* mt.h + * + * Mersenne Twister random number generator, based on the implementation of + * Michael Brundage (which has been placed in the public domain). + * + * Author: Tamas Nepusz (original by Michael Brundage) + * + * See the following URL for the original implementation: + * http://www.qbrundage.com/michaelb/pubs/essays/random_number_generation.html + * + * This file has been placed in the public domain. + */ + +#ifndef __MT_H__ +#define __MT_H__ + +#ifdef _MSC_VER +# define uint32_t __int32 +#else +# include +#endif + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +__BEGIN_DECLS + +#define MT_LEN 624 + +/** + * \def MT_RAND_MAX + * + * The maximum random number that \c mt_random() can generate. + */ +#define MT_RAND_MAX 0xFFFFFFFF + +/** + * Struct that stores the internal state of a Mersenne Twister random number + * generator. + */ +typedef struct { + int mt_index; + uint32_t mt_buffer[MT_LEN]; +} mt_rng_t; + +/** + * \brief Initializes a Mersenne Twister random number generator. + * + * The random number generator is seeded with random 32-bit numbers obtained + * from the \em built-in random number generator using consecutive calls to + * \c rand(). + * + * \param rng the random number generator to initialize + */ +void mt_init(mt_rng_t* rng); + +/** + * \brief Initializes a Mersenne Twister random number generator, seeding it + * from another one. + * + * The random number generator is seeded with random 32-bit numbers obtained + * from another, initialized Mersenne Twister random number generator. + * + * \param rng the random number generator to initialize + * \param seeder the random number generator that will seed the one being + * initialized. When null, the random number generator will + * be initialized from the built-in RNG as if \ref mt_init() + * was called. + */ +void mt_init_from_rng(mt_rng_t* rng, mt_rng_t* seeder); + +/** + * \brief Returns the next 32-bit random number from the given Mersenne Twister + * random number generator. + * + * \param rng the random number generator to use + * \return the next 32-bit random number from the generator + */ +uint32_t mt_random(mt_rng_t* rng); + +/** + * \brief Returns a uniformly distributed double from the interval [0;1) + * based on the next value of the given Mersenne Twister random number + * generator. + * + * \param rng the random number generator to use + * \return a uniformly distributed random number from the interval [0;1) + */ +double mt_uniform_01(mt_rng_t* rng); + +__END_DECLS + +#endif + + diff --git a/src/plfit/options.c b/src/plfit/options.c new file mode 100644 index 0000000..df963f6 --- /dev/null +++ b/src/plfit/options.c @@ -0,0 +1,52 @@ +/* options.c + * + * Copyright (C) 2012 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "error.h" +#include "plfit.h" + +const plfit_continuous_options_t plfit_continuous_default_options = { + /* .finite_size_correction = */ 0, + /* .xmin_method = */ PLFIT_DEFAULT_CONTINUOUS_METHOD, + /* .p_value_method = */ PLFIT_DEFAULT_P_VALUE_METHOD, + /* .p_value_precision = */ 0.01, + /* .rng = */ 0 +}; + +const plfit_discrete_options_t plfit_discrete_default_options = { + /* .finite_size_correction = */ 0, + /* .alpha_method = */ PLFIT_DEFAULT_DISCRETE_METHOD, + /* .alpha = */ { + /* .min = */ 1.01, + /* .max = */ 5, + /* .step = */ 0.01 + }, + /* .p_value_method = */ PLFIT_DEFAULT_P_VALUE_METHOD, + /* .p_value_precision = */ 0.01, + /* .rng = */ 0 +}; + +int plfit_continuous_options_init(plfit_continuous_options_t* options) { + *options = plfit_continuous_default_options; + return PLFIT_SUCCESS; +} + +int plfit_discrete_options_init(plfit_discrete_options_t* options) { + *options = plfit_discrete_default_options; + return PLFIT_SUCCESS; +} diff --git a/src/plfit/platform.h b/src/plfit/platform.h new file mode 100644 index 0000000..c98b888 --- /dev/null +++ b/src/plfit/platform.h @@ -0,0 +1,64 @@ +/* platform.h + * + * Copyright (C) 2010-2011 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __PLATFORM_H__ +#define __PLATFORM_H__ + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +#include + +__BEGIN_DECLS + +#ifdef _MSC_VER +#include +#include + +#define snprintf _snprintf +#define inline __inline +#define isnan(x) ((x) != (x)) +#define isfinite(x) _finite(x) + +extern double _plfit_fmin(double a, double b); +extern double _plfit_round(double x); + +#define fmin _plfit_fmin +#define round _plfit_round + +#endif + +#ifndef INFINITY +# define INFINITY (1.0/0.0) +#endif + +#ifndef NAN +# define NAN ((double)0.0 / (double)DBL_MIN) +#endif + +__END_DECLS + +#endif /* __PLATFORM_H__ */ diff --git a/src/plfit/plfit.c b/src/plfit/plfit.c new file mode 100644 index 0000000..788bce8 --- /dev/null +++ b/src/plfit/plfit.c @@ -0,0 +1,1309 @@ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* plfit.c + * + * Copyright (C) 2010-2011 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include "error.h" +#include "gss.h" +#include "lbfgs.h" +#include "platform.h" +#include "plfit.h" +#include "kolmogorov.h" +#include "sampling.h" +#include "hzeta.h" + +/* #define PLFIT_DEBUG */ + +#define DATA_POINTS_CHECK \ + if (n <= 0) { \ + PLFIT_ERROR("no data points", PLFIT_EINVAL); \ + } + +#define XMIN_CHECK_ZERO \ + if (xmin <= 0) { \ + PLFIT_ERROR("xmin must be greater than zero", PLFIT_EINVAL); \ + } +#define XMIN_CHECK_ONE \ + if (xmin < 1) { \ + PLFIT_ERROR("xmin must be at least 1", PLFIT_EINVAL); \ + } + +static int plfit_i_resample_continuous(double* xs_head, size_t num_smaller, + size_t n, double alpha, double xmin, size_t num_samples, mt_rng_t* rng, + double* result); +static int plfit_i_resample_discrete(double* xs_head, size_t num_smaller, + size_t n, double alpha, double xmin, size_t num_samples, mt_rng_t* rng, + double* result); + +static int double_comparator(const void *a, const void *b) { + const double *da = (const double*)a; + const double *db = (const double*)b; + return (*da > *db) - (*da < *db); +} + +static int plfit_i_copy_and_sort(double* xs, size_t n, double** result) { + *result = (double*)malloc(sizeof(double) * n); + if (*result == 0) { + PLFIT_ERROR("cannot create sorted copy of input data", PLFIT_ENOMEM); + } + + memcpy(*result, xs, sizeof(double) * n); + qsort(*result, n, sizeof(double), double_comparator); + + return PLFIT_SUCCESS; +} + +/** + * Given an unsorted array of doubles, counts how many elements there are that + * are smaller than a given value. + * + * \param begin pointer to the beginning of the array + * \param end pointer to the first element after the end of the array + * \param xmin the threshold value + * + * \return the nubmer of elements in the array that are smaller than the given + * value. + */ +static size_t count_smaller(double* begin, double* end, double xmin) { + double* p; + size_t counter = 0; + + for (p = begin; p < end; p++) { + if (*p < xmin) { + counter++; + } + } + + return counter; +} + +/** + * Given an unsorted array of doubles, return another array that contains the + * elements that are smaller than a given value + * + * \param begin pointer to the beginning of the array + * \param end pointer to the first element after the end of the array + * \param xmin the threshold value + * \param result_length if not \c NULL, the number of unique elements in the + * given array is returned here + * + * \return pointer to the head of the new array or 0 if there is not enough + * memory + */ +static double* extract_smaller(double* begin, double* end, double xmin, + size_t* result_length) { + size_t counter = count_smaller(begin, end, xmin); + double *p, *result; + + result = calloc(counter, sizeof(double)); + if (result == 0) + return 0; + + for (p = result; begin < end; begin++) { + if (*begin < xmin) { + *p = *begin; + p++; + } + } + + if (result_length) { + *result_length = counter; + } + + return result; +} + +/** + * Given a sorted array of doubles, return another array that contains pointers + * into the array for the start of each block of identical elements. + * + * \param begin pointer to the beginning of the array + * \param end pointer to the first element after the end of the array + * \param result_length if not \c NULL, the number of unique elements in the + * given array is returned here + * + * \return pointer to the head of the new array or 0 if there is not enough + * memory + */ +static double** unique_element_pointers(double* begin, double* end, size_t* result_length) { + double* ptr = begin; + double** result; + double prev_x; + size_t num_elts = 15; + size_t used_elts = 0; + + /* Special case: empty array */ + if (begin == end) { + result = calloc(1, sizeof(double*)); + if (result != 0) { + result[0] = 0; + } + return result; + } + + /* Allocate initial result array, including the guard element */ + result = calloc(num_elts+1, sizeof(double*)); + if (result == 0) + return 0; + + prev_x = *begin; + result[used_elts++] = begin; + + /* Process the input array */ + for (ptr = begin+1; ptr < end; ptr++) { + if (*ptr == prev_x) + continue; + + /* New block found */ + if (used_elts >= num_elts) { + /* Array full; allocate a new chunk */ + num_elts = num_elts*2 + 1; + result = realloc(result, sizeof(double*) * (num_elts+1)); + if (result == 0) + return 0; + } + + /* Store the new element */ + result[used_elts++] = ptr; + prev_x = *ptr; + } + + /* Calculate the result length */ + if (result_length != 0) { + *result_length = used_elts; + } + + /* Add the guard entry to the end of the result */ + result[used_elts++] = 0; + + return result; +} + +static void plfit_i_perform_finite_size_correction(plfit_result_t* result, size_t n) { + result->alpha = result->alpha * (n-1) / n + 1.0 / n; +} + +/********** Continuous power law distribution fitting **********/ + +static void plfit_i_logsum_less_than_continuous(double* begin, double* end, + double xmin, double* result, size_t* m) { + double logsum = 0.0; + size_t count = 0; + + for (; begin != end; begin++) { + if (*begin >= xmin) { + count++; + logsum += log(*begin / xmin); + } + } + + *m = count; + *result = logsum; +} + +static double plfit_i_logsum_continuous(double* begin, double* end, double xmin) { + double logsum = 0.0; + for (; begin != end; begin++) + logsum += log(*begin / xmin); + return logsum; +} + +static int plfit_i_estimate_alpha_continuous(double* xs, size_t n, + double xmin, double* alpha) { + double result; + size_t m; + + XMIN_CHECK_ZERO; + + plfit_i_logsum_less_than_continuous(xs, xs+n, xmin, &result, &m); + + if (m == 0) { + PLFIT_ERROR("no data point was larger than xmin", PLFIT_EINVAL); + } + + *alpha = 1 + m / result; + + return PLFIT_SUCCESS; +} + +static int plfit_i_estimate_alpha_continuous_sorted(double* xs, size_t n, + double xmin, double* alpha) { + double* end = xs+n; + + XMIN_CHECK_ZERO; + + for (; xs != end && *xs < xmin; xs++); + if (xs == end) { + PLFIT_ERROR("no data point was larger than xmin", PLFIT_EINVAL); + } + + *alpha = 1 + (end-xs) / plfit_i_logsum_continuous(xs, end, xmin); + + return PLFIT_SUCCESS; +} + +static int plfit_i_ks_test_continuous(double* xs, double* xs_end, + const double alpha, const double xmin, double* D) { + /* Assumption: xs is sorted and cut off at xmin so the first element is + * always larger than or equal to xmin. */ + double result = 0, n; + int m = 0; + + n = xs_end - xs; + + while (xs < xs_end) { + double d = fabs(1-pow(xmin / *xs, alpha-1) - m / n); + + if (d > result) + result = d; + + xs++; m++; + } + + *D = result; + + return PLFIT_SUCCESS; +} + +static int plfit_i_calculate_p_value_continuous(double* xs, size_t n, + const plfit_continuous_options_t *options, plfit_bool_t xmin_fixed, + plfit_result_t *result) { + long int num_trials; + long int successes = 0; + double *xs_head; + size_t num_smaller; + plfit_continuous_options_t options_no_p_value = *options; + int retval = PLFIT_SUCCESS; + + if (options->p_value_method == PLFIT_P_VALUE_SKIP) { + result->p = NAN; + return PLFIT_SUCCESS; + } + + if (options->p_value_method == PLFIT_P_VALUE_APPROXIMATE) { + num_smaller = count_smaller(xs, xs + n, result->xmin); + result->p = plfit_ks_test_one_sample_p(result->D, n - num_smaller); + return PLFIT_SUCCESS; + } + + options_no_p_value.p_value_method = PLFIT_P_VALUE_SKIP; + num_trials = (long int)(0.25 / options->p_value_precision / options->p_value_precision); + if (num_trials <= 0) { + PLFIT_ERROR("invalid p-value precision", PLFIT_EINVAL); + } + + /* Extract the head of xs that contains elements smaller than xmin */ + xs_head = extract_smaller(xs, xs+n, result->xmin, &num_smaller); + if (xs_head == 0) + PLFIT_ERROR("cannot calculate exact p-value", PLFIT_ENOMEM); + +#ifdef _OPENMP +#pragma omp parallel +#endif + { + /* Parallel section starts here. If we are compiling using OpenMP, each + * thread will use its own RNG that is seeded from the master RNG. If + * we are compiling without OpenMP, there is only one thread and it uses + * the master RNG. This section must be critical to ensure that only one + * thread is using the master RNG at the same time. */ +#ifdef _OPENMP + mt_rng_t private_rng; +#endif + mt_rng_t *p_rng; + double *ys; + long int i; + plfit_result_t result_synthetic; + +#ifdef _OPENMP +#pragma omp critical + { + p_rng = &private_rng; + mt_init_from_rng(p_rng, options->rng); + } +#else + p_rng = options->rng; +#endif + + /* Allocate memory to sample into */ + ys = calloc(n, sizeof(double)); + if (ys == 0) { + retval = PLFIT_ENOMEM; + } else { + /* The main for loop starts here. */ +#ifdef _OPENMP +#pragma omp for reduction(+:successes) +#endif + for (i = 0; i < num_trials; i++) { + plfit_i_resample_continuous(xs_head, num_smaller, n, result->alpha, + result->xmin, n, p_rng, ys); + if (xmin_fixed) { + plfit_estimate_alpha_continuous(ys, n, result->xmin, + &options_no_p_value, &result_synthetic); + } else { + plfit_continuous(ys, n, &options_no_p_value, &result_synthetic); + } + if (result_synthetic.D > result->D) + successes++; + } + free(ys); + } + + /* End of parallelized part */ + } + + free(xs_head); + + if (retval == PLFIT_SUCCESS) { + result->p = successes / ((double)num_trials); + } else { + PLFIT_ERROR("cannot calculate exact p-value", retval); + } + + return retval; +} + +int plfit_log_likelihood_continuous(double* xs, size_t n, double alpha, + double xmin, double* L) { + double logsum, c; + size_t m; + + if (alpha <= 1) { + PLFIT_ERROR("alpha must be greater than one", PLFIT_EINVAL); + } + XMIN_CHECK_ZERO; + + c = (alpha - 1) / xmin; + plfit_i_logsum_less_than_continuous(xs, xs+n, xmin, &logsum, &m); + *L = -alpha * logsum + log(c) * m; + + return PLFIT_SUCCESS; +} + +int plfit_estimate_alpha_continuous_sorted(double* xs, size_t n, double xmin, + const plfit_continuous_options_t* options, plfit_result_t *result) { + double *begin, *end; + + if (!options) + options = &plfit_continuous_default_options; + + begin = xs; + end = xs + n; + while (begin < end && *begin < xmin) + begin++; + + PLFIT_CHECK(plfit_i_estimate_alpha_continuous_sorted(begin, end-begin, + xmin, &result->alpha)); + PLFIT_CHECK(plfit_i_ks_test_continuous(begin, end, result->alpha, + xmin, &result->D)); + + if (options->finite_size_correction) + plfit_i_perform_finite_size_correction(result, end-begin); + result->xmin = xmin; + + PLFIT_CHECK(plfit_log_likelihood_continuous(begin, end-begin, result->alpha, + result->xmin, &result->L)); + PLFIT_CHECK(plfit_i_calculate_p_value_continuous(xs, n, options, 1, result)); + + return PLFIT_SUCCESS; +} + +int plfit_estimate_alpha_continuous(double* xs, size_t n, double xmin, + const plfit_continuous_options_t* options, plfit_result_t *result) { + double *xs_copy; + + if (!options) + options = &plfit_continuous_default_options; + + PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &xs_copy)); + PLFIT_CHECK(plfit_estimate_alpha_continuous_sorted(xs_copy, n, xmin, + options, result)); + free(xs_copy); + + return PLFIT_SUCCESS; +} + +typedef struct { + double *begin; /**< Pointer to the beginning of the array holding the data */ + double *end; /**< Pointer to after the end of the array holding the data */ + double **probes; /**< Pointers to the elements of the array that will be probed */ + size_t num_probes; /**< Number of probes */ + plfit_result_t last; /**< Result of the last evaluation */ +} plfit_continuous_xmin_opt_data_t; + +static double plfit_i_continuous_xmin_opt_evaluate(void* instance, double x) { + plfit_continuous_xmin_opt_data_t* data = (plfit_continuous_xmin_opt_data_t*)instance; + double* begin = data->probes[(long int)x]; + + data->last.xmin = *begin; + +#ifdef PLFIT_DEBUG + printf("Trying with probes[%ld] = %.4f\n", (long int)x, *begin); +#endif + + plfit_i_estimate_alpha_continuous_sorted(begin, data->end-begin, *begin, + &data->last.alpha); + plfit_i_ks_test_continuous(begin, data->end, data->last.alpha, *begin, + &data->last.D); + + return data->last.D; +} + +static int plfit_i_continuous_xmin_opt_progress(void* instance, double x, double fx, + double min, double fmin, double left, double right, int k) { +#ifdef PLFIT_DEBUG + printf("Iteration #%d: [%.4f; %.4f), x=%.4f, fx=%.4f, min=%.4f, fmin=%.4f\n", + k, left, right, x, fx, min, fmin); +#endif + + /* Continue only if `left' and `right' point to different integers */ + return (int)left == (int)right; +} + +static int plfit_i_continuous_xmin_opt_linear_scan( + plfit_continuous_xmin_opt_data_t* opt_data, plfit_result_t* best_result, + size_t* best_n) { + size_t i; + plfit_result_t global_best_result; + size_t global_best_n; + + /* Prepare some variables */ + global_best_n = 0; + global_best_result.D = DBL_MAX; + global_best_result.xmin = 0; + global_best_result.alpha = 0; + + /* Due to the OpenMP parallelization, we do things as follows. Each + * OpenMP thread will search for the best D-score on its own and store + * the result in a private local_best_result variable. The end of the + * parallel block contains a critical section that threads will enter + * one by one and compare their private local_best_result with a + * global_best that is shared among the threads. + */ +#ifdef _OPENMP +#pragma omp parallel shared(global_best_result, global_best_n) private(i) firstprivate(opt_data) +#endif + { + /* These variables are private since they are declared within the + * parallel block */ + plfit_result_t local_best_result; + plfit_continuous_xmin_opt_data_t local_opt_data = *opt_data; + size_t local_best_n; + + /* Initialize the local_best_result and local_best_n variables */ + local_best_n = 0; + local_best_result.D = DBL_MAX; + local_best_result.xmin = 0; + local_best_result.alpha = 0; + + /* The range of the for loop below is divided among the threads. + * nowait means that there will be no implicit barrier at the end + * of the loop so threads that get there earlier can enter the + * critical section without waiting for the others */ +#ifdef _OPENMP +#pragma omp for nowait schedule(dynamic,10) +#endif + for (i = 0; i < local_opt_data.num_probes-1; i++) { + plfit_i_continuous_xmin_opt_evaluate(&local_opt_data, i); + if (local_opt_data.last.D < local_best_result.D) { +#ifdef PLFIT_DEBUG + printf("Found new local best at %g with D=%g\n", + local_opt_data.last.xmin, local_opt_data.last.D); +#endif + local_best_result = local_opt_data.last; + local_best_n = local_opt_data.end - local_opt_data.probes[i] + 1; + } + } + + /* Critical section that finds the global best result from the + * local ones collected by each thread */ +#ifdef _OPENMP +#pragma omp critical +#endif + if (local_best_result.D < global_best_result.D) { + global_best_result = local_best_result; + global_best_n = local_best_n; +#ifdef PLFIT_DEBUG + printf("Found new global best at %g with D=%g\n", global_best_result.xmin, + global_best_result.D); +#endif + } + } + + *best_result = global_best_result; + *best_n = global_best_n; + +#ifdef PLFIT_DEBUG + printf("Returning global best: %g\n", best_result->xmin); +#endif + + return PLFIT_SUCCESS; +} + +int plfit_continuous(double* xs, size_t n, const plfit_continuous_options_t* options, + plfit_result_t* result) { + gss_parameter_t gss_param; + plfit_continuous_xmin_opt_data_t opt_data; + plfit_result_t best_result = { + /* alpha = */ NAN, + /* xmin = */ NAN, + /* L = */ NAN, + /* D = */ NAN, + /* p = */ NAN + }; + + int success; + size_t i, best_n, num_uniques; + double x, *px, **uniques; + + DATA_POINTS_CHECK; + + /* Sane defaults */ + best_n = n; + if (!options) + options = &plfit_continuous_default_options; + + /* Make a copy of xs and sort it */ + PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &opt_data.begin)); + opt_data.end = opt_data.begin + n; + + /* Create an array containing pointers to the unique elements of the input. From + * each block of unique elements, we add the pointer to the first one. */ + uniques = unique_element_pointers(opt_data.begin, opt_data.end, &num_uniques); + if (uniques == 0) + PLFIT_ERROR("cannot fit continuous power-law", PLFIT_ENOMEM); + + /* We will now determine the best xmin that yields the lowest D-score. The + * 'success' variable will denote whether the search procedure we tried was + * successful. If it is false after having exhausted all options, we fall + * back to a linear search. */ + success = 0; + switch (options->xmin_method) { + case PLFIT_GSS_OR_LINEAR: + /* Try golden section search first. */ + if (num_uniques > 5) { + opt_data.probes = uniques; + opt_data.num_probes = num_uniques; + gss_parameter_init(&gss_param); + success = (gss(0, opt_data.num_probes-5, &x, 0, + plfit_i_continuous_xmin_opt_evaluate, + plfit_i_continuous_xmin_opt_progress, &opt_data, &gss_param) == 0); + if (success) { + px = opt_data.probes[(int)x]; + best_n = opt_data.end-px+1; + best_result = opt_data.last; + } + } + break; + + case PLFIT_STRATIFIED_SAMPLING: + if (num_uniques >= 50) { + /* Try stratified sampling to narrow down the interval where the minimum + * is likely to reside. We check 10% of the unique items, distributed + * evenly, find the one with the lowest D-score, and then check the + * area around it more thoroughly. */ + const size_t subdivision_length = 10; + size_t num_strata = num_uniques / subdivision_length; + double **strata = calloc(num_strata, sizeof(double*)); + + for (i = 0; i < num_strata; i++) { + strata[i] = uniques[i * subdivision_length]; + } + + opt_data.probes = strata; + opt_data.num_probes = num_strata; + plfit_i_continuous_xmin_opt_linear_scan(&opt_data, &best_result, &best_n); + + opt_data.num_probes = 0; + for (i = 0; i < num_strata; i++) { + if (*strata[i] == best_result.xmin) { + /* Okay, scan more thoroughly from strata[i-1] to strata[i+1], + * which is from uniques[(i-1)*subdivision_length] to + * uniques[(i+1)*subdivision_length */ + opt_data.probes = uniques + (i > 0 ? (i-1)*subdivision_length : 0); + opt_data.num_probes = 0; + if (i != 0) + opt_data.num_probes += subdivision_length; + if (i != num_strata-1) + opt_data.num_probes += subdivision_length; + break; + } + } + + free(strata); + if (opt_data.num_probes > 0) { + /* Do a strict linear scan in the subrange determined above */ + plfit_i_continuous_xmin_opt_linear_scan(&opt_data, + &best_result, &best_n); + success = 1; + } else { + /* This should not happen, but we handle it anyway */ + success = 0; + } + } + break; + + default: + /* Just use the linear search */ + break; + } + + if (!success) { + /* More advanced search methods failed or were skipped; try linear search */ + opt_data.probes = uniques; + opt_data.num_probes = num_uniques; + plfit_i_continuous_xmin_opt_linear_scan(&opt_data, &best_result, &best_n); + success = 1; + } + + /* Get rid of the uniques array, we don't need it any more */ + free(uniques); + + /* Sort out the result */ + *result = best_result; + if (options->finite_size_correction) + plfit_i_perform_finite_size_correction(result, best_n); + + PLFIT_CHECK(plfit_log_likelihood_continuous(opt_data.begin + n - best_n, best_n, + result->alpha, result->xmin, &result->L)); + PLFIT_CHECK(plfit_i_calculate_p_value_continuous(opt_data.begin, n, options, 0, result)); + + /* Get rid of the copied data as well */ + free(opt_data.begin); + + return PLFIT_SUCCESS; +} + +/********** Discrete power law distribution fitting **********/ + +typedef struct { + size_t m; + double logsum; + double xmin; +} plfit_i_estimate_alpha_discrete_data_t; + +static double plfit_i_logsum_discrete(double* begin, double* end, double xmin) { + double logsum = 0.0; + for (; begin != end; begin++) + logsum += log(*begin); + return logsum; +} + +static void plfit_i_logsum_less_than_discrete(double* begin, double* end, double xmin, + double* logsum, size_t* m) { + double result = 0.0; + size_t count = 0; + + for (; begin != end; begin++) { + if (*begin < xmin) + continue; + + result += log(*begin); + count++; + } + + *logsum = result; + *m = count; +} + +static lbfgsfloatval_t plfit_i_estimate_alpha_discrete_lbfgs_evaluate( + void* instance, const lbfgsfloatval_t* x, + lbfgsfloatval_t* g, const int n, + const lbfgsfloatval_t step) { + plfit_i_estimate_alpha_discrete_data_t* data; + lbfgsfloatval_t result; + double dx = step; + double huge = 1e10; /* pseudo-infinity; apparently DBL_MAX does not work */ + double lnhzeta_x=NAN; + double lnhzeta_deriv_x=NAN; + + data = (plfit_i_estimate_alpha_discrete_data_t*)instance; + +#ifdef PLFIT_DEBUG + printf("- Evaluating at %.4f (step = %.4f, xmin = %.4f)\n", *x, step, data->xmin); +#endif + + if (isnan(*x)) { + g[0] = huge; + return huge; + } + + /* Find the delta X value to estimate the gradient */ + if (dx > 0.001 || dx == 0) + dx = 0.001; + else if (dx < -0.001) + dx = -0.001; + + /* Is x[0] in its valid range? */ + if (x[0] <= 1.0) { + /* The Hurwitz zeta function is infinite in this case */ + g[0] = (dx > 0) ? -huge : huge; + return huge; + } + if (x[0] + dx <= 1.0) { + g[0] = huge; + result = x[0] * data->logsum + data->m * hsl_sf_lnhzeta(x[0], data->xmin); + } else { + hsl_sf_lnhzeta_deriv_tuple(x[0], data->xmin, &lnhzeta_x, &lnhzeta_deriv_x); + g[0] = data->logsum + data->m * lnhzeta_deriv_x; + result = x[0] * data->logsum + data->m * lnhzeta_x; + } + +#ifdef PLFIT_DEBUG + printf(" - Gradient: %.4f\n", g[0]); + printf(" - Result: %.4f\n", result); +#endif + + return result; +} + +static int plfit_i_estimate_alpha_discrete_lbfgs_progress(void* instance, + const lbfgsfloatval_t* x, const lbfgsfloatval_t* g, + const lbfgsfloatval_t fx, const lbfgsfloatval_t xnorm, + const lbfgsfloatval_t gnorm, const lbfgsfloatval_t step, + int n, int k, int ls) { + return 0; +} + +static int plfit_i_estimate_alpha_discrete_linear_scan(double* xs, size_t n, + double xmin, double* alpha, const plfit_discrete_options_t* options, + plfit_bool_t sorted) { + double curr_alpha, best_alpha, L, L_max; + double logsum; + size_t m; + + XMIN_CHECK_ONE; + if (options->alpha.min <= 1.0) { + PLFIT_ERROR("alpha.min must be greater than 1.0", PLFIT_EINVAL); + } + if (options->alpha.max < options->alpha.min) { + PLFIT_ERROR("alpha.max must be greater than alpha.min", PLFIT_EINVAL); + } + if (options->alpha.step <= 0) { + PLFIT_ERROR("alpha.step must be positive", PLFIT_EINVAL); + } + + if (sorted) { + logsum = plfit_i_logsum_discrete(xs, xs+n, xmin); + m = n; + } else { + plfit_i_logsum_less_than_discrete(xs, xs+n, xmin, &logsum, &m); + } + + best_alpha = options->alpha.min; L_max = -DBL_MAX; + for (curr_alpha = options->alpha.min; curr_alpha <= options->alpha.max; + curr_alpha += options->alpha.step) { + L = -curr_alpha * logsum - m * hsl_sf_lnhzeta(curr_alpha, xmin); + if (L > L_max) { + L_max = L; + best_alpha = curr_alpha; + } + } + + *alpha = best_alpha; + + return PLFIT_SUCCESS; +} + +static int plfit_i_estimate_alpha_discrete_lbfgs(double* xs, size_t n, double xmin, + double* alpha, const plfit_discrete_options_t* options, plfit_bool_t sorted) { + lbfgs_parameter_t param; + lbfgsfloatval_t* variables; + plfit_i_estimate_alpha_discrete_data_t data; + int ret; + + XMIN_CHECK_ONE; + + /* Initialize algorithm parameters */ + lbfgs_parameter_init(¶m); + param.max_iterations = 0; /* proceed until infinity */ + + /* Set up context for optimization */ + data.xmin = xmin; + if (sorted) { + data.logsum = plfit_i_logsum_discrete(xs, xs+n, xmin); + data.m = n; + } else { + plfit_i_logsum_less_than_discrete(xs, xs+n, xmin, &data.logsum, &data.m); + } + + /* Allocate space for the single alpha variable */ + variables = lbfgs_malloc(1); + variables[0] = 3.0; /* initial guess */ + + /* Optimization */ + ret = lbfgs(1, variables, /* ptr_fx = */ 0, + plfit_i_estimate_alpha_discrete_lbfgs_evaluate, + plfit_i_estimate_alpha_discrete_lbfgs_progress, + &data, ¶m); + + if (ret < 0 && + ret != LBFGSERR_ROUNDING_ERROR && + ret != LBFGSERR_MAXIMUMLINESEARCH && + ret != LBFGSERR_MINIMUMSTEP && + ret != LBFGSERR_CANCELED) { + char buf[4096]; + snprintf(buf, 4096, "L-BFGS optimization signaled an error (error code = %d)", ret); + lbfgs_free(variables); + PLFIT_ERROR(buf, PLFIT_FAILURE); + } + *alpha = variables[0]; + + /* Deallocate the variable array */ + lbfgs_free(variables); + + return PLFIT_SUCCESS; +} + +static int plfit_i_estimate_alpha_discrete_fast(double* xs, size_t n, double xmin, + double* alpha, const plfit_discrete_options_t* options, plfit_bool_t sorted) { + plfit_continuous_options_t cont_options; + + if (!options) + options = &plfit_discrete_default_options; + + plfit_continuous_options_init(&cont_options); + cont_options.finite_size_correction = options->finite_size_correction; + + XMIN_CHECK_ONE; + + if (sorted) { + return plfit_i_estimate_alpha_continuous_sorted(xs, n, xmin-0.5, alpha); + } else { + return plfit_i_estimate_alpha_continuous(xs, n, xmin-0.5, alpha); + } +} + +static int plfit_i_estimate_alpha_discrete(double* xs, size_t n, double xmin, + double* alpha, const plfit_discrete_options_t* options, + plfit_bool_t sorted) { + switch (options->alpha_method) { + case PLFIT_LBFGS: + PLFIT_CHECK(plfit_i_estimate_alpha_discrete_lbfgs(xs, n, xmin, alpha, + options, sorted)); + break; + + case PLFIT_LINEAR_SCAN: + PLFIT_CHECK(plfit_i_estimate_alpha_discrete_linear_scan(xs, n, xmin, + alpha, options, sorted)); + break; + + case PLFIT_PRETEND_CONTINUOUS: + PLFIT_CHECK(plfit_i_estimate_alpha_discrete_fast(xs, n, xmin, + alpha, options, sorted)); + break; + + default: + PLFIT_ERROR("unknown optimization method specified", PLFIT_EINVAL); + } + + return PLFIT_SUCCESS; +} + +static int plfit_i_ks_test_discrete(double* xs, double* xs_end, const double alpha, + const double xmin, double* D) { + /* Assumption: xs is sorted and cut off at xmin so the first element is + * always larger than or equal to xmin. */ + double result = 0, n, lnhzeta, x; + int m = 0; + + n = xs_end - xs; + lnhzeta = hsl_sf_lnhzeta(alpha, xmin); + + while (xs < xs_end) { + double d; + + x = *xs; + + /* Re the next line: this used to be the following: + * + * fabs( 1 - hzeta(alpha, x) / hzeta(alpha, xmin) - m / n) + * + * However, using the Hurwitz zeta directly sometimes yields + * underflows (see Github pull request #17 and related issues). + * hzeta(alpha, x) / hzeta(alpha, xmin) can be replaced with + * exp(lnhzeta(alpha, x) - lnhzeta(alpha, xmin)), but then + * we have 1 - exp(something), which is better to calculate + * with a dedicated expm1() function. + */ + d = fabs( expm1( hsl_sf_lnhzeta(alpha, x) - lnhzeta ) + m / n); + + if (d > result) + result = d; + + do { + xs++; m++; + } while (xs < xs_end && *xs == x); + } + + *D = result; + + return PLFIT_SUCCESS; +} + +static int plfit_i_calculate_p_value_discrete(double* xs, size_t n, + const plfit_discrete_options_t* options, plfit_bool_t xmin_fixed, + plfit_result_t *result) { + long int num_trials; + long int successes = 0; + double *xs_head; + size_t num_smaller; + plfit_discrete_options_t options_no_p_value = *options; + int retval = PLFIT_SUCCESS; + + if (options->p_value_method == PLFIT_P_VALUE_SKIP) { + /* skipping p-value calculation */ + result->p = NAN; + return PLFIT_SUCCESS; + } + + if (options->p_value_method == PLFIT_P_VALUE_APPROXIMATE) { + /* p-value approximation; most likely an upper bound */ + num_smaller = count_smaller(xs, xs + n, result->xmin); + result->p = plfit_ks_test_one_sample_p(result->D, n - num_smaller); + return PLFIT_SUCCESS; + } + + options_no_p_value.p_value_method = PLFIT_P_VALUE_SKIP; + num_trials = (long int)(0.25 / options->p_value_precision / options->p_value_precision); + if (num_trials <= 0) { + PLFIT_ERROR("invalid p-value precision", PLFIT_EINVAL); + } + + /* Extract the head of xs that contains elements smaller than xmin */ + xs_head = extract_smaller(xs, xs+n, result->xmin, &num_smaller); + if (xs_head == 0) + PLFIT_ERROR("cannot calculate exact p-value", PLFIT_ENOMEM); + +#ifdef _OPENMP +#pragma omp parallel +#endif + { + /* Parallel section starts here. If we are compiling using OpenMP, each + * thread will use its own RNG that is seeded from the master RNG. If + * we are compiling without OpenMP, there is only one thread and it uses + * the master RNG. This section must be critical to ensure that only one + * thread is using the master RNG at the same time. */ +#ifdef _OPENMP + mt_rng_t private_rng; +#endif + mt_rng_t *p_rng; + double *ys; + long int i; + plfit_result_t result_synthetic; + +#ifdef _OPENMP +#pragma omp critical + { + p_rng = &private_rng; + mt_init_from_rng(p_rng, options->rng); + } +#else + p_rng = options->rng; +#endif + + /* Allocate memory to sample into */ + ys = calloc(n, sizeof(double)); + if (ys == 0) { + retval = PLFIT_ENOMEM; + } else { + /* The main for loop starts here. */ +#ifdef _OPENMP +#pragma omp for reduction(+:successes) +#endif + for (i = 0; i < num_trials; i++) { + plfit_i_resample_discrete(xs_head, num_smaller, n, result->alpha, + result->xmin, n, p_rng, ys); + if (xmin_fixed) { + plfit_estimate_alpha_discrete(ys, n, result->xmin, + &options_no_p_value, &result_synthetic); + } else { + plfit_discrete(ys, n, &options_no_p_value, &result_synthetic); + } + if (result_synthetic.D > result->D) + successes++; + } + + free(ys); + } + + /* End of parallelized part */ + } + + free(xs_head); + + if (retval == PLFIT_SUCCESS) { + result->p = successes / ((double)num_trials); + } else { + PLFIT_ERROR("cannot calculate exact p-value", retval); + } + + return retval; +} + +int plfit_log_likelihood_discrete(double* xs, size_t n, double alpha, double xmin, double* L) { + double result; + size_t m; + + if (alpha <= 1) { + PLFIT_ERROR("alpha must be greater than one", PLFIT_EINVAL); + } + XMIN_CHECK_ONE; + + plfit_i_logsum_less_than_discrete(xs, xs+n, xmin, &result, &m); + result = - alpha * result - m * hsl_sf_lnhzeta(alpha, xmin); + + *L = result; + + return PLFIT_SUCCESS; +} + +int plfit_estimate_alpha_discrete(double* xs, size_t n, double xmin, + const plfit_discrete_options_t* options, plfit_result_t *result) { + double *xs_copy, *begin, *end; + + if (!options) + options = &plfit_discrete_default_options; + + /* Check the validity of the input parameters */ + DATA_POINTS_CHECK; + if (options->alpha_method == PLFIT_LINEAR_SCAN) { + if (options->alpha.min <= 1.0) { + PLFIT_ERROR("alpha.min must be greater than 1.0", PLFIT_EINVAL); + } + if (options->alpha.max < options->alpha.min) { + PLFIT_ERROR("alpha.max must be greater than alpha.min", PLFIT_EINVAL); + } + if (options->alpha.step <= 0) { + PLFIT_ERROR("alpha.step must be positive", PLFIT_EINVAL); + } + } + + PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &xs_copy)); + + begin = xs_copy; end = xs_copy + n; + while (begin < end && *begin < xmin) + begin++; + + PLFIT_CHECK(plfit_i_estimate_alpha_discrete(begin, end-begin, xmin, &result->alpha, + options, /* sorted = */ 1)); + PLFIT_CHECK(plfit_i_ks_test_discrete(begin, end, result->alpha, xmin, &result->D)); + + result->xmin = xmin; + if (options->finite_size_correction) + plfit_i_perform_finite_size_correction(result, end-begin); + + PLFIT_CHECK(plfit_log_likelihood_discrete(begin, end-begin, result->alpha, + result->xmin, &result->L)); + PLFIT_CHECK(plfit_i_calculate_p_value_discrete(xs, n, options, 1, result)); + + free(xs_copy); + + return PLFIT_SUCCESS; +} + +int plfit_discrete(double* xs, size_t n, const plfit_discrete_options_t* options, + plfit_result_t* result) { + double curr_D, curr_alpha; + plfit_result_t best_result; + double *xs_copy, *px, *end, *end_xmin, prev_x; + size_t best_n; + int m; + + if (!options) + options = &plfit_discrete_default_options; + + /* Check the validity of the input parameters */ + DATA_POINTS_CHECK; + if (options->alpha_method == PLFIT_LINEAR_SCAN) { + if (options->alpha.min <= 1.0) { + PLFIT_ERROR("alpha.min must be greater than 1.0", PLFIT_EINVAL); + } + if (options->alpha.max < options->alpha.min) { + PLFIT_ERROR("alpha.max must be greater than alpha.min", PLFIT_EINVAL); + } + if (options->alpha.step <= 0) { + PLFIT_ERROR("alpha.step must be positive", PLFIT_EINVAL); + } + } + + PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &xs_copy)); + + best_result.D = DBL_MAX; + best_result.xmin = 1; + best_result.alpha = 1; + best_n = 0; + + /* Make sure there are at least three distinct values if possible */ + px = xs_copy; end = px + n; end_xmin = end - 1; m = 0; + prev_x = *end_xmin; + while (*end_xmin == prev_x && end_xmin > px) + end_xmin--; + prev_x = *end_xmin; + while (*end_xmin == prev_x && end_xmin > px) + end_xmin--; + + prev_x = 0; + while (px < end_xmin) { + while (px < end_xmin && *px == prev_x) { + px++; m++; + } + + plfit_i_estimate_alpha_discrete(px, n-m, *px, &curr_alpha, options, + /* sorted = */ 1); + plfit_i_ks_test_discrete(px, end, curr_alpha, *px, &curr_D); + + if (curr_D < best_result.D) { + best_result.alpha = curr_alpha; + best_result.xmin = *px; + best_result.D = curr_D; + best_n = n-m; + } + + prev_x = *px; + px++; m++; + } + + *result = best_result; + if (options->finite_size_correction) + plfit_i_perform_finite_size_correction(result, best_n); + + PLFIT_CHECK(plfit_log_likelihood_discrete(xs_copy+(n-best_n), best_n, + result->alpha, result->xmin, &result->L)); + PLFIT_CHECK(plfit_i_calculate_p_value_discrete(xs_copy, n, options, 0, result)); + + free(xs_copy); + + return PLFIT_SUCCESS; +} + +/***** resampling routines to generate synthetic replicates ****/ + +static int plfit_i_resample_continuous(double* xs_head, size_t num_smaller, + size_t n, double alpha, double xmin, size_t num_samples, mt_rng_t* rng, + double* result) +{ + size_t num_orig_samples, i; + + /* Calculate how many samples have to be drawn from xs_head */ + num_orig_samples = (size_t) plfit_rbinom(num_samples, num_smaller / (double)n, rng); + + /* Draw the samples from xs_head */ + for (i = 0; i < num_orig_samples; i++, result++) { + *result = xs_head[(size_t)plfit_runif(0, num_smaller, rng)]; + } + + /* Draw the remaining samples from the fitted distribution */ + PLFIT_CHECK(plfit_rpareto_array(xmin, alpha-1, num_samples-num_orig_samples, rng, + result)); + + return PLFIT_SUCCESS; +} + +int plfit_resample_continuous(double* xs, size_t n, double alpha, double xmin, + size_t num_samples, mt_rng_t* rng, double* result) { + double *xs_head; + size_t num_smaller = 0; + int retval; + + /* Extract the head of xs that contains elements smaller than xmin */ + xs_head = extract_smaller(xs, xs+n, xmin, &num_smaller); + if (xs_head == 0) + PLFIT_ERROR("cannot resample continuous dataset", PLFIT_ENOMEM); + + retval = plfit_i_resample_continuous(xs_head, num_smaller, n, alpha, xmin, + num_samples, rng, result); + + /* Free xs_head; we don't need it any more */ + free(xs_head); + + return retval; +} + +static int plfit_i_resample_discrete(double* xs_head, size_t num_smaller, size_t n, + double alpha, double xmin, size_t num_samples, mt_rng_t* rng, + double* result) +{ + size_t num_orig_samples, i; + + /* Calculate how many samples have to be drawn from xs_head */ + num_orig_samples = (size_t) plfit_rbinom(num_samples, num_smaller / (double)n, rng); + + /* Draw the samples from xs_head */ + for (i = 0; i < num_orig_samples; i++, result++) { + *result = xs_head[(size_t)plfit_runif(0, num_smaller, rng)]; + } + + /* Draw the remaining samples from the fitted distribution */ + PLFIT_CHECK(plfit_rzeta_array((long int)xmin, alpha, + num_samples-num_orig_samples, rng, result)); + + return PLFIT_SUCCESS; +} + +int plfit_resample_discrete(double* xs, size_t n, double alpha, double xmin, + size_t num_samples, mt_rng_t* rng, double* result) { + double *xs_head; + size_t num_smaller = 0; + int retval; + + /* Extract the head of xs that contains elements smaller than xmin */ + xs_head = extract_smaller(xs, xs+n, xmin, &num_smaller); + if (xs_head == 0) + PLFIT_ERROR("cannot resample discrete dataset", PLFIT_ENOMEM); + + retval = plfit_i_resample_discrete(xs_head, num_smaller, n, alpha, xmin, + num_samples, rng, result); + + /* Free xs_head; we don't need it any more */ + free(xs_head); + + return retval; +} + +/******** calculating the p-value of a fitted model only *******/ + +int plfit_calculate_p_value_continuous(double* xs, size_t n, + const plfit_continuous_options_t* options, plfit_bool_t xmin_fixed, + plfit_result_t *result) { + double* xs_copy; + + PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &xs_copy)); + PLFIT_CHECK(plfit_i_calculate_p_value_continuous(xs_copy, n, options, + xmin_fixed, result)); + free(xs_copy); + + return PLFIT_SUCCESS; +} + +int plfit_calculate_p_value_discrete(double* xs, size_t n, + const plfit_discrete_options_t* options, plfit_bool_t xmin_fixed, + plfit_result_t *result) { + double* xs_copy; + + PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &xs_copy)); + PLFIT_CHECK(plfit_i_calculate_p_value_discrete(xs_copy, n, options, + xmin_fixed, result)); + free(xs_copy); + + return PLFIT_SUCCESS; +} diff --git a/src/plfit/plfit.h b/src/plfit/plfit.h new file mode 100644 index 0000000..8c78437 --- /dev/null +++ b/src/plfit/plfit.h @@ -0,0 +1,143 @@ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* plfit.h + * + * Copyright (C) 2010-2011 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __PLFIT_H__ +#define __PLFIT_H__ + +#include +#include "mt.h" + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +__BEGIN_DECLS + +#define PLFIT_VERSION_MAJOR 0 +#define PLFIT_VERSION_MINOR 8 +#define PLFIT_VERSION_STRING "0.8" + +typedef unsigned short int plfit_bool_t; + +typedef enum { + PLFIT_LINEAR_ONLY, + PLFIT_STRATIFIED_SAMPLING, + PLFIT_GSS_OR_LINEAR, + PLFIT_DEFAULT_CONTINUOUS_METHOD = PLFIT_STRATIFIED_SAMPLING +} plfit_continuous_method_t; + +typedef enum { + PLFIT_LBFGS, + PLFIT_LINEAR_SCAN, + PLFIT_PRETEND_CONTINUOUS, + PLFIT_DEFAULT_DISCRETE_METHOD = PLFIT_LBFGS +} plfit_discrete_method_t; + +typedef enum { + PLFIT_P_VALUE_SKIP, + PLFIT_P_VALUE_APPROXIMATE, + PLFIT_P_VALUE_EXACT, + PLFIT_DEFAULT_P_VALUE_METHOD = PLFIT_P_VALUE_EXACT +} plfit_p_value_method_t; + +typedef struct _plfit_result_t { + double alpha; /* fitted power-law exponent */ + double xmin; /* cutoff where the power-law behaviour kicks in */ + double L; /* log-likelihood of the sample */ + double D; /* test statistic for the KS test */ + double p; /* p-value of the KS test */ +} plfit_result_t; + +/********** structure that holds the options of plfit **********/ + +typedef struct _plfit_continuous_options_t { + plfit_bool_t finite_size_correction; + plfit_continuous_method_t xmin_method; + plfit_p_value_method_t p_value_method; + double p_value_precision; + mt_rng_t* rng; +} plfit_continuous_options_t; + +typedef struct _plfit_discrete_options_t { + plfit_bool_t finite_size_correction; + plfit_discrete_method_t alpha_method; + struct { + double min; + double max; + double step; + } alpha; + plfit_p_value_method_t p_value_method; + double p_value_precision; + mt_rng_t* rng; +} plfit_discrete_options_t; + +int plfit_continuous_options_init(plfit_continuous_options_t* options); +int plfit_discrete_options_init(plfit_discrete_options_t* options); + +extern const plfit_continuous_options_t plfit_continuous_default_options; +extern const plfit_discrete_options_t plfit_discrete_default_options; + +/********** continuous power law distribution fitting **********/ + +int plfit_log_likelihood_continuous(double* xs, size_t n, double alpha, + double xmin, double* l); +int plfit_estimate_alpha_continuous(double* xs, size_t n, double xmin, + const plfit_continuous_options_t* options, plfit_result_t* result); +int plfit_continuous(double* xs, size_t n, + const plfit_continuous_options_t* options, plfit_result_t* result); + +/*********** discrete power law distribution fitting ***********/ + +int plfit_estimate_alpha_discrete(double* xs, size_t n, double xmin, + const plfit_discrete_options_t* options, plfit_result_t *result); +int plfit_log_likelihood_discrete(double* xs, size_t n, double alpha, double xmin, double* l); +int plfit_discrete(double* xs, size_t n, const plfit_discrete_options_t* options, + plfit_result_t* result); + +/***** resampling routines to generate synthetic replicates ****/ + +int plfit_resample_continuous(double* xs, size_t n, double alpha, double xmin, + size_t num_samples, mt_rng_t* rng, double* result); +int plfit_resample_discrete(double* xs, size_t n, double alpha, double xmin, + size_t num_samples, mt_rng_t* rng, double* result); + +/******** calculating the p-value of a fitted model only *******/ + +int plfit_calculate_p_value_continuous(double* xs, size_t n, + const plfit_continuous_options_t* options, plfit_bool_t xmin_fixed, + plfit_result_t *result); +int plfit_calculate_p_value_discrete(double* xs, size_t n, + const plfit_discrete_options_t* options, plfit_bool_t xmin_fixed, + plfit_result_t *result); + +/************* calculating descriptive statistics **************/ + +int plfit_moments(double* data, size_t n, double* mean, double* variance, + double* skewness, double* kurtosis); + +__END_DECLS + +#endif /* __PLFIT_H__ */ diff --git a/src/plfit/plfit.inc b/src/plfit/plfit.inc new file mode 100644 index 0000000..0d99c0c --- /dev/null +++ b/src/plfit/plfit.inc @@ -0,0 +1,10 @@ +PLFIT = plfit/error.c plfit/gss.c plfit/kolmogorov.c \ + plfit/lbfgs.c plfit/options.c plfit/plfit.c \ + plfit/hzeta.c plfit/mt.c plfit/sampling.c \ + plfit/rbinom.c \ + plfit/arithmetic_ansi.h plfit/arithmetic_sse_double.h plfit/arithmetic_sse_float.h \ + plfit/error.h plfit/gss.h plfit/kolmogorov.h \ + plfit/lbfgs.h plfit/platform.h plfit/plfit.h \ + plfit/hzeta.h plfit/mt.h plfit/sampling.h + + diff --git a/src/plfit/rbinom.c b/src/plfit/rbinom.c new file mode 100644 index 0000000..4746595 --- /dev/null +++ b/src/plfit/rbinom.c @@ -0,0 +1,209 @@ +/* + * Mathlib : A C Library of Special Functions + * Copyright (C) 1998 Ross Ihaka + * Copyright (C) 2000-2002 The R Core Team + * Copyright (C) 2007 The R Foundation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, a copy is available at + * http://www.r-project.org/Licenses/ + * + * SYNOPSIS + * + * #include + * double rbinom(double nin, double pp) + * + * DESCRIPTION + * + * Random variates from the binomial distribution. + * + * REFERENCE + * + * Kachitvichyanukul, V. and Schmeiser, B. W. (1988). + * Binomial random variate generation. + * Communications of the ACM 31, 216-222. + * (Algorithm BTPEC). + */ + +/* + * Modifications for this file were performed by Tamas Nepusz to make it fit + * better with plfit. The license of the original file applies to the + * modifications as well. + */ + +#include +#include +#include +#include "sampling.h" +#include "platform.h" + +#define repeat for(;;) + +double plfit_rbinom(double nin, double pp, mt_rng_t* rng) +{ + /* FIXME: These should become THREAD_specific globals : */ + + static double c, fm, npq, p1, p2, p3, p4, qn; + static double xl, xll, xlr, xm, xr; + + static double psave = -1.0; + static int nsave = -1; + static int m; + + double f, f1, f2, u, v, w, w2, x, x1, x2, z, z2; + double p, q, np, g, r, al, alv, amaxp, ffm, ynorm; + int i, ix, k, n; + + if (!isfinite(nin)) return NAN; + r = floor(nin + 0.5); + if (r != nin) return NAN; + if (!isfinite(pp) || + /* n=0, p=0, p=1 are not errors */ + r < 0 || pp < 0. || pp > 1.) return NAN; + + if (r == 0 || pp == 0.) return 0; + if (pp == 1.) return r; + + n = (int) r; + + p = fmin(pp, 1. - pp); + q = 1. - p; + np = n * p; + r = p / q; + g = r * (n + 1); + + /* Setup, perform only when parameters change [using static (globals): */ + + /* FIXING: Want this thread safe + -- use as little (thread globals) as possible + */ + if (pp != psave || n != nsave) { + psave = pp; + nsave = n; + if (np < 30.0) { + /* inverse cdf logic for mean less than 30 */ + qn = pow(q, (double) n); + goto L_np_small; + } else { + ffm = np + p; + m = (int) ffm; + fm = m; + npq = np * q; + p1 = (int)(2.195 * sqrt(npq) - 4.6 * q) + 0.5; + xm = fm + 0.5; + xl = xm - p1; + xr = xm + p1; + c = 0.134 + 20.5 / (15.3 + fm); + al = (ffm - xl) / (ffm - xl * p); + xll = al * (1.0 + 0.5 * al); + al = (xr - ffm) / (xr * q); + xlr = al * (1.0 + 0.5 * al); + p2 = p1 * (1.0 + c + c); + p3 = p2 + c / xll; + p4 = p3 + c / xlr; + } + } else if (n == nsave) { + if (np < 30.0) + goto L_np_small; + } + + /*-------------------------- np = n*p >= 30 : ------------------- */ + repeat { + u = plfit_runif_01(rng) * p4; + v = plfit_runif_01(rng); + /* triangular region */ + if (u <= p1) { + ix = (int)(xm - p1 * v + u); + goto finis; + } + /* parallelogram region */ + if (u <= p2) { + x = xl + (u - p1) / c; + v = v * c + 1.0 - fabs(xm - x) / p1; + if (v > 1.0 || v <= 0.) + continue; + ix = (int) x; + } else { + if (u > p3) { /* right tail */ + ix = (int)(xr - log(v) / xlr); + if (ix > n) + continue; + v = v * (u - p3) * xlr; + } else {/* left tail */ + ix = (int)(xl + log(v) / xll); + if (ix < 0) + continue; + v = v * (u - p2) * xll; + } + } + /* determine appropriate way to perform accept/reject test */ + k = abs(ix - m); + if (k <= 20 || k >= npq / 2 - 1) { + /* explicit evaluation */ + f = 1.0; + if (m < ix) { + for (i = m + 1; i <= ix; i++) + f *= (g / i - r); + } else if (m != ix) { + for (i = ix + 1; i <= m; i++) + f /= (g / i - r); + } + if (v <= f) + goto finis; + } else { + /* squeezing using upper and lower bounds on log(f(x)) */ + amaxp = (k / npq) * ((k * (k / 3. + 0.625) + 0.1666666666666) / npq + 0.5); + ynorm = -k * k / (2.0 * npq); + alv = log(v); + if (alv < ynorm - amaxp) + goto finis; + if (alv <= ynorm + amaxp) { + /* stirling's formula to machine accuracy */ + /* for the final acceptance/rejection test */ + x1 = ix + 1; + f1 = fm + 1.0; + z = n + 1 - fm; + w = n - ix + 1.0; + z2 = z * z; + x2 = x1 * x1; + f2 = f1 * f1; + w2 = w * w; + if (alv <= xm * log(f1 / x1) + (n - m + 0.5) * log(z / w) + (ix - m) * log(w * p / (x1 * q)) + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / f2) / f2) / f2) / f2) / f1 / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / z2) / z2) / z2) / z2) / z / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / x2) / x2) / x2) / x2) / x1 / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / w2) / w2) / w2) / w2) / w / 166320.) + goto finis; + } + } + } + + L_np_small: + /*---------------------- np = n*p < 30 : ------------------------- */ + + repeat { + ix = 0; + f = qn; + u = plfit_runif_01(rng); + repeat { + if (u < f) + goto finis; + if (ix > 110) + break; + u -= f; + ix++; + f *= (g / ix - r); + } + } + finis: + if (psave > 0.5) + ix = n - ix; + return (double)ix; +} + diff --git a/src/plfit/sampling.c b/src/plfit/sampling.c new file mode 100644 index 0000000..b58d91a --- /dev/null +++ b/src/plfit/sampling.c @@ -0,0 +1,301 @@ +/* sampling.c + * + * Copyright (C) 2012 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "error.h" +#include "sampling.h" +#include "platform.h" + +inline double plfit_runif(double lo, double hi, mt_rng_t* rng) { + if (rng == 0) { + return lo + rand() / ((double)RAND_MAX) * (hi-lo); + } + return lo + mt_uniform_01(rng) * (hi-lo); +} + +inline double plfit_runif_01(mt_rng_t* rng) { + if (rng == 0) { + return rand() / ((double)RAND_MAX); + } + return mt_uniform_01(rng); +} + +inline double plfit_rpareto(double xmin, double alpha, mt_rng_t* rng) { + if (alpha <= 0 || xmin <= 0) + return NAN; + + /* 1-u is used in the base here because we want to avoid the case of + * sampling zero */ + return pow(1-plfit_runif_01(rng), -1.0 / alpha) * xmin; +} + +int plfit_rpareto_array(double xmin, double alpha, size_t n, mt_rng_t* rng, + double* result) { + double gamma; + + if (alpha <= 0 || xmin <= 0) + return PLFIT_EINVAL; + + if (result == 0 || n == 0) + return PLFIT_SUCCESS; + + gamma = -1.0 / alpha; + while (n > 0) { + /* 1-u is used in the base here because we want to avoid the case of + * sampling zero */ + *result = pow(1-plfit_runif_01(rng), gamma) * xmin; + result++; n--; + } + + return PLFIT_SUCCESS; +} + +inline double plfit_rzeta(long int xmin, double alpha, mt_rng_t* rng) { + double u, v, t; + long int x; + double alpha_minus_1 = alpha-1; + double minus_1_over_alpha_minus_1 = -1.0 / (alpha-1); + double b; + double one_over_b_minus_1; + + if (alpha <= 0 || xmin < 1) + return NAN; + + xmin = (long int) round(xmin); + + /* Rejection sampling for the win. We use Y=floor(U^{-1/alpha} * xmin) as the + * envelope distribution, similarly to Chapter X.6 of Luc Devroye's book + * (where xmin is assumed to be 1): http://luc.devroye.org/chapter_ten.pdf + * + * Some notes that should help me recover what I was doing: + * + * p_i = 1/zeta(alpha, xmin) * i^-alpha + * q_i = (xmin/i)^{alpha-1} - (xmin/(i+1))^{alpha-1} + * = (i/xmin)^{1-alpha} - ((i+1)/xmin)^{1-alpha} + * = [i^{1-alpha} - (i+1)^{1-alpha}] / xmin^{1-alpha} + * + * p_i / q_i attains its maximum at xmin=i, so the rejection constant is: + * + * c = p_xmin / q_xmin + * + * We have to accept the sample if V <= (p_i / q_i) * (q_xmin / p_xmin) = + * (i/xmin)^-alpha * [xmin^{1-alpha} - (xmin+1)^{1-alpha}] / [i^{1-alpha} - (i+1)^{1-alpha}] = + * [xmin - xmin^alpha / (xmin+1)^{alpha-1}] / [i - i^alpha / (i+1)^{alpha-1}] = + * xmin/i * [1-(xmin/(xmin+1))^{alpha-1}]/[1-(i/(i+1))^{alpha-1}] + * + * In other words (and substituting i with X, which is the same), + * + * V * (X/xmin) <= [1 - (1+1/xmin)^{1-alpha}] / [1 - (1+1/i)^{1-alpha}] + * + * Let b := (1+1/xmin)^{alpha-1} and let T := (1+1/i)^{alpha-1}. Then: + * + * V * (X/xmin) <= [(b-1)/b] / [(T-1)/T] + * V * (X/xmin) * (T-1) / (b-1) <= T / b + * + * which is the same as in Devroye's book, except for the X/xmin term, and + * the definition of b. + */ + b = pow(1 + 1.0/xmin, alpha_minus_1); + one_over_b_minus_1 = 1.0/(b-1); + do { + do { + u = plfit_runif_01(rng); + v = plfit_runif_01(rng); + /* 1-u is used in the base here because we want to avoid the case of + * having zero in x */ + x = (long int) floor(pow(1-u, minus_1_over_alpha_minus_1) * xmin); + } while (x < xmin); + t = pow((x+1.0)/x, alpha_minus_1); + } while (v*x*(t-1)*one_over_b_minus_1*b > t*xmin); + + return x; +} + +int plfit_rzeta_array(long int xmin, double alpha, size_t n, mt_rng_t* rng, + double* result) { + double u, v, t; + long int x; + double alpha_minus_1 = alpha-1; + double minus_1_over_alpha_minus_1 = -1.0 / (alpha-1); + double b, one_over_b_minus_1; + + if (alpha <= 0 || xmin < 1) + return PLFIT_EINVAL; + + if (result == 0 || n == 0) + return PLFIT_SUCCESS; + + /* See the comments in plfit_rzeta for an explanation of the algorithm + * below. */ + xmin = (long int) round(xmin); + b = pow(1 + 1.0/xmin, alpha_minus_1); + one_over_b_minus_1 = 1.0/(b-1); + + while (n > 0) { + do { + do { + u = plfit_runif_01(rng); + v = plfit_runif_01(rng); + /* 1-u is used in the base here because we want to avoid the case of + * having zero in x */ + x = (long int) floor(pow(1-u, minus_1_over_alpha_minus_1) * xmin); + } while (x < xmin); /* handles overflow as well */ + t = pow((x+1.0)/x, alpha_minus_1); + } while (v*x*(t-1)*one_over_b_minus_1*b > t*xmin); + *result = x; + if (x < 0) abort(); + result++; n--; + } + + return PLFIT_SUCCESS; +} + +int plfit_walker_alias_sampler_init(plfit_walker_alias_sampler_t* sampler, + double* ps, size_t n) { + double *p, *p2, *ps_end; + double sum; + long int *short_sticks, *long_sticks; + long int num_short_sticks, num_long_sticks; + size_t i; + + sampler->num_bins = n; + + ps_end = ps + n; + + /* Initialize indexes and probs */ + sampler->indexes = (long int*)calloc(n, sizeof(long int)); + if (sampler->indexes == 0) { + return PLFIT_ENOMEM; + } + sampler->probs = (double*)calloc(n, sizeof(double)); + if (sampler->probs == 0) { + free(sampler->indexes); + return PLFIT_ENOMEM; + } + + /* Normalize the probability vector; count how many short and long sticks + * are there initially */ + for (sum = 0.0, p = ps; p != ps_end; p++) { + sum += *p; + } + sum = n / sum; + + num_short_sticks = num_long_sticks = 0; + for (p = ps, p2 = sampler->probs; p != ps_end; p++, p2++) { + *p2 = *p * sum; + if (*p2 < 1) { + num_short_sticks++; + } else if (*p2 > 1) { + num_long_sticks++; + } + } + + /* Allocate space for short & long stick indexes */ + long_sticks = (long int*)calloc(num_long_sticks, sizeof(long int)); + if (long_sticks == 0) { + free(sampler->probs); + free(sampler->indexes); + return PLFIT_ENOMEM; + } + short_sticks = (long int*)calloc(num_long_sticks, sizeof(long int)); + if (short_sticks == 0) { + free(sampler->probs); + free(sampler->indexes); + free(long_sticks); + return PLFIT_ENOMEM; + } + + /* Initialize short_sticks and long_sticks */ + num_short_sticks = num_long_sticks = 0; + for (i = 0, p = sampler->probs; i < n; i++, p++) { + if (*p < 1) { + short_sticks[num_short_sticks++] = i; + } else if (*p > 1) { + long_sticks[num_long_sticks++] = i; + } + } + + /* Prepare the index table */ + while (num_short_sticks && num_long_sticks) { + long int short_index, long_index; + short_index = short_sticks[--num_short_sticks]; + long_index = long_sticks[num_long_sticks-1]; + sampler->indexes[short_index] = long_index; + sampler->probs[long_index] = /* numerical stability */ + (sampler->probs[long_index] + sampler->probs[short_index]) - 1; + if (sampler->probs[long_index] < 1) { + short_sticks[num_short_sticks++] = long_index; + num_long_sticks--; + } + } + + /* Fix numerical stability issues */ + while (num_long_sticks) { + i = long_sticks[--num_long_sticks]; + sampler->probs[i] = 1; + } + while (num_short_sticks) { + i = short_sticks[--num_short_sticks]; + sampler->probs[i] = 1; + } + + return PLFIT_SUCCESS; +} + + +void plfit_walker_alias_sampler_destroy(plfit_walker_alias_sampler_t* sampler) { + if (sampler->indexes) { + free(sampler->indexes); + sampler->indexes = 0; + } + if (sampler->probs) { + free(sampler->probs); + sampler->probs = 0; + } +} + + +int plfit_walker_alias_sampler_sample(const plfit_walker_alias_sampler_t* sampler, + long int *xs, size_t n, mt_rng_t* rng) { + double u; + long int j; + long int *x; + + x = xs; + + if (rng == 0) { + /* Using built-in RNG */ + while (n > 0) { + u = rand() / ((double)RAND_MAX); + j = rand() % sampler->num_bins; + *x = (u < sampler->probs[j]) ? j : sampler->indexes[j]; + n--; x++; + } + } else { + /* Using Mersenne Twister */ + while (n > 0) { + u = mt_uniform_01(rng); + j = mt_random(rng) % sampler->num_bins; + *x = (u < sampler->probs[j]) ? j : sampler->indexes[j]; + n--; x++; + } + } + + return PLFIT_SUCCESS; +} diff --git a/src/plfit/sampling.h b/src/plfit/sampling.h new file mode 100644 index 0000000..2a2cee6 --- /dev/null +++ b/src/plfit/sampling.h @@ -0,0 +1,177 @@ +/* sampling.h + * + * Copyright (C) 2012 Tamas Nepusz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __SAMPLING_H__ +#define __SAMPLING_H__ + +#include +#include "mt.h" + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +__BEGIN_DECLS + +/** + * Draws a sample from a binomial distribution with the given count and + * probability values. + * + * This function is borrowed from R; see the corresponding license in + * \c rbinom.c. The return value is always an integer. + * + * The function is \em not thread-safe. + * + * \param n the number of trials + * \param p the success probability of each trial + * \param rng the Mersenne Twister random number generator to use + * \return the value drawn from the given binomial distribution. + */ +double plfit_rbinom(double n, double p, mt_rng_t* rng); + +/** + * Draws a sample from a Pareto distribution with the given minimum value and + * power-law exponent. + * + * \param xmin the minimum value of the distribution. Must be positive. + * \param alpha the exponent. Must be positive + * \param rng the Mersenne Twister random number generator to use + * + * \return the sample or NaN if one of the parameters is invalid + */ +extern double plfit_rpareto(double xmin, double alpha, mt_rng_t* rng); + +/** + * Draws a given number of samples from a Pareto distribution with the given + * minimum value and power-law exponent. + * + * \param xmin the minimum value of the distribution. Must be positive. + * \param alpha the exponent. Must be positive + * \param n the number of samples to draw + * \param rng the Mersenne Twister random number generator to use + * \param result the array where the result should be written. It must + * have enough space to store n items + * + * \return \c PLFIT_EINVAL if one of the parameters is invalid, zero otherwise + */ +int plfit_rpareto_array(double xmin, double alpha, size_t n, mt_rng_t* rng, + double* result); + +/** + * Draws a sample from a zeta distribution with the given minimum value and + * power-law exponent. + * + * \param xmin the minimum value of the distribution. Must be positive. + * \param alpha the exponent. Must be positive + * \param rng the Mersenne Twister random number generator to use + * + * \return the sample or NaN if one of the parameters is invalid + */ +extern double plfit_rzeta(long int xmin, double alpha, mt_rng_t* rng); + +/** + * Draws a given number of samples from a zeta distribution with the given + * minimum value and power-law exponent. + * + * \param xmin the minimum value of the distribution. Must be positive. + * \param alpha the exponent. Must be positive + * \param n the number of samples to draw + * \param rng the Mersenne Twister random number generator to use + * \param result the array where the result should be written. It must + * have enough space to store n items + * + * \return \c PLFIT_EINVAL if one of the parameters is invalid, zero otherwise + */ +int plfit_rzeta_array(long int xmin, double alpha, size_t n, mt_rng_t* rng, + double* result); + +/** + * Draws a sample from a uniform distribution with the given lower and + * upper bounds. + * + * The lower bound is inclusive, the uppoer bound is not. + * + * \param lo the lower bound + * \param hi the upper bound + * \param rng the Mersenne Twister random number generator to use + * \return the value drawn from the given uniform distribution. + */ +extern double plfit_runif(double lo, double hi, mt_rng_t* rng); + +/** + * Draws a sample from a uniform distribution over the [0; 1) interval. + * + * The interval is closed from the left and open from the right. + * + * \param rng the Mersenne Twister random number generator to use + * \return the value drawn from the given uniform distribution. + */ +extern double plfit_runif_01(mt_rng_t* rng); + +/** + * Random sampler using Walker's alias method. + */ +typedef struct { + long int num_bins; /**< Number of bins */ + long int* indexes; /**< Index of the "other" element in each bin */ + double* probs; /**< Probability of drawing the "own" element from a bin */ +} plfit_walker_alias_sampler_t; + +/** + * \brief Initializes the sampler with item probabilities. + * + * \param sampler the sampler to initialize + * \param ps pointer to an array containing a value proportional to the + * sampling probability of each item in the set being sampled. + * \param n the number of items in the array + * \return error code + */ +int plfit_walker_alias_sampler_init(plfit_walker_alias_sampler_t* sampler, + double* ps, size_t n); + +/** + * \brief Destroys an initialized sampler and frees the allocated memory. + * + * \param sampler the sampler to destroy + */ +void plfit_walker_alias_sampler_destroy(plfit_walker_alias_sampler_t* sampler); + +/** + * \brief Draws a given number of samples from the sampler and writes them + * to a given array. + * + * \param sampler the sampler to use + * \param xs pointer to an array where the sampled items should be + * written + * \param n the number of samples to draw + * \param rng the Mersenne Twister random number generator to use + * \return error code + */ +int plfit_walker_alias_sampler_sample(const plfit_walker_alias_sampler_t* sampler, + long int* xs, size_t n, mt_rng_t* rng); + +__END_DECLS + +#endif diff --git a/src/pottsmodel_2.cpp b/src/pottsmodel_2.cpp new file mode 100644 index 0000000..594d74b --- /dev/null +++ b/src/pottsmodel_2.cpp @@ -0,0 +1,2224 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + This file was modified by Vincent Traag + The original copyright notice follows here */ + +/*************************************************************************** + pottsmodel.cpp - description + ------------------- + begin : Fri May 28 2004 + copyright : (C) 2004 by + email : + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "pottsmodel_2.h" +#include "NetRoutines.h" + +#include "igraph_random.h" +#include "igraph_interrupt_internal.h" +#include "config.h" + +#include +#include + +using namespace std; + +//################################################################################################# +PottsModel::PottsModel(network *n, unsigned int qvalue, int m) : acceptance(0) { + DLList_Iter iter; + NNode *n_cur; + unsigned int *i_ptr; + net = n; + q = qvalue; + operation_mode = m; + k_max = 0; + //needed in calculating modularity + Qa = new double[q + 1]; + //weights for each spin state needed in Monte Carlo process + weights = new double[q + 1]; + //bookkeeping of occupation numbers of spin states or the number of links in community + color_field = new double[q + 1]; + neighbours = new double[q + 1]; + + num_of_nodes = net->node_list->Size(); + num_of_links = net->link_list->Size(); + + n_cur = iter.First(net->node_list); + //these lists are needed to keep track of spin states for parallel update mode + new_spins = new DL_Indexed_List(); + previous_spins = new DL_Indexed_List(); + while (!iter.End()) { + if (k_max < n_cur->Get_Degree()) { + k_max = n_cur->Get_Degree(); + } + i_ptr = new unsigned int; + *i_ptr = 0; + new_spins->Push(i_ptr); + i_ptr = new unsigned int; + *i_ptr = 0; + previous_spins->Push(i_ptr); + n_cur = iter.Next(); + } + return; +} +//####################################################### +//Destructor of PottsModel +//######################################################## +PottsModel::~PottsModel() { + /* The DLItem destructor does not delete its item currently, + because of some bad design. As a workaround, we delete them here + by hand */ + new_spins->delete_items(); + previous_spins->delete_items(); + delete new_spins; + delete previous_spins; + delete [] Qa; + delete [] weights; + delete [] color_field; + delete [] neighbours; + return; +} +//##################################################### +//Assing an initial random configuration of spins to nodes +//if called with negative argument or the spin used as argument +//when called with positve one. +//This may be handy, if you want to warm up the network. +//#################################################### +unsigned long PottsModel::assign_initial_conf(int spin) { + int s; + DLList_Iter iter; + DLList_Iter l_iter; + NNode *n_cur; + NLink *l_cur; + double sum_weight; + double av_k_squared = 0.0; + double av_k = 0.0; +// printf("Assigning initial configuration...\n"); + // initialize colorfield + for (unsigned int i = 0; i <= q; i++) { + color_field[i] = 0.0; + } + // + total_degree_sum = 0.0; + n_cur = iter.First(net->node_list); + while (!iter.End()) { + if (spin < 0) { + s = RNG_INTEGER(1, q); + } else { + s = spin; + } + n_cur->Set_ClusterIndex(s); + l_cur = l_iter.First(n_cur->Get_Links()); + sum_weight = 0; + while (!l_iter.End()) { + sum_weight += l_cur->Get_Weight(); //weight should be one, in case we are not using it. + l_cur = l_iter.Next(); + } + // we set the sum of the weights or the degree as the weight of the node, this way + // we do not have to calculate it again. + n_cur->Set_Weight(sum_weight); + av_k_squared += sum_weight * sum_weight; + av_k += sum_weight; + + // in case we want all links to be contribute equally - parameter gamm=fixed + if (operation_mode == 0) { + color_field[s]++; + } else { + color_field[s] += sum_weight; + } + // or in case we want to use a weight of each link that is proportional to k_i\times k_j + total_degree_sum += sum_weight; + n_cur = iter.Next(); + } + av_k_squared /= double(net->node_list->Size()); + av_k /= double(net->node_list->Size()); + // total_degree_sum-=av_k_squared/av_k; +// printf("Total Degree Sum=2M=%f\n",total_degree_sum); + return net->node_list->Size(); +} +//##################################################################### +//If I ever manage to write a decent LookUp function, it will be here +//##################################################################### +unsigned long PottsModel::initialize_lookup(double kT, double gamma) { + IGRAPH_UNUSED(kT); + IGRAPH_UNUSED(gamma); + /* + double beta; + // the look-up table contains all entries of exp(-beta(-neighbours+gamma*h)) + // as needed in the HeatBath algorithm + beta=1.0/kT; + for (long w=0; w<=k_max+num_of_nodes; w++) + { + neg_lookup[w]=exp(-beta*-w + } + delta_ij[0]=1.0; + for (long w=-num_of_nodes-k_max; w<=k_max+num_of_nodes; w++) + { + + } + + // wenn wir spaeter exp(-1/kT*gamma*(nk+1-nj) fuer eine spin-flip von j nach k benoetigen schauen wir nur noch hier nach + for (unsigned long n=1; n<=num_of_nodes; n++) + { + gamma_term[n]=exp(-double(n)/kT*gamma); + } + gamma_term[0]=1.0; + */ + return 1; +} +//##################################################################### +// Q denotes the modulary of the network +// This function calculates it initially +// In the event of a spin changing its state, it only needs updating +// Note that Qmatrix and Qa are only counting! The normalization +// by num_of_links is done later +//#################################################################### +double PottsModel::initialize_Qmatrix(void) { + DLList_Iter l_iter; + NLink *l_cur; + unsigned int i, j; + //initialize with zeros + num_of_links = net->link_list->Size(); + for (i = 0; i <= q; i++) { + Qa[i] = 0.0; + for (j = i; j <= q; j++) { + Qmatrix[i][j] = 0.0; + Qmatrix[j][i] = 0.0; + } + } + //go over all links and make corresponding entries in Q matrix + //An edge connecting state i wiht state j will get an entry in Qij and Qji + l_cur = l_iter.First(net->link_list); + while (!l_iter.End()) { + i = l_cur->Get_Start()->Get_ClusterIndex(); + j = l_cur->Get_End()->Get_ClusterIndex(); + //printf("%d %d\n",i,j); + Qmatrix[i][j] += l_cur->Get_Weight(); + Qmatrix[j][i] += l_cur->Get_Weight(); + + l_cur = l_iter.Next(); + } + //Finally, calculate sum over rows and keep in Qa + for (i = 0; i <= q; i++) { + for (j = 0; j <= q; j++) { + Qa[i] += Qmatrix[i][j]; + } + } + return calculate_Q(); +} +//#################################################################### +// This function does the actual calculation of Q from the matrix +// The normalization by num_of_links is done here +//#################################################################### +double PottsModel::calculate_Q() { + double Q = 0.0; + for (unsigned int i = 0; i <= q; i++) { + Q += Qmatrix[i][i] - Qa[i] * Qa[i] / double(2.0 * net->sum_weights); + if ((Qa[i] < 0.0) || Qmatrix[i][i] < 0.0) { +// printf("Negatives Qa oder Qii\n\n\n"); + //printf("Press any key to continue\n\n"); + //cin >> Q; + } + } + Q /= double(2.0 * net->sum_weights); + return Q; +} +double PottsModel::calculate_genQ(double gamma) { + double Q = 0.0; + for (unsigned int i = 0; i <= q; i++) { + Q += Qmatrix[i][i] - gamma * Qa[i] * Qa[i] / double(2.0 * net->sum_weights); + if ((Qa[i] < 0.0) || Qmatrix[i][i] < 0.0) { +// printf("Negatives Qa oder Qii\n\n\n"); + //printf("Press any key to continue\n\n"); + //cin >> Q; + } + } + Q /= double(2.0 * net->sum_weights); + return Q; +} +//####################################################################### +// This function calculates the Energy for the standard Hamiltonian +// given a particular value of gamma and the current spin states +// ##################################################################### +double PottsModel::calculate_energy(double gamma) { + double e = 0.0; + DLList_Iter l_iter; + NLink *l_cur; + l_cur = l_iter.First(net->link_list); + //every in-cluster edge contributes -1 + while (!l_iter.End()) { + if (l_cur->Get_Start()->Get_ClusterIndex() == l_cur->Get_End()->Get_ClusterIndex()) { + e--; + }; + l_cur = l_iter.Next(); + } + //and the penalty term contributes according to cluster sizes + for (unsigned int i = 1; i <= q; i++) { + e += gamma * 0.5 * double(color_field[i]) * double((color_field[i] - 1)); + } + energy = e; + return e; +} +//########################################################################## +// We would like to start from a temperature with at least 95 of all proposed +// spin changes accepted in 50 sweeps over the network +// The function returns the Temperature found +//######################################################################### +double PottsModel::FindStartTemp(double gamma, double prob, double ts) { + double kT; + kT = ts; + //assing random initial condition + assign_initial_conf(-1); + //initialize Modularity matrix, from now on, it will be updated at every spin change + initialize_Qmatrix(); + // the factor 1-1/q is important, since even, at infinite temperature, + // only 1-1/q of all spins do change their state, since a randomly chooses new + // state is with prob. 1/q the old state. + while (acceptance < (1.0 - 1.0 / double(q)) * 0.95) { //want 95% acceptance + kT = kT * 1.1; + // if I ever have a lookup table, it will need initialization for every kT + //initialize_lookup(kT,k_max,net->node_list->Size()); + HeatBathParallelLookup(gamma, prob, kT, 50); +// printf("kT=%f acceptance=%f\n", kT, acceptance); + } + kT *= 1.1; // just to be sure... +// printf("Starting with acceptance ratio: %1.6f bei kT=%2.4f\n",acceptance,kT); + return kT; +} + +//############################################################## +//This function does a parallel update at zero T +//Hence, it is really fast on easy problems +//max sweeps is the maximum number of sweeps it should perform, +//if it does not converge earlier +//############################################################## +long PottsModel::HeatBathParallelLookupZeroTemp(double gamma, double prob, unsigned int max_sweeps) { + DLList_Iter iter, net_iter; + DLList_Iter l_iter; + DLList_Iter i_iter, i_iter2; + NNode *node, *n_cur; + NLink *l_cur; + unsigned int *SPIN, *P_SPIN, new_spin, spin_opt, old_spin, spin, sweep; + // long h; // degree; + unsigned long changes; + double h, delta = 0, deltaE, deltaEmin, w, degree; + //HugeArray neighbours; + bool cyclic = 0; + + sweep = 0; + changes = 1; + while (sweep < max_sweeps && changes) { + cyclic = true; + sweep++; + changes = 0; + //Loop over all nodes + node = net_iter.First(net->node_list); + SPIN = i_iter.First(new_spins); + while (!net_iter.End()) { + // How many neigbors of each type? + // set them all zero + for (unsigned int i = 0; i <= q; i++) { + neighbours[i] = 0; + } + degree = node->Get_Weight(); + //Loop over all links (=neighbours) + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + //printf("%s %s\n",node->Get_Name(),n_cur->Get_Name()); + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + neighbours[n_cur->Get_ClusterIndex()] += w; + l_cur = l_iter.Next(); + } + //Search optimal Spin + old_spin = node->Get_ClusterIndex(); + //degree=node->Get_Degree(); + switch (operation_mode) { + case 0: { + delta = 1.0; + break; + } + case 1: { //newman modularity + prob = degree / total_degree_sum; + delta = degree; + break; + } + } + + + spin_opt = old_spin; + deltaEmin = 0.0; + for (spin = 1; spin <= q; spin++) { // all possible spin states + if (spin != old_spin) { + h = color_field[spin] + delta - color_field[old_spin]; + deltaE = double(neighbours[old_spin] - neighbours[spin]) + gamma * prob * double(h); + if (deltaE < deltaEmin) { + spin_opt = spin; + deltaEmin = deltaE; + } + } + } // for spin + + //Put optimal spin on list for later update + *SPIN = spin_opt; + node = net_iter.Next(); + SPIN = i_iter.Next(); + } // while !net_iter.End() + + //------------------------------- + //Now set all spins to new values + node = net_iter.First(net->node_list); + SPIN = i_iter.First(new_spins); + P_SPIN = i_iter2.First(previous_spins); + while (!net_iter.End()) { + old_spin = node->Get_ClusterIndex(); + new_spin = *SPIN; + if (new_spin != old_spin) { // Do we really have a change?? + changes++; + node->Set_ClusterIndex(new_spin); + //this is important!! + //In Parallel update, there occur cyclic attractors of size two + //which then make the program run for ever + if (new_spin != *P_SPIN) { + cyclic = false; + } + *P_SPIN = old_spin; + color_field[old_spin]--; + color_field[new_spin]++; + + //Qmatrix update + //iteration over all neighbours + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + Qmatrix[old_spin][n_cur->Get_ClusterIndex()] -= w; + Qmatrix[new_spin][n_cur->Get_ClusterIndex()] += w; + Qmatrix[n_cur->Get_ClusterIndex()][old_spin] -= w; + Qmatrix[n_cur->Get_ClusterIndex()][new_spin] += w; + Qa[old_spin] -= w; + Qa[new_spin] += w; + l_cur = l_iter.Next(); + } // while l_iter + } + node = net_iter.Next(); + SPIN = i_iter.Next(); + P_SPIN = i_iter2.Next(); + } // while (!net_iter.End()) + } // while markov + + // In case of a cyclic attractor, we want to interrupt + if (cyclic) { +// printf("Cyclic attractor!\n"); + acceptance = 0.0; + return 0; + } else { + acceptance = double(changes) / double(num_of_nodes); + return changes; + } +} +//################################################################################### +//The same function as before, but rather than parallel update, it pics the nodes to update +//randomly +//################################################################################### +double PottsModel::HeatBathLookupZeroTemp(double gamma, double prob, unsigned int max_sweeps) { + DLList_Iter iter; + DLList_Iter l_iter; + DLList_Iter i_iter, i_iter2; + NNode *node, *n_cur; + NLink *l_cur; + unsigned int new_spin, spin_opt, old_spin, spin, sweep; + long r;// degree; + unsigned long changes; + double delta = 0, h, deltaE, deltaEmin, w, degree; + //HugeArray neighbours; + + sweep = 0; + changes = 0; + while (sweep < max_sweeps) { + sweep++; + //ueber alle Knoten im Netz + for (unsigned long n = 0; n < num_of_nodes; n++) { + r = -1; + while ((r < 0) || (r > (long)num_of_nodes - 1)) { + r = RNG_INTEGER(0, num_of_nodes - 1); + } + /* r=long(double(num_of_nodes*double(rand())/double(RAND_MAX+1.0)));*/ + node = net->node_list->Get(r); + // Wir zaehlen, wieviele Nachbarn von jedem spin vorhanden sind + // erst mal alles Null setzen + for (unsigned int i = 0; i <= q; i++) { + neighbours[i] = 0; + } + degree = node->Get_Weight(); + //Loop over all links (=neighbours) + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + //printf("%s %s\n",node->Get_Name(),n_cur->Get_Name()); + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + neighbours[n_cur->Get_ClusterIndex()] += w; + l_cur = l_iter.Next(); + } + //Search optimal Spin + old_spin = node->Get_ClusterIndex(); + //degree=node->Get_Degree(); + switch (operation_mode) { + case 0: { + delta = 1.0; + break; + } + case 1: { //newman modularity + prob = degree / total_degree_sum; + delta = degree; + break; + } + } + + + spin_opt = old_spin; + deltaEmin = 0.0; + for (spin = 1; spin <= q; spin++) { // alle moeglichen Spins + if (spin != old_spin) { + h = color_field[spin] + delta - color_field[old_spin]; + deltaE = double(neighbours[old_spin] - neighbours[spin]) + gamma * prob * double(h); + if (deltaE < deltaEmin) { + spin_opt = spin; + deltaEmin = deltaE; + } + } + } // for spin + + //------------------------------- + //Now update the spins + new_spin = spin_opt; + if (new_spin != old_spin) { // Did we really change something?? + changes++; + node->Set_ClusterIndex(new_spin); + color_field[old_spin] -= delta; + color_field[new_spin] += delta; + + //Qmatrix update + //iteration over all neighbours + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + Qmatrix[old_spin][n_cur->Get_ClusterIndex()] -= w; + Qmatrix[new_spin][n_cur->Get_ClusterIndex()] += w; + Qmatrix[n_cur->Get_ClusterIndex()][old_spin] -= w; + Qmatrix[n_cur->Get_ClusterIndex()][new_spin] += w; + Qa[old_spin] -= w; + Qa[new_spin] += w; + l_cur = l_iter.Next(); + } // while l_iter + } + } // for n + } // while markov + + acceptance = double(changes) / double(num_of_nodes) / double(sweep); + return acceptance; +} +//##################################################################################### +//This function performs a parallel update at Terperature T +//##################################################################################### +long PottsModel::HeatBathParallelLookup(double gamma, double prob, double kT, unsigned int max_sweeps) { + DLList_Iter iter, net_iter; + DLList_Iter l_iter; + DLList_Iter i_iter, i_iter2; + NNode *node, *n_cur; + NLink *l_cur; + unsigned int new_spin, spin_opt, old_spin; + unsigned int *SPIN, *P_SPIN; + unsigned int sweep; + long max_q; + unsigned long changes, /*degree,*/ problemcount; + //HugeArray neighbours; + double h, delta = 0, norm, r, beta, minweight, prefac = 0, w, degree; + bool cyclic = 0, found; + unsigned long num_of_nodes; + + sweep = 0; + changes = 1; + num_of_nodes = net->node_list->Size(); + while (sweep < max_sweeps && changes) { + cyclic = true; + sweep++; + changes = 0; + //Loop over all nodes + node = net_iter.First(net->node_list); + SPIN = i_iter.First(new_spins); + while (!net_iter.End()) { + // Initialize neighbours and weights + problemcount = 0; + for (unsigned int i = 0; i <= q; i++) { + neighbours[i] = 0; + weights[i] = 0; + } + norm = 0.0; + degree = node->Get_Weight(); + //Loop over all links (=neighbours) + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + //printf("%s %s\n",node->Get_Name(),n_cur->Get_Name()); + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + neighbours[n_cur->Get_ClusterIndex()] += w; + l_cur = l_iter.Next(); + } + //Search optimal Spin + old_spin = node->Get_ClusterIndex(); + //degree=node->Get_Degree(); + switch (operation_mode) { + case 0: { + prefac = 1.0; + delta = 1.0; + break; + } + case 1: { //newman modularity + prefac = 1.0; + prob = degree / total_degree_sum; + delta = degree; + break; + } + } + spin_opt = old_spin; + beta = 1.0 / kT * prefac; + minweight = 0.0; + weights[old_spin] = 0.0; + for (unsigned spin = 1; spin <= q; spin++) { // loop over all possible new spins + if (spin != old_spin) { // only if we have a different than old spin! + h = color_field[spin] + delta - color_field[old_spin]; + weights[spin] = double(neighbours[old_spin] - neighbours[spin]) + gamma * prob * double(h); + if (weights[spin] < minweight) { + minweight = weights[spin]; + } + } + } // for spin + for (unsigned spin = 1; spin <= q; spin++) { // loop over all possibe spins + weights[spin] -= minweight; // subtract minweight + // to avoid numerical problems with large exponents + weights[spin] = exp(-beta * weights[spin]); + norm += weights[spin]; + } // for spin + + //now choose a new spin + r = RNG_UNIF(0, norm); + /* norm*double(rand())/double(RAND_MAX + 1.0); */ + new_spin = 1; + found = false; + while (!found && new_spin <= q) { + if (r <= weights[new_spin]) { + spin_opt = new_spin; + found = true; + break; + } else { + r -= weights[new_spin]; + } + new_spin++; + } + if (!found) { +// printf("."); + problemcount++; + } + //Put new spin on list + *SPIN = spin_opt; + + node = net_iter.Next(); + SPIN = i_iter.Next(); + } // while !net_iter.End() + + //------------------------------- + //now update all spins + node = net_iter.First(net->node_list); + SPIN = i_iter.First(new_spins); + P_SPIN = i_iter2.First(previous_spins); + while (!net_iter.End()) { + old_spin = node->Get_ClusterIndex(); + new_spin = *SPIN; + if (new_spin != old_spin) { // Did we really change something?? + changes++; + node->Set_ClusterIndex(new_spin); + if (new_spin != *P_SPIN) { + cyclic = false; + } + *P_SPIN = old_spin; + color_field[old_spin] -= delta; + color_field[new_spin] += delta; + + //Qmatrix update + //iteration over all neighbours + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + Qmatrix[old_spin][n_cur->Get_ClusterIndex()] -= w; + Qmatrix[new_spin][n_cur->Get_ClusterIndex()] += w; + Qmatrix[n_cur->Get_ClusterIndex()][old_spin] -= w; + Qmatrix[n_cur->Get_ClusterIndex()][new_spin] += w; + Qa[old_spin] -= w; + Qa[new_spin] += w; + l_cur = l_iter.Next(); + } // while l_iter + } + node = net_iter.Next(); + SPIN = i_iter.Next(); + P_SPIN = i_iter2.Next(); + } // while (!net_iter.End()) + + } // while markov + max_q = 0; + for (unsigned int i = 1; i <= q; i++) if (color_field[i] > max_q) { + max_q = long(color_field[i]); + } + + //again, we would not like to end up in cyclic attractors + if (cyclic && changes) { +// printf("Cyclic attractor!\n"); + acceptance = double(changes) / double(num_of_nodes); + return 0; + } else { + acceptance = double(changes) / double(num_of_nodes); + return changes; + } +} +//############################################################## +// This is the function generally used for optimisation, +// as the parallel update has its flaws, due to the cyclic attractors +//############################################################## +double PottsModel::HeatBathLookup(double gamma, double prob, double kT, unsigned int max_sweeps) { + DLList_Iter iter; + DLList_Iter l_iter; + DLList_Iter i_iter, i_iter2; + NNode *node, *n_cur; + NLink *l_cur; + unsigned int new_spin, spin_opt, old_spin; + unsigned int sweep; + long max_q, rn; + unsigned long changes, /*degree,*/ problemcount; + double degree, w, delta = 0, h; + //HugeArray neighbours; + double norm, r, beta, minweight, prefac = 0; + bool found; + long int num_of_nodes; + sweep = 0; + changes = 0; + num_of_nodes = net->node_list->Size(); + while (sweep < max_sweeps) { + sweep++; + //loop over all nodes in network + for (int n = 0; n < num_of_nodes; n++) { + rn = -1; + while ((rn < 0) || (rn > num_of_nodes - 1)) { + rn = RNG_INTEGER(0, num_of_nodes - 1); + } + /* rn=long(double(num_of_nodes*double(rand())/double(RAND_MAX+1.0))); */ + + node = net->node_list->Get(rn); + // initialize the neighbours and the weights + problemcount = 0; + for (unsigned int i = 0; i <= q; i++) { + neighbours[i] = 0.0; + weights[i] = 0.0; + } + norm = 0.0; + degree = node->Get_Weight(); + //Loop over all links (=neighbours) + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + //printf("%s %s\n",node->Get_Name(),n_cur->Get_Name()); + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + neighbours[n_cur->Get_ClusterIndex()] += w; + l_cur = l_iter.Next(); + } + + //Look for optimal spin + + old_spin = node->Get_ClusterIndex(); + //degree=node->Get_Degree(); + switch (operation_mode) { + case 0: { + prefac = 1.0; + delta = 1.0; + break; + } + case 1: {//newman modularity + prefac = 1.0; + prob = degree / total_degree_sum; + delta = degree; + break; + } + } + spin_opt = old_spin; + beta = 1.0 / kT * prefac; + minweight = 0.0; + weights[old_spin] = 0.0; + for (unsigned spin = 1; spin <= q; spin++) { // all possible new spins + if (spin != old_spin) { // except the old one! + h = color_field[spin] - (color_field[old_spin] - delta); + weights[spin] = neighbours[old_spin] - neighbours[spin] + gamma * prob * h; + if (weights[spin] < minweight) { + minweight = weights[spin]; + } + } + } // for spin + for (unsigned spin = 1; spin <= q; spin++) { // all possible new spins + weights[spin] -= minweight; // subtract minweigt + // for numerical stability + weights[spin] = exp(-beta * weights[spin]); + norm += weights[spin]; + } // for spin + + + //choose a new spin + /* r = norm*double(rand())/double(RAND_MAX + 1.0); */ + r = RNG_UNIF(0, norm); + new_spin = 1; + found = false; + while (!found && new_spin <= q) { + if (r <= weights[new_spin]) { + spin_opt = new_spin; + found = true; + break; + } else { + r -= weights[new_spin]; + } + new_spin++; + } + if (!found) { +// printf("."); + problemcount++; + } + //------------------------------- + //now set the new spin + new_spin = spin_opt; + if (new_spin != old_spin) { // Did we really change something?? + changes++; + node->Set_ClusterIndex(new_spin); + color_field[old_spin] -= delta; + color_field[new_spin] += delta; + + //Qmatrix update + //iteration over all neighbours + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + Qmatrix[old_spin][n_cur->Get_ClusterIndex()] -= w; + Qmatrix[new_spin][n_cur->Get_ClusterIndex()] += w; + Qmatrix[n_cur->Get_ClusterIndex()][old_spin] -= w; + Qmatrix[n_cur->Get_ClusterIndex()][new_spin] += w; + Qa[old_spin] -= w; + Qa[new_spin] += w; + l_cur = l_iter.Next(); + } // while l_iter + } + } // for n + } // while markov + max_q = 0; + + for (unsigned int i = 1; i <= q; i++) if (color_field[i] > max_q) { + max_q = long(color_field[i] + 0.5); + } + + acceptance = double(changes) / double(num_of_nodes) / double(sweep); + return acceptance; +} + +//############################################################################################### +//# Here we try to minimize the affinity to the rest of the network +//############################################################################################### +double PottsModel::FindCommunityFromStart(double gamma, double prob, + char *nodename, + igraph_vector_t *result, + igraph_real_t *cohesion, + igraph_real_t *adhesion, + igraph_integer_t *my_inner_links, + igraph_integer_t *my_outer_links) { + DLList_Iter iter, iter2; + DLList_Iter l_iter; + DLList* to_do; + DLList* community; + NNode *start_node = 0, *n_cur, *neighbor, *max_aff_node, *node; + NLink *l_cur; + bool found = false, add = false, remove = false; + double degree, delta_aff_add, delta_aff_rem, max_delta_aff, Ks = 0.0, Kr = 0, kis, kir, w; + long community_marker = 5; + long to_do_marker = 10; + double inner_links = 0, outer_links = 0, aff_r, aff_s; + + IGRAPH_UNUSED(prob); + + to_do = new DLList; + community = new DLList; + + // find the node in the network + n_cur = iter.First(net->node_list); + while (!found && !iter.End()) { + if (0 == strcmp(n_cur->Get_Name(), nodename)) { + start_node = n_cur; + found = true; + start_node->Set_Affinity(0.0); + community->Push(start_node); + start_node->Set_Marker(community_marker); + Ks = start_node->Get_Weight(); + Kr = total_degree_sum - start_node->Get_Weight(); + } + n_cur = iter.Next(); + } + if (!found) { +// printf("%s not found found. Aborting.\n",nodename); +// fprintf(file,"%s not found found. Aborting.\n",nodename); + delete to_do; + delete community; + return -1; + } + //############################# + // initialize the to_do list and community with the neighbours of start node + //############################# + neighbor = iter.First(start_node->Get_Neighbours()); + while (!iter.End()) { +// printf("Adding node %s to comunity.\n",neighbor->Get_Name()); + community->Push(neighbor); + neighbor->Set_Marker(community_marker); + Ks += neighbor->Get_Weight(); + Kr -= neighbor->Get_Weight(); + neighbor = iter.Next(); + } + node = iter.First(community); + while (!iter.End()) { + //now add at the second neighbors to the to_do list + neighbor = iter2.First(node->Get_Neighbours()); + while (!iter2.End()) { + if ((long)neighbor->Get_Marker() != community_marker && (long)neighbor->Get_Marker() != to_do_marker) { + to_do->Push(neighbor); + neighbor->Set_Marker(to_do_marker); +// printf("Adding node %s to to_do list.\n",neighbor->Get_Name()); + } + neighbor = iter2.Next(); + } + node = iter.Next(); + } + + //############# + //repeat, as long as we are still adding nodes to the communtiy + //############# + add = true; + remove = true; + while (add || remove) { + //############################# + //calculate the affinity changes of all nodes for adding every node in the to_do list to the community + //############################## + + IGRAPH_ALLOW_INTERRUPTION(); /* This is not clean.... */ + + max_delta_aff = 0.0; + max_aff_node = NULL; + add = false; + node = iter.First(to_do); + while (!iter.End()) { + //printf("Checking Links of %s\n",node->Get_Name()); + degree = node->Get_Weight(); + kis = 0.0; + kir = 0.0; + // For every of the neighbors, check, count the links to the community + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + if ((long)n_cur->Get_Marker() == community_marker) { + kis += w; //the weight/number of links to the community + } else { + kir += w; //the weight/number of links to the rest of the network + } + l_cur = l_iter.Next(); + } + aff_r = kir - gamma / total_degree_sum * (Kr - degree) * degree; + aff_s = kis - gamma / total_degree_sum * Ks * degree; + delta_aff_add = aff_r - aff_s; + // if (aff_s>=aff_r && delta_aff_add<=max_delta_aff) { + if (delta_aff_add <= max_delta_aff) { + node->Set_Affinity(aff_s); + max_delta_aff = delta_aff_add; + max_aff_node = node; + add = true; + } + //printf("%s in to_do list with affinity %f\n",node->Get_Name(),node->Get_Affinity()); + node = iter.Next(); + } + //################ + //calculate the affinity changes for removing every single node from the community + //################ + inner_links = 0; + outer_links = 0; + remove = false; + node = iter.First(community); + while (!iter.End()) { + //printf("Checking Links of %s\n",node->Get_Name()); + degree = node->Get_Weight(); + kis = 0.0; + kir = 0.0; + // For every of the neighbors, check, count the links to the community + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + if ((long)n_cur->Get_Marker() == community_marker) { + kis += w; + inner_links += w; //summing all w gives twice the number of inner links(weights) + } else { + kir += w; + outer_links += w; + } + l_cur = l_iter.Next(); + } +// if (kir+kis!=degree) { printf("error kir=%f\tkis=%f\tk=%f\n",kir,kis,degree); } + aff_r = kir - gamma / total_degree_sum * Kr * degree; + aff_s = kis - gamma / total_degree_sum * (Ks - degree) * degree; + delta_aff_rem = aff_s - aff_r; + node->Set_Affinity(aff_s); + // we should not remove the nodes, we have just added + if (delta_aff_rem < max_delta_aff) { + max_delta_aff = delta_aff_rem ; + max_aff_node = node; + remove = true; + add = false; + } + //printf("%s in to_do list with affinity %f\n",node->Get_Name(),node->Get_Affinity()); + node = iter.Next(); + } + inner_links = inner_links * 0.5; + //################ + // Now check, whether we want to remove or add a node + //################ + if (add) { + //################ + //add the node of maximum affinity to the community + //############### + community->Push(max_aff_node); + max_aff_node->Set_Marker(community_marker); + //delete node from to_do + to_do->fDelete(max_aff_node); + //update the sum of degrees in the community + Ks += max_aff_node->Get_Weight(); + Kr -= max_aff_node->Get_Weight(); +// printf("Adding node %s to community with affinity of %f delta_aff: %f.\n",max_aff_node->Get_Name(), max_aff_node->Get_Affinity(),max_delta_aff); + //now add all neighbors of this node, that are not already + //in the to_do list or in the community + neighbor = iter.First(max_aff_node->Get_Neighbours()); + while (!iter.End()) { + if ((long)neighbor->Get_Marker() != community_marker && (long)neighbor->Get_Marker() != to_do_marker) { + to_do->Push(neighbor); + neighbor->Set_Marker(to_do_marker); + //printf("Adding node %s to to_do list.\n",neighbor->Get_Name()); + } + neighbor = iter.Next(); + } + } + if (remove) { + //################ + //remove those with negative affinities + //################ + community->fDelete(max_aff_node); + max_aff_node->Set_Marker(to_do_marker); + //update the sum of degrees in the community + Ks -= max_aff_node->Get_Weight(); + Kr += max_aff_node->Get_Weight(); + //add the node to to_do again + to_do->Push(max_aff_node); +// printf("Removing node %s from community with affinity of %f delta_aff: %f.\n",max_aff_node->Get_Name(), max_aff_node->Get_Affinity(),max_delta_aff); + } + IGRAPH_ALLOW_INTERRUPTION(); /* This is not clean.... */ + } + //################### + //write the node in the community to a file + //################### + // TODO return this instead of writing it +// fprintf(file,"Number_of_nodes:\t%d\n",community->Size()); +// fprintf(file,"Inner_Links:\t%f\n",inner_links); +// fprintf(file,"Outer_Links:\t%f\n",Ks-2*inner_links); +// fprintf(file,"Cohesion:\t%f\n",inner_links-gamma/total_degree_sum*Ks*Ks*0.5); +// fprintf(file,"Adhesion:\t%f\n",outer_links-gamma/total_degree_sum*Ks*Kr); +// fprintf(file,"\n"); + if (cohesion) { + *cohesion = inner_links - gamma / total_degree_sum * Ks * Ks * 0.5; + } + if (adhesion) { + *adhesion = outer_links - gamma / total_degree_sum * Ks * Kr; + } + if (my_inner_links) { + *my_inner_links = inner_links; + } + if (my_outer_links) { + *my_outer_links = outer_links; + } + if (result) { + node = iter.First(community); + igraph_vector_resize(result, 0); + while (!iter.End()) { + // printf("%s in community.\n",node->Get_Name()); + // fprintf(file,"%s\t%f\n",node->Get_Name(),node->Get_Affinity()); + IGRAPH_CHECK(igraph_vector_push_back(result, node->Get_Index())); + node = iter.Next(); + } + } +// printf("%d nodes in community around %s\n",community->Size(),start_node->Get_Name()); +// fclose(file); + unsigned int size = community->Size(); + delete to_do; + delete community; + return size; +} + +//################################################################################################ +// this Function writes the clusters to disk +//################################################################################################ +long PottsModel::WriteClusters(igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_t *csize, + igraph_vector_t *membership, + double kT, double gamma) { + NNode *n_cur, *n_cur2; + /* + double a1,a2,a3,p,p1,p2; + long n,N,lin,lout; + */ + DLList_Iter iter, iter2; + HugeArray inner_links; + HugeArray outer_links; + HugeArray nodes; + + //den Header schreiben +// p=2.0*double(num_of_links)/double(num_of_nodes)/double(num_of_nodes-1); +// fprintf(file," Nodes=\t%lu\n",num_of_nodes); +// fprintf(file," Links=\t%lu\n",num_of_links); +// fprintf(file," q=\t%d\n",q); +// fprintf(file," p=\t%f\n",p); +// fprintf(file," Modularity=\t%f\n",calculate_Q()); +// fprintf(file,"Temperature=\t%f\n", kT); +// fprintf(file,"Cluster\tNodes\tInnerLinks\tOuterLinks\tp_in\tp_out\t\n"); + + if (temperature) { + *temperature = kT; + } + + if (csize || membership || modularity) { + // TODO: count the number of clusters + for (unsigned int spin = 1; spin <= q; spin++) { + inner_links[spin] = 0; + outer_links[spin] = 0; + nodes[spin] = 0; + n_cur = iter.First(net->node_list); + while (!iter.End()) { + if (n_cur->Get_ClusterIndex() == spin) { + nodes[spin]++; + n_cur2 = iter2.First(n_cur->Get_Neighbours()); + while (!iter2.End()) { + if (n_cur2->Get_ClusterIndex() == spin) { + inner_links[spin]++; + } else { + outer_links[spin]++; + } + n_cur2 = iter2.Next(); + } + } + n_cur = iter.Next(); + } + } + } + if (modularity) { + *modularity = 0.0; + for (unsigned int spin = 1; spin <= q; spin++) { + if (nodes[spin] > 0) { + double t1 = inner_links[spin] / net->sum_weights / 2.0; + double t2 = (inner_links[spin] + outer_links[spin]) / + net->sum_weights / 2.0; + *modularity += t1; + *modularity -= gamma * t2 * t2; + } + } + } + if (csize) { + igraph_vector_resize(csize, 0); + for (unsigned int spin = 1; spin <= q; spin++) { + if (nodes[spin] > 0) { + inner_links[spin] /= 2; + // fprintf(file,"Cluster\tNodes\tInnerLinks\tOuterLinks\tp_in\tp_out\n"); + /* + N=num_of_nodes; + n=nodes[spin]; + lin=inner_links[spin]; + lout=outer_links[spin]; + a1=N*log((double)N)-n*log((double)n)*(N-n)*log((double)N-n); + if ((lin==long(n*(n-1)*0.5+0.5)) || (n==1)) a2=0.0; + else a2=(n*(n-1)*0.5 )*log((double)n*(n-1)*0.5 )-(n*(n-1)*0.5 )- + (n*(n-1)*0.5-lin)*log((double)n*(n-1)*0.5-lin)+(n*(n-1)*0.5-lin)- + lin*log((double)lin )+lin; + */ + + /* + if ((lout==n*(N-n)) || n==N) a3=0.0; + else a3=(n*(N-n) )*log((double)n*(N-n) )-(n*(N-n))- + (n*(N-n)-lout)*log((double)n*(N-n)-lout)+(n*(N-n)-lout)- + lout*log((double)lout )+lout; + */ + + /* + p1=(lin+lout)*log((double)p); + p2=(0.5*n*(n-1)-lin + n*(N-n)-lout)*log((double)1.0-p); + */ + // fprintf(file,"%d\t%d\t%d\t%d\t%f\t%f\t%f\n",spin,nodes[spin], inner_links[spin], outer_links[spin], p_in, p_out,log_num_exp); + IGRAPH_CHECK(igraph_vector_push_back(csize, nodes[spin])); + } + } + // fprintf(file,"\n"); + } + + //die Elemente der Cluster + if (membership) { + long int no = -1; + IGRAPH_CHECK(igraph_vector_resize(membership, num_of_nodes)); + for (unsigned int spin = 1; spin <= q; spin++) { + if (nodes[spin] > 0) { + no++; + } + n_cur = iter.First(net->node_list); + while (!iter.End()) { + if (n_cur->Get_ClusterIndex() == spin) { + // fprintf(file,"%d\t%s\n",spin,n_cur->Get_Name()); + VECTOR(*membership)[ n_cur->Get_Index() ] = no; + } + n_cur = iter.Next(); + } + } + } + + return num_of_nodes; +} +//################################################################################################ +//This function writes the soft clusters after a gamma sweep +//that is, it groups every node together that was found in +// more than threshold percent together with the other node +// in the same cluster +//################################################################################################ +// Does not work at the moment !!! +//################################################################################################ +// long PottsModel::WriteSoftClusters(char *filename, double threshold) +// { +// FILE *file; +// NNode *n_cur, *n_cur2; +// DLList_Iter iter, iter2; +// DL_Indexed_List*> *cl_list, *old_clusterlist; +// ClusterList *cl_cur; + +// double max; + +// file=fopen(filename,"w"); +// if (!file) { +// printf("Could not open %s for writing.\n",filename); +// return -1; +// } + +// max=correlation[0]->Get(0); +// //printf("max=%f\n",max); +// cl_list=new DL_Indexed_List*>(); + +// n_cur=iter.First(net->node_list); +// while (!iter.End()) +// { +// cl_cur=new ClusterList(); +// cl_list->Push(cl_cur); +// n_cur2=iter2.First(net->node_list); +// while (!iter2.End()) +// { +// if (double(correlation[n_cur->Get_Index()]->Get(n_cur2->Get_Index()))/max>threshold) +// cl_cur->Push(n_cur2); +// n_cur2=iter2.Next(); +// } +// n_cur=iter.Next(); +// } +// old_clusterlist=net->cluster_list; +// net->cluster_list=cl_list; +// clear_all_markers(net); +// //printf("Es gibt %d Cluster\n",cl_list->Size()); +// reduce_cliques2(net, false, 15); +// //printf("Davon bleiben %d Cluster uebrig\n",cl_list->Size()); +// clear_all_markers(net); +// while (net->cluster_list->Size()){ +// cl_cur=net->cluster_list->Pop(); +// while (cl_cur->Size()) +// { +// n_cur=cl_cur->Pop(); +// fprintf(file,"%s\n",n_cur->Get_Name()); +// //printf("%s\n",n_cur->Get_Name()); +// } +// fprintf(file,"\n"); +// } +// net->cluster_list=old_clusterlist; +// fclose(file); + +// return 1; +// } +//############################################################################# +// Performs a gamma sweep +//############################################################################# +double PottsModel::GammaSweep(double gamma_start, double gamma_stop, double prob, unsigned int steps, bool non_parallel, int repetitions) { + double stepsize; + double kT, kT_start; + long changes; + double gamma, acc; + NNode *n_cur, *n_cur2; + DLList_Iter iter, iter2; + + stepsize = (gamma_stop - gamma_start) / double(steps); + + n_cur = iter.First(net->node_list); + while (!iter.End()) { + correlation[n_cur->Get_Index()] = new HugeArray(); + n_cur2 = iter2.First(net->node_list); + while (!iter2.End()) { + correlation[n_cur->Get_Index()]->Set(n_cur->Get_Index()) = 0.0; + n_cur2 = iter2.Next(); + } + n_cur = iter.Next(); + } + + for (unsigned int n = 0; n <= steps; n++) { + assign_initial_conf(-1); + initialize_Qmatrix(); + gamma = gamma_start + stepsize * n; + kT = 0.5; + acceptance = 0.5; + while (acceptance < (1.0 - 1.0 / double(q)) * 0.95) { //wollen 95% Acceptance + kT *= 1.1; + //initialize_lookup(kT,kmax,net->node_list->Size()); + if (!non_parallel) { + HeatBathParallelLookup(gamma, prob, kT, 25); + } else { + HeatBathLookup(gamma, prob, kT, 25); + } + // printf("kT=%f acceptance=%f\n", kT, acceptance); + } + // printf("Starting with gamma=%f\n", gamma); + kT_start = kT; + + for (int i = 0; i < repetitions; i++) { + changes = 1; + kT = kT_start; + assign_initial_conf(-1); + initialize_Qmatrix(); + while ((changes > 0) && (kT > 0.01)) { + kT = kT * 0.99; + //initialize_lookup(kT,kmax,net->node_list->Size()); + if (!non_parallel) { + changes = HeatBathParallelLookup(gamma, prob, kT, 50); + // printf("kT: %f \t Changes %li\n",kT, changes); + } else { + acc = HeatBathLookup(gamma, prob, kT, 50); + if (acc > (1.0 - 1.0 / double(q)) * 0.01) { + changes = 1; + } else { + changes = 0; + } + // printf("kT: %f Acceptance: %f\n",kT, acc); + } + } + // printf("Finisched with acceptance: %1.6f bei kT=%2.4f und gamma=%2.4f\n",acceptance,kT, gamma); +// fprintf(file,"%f\t%f\n",gamma_,acceptance); +// fprintf(file2,"%f\t%f\n",gamma_,kT); + // fprintf(file3,"%f\t%d\n",gamma_,count_clusters(5)); + + //Die Correlation berechnen + n_cur = iter.First(net->node_list); + while (!iter.End()) { + n_cur2 = iter2.First(net->node_list); + while (!iter2.End()) { + if (n_cur->Get_ClusterIndex() == n_cur2->Get_ClusterIndex()) { + correlation[n_cur->Get_Index()]->Set(n_cur2->Get_Index()) += 0.5; + } + n_cur2 = iter2.Next(); + } + n_cur = iter.Next(); + } + } // for i + } //for n + return kT; +} +//############################################################################# +//Performs a Gamma sweep at zero T +//############################################################################# +double PottsModel::GammaSweepZeroTemp(double gamma_start, double gamma_stop, double prob, unsigned int steps, bool non_parallel, int repetitions) { + double stepsize; + long changes; + double gamma, acc; + long runs; + NNode *n_cur, *n_cur2; + DLList_Iter iter, iter2; + + stepsize = (gamma_stop - gamma_start) / double(steps); + + n_cur = iter.First(net->node_list); + while (!iter.End()) { + correlation[n_cur->Get_Index()] = new HugeArray(); + n_cur2 = iter2.First(net->node_list); + while (!iter2.End()) { + correlation[n_cur->Get_Index()]->Set(n_cur->Get_Index()) = 0.0; + n_cur2 = iter2.Next(); + } + n_cur = iter.Next(); + } + + for (unsigned int n = 0; n <= steps; n++) { + assign_initial_conf(-1); + initialize_Qmatrix(); + gamma = gamma_start + stepsize * n; + // printf("Starting with gamma=%f\n", gamma); + for (int i = 0; i < repetitions; i++) { + changes = 1; + assign_initial_conf(-1); + initialize_Qmatrix(); + runs = 0; + while (changes > 0 && runs < 250) { + //initialize_lookup(kT,kmax,net->node_list->Size()); + if (!non_parallel) { + changes = HeatBathParallelLookupZeroTemp(gamma, prob, 1); + // printf("Changes %li\n", changes); + } else { + acc = HeatBathLookupZeroTemp(gamma, prob, 1); + if (acc > (1.0 - 1.0 / double(q)) * 0.01) { + changes = 1; + } else { + changes = 0; + } + // printf("Acceptance: %f\n", acc); + } + runs++; + } + // printf("Finisched with Modularity: %1.6f bei Gamma=%1.6f\n",calculate_Q(), gamma); +// fprintf(file,"%f\t%f\n",gamma_,acceptance); +// fprintf(file2,"%f\t%f\n",gamma_,kT); + // fprintf(file3,"%f\t%d\n",gamma_,count_clusters(5)); + + //Die Correlation berechnen + n_cur = iter.First(net->node_list); + while (!iter.End()) { + n_cur2 = iter2.First(net->node_list); + while (!iter2.End()) { + if (n_cur->Get_ClusterIndex() == n_cur2->Get_ClusterIndex()) { + correlation[n_cur->Get_Index()]->Set(n_cur2->Get_Index()) += 0.5; + correlation[n_cur2->Get_Index()]->Set(n_cur->Get_Index()) += 0.5; + } + n_cur2 = iter2.Next(); + } + n_cur = iter.Next(); + } + } // for i + } //for n + return gamma; +} +//####################################################################### +//----------------------------------------------------------------------- +//####################################################################### +// This function writes the Correlation Matrix that results from a +// Gamma-Sweep, this matrix is used to make ps files of it. +// ###################################################################### +// long PottsModel::WriteCorrelationMatrix(char *filename) +// { +// FILE *file, *file2; +// char filename2[255]; +// NNode *n_cur, *n_cur2; +// DLList_Iter iter, iter2; + +// sprintf(filename2,"%s.mat",filename); +// file=fopen(filename,"w"); +// if (!file) { +// printf("Could not open %s for writing.\n",filename); +// return -1; +// } +// file2=fopen(filename2,"w"); +// if (!file2) { +// printf("Could not open %s for writing.\n",filename2); +// return -1; +// } +// //write the header in one line +// n_cur=iter.First(net->node_list); +// while (!iter.End()) +// { +// fprintf(file, "\t%s",n_cur->Get_Name()); +// n_cur=iter.Next(); +// } +// fprintf(file, "\n"); + +// //fprintf(file, "%d\t%d\n",net->node_list->Size(),net->node_list->Size()); + +// long r=0,c=0; +// n_cur=iter.First(net->node_list); +// while (!iter.End()) +// { +// fprintf(file, "%s",n_cur->Get_Name()); +// r++; +// n_cur2=iter2.First(net->node_list); +// while (!iter2.End()) +// { +// c++; +// fprintf(file,"\t%f",correlation[n_cur->Get_Index()]->Get(n_cur2->Get_Index())); +// fprintf(file2,"%li\t%li\t%f\n",r,c,correlation[n_cur->Get_Index()]->Get(n_cur2->Get_Index())); +// n_cur2=iter2.Next(); +// } +// fprintf(file,"\n"); +// n_cur=iter.Next(); +// } +// fclose(file); +// fclose(file2); +// return 1; +// } +//############################################################################## + +//################################################################################################# +PottsModelN::PottsModelN(network *n, unsigned int num_communities, bool directed) { + //Set internal variable + net = n; + q = num_communities; + + is_directed = directed; + + is_init = false; + + num_nodes = net->node_list->Size(); +} +//####################################################### +//Destructor of PottsModel +//######################################################## +PottsModelN::~PottsModelN() { + delete degree_pos_in; + delete degree_neg_in; + delete degree_pos_out; + delete degree_neg_out; + + delete degree_community_pos_in; + delete degree_community_neg_in; + delete degree_community_pos_out; + delete degree_community_neg_out; + + delete weights; + delete neighbours; + delete csize; + + delete spin; + + return; +} + +void PottsModelN::assign_initial_conf(bool init_spins) { +#ifdef DEBUG + printf("Start assigning.\n"); +#endif + int s; + DLList_Iter iter; + DLList_Iter l_iter; + NNode *n_cur; + NLink *l_cur; + + + if (init_spins) { +#ifdef DEBUG + printf("Initializing spin.\n"); +#endif + //Bookkeeping of the various degrees (positive/negative) and (in/out) + degree_pos_in = new double[num_nodes]; //Postive indegree of the nodes (or sum of weights) + degree_neg_in = new double[num_nodes]; //Negative indegree of the nodes (or sum of weights) + degree_pos_out = new double[num_nodes]; //Postive outdegree of the nodes (or sum of weights) + degree_neg_out = new double[num_nodes]; //Negative outdegree of the nodes (or sum of weights) + + spin = new unsigned int[num_nodes]; //The spin state of each node + } + + if (is_init) { + delete degree_community_pos_in; + delete degree_community_neg_in; + delete degree_community_pos_out; + delete degree_community_neg_out; + + delete weights; + delete neighbours; + delete csize; + } + + is_init = true; + + //Bookkeep of occupation numbers of spin states or the number of links in community... + degree_community_pos_in = new double[q + 1]; //Positive sum of indegree for communities + degree_community_neg_in = new double[q + 1]; //Negative sum of indegree for communities + degree_community_pos_out = new double[q + 1]; //Positive sum of outegree for communities + degree_community_neg_out = new double[q + 1]; //Negative sum of outdegree for communities + + //...and of weights and neighbours for in the HeathBathLookup + weights = new double[q + 1]; //The weights for changing to another spin state + neighbours = new double[q + 1]; //The number of neighbours (or weights) in different spin states + csize = new unsigned int[q + 1]; //The number of nodes in each community + + + //Initialize communities + for (unsigned int i = 0; i <= q; i++) { + degree_community_pos_in[i] = 0.0; + degree_community_neg_in[i] = 0.0; + degree_community_pos_out[i] = 0.0; + degree_community_neg_out[i] = 0.0; + + csize[i] = 0; + } + + //Initialize vectors + if (init_spins) { + for (unsigned int i = 0; i < num_nodes; i++) { + degree_pos_in[i] = 0.0; + degree_neg_in[i] = 0.0; + degree_pos_out[i] = 0.0; + degree_neg_out[i] = 0.0; + +#ifdef DEBUG + printf("Initializing spin %d", i); +#endif + spin[i] = 0; + } + } + m_p = 0.0; + m_n = 0.0; + //Set community for each node, and + //correctly store it in the bookkeeping + + double sum_weight_pos_in, sum_weight_pos_out, sum_weight_neg_in, sum_weight_neg_out; + //double av_w = 0.0, av_k=0.0; + //int l = 0; +#ifdef DEBUG + printf("Visiting each node.\n"); +#endif + for (unsigned int v = 0; v < num_nodes; v++) { + if (init_spins) { + s = RNG_INTEGER(1, q); //The new spin s + spin[v] = (unsigned int)s; + } else { + s = spin[v]; + } + +#ifdef DEBUG + printf("Spin %d assigned to node %d.\n", s, v); +#endif + + n_cur = net->node_list->Get(v); + + l_cur = l_iter.First(n_cur->Get_Links()); + + sum_weight_pos_in = 0.0; + sum_weight_pos_out = 0.0; + sum_weight_neg_in = 0.0; + sum_weight_neg_out = 0.0; + + while (!l_iter.End()) { + double w = l_cur->Get_Weight(); + //av_w = (av_w*l + w)/(l+1); //Average weight + //l++; + if (l_cur->Get_Start() == n_cur) //From this to other, so outgoing link + if (w > 0) { + sum_weight_pos_out += w; //Increase positive outgoing weight + } else { + sum_weight_neg_out -= w; //Increase negative outgoing weight + } else if (w > 0) { + sum_weight_pos_in += w; //Increase positive incoming weight + } else { + sum_weight_neg_in -= w; //Increase negative incoming weight + } + + l_cur = l_iter.Next(); + } + + if (!is_directed) { + double sum_weight_pos = sum_weight_pos_out + sum_weight_pos_in; + sum_weight_pos_out = sum_weight_pos; + sum_weight_pos_in = sum_weight_pos; + double sum_weight_neg = sum_weight_neg_out + sum_weight_neg_in; + sum_weight_neg_out = sum_weight_neg; + sum_weight_neg_in = sum_weight_neg; + } + + //av_k = (av_k*l + sum_weight_pos_in)/(l+1); //Average k + + if (init_spins) { + //Set the degrees correctly + degree_pos_in[v] = sum_weight_pos_in; + degree_neg_in[v] = sum_weight_neg_in; + degree_pos_out[v] = sum_weight_pos_out; + degree_neg_out[v] = sum_weight_neg_out; + } + + //Correct the community bookkeeping + degree_community_pos_in[s] += sum_weight_pos_in; + degree_community_neg_in[s] += sum_weight_neg_in; + degree_community_pos_out[s] += sum_weight_pos_out; + degree_community_neg_out[s] += sum_weight_neg_out; + + //Community just increased + csize[s]++; + + //Sum the weights (notice that sum of indegrees equals sum of outdegrees) + m_p += sum_weight_pos_in; + m_n += sum_weight_neg_in; + } + +#ifdef DEBUG + printf("Done assigning.\n"); +#endif + + return; +} +//############################################################## +// This is the function generally used for optimisation, +// as the parallel update has its flaws, due to the cyclic attractors +//############################################################## +double PottsModelN::HeatBathLookup(double gamma, double lambda, double t, unsigned int max_sweeps) { +#ifdef DEBUG + printf("Starting sweep at temperature %f.\n", t); +#endif + DLList_Iter iter; + DLList_Iter l_iter; + DLList_Iter i_iter, i_iter2; + NNode *node, *n_cur; + NLink *l_cur; + /* The new_spin contains the spin to which we will update, + * the spin_opt is the optional spin we will consider and + * the old_spin is the spin of the node we are currently + * changing. + */ + unsigned int new_spin, spin_opt, old_spin; + unsigned int sweep; //current sweep + unsigned long changes, problemcount; //Number of changes and number of problems encountered + + double exp_old_spin; //The expectation value for the old spin + double exp_spin; //The expectation value for the other spin(s) + int v; //The node we will be investigating + + //The variables required for the calculations + double delta_pos_out, delta_pos_in, delta_neg_out, delta_neg_in; + double k_v_pos_out, k_v_pos_in, k_v_neg_out, k_v_neg_in; + + //weight of edge + double w; + + double beta = 1 / t; //Weight for probabilities + double r = 0.0; //random number used for assigning new spin + + double maxweight = 0.0; + double sum_weights = 0.0; //sum_weights for normalizing the probabilities + + sweep = 0; + changes = 0; + double m_pt = m_p; + double m_nt = m_n; + + if (m_pt < 0.001) { + m_pt = 1; + } + + if (m_nt < 0.001) { + m_nt = 1; + } + + while (sweep < max_sweeps) { + sweep++; + //loop over all nodes in network + for (unsigned int n = 0; n < num_nodes; n++) { + //Look for a random node + v = RNG_INTEGER(0, num_nodes - 1); + //We will be investigating node v + + node = net->node_list->Get(v); + + /*******************************************/ + // initialize the neighbours and the weights + problemcount = 0; + for (unsigned int i = 0; i <= q; i++) { + neighbours[i] = 0.0; + weights[i] = 0.0; + } + + //Loop over all links (=neighbours) + l_cur = l_iter.First(node->Get_Links()); + while (!l_iter.End()) { + w = l_cur->Get_Weight(); + if (node == l_cur->Get_Start()) { + n_cur = l_cur->Get_End(); + } else { + n_cur = l_cur->Get_Start(); + } + //Add the link to the correct cluster + neighbours[spin[n_cur->Get_Index()]] += w; + l_cur = l_iter.Next(); + } + //We now have the weight of the (in and out) neighbours + //in each cluster available to us. + /*******************************************/ + old_spin = spin[v]; + + //Look for optimal spin + + //Set the appropriate variable + delta_pos_out = degree_pos_out[v]; + delta_pos_in = degree_pos_in[v]; + delta_neg_out = degree_neg_out[v]; + delta_neg_in = degree_neg_in[v]; + + k_v_pos_out = gamma * delta_pos_out / m_pt; + k_v_pos_in = gamma * delta_pos_in / m_pt; + k_v_neg_out = lambda * delta_neg_out / m_nt; + k_v_neg_in = lambda * delta_neg_in / m_nt; + + //The expectation value for the old spin + if (is_directed) + exp_old_spin = (k_v_pos_out * (degree_community_pos_in[old_spin] - delta_pos_in) - + k_v_neg_out * (degree_community_neg_in[old_spin] - delta_neg_in)) + + (k_v_pos_in * (degree_community_pos_out[old_spin] - delta_pos_out) - + k_v_neg_in * (degree_community_neg_out[old_spin] - delta_neg_out)); + else + exp_old_spin = (k_v_pos_out * (degree_community_pos_in[old_spin] - delta_pos_in) - + k_v_neg_out * (degree_community_neg_in[old_spin] - delta_neg_in)); + + /*******************************************/ + //Calculating probabilities for each transition to another + //community. + + maxweight = 0.0; + weights[old_spin] = 0.0; + + for (spin_opt = 1; spin_opt <= q; spin_opt++) { // all possible new spins + if (spin_opt != old_spin) { // except the old one! + if (is_directed) + exp_spin = (k_v_pos_out * degree_community_pos_in[spin_opt] - k_v_neg_out * degree_community_neg_in[spin_opt]) + + (k_v_pos_in * degree_community_pos_out[spin_opt] - k_v_neg_in * degree_community_neg_out[spin_opt]); + else { + exp_spin = (k_v_pos_out * degree_community_pos_in[spin_opt] - k_v_neg_out * degree_community_neg_in[spin_opt]); + } + + weights[spin_opt] = (neighbours[spin_opt] - exp_spin) - (neighbours[old_spin] - exp_old_spin); + + if (weights[spin_opt] > maxweight) { + maxweight = weights[spin_opt]; + } + } + } // for spin + + //Calculate exp. prob. an + sum_weights = 0.0; + for (spin_opt = 1; spin_opt <= q; spin_opt++) { // all possible new spins + weights[spin_opt] -= maxweight; //subtract maxweight for numerical stability (otherwise overflow). + weights[spin_opt] = exp((double)(beta * weights[spin_opt])); + sum_weights += weights[spin_opt]; + } // for spin + /*******************************************/ + + + /*******************************************/ + //Choose a new spin dependent on the calculated probabilities + r = RNG_UNIF(0, sum_weights); + new_spin = 1; + + bool found = false; + while (!found && new_spin <= q) { + if (r <= weights[new_spin]) { + spin_opt = new_spin; //We have found are new spin + found = true; + break; + } else { + r -= weights[new_spin]; //Perhaps the next spin is the one we want + } + + new_spin++; + } + + //Some weird thing happened. We haven't found a new spin + //while that shouldn't be the case. Numerical problems? + if (!found) { + problemcount++; + } + + new_spin = spin_opt; + //If there wasn't a problem we should have found + //our new spin. + /*******************************************/ + + + /*******************************************/ + //The new spin is available to us, so change + //all the appropriate counters. + if (new_spin != old_spin) { // Did we really change something?? + changes++; + spin[v] = new_spin; + + //The new spin increase by one, and the old spin decreases by one + csize[new_spin]++; csize[old_spin]--; + + //Change the sums of degree for the old spin... + degree_community_pos_in[old_spin] -= delta_pos_in; + degree_community_neg_in[old_spin] -= delta_neg_in; + degree_community_pos_out[old_spin] -= delta_pos_out; + degree_community_neg_out[old_spin] -= delta_neg_out; + + //...and for the new spin + degree_community_pos_in[new_spin] += delta_pos_in; + degree_community_neg_in[new_spin] += delta_neg_in; + degree_community_pos_out[new_spin] += delta_pos_out; + degree_community_neg_out[new_spin] += delta_neg_out; + } + + //We have no change a node from old_spin to new_spin + /*******************************************/ + + } // for n + } // while sweep +#ifdef DEBUG + printf("Done %d sweeps.\n", max_sweeps); + printf("%d changes made for %d nodes.\n", changes, num_nodes); + printf("Last node is %d and last random number is %f with sum of weights %f with spin %d.\n", v, r, sum_weights, old_spin); +#endif + + return (double(changes) / double(num_nodes) / double(sweep)); +} + +//We need to begin at a suitable temperature. That is, a temperature at which +//enough nodes may change their initially assigned communties +double PottsModelN::FindStartTemp(double gamma, double lambda, double ts) { + double kT; + kT = ts; + //assing random initial condition + assign_initial_conf(true); + // the factor 1-1/q is important, since even, at infinite temperature, + // only 1-1/q of all spins do change their state, since a randomly chooses new + // state is with prob. 1/q the old state. + double acceptance = 0.0; + while (acceptance < (1.0 - 1.0 / double(q)) * 0.95) { //want 95% acceptance + kT = kT * 1.1; + acceptance = HeatBathLookup(gamma, lambda, kT, 50); + } + kT *= 1.1; // just to be sure... + return kT; +} + +long PottsModelN::WriteClusters(igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_t *community_size, + igraph_vector_t *membership, + igraph_matrix_t *adhesion, + igraph_matrix_t *normalised_adhesion, + igraph_real_t *polarization, + double t, + double d_p, + double d_n, + double gamma, + double lambda) { + IGRAPH_UNUSED(gamma); + IGRAPH_UNUSED(lambda); +#ifdef DEBUG + printf("Start writing clusters.\n"); +#endif + //Reassign each community so that we retrieve a community assignment 1 through num_communities + unsigned int *cluster_assign = new unsigned int[q + 1]; + for (unsigned int i = 0; i <= q; i++) { + cluster_assign[i] = 0; + } + + int num_clusters = 0; + + //Find out what the new communities will be + for (unsigned int i = 0; i < num_nodes; i++) { + int s = spin[i]; + if (cluster_assign[s] == 0) { + num_clusters++; + cluster_assign[s] = num_clusters; +#ifdef DEBUG + printf("Setting cluster %d to %d.\n", s, num_clusters); +#endif + } + } + + + /* + DLList_Iter iter; + NNode *n_cur=iter.First(net->node_list); + n_cur = iter.First(net->node_list); + */ + + //And now assign each node to its new community + q = num_clusters; + for (unsigned int i = 0; i < num_nodes; i++) { +#ifdef DEBUG + printf("Setting node %d to %d.\n", i, cluster_assign[spin[i]]); +#endif + unsigned int s = cluster_assign[spin[i]]; + spin[i] = s; +#ifdef DEBUG + printf("Have set node %d to %d.\n", i, s); +#endif + } + assign_initial_conf(false); + + delete[] cluster_assign; + + if (temperature) { + *temperature = t; + } + + if (community_size) { + //Initialize the vector + IGRAPH_CHECK(igraph_vector_resize(community_size, q)); + for (unsigned int spin_opt = 1; spin_opt <= q; spin_opt++) { + //Set the community size + VECTOR(*community_size)[spin_opt - 1] = csize[spin_opt]; + } + } + + //Set the membership + if (membership) { + IGRAPH_CHECK(igraph_vector_resize(membership, num_nodes)); + for (unsigned int i = 0; i < num_nodes; i++) { + VECTOR(*membership)[ i ] = spin[i] - 1; + } + } + + double Q = 0.0; //Modularity + if (adhesion) { + IGRAPH_CHECK(igraph_matrix_resize(adhesion, q, q)); + IGRAPH_CHECK(igraph_matrix_resize(normalised_adhesion, q, q)); + + double **num_links_pos = 0; + double **num_links_neg = 0; + //memory allocated for elements of rows. + num_links_pos = new double *[q + 1] ; + num_links_neg = new double *[q + 1] ; + + //memory allocated for elements of each column. + for ( unsigned int i = 0 ; i < q + 1 ; i++) { + num_links_pos[i] = new double[q + 1]; + num_links_neg[i] = new double[q + 1]; + } + + + + //Init num_links + for (unsigned int i = 0; i <= q; i++) { + for (unsigned int j = 0; j <= q; j++) { + num_links_pos[i][j] = 0.0; + num_links_neg[i][j] = 0.0; + } + } + + DLList_Iter iter_l; + NLink *l_cur = iter_l.First(net->link_list); + + double w = 0.0; + + while (!iter_l.End()) { + w = l_cur->Get_Weight(); + unsigned int a = spin[l_cur->Get_Start()->Get_Index()]; + unsigned int b = spin[l_cur->Get_End()->Get_Index()]; + if (w > 0) { + num_links_pos[a][b] += w; + if (!is_directed && a != b) { //Only one edge is defined in case it is undirected + num_links_pos[b][a] += w; + } + } else { + num_links_neg[a][b] -= w; + if (!is_directed && a != b) { //Only one edge is defined in case it is undirected + num_links_neg[b][a] -= w; + } + } + + l_cur = iter_l.Next(); + } //while links + +#ifdef DEBUG + printf("d_p: %f\n", d_p); + printf("d_n: %f\n", d_n); +#endif + + double expected = 0.0; + double a = 0.0; + double normal_a = 0.0; + + double delta, u_p, u_n; + double max_expected, max_a; + + //We don't take into account the lambda or gamma for + //computing the modularity and adhesion, since they + //are then incomparable to other definitions. + for (unsigned int i = 1; i <= q; i++) { + for (unsigned int j = 1; j <= q; j++) { + if (!is_directed && i == j) + expected = degree_community_pos_out[i] * degree_community_pos_in[j] / (m_p == 0 ? 1 : 2 * m_p) + - degree_community_neg_out[i] * degree_community_neg_in[j] / (m_n == 0 ? 1 : 2 * m_n); + else + expected = degree_community_pos_out[i] * degree_community_pos_in[j] / (m_p == 0 ? 1 : m_p) + - degree_community_neg_out[i] * degree_community_neg_in[j] / (m_n == 0 ? 1 : m_n); + + a = (num_links_pos[i][j] - num_links_neg[i][j]) - expected; + + if (i == j) { //cohesion + if (is_directed) { + delta = d_p * csize[i] * (csize[i] - 1); //Maximum amount + } else { + delta = d_p * csize[i] * (csize[i] - 1) / 2; //Maximum amount + } + + u_p = delta - num_links_pos[i][i]; //Add as many positive links we can + u_n = -num_links_neg[i][i]; //Delete as many negative links we can + Q += a; + } else { //adhesion + if (is_directed) { + delta = d_n * csize[i] * csize[j] * 2; //Maximum amount + } else { + delta = d_n * csize[i] * csize[j]; //Maximum amount + } + + u_p = -num_links_pos[i][j]; //Delete as many positive links we can + u_n = delta - num_links_neg[i][j]; //Add as many negative links we can + } + + if (!is_directed && i == j) + max_expected = (degree_community_pos_out[i] + u_p) * (degree_community_pos_in[j] + u_p) / ((m_p + u_p) == 0 ? 1 : 2 * (m_p + u_p)) + - (degree_community_neg_out[i] - u_n) * (degree_community_neg_in[j] + u_n) / ((m_n + u_n) == 0 ? 1 : 2 * (m_n + u_n)); + else + max_expected = (degree_community_pos_out[i] + u_p) * (degree_community_pos_in[j] + u_p) / ((m_p + u_p) == 0 ? 1 : m_p + u_p) + - (degree_community_neg_out[i] - u_n) * (degree_community_neg_in[j] + u_n) / ((m_n + u_n) == 0 ? 1 : m_n + u_n); + //printf("%f/%f %d/%d\t", num_links_pos[i][j], num_links_neg[i][j], csize[i], csize[j]); + //printf("%f/%f - %f(%f)\t", u_p, u_n, expected, max_expected); + max_a = ((num_links_pos[i][j] + u_p) - (num_links_neg[i][j] + u_n)) - max_expected; + + + //In cases where we haven't actually found a ground state + //the adhesion/cohesion *might* not be negative/positive, + //hence the maximum adhesion and cohesion might behave quite + //strangely. In order to prevent that, we limit them to 1 in + //absolute value, and prevent from dividing by zero (even if + //chuck norris would). + if (i == j) { + normal_a = a / (max_a == 0 ? a : max_a); + } else { + normal_a = -a / (max_a == 0 ? a : max_a); + } + + if (normal_a > 1) { + normal_a = 1; + } else if (normal_a < -1) { + normal_a = -1; + } + + MATRIX(*adhesion, i - 1, j - 1) = a; + MATRIX(*normalised_adhesion, i - 1, j - 1) = normal_a; + } //for j + //printf("\n"); + } //for i + + //free the allocated memory + for ( unsigned int i = 0 ; i < q + 1 ; i++ ) { + delete [] num_links_pos[i] ; + delete [] num_links_neg[i]; + } + delete [] num_links_pos ; + delete [] num_links_neg ; + + } //adhesion + + if (modularity) { + if (is_directed) { + *modularity = Q / (m_p + m_n); + } else { + *modularity = 2 * Q / (m_p + m_n); //Correction for the way m_p and m_n are counted. Modularity is 1/m, not 1/2m + } + } + + if (polarization) { + double sum_ad = 0.0; + for (unsigned int i = 0; i < q; i++) { + for (unsigned int j = 0; j < q; j++) { + if (i != j) { + sum_ad -= MATRIX(*normalised_adhesion, i, j); + } + } + } + *polarization = sum_ad / (q * q - q); + } +#ifdef DEBUG + printf("Finished writing cluster.\n"); +#endif + return num_nodes; +} diff --git a/src/pottsmodel_2.h b/src/pottsmodel_2.h new file mode 100644 index 0000000..54eb30d --- /dev/null +++ b/src/pottsmodel_2.h @@ -0,0 +1,167 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Jörg Reichardt + This file was modified by Vincent Traag + The original copyright notice follows here */ + +/*************************************************************************** + pottsmodel.h - description + ------------------- + begin : Fri May 28 2004 + copyright : (C) 2004 by + email : + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef POTTSMODEL_H +#define POTTSMODEL_H + +#include "NetDataTypes.h" + +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" + +#define qmax 500 + +class PottsModel { +private: + // HugeArray neg_gammalookup; + // HugeArray pos_gammalookup; + DL_Indexed_List *new_spins; + DL_Indexed_List *previous_spins; + HugeArray*> correlation; + network *net; + unsigned int q; + unsigned int operation_mode; + FILE *Qfile, *Magfile; + double Qmatrix[qmax + 1][qmax + 1]; + double* Qa; + double* weights; + double total_degree_sum; + unsigned long num_of_nodes; + unsigned long num_of_links; + unsigned long k_max; + double energy; + double acceptance; + double *neighbours; +public: + PottsModel(network *net, unsigned int q, int norm_by_degree); + ~PottsModel(); + double* color_field; + unsigned long assign_initial_conf(int spin); + unsigned long initialize_lookup(double kT, double gamma); + double initialize_Qmatrix(void); + double calculate_Q(void); + double calculate_genQ(double gamma); + double FindStartTemp(double gamma, double prob, double ts); + long HeatBathParallelLookupZeroTemp(double gamma, double prob, unsigned int max_sweeps); + double HeatBathLookupZeroTemp(double gamma, double prob, unsigned int max_sweeps); + long HeatBathParallelLookup(double gamma, double prob, double kT, unsigned int max_sweeps); + double HeatBathLookup(double gamma, double prob, double kT, unsigned int max_sweeps); + double GammaSweep(double gamma_start, double gamma_stop, double prob, unsigned int steps, bool non_parallel = true, int repetitions = 1); + double GammaSweepZeroTemp(double gamma_start, double gamma_stop, double prob, unsigned int steps, bool non_parallel = true, int repetitions = 1); + long WriteCorrelationMatrix(char *filename); + double calculate_energy(double gamma); + long WriteClusters(igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_t *csize, igraph_vector_t *membership, + double kT, double gamma); + long WriteSoftClusters(char *filename, double threshold); + double Get_Energy(void) { + return energy; + } + double FindCommunityFromStart(double gamma, double prob, char *nodename, + igraph_vector_t *result, + igraph_real_t *cohesion, + igraph_real_t *adhesion, + igraph_integer_t *inner_links, + igraph_integer_t *outer_links); +}; + + +class PottsModelN { +private: + // HugeArray neg_gammalookup; + // HugeArray pos_gammalookup; + DL_Indexed_List *new_spins; + DL_Indexed_List *previous_spins; + HugeArray*> correlation; + network *net; + + unsigned int q; //number of communities + double m_p; //number of positive ties (or sum of degrees), this equals the number of edges only if it is undirected and each edge has a weight of 1 + double m_n; //number of negative ties (or sum of degrees) + unsigned int num_nodes; //number of nodes + bool is_directed; + + bool is_init; + + double *degree_pos_in; //Postive indegree of the nodes (or sum of weights) + double *degree_neg_in; //Negative indegree of the nodes (or sum of weights) + double *degree_pos_out; //Postive outdegree of the nodes (or sum of weights) + double *degree_neg_out; //Negative outdegree of the nodes (or sum of weights) + + double *degree_community_pos_in; //Positive sum of indegree for communities + double *degree_community_neg_in; //Negative sum of indegree for communities + double *degree_community_pos_out; //Positive sum of outegree for communities + double *degree_community_neg_out; //Negative sum of outdegree for communities + + unsigned int *csize; //The number of nodes in each community + unsigned int *spin; //The membership of each node + + double *neighbours; //Array of neighbours of a vertex in each community + double *weights; //Weights of all possible transitions to another community + +public: + PottsModelN(network *n, unsigned int num_communities, bool directed); + ~PottsModelN(); + void assign_initial_conf(bool init_spins); + double FindStartTemp(double gamma, double lambda, double ts); + double HeatBathLookup(double gamma, double lambda, double t, unsigned int max_sweeps); + double HeatBathJoin(double gamma, double lambda); + double HeatBathLookupZeroTemp(double gamma, double lambda, unsigned int max_sweeps); + long WriteClusters(igraph_real_t *modularity, + igraph_real_t *temperature, + igraph_vector_t *community_size, + igraph_vector_t *membership, + igraph_matrix_t *adhesion, + igraph_matrix_t *normalised_adhesion, + igraph_real_t *polarization, + double t, + double d_p, + double d_n, + double gamma, + double lambda); +}; + +#endif diff --git a/src/progress.c b/src/progress.c new file mode 100644 index 0000000..7edfa92 --- /dev/null +++ b/src/progress.c @@ -0,0 +1,153 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_progress.h" +#include "config.h" + +static IGRAPH_THREAD_LOCAL igraph_progress_handler_t *igraph_i_progress_handler = 0; +static IGRAPH_THREAD_LOCAL char igraph_i_progressmsg_buffer[1000]; + +/** + * \function igraph_progress + * Report progress + * + * Note that the usual way to report progress is the \ref IGRAPH_PROGRESS + * macro, as that takes care of the return value of the progress + * handler. + * \param message A string describing the function or algorithm + * that is reporting the progress. Current igraph functions + * always use the name \p message argument if reporting from the + * same function. + * \param percent Numeric, the percentage that was completed by the + * algorithm or function. + * \param data User-defined data. Current igraph functions that + * report progress pass a null pointer here. Users can + * write their own progress handlers and functions with progress + * reporting, and then pass some meaningfull context here. + * \return If there is a progress handler installed and + * it does not return \c IGRAPH_SUCCESS, then \c IGRAPH_INTERRUPTED + * is returned. + * + * Time complexity: O(1). + */ + +int igraph_progress(const char *message, igraph_real_t percent, void *data) { + if (igraph_i_progress_handler) { + if (igraph_i_progress_handler(message, percent, data) != IGRAPH_SUCCESS) { + return IGRAPH_INTERRUPTED; + } + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_progressf + * Report progress, printf-like version + * + * This is a more flexible version of \ref igraph_progress(), with + * a printf-like template string. First the template string + * is filled with the additional arguments and then \ref + * igraph_progress() is called. + * + *
    Note that there is an upper limit for the length of + * the \p message string, currently 1000 characters. + * \param message A string describing the function or algorithm + * that is reporting the progress. For this function this is a + * template string, using the same syntax as the standard + * \c libc \c printf function. + * \param percent Numeric, the percentage that was completed by the + * algorithm or function. + * \param data User-defined data. Current igraph functions that + * report progress pass a null pointer here. Users can + * write their own progress handlers and functions with progress + * reporting, and then pass some meaningfull context here. + * \param ... Additional argument that were specified in the + * \p message argument. + * \return If there is a progress handler installed and + * it does not return \c IGRAPH_SUCCESS, then \c IGRAPH_INTERRUPTED + * is returned. + * \return + */ + +int igraph_progressf(const char *message, igraph_real_t percent, void *data, + ...) { + va_list ap; + va_start(ap, data); + vsnprintf(igraph_i_progressmsg_buffer, + sizeof(igraph_i_progressmsg_buffer) / sizeof(char), message, ap); + return igraph_progress(igraph_i_progressmsg_buffer, percent, data); +} + +#ifndef USING_R + +/** + * \function igraph_progress_handler_stderr + * A simple predefined progress handler + * + * This simple progress handler first prints \p message, and then + * the percentage complete value in a short message to standard error. + * \param message A string describing the function or algorithm + * that is reporting the progress. Current igraph functions + * always use the name \p message argument if reporting from the + * same function. + * \param percent Numeric, the percentage that was completed by the + * algorithm or function. + * \param data User-defined data. Current igraph functions that + * report progress pass a null pointer here. Users can + * write their own progress handlers and functions with progress + * reporting, and then pass some meaningfull context here. + * \return This function always returns with \c IGRAPH_SUCCESS. + * + * Time complexity: O(1). + */ + +int igraph_progress_handler_stderr(const char *message, igraph_real_t percent, + void* data) { + IGRAPH_UNUSED(data); + fputs(message, stderr); + fprintf(stderr, "%.1f percent ready\n", (double)percent); + return 0; +} +#endif + +/** + * \function igraph_set_progress_handler + * Install a progress handler, or remove the current handler + * + * There is a single simple predefined progress handler: + * \ref igraph_progress_handler_stderr(). + * \param new_handler Pointer to a function of type + * \ref igraph_progress_handler_t, the progress handler function to + * install. To uninstall the current progress handler, this argument + * can be a null pointer. + * \return Pointer to the previously installed progress handler function. + * + * Time complexity: O(1). + */ + +igraph_progress_handler_t * +igraph_set_progress_handler(igraph_progress_handler_t new_handler) { + igraph_progress_handler_t *previous_handler = igraph_i_progress_handler; + igraph_i_progress_handler = new_handler; + return previous_handler; +} diff --git a/src/prpack.cpp b/src/prpack.cpp new file mode 100644 index 0000000..d6a7260 --- /dev/null +++ b/src/prpack.cpp @@ -0,0 +1,103 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "prpack.h" +#include "prpack/prpack_igraph_graph.h" +#include "prpack/prpack_solver.h" +#include "igraph_error.h" + +using namespace prpack; +using namespace std; + +/* + * PRPACK-based implementation of \c igraph_personalized_pagerank. + * + * See \c igraph_personalized_pagerank for the documentation of the parameters. + */ +int igraph_personalized_pagerank_prpack(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, const igraph_vs_t vids, + igraph_bool_t directed, igraph_real_t damping, + igraph_vector_t *reset, + const igraph_vector_t *weights) { + long int i, no_of_nodes = igraph_vcount(graph), nodes_to_calc; + igraph_vit_t vit; + double* u = 0; + double* v = 0; + const prpack_result* res; + + if (reset) { + /* Normalize reset vector so the sum is 1 */ + double reset_sum = igraph_vector_sum(reset); + if (igraph_vector_min(reset) < 0) { + IGRAPH_ERROR("the reset vector must not contain negative elements", IGRAPH_EINVAL); + } + if (reset_sum == 0) { + IGRAPH_ERROR("the sum of the elements in the reset vector must not be zero", IGRAPH_EINVAL); + } + + // Construct the personalization vector + v = new double[no_of_nodes]; + for (i = 0; i < no_of_nodes; i++) { + v[i] = VECTOR(*reset)[i] / reset_sum; + } + } + + // Construct and run the solver + prpack_igraph_graph prpack_graph(graph, weights, directed); + prpack_solver solver(&prpack_graph, false); + res = solver.solve(damping, 1e-10, u, v, ""); + + // Delete the personalization vector + if (v) { + delete[] v; + } + + // Check whether the solver converged + // TODO: this is commented out because some of the solvers do not implement it yet + /* + if (!res->converged) { + IGRAPH_WARNING("PRPACK solver failed to converge. Results may be inaccurate."); + } + */ + + // Fill the result vector + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + IGRAPH_CHECK(igraph_vector_resize(vector, nodes_to_calc)); + for (IGRAPH_VIT_RESET(vit), i = 0; !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + VECTOR(*vector)[i] = res->x[(long int)IGRAPH_VIT_GET(vit)]; + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + // TODO: can we get the eigenvalue? We'll just fake it until we can. + if (value) { + *value = 1.0; + } + delete res; + + return IGRAPH_SUCCESS; +} + diff --git a/src/prpack.h b/src/prpack.h new file mode 100644 index 0000000..c5c05fc --- /dev/null +++ b/src/prpack.h @@ -0,0 +1,54 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef IGRAPH_PRPACK +#define IGRAPH_PRPACK + +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus + #define __BEGIN_DECLS extern "C" { + #define __END_DECLS } +#else + #define __BEGIN_DECLS /* empty */ + #define __END_DECLS /* empty */ +#endif + +#include "igraph_types.h" +#include "igraph_datatype.h" +#include "igraph_iterators.h" + +#include "igraph_interface.h" + +__BEGIN_DECLS + +int igraph_personalized_pagerank_prpack(const igraph_t *graph, igraph_vector_t *vector, + igraph_real_t *value, const igraph_vs_t vids, + igraph_bool_t directed, igraph_real_t damping, + igraph_vector_t *reset, + const igraph_vector_t *weights); + +__END_DECLS + +#endif + diff --git a/src/prpack/prpack.h b/src/prpack/prpack.h new file mode 100644 index 0000000..bcddf37 --- /dev/null +++ b/src/prpack/prpack.h @@ -0,0 +1,11 @@ +#ifndef PRPACK +#define PRPACK + +#include "prpack_csc.h" +#include "prpack_csr.h" +#include "prpack_edge_list.h" +#include "prpack_base_graph.h" +#include "prpack_solver.h" +#include "prpack_result.h" + +#endif diff --git a/src/prpack/prpack.inc b/src/prpack/prpack.inc new file mode 100644 index 0000000..ce1e7fa --- /dev/null +++ b/src/prpack/prpack.inc @@ -0,0 +1,23 @@ +PRPACK = prpack/prpack_base_graph.cpp \ + prpack/prpack_igraph_graph.cpp \ + prpack/prpack_preprocessed_ge_graph.cpp \ + prpack/prpack_preprocessed_gs_graph.cpp \ + prpack/prpack_preprocessed_scc_graph.cpp \ + prpack/prpack_preprocessed_schur_graph.cpp \ + prpack/prpack_result.cpp \ + prpack/prpack_solver.cpp \ + prpack/prpack_utils.cpp \ + prpack/prpack.h \ + prpack/prpack_base_graph.h \ + prpack/prpack_csc.h \ + prpack/prpack_csr.h \ + prpack/prpack_edge_list.h \ + prpack/prpack_igraph_graph.h \ + prpack/prpack_preprocessed_ge_graph.h \ + prpack/prpack_preprocessed_graph.h \ + prpack/prpack_preprocessed_gs_graph.h \ + prpack/prpack_preprocessed_scc_graph.h \ + prpack/prpack_preprocessed_schur_graph.h \ + prpack/prpack_result.h \ + prpack/prpack_solver.h \ + prpack/prpack_utils.h diff --git a/src/prpack/prpack_base_graph.cpp b/src/prpack/prpack_base_graph.cpp new file mode 100644 index 0000000..b7a6cbe --- /dev/null +++ b/src/prpack/prpack_base_graph.cpp @@ -0,0 +1,333 @@ +#include "prpack_base_graph.h" +#include "prpack_utils.h" +#include +#include +#include +#include +#include +#include +using namespace prpack; +using namespace std; + +void prpack_base_graph::initialize() { + heads = NULL; + tails = NULL; + vals = NULL; +} + +prpack_base_graph::prpack_base_graph() { + initialize(); + num_vs = num_es = 0; +} + +prpack_base_graph::prpack_base_graph(const prpack_csc* g) { + initialize(); + num_vs = g->num_vs; + num_es = g->num_es; + // fill in heads and tails + num_self_es = 0; + int* hs = g->heads; + int* ts = g->tails; + tails = new int[num_vs]; + memset(tails, 0, num_vs*sizeof(tails[0])); + for (int h = 0; h < num_vs; ++h) { + const int start_ti = hs[h]; + const int end_ti = (h + 1 != num_vs) ? hs[h + 1] : num_es; + for (int ti = start_ti; ti < end_ti; ++ti) { + const int t = ts[ti]; + ++tails[t]; + if (h == t) + ++num_self_es; + } + } + for (int i = 0, sum = 0; i < num_vs; ++i) { + const int temp = sum; + sum += tails[i]; + tails[i] = temp; + } + heads = new int[num_es]; + int* osets = new int[num_vs]; + memset(osets, 0, num_vs*sizeof(osets[0])); + for (int h = 0; h < num_vs; ++h) { + const int start_ti = hs[h]; + const int end_ti = (h + 1 != num_vs) ? hs[h + 1] : num_es; + for (int ti = start_ti; ti < end_ti; ++ti) { + const int t = ts[ti]; + heads[tails[t] + osets[t]++] = h; + } + } + // clean up + delete[] osets; +} + +prpack_base_graph::prpack_base_graph(const prpack_int64_csc* g) { + initialize(); + // TODO remove the assert and add better behavior + assert(num_vs <= std::numeric_limits::max()); + num_vs = (int)g->num_vs; + num_es = (int)g->num_es; + // fill in heads and tails + num_self_es = 0; + int64_t* hs = g->heads; + int64_t* ts = g->tails; + tails = new int[num_vs]; + memset(tails, 0, num_vs*sizeof(tails[0])); + for (int h = 0; h < num_vs; ++h) { + const int start_ti = (int)hs[h]; + const int end_ti = (h + 1 != num_vs) ? (int)hs[h + 1] : num_es; + for (int ti = start_ti; ti < end_ti; ++ti) { + const int t = (int)ts[ti]; + ++tails[t]; + if (h == t) + ++num_self_es; + } + } + for (int i = 0, sum = 0; i < num_vs; ++i) { + const int temp = sum; + sum += tails[i]; + tails[i] = temp; + } + heads = new int[num_es]; + int* osets = new int[num_vs]; + memset(osets, 0, num_vs*sizeof(osets[0])); + for (int h = 0; h < num_vs; ++h) { + const int start_ti = (int)hs[h]; + const int end_ti = (h + 1 != num_vs) ? (int)hs[h + 1] : num_es; + for (int ti = start_ti; ti < end_ti; ++ti) { + const int t = (int)ts[ti]; + heads[tails[t] + osets[t]++] = h; + } + } + // clean up + delete[] osets; +} + +prpack_base_graph::prpack_base_graph(const prpack_csr* g) { + initialize(); + assert(false); + // TODO +} + +prpack_base_graph::prpack_base_graph(const prpack_edge_list* g) { + initialize(); + num_vs = g->num_vs; + num_es = g->num_es; + // fill in heads and tails + num_self_es = 0; + int* hs = g->heads; + int* ts = g->tails; + tails = new int[num_vs]; + memset(tails, 0, num_vs*sizeof(tails[0])); + for (int i = 0; i < num_es; ++i) { + ++tails[ts[i]]; + if (hs[i] == ts[i]) + ++num_self_es; + } + for (int i = 0, sum = 0; i < num_vs; ++i) { + const int temp = sum; + sum += tails[i]; + tails[i] = temp; + } + heads = new int[num_es]; + int* osets = new int[num_vs]; + memset(osets, 0, num_vs*sizeof(osets[0])); + for (int i = 0; i < num_es; ++i) + heads[tails[ts[i]] + osets[ts[i]]++] = hs[i]; + // clean up + delete[] osets; +} + +prpack_base_graph::prpack_base_graph(const char* filename, const char* format, const bool weighted) { + initialize(); + FILE* f = fopen(filename, "r"); + const string s(filename); + const string t(format); + const string ext = (t == "") ? s.substr(s.rfind('.') + 1) : t; + if (ext == "smat") { + read_smat(f, weighted); + } else { + prpack_utils::validate(!weighted, + "Error: graph format is not compatible with weighted option."); + if (ext == "edges" || ext == "eg2") { + read_edges(f); + } else if (ext == "graph-txt") { + read_ascii(f); + } else { + prpack_utils::validate(false, "Error: invalid graph format."); + } + } + fclose(f); +} + +prpack_base_graph::~prpack_base_graph() { + delete[] heads; + delete[] tails; + delete[] vals; +} + +void prpack_base_graph::read_smat(FILE* f, const bool weighted) { + // read in header + double ignore = 0.0; + assert(fscanf(f, "%d %lf %d", &num_vs, &ignore, &num_es) == 3); + // fill in heads and tails + num_self_es = 0; + int* hs = new int[num_es]; + int* ts = new int[num_es]; + heads = new int[num_es]; + tails = new int[num_vs]; + double* vs = NULL; + if (weighted) { + vs = new double[num_es]; + vals = new double[num_es]; + } + memset(tails, 0, num_vs*sizeof(tails[0])); + for (int i = 0; i < num_es; ++i) { + assert(fscanf(f, "%d %d %lf", + &hs[i], &ts[i], &((weighted) ? vs[i] : ignore)) == 3); + ++tails[ts[i]]; + if (hs[i] == ts[i]) + ++num_self_es; + } + for (int i = 0, sum = 0; i < num_vs; ++i) { + const int temp = sum; + sum += tails[i]; + tails[i] = temp; + } + int* osets = new int[num_vs]; + memset(osets, 0, num_vs*sizeof(osets[0])); + for (int i = 0; i < num_es; ++i) { + const int idx = tails[ts[i]] + osets[ts[i]]++; + heads[idx] = hs[i]; + if (weighted) + vals[idx] = vs[i]; + } + // clean up + delete[] hs; + delete[] ts; + delete[] vs; + delete[] osets; +} + +void prpack_base_graph::read_edges(FILE* f) { + vector > al; + int h, t; + num_es = num_self_es = 0; + while (fscanf(f, "%d %d", &h, &t) == 2) { + const int m = (h < t) ? t : h; + if ((int) al.size() < m + 1) + al.resize(m + 1); + al[t].push_back(h); + ++num_es; + if (h == t) + ++num_self_es; + } + num_vs = al.size(); + heads = new int[num_es]; + tails = new int[num_vs]; + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + tails[tails_i] = heads_i; + for (int j = 0; j < (int) al[tails_i].size(); ++j) + heads[heads_i++] = al[tails_i][j]; + } +} + +void prpack_base_graph::read_ascii(FILE* f) { + assert(fscanf(f, "%d", &num_vs) == 1); + while (getc(f) != '\n'); + vector* al = new vector[num_vs]; + num_es = num_self_es = 0; + char s[32]; + for (int h = 0; h < num_vs; ++h) { + bool line_ended = false; + while (!line_ended) { + for (int i = 0; ; ++i) { + s[i] = getc(f); + if ('9' < s[i] || s[i] < '0') { + line_ended = s[i] == '\n'; + if (i != 0) { + s[i] = '\0'; + const int t = atoi(s); + al[t].push_back(h); + ++num_es; + if (h == t) + ++num_self_es; + } + break; + } + } + } + } + heads = new int[num_es]; + tails = new int[num_vs]; + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + tails[tails_i] = heads_i; + for (int j = 0; j < (int) al[tails_i].size(); ++j) + heads[heads_i++] = al[tails_i][j]; + } + delete[] al; +} + +prpack_base_graph::prpack_base_graph(int nverts, int nedges, + std::pair* edges) { + initialize(); + num_vs = nverts; + num_es = nedges; + + // fill in heads and tails + num_self_es = 0; + int* hs = new int[num_es]; + int* ts = new int[num_es]; + tails = new int[num_vs]; + memset(tails, 0, num_vs*sizeof(tails[0])); + for (int i = 0; i < num_es; ++i) { + assert(edges[i].first >= 0 && edges[i].first < num_vs); + assert(edges[i].second >= 0 && edges[i].second < num_vs); + hs[i] = edges[i].first; + ts[i] = edges[i].second; + ++tails[ts[i]]; + if (hs[i] == ts[i]) + ++num_self_es; + } + for (int i = 0, sum = 0; i < num_vs; ++i) { + int temp = sum; + sum += tails[i]; + tails[i] = temp; + } + heads = new int[num_es]; + int* osets = new int[num_vs]; + memset(osets, 0, num_vs*sizeof(osets[0])); + for (int i = 0; i < num_es; ++i) + heads[tails[ts[i]] + osets[ts[i]]++] = hs[i]; + // clean up + delete[] hs; + delete[] ts; + delete[] osets; +} + +/** Normalize the edge weights to sum to one. + */ +void prpack_base_graph::normalize_weights() { + if (!vals) { + // skip normalizing weights if not using values + return; + } + std::vector rowsums(num_vs,0.); + // the graph is in a compressed in-edge list. + for (int i=0; i +#include + +namespace prpack { + + class prpack_base_graph { + private: + // helper methods + void initialize(); + void read_smat(std::FILE* f, const bool weighted); + void read_edges(std::FILE* f); + void read_ascii(std::FILE* f); + public: + // instance variables + int num_vs; + int num_es; + int num_self_es; + int* heads; + int* tails; + double* vals; + // constructors + prpack_base_graph(); // only to support inheritance + prpack_base_graph(const prpack_csc* g); + prpack_base_graph(const prpack_int64_csc* g); + prpack_base_graph(const prpack_csr* g); + prpack_base_graph(const prpack_edge_list* g); + prpack_base_graph(const char* filename, const char* format, const bool weighted); + prpack_base_graph(int nverts, int nedges, std::pair* edges); + // destructor + ~prpack_base_graph(); + // operations + void normalize_weights(); + }; + +}; + +#endif diff --git a/src/prpack/prpack_csc.h b/src/prpack/prpack_csc.h new file mode 100644 index 0000000..977481e --- /dev/null +++ b/src/prpack/prpack_csc.h @@ -0,0 +1,30 @@ +#ifndef PRPACK_CSC +#define PRPACK_CSC + +#if !defined(_MSC_VER) && !defined (__MINGW32__) && !defined (__MINGW64__) +# include +#else +# include +typedef __int64 int64_t; +#endif + +namespace prpack { + + class prpack_csc { + public: + int num_vs; + int num_es; + int* heads; + int* tails; + }; + + class prpack_int64_csc { + public: + int64_t num_vs; + int64_t num_es; + int64_t* heads; + int64_t* tails; + }; +}; + +#endif diff --git a/src/prpack/prpack_csr.h b/src/prpack/prpack_csr.h new file mode 100644 index 0000000..5eab466 --- /dev/null +++ b/src/prpack/prpack_csr.h @@ -0,0 +1,16 @@ +#ifndef PRPACK_CSR +#define PRPACK_CSR + +namespace prpack { + + class prpack_csr { + public: + int num_vs; + int num_es; + int* heads; + int* tails; + }; + +}; + +#endif diff --git a/src/prpack/prpack_edge_list.h b/src/prpack/prpack_edge_list.h new file mode 100644 index 0000000..0f62113 --- /dev/null +++ b/src/prpack/prpack_edge_list.h @@ -0,0 +1,16 @@ +#ifndef PRPACK_EDGE_LIST +#define PRPACK_EDGE_LIST + +namespace prpack { + + class prpack_edge_list { + public: + int num_vs; + int num_es; + int* heads; + int* tails; + }; + +}; + +#endif diff --git a/src/prpack/prpack_igraph_graph.cpp b/src/prpack/prpack_igraph_graph.cpp new file mode 100644 index 0000000..6c0400e --- /dev/null +++ b/src/prpack/prpack_igraph_graph.cpp @@ -0,0 +1,146 @@ +#include "prpack_igraph_graph.h" +#include +#include + +using namespace prpack; +using namespace std; + +#ifdef PRPACK_IGRAPH_SUPPORT + +prpack_igraph_graph::prpack_igraph_graph(const igraph_t* g, const igraph_vector_t* weights, + igraph_bool_t directed) { + const igraph_bool_t treat_as_directed = igraph_is_directed(g) && directed; + igraph_es_t es; + igraph_eit_t eit; + igraph_vector_t neis; + long int i, j, eid, sum, temp, num_ignored_es; + int *p_head, *p_head_copy; + double* p_weight; + + // Get the number of vertices and edges. For undirected graphs, we add + // an edge in both directions. + num_vs = igraph_vcount(g); + num_es = igraph_ecount(g); + num_self_es = 0; + if (!treat_as_directed) { + num_es *= 2; + } + + // Allocate memory for heads and tails + p_head = heads = new int[num_es]; + tails = new int[num_vs]; + memset(tails, 0, num_vs * sizeof(tails[0])); + + // Allocate memory for weights if needed + if (weights != 0) { + p_weight = vals = new double[num_es]; + } + + // Count the number of ignored edges (those with negative or zero weight) + num_ignored_es = 0; + + if (treat_as_directed) { + // Select all the edges and iterate over them by the source vertices + es = igraph_ess_all(IGRAPH_EDGEORDER_TO); + + // Add the edges + igraph_eit_create(g, es, &eit); + while (!IGRAPH_EIT_END(eit)) { + eid = IGRAPH_EIT_GET(eit); + IGRAPH_EIT_NEXT(eit); + + // Handle the weight + if (weights != 0) { + // Does this edge have zero or negative weight? + if (VECTOR(*weights)[eid] <= 0) { + // Ignore it. + num_ignored_es++; + continue; + } + + *p_weight = VECTOR(*weights)[eid]; + ++p_weight; + } + + *p_head = IGRAPH_FROM(g, eid); + ++p_head; + ++tails[IGRAPH_TO(g, eid)]; + + if (IGRAPH_FROM(g, eid) == IGRAPH_TO(g, eid)) { + ++num_self_es; + } + } + igraph_eit_destroy(&eit); + } else { + // Select all the edges and iterate over them by the target vertices + igraph_vector_init(&neis, 0); + + for (i = 0; i < num_vs; i++) { + igraph_incident(g, &neis, i, IGRAPH_ALL); + temp = igraph_vector_size(&neis); + + // TODO: should loop edges be added in both directions? + p_head_copy = p_head; + for (j = 0; j < temp; j++) { + if (weights != 0) { + if (VECTOR(*weights)[(long int)VECTOR(neis)[j]] <= 0) { + // Ignore + num_ignored_es++; + continue; + } + + *p_weight = VECTOR(*weights)[(long int)VECTOR(neis)[j]]; + ++p_weight; + } + + *p_head = IGRAPH_OTHER(g, VECTOR(neis)[j], i); + if (i == *p_head) { + num_self_es++; + } + ++p_head; + } + tails[i] = p_head - p_head_copy; + } + + igraph_vector_destroy(&neis); + } + + // Decrease num_es by the number of ignored edges + num_es -= num_ignored_es; + + // Finalize the tails vector + for (i = 0, sum = 0; i < num_vs; ++i) { + temp = sum; + sum += tails[i]; + tails[i] = temp; + } + + // Normalize the weights + normalize_weights(); + + // Debug + /* + printf("Heads:"); + for (i = 0; i < num_es; ++i) { + printf(" %d", heads[i]); + } + printf("\n"); + printf("Tails:"); + for (i = 0; i < num_vs; ++i) { + printf(" %d", tails[i]); + } + printf("\n"); + if (vals) { + printf("Vals:"); + for (i = 0; i < num_es; ++i) { + printf(" %.4f", vals[i]); + } + printf("\n"); + } + printf("===========================\n"); + */ +} + +// PRPACK_IGRAPH_SUPPORT +#endif + diff --git a/src/prpack/prpack_igraph_graph.h b/src/prpack/prpack_igraph_graph.h new file mode 100644 index 0000000..682c4c5 --- /dev/null +++ b/src/prpack/prpack_igraph_graph.h @@ -0,0 +1,26 @@ +#ifndef PRPACK_IGRAPH_GRAPH +#define PRPACK_IGRAPH_GRAPH + +#ifdef PRPACK_IGRAPH_SUPPORT + +#include "igraph_interface.h" +#include "prpack_base_graph.h" + +namespace prpack { + + class prpack_igraph_graph : public prpack_base_graph { + + public: + // constructors + explicit prpack_igraph_graph(const igraph_t* g, + const igraph_vector_t* weights = 0, + igraph_bool_t directed = true); + }; + +}; + +// PRPACK_IGRAPH_SUPPORT +#endif + +// PRPACK_IGRAPH_GRAPH +#endif diff --git a/src/prpack/prpack_preprocessed_ge_graph.cpp b/src/prpack/prpack_preprocessed_ge_graph.cpp new file mode 100644 index 0000000..20826e6 --- /dev/null +++ b/src/prpack/prpack_preprocessed_ge_graph.cpp @@ -0,0 +1,64 @@ +#include "prpack_preprocessed_ge_graph.h" +#include +using namespace prpack; +using namespace std; + +void prpack_preprocessed_ge_graph::initialize() { + matrix = NULL; + d = NULL; +} + +void prpack_preprocessed_ge_graph::initialize_weighted(const prpack_base_graph* bg) { + // initialize d + fill(d, d + num_vs, 1); + // fill in the matrix + for (int i = 0, inum_vs = 0; i < num_vs; ++i, inum_vs += num_vs) { + const int start_j = bg->tails[i]; + const int end_j = (i + 1 != num_vs) ? bg->tails[i + 1] : bg->num_es; + for (int j = start_j; j < end_j; ++j) + d[bg->heads[j]] -= matrix[inum_vs + bg->heads[j]] = bg->vals[j]; + } +} + +void prpack_preprocessed_ge_graph::initialize_unweighted(const prpack_base_graph* bg) { + // fill in the matrix + for (int i = 0, inum_vs = 0; i < num_vs; ++i, inum_vs += num_vs) { + const int start_j = bg->tails[i]; + const int end_j = (i + 1 != num_vs) ? bg->tails[i + 1] : bg->num_es; + for (int j = start_j; j < end_j; ++j) + ++matrix[inum_vs + bg->heads[j]]; + } + // normalize the columns + for (int j = 0; j < num_vs; ++j) { + double sum = 0; + for (int inum_vs = 0; inum_vs < num_vs*num_vs; inum_vs += num_vs) + sum += matrix[inum_vs + j]; + if (sum > 0) { + d[j] = 0; + const double coeff = 1/sum; + for (int inum_vs = 0; inum_vs < num_vs*num_vs; inum_vs += num_vs) + matrix[inum_vs + j] *= coeff; + } else { + d[j] = 1; + } + } +} + +prpack_preprocessed_ge_graph::prpack_preprocessed_ge_graph(const prpack_base_graph* bg) { + initialize(); + num_vs = bg->num_vs; + num_es = bg->num_es; + matrix = new double[num_vs*num_vs]; + d = new double[num_vs]; + fill(matrix, matrix + num_vs*num_vs, 0); + if (bg->vals != NULL) + initialize_weighted(bg); + else + initialize_unweighted(bg); +} + +prpack_preprocessed_ge_graph::~prpack_preprocessed_ge_graph() { + delete[] matrix; + delete[] d; +} + diff --git a/src/prpack/prpack_preprocessed_ge_graph.h b/src/prpack/prpack_preprocessed_ge_graph.h new file mode 100644 index 0000000..83476a6 --- /dev/null +++ b/src/prpack/prpack_preprocessed_ge_graph.h @@ -0,0 +1,26 @@ +#ifndef PRPACK_PREPROCESSED_GE_GRAPH +#define PRPACK_PREPROCESSED_GE_GRAPH +#include "prpack_preprocessed_graph.h" +#include "prpack_base_graph.h" + +namespace prpack { + + // Pre-processed graph class + class prpack_preprocessed_ge_graph : public prpack_preprocessed_graph { + private: + // helper methods + void initialize(); + void initialize_weighted(const prpack_base_graph* bg); + void initialize_unweighted(const prpack_base_graph* bg); + public: + // instance variables + double* matrix; + // constructors + prpack_preprocessed_ge_graph(const prpack_base_graph* bg); + // destructor + ~prpack_preprocessed_ge_graph(); + }; + +}; + +#endif diff --git a/src/prpack/prpack_preprocessed_graph.h b/src/prpack/prpack_preprocessed_graph.h new file mode 100644 index 0000000..043415f --- /dev/null +++ b/src/prpack/prpack_preprocessed_graph.h @@ -0,0 +1,17 @@ +#ifndef PRPACK_PREPROCESSED_GRAPH +#define PRPACK_PREPROCESSED_GRAPH + +namespace prpack { + + // TODO: this class should not be seeable by the users of the library. + // Super graph class. + class prpack_preprocessed_graph { + public: + int num_vs; + int num_es; + double* d; + }; + +}; + +#endif diff --git a/src/prpack/prpack_preprocessed_gs_graph.cpp b/src/prpack/prpack_preprocessed_gs_graph.cpp new file mode 100644 index 0000000..c50e859 --- /dev/null +++ b/src/prpack/prpack_preprocessed_gs_graph.cpp @@ -0,0 +1,81 @@ +#include "prpack_preprocessed_gs_graph.h" +#include +using namespace prpack; +using namespace std; + +void prpack_preprocessed_gs_graph::initialize() { + heads = NULL; + tails = NULL; + vals = NULL; + ii = NULL; + d = NULL; + num_outlinks = NULL; +} + +void prpack_preprocessed_gs_graph::initialize_weighted(const prpack_base_graph* bg) { + vals = new double[num_es]; + d = new double[num_vs]; + fill(d, d + num_vs, 1); + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + tails[tails_i] = heads_i; + ii[tails_i] = 0; + const int start_j = bg->tails[tails_i]; + const int end_j = (tails_i + 1 != num_vs) ? bg->tails[tails_i + 1]: bg->num_es; + for (int j = start_j; j < end_j; ++j) { + if (tails_i == bg->heads[j]) + ii[tails_i] += bg->vals[j]; + else { + heads[heads_i] = bg->heads[j]; + vals[heads_i] = bg->vals[j]; + ++heads_i; + } + d[bg->heads[j]] -= bg->vals[j]; + } + } +} + +void prpack_preprocessed_gs_graph::initialize_unweighted(const prpack_base_graph* bg) { + num_outlinks = new double[num_vs]; + fill(num_outlinks, num_outlinks + num_vs, 0); + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + tails[tails_i] = heads_i; + ii[tails_i] = 0; + const int start_j = bg->tails[tails_i]; + const int end_j = (tails_i + 1 != num_vs) ? bg->tails[tails_i + 1]: bg->num_es; + for (int j = start_j; j < end_j; ++j) { + if (tails_i == bg->heads[j]) + ++ii[tails_i]; + else + heads[heads_i++] = bg->heads[j]; + ++num_outlinks[bg->heads[j]]; + } + } + for (int i = 0; i < num_vs; ++i) { + if (num_outlinks[i] == 0) + num_outlinks[i] = -1; + ii[i] /= num_outlinks[i]; + } +} + +prpack_preprocessed_gs_graph::prpack_preprocessed_gs_graph(const prpack_base_graph* bg) { + initialize(); + num_vs = bg->num_vs; + num_es = bg->num_es - bg->num_self_es; + heads = new int[num_es]; + tails = new int[num_vs]; + ii = new double[num_vs]; + if (bg->vals != NULL) + initialize_weighted(bg); + else + initialize_unweighted(bg); +} + +prpack_preprocessed_gs_graph::~prpack_preprocessed_gs_graph() { + delete[] heads; + delete[] tails; + delete[] vals; + delete[] ii; + delete[] d; + delete[] num_outlinks; +} + diff --git a/src/prpack/prpack_preprocessed_gs_graph.h b/src/prpack/prpack_preprocessed_gs_graph.h new file mode 100644 index 0000000..5a459d2 --- /dev/null +++ b/src/prpack/prpack_preprocessed_gs_graph.h @@ -0,0 +1,30 @@ +#ifndef PRPACK_PREPROCESSED_GS_GRAPH +#define PRPACK_PREPROCESSED_GS_GRAPH +#include "prpack_preprocessed_graph.h" +#include "prpack_base_graph.h" + +namespace prpack { + + // Pre-processed graph class + class prpack_preprocessed_gs_graph : public prpack_preprocessed_graph { + private: + // helper methods + void initialize(); + void initialize_weighted(const prpack_base_graph* bg); + void initialize_unweighted(const prpack_base_graph* bg); + public: + // instance variables + int* heads; + int* tails; + double* vals; + double* ii; + double* num_outlinks; + // constructors + prpack_preprocessed_gs_graph(const prpack_base_graph* bg); + // destructor + ~prpack_preprocessed_gs_graph(); + }; + +}; + +#endif diff --git a/src/prpack/prpack_preprocessed_scc_graph.cpp b/src/prpack/prpack_preprocessed_scc_graph.cpp new file mode 100644 index 0000000..6bf911e --- /dev/null +++ b/src/prpack/prpack_preprocessed_scc_graph.cpp @@ -0,0 +1,202 @@ +#include "prpack_preprocessed_scc_graph.h" +#include +#include +#include +using namespace prpack; +using namespace std; + +void prpack_preprocessed_scc_graph::initialize() { + heads_inside = NULL; + tails_inside = NULL; + vals_inside = NULL; + heads_outside = NULL; + tails_outside = NULL; + vals_outside = NULL; + ii = NULL; + d = NULL; + num_outlinks = NULL; + divisions = NULL; + encoding = NULL; + decoding = NULL; +} + +void prpack_preprocessed_scc_graph::initialize_weighted(const prpack_base_graph* bg) { + vals_inside = new double[num_es]; + vals_outside = new double[num_es]; + d = new double[num_vs]; + fill(d, d + num_vs, 1); + for (int comp_i = 0; comp_i < num_comps; ++comp_i) { + const int start_i = divisions[comp_i]; + const int end_i = (comp_i + 1 != num_comps) ? divisions[comp_i + 1] : num_vs; + for (int i = start_i; i < end_i; ++i) { + ii[i] = 0; + const int decoded = decoding[i]; + const int start_j = bg->tails[decoded]; + const int end_j = (decoded + 1 != num_vs) ? bg->tails[decoded + 1] : bg->num_es; + tails_inside[i] = num_es_inside; + tails_outside[i] = num_es_outside; + for (int j = start_j; j < end_j; ++j) { + const int h = encoding[bg->heads[j]]; + if (h == i) { + ii[i] += bg->vals[j]; + } else { + if (start_i <= h && h < end_i) { + heads_inside[num_es_inside] = h; + vals_inside[num_es_inside] = bg->vals[j]; + ++num_es_inside; + } else { + heads_outside[num_es_outside] = h; + vals_outside[num_es_outside] = bg->vals[j]; + ++num_es_outside; + } + } + d[h] -= bg->vals[j]; + } + } + } +} + +void prpack_preprocessed_scc_graph::initialize_unweighted(const prpack_base_graph* bg) { + num_outlinks = new double[num_vs]; + fill(num_outlinks, num_outlinks + num_vs, 0); + for (int comp_i = 0; comp_i < num_comps; ++comp_i) { + const int start_i = divisions[comp_i]; + const int end_i = (comp_i + 1 != num_comps) ? divisions[comp_i + 1] : num_vs; + for (int i = start_i; i < end_i; ++i) { + ii[i] = 0; + const int decoded = decoding[i]; + const int start_j = bg->tails[decoded]; + const int end_j = (decoded + 1 != num_vs) ? bg->tails[decoded + 1] : bg->num_es; + tails_inside[i] = num_es_inside; + tails_outside[i] = num_es_outside; + for (int j = start_j; j < end_j; ++j) { + const int h = encoding[bg->heads[j]]; + if (h == i) { + ++ii[i]; + } else { + if (start_i <= h && h < end_i) + heads_inside[num_es_inside++] = h; + else + heads_outside[num_es_outside++] = h; + } + ++num_outlinks[h]; + } + } + } + for (int i = 0; i < num_vs; ++i) { + if (num_outlinks[i] == 0) + num_outlinks[i] = -1; + ii[i] /= num_outlinks[i]; + } +} + +prpack_preprocessed_scc_graph::prpack_preprocessed_scc_graph(const prpack_base_graph* bg) { + initialize(); + // initialize instance variables + num_vs = bg->num_vs; + num_es = bg->num_es - bg->num_self_es; + // initialize Tarjan's algorithm variables + num_comps = 0; + int mn = 0; // the number of vertices seen so far + int sz = 0; // size of st + int decoding_i = 0; // size of decoding currently filled in + decoding = new int[num_vs]; + int* scc = new int[num_vs]; // the strongly connected component this vertex is in + int* low = new int[num_vs]; // the lowest index this vertex can reach + int* num = new int[num_vs]; // the index of this vertex in the dfs traversal + int* st = new int[num_vs]; // a stack for the dfs + memset(num, -1, num_vs*sizeof(num[0])); + memset(scc, -1, num_vs*sizeof(scc[0])); + int* cs1 = new int[num_vs]; // call stack variable for dfs + int* cs2 = new int[num_vs]; // call stack variable for dfs + // run iterative Tarjan's algorithm + for (int root = 0; root < num_vs; ++root) { + if (num[root] != -1) + continue; + int csz = 1; + cs1[0] = root; + cs2[0] = bg->tails[root]; + // dfs + while (csz) { + const int p = cs1[csz - 1]; // node we're dfs-ing on + int& it = cs2[csz - 1]; // iteration of the for loop + if (it == bg->tails[p]) { + low[p] = num[p] = mn++; + st[sz++] = p; + } else { + low[p] = min(low[p], low[bg->heads[it - 1]]); + } + bool done = false; + int end_it = (p + 1 != num_vs) ? bg->tails[p + 1] : bg->num_es; + for (; it < end_it; ++it) { + int h = bg->heads[it]; + if (scc[h] == -1) { + if (num[h] == -1) { + // dfs(h, p); + cs1[csz] = h; + cs2[csz++] = bg->tails[h]; + ++it; + done = true; + break; + } + low[p] = min(low[p], low[h]); + } + } + if (done) + continue; + // if p is the first explored vertex of a scc + if (low[p] == num[p]) { + cs1[num_vs - 1 - num_comps] = decoding_i; + while (scc[p] != num_comps) { + scc[st[--sz]] = num_comps; + decoding[decoding_i++] = st[sz]; + } + ++num_comps; + } + --csz; + } + } + // set up other instance variables + divisions = new int[num_comps]; + divisions[0] = 0; + for (int i = 1; i < num_comps; ++i) + divisions[i] = cs1[num_vs - 1 - i]; + encoding = num; + for (int i = 0; i < num_vs; ++i) + encoding[decoding[i]] = i; + // fill in inside and outside instance variables + ii = new double[num_vs]; + tails_inside = cs1; + heads_inside = new int[num_es]; + tails_outside = cs2; + heads_outside = new int[num_es]; + num_es_inside = num_es_outside = 0; + // continue initialization based off of weightedness + if (bg->vals != NULL) + initialize_weighted(bg); + else + initialize_unweighted(bg); + // free memory + // do not free num <==> encoding + // do not free cs1 <==> tails_inside + // do not free cs2 <==> tails_outside + delete[] scc; + delete[] low; + delete[] st; +} + +prpack_preprocessed_scc_graph::~prpack_preprocessed_scc_graph() { + delete[] heads_inside; + delete[] tails_inside; + delete[] vals_inside; + delete[] heads_outside; + delete[] tails_outside; + delete[] vals_outside; + delete[] ii; + delete[] d; + delete[] num_outlinks; + delete[] divisions; + delete[] encoding; + delete[] decoding; +} + diff --git a/src/prpack/prpack_preprocessed_scc_graph.h b/src/prpack/prpack_preprocessed_scc_graph.h new file mode 100644 index 0000000..1cc9e94 --- /dev/null +++ b/src/prpack/prpack_preprocessed_scc_graph.h @@ -0,0 +1,39 @@ +#ifndef PRPACK_PREPROCESSED_SCC_GRAPH +#define PRPACK_PREPROCESSED_SCC_GRAPH +#include "prpack_preprocessed_graph.h" +#include "prpack_base_graph.h" + +namespace prpack { + + // Pre-processed graph class + class prpack_preprocessed_scc_graph : public prpack_preprocessed_graph { + private: + // helper methods + void initialize(); + void initialize_weighted(const prpack_base_graph* bg); + void initialize_unweighted(const prpack_base_graph* bg); + public: + // instance variables + int num_es_inside; + int* heads_inside; + int* tails_inside; + double* vals_inside; + int num_es_outside; + int* heads_outside; + int* tails_outside; + double* vals_outside; + double* ii; + double* num_outlinks; + int num_comps; + int* divisions; + int* encoding; + int* decoding; + // constructors + prpack_preprocessed_scc_graph(const prpack_base_graph* bg); + // destructor + ~prpack_preprocessed_scc_graph(); + }; + +}; + +#endif diff --git a/src/prpack/prpack_preprocessed_schur_graph.cpp b/src/prpack/prpack_preprocessed_schur_graph.cpp new file mode 100644 index 0000000..a7e961e --- /dev/null +++ b/src/prpack/prpack_preprocessed_schur_graph.cpp @@ -0,0 +1,121 @@ +#include "prpack_preprocessed_schur_graph.h" +#include +#include +using namespace prpack; +using namespace std; + +void prpack_preprocessed_schur_graph::initialize() { + heads = NULL; + tails = NULL; + vals = NULL; + ii = NULL; + d = NULL; + num_outlinks = NULL; + encoding = NULL; + decoding = NULL; +} + +void prpack_preprocessed_schur_graph::initialize_weighted(const prpack_base_graph* bg) { + // permute d + ii = d; + d = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + d[encoding[i]] = ii[i]; + // convert bg to head/tail format + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + ii[tails_i] = 0; + tails[tails_i] = heads_i; + const int decoded = decoding[tails_i]; + const int start_i = bg->tails[decoded]; + const int end_i = (decoded + 1 != num_vs) ? bg->tails[decoded + 1] : bg->num_es; + for (int i = start_i; i < end_i; ++i) { + if (decoded == bg->heads[i]) + ii[tails_i] += bg->vals[i]; + else { + heads[heads_i] = encoding[bg->heads[i]]; + vals[heads_i] = bg->vals[i]; + ++heads_i; + } + } + } +} + +void prpack_preprocessed_schur_graph::initialize_unweighted(const prpack_base_graph* bg) { + // permute num_outlinks + ii = num_outlinks; + num_outlinks = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + num_outlinks[encoding[i]] = (ii[i] == 0) ? -1 : ii[i]; + // convert bg to head/tail format + for (int tails_i = 0, heads_i = 0; tails_i < num_vs; ++tails_i) { + ii[tails_i] = 0; + tails[tails_i] = heads_i; + const int decoded = decoding[tails_i]; + const int start_i = bg->tails[decoded]; + const int end_i = (decoded + 1 != num_vs) ? bg->tails[decoded + 1] : bg->num_es; + for (int i = start_i; i < end_i; ++i) { + if (decoded == bg->heads[i]) + ++ii[tails_i]; + else + heads[heads_i++] = encoding[bg->heads[i]]; + } + if (ii[tails_i] > 0) + ii[tails_i] /= num_outlinks[tails_i]; + } +} + +prpack_preprocessed_schur_graph::prpack_preprocessed_schur_graph(const prpack_base_graph* bg) { + initialize(); + // initialize instance variables + num_vs = bg->num_vs; + num_es = bg->num_es - bg->num_self_es; + tails = new int[num_vs]; + heads = new int[num_es]; + const bool weighted = bg->vals != NULL; + if (weighted) { + vals = new double[num_vs]; + d = new double[num_vs]; + fill(d, d + num_vs, 1); + for (int i = 0; i < bg->num_es; ++i) + d[bg->heads[i]] -= bg->vals[i]; + } else { + num_outlinks = new double[num_vs]; + fill(num_outlinks, num_outlinks + num_vs, 0); + for (int i = 0; i < bg->num_es; ++i) + ++num_outlinks[bg->heads[i]]; + } + // permute no-inlink vertices to the beginning, and no-outlink vertices to the end + encoding = new int[num_vs]; + decoding = new int[num_vs]; + num_no_in_vs = num_no_out_vs = 0; + for (int i = 0; i < num_vs; ++i) { + if (bg->tails[i] == ((i + 1 != num_vs) ? bg->tails[i + 1] : bg->num_es)) { + decoding[encoding[i] = num_no_in_vs] = i; + ++num_no_in_vs; + } else if ((weighted) ? (d[i] == 1) : (num_outlinks[i] == 0)) { + decoding[encoding[i] = num_vs - 1 - num_no_out_vs] = i; + ++num_no_out_vs; + } + } + // permute everything else + for (int i = 0, p = num_no_in_vs; i < num_vs; ++i) + if (bg->tails[i] < ((i + 1 != num_vs) ? bg->tails[i + 1] : bg->num_es) && ((weighted) ? (d[i] < 1) : (num_outlinks[i] > 0))) + decoding[encoding[i] = p++] = i; + // continue initialization based off of weightedness + if (weighted) + initialize_weighted(bg); + else + initialize_unweighted(bg); +} + +prpack_preprocessed_schur_graph::~prpack_preprocessed_schur_graph() { + delete[] heads; + delete[] tails; + delete[] vals; + delete[] ii; + delete[] d; + delete[] num_outlinks; + delete[] encoding; + delete[] decoding; +} + diff --git a/src/prpack/prpack_preprocessed_schur_graph.h b/src/prpack/prpack_preprocessed_schur_graph.h new file mode 100644 index 0000000..a8c7845 --- /dev/null +++ b/src/prpack/prpack_preprocessed_schur_graph.h @@ -0,0 +1,33 @@ +#ifndef PRPACK_PREPROCESSED_SCHUR_GRAPH +#define PRPACK_PREPROCESSED_SCHUR_GRAPH +#include "prpack_preprocessed_graph.h" +#include "prpack_base_graph.h" + +namespace prpack { + + class prpack_preprocessed_schur_graph : public prpack_preprocessed_graph { + private: + // helper methods + void initialize(); + void initialize_weighted(const prpack_base_graph* bg); + void initialize_unweighted(const prpack_base_graph* bg); + public: + // instance variables + int num_no_in_vs; + int num_no_out_vs; + int* heads; + int* tails; + double* vals; + double* ii; + double* num_outlinks; + int* encoding; + int* decoding; + // constructors + prpack_preprocessed_schur_graph(const prpack_base_graph* bg); + // destructor + ~prpack_preprocessed_schur_graph(); + }; + +}; + +#endif diff --git a/src/prpack/prpack_result.cpp b/src/prpack/prpack_result.cpp new file mode 100644 index 0000000..05b6b5f --- /dev/null +++ b/src/prpack/prpack_result.cpp @@ -0,0 +1,12 @@ +#include "prpack_result.h" +#include +using namespace prpack; + +prpack_result::prpack_result() { + x = NULL; +} + +prpack_result::~prpack_result() { + delete[] x; +} + diff --git a/src/prpack/prpack_result.h b/src/prpack/prpack_result.h new file mode 100644 index 0000000..0b7fa80 --- /dev/null +++ b/src/prpack/prpack_result.h @@ -0,0 +1,27 @@ +#ifndef PRPACK_RESULT +#define PRPACK_RESULT + +namespace prpack { + + // Result class. + class prpack_result { + public: + // instance variables + int num_vs; + int num_es; + double* x; + double read_time; + double preprocess_time; + double compute_time; + long num_es_touched; + const char* method; + int converged; + // constructor + prpack_result(); + // destructor + ~prpack_result(); + }; + +}; + +#endif diff --git a/src/prpack/prpack_solver.cpp b/src/prpack/prpack_solver.cpp new file mode 100644 index 0000000..de82769 --- /dev/null +++ b/src/prpack/prpack_solver.cpp @@ -0,0 +1,878 @@ +#include "prpack_solver.h" +#include "prpack_utils.h" +#include +#include +#include +#include +using namespace prpack; +using namespace std; + +void prpack_solver::initialize() { + geg = NULL; + gsg = NULL; + sg = NULL; + sccg = NULL; + owns_bg = true; +} + +prpack_solver::prpack_solver(const prpack_csc* g) { + initialize(); + TIME(read_time, bg = new prpack_base_graph(g)); +} + +prpack_solver::prpack_solver(const prpack_int64_csc* g) { + initialize(); + TIME(read_time, bg = new prpack_base_graph(g)); +} + +prpack_solver::prpack_solver(const prpack_csr* g) { + initialize(); + TIME(read_time, bg = new prpack_base_graph(g)); +} + +prpack_solver::prpack_solver(const prpack_edge_list* g) { + initialize(); + TIME(read_time, bg = new prpack_base_graph(g)); +} + +prpack_solver::prpack_solver(prpack_base_graph* g, bool owns_bg) { + initialize(); + this->owns_bg = owns_bg; + TIME(read_time, bg = g); +} + +prpack_solver::prpack_solver(const char* filename, const char* format, const bool weighted) { + initialize(); + TIME(read_time, bg = new prpack_base_graph(filename, format, weighted)); +} + +prpack_solver::~prpack_solver() { + if (owns_bg) { + delete bg; + } + delete geg; + delete gsg; + delete sg; + delete sccg; +} + +int prpack_solver::get_num_vs() { + return bg->num_vs; +} + +prpack_result* prpack_solver::solve(const double alpha, const double tol, const char* method) { + return solve(alpha, tol, NULL, NULL, method); +} + +prpack_result* prpack_solver::solve( + const double alpha, + const double tol, + const double* u, + const double* v, + const char* method) { + double preprocess_time = 0; + double compute_time = 0; + prpack_result* ret = NULL; + // decide which method to run + string m; + if (strcmp(method, "") != 0) + m = string(method); + else { + if (bg->num_vs < 128) + m = "ge"; + else if (sccg != NULL) + m = "sccgs"; + else if (sg != NULL) + m = "sg"; + else + m = "sccgs"; + if (u != v) + m += "_uv"; + } + // run the appropriate method + if (m == "ge") { + if (geg == NULL) { + TIME(preprocess_time, geg = new prpack_preprocessed_ge_graph(bg)); + } + TIME(compute_time, ret = solve_via_ge( + alpha, + tol, + geg->num_vs, + geg->matrix, + u)); + } else if (m == "ge_uv") { + if (geg == NULL) { + TIME(preprocess_time, geg = new prpack_preprocessed_ge_graph(bg)); + } + TIME(compute_time, ret = solve_via_ge_uv( + alpha, + tol, + geg->num_vs, + geg->matrix, + geg->d, + u, + v)); + } else if (m == "gs") { + if (gsg == NULL) { + TIME(preprocess_time, gsg = new prpack_preprocessed_gs_graph(bg)); + } + TIME(compute_time, ret = solve_via_gs( + alpha, + tol, + gsg->num_vs, + gsg->num_es, + gsg->heads, + gsg->tails, + gsg->vals, + gsg->ii, + gsg->d, + gsg->num_outlinks, + u, + v)); + } else if (m == "gserr") { + if (gsg == NULL) { + TIME(preprocess_time, gsg = new prpack_preprocessed_gs_graph(bg)); + } + TIME(compute_time, ret = solve_via_gs_err( + alpha, + tol, + gsg->num_vs, + gsg->num_es, + gsg->heads, + gsg->tails, + gsg->ii, + gsg->num_outlinks, + u, + v)); + } else if (m == "sgs") { + if (sg == NULL) { + TIME(preprocess_time, sg = new prpack_preprocessed_schur_graph(bg)); + } + TIME(compute_time, ret = solve_via_schur_gs( + alpha, + tol, + sg->num_vs, + sg->num_no_in_vs, + sg->num_no_out_vs, + sg->num_es, + sg->heads, + sg->tails, + sg->vals, + sg->ii, + sg->d, + sg->num_outlinks, + u, + sg->encoding, + sg->decoding)); + } else if (m == "sgs_uv") { + if (sg == NULL) { + TIME(preprocess_time, sg = new prpack_preprocessed_schur_graph(bg)); + } + TIME(compute_time, ret = solve_via_schur_gs_uv( + alpha, + tol, + sg->num_vs, + sg->num_no_in_vs, + sg->num_no_out_vs, + sg->num_es, + sg->heads, + sg->tails, + sg->vals, + sg->ii, + sg->d, + sg->num_outlinks, + u, + v, + sg->encoding, + sg->decoding)); + } else if (m == "sccgs") { + if (sccg == NULL) { + TIME(preprocess_time, sccg = new prpack_preprocessed_scc_graph(bg)); + } + TIME(compute_time, ret = solve_via_scc_gs( + alpha, + tol, + sccg->num_vs, + sccg->num_es_inside, + sccg->heads_inside, + sccg->tails_inside, + sccg->vals_inside, + sccg->num_es_outside, + sccg->heads_outside, + sccg->tails_outside, + sccg->vals_outside, + sccg->ii, + sccg->d, + sccg->num_outlinks, + u, + sccg->num_comps, + sccg->divisions, + sccg->encoding, + sccg->decoding)); + } else if (m == "sccgs_uv") { + if (sccg == NULL) { + TIME(preprocess_time, sccg = new prpack_preprocessed_scc_graph(bg)); + } + TIME(compute_time, ret = solve_via_scc_gs_uv( + alpha, + tol, + sccg->num_vs, + sccg->num_es_inside, + sccg->heads_inside, + sccg->tails_inside, + sccg->vals_inside, + sccg->num_es_outside, + sccg->heads_outside, + sccg->tails_outside, + sccg->vals_outside, + sccg->ii, + sccg->d, + sccg->num_outlinks, + u, + v, + sccg->num_comps, + sccg->divisions, + sccg->encoding, + sccg->decoding)); + } else { + // TODO: throw exception + } + ret->method = m.c_str(); + ret->read_time = read_time; + ret->preprocess_time = preprocess_time; + ret->compute_time = compute_time; + ret->num_vs = bg->num_vs; + ret->num_es = bg->num_es; + return ret; +} + +// VARIOUS SOLVING METHODS //////////////////////////////////////////////////////////////////////// + +prpack_result* prpack_solver::solve_via_ge( + const double alpha, + const double tol, + const int num_vs, + const double* matrix, + const double* uv) { + prpack_result* ret = new prpack_result(); + // initialize uv values + const double uv_const = 1.0/num_vs; + const int uv_exists = (uv) ? 1 : 0; + uv = (uv) ? uv : &uv_const; + // create matrix A + double* A = new double[num_vs*num_vs]; + for (int i = 0; i < num_vs*num_vs; ++i) + A[i] = -alpha*matrix[i]; + for (int i = 0; i < num_vs*num_vs; i += num_vs + 1) + ++A[i]; + // create vector b + double* b = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + b[i] = uv[uv_exists*i]; + // solve and normalize + ge(num_vs, A, b); + normalize(num_vs, b); + // clean up and return + delete[] A; + ret->num_es_touched = -1; + ret->x = b; + return ret; +} + +prpack_result* prpack_solver::solve_via_ge_uv( + const double alpha, + const double tol, + const int num_vs, + const double* matrix, + const double* d, + const double* u, + const double* v) { + prpack_result* ret = new prpack_result(); + // initialize u and v values + const double u_const = 1.0/num_vs; + const double v_const = 1.0/num_vs; + const int u_exists = (u) ? 1 : 0; + const int v_exists = (v) ? 1 : 0; + u = (u) ? u : &u_const; + v = (v) ? v : &v_const; + // create matrix A + double* A = new double[num_vs*num_vs]; + for (int i = 0; i < num_vs*num_vs; ++i) + A[i] = -alpha*matrix[i]; + for (int i = 0, inum_vs = 0; i < num_vs; ++i, inum_vs += num_vs) + for (int j = 0; j < num_vs; ++j) + A[inum_vs + j] -= alpha*u[u_exists*i]*d[j]; + for (int i = 0; i < num_vs*num_vs; i += num_vs + 1) + ++A[i]; + // create vector b + double* b = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + b[i] = (1 - alpha)*v[v_exists*i]; + // solve + ge(num_vs, A, b); + // clean up and return + delete[] A; + ret->num_es_touched = -1; + ret->x = b; + return ret; +} + +// Vanilla Gauss-Seidel. +prpack_result* prpack_solver::solve_via_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v) { + prpack_result* ret = new prpack_result(); + const bool weighted = vals != NULL; + // initialize u and v values + const double u_const = 1.0/num_vs; + const double v_const = 1.0/num_vs; + const int u_exists = (u) ? 1 : 0; + const int v_exists = (v) ? 1 : 0; + u = (u) ? u : &u_const; + v = (v) ? v : &v_const; + // initialize the eigenvector (and use personalization vector) + double* x = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + x[i] = 0; + // initialize delta + double delta = 0; + // run Gauss-Seidel + ret->num_es_touched = 0; + double err = 1, c = 0; + do { + if (weighted) { + for (int i = 0; i < num_vs; ++i) { + double new_val = 0; + const int start_j = tails[i]; + const int end_j = (i + 1 != num_vs) ? tails[i + 1] : num_es; + for (int j = start_j; j < end_j; ++j) + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads[j]]*vals[j]; + new_val = alpha*new_val + (1 - alpha)*v[v_exists*i]; + delta -= alpha*x[i]*d[i]; + new_val += delta*u[u_exists*i]; + new_val /= 1 - alpha*(d[i]*u[u_exists*i] + (1 - d[i])*ii[i]); + delta += alpha*new_val*d[i]; + COMPENSATED_SUM(err, x[i] - new_val, c); + x[i] = new_val; + } + } else { + for (int i = 0; i < num_vs; ++i) { + const double old_val = x[i]*num_outlinks[i]; + double new_val = 0; + const int start_j = tails[i]; + const int end_j = (i + 1 != num_vs) ? tails[i + 1] : num_es; + for (int j = start_j; j < end_j; ++j) + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads[j]]; + new_val = alpha*new_val + (1 - alpha)*v[v_exists*i]; + if (num_outlinks[i] < 0) { + delta -= alpha*old_val; + new_val += delta*u[u_exists*i]; + new_val /= 1 - alpha*u[u_exists*i]; + delta += alpha*new_val; + } else { + new_val += delta*u[u_exists*i]; + new_val /= 1 - alpha*ii[i]; + } + COMPENSATED_SUM(err, old_val - new_val, c); + x[i] = new_val/num_outlinks[i]; + } + } + // update iteration index + ret->num_es_touched += num_es; + } while (err >= tol); + // undo num_outlinks transformation + if (!weighted) + for (int i = 0; i < num_vs; ++i) + x[i] *= num_outlinks[i]; + // return results + ret->x = x; + return ret; +} + +// Implement a gauss-seidel-like process with a strict error bound +// we return a solution with 1-norm error less than tol. +prpack_result* prpack_solver::solve_via_gs_err( + const double alpha, + const double tol, + const int num_vs, + const int num_es, + const int* heads, + const int* tails, + const double* ii, + const double* num_outlinks, + const double* u, + const double* v) { + prpack_result* ret = new prpack_result(); + // initialize u and v values + const double u_const = 1.0/num_vs; + const double v_const = 1.0/num_vs; + const int u_exists = (u) ? 1 : 0; + const int v_exists = (v) ? 1 : 0; + u = (u) ? u : &u_const; + v = (v) ? v : &v_const; + // Note to Dave, we can't rescale v because we could be running this + // same routine from multiple threads. + // initialize the eigenvector (and use personalization vector) + double* x = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) { + x[i] = 0.; + } + // initialize delta + double delta = 0.; + // run Gauss-Seidel, note that we store x/deg[i] throughout this + // iteration. + int64_t maxedges = (int64_t)((double)num_es*std::min( + log(tol)/log(alpha), + (double)PRPACK_SOLVER_MAX_ITERS)); + ret->num_es_touched = 0; + double err=1., c = 0.; + do { + // iterate through vertices + for (int i = 0; i < num_vs; ++i) { + double old_val = x[i]*num_outlinks[i]; // adjust back to the "true" value. + double new_val = 0.; + int start_j = tails[i], end_j = (i + 1 != num_vs) ? tails[i + 1] : num_es; + for (int j = start_j; j < end_j; ++j) { + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads[j]]; + } + new_val = alpha*new_val + alpha*ii[i]*old_val + (1.0-alpha)*v[v_exists*i]; + new_val += delta*u[u_exists*i]; // add the dangling node adjustment + if (num_outlinks[i] < 0) { + delta += alpha*(new_val - old_val); + } + // note that new_val > old_val, but the fabs is just for + COMPENSATED_SUM(err, -(new_val - old_val), c); + x[i] = new_val/num_outlinks[i]; + } + // update iteration index + ret->num_es_touched += num_es; + } while (err >= tol && ret->num_es_touched < maxedges); + if (err >= tol) { + ret->converged = 0; + } else { + ret->converged = 1; + } + // undo num_outlinks transformation + for (int i = 0; i < num_vs; ++i) + x[i] *= num_outlinks[i]; + // return results + ret->x = x; + return ret; +} + +// Gauss-Seidel using the Schur complement to separate dangling nodes. +prpack_result* prpack_solver::solve_via_schur_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_no_in_vs, + const int num_no_out_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* uv, + const int* encoding, + const int* decoding, + const bool should_normalize) { + prpack_result* ret = new prpack_result(); + const bool weighted = vals != NULL; + // initialize uv values + const double uv_const = 1.0/num_vs; + const int uv_exists = (uv) ? 1 : 0; + uv = (uv) ? prpack_utils::permute(num_vs, uv, encoding) : &uv_const; + // initialize the eigenvector (and use personalization vector) + double* x = new double[num_vs]; + for (int i = 0; i < num_vs - num_no_out_vs; ++i) + x[i] = uv[uv_exists*i]/(1 - alpha*ii[i])/((weighted) ? 1 : num_outlinks[i]); + // run Gauss-Seidel for the top left part of (I - alpha*P)*x = uv + ret->num_es_touched = 0; + double err, c; + do { + // iterate through vertices + int num_es_touched = 0; + err = c = 0; + #pragma omp parallel for firstprivate(c) reduction(+:err, num_es_touched) schedule(dynamic, 64) + for (int i = num_no_in_vs; i < num_vs - num_no_out_vs; ++i) { + double new_val = 0; + const int start_j = tails[i]; + const int end_j = (i + 1 != num_vs) ? tails[i + 1] : num_es; + if (weighted) { + for (int j = start_j; j < end_j; ++j) + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads[j]]*vals[j]; + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]), c); + new_val = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i]); + x[i] = new_val; + } else { + for (int j = start_j; j < end_j; ++j) + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads[j]]; + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]*num_outlinks[i]), c); + new_val = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i]); + x[i] = new_val/num_outlinks[i]; + } + num_es_touched += end_j - start_j; + } + // update iteration index + ret->num_es_touched += num_es_touched; + } while (err/(1 - alpha) >= tol); + // solve for the dangling nodes + int num_es_touched = 0; + #pragma omp parallel for reduction(+:num_es_touched) schedule(dynamic, 64) + for (int i = num_vs - num_no_out_vs; i < num_vs; ++i) { + x[i] = 0; + const int start_j = tails[i]; + const int end_j = (i + 1 != num_vs) ? tails[i + 1] : num_es; + for (int j = start_j; j < end_j; ++j) + x[i] += x[heads[j]]*((weighted) ? vals[j] : 1); + x[i] = (alpha*x[i] + uv[uv_exists*i])/(1 - alpha*ii[i]); + num_es_touched += end_j - start_j; + } + ret->num_es_touched += num_es_touched; + // undo num_outlinks transformation + if (!weighted) + for (int i = 0; i < num_vs - num_no_out_vs; ++i) + x[i] *= num_outlinks[i]; + // normalize x to get the solution for: (I - alpha*P - alpha*u*d')*x = (1 - alpha)*v + if (should_normalize) + normalize(num_vs, x); + // return results + ret->x = prpack_utils::permute(num_vs, x, decoding); + delete[] x; + if (uv_exists) + delete[] uv; + return ret; +} + +prpack_result* prpack_solver::solve_via_schur_gs_uv( + const double alpha, + const double tol, + const int num_vs, + const int num_no_in_vs, + const int num_no_out_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v, + const int* encoding, + const int* decoding) { + // solve uv = u + prpack_result* ret_u = solve_via_schur_gs( + alpha, + tol, + num_vs, + num_no_in_vs, + num_no_out_vs, + num_es, + heads, + tails, + vals, + ii, + d, + num_outlinks, + u, + encoding, + decoding, + false); + // solve uv = v + prpack_result* ret_v = solve_via_schur_gs( + alpha, + tol, + num_vs, + num_no_in_vs, + num_no_out_vs, + num_es, + heads, + tails, + vals, + ii, + d, + num_outlinks, + v, + encoding, + decoding, + false); + // combine the u and v cases + return combine_uv(num_vs, d, num_outlinks, encoding, alpha, ret_u, ret_v); +} + +/** Gauss-Seidel using strongly connected components. + * Notes: + * If not weighted, then we store x[i] = "x[i]/outdegree" to + * avoid additional arithmetic. We don't do this for the weighted + * case because the adjustment may not be constant. + */ +prpack_result* prpack_solver::solve_via_scc_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_es_inside, + const int* heads_inside, + const int* tails_inside, + const double* vals_inside, + const int num_es_outside, + const int* heads_outside, + const int* tails_outside, + const double* vals_outside, + const double* ii, + const double* d, + const double* num_outlinks, + const double* uv, + const int num_comps, + const int* divisions, + const int* encoding, + const int* decoding, + const bool should_normalize) { + prpack_result* ret = new prpack_result(); + const bool weighted = vals_inside != NULL; + // initialize uv values + const double uv_const = 1.0/num_vs; + const int uv_exists = (uv) ? 1 : 0; + uv = (uv) ? prpack_utils::permute(num_vs, uv, encoding) : &uv_const; + // CHECK initialize the solution with one iteration of GS from x=0. + double* x = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + x[i] = uv[uv_exists*i]/(1 - alpha*ii[i])/((weighted) ? 1 : num_outlinks[i]); + // create x_outside + double* x_outside = new double[num_vs]; + // run Gauss-Seidel for (I - alpha*P)*x = uv + ret->num_es_touched = 0; + for (int comp_i = 0; comp_i < num_comps; ++comp_i) { + const int start_comp = divisions[comp_i]; + const int end_comp = (comp_i + 1 != num_comps) ? divisions[comp_i + 1] : num_vs; + const bool parallelize = end_comp - start_comp > 512; + // initialize relevant x_outside values + for (int i = start_comp; i < end_comp; ++i) { + x_outside[i] = 0; + const int start_j = tails_outside[i]; + const int end_j = (i + 1 != num_vs) ? tails_outside[i + 1] : num_es_outside; + for (int j = start_j; j < end_j; ++j) + x_outside[i] += x[heads_outside[j]]*((weighted) ? vals_outside[j] : 1.); + ret->num_es_touched += end_j - start_j; + } + double err, c; + do { + int num_es_touched = 0; + err = c = 0; + if (parallelize) { + // iterate through vertices + #pragma omp parallel for firstprivate(c) reduction(+:err, num_es_touched) schedule(dynamic, 64) + for (int i = start_comp; i < end_comp; ++i) { + double new_val = x_outside[i]; + const int start_j = tails_inside[i]; + const int end_j = (i + 1 != num_vs) ? tails_inside[i + 1] : num_es_inside; + if (weighted) { + for (int j = start_j; j < end_j; ++j) { + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads_inside[j]]*vals_inside[j]; + } + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]), c); + x[i] = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i]); + } else { + for (int j = start_j; j < end_j; ++j) { + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads_inside[j]]; + } + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]*num_outlinks[i]), c); + x[i] = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i])/num_outlinks[i]; + } + num_es_touched += end_j - start_j; + } + } else { + for (int i = start_comp; i < end_comp; ++i) { + double new_val = x_outside[i]; + const int start_j = tails_inside[i]; + const int end_j = (i + 1 != num_vs) ? tails_inside[i + 1] : num_es_inside; + if (weighted) { + for (int j = start_j; j < end_j; ++j) { + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads_inside[j]]*vals_inside[j]; + } + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]), c); + x[i] = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i]); + } else { + for (int j = start_j; j < end_j; ++j) { + // TODO: might want to use compensation summation for large: end_j - start_j + new_val += x[heads_inside[j]]; + } + COMPENSATED_SUM(err, fabs(uv[uv_exists*i] + alpha*new_val - (1 - alpha*ii[i])*x[i]*num_outlinks[i]), c); + x[i] = (alpha*new_val + uv[uv_exists*i])/(1 - alpha*ii[i])/num_outlinks[i]; + } + num_es_touched += end_j - start_j; + } + } + // update iteration index + ret->num_es_touched += num_es_touched; + } while (err/(1 - alpha) >= tol*(end_comp - start_comp)/num_vs); + } + // undo num_outlinks transformation + if (!weighted) + for (int i = 0; i < num_vs; ++i) + x[i] *= num_outlinks[i]; + // normalize x to get the solution for: (I - alpha*P - alpha*u*d')*x = (1 - alpha)*v + if (should_normalize) + normalize(num_vs, x); + // return results + ret->x = prpack_utils::permute(num_vs, x, decoding); + delete[] x; + delete[] x_outside; + if (uv_exists) + delete[] uv; + return ret; +} + +prpack_result* prpack_solver::solve_via_scc_gs_uv( + const double alpha, + const double tol, + const int num_vs, + const int num_es_inside, + const int* heads_inside, + const int* tails_inside, + const double* vals_inside, + const int num_es_outside, + const int* heads_outside, + const int* tails_outside, + const double* vals_outside, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v, + const int num_comps, + const int* divisions, + const int* encoding, + const int* decoding) { + // solve uv = u + prpack_result* ret_u = solve_via_scc_gs( + alpha, + tol, + num_vs, + num_es_inside, + heads_inside, + tails_inside, + vals_inside, + num_es_outside, + heads_outside, + tails_outside, + vals_outside, + ii, + d, + num_outlinks, + u, + num_comps, + divisions, + encoding, + decoding, + false); + // solve uv = v + prpack_result* ret_v = solve_via_scc_gs( + alpha, + tol, + num_vs, + num_es_inside, + heads_inside, + tails_inside, + vals_inside, + num_es_outside, + heads_outside, + tails_outside, + vals_outside, + ii, + d, + num_outlinks, + v, + num_comps, + divisions, + encoding, + decoding, + false); + // combine u and v + return combine_uv(num_vs, d, num_outlinks, encoding, alpha, ret_u, ret_v); +} + +// VARIOUS HELPER METHODS ///////////////////////////////////////////////////////////////////////// + +// Run Gaussian-Elimination (note: this changes A and returns the solution in b) +void prpack_solver::ge(const int sz, double* A, double* b) { + // put into triangular form + for (int i = 0, isz = 0; i < sz; ++i, isz += sz) + for (int k = 0, ksz = 0; k < i; ++k, ksz += sz) + if (A[isz + k] != 0) { + const double coeff = A[isz + k]/A[ksz + k]; + A[isz + k] = 0; + for (int j = k + 1; j < sz; ++j) + A[isz + j] -= coeff*A[ksz + j]; + b[i] -= coeff*b[k]; + } + // backwards substitution + for (int i = sz - 1, isz = (sz - 1)*sz; i >= 0; --i, isz -= sz) { + for (int j = i + 1; j < sz; ++j) + b[i] -= A[isz + j]*b[j]; + b[i] /= A[isz + i]; + } +} + +// Normalize a vector to sum to 1. +void prpack_solver::normalize(const int length, double* x) { + double norm = 0, c = 0; + for (int i = 0; i < length; ++i) { + COMPENSATED_SUM(norm, x[i], c); + } + norm = 1/norm; + for (int i = 0; i < length; ++i) + x[i] *= norm; +} + +// Combine u and v results. +prpack_result* prpack_solver::combine_uv( + const int num_vs, + const double* d, + const double* num_outlinks, + const int* encoding, + const double alpha, + const prpack_result* ret_u, + const prpack_result* ret_v) { + prpack_result* ret = new prpack_result(); + const bool weighted = d != NULL; + double delta_u = 0; + double delta_v = 0; + for (int i = 0; i < num_vs; ++i) { + if ((weighted) ? (d[encoding[i]] == 1) : (num_outlinks[encoding[i]] < 0)) { + delta_u += ret_u->x[i]; + delta_v += ret_v->x[i]; + } + } + const double s = ((1 - alpha)*alpha*delta_v)/(1 - alpha*delta_u); + const double t = 1 - alpha; + ret->x = new double[num_vs]; + for (int i = 0; i < num_vs; ++i) + ret->x[i] = s*ret_u->x[i] + t*ret_v->x[i]; + ret->num_es_touched = ret_u->num_es_touched + ret_v->num_es_touched; + // clean up and return + delete ret_u; + delete ret_v; + return ret; +} + diff --git a/src/prpack/prpack_solver.h b/src/prpack/prpack_solver.h new file mode 100644 index 0000000..00fda0f --- /dev/null +++ b/src/prpack/prpack_solver.h @@ -0,0 +1,178 @@ +#ifndef PRPACK_SOLVER +#define PRPACK_SOLVER +#include "prpack_base_graph.h" +#include "prpack_csc.h" +#include "prpack_csr.h" +#include "prpack_edge_list.h" +#include "prpack_preprocessed_ge_graph.h" +#include "prpack_preprocessed_gs_graph.h" +#include "prpack_preprocessed_scc_graph.h" +#include "prpack_preprocessed_schur_graph.h" +#include "prpack_result.h" + +// TODO Make this a user configurable variable +#define PRPACK_SOLVER_MAX_ITERS 1000000 + +namespace prpack { + + // Solver class. + class prpack_solver { + private: + // instance variables + double read_time; + prpack_base_graph* bg; + prpack_preprocessed_ge_graph* geg; + prpack_preprocessed_gs_graph* gsg; + prpack_preprocessed_schur_graph* sg; + prpack_preprocessed_scc_graph* sccg; + bool owns_bg; + // methods + void initialize(); + static prpack_result* solve_via_ge( + const double alpha, + const double tol, + const int num_vs, + const double* matrix, + const double* uv); + static prpack_result* solve_via_ge_uv( + const double alpha, + const double tol, + const int num_vs, + const double* matrix, + const double* d, + const double* u, + const double* v); + static prpack_result* solve_via_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v); + static prpack_result* solve_via_gs_err( + const double alpha, + const double tol, + const int num_vs, + const int num_es, + const int* heads, + const int* tails, + const double* ii, + const double* num_outlinks, + const double* u, + const double* v); + static prpack_result* solve_via_schur_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_no_in_vs, + const int num_no_out_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* uv, + const int* encoding, + const int* decoding, + const bool should_normalize = true); + static prpack_result* solve_via_schur_gs_uv( + const double alpha, + const double tol, + const int num_vs, + const int num_no_in_vs, + const int num_no_out_vs, + const int num_es, + const int* heads, + const int* tails, + const double* vals, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v, + const int* encoding, + const int* decoding); + static prpack_result* solve_via_scc_gs( + const double alpha, + const double tol, + const int num_vs, + const int num_es_inside, + const int* heads_inside, + const int* tails_inside, + const double* vals_inside, + const int num_es_outside, + const int* heads_outside, + const int* tails_outside, + const double* vals_outside, + const double* ii, + const double* d, + const double* num_outlinks, + const double* uv, + const int num_comps, + const int* divisions, + const int* encoding, + const int* decoding, + const bool should_normalize = true); + static prpack_result* solve_via_scc_gs_uv( + const double alpha, + const double tol, + const int num_vs, + const int num_es_inside, + const int* heads_inside, + const int* tails_inside, + const double* vals_inside, + const int num_es_outside, + const int* heads_outside, + const int* tails_outside, + const double* vals_outside, + const double* ii, + const double* d, + const double* num_outlinks, + const double* u, + const double* v, + const int num_comps, + const int* divisions, + const int* encoding, + const int* decoding); + static void ge(const int sz, double* A, double* b); + static void normalize(const int length, double* x); + static prpack_result* combine_uv( + const int num_vs, + const double* d, + const double* num_outlinks, + const int* encoding, + const double alpha, + const prpack_result* ret_u, + const prpack_result* ret_v); + public: + // constructors + prpack_solver(const prpack_csc* g); + prpack_solver(const prpack_int64_csc* g); + prpack_solver(const prpack_csr* g); + prpack_solver(const prpack_edge_list* g); + prpack_solver(prpack_base_graph* g, bool owns_bg=true); + prpack_solver(const char* filename, const char* format, const bool weighted); + // destructor + ~prpack_solver(); + // methods + int get_num_vs(); + prpack_result* solve(const double alpha, const double tol, const char* method); + prpack_result* solve( + const double alpha, + const double tol, + const double* u, + const double* v, + const char* method); + }; + +}; + +#endif diff --git a/src/prpack/prpack_utils.cpp b/src/prpack/prpack_utils.cpp new file mode 100644 index 0000000..a306d3f --- /dev/null +++ b/src/prpack/prpack_utils.cpp @@ -0,0 +1,60 @@ +/** + * @file prpack_utils.cpp + * An assortment of utility functions for reporting errors, checking time, + * and working with vectors. + */ + +#include +#include "prpack_utils.h" +#include +#include +#include +using namespace prpack; +using namespace std; + +#ifdef PRPACK_IGRAPH_SUPPORT +#include "igraph_error.h" +#endif + +#if defined(_WIN32) || defined(_WIN64) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include +#endif +double prpack_utils::get_time() { + LARGE_INTEGER t, freq; + QueryPerformanceCounter(&t); + QueryPerformanceFrequency(&freq); + return double(t.QuadPart)/double(freq.QuadPart); +} +#else +#include +#include +double prpack_utils::get_time() { + struct timeval t; + gettimeofday(&t, 0); + return (t.tv_sec*1.0 + t.tv_usec/1000000.0); +} +#endif + +// Fails and outputs 'msg' if 'condition' is false. +void prpack_utils::validate(const bool condition, const string& msg) { + if (!condition) { +#ifdef PRPACK_IGRAPH_SUPPORT + igraph_error("Internal error in PRPACK", __FILE__, __LINE__, + IGRAPH_EINTERNAL); +#else + cerr << msg << endl; + exit(-1); +#endif + } +} + +// Permute a vector. +double* prpack_utils::permute(const int length, const double* a, const int* coding) { + double* ret = new double[length]; + for (int i = 0; i < length; ++i) + ret[coding[i]] = a[i]; + return ret; +} + diff --git a/src/prpack/prpack_utils.h b/src/prpack/prpack_utils.h new file mode 100644 index 0000000..0148c1b --- /dev/null +++ b/src/prpack/prpack_utils.h @@ -0,0 +1,34 @@ +#ifndef PRPACK_UTILS +#define PRPACK_UTILS +#ifdef MATLAB_MEX_FILE +#include "mex.h" +#endif +#include + +// Computes the time taken to do X and stores it in T. +#define TIME(T, X) \ + (T) = prpack_utils::get_time(); \ + (X); \ + (T) = prpack_utils::get_time() - (T) + +// Computes S += A using C as a carry-over. +// This is a macro over a function as it is faster this way. +#define COMPENSATED_SUM(S, A, C) \ + double compensated_sum_y = (A) - (C); \ + double compensated_sum_t = (S) + compensated_sum_y; \ + (C) = compensated_sum_t - (S) - compensated_sum_y; \ + (S) = compensated_sum_t + +namespace prpack { + + class prpack_utils { + public: + static double get_time(); + static void validate(const bool condition, const std::string& msg); + static double* permute(const int length, const double* a, const int* coding); + }; + +}; + +#endif + diff --git a/src/pstdint.h b/src/pstdint.h new file mode 100644 index 0000000..bd5697f --- /dev/null +++ b/src/pstdint.h @@ -0,0 +1,817 @@ +/* A portable stdint.h + **************************************************************************** + * BSD License: + **************************************************************************** + * + * Copyright (c) 2005-2007 Paul Hsieh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************** + * + * Version 0.1.11 + * + * The ANSI C standard committee, for the C99 standard, specified the + * inclusion of a new standard include file called stdint.h. This is + * a very useful and long desired include file which contains several + * very precise definitions for integer scalar types that is + * critically important for making portable several classes of + * applications including cryptography, hashing, variable length + * integer libraries and so on. But for most developers its likely + * useful just for programming sanity. + * + * The problem is that most compiler vendors have decided not to + * implement the C99 standard, and the next C++ language standard + * (which has a lot more mindshare these days) will be a long time in + * coming and its unknown whether or not it will include stdint.h or + * how much adoption it will have. Either way, it will be a long time + * before all compilers come with a stdint.h and it also does nothing + * for the extremely large number of compilers available today which + * do not include this file, or anything comparable to it. + * + * So that's what this file is all about. Its an attempt to build a + * single universal include file that works on as many platforms as + * possible to deliver what stdint.h is supposed to. A few things + * that should be noted about this file: + * + * 1) It is not guaranteed to be portable and/or present an identical + * interface on all platforms. The extreme variability of the + * ANSI C standard makes this an impossibility right from the + * very get go. Its really only meant to be useful for the vast + * majority of platforms that possess the capability of + * implementing usefully and precisely defined, standard sized + * integer scalars. Systems which are not intrinsically 2s + * complement may produce invalid constants. + * + * 2) There is an unavoidable use of non-reserved symbols. + * + * 3) Other standard include files are invoked. + * + * 4) This file may come in conflict with future platforms that do + * include stdint.h. The hope is that one or the other can be + * used with no real difference. + * + * 5) In the current verison, if your platform can't represent + * int32_t, int16_t and int8_t, it just dumps out with a compiler + * error. + * + * 6) 64 bit integers may or may not be defined. Test for their + * presence with the test: #ifdef INT64_MAX or #ifdef UINT64_MAX. + * Note that this is different from the C99 specification which + * requires the existence of 64 bit support in the compiler. If + * this is not defined for your platform, yet it is capable of + * dealing with 64 bits then it is because this file has not yet + * been extended to cover all of your system's capabilities. + * + * 7) (u)intptr_t may or may not be defined. Test for its presence + * with the test: #ifdef PTRDIFF_MAX. If this is not defined + * for your platform, then it is because this file has not yet + * been extended to cover all of your system's capabilities, not + * because its optional. + * + * 8) The following might not been defined even if your platform is + * capable of defining it: + * + * WCHAR_MIN + * WCHAR_MAX + * (u)int64_t + * PTRDIFF_MIN + * PTRDIFF_MAX + * (u)intptr_t + * + * 9) The following have not been defined: + * + * WINT_MIN + * WINT_MAX + * + * 10) The criteria for defining (u)int_least(*)_t isn't clear, + * except for systems which don't have a type that precisely + * defined 8, 16, or 32 bit types (which this include file does + * not support anyways). Default definitions have been given. + * + * 11) The criteria for defining (u)int_fast(*)_t isn't something I + * would trust to any particular compiler vendor or the ANSI C + * committee. It is well known that "compatible systems" are + * commonly created that have very different performance + * characteristics from the systems they are compatible with, + * especially those whose vendors make both the compiler and the + * system. Default definitions have been given, but its strongly + * recommended that users never use these definitions for any + * reason (they do *NOT* deliver any serious guarantee of + * improved performance -- not in this file, nor any vendor's + * stdint.h). + * + * 12) The following macros: + * + * PRINTF_INTMAX_MODIFIER + * PRINTF_INT64_MODIFIER + * PRINTF_INT32_MODIFIER + * PRINTF_INT16_MODIFIER + * PRINTF_LEAST64_MODIFIER + * PRINTF_LEAST32_MODIFIER + * PRINTF_LEAST16_MODIFIER + * PRINTF_INTPTR_MODIFIER + * + * are strings which have been defined as the modifiers required + * for the "d", "u" and "x" printf formats to correctly output + * (u)intmax_t, (u)int64_t, (u)int32_t, (u)int16_t, (u)least64_t, + * (u)least32_t, (u)least16_t and (u)intptr_t types respectively. + * PRINTF_INTPTR_MODIFIER is not defined for some systems which + * provide their own stdint.h. PRINTF_INT64_MODIFIER is not + * defined if INT64_MAX is not defined. These are an extension + * beyond what C99 specifies must be in stdint.h. + * + * In addition, the following macros are defined: + * + * PRINTF_INTMAX_HEX_WIDTH + * PRINTF_INT64_HEX_WIDTH + * PRINTF_INT32_HEX_WIDTH + * PRINTF_INT16_HEX_WIDTH + * PRINTF_INT8_HEX_WIDTH + * PRINTF_INTMAX_DEC_WIDTH + * PRINTF_INT64_DEC_WIDTH + * PRINTF_INT32_DEC_WIDTH + * PRINTF_INT16_DEC_WIDTH + * PRINTF_INT8_DEC_WIDTH + * + * Which specifies the maximum number of characters required to + * print the number of that type in either hexadecimal or decimal. + * These are an extension beyond what C99 specifies must be in + * stdint.h. + * + * Compilers tested (all with 0 warnings at their highest respective + * settings): Borland Turbo C 2.0, WATCOM C/C++ 11.0 (16 bits and 32 + * bits), Microsoft Visual C++ 6.0 (32 bit), Microsoft Visual Studio + * .net (VC7), Intel C++ 4.0, GNU gcc v3.3.3 + * + * This file should be considered a work in progress. Suggestions for + * improvements, especially those which increase coverage are strongly + * encouraged. + * + * Acknowledgements + * + * The following people have made significant contributions to the + * development and testing of this file: + * + * Chris Howie + * John Steele Scott + * Dave Thorup + * + */ + +#include +#include +#include + +/* + * For gcc with _STDINT_H, fill in the PRINTF_INT*_MODIFIER macros, and + * do nothing else. On the Mac OS X version of gcc this is _STDINT_H_. + */ + +#if ((defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined (__WATCOMC__) && (defined (_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) )) && !defined (_PSTDINT_H_INCLUDED) + #include + #define _PSTDINT_H_INCLUDED + #ifndef PRINTF_INT64_MODIFIER + #define PRINTF_INT64_MODIFIER "ll" + #endif + #ifndef PRINTF_INT32_MODIFIER + #define PRINTF_INT32_MODIFIER "l" + #endif + #ifndef PRINTF_INT16_MODIFIER + #define PRINTF_INT16_MODIFIER "h" + #endif + #ifndef PRINTF_INTMAX_MODIFIER + #define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER + #endif + #ifndef PRINTF_INT64_HEX_WIDTH + #define PRINTF_INT64_HEX_WIDTH "16" + #endif + #ifndef PRINTF_INT32_HEX_WIDTH + #define PRINTF_INT32_HEX_WIDTH "8" + #endif + #ifndef PRINTF_INT16_HEX_WIDTH + #define PRINTF_INT16_HEX_WIDTH "4" + #endif + #ifndef PRINTF_INT8_HEX_WIDTH + #define PRINTF_INT8_HEX_WIDTH "2" + #endif + #ifndef PRINTF_INT64_DEC_WIDTH + #define PRINTF_INT64_DEC_WIDTH "20" + #endif + #ifndef PRINTF_INT32_DEC_WIDTH + #define PRINTF_INT32_DEC_WIDTH "10" + #endif + #ifndef PRINTF_INT16_DEC_WIDTH + #define PRINTF_INT16_DEC_WIDTH "5" + #endif + #ifndef PRINTF_INT8_DEC_WIDTH + #define PRINTF_INT8_DEC_WIDTH "3" + #endif + #ifndef PRINTF_INTMAX_HEX_WIDTH + #define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT64_HEX_WIDTH + #endif + #ifndef PRINTF_INTMAX_DEC_WIDTH + #define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT64_DEC_WIDTH + #endif + + /* + * Something really weird is going on with Open Watcom. Just pull some of + * these duplicated definitions from Open Watcom's stdint.h file for now. + */ + + #if defined (__WATCOMC__) && __WATCOMC__ >= 1250 + #if !defined (INT64_C) + #define INT64_C(x) (x + (INT64_MAX - INT64_MAX)) + #endif + #if !defined (UINT64_C) + #define UINT64_C(x) (x + (UINT64_MAX - UINT64_MAX)) + #endif + #if !defined (INT32_C) + #define INT32_C(x) (x + (INT32_MAX - INT32_MAX)) + #endif + #if !defined (UINT32_C) + #define UINT32_C(x) (x + (UINT32_MAX - UINT32_MAX)) + #endif + #if !defined (INT16_C) + #define INT16_C(x) (x) + #endif + #if !defined (UINT16_C) + #define UINT16_C(x) (x) + #endif + #if !defined (INT8_C) + #define INT8_C(x) (x) + #endif + #if !defined (UINT8_C) + #define UINT8_C(x) (x) + #endif + #if !defined (UINT64_MAX) + #define UINT64_MAX 18446744073709551615ULL + #endif + #if !defined (INT64_MAX) + #define INT64_MAX 9223372036854775807LL + #endif + #if !defined (UINT32_MAX) + #define UINT32_MAX 4294967295UL + #endif + #if !defined (INT32_MAX) + #define INT32_MAX 2147483647L + #endif + #if !defined (INTMAX_MAX) + #define INTMAX_MAX INT64_MAX + #endif + #if !defined (INTMAX_MIN) + #define INTMAX_MIN INT64_MIN + #endif + #endif +#endif + +#ifndef _PSTDINT_H_INCLUDED + #define _PSTDINT_H_INCLUDED + + #ifndef SIZE_MAX + #define SIZE_MAX (~(size_t)0) + #endif + + /* + * Deduce the type assignments from limits.h under the assumption that + * integer sizes in bits are powers of 2, and follow the ANSI + * definitions. + */ + + #ifndef UINT8_MAX + #define UINT8_MAX 0xff + #endif + #ifndef uint8_t + #if (UCHAR_MAX == UINT8_MAX) || defined (S_SPLINT_S) + typedef unsigned char uint8_t; + #define UINT8_C(v) ((uint8_t) v) + #else + # error "Platform not supported" + #endif + #endif + + #ifndef INT8_MAX + #define INT8_MAX 0x7f + #endif + #ifndef INT8_MIN + #define INT8_MIN INT8_C(0x80) + #endif + #ifndef int8_t + #if (SCHAR_MAX == INT8_MAX) || defined (S_SPLINT_S) + typedef signed char int8_t; + #define INT8_C(v) ((int8_t) v) + #else + # error "Platform not supported" + #endif + #endif + + #ifndef UINT16_MAX + #define UINT16_MAX 0xffff + #endif + #ifndef uint16_t + #if (UINT_MAX == UINT16_MAX) || defined (S_SPLINT_S) + typedef unsigned int uint16_t; + #ifndef PRINTF_INT16_MODIFIER + #define PRINTF_INT16_MODIFIER "" + #endif + #define UINT16_C(v) ((uint16_t) (v)) + #elif (USHRT_MAX == UINT16_MAX) + typedef unsigned short uint16_t; + #define UINT16_C(v) ((uint16_t) (v)) + #ifndef PRINTF_INT16_MODIFIER + #define PRINTF_INT16_MODIFIER "h" + #endif + #else + #error "Platform not supported" + #endif + #endif + + #ifndef INT16_MAX + #define INT16_MAX 0x7fff + #endif + #ifndef INT16_MIN + #define INT16_MIN INT16_C(0x8000) + #endif + #ifndef int16_t + #if (INT_MAX == INT16_MAX) || defined (S_SPLINT_S) + typedef signed int int16_t; + #define INT16_C(v) ((int16_t) (v)) + #ifndef PRINTF_INT16_MODIFIER + #define PRINTF_INT16_MODIFIER "" + #endif + #elif (SHRT_MAX == INT16_MAX) + typedef signed short int16_t; + #define INT16_C(v) ((int16_t) (v)) + #ifndef PRINTF_INT16_MODIFIER + #define PRINTF_INT16_MODIFIER "h" + #endif + #else + #error "Platform not supported" + #endif + #endif + + #ifndef UINT32_MAX + #define UINT32_MAX (0xffffffffUL) + #endif + #ifndef uint32_t + #if (ULONG_MAX == UINT32_MAX) || defined (S_SPLINT_S) + typedef unsigned long uint32_t; + #define UINT32_C(v) v ## UL + #ifndef PRINTF_INT32_MODIFIER + #define PRINTF_INT32_MODIFIER "l" + #endif + #elif (UINT_MAX == UINT32_MAX) + typedef unsigned int uint32_t; + #ifndef PRINTF_INT32_MODIFIER + #define PRINTF_INT32_MODIFIER "" + #endif + #define UINT32_C(v) v ## U + #elif (USHRT_MAX == UINT32_MAX) + typedef unsigned short uint32_t; + #define UINT32_C(v) ((unsigned short) (v)) + #ifndef PRINTF_INT32_MODIFIER + #define PRINTF_INT32_MODIFIER "" + #endif + #else + #error "Platform not supported" + #endif + #endif + + #ifndef INT32_MAX + #define INT32_MAX (0x7fffffffL) + #endif + #ifndef INT32_MIN + #define INT32_MIN INT32_C(0x80000000) + #endif + #ifndef int32_t + #if (LONG_MAX == INT32_MAX) || defined (S_SPLINT_S) + typedef signed long int32_t; + #define INT32_C(v) v ## L + #ifndef PRINTF_INT32_MODIFIER + #define PRINTF_INT32_MODIFIER "l" + #endif + #elif (INT_MAX == INT32_MAX) + typedef signed int int32_t; + #define INT32_C(v) v + #ifndef PRINTF_INT32_MODIFIER + #define PRINTF_INT32_MODIFIER "" + #endif + #elif (SHRT_MAX == INT32_MAX) + typedef signed short int32_t; + #define INT32_C(v) ((short) (v)) + #ifndef PRINTF_INT32_MODIFIER + #define PRINTF_INT32_MODIFIER "" + #endif + #else + #error "Platform not supported" + #endif + #endif + + /* + * The macro stdint_int64_defined is temporarily used to record + * whether or not 64 integer support is available. It must be + * defined for any 64 integer extensions for new platforms that are + * added. + */ + + #undef stdint_int64_defined + #if (defined(__STDC__) && defined(__STDC_VERSION__)) || defined (S_SPLINT_S) + #if (__STDC__ && __STDC_VERSION >= 199901L) || defined (S_SPLINT_S) + #define stdint_int64_defined + typedef long long int64_t; + typedef unsigned long long uint64_t; + #define UINT64_C(v) v ## ULL + #define INT64_C(v) v ## LL + #ifndef PRINTF_INT64_MODIFIER + #define PRINTF_INT64_MODIFIER "ll" + #endif + #endif + #endif + + #if !defined (stdint_int64_defined) + #if defined(__GNUC__) + #define stdint_int64_defined + __extension__ typedef long long int64_t; + __extension__ typedef unsigned long long uint64_t; + #define UINT64_C(v) v ## ULL + #define INT64_C(v) v ## LL + #ifndef PRINTF_INT64_MODIFIER + #define PRINTF_INT64_MODIFIER "ll" + #endif + #elif defined(__MWERKS__) || defined (__SUNPRO_C) || defined (__SUNPRO_CC) || defined (__APPLE_CC__) || defined (_LONG_LONG) || defined (_CRAYC) || defined (S_SPLINT_S) + #define stdint_int64_defined + typedef long long int64_t; + typedef unsigned long long uint64_t; + #define UINT64_C(v) v ## ULL + #define INT64_C(v) v ## LL + #ifndef PRINTF_INT64_MODIFIER + #define PRINTF_INT64_MODIFIER "ll" + #endif + #elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined (__BORLANDC__) && __BORLANDC__ > 0x460) || defined (__alpha) || defined (__DECC) + #define stdint_int64_defined + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; + #define UINT64_C(v) v ## UI64 + #define INT64_C(v) v ## I64 + #ifndef PRINTF_INT64_MODIFIER + #define PRINTF_INT64_MODIFIER "I64" + #endif + #endif + #endif + + #if !defined (LONG_LONG_MAX) && defined (INT64_C) + #define LONG_LONG_MAX INT64_C (9223372036854775807) + #endif + #ifndef ULONG_LONG_MAX + #define ULONG_LONG_MAX UINT64_C (18446744073709551615) + #endif + + #if !defined (INT64_MAX) && defined (INT64_C) + #define INT64_MAX INT64_C (9223372036854775807) + #endif + #if !defined (INT64_MIN) && defined (INT64_C) + #define INT64_MIN INT64_C (-9223372036854775808) + #endif + #if !defined (UINT64_MAX) && defined (INT64_C) + #define UINT64_MAX UINT64_C (18446744073709551615) + #endif + + /* + * Width of hexadecimal for number field. + */ + + #ifndef PRINTF_INT64_HEX_WIDTH + #define PRINTF_INT64_HEX_WIDTH "16" + #endif + #ifndef PRINTF_INT32_HEX_WIDTH + #define PRINTF_INT32_HEX_WIDTH "8" + #endif + #ifndef PRINTF_INT16_HEX_WIDTH + #define PRINTF_INT16_HEX_WIDTH "4" + #endif + #ifndef PRINTF_INT8_HEX_WIDTH + #define PRINTF_INT8_HEX_WIDTH "2" + #endif + + #ifndef PRINTF_INT64_DEC_WIDTH + #define PRINTF_INT64_DEC_WIDTH "20" + #endif + #ifndef PRINTF_INT32_DEC_WIDTH + #define PRINTF_INT32_DEC_WIDTH "10" + #endif + #ifndef PRINTF_INT16_DEC_WIDTH + #define PRINTF_INT16_DEC_WIDTH "5" + #endif + #ifndef PRINTF_INT8_DEC_WIDTH + #define PRINTF_INT8_DEC_WIDTH "3" + #endif + + /* + * Ok, lets not worry about 128 bit integers for now. Moore's law says + * we don't need to worry about that until about 2040 at which point + * we'll have bigger things to worry about. + */ + + #ifdef stdint_int64_defined + typedef int64_t intmax_t; + typedef uint64_t uintmax_t; + #define INTMAX_MAX INT64_MAX + #define INTMAX_MIN INT64_MIN + #define UINTMAX_MAX UINT64_MAX + #define UINTMAX_C(v) UINT64_C(v) + #define INTMAX_C(v) INT64_C(v) + #ifndef PRINTF_INTMAX_MODIFIER + #define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER + #endif + #ifndef PRINTF_INTMAX_HEX_WIDTH + #define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT64_HEX_WIDTH + #endif + #ifndef PRINTF_INTMAX_DEC_WIDTH + #define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT64_DEC_WIDTH + #endif + #else + typedef int32_t intmax_t; + typedef uint32_t uintmax_t; + #define INTMAX_MAX INT32_MAX + #define UINTMAX_MAX UINT32_MAX + #define UINTMAX_C(v) UINT32_C(v) + #define INTMAX_C(v) INT32_C(v) + #ifndef PRINTF_INTMAX_MODIFIER + #define PRINTF_INTMAX_MODIFIER PRINTF_INT32_MODIFIER + #endif + #ifndef PRINTF_INTMAX_HEX_WIDTH + #define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT32_HEX_WIDTH + #endif + #ifndef PRINTF_INTMAX_DEC_WIDTH + #define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT32_DEC_WIDTH + #endif + #endif + + /* + * Because this file currently only supports platforms which have + * precise powers of 2 as bit sizes for the default integers, the + * least definitions are all trivial. Its possible that a future + * version of this file could have different definitions. + */ + + #ifndef stdint_least_defined + typedef int8_t int_least8_t; + typedef uint8_t uint_least8_t; + typedef int16_t int_least16_t; + typedef uint16_t uint_least16_t; + typedef int32_t int_least32_t; + typedef uint32_t uint_least32_t; + #define PRINTF_LEAST32_MODIFIER PRINTF_INT32_MODIFIER + #define PRINTF_LEAST16_MODIFIER PRINTF_INT16_MODIFIER + #define UINT_LEAST8_MAX UINT8_MAX + #define INT_LEAST8_MAX INT8_MAX + #define UINT_LEAST16_MAX UINT16_MAX + #define INT_LEAST16_MAX INT16_MAX + #define UINT_LEAST32_MAX UINT32_MAX + #define INT_LEAST32_MAX INT32_MAX + #define INT_LEAST8_MIN INT8_MIN + #define INT_LEAST16_MIN INT16_MIN + #define INT_LEAST32_MIN INT32_MIN + #ifdef stdint_int64_defined + typedef int64_t int_least64_t; + typedef uint64_t uint_least64_t; + #define PRINTF_LEAST64_MODIFIER PRINTF_INT64_MODIFIER + #define UINT_LEAST64_MAX UINT64_MAX + #define INT_LEAST64_MAX INT64_MAX + #define INT_LEAST64_MIN INT64_MIN + #endif + #endif + #undef stdint_least_defined + + /* + * The ANSI C committee pretending to know or specify anything about + * performance is the epitome of misguided arrogance. The mandate of + * this file is to *ONLY* ever support that absolute minimum + * definition of the fast integer types, for compatibility purposes. + * No extensions, and no attempt to suggest what may or may not be a + * faster integer type will ever be made in this file. Developers are + * warned to stay away from these types when using this or any other + * stdint.h. + */ + + typedef int_least8_t int_fast8_t; + typedef uint_least8_t uint_fast8_t; + typedef int_least16_t int_fast16_t; + typedef uint_least16_t uint_fast16_t; + typedef int_least32_t int_fast32_t; + typedef uint_least32_t uint_fast32_t; + #define UINT_FAST8_MAX UINT_LEAST8_MAX + #define INT_FAST8_MAX INT_LEAST8_MAX + #define UINT_FAST16_MAX UINT_LEAST16_MAX + #define INT_FAST16_MAX INT_LEAST16_MAX + #define UINT_FAST32_MAX UINT_LEAST32_MAX + #define INT_FAST32_MAX INT_LEAST32_MAX + #define INT_FAST8_MIN INT_LEAST8_MIN + #define INT_FAST16_MIN INT_LEAST16_MIN + #define INT_FAST32_MIN INT_LEAST32_MIN + #ifdef stdint_int64_defined + typedef int_least64_t int_fast64_t; + typedef uint_least64_t uint_fast64_t; + #define UINT_FAST64_MAX UINT_LEAST64_MAX + #define INT_FAST64_MAX INT_LEAST64_MAX + #define INT_FAST64_MIN INT_LEAST64_MIN + #endif + + #undef stdint_int64_defined + + /* + * Whatever piecemeal, per compiler thing we can do about the wchar_t + * type limits. + */ + + #if defined(__WATCOMC__) || defined(_MSC_VER) || defined (__GNUC__) + #include + #ifndef WCHAR_MIN + #define WCHAR_MIN 0 + #endif + #ifndef WCHAR_MAX + #define WCHAR_MAX ((wchar_t)-1) + #endif + #endif + + /* + * Whatever piecemeal, per compiler/platform thing we can do about the + * (u)intptr_t types and limits. + */ + + #if defined (_MSC_VER) && defined (_UINTPTR_T_DEFINED) + #define STDINT_H_UINTPTR_T_DEFINED + #endif + + #ifndef STDINT_H_UINTPTR_T_DEFINED + #if defined (__alpha__) || defined (__ia64__) || defined (__x86_64__) || defined (_WIN64) + #define stdint_intptr_bits 64 + #elif defined (__WATCOMC__) || defined (__TURBOC__) + #if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__) + #define stdint_intptr_bits 16 + #else + #define stdint_intptr_bits 32 + #endif + #elif defined (__i386__) || defined (_WIN32) || defined (WIN32) + #define stdint_intptr_bits 32 + #elif defined (__INTEL_COMPILER) + /* TODO -- what will Intel do about x86-64? */ + #endif + + #ifdef stdint_intptr_bits + #define stdint_intptr_glue3_i(a,b,c) a##b##c + #define stdint_intptr_glue3(a,b,c) stdint_intptr_glue3_i(a,b,c) + #ifndef PRINTF_INTPTR_MODIFIER + #define PRINTF_INTPTR_MODIFIER stdint_intptr_glue3(PRINTF_INT,stdint_intptr_bits,_MODIFIER) + #endif + #ifndef PTRDIFF_MAX + #define PTRDIFF_MAX stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX) + #endif + #ifndef PTRDIFF_MIN + #define PTRDIFF_MIN stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN) + #endif + #ifndef UINTPTR_MAX + #define UINTPTR_MAX stdint_intptr_glue3(UINT,stdint_intptr_bits,_MAX) + #endif + #ifndef INTPTR_MAX + #define INTPTR_MAX stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX) + #endif + #ifndef INTPTR_MIN + #define INTPTR_MIN stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN) + #endif + #ifndef INTPTR_C + #define INTPTR_C(x) stdint_intptr_glue3(INT,stdint_intptr_bits,_C)(x) + #endif + #ifndef UINTPTR_C + #define UINTPTR_C(x) stdint_intptr_glue3(UINT,stdint_intptr_bits,_C)(x) + #endif + typedef stdint_intptr_glue3(uint, stdint_intptr_bits, _t) uintptr_t; + typedef stdint_intptr_glue3( int, stdint_intptr_bits, _t) intptr_t; + #else + /* TODO -- This following is likely wrong for some platforms, and does + nothing for the definition of uintptr_t. */ + typedef ptrdiff_t intptr_t; + #endif + #define STDINT_H_UINTPTR_T_DEFINED + #endif + + /* + * Assumes sig_atomic_t is signed and we have a 2s complement machine. + */ + + #ifndef SIG_ATOMIC_MAX + #define SIG_ATOMIC_MAX ((((sig_atomic_t) 1) << (sizeof (sig_atomic_t)*CHAR_BIT-1)) - 1) + #endif + +#endif + +#if defined (__TEST_PSTDINT_FOR_CORRECTNESS) + +/* + * Please compile with the maximum warning settings to make sure macros are not + * defined more than once. + */ + +#include +#include +#include + +#define glue3_aux(x,y,z) x ## y ## z +#define glue3(x,y,z) glue3_aux(x,y,z) + +#define DECLU(bits) glue3(uint,bits,_t) glue3(u,bits,=) glue3(UINT,bits,_C) (0); +#define DECLI(bits) glue3(int,bits,_t) glue3(i,bits,=) glue3(INT,bits,_C) (0); + +#define DECL(us,bits) glue3(DECL,us,) (bits) + +#define TESTUMAX(bits) glue3(u,bits,=) glue3(~,u,bits); if (glue3(UINT,bits,_MAX) glue3(!=,u,bits)) printf ("Something wrong with UINT%d_MAX\n", bits) + +int main () { + DECL(I, 8) + DECL(U, 8) + DECL(I, 16) + DECL(U, 16) + DECL(I, 32) + DECL(U, 32) +#ifdef INT64_MAX + DECL(I, 64) + DECL(U, 64) +#endif + intmax_t imax = INTMAX_C(0); + uintmax_t umax = UINTMAX_C(0); + char str0[256], str1[256]; + + sprintf (str0, "%d %x\n", 0, ~0); + + sprintf (str1, "%d %x\n", i8, ~0); + if (0 != strcmp (str0, str1)) { + printf ("Something wrong with i8 : %s\n", str1); + } + sprintf (str1, "%u %x\n", u8, ~0); + if (0 != strcmp (str0, str1)) { + printf ("Something wrong with u8 : %s\n", str1); + } + sprintf (str1, "%d %x\n", i16, ~0); + if (0 != strcmp (str0, str1)) { + printf ("Something wrong with i16 : %s\n", str1); + } + sprintf (str1, "%u %x\n", u16, ~0); + if (0 != strcmp (str0, str1)) { + printf ("Something wrong with u16 : %s\n", str1); + } + sprintf (str1, "%" PRINTF_INT32_MODIFIER "d %x\n", i32, ~0); + if (0 != strcmp (str0, str1)) { + printf ("Something wrong with i32 : %s\n", str1); + } + sprintf (str1, "%" PRINTF_INT32_MODIFIER "u %x\n", u32, ~0); + if (0 != strcmp (str0, str1)) { + printf ("Something wrong with u32 : %s\n", str1); + } +#ifdef INT64_MAX + sprintf (str1, "%" PRINTF_INT64_MODIFIER "d %x\n", i64, ~0); + if (0 != strcmp (str0, str1)) { + printf ("Something wrong with i64 : %s\n", str1); + } +#endif + sprintf (str1, "%" PRINTF_INTMAX_MODIFIER "d %x\n", imax, ~0); + if (0 != strcmp (str0, str1)) { + printf ("Something wrong with imax : %s\n", str1); + } + sprintf (str1, "%" PRINTF_INTMAX_MODIFIER "u %x\n", umax, ~0); + if (0 != strcmp (str0, str1)) { + printf ("Something wrong with umax : %s\n", str1); + } + + TESTUMAX(8); + TESTUMAX(16); + TESTUMAX(32); +#ifdef INT64_MAX + TESTUMAX(64); +#endif + + return EXIT_SUCCESS; +} + +#endif diff --git a/src/qsort.c b/src/qsort.c new file mode 100644 index 0000000..6069b3c --- /dev/null +++ b/src/qsort.c @@ -0,0 +1,209 @@ +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifdef _MSC_VER + /* MSVC does not have inline when compiling C source files */ + #define inline __inline + #define __unused +#endif + +#ifndef __unused + #define __unused __attribute__ ((unused)) +#endif + +#if defined(LIBC_SCCS) && !defined(lint) + static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; +#endif /* LIBC_SCCS and not lint */ +/*#include */ + +#include + +#ifdef I_AM_QSORT_R + typedef int cmp_t(void *, const void *, const void *); +#else + typedef int cmp_t(const void *, const void *); +#endif +static inline char *med3(char *, char *, char *, cmp_t *, void *); +static inline void swapfunc(char *, char *, int, int); + +#define igraph_min(a, b) (a) < (b) ? a : b + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + TYPE *pi = (TYPE *) (parmi); \ + TYPE *pj = (TYPE *) (parmj); \ + do { \ + TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ + } + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static inline void +swapfunc(a, b, n, swaptype) +char *a, *b; +int n, swaptype; +{ + if (swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) + } + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +#ifdef I_AM_QSORT_R + #define CMP(t, x, y) (cmp((t), (x), (y))) +#else + #define CMP(t, x, y) (cmp((x), (y))) +#endif + +static inline char * +med3(char *a, char *b, char *c, cmp_t *cmp, void *thunk +#ifndef I_AM_QSORT_R + __unused +#endif + ) { + return CMP(thunk, a, b) < 0 ? + (CMP(thunk, b, c) < 0 ? b : (CMP(thunk, a, c) < 0 ? c : a )) + : (CMP(thunk, b, c) > 0 ? b : (CMP(thunk, a, c) < 0 ? a : c )); +} + +#ifdef I_AM_QSORT_R + void + igraph_qsort_r(void *a, size_t n, size_t es, void *thunk, cmp_t *cmp) +#else + #define thunk NULL + void + igraph_qsort(void *a, size_t n, size_t es, cmp_t *cmp) +#endif +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype, swap_cnt; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; + pl > (char *)a && CMP(thunk, pl - es, pl) > 0; + pl -= es) { + swap(pl, pl - es); + } + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp, thunk); + pm = med3(pm - d, pm, pm + d, cmp, thunk); + pn = med3(pn - 2 * d, pn - d, pn, cmp, thunk); + } + pm = med3(pl, pm, pn, cmp, thunk); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (r = CMP(thunk, pb, a)) <= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = CMP(thunk, pc, a)) >= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) { + break; + } + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; + pl > (char *)a && CMP(thunk, pl - es, pl) > 0; + pl -= es) { + swap(pl, pl - es); + } + return; + } + + pn = (char *)a + n * es; + r = igraph_min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = igraph_min((size_t)(pd - pc), (size_t)(pn - pd - es)); + vecswap(pb, pn - r, r); + if ((size_t)(r = pb - pa) > es) +#ifdef I_AM_QSORT_R + igraph_qsort_r(a, r / es, es, thunk, cmp); +#else + igraph_qsort(a, r / es, es, cmp); +#endif + if ((size_t)(r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } + /* qsort(pn - r, r / es, es, cmp);*/ +} + diff --git a/src/qsort_r.c b/src/qsort_r.c new file mode 100644 index 0000000..f7c0e54 --- /dev/null +++ b/src/qsort_r.c @@ -0,0 +1,8 @@ +/* + * This file is in the public domain. Originally written by Garrett + * A. Wollman. + * + * $FreeBSD: src/lib/libc/stdlib/qsort_r.c,v 1.1 2002/09/10 02:04:49 wollman Exp $ + */ +#define I_AM_QSORT_R +#include "qsort.c" diff --git a/src/random.c b/src/random.c new file mode 100644 index 0000000..8e15415 --- /dev/null +++ b/src/random.c @@ -0,0 +1,2497 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_random.h" +#include "igraph_error.h" +#include "igraph_math.h" +#include "igraph_types.h" +#include "igraph_vector.h" +#include "igraph_memory.h" +#include "config.h" +#include +#include + +/** + * \section about_rngs + * + *
    + * About random numbers in igraph, use cases + * + * + * Some algorithms in igraph, e.g. the generation of random graphs, + * require random number generators (RNGs). Prior to version 0.6 + * igraph did not have a sophisticated way to deal with random number + * generators at the C level, but this has changed. From version 0.6 + * different and multiple random number generators are supported. + * + *
    + * + */ + +/** + * \section rng_use_cases + * + *
    Use cases + * + *
    Normal (default) use + * + * If the user does not use any of the RNG functions explicitly, but calls + * some of the randomized igraph functions, then a default RNG is set + * up the first time an igraph function needs random numbers. The + * seed of this RNG is the output of the time(0) function + * call, using the time function from the standard C + * library. This ensures that igraph creates a different random graph, + * each time the C program is called. + * + * + * + * The created default generator is stored internally and can be + * queried with the \ref igraph_rng_default() function. + * + *
    + * + *
    Reproducible simulations + * + * If reproducible results are needed, then the user should set the + * seed of the default random number generator explicitly, using the + * \ref igraph_rng_seed() function on the default generator, \ref + * igraph_rng_default(). When setting the seed to the same number, + * igraph generates exactly the same random graph (or series of random + * graphs). + * + *
    + * + *
    Changing the default generator + * + * By default igraph uses the \ref igraph_rng_default() random number + * generator. This can be changed any time by calling \ref + * igraph_rng_set_default(), with an already initialized random number + * generator. Note that the old (replaced) generator is not + * destroyed, so no memory is deallocated. + * + *
    + * + *
    Using multiple generators + * + * igraph also provides functions to set up multiple random number + * generators, using the \ref igraph_rng_init() function, and then + * generating random numbers from them, e.g. with \ref igraph_rng_get_integer() + * and/or \ref igraph_rng_get_unif() calls. + * + * + * + * Note that initializing a new random number generator is + * independent of the generator that the igraph functions themselves + * use. If you want to replace that, then please use \ref + * igraph_rng_set_default(). + * + *
    + * + *
    Example + * + * \example examples/simple/random_seed.c + * + *
    + * + *
    + */ + +/* ------------------------------------ */ + +typedef struct { + int i, j; + long int x[31]; +} igraph_i_rng_glibc2_state_t; + +static unsigned long int igraph_i_rng_glibc2_get(int *i, int *j, int n, long int *x) { + unsigned long int k; + + x[*i] += x[*j]; + k = (x[*i] >> 1) & 0x7FFFFFFF; + + (*i)++; + if (*i == n) { + *i = 0; + } + + (*j)++ ; + if (*j == n) { + *j = 0; + } + + return k; +} + +unsigned long int igraph_rng_glibc2_get(void *vstate) { + igraph_i_rng_glibc2_state_t *state = + (igraph_i_rng_glibc2_state_t*) vstate; + return igraph_i_rng_glibc2_get(&state->i, &state->j, 31, state->x); +} + +igraph_real_t igraph_rng_glibc2_get_real(void *state) { + return igraph_rng_glibc2_get(state) / 2147483648.0; +} + +/* this function is independent of the bit size */ + +static void igraph_i_rng_glibc2_init(long int *x, int n, + unsigned long int s) { + int i; + + if (s == 0) { + s = 1; + } + + x[0] = (long) s; + for (i = 1 ; i < n ; i++) { + const long int h = s / 127773; + const long int t = 16807 * ((long) s - h * 127773) - h * 2836; + if (t < 0) { + s = (unsigned long) t + 2147483647 ; + } else { + s = (unsigned long) t ; + } + + x[i] = (long int) s ; + } +} + +int igraph_rng_glibc2_seed(void *vstate, unsigned long int seed) { + igraph_i_rng_glibc2_state_t *state = + (igraph_i_rng_glibc2_state_t*) vstate; + int i; + + igraph_i_rng_glibc2_init(state->x, 31, seed); + + state->i = 3; + state->j = 0; + + for (i = 0; i < 10 * 31; i++) { + igraph_rng_glibc2_get(state); + } + + return 0; +} + +int igraph_rng_glibc2_init(void **state) { + igraph_i_rng_glibc2_state_t *st; + + st = igraph_Calloc(1, igraph_i_rng_glibc2_state_t); + if (!st) { + IGRAPH_ERROR("Cannot initialize RNG", IGRAPH_ENOMEM); + } + (*state) = st; + + igraph_rng_glibc2_seed(st, 0); + + return 0; +} + +void igraph_rng_glibc2_destroy(void *vstate) { + igraph_i_rng_glibc2_state_t *state = + (igraph_i_rng_glibc2_state_t*) vstate; + igraph_Free(state); +} + +/** + * \var igraph_rngtype_glibc2 + * \brief The random number generator type introduced in GNU libc 2 + * + * It is a linear feedback shift register generator with a 128-byte + * buffer. This generator was the default prior to igraph version 0.6, + * at least on systems relying on GNU libc. + * + * This generator was ported from the GNU Scientific Library. + */ + +const igraph_rng_type_t igraph_rngtype_glibc2 = { + /* name= */ "LIBC", + /* min= */ 0, + /* max= */ RAND_MAX, + /* init= */ igraph_rng_glibc2_init, + /* destroy= */ igraph_rng_glibc2_destroy, + /* seed= */ igraph_rng_glibc2_seed, + /* get= */ igraph_rng_glibc2_get, + /* get_real= */ igraph_rng_glibc2_get_real, + /* get_norm= */ 0, + /* get_geom= */ 0, + /* get_binom= */ 0, + /* get_exp= */ 0, + /* get_gamma= */ 0 +}; + +/* ------------------------------------ */ + +typedef struct { + unsigned long int x; +} igraph_i_rng_rand_state_t; + +unsigned long int igraph_rng_rand_get(void *vstate) { + igraph_i_rng_rand_state_t *state = vstate; + state->x = (1103515245 * state->x + 12345) & 0x7fffffffUL; + return state->x; +} + +igraph_real_t igraph_rng_rand_get_real(void *vstate) { + return igraph_rng_rand_get (vstate) / 2147483648.0 ; +} + +int igraph_rng_rand_seed(void *vstate, unsigned long int seed) { + igraph_i_rng_rand_state_t *state = vstate; + state->x = seed; + return 0; +} + +int igraph_rng_rand_init(void **state) { + igraph_i_rng_rand_state_t *st; + + st = igraph_Calloc(1, igraph_i_rng_rand_state_t); + if (!st) { + IGRAPH_ERROR("Cannot initialize RNG", IGRAPH_ENOMEM); + } + (*state) = st; + + igraph_rng_rand_seed(st, 0); + + return 0; +} + +void igraph_rng_rand_destroy(void *vstate) { + igraph_i_rng_rand_state_t *state = + (igraph_i_rng_rand_state_t*) vstate; + igraph_Free(state); +} + +/** + * \var igraph_rngtype_rand + * \brief The old BSD rand/stand random number generator + * + * The sequence is + * x_{n+1} = (a x_n + c) mod m + * with a = 1103515245, c = 12345 and m = 2^31 = 2147483648. The seed + * specifies the initial value, x_1. + * + * The theoretical value of x_{10001} is 1910041713. + * + * The period of this generator is 2^31. + * + * This generator is not very good -- the low bits of successive + * numbers are correlated. + * + * This generator was ported from the GNU Scientific Library. + */ + +const igraph_rng_type_t igraph_rngtype_rand = { + /* name= */ "RAND", + /* min= */ 0, + /* max= */ 0x7fffffffUL, + /* init= */ igraph_rng_rand_init, + /* destroy= */ igraph_rng_rand_destroy, + /* seed= */ igraph_rng_rand_seed, + /* get= */ igraph_rng_rand_get, + /* get_real= */ igraph_rng_rand_get_real, + /* get_norm= */ 0, + /* get_geom= */ 0, + /* get_binom= */ 0, + /* get_exp= */ 0, + /* get_gamma= */ 0 +}; + +/* ------------------------------------ */ + +#define N 624 /* Period parameters */ +#define M 397 + +/* most significant w-r bits */ +static const unsigned long UPPER_MASK = 0x80000000UL; + +/* least significant r bits */ +static const unsigned long LOWER_MASK = 0x7fffffffUL; + +typedef struct { + unsigned long mt[N]; + int mti; +} igraph_i_rng_mt19937_state_t; + +unsigned long int igraph_rng_mt19937_get(void *vstate) { + igraph_i_rng_mt19937_state_t *state = vstate; + + unsigned long k ; + unsigned long int *const mt = state->mt; + +#define MAGIC(y) (((y)&0x1) ? 0x9908b0dfUL : 0) + + if (state->mti >= N) { + /* generate N words at one time */ + int kk; + + for (kk = 0; kk < N - M; kk++) { + unsigned long y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK); + mt[kk] = mt[kk + M] ^ (y >> 1) ^ MAGIC(y); + } + for (; kk < N - 1; kk++) { + unsigned long y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK); + mt[kk] = mt[kk + (M - N)] ^ (y >> 1) ^ MAGIC(y); + } + + { + unsigned long y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK); + mt[N - 1] = mt[M - 1] ^ (y >> 1) ^ MAGIC(y); + } + + state->mti = 0; + } + +#undef MAGIC + + /* Tempering */ + + k = mt[state->mti]; + k ^= (k >> 11); + k ^= (k << 7) & 0x9d2c5680UL; + k ^= (k << 15) & 0xefc60000UL; + k ^= (k >> 18); + + state->mti++; + + return k; +} + +igraph_real_t igraph_rng_mt19937_get_real(void *vstate) { + return igraph_rng_mt19937_get (vstate) / 4294967296.0 ; +} + +int igraph_rng_mt19937_seed(void *vstate, unsigned long int seed) { + igraph_i_rng_mt19937_state_t *state = vstate; + int i; + + memset(state, 0, sizeof(igraph_i_rng_mt19937_state_t)); + + if (seed == 0) { + seed = 4357; /* the default seed is 4357 */ + } + state->mt[0] = seed & 0xffffffffUL; + + for (i = 1; i < N; i++) { + /* See Knuth's "Art of Computer Programming" Vol. 2, 3rd + Ed. p.106 for multiplier. */ + state->mt[i] = + (1812433253UL * (state->mt[i - 1] ^ (state->mt[i - 1] >> 30)) + + (unsigned long) i); + state->mt[i] &= 0xffffffffUL; + } + + state->mti = i; + return 0; +} + +int igraph_rng_mt19937_init(void **state) { + igraph_i_rng_mt19937_state_t *st; + + st = igraph_Calloc(1, igraph_i_rng_mt19937_state_t); + if (!st) { + IGRAPH_ERROR("Cannot initialize RNG", IGRAPH_ENOMEM); + } + (*state) = st; + + igraph_rng_mt19937_seed(st, 0); + + return 0; +} + +void igraph_rng_mt19937_destroy(void *vstate) { + igraph_i_rng_mt19937_state_t *state = + (igraph_i_rng_mt19937_state_t*) vstate; + igraph_Free(state); +} + +/** + * \var igraph_rngtype_mt19937 + * \brief The MT19937 random number generator + * + * The MT19937 generator of Makoto Matsumoto and Takuji Nishimura is a + * variant of the twisted generalized feedback shift-register + * algorithm, and is known as the “Mersenne Twister” generator. It has + * a Mersenne prime period of 2^19937 - 1 (about 10^6000) and is + * equi-distributed in 623 dimensions. It has passed the diehard + * statistical tests. It uses 624 words of state per generator and is + * comparable in speed to the other generators. The original generator + * used a default seed of 4357 and choosing s equal to zero in + * gsl_rng_set reproduces this. Later versions switched to 5489 as the + * default seed, you can choose this explicitly via igraph_rng_seed + * instead if you require it. + * + * For more information see, + * Makoto Matsumoto and Takuji Nishimura, “Mersenne Twister: A + * 623-dimensionally equidistributed uniform pseudorandom number + * generator”. ACM Transactions on Modeling and Computer Simulation, + * Vol. 8, No. 1 (Jan. 1998), Pages 3–30 + * + * The generator igraph_rngtype_mt19937 uses the second revision of the + * seeding procedure published by the two authors above in 2002. The + * original seeding procedures could cause spurious artifacts for some + * seed values. + * + * This generator was ported from the GNU Scientific Library. + */ + +const igraph_rng_type_t igraph_rngtype_mt19937 = { + /* name= */ "MT19937", + /* min= */ 0, + /* max= */ 0xffffffffUL, + /* init= */ igraph_rng_mt19937_init, + /* destroy= */ igraph_rng_mt19937_destroy, + /* seed= */ igraph_rng_mt19937_seed, + /* get= */ igraph_rng_mt19937_get, + /* get_real= */ igraph_rng_mt19937_get_real, + /* get_norm= */ 0, + /* get_geom= */ 0, + /* get_binom= */ 0, + /* get_exp= */ 0, + /* get_gamma= */ 0 +}; + +#undef N +#undef M + +/* ------------------------------------ */ + +#ifndef USING_R + +igraph_i_rng_mt19937_state_t igraph_i_rng_default_state; + +#define addr(a) (&a) + +/** + * \var igraph_i_rng_default + * The default igraph random number generator + * + * This generator is used by all builtin igraph functions that need to + * generate random numbers; e.g. all random graph generators. + * + * You can use \ref igraph_i_rng_default with \ref igraph_rng_seed() + * to set its seed. + * + * You can change the default generator using the \ref + * igraph_rng_set_default() function. + */ + +IGRAPH_THREAD_LOCAL igraph_rng_t igraph_i_rng_default = { + addr(igraph_rngtype_mt19937), + addr(igraph_i_rng_default_state), + /* def= */ 1 +}; + +#undef addr + +/** + * \function igraph_rng_set_default + * Set the default igraph random number generator + * + * \param rng The random number generator to use as default from now + * on. Calling \ref igraph_rng_destroy() on it, while it is still + * being used as the default will result crashes and/or + * unpredictable results. + * + * Time complexity: O(1). + */ + +void igraph_rng_set_default(igraph_rng_t *rng) { + igraph_i_rng_default = (*rng); +} + +#endif + + +/* ------------------------------------ */ + +#ifdef USING_R + +double unif_rand(void); +double norm_rand(void); +double exp_rand(void); +double Rf_rgeom(double); +double Rf_rbinom(double, double); +double Rf_rgamma(double, double); + +int igraph_rng_R_init(void **state) { + IGRAPH_ERROR("R RNG error, unsupported function called", + IGRAPH_EINTERNAL); + return 0; +} + +void igraph_rng_R_destroy(void *state) { + igraph_error("R RNG error, unsupported function called", + __FILE__, __LINE__, IGRAPH_EINTERNAL); +} + +int igraph_rng_R_seed(void *state, unsigned long int seed) { + IGRAPH_ERROR("R RNG error, unsupported function called", + IGRAPH_EINTERNAL); + return 0; +} + +unsigned long int igraph_rng_R_get(void *state) { + return (unsigned long) (unif_rand() * 0x7FFFFFFFUL); +} + +igraph_real_t igraph_rng_R_get_real(void *state) { + return unif_rand(); +} + +igraph_real_t igraph_rng_R_get_norm(void *state) { + return norm_rand(); +} + +igraph_real_t igraph_rng_R_get_geom(void *state, igraph_real_t p) { + return Rf_rgeom(p); +} + +igraph_real_t igraph_rng_R_get_binom(void *state, long int n, + igraph_real_t p) { + return Rf_rbinom(n, p); +} + +igraph_real_t igraph_rng_R_get_gamma(void *state, igraph_real_t shape, + igraph_real_t scale) { + return Rf_rgamma(shape, scale); +} + +igraph_real_t igraph_rng_R_get_exp(void *state, igraph_real_t rate) { + igraph_real_t scale = 1.0 / rate; + if (!IGRAPH_FINITE(scale) || scale <= 0.0) { + if (scale == 0.0) { + return 0.0; + } + return IGRAPH_NAN; + } + return scale * exp_rand(); +} + +igraph_rng_type_t igraph_rngtype_R = { + /* name= */ "GNU R", + /* min= */ 0, + /* max= */ 0x7FFFFFFFUL, + /* init= */ igraph_rng_R_init, + /* destroy= */ igraph_rng_R_destroy, + /* seed= */ igraph_rng_R_seed, + /* get= */ igraph_rng_R_get, + /* get_real= */ igraph_rng_R_get_real, + /* get_norm= */ igraph_rng_R_get_norm, + /* get_geom= */ igraph_rng_R_get_geom, + /* get_binom= */ igraph_rng_R_get_binom, + /* get_exp= */ igraph_rng_R_get_exp +}; + +IGRAPH_THREAD_LOCAL igraph_rng_t igraph_i_rng_default = { + &igraph_rngtype_R, + 0, + /* def= */ 1 +}; + +#endif + +/* ------------------------------------ */ + +/** + * \function igraph_rng_default + * Query the default random number generator. + * + * \return A pointer to the default random number generator. + * + * \sa igraph_rng_set_default() + */ + +igraph_rng_t *igraph_rng_default() { + return &igraph_i_rng_default; +} + +/* ------------------------------------ */ + +double igraph_norm_rand(igraph_rng_t *rng); +double igraph_rgeom(igraph_rng_t *rng, double p); +double igraph_rbinom(igraph_rng_t *rng, double nin, double pp); +double igraph_rexp(igraph_rng_t *rng, double rate); +double igraph_rgamma(igraph_rng_t *rng, double shape, double scale); + +/** + * \function igraph_rng_init + * Initialize a random number generator + * + * This function allocates memory for a random number generator, with + * the given type, and sets its seed to the default. + * + * \param rng Pointer to an uninitialized RNG. + * \param type The type of the RNG, please see the documentation for + * the supported types. + * \return Error code. + * + * Time complexity: depends on the type of the generator, but usually + * it should be O(1). + */ + +int igraph_rng_init(igraph_rng_t *rng, const igraph_rng_type_t *type) { + rng->type = type; + IGRAPH_CHECK(rng->type->init(&rng->state)); + return 0; +} + +/** + * \function igraph_rng_destroy + * Deallocate memory associated with a random number generator + * + * \param rng The RNG to destroy. Do not destroy an RNG that is used + * as the default igraph RNG. + * + * Time complexity: O(1). + */ + +void igraph_rng_destroy(igraph_rng_t *rng) { + rng->type->destroy(rng->state); +} + +/** + * \function igraph_rng_seed + * Set the seed of a random number generator + * + * \param rng The RNG. + * \param seed The new seed. + * \return Error code. + * + * Time complexity: usually O(1), but may depend on the type of the + * RNG. + */ +int igraph_rng_seed(igraph_rng_t *rng, unsigned long int seed) { + const igraph_rng_type_t *type = rng->type; + rng->def = 0; + IGRAPH_CHECK(type->seed(rng->state, seed)); + return 0; +} + +/** + * \function igraph_rng_max + * Query the maximum possible integer for a random number generator + * + * \param rng The RNG. + * \return The largest possible integer that can be generated by + * calling \ref igraph_rng_get_integer() on the RNG. + * + * Time complexity: O(1). + */ + +unsigned long int igraph_rng_max(igraph_rng_t *rng) { + const igraph_rng_type_t *type = rng->type; + return type->max; +} + +/** + * \function igraph_rng_min + * Query the minimum possible integer for a random number generator + * + * \param rng The RNG. + * \return The smallest possible integer that can be generated by + * calling \ref igraph_rng_get_integer() on the RNG. + * + * Time complexity: O(1). + */ + +unsigned long int igraph_rng_min(igraph_rng_t *rng) { + const igraph_rng_type_t *type = rng->type; + return type->min; +} + +/** + * \function igraph_rng_name + * Query the type of a random number generator + * + * \param rng The RNG. + * \return The name of the type of the generator. Do not deallocate or + * change the returned string pointer. + * + * Time complexity: O(1). + */ + +const char *igraph_rng_name(igraph_rng_t *rng) { + const igraph_rng_type_t *type = rng->type; + return type->name; +} + +/** + * \function igraph_rng_get_integer + * Generate an integer random number from an interval + * + * \param rng Pointer to the RNG to use for the generation. Use \ref + * igraph_rng_default() here to use the default igraph RNG. + * \param l Lower limit, inclusive, it can be negative as well. + * \param h Upper limit, inclusive, it can be negative as well, but it + * should be at least l. + * \return The generated random integer. + * + * Time complexity: depends on the generator, but should be usually + * O(1). + */ + +long int igraph_rng_get_integer(igraph_rng_t *rng, + long int l, long int h) { + const igraph_rng_type_t *type = rng->type; + if (type->get_real) { + return (long int)(type->get_real(rng->state) * (h - l + 1) + l); + } else if (type->get) { + unsigned long int max = type->max; + return (long int)(type->get(rng->state) / ((double)max + 1) * (h - l + 1) + l); + } + IGRAPH_ERROR("Internal random generator error", IGRAPH_EINTERNAL); + return 0; +} + +/** + * \function igraph_rng_get_normal + * Normally distributed random numbers + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param m The mean. + * \param s Standard deviation. + * \return The generated normally distributed random number. + * + * Time complexity: depends on the type of the RNG. + */ + +igraph_real_t igraph_rng_get_normal(igraph_rng_t *rng, + igraph_real_t m, igraph_real_t s) { + const igraph_rng_type_t *type = rng->type; + if (type->get_norm) { + return type->get_norm(rng->state) * s + m; + } else { + return igraph_norm_rand(rng) * s + m; + } +} + +/** + * \function igraph_rng_get_unif + * Generate real, uniform random numbers from an interval + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param l The lower bound, it can be negative. + * \param h The upper bound, it can be negative, but it has to be + * larger than the lower bound. + * \return The generated uniformly distributed random number. + * + * Time complexity: depends on the type of the RNG. + */ + +igraph_real_t igraph_rng_get_unif(igraph_rng_t *rng, + igraph_real_t l, igraph_real_t h) { + const igraph_rng_type_t *type = rng->type; + if (type->get_real) { + return type->get_real(rng->state) * (h - l) + l; + } else if (type->get) { + unsigned long int max = type->max; + return type->get(rng->state) / ((double)max + 1) * (double)(h - l) + l; + } + IGRAPH_ERROR("Internal random generator error", IGRAPH_EINTERNAL); + return 0; +} + +/** + * \function igraph_rng_get_unif01 + * Generate real, uniform random number from the unit interval + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \return The generated uniformly distributed random number. + * + * Time complexity: depends on the type of the RNG. + */ + +igraph_real_t igraph_rng_get_unif01(igraph_rng_t *rng) { + const igraph_rng_type_t *type = rng->type; + if (type->get_real) { + return type->get_real(rng->state); + } else if (type->get) { + unsigned long int max = type->max; + return type->get(rng->state) / ((double)max + 1); + } + IGRAPH_ERROR("Internal random generator error", IGRAPH_EINTERNAL); + return 0; +} + +/** + * \function igraph_rng_get_geom + * Generate geometrically distributed random numbers + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param p The probability of success in each trial. Must be larger + * than zero and smaller or equal to 1. + * \return The generated geometrically distributed random number. + * + * Time complexity: depends on the type of the RNG. + */ + +igraph_real_t igraph_rng_get_geom(igraph_rng_t *rng, igraph_real_t p) { + const igraph_rng_type_t *type = rng->type; + if (type->get_geom) { + return type->get_geom(rng->state, p); + } else { + return igraph_rgeom(rng, p); + } +} + +/** + * \function igraph_rng_get_binom + * Generate binomially distributed random numbers + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param n Number of observations. + * \param p Probability of an event. + * \return The generated binomially distributed random number. + * + * Time complexity: depends on the type of the RNG. + */ + +igraph_real_t igraph_rng_get_binom(igraph_rng_t *rng, long int n, + igraph_real_t p) { + const igraph_rng_type_t *type = rng->type; + if (type->get_binom) { + return type->get_binom(rng->state, n, p); + } else { + return igraph_rbinom(rng, n, p); + } +} + +/** + * \function igraph_rng_get_gamma + * Generate sample from a Gamma distribution + * + * \param rng Pointer to the RNG to use. Use \ref igraph_rng_default() + * here to use the default igraph RNG. + * \param shape Shape parameter. + * \param scale Scale parameter. + * \return The generated sample + * + * Time complexity: depends on RNG. + */ + +igraph_real_t igraph_rng_get_gamma(igraph_rng_t *rng, igraph_real_t shape, + igraph_real_t scale) { + const igraph_rng_type_t *type = rng->type; + if (type->get_gamma) { + return type->get_gamma(rng->state, shape, scale); + } else { + return igraph_rgamma(rng, shape, scale); + } +} + +unsigned long int igraph_rng_get_int31(igraph_rng_t *rng) { + const igraph_rng_type_t *type = rng->type; + unsigned long int max = type->max; + if (type->get && max == 0x7FFFFFFFUL) { + return type->get(rng->state); + } else if (type->get_real) { + return (unsigned long int) (type->get_real(rng->state) * 0x7FFFFFFFUL); + } else { + return (unsigned long int) (igraph_rng_get_unif01(rng) * 0x7FFFFFFFUL); + } +} + +igraph_real_t igraph_rng_get_exp(igraph_rng_t *rng, igraph_real_t rate) { + const igraph_rng_type_t *type = rng->type; + if (type->get_exp) { + return type->get_exp(rng->state, rate); + } else { + return igraph_rexp(rng, rate); + } +} + + +#ifndef HAVE_EXPM1 +#ifndef USING_R /* R provides a replacement */ +/* expm1 replacement */ +double expm1 (double x) { + if (fabs(x) < M_LN2) { + /* Compute the Taylor series S = x + (1/2!) x^2 + (1/3!) x^3 + ... */ + + double i = 1.0; + double sum = x; + double term = x / 1.0; + + do { + term *= x / ++i; + sum += term; + } while (fabs(term) > fabs(sum) * 2.22e-16); + + return sum; + } + + return expl(x) - 1.0L; +} +#endif +#endif + +#ifndef HAVE_RINT +#ifndef USING_R /* R provides a replacement */ +/* rint replacement */ +double rint (double x) { + return ( (x < 0.) ? -floor(-x + .5) : floor(x + .5) ); +} +#endif +#endif + +#ifndef HAVE_RINTF +float rintf (float x) { + return ( (x < (float)0.) ? -(float)floor(-x + .5) : (float)floor(x + .5) ); +} +#endif + +/* + * \ingroup internal + * + * This function appends the rest of the needed random number to the + * result vector. + */ + +static int igraph_i_random_sample_alga(igraph_vector_t *res, + igraph_integer_t l, igraph_integer_t h, + igraph_integer_t length) { + igraph_real_t N = h - l + 1; + igraph_real_t n = length; + + igraph_real_t top = N - n; + igraph_real_t Nreal = N; + igraph_real_t S = 0; + igraph_real_t V, quot; + + l = l - 1; + + while (n >= 2) { + V = RNG_UNIF01(); + S = 1; + quot = top / Nreal; + while (quot > V) { + S += 1; + top = -1.0 + top; + Nreal = -1.0 + Nreal; + quot = (quot * top) / Nreal; + } + l += S; + igraph_vector_push_back(res, l); /* allocated */ + Nreal = -1.0 + Nreal; n = -1 + n; + } + + S = floor(round(Nreal) * RNG_UNIF01()); + l += S + 1; + igraph_vector_push_back(res, l); /* allocated */ + + return 0; +} + +/** + * \ingroup nongraph + * \function igraph_random_sample + * \brief Generates an increasing random sequence of integers. + * + *
    + * This function generates an increasing sequence of random integer + * numbers from a given interval. The algorithm is taken literally + * from (Vitter 1987). This method can be used for generating numbers from a + * \em very large interval. It is primarily created for randomly + * selecting some edges from the sometimes huge set of possible edges + * in a large graph. + * + * Note that the type of the lower and the upper limit is \c igraph_real_t, + * not \c igraph_integer_t. This does not mean that you can pass fractional + * numbers there; these values must still be integral, but we need the + * longer range of \c igraph_real_t in several places in the library + * (for instance, when generating Erdos-Renyi graphs). + * \param res Pointer to an initialized vector. This will hold the + * result. It will be resized to the proper size. + * \param l The lower limit of the generation interval (inclusive). This must + * be less than or equal to the upper limit, and it must be integral. + * Passing a fractional number here results in undefined behaviour. + * \param h The upper limit of the generation interval (inclusive). This must + * be greater than or equal to the lower limit, and it must be integral. + * Passing a fractional number here results in undefined behaviour. + * \param length The number of random integers to generate. + * \return The error code \c IGRAPH_EINVAL is returned in each of the + * following cases: (1) The given lower limit is greater than the + * given upper limit, i.e. \c l > \c h. (2) Assuming that + * \c l < \c h and N is the sample size, the above error code is + * returned if N > |\c h - \c l|, i.e. the sample size exceeds the + * size of the candidate pool. + * + * Time complexity: according to (Vitter 1987), the expected + * running time is O(length). + * + * + * Reference: + * \clist + * \cli (Vitter 1987) + * J. S. Vitter. An efficient algorithm for sequential random sampling. + * \emb ACM Transactions on Mathematical Software, \eme 13(1):58--67, 1987. + * \endclist + * + * \example examples/simple/igraph_random_sample.c + */ + +int igraph_random_sample(igraph_vector_t *res, igraph_real_t l, igraph_real_t h, + igraph_integer_t length) { + igraph_real_t N = h - l + 1; + igraph_real_t n = length; + int retval; + + igraph_real_t nreal = length; + igraph_real_t ninv = (nreal != 0) ? 1.0 / nreal : 0.0; + igraph_real_t Nreal = N; + igraph_real_t Vprime; + igraph_real_t qu1 = -n + 1 + N; + igraph_real_t qu1real = -nreal + 1.0 + Nreal; + igraph_real_t negalphainv = -13; + igraph_real_t threshold = -negalphainv * n; + igraph_real_t S; + + /* getting back some sense of sanity */ + if (l > h) { + IGRAPH_ERROR("Lower limit is greater than upper limit", IGRAPH_EINVAL); + } + /* now we know that l <= h */ + if (length > N) { + IGRAPH_ERROR("Sample size exceeds size of candidate pool", IGRAPH_EINVAL); + } + + /* treat rare cases quickly */ + if (l == h) { + IGRAPH_CHECK(igraph_vector_resize(res, 1)); + VECTOR(*res)[0] = l; + return 0; + } + if (length == 0) { + igraph_vector_clear(res); + return 0; + } + if (length == N) { + long int i = 0; + IGRAPH_CHECK(igraph_vector_resize(res, length)); + for (i = 0; i < length; i++) { + VECTOR(*res)[i] = l++; + } + return 0; + } + + igraph_vector_clear(res); + IGRAPH_CHECK(igraph_vector_reserve(res, length)); + + RNG_BEGIN(); + + Vprime = exp(log(RNG_UNIF01()) * ninv); + l = l - 1; + + while (n > 1 && threshold < N) { + igraph_real_t X, U; + igraph_real_t limit, t; + igraph_real_t negSreal, y1, y2, top, bottom; + igraph_real_t nmin1inv = 1.0 / (-1.0 + nreal); + while (1) { + while (1) { + X = Nreal * (-Vprime + 1.0); + S = floor(X); + // if (S==0) { S=1; } + if (S < qu1) { + break; + } + Vprime = exp(log(RNG_UNIF01()) * ninv); + } + U = RNG_UNIF01(); + negSreal = -S; + + y1 = exp(log(U * Nreal / qu1real) * nmin1inv); + Vprime = y1 * (-X / Nreal + 1.0) * (qu1real / (negSreal + qu1real)); + if (Vprime <= 1.0) { + break; + } + + y2 = 1.0; + top = -1.0 + Nreal; + if (-1 + n > S) { + bottom = -nreal + Nreal; + limit = -S + N; + } else { + bottom = -1.0 + negSreal + Nreal; + limit = qu1; + } + for (t = -1 + N; t >= limit; t--) { + y2 = (y2 * top) / bottom; + top = -1.0 + top; + bottom = -1.0 + bottom; + } + if (Nreal / (-X + Nreal) >= y1 * exp(log(y2)*nmin1inv)) { + Vprime = exp(log(RNG_UNIF01()) * nmin1inv); + break; + } + Vprime = exp(log(RNG_UNIF01()) * ninv); + } + + l += S + 1; + igraph_vector_push_back(res, l); /* allocated */ + N = -S + (-1 + N); Nreal = negSreal + (-1.0 + Nreal); + n = -1 + n; nreal = -1.0 + nreal; ninv = nmin1inv; + qu1 = -S + qu1; qu1real = negSreal + qu1real; + threshold = threshold + negalphainv; + } + + if (n > 1) { + retval = igraph_i_random_sample_alga(res, (igraph_integer_t) l + 1, + (igraph_integer_t) h, + (igraph_integer_t) n); + } else { + retval = 0; + S = floor(N * Vprime); + l += S + 1; + igraph_vector_push_back(res, l); /* allocated */ + } + + RNG_END(); + + return retval; +} + +#ifdef USING_R + +/* These are never called. But they are correct, nevertheless */ + +double igraph_norm_rand(igraph_rng_t *rng) { + return norm_rand(); +} + +double igraph_rgeom(igraph_rng_t *rng, double p) { + return Rf_rgeom(p); +} + +double igraph_rbinom(igraph_rng_t *rng, double nin, double pp) { + return Rf_rbinom(nin, pp); +} + +double igraph_rexp(igraph_rng_t *rng, double rate) { + igraph_real_t scale = 1.0 / rate; + if (!IGRAPH_FINITE(scale) || scale <= 0.0) { + if (scale == 0.0) { + return 0.0; + } + return IGRAPH_NAN; + } + return scale * exp_rand(); +} + +double igraph_rgamma(igraph_rng_t *rng, double shape, double scale) { + return Rf_rgamma(shape, scale); +} + +#else + +/* + * Mathlib : A C Library of Special Functions + * Copyright (C) 1998 Ross Ihaka + * Copyright (C) 2000 The R Development Core Team + * based on AS 111 (C) 1977 Royal Statistical Society + * and on AS 241 (C) 1988 Royal Statistical Society + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * SYNOPSIS + * + * double qnorm5(double p, double mu, double sigma, + * int lower_tail, int log_p) + * {qnorm (..) is synonymous and preferred inside R} + * + * DESCRIPTION + * + * Compute the quantile function for the normal distribution. + * + * For small to moderate probabilities, algorithm referenced + * below is used to obtain an initial approximation which is + * polished with a final Newton step. + * + * For very large arguments, an algorithm of Wichura is used. + * + * REFERENCE + * + * Beasley, J. D. and S. G. Springer (1977). + * Algorithm AS 111: The percentage points of the normal distribution, + * Applied Statistics, 26, 118-121. + * + * Wichura, M.J. (1988). + * Algorithm AS 241: The Percentage Points of the Normal Distribution. + * Applied Statistics, 37, 477-484. + */ + +/* + * Mathlib : A C Library of Special Functions + * Copyright (C) 1998-2004 The R Development Core Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* Private header file for use during compilation of Mathlib */ +#ifndef MATHLIB_PRIVATE_H +#define MATHLIB_PRIVATE_H + +#define ML_POSINF IGRAPH_INFINITY +#define ML_NEGINF -IGRAPH_INFINITY +#define ML_NAN IGRAPH_NAN + +#define ML_ERROR(x) /* nothing */ +#define ML_UNDERFLOW (DBL_MIN * DBL_MIN) +#define ML_VALID(x) (!ISNAN(x)) + +#define ME_NONE 0 +/* no error */ +#define ME_DOMAIN 1 +/* argument out of domain */ +#define ME_RANGE 2 +/* value out of range */ +#define ME_NOCONV 4 +/* process did not converge */ +#define ME_PRECISION 8 +/* does not have "full" precision */ +#define ME_UNDERFLOW 16 +/* and underflow occurred (important for IEEE)*/ + +#define ML_ERR_return_NAN { ML_ERROR(ME_DOMAIN); return ML_NAN; } + +/* Wilcoxon Rank Sum Distribution */ + +#define WILCOX_MAX 50 + +/* Wilcoxon Signed Rank Distribution */ + +#define SIGNRANK_MAX 50 + +/* Formerly private part of Mathlib.h */ + +/* always remap internal functions */ +#define bd0 Rf_bd0 +#define chebyshev_eval Rf_chebyshev_eval +#define chebyshev_init Rf_chebyshev_init +#define i1mach Rf_i1mach +#define gammalims Rf_gammalims +#define lfastchoose Rf_lfastchoose +#define lgammacor Rf_lgammacor +#define stirlerr Rf_stirlerr + +/* Chebyshev Series */ + +int chebyshev_init(double*, int, double); +double chebyshev_eval(double, const double *, const int); + +/* Gamma and Related Functions */ + +void gammalims(double*, double*); +double lgammacor(double); /* log(gamma) correction */ +double stirlerr(double); /* Stirling expansion "error" */ + +double lfastchoose(double, double); + +double bd0(double, double); + +/* Consider adding these two to the API (Rmath.h): */ +double dbinom_raw(double, double, double, double, int); +double dpois_raw (double, double, int); +double pnchisq_raw(double, double, double, double, double, int); + +int i1mach(int); + +/* From toms708.c */ +void bratio(double a, double b, double x, double y, + double *w, double *w1, int *ierr); + + +#endif /* MATHLIB_PRIVATE_H */ + + +/* Utilities for `dpq' handling (density/probability/quantile) */ + +/* give_log in "d"; log_p in "p" & "q" : */ +#define give_log log_p +/* "DEFAULT" */ +/* --------- */ +#define R_D__0 (log_p ? ML_NEGINF : 0.) /* 0 */ +#define R_D__1 (log_p ? 0. : 1.) /* 1 */ +#define R_DT_0 (lower_tail ? R_D__0 : R_D__1) /* 0 */ +#define R_DT_1 (lower_tail ? R_D__1 : R_D__0) /* 1 */ + +#define R_D_Lval(p) (lower_tail ? (p) : (1 - (p))) /* p */ +#define R_D_Cval(p) (lower_tail ? (1 - (p)) : (p)) /* 1 - p */ + +#define R_D_val(x) (log_p ? log(x) : (x)) /* x in pF(x,..) */ +#define R_D_qIv(p) (log_p ? exp(p) : (p)) /* p in qF(p,..) */ +#define R_D_exp(x) (log_p ? (x) : exp(x)) /* exp(x) */ +#define R_D_log(p) (log_p ? (p) : log(p)) /* log(p) */ +#define R_D_Clog(p) (log_p ? log1p(-(p)) : (1 - (p)))/* [log](1-p) */ + +/* log(1-exp(x)): R_D_LExp(x) == (log1p(- R_D_qIv(x))) but even more stable:*/ +#define R_D_LExp(x) (log_p ? R_Log1_Exp(x) : log1p(-x)) + +/*till 1.8.x: + * #define R_DT_val(x) R_D_val(R_D_Lval(x)) + * #define R_DT_Cval(x) R_D_val(R_D_Cval(x)) */ +#define R_DT_val(x) (lower_tail ? R_D_val(x) : R_D_Clog(x)) +#define R_DT_Cval(x) (lower_tail ? R_D_Clog(x) : R_D_val(x)) + +/*#define R_DT_qIv(p) R_D_Lval(R_D_qIv(p)) * p in qF ! */ +#define R_DT_qIv(p) (log_p ? (lower_tail ? exp(p) : - expm1(p)) \ + : R_D_Lval(p)) + +/*#define R_DT_CIv(p) R_D_Cval(R_D_qIv(p)) * 1 - p in qF */ +#define R_DT_CIv(p) (log_p ? (lower_tail ? -expm1(p) : exp(p)) \ + : R_D_Cval(p)) + +#define R_DT_exp(x) R_D_exp(R_D_Lval(x)) /* exp(x) */ +#define R_DT_Cexp(x) R_D_exp(R_D_Cval(x)) /* exp(1 - x) */ + +#define R_DT_log(p) (lower_tail? R_D_log(p) : R_D_LExp(p))/* log(p) in qF */ +#define R_DT_Clog(p) (lower_tail? R_D_LExp(p): R_D_log(p))/* log(1-p) in qF*/ +#define R_DT_Log(p) (lower_tail? (p) : R_Log1_Exp(p)) +/* == R_DT_log when we already "know" log_p == TRUE :*/ + +#define R_Q_P01_check(p) \ + if ((log_p && p > 0) || \ + (!log_p && (p < 0 || p > 1)) ) \ + ML_ERR_return_NAN + +/* additions for density functions (C.Loader) */ +#define R_D_fexp(f,x) (give_log ? -0.5*log(f)+(x) : exp(x)/sqrt(f)) +#define R_D_forceint(x) floor((x) + 0.5) +#define R_D_nonint(x) (fabs((x) - floor((x)+0.5)) > 1e-7) +/* [neg]ative or [non int]eger : */ +#define R_D_negInonint(x) (x < 0. || R_D_nonint(x)) + +#define R_D_nonint_check(x) \ + if(R_D_nonint(x)) { \ + MATHLIB_WARNING("non-integer x = %f", x); \ + return R_D__0; \ + } + +double igraph_qnorm5(double p, double mu, double sigma, int lower_tail, int log_p) { + double p_, q, r, val; + +#ifdef IEEE_754 + if (ISNAN(p) || ISNAN(mu) || ISNAN(sigma)) { + return p + mu + sigma; + } +#endif + if (p == R_DT_0) { + return ML_NEGINF; + } + if (p == R_DT_1) { + return ML_POSINF; + } + R_Q_P01_check(p); + + if (sigma < 0) { + ML_ERR_return_NAN; + } + if (sigma == 0) { + return mu; + } + + p_ = R_DT_qIv(p);/* real lower_tail prob. p */ + q = p_ - 0.5; + + /*-- use AS 241 --- */ + /* double ppnd16_(double *p, long *ifault)*/ + /* ALGORITHM AS241 APPL. STATIST. (1988) VOL. 37, NO. 3 + + Produces the normal deviate Z corresponding to a given lower + tail area of P; Z is accurate to about 1 part in 10**16. + + (original fortran code used PARAMETER(..) for the coefficients + and provided hash codes for checking them...) + */ + if (fabs(q) <= .425) {/* 0.075 <= p <= 0.925 */ + r = .180625 - q * q; + val = + q * (((((((r * 2509.0809287301226727 + + 33430.575583588128105) * r + 67265.770927008700853) * r + + 45921.953931549871457) * r + 13731.693765509461125) * r + + 1971.5909503065514427) * r + 133.14166789178437745) * r + + 3.387132872796366608) + / (((((((r * 5226.495278852854561 + + 28729.085735721942674) * r + 39307.89580009271061) * r + + 21213.794301586595867) * r + 5394.1960214247511077) * r + + 687.1870074920579083) * r + 42.313330701600911252) * r + 1.); + } else { /* closer than 0.075 from {0,1} boundary */ + + /* r = min(p, 1-p) < 0.075 */ + if (q > 0) { + r = R_DT_CIv(p); /* 1-p */ + } else { + r = p_; /* = R_DT_Iv(p) ^= p */ + } + + r = sqrt(- ((log_p && + ((lower_tail && q <= 0) || (!lower_tail && q > 0))) ? + p : /* else */ log(r))); + /* r = sqrt(-log(r)) <==> min(p, 1-p) = exp( - r^2 ) */ + + if (r <= 5.) { /* <==> min(p,1-p) >= exp(-25) ~= 1.3888e-11 */ + r += -1.6; + val = (((((((r * 7.7454501427834140764e-4 + + .0227238449892691845833) * r + .24178072517745061177) * + r + 1.27045825245236838258) * r + + 3.64784832476320460504) * r + 5.7694972214606914055) * + r + 4.6303378461565452959) * r + + 1.42343711074968357734) + / (((((((r * + 1.05075007164441684324e-9 + 5.475938084995344946e-4) * + r + .0151986665636164571966) * r + + .14810397642748007459) * r + .68976733498510000455) * + r + 1.6763848301838038494) * r + + 2.05319162663775882187) * r + 1.); + } else { /* very close to 0 or 1 */ + r += -5.; + val = (((((((r * 2.01033439929228813265e-7 + + 2.71155556874348757815e-5) * r + + .0012426609473880784386) * r + .026532189526576123093) * + r + .29656057182850489123) * r + + 1.7848265399172913358) * r + 5.4637849111641143699) * + r + 6.6579046435011037772) + / (((((((r * + 2.04426310338993978564e-15 + 1.4215117583164458887e-7) * + r + 1.8463183175100546818e-5) * r + + 7.868691311456132591e-4) * r + .0148753612908506148525) + * r + .13692988092273580531) * r + + .59983220655588793769) * r + 1.); + } + + if (q < 0.0) { + val = -val; + } + /* return (q >= 0.)? r : -r ;*/ + } + return mu + sigma * val; +} + +double fsign(double x, double y) { +#ifdef IEEE_754 + if (ISNAN(x) || ISNAN(y)) { + return x + y; + } +#endif + return ((y >= 0) ? fabs(x) : -fabs(x)); +} + +int imax2(int x, int y) { + return (x < y) ? y : x; +} + +int imin2(int x, int y) { + return (x < y) ? x : y; +} + +#if HAVE_WORKING_ISFINITE || HAVE_DECL_ISFINITE + /* isfinite is defined in according to C99 */ + #define R_FINITE(x) isfinite(x) +#elif HAVE_WORKING_FINITE || HAVE_FINITE + /* include header needed to define finite() */ + #ifdef HAVE_IEEE754_H + #include /* newer Linuxen */ + #else + #ifdef HAVE_IEEEFP_H + #include /* others [Solaris], .. */ + #endif + #endif + #define R_FINITE(x) finite(x) +#else + #define R_FINITE(x) R_finite(x) +#endif + +int R_finite(double x) { +#if HAVE_WORKING_ISFINITE || HAVE_DECL_ISFINITE + return isfinite(x); +#elif HAVE_WORKING_FINITE || HAVE_FINITE + return finite(x); +#else + /* neither finite nor isfinite work. Do we really need the AIX exception? */ +# ifdef _AIX +# include + return FINITE(x); +# elif defined(_MSC_VER) + return _finite(x); +#else + return (!isnan(x) & (x != 1 / 0.0) & (x != -1.0 / 0.0)); +# endif +#endif +} + +int R_isnancpp(double x) { + return (isnan(x) != 0); +} + +#ifdef __cplusplus + int R_isnancpp(double); /* in arithmetic.c */ + #define ISNAN(x) R_isnancpp(x) +#else + #define ISNAN(x) (isnan(x)!=0) +#endif + +double igraph_norm_rand(igraph_rng_t *rng) { + + double u1; + +#define BIG 134217728 /* 2^27 */ + /* unif_rand() alone is not of high enough precision */ + u1 = igraph_rng_get_unif01(rng); + u1 = (int)(BIG * u1) + igraph_rng_get_unif01(rng); + return igraph_qnorm5(u1 / BIG, 0.0, 1.0, 1, 0); +} + +/* + * Mathlib : A C Library of Special Functions + * Copyright (C) 1998 Ross Ihaka + * Copyright (C) 2000-2002 the R Development Core Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * SYNOPSIS + * + * #include + * double exp_rand(void); + * + * DESCRIPTION + * + * Random variates from the standard exponential distribution. + * + * REFERENCE + * + * Ahrens, J.H. and Dieter, U. (1972). + * Computer methods for sampling from the exponential and + * normal distributions. + * Comm. ACM, 15, 873-882. + */ + +double igraph_exp_rand(igraph_rng_t *rng) { + /* q[k-1] = sum(log(2)^k / k!) k=1,..,n, */ + /* The highest n (here 8) is determined by q[n-1] = 1.0 */ + /* within standard precision */ + const double q[] = { + 0.6931471805599453, + 0.9333736875190459, + 0.9888777961838675, + 0.9984959252914960, + 0.9998292811061389, + 0.9999833164100727, + 0.9999985691438767, + 0.9999998906925558, + 0.9999999924734159, + 0.9999999995283275, + 0.9999999999728814, + 0.9999999999985598, + 0.9999999999999289, + 0.9999999999999968, + 0.9999999999999999, + 1.0000000000000000 + }; + double a, u, ustar, umin; + int i; + + a = 0.; + /* precaution if u = 0 is ever returned */ + u = igraph_rng_get_unif01(rng); + while (u <= 0.0 || u >= 1.0) { + u = igraph_rng_get_unif01(rng); + } + for (;;) { + u += u; + if (u > 1.0) { + break; + } + a += q[0]; + } + u -= 1.; + + if (u <= q[0]) { + return a + u; + } + + i = 0; + ustar = igraph_rng_get_unif01(rng); + umin = ustar; + do { + ustar = igraph_rng_get_unif01(rng); + if (ustar < umin) { + umin = ustar; + } + i++; + } while (u > q[i]); + return a + umin * q[0]; +} + +/* + * Mathlib : A C Library of Special Functions + * Copyright (C) 1998 Ross Ihaka + * Copyright (C) 2000-2001 The R Development Core Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * SYNOPSIS + * + * #include + * double rpois(double lambda) + * + * DESCRIPTION + * + * Random variates from the Poisson distribution. + * + * REFERENCE + * + * Ahrens, J.H. and Dieter, U. (1982). + * Computer generation of Poisson deviates + * from modified normal distributions. + * ACM Trans. Math. Software 8, 163-179. + */ + +#define a0 -0.5 +#define a1 0.3333333 +#define a2 -0.2500068 +#define a3 0.2000118 +#define a4 -0.1661269 +#define a5 0.1421878 +#define a6 -0.1384794 +#define a7 0.1250060 + +#define one_7 0.1428571428571428571 +#define one_12 0.0833333333333333333 +#define one_24 0.0416666666666666667 + +#define repeat for(;;) + +#define FALSE 0 +#define TRUE 1 +#define M_1_SQRT_2PI 0.398942280401432677939946059934 /* 1/sqrt(2pi) */ + +double igraph_rpois(igraph_rng_t *rng, double mu) { + /* Factorial Table (0:9)! */ + const double fact[10] = { + 1., 1., 2., 6., 24., 120., 720., 5040., 40320., 362880. + }; + + /* These are static --- persistent between calls for same mu : */ + static IGRAPH_THREAD_LOCAL int l, m; + + static IGRAPH_THREAD_LOCAL double b1, b2, c, c0, c1, c2, c3; + static IGRAPH_THREAD_LOCAL double pp[36], p0, p, q, s, d, omega; + static IGRAPH_THREAD_LOCAL double big_l;/* integer "w/o overflow" */ + static IGRAPH_THREAD_LOCAL double muprev = 0., muprev2 = 0.;/*, muold = 0.*/ + + /* Local Vars [initialize some for -Wall]: */ + double del, difmuk = 0., E = 0., fk = 0., fx, fy, g, px, py, t, u = 0., v, x; + double pois = -1.; + int k, kflag, big_mu, new_big_mu = FALSE; + + if (!R_FINITE(mu)) { + ML_ERR_return_NAN; + } + + if (mu <= 0.) { + return 0.; + } + + big_mu = mu >= 10.; + if (big_mu) { + new_big_mu = FALSE; + } + + if (!(big_mu && mu == muprev)) {/* maybe compute new persistent par.s */ + + if (big_mu) { + new_big_mu = TRUE; + /* Case A. (recalculation of s,d,l because mu has changed): + * The Poisson probabilities pk exceed the discrete normal + * probabilities fk whenever k >= m(mu). + */ + muprev = mu; + s = sqrt(mu); + d = 6. * mu * mu; + big_l = floor(mu - 1.1484); + /* = an upper bound to m(mu) for all mu >= 10.*/ + } else { /* Small mu ( < 10) -- not using normal approx. */ + + /* Case B. (start new table and calculate p0 if necessary) */ + + /*muprev = 0.;-* such that next time, mu != muprev ..*/ + if (mu != muprev) { + muprev = mu; + m = imax2(1, (int) mu); + l = 0; /* pp[] is already ok up to pp[l] */ + q = p0 = p = exp(-mu); + } + + repeat { + /* Step U. uniform sample for inversion method */ + u = igraph_rng_get_unif01(rng); + if (u <= p0) { + return 0.; + } + + /* Step T. table comparison until the end pp[l] of the + pp-table of cumulative Poisson probabilities + (0.458 > ~= pp[9](= 0.45792971447) for mu=10 ) */ + if (l != 0) { + for (k = (u <= 0.458) ? 1 : imin2(l, m); k <= l; k++) + if (u <= pp[k]) { + return (double)k; + } + if (l == 35) { /* u > pp[35] */ + continue; + } + } + /* Step C. creation of new Poisson + probabilities p[l..] and their cumulatives q =: pp[k] */ + l++; + for (k = l; k <= 35; k++) { + p *= mu / k; + q += p; + pp[k] = q; + if (u <= q) { + l = k; + return (double)k; + } + } + l = 35; + } /* end(repeat) */ + }/* mu < 10 */ + + } /* end {initialize persistent vars} */ + + /* Only if mu >= 10 : ----------------------- */ + + /* Step N. normal sample */ + g = mu + s * igraph_norm_rand(rng);/* norm_rand() ~ N(0,1), standard normal */ + + if (g >= 0.) { + pois = floor(g); + /* Step I. immediate acceptance if pois is large enough */ + if (pois >= big_l) { + return pois; + } + /* Step S. squeeze acceptance */ + fk = pois; + difmuk = mu - fk; + u = igraph_rng_get_unif01(rng); /* ~ U(0,1) - sample */ + if (d * u >= difmuk * difmuk * difmuk) { + return pois; + } + } + + /* Step P. preparations for steps Q and H. + (recalculations of parameters if necessary) */ + + if (new_big_mu || mu != muprev2) { + /* Careful! muprev2 is not always == muprev + because one might have exited in step I or S + */ + muprev2 = mu; + omega = M_1_SQRT_2PI / s; + /* The quantities b1, b2, c3, c2, c1, c0 are for the Hermite + * approximations to the discrete normal probabilities fk. */ + + b1 = one_24 / mu; + b2 = 0.3 * b1 * b1; + c3 = one_7 * b1 * b2; + c2 = b2 - 15. * c3; + c1 = b1 - 6. * b2 + 45. * c3; + c0 = 1. - b1 + 3. * b2 - 15. * c3; + c = 0.1069 / mu; /* guarantees majorization by the 'hat'-function. */ + } + + if (g >= 0.) { + /* 'Subroutine' F is called (kflag=0 for correct return) */ + kflag = 0; + goto Step_F; + } + + + repeat { + /* Step E. Exponential Sample */ + + E = igraph_exp_rand(rng);/* ~ Exp(1) (standard exponential) */ + + /* sample t from the laplace 'hat' + (if t <= -0.6744 then pk < fk for all mu >= 10.) */ + u = 2 * igraph_rng_get_unif01(rng) - 1.; + t = 1.8 + fsign(E, u); + if (t > -0.6744) { + pois = floor(mu + s * t); + fk = pois; + difmuk = mu - fk; + + /* 'subroutine' F is called (kflag=1 for correct return) */ + kflag = 1; + +Step_F: /* 'subroutine' F : calculation of px,py,fx,fy. */ + + if (pois < 10) { /* use factorials from table fact[] */ + px = -mu; + py = pow(mu, pois) / fact[(int)pois]; + } else { + /* Case pois >= 10 uses polynomial approximation + a0-a7 for accuracy when advisable */ + del = one_12 / fk; + del = del * (1. - 4.8 * del * del); + v = difmuk / fk; + if (fabs(v) <= 0.25) + px = fk * v * v * (((((((a7 * v + a6) * v + a5) * v + a4) * + v + a3) * v + a2) * v + a1) * v + a0) + - del; + else { /* |v| > 1/4 */ + px = fk * log(1. + v) - difmuk - del; + } + py = M_1_SQRT_2PI / sqrt(fk); + } + x = (0.5 - difmuk) / s; + x *= x;/* x^2 */ + fx = -0.5 * x; + fy = omega * (((c3 * x + c2) * x + c1) * x + c0); + if (kflag > 0) { + /* Step H. Hat acceptance (E is repeated on rejection) */ + if (c * fabs(u) <= py * exp(px + E) - fy * exp(fx + E)) { + break; + } + } else + /* Step Q. Quotient acceptance (rare case) */ + if (fy - u * fy <= py * exp(px - fx)) { + break; + } + }/* t > -.67.. */ + } + return pois; +} + +#undef a1 +#undef a2 +#undef a3 +#undef a4 +#undef a5 +#undef a6 +#undef a7 + +double igraph_rgeom(igraph_rng_t *rng, double p) { + if (ISNAN(p) || p <= 0 || p > 1) { + ML_ERR_return_NAN; + } + + return igraph_rpois(rng, igraph_exp_rand(rng) * ((1 - p) / p)); +} + +/* This is from nmath/rbinom.c */ + +#define repeat for(;;) + +double igraph_rbinom(igraph_rng_t *rng, double nin, double pp) { + /* FIXME: These should become THREAD_specific globals : */ + + static IGRAPH_THREAD_LOCAL double c, fm, npq, p1, p2, p3, p4, qn; + static IGRAPH_THREAD_LOCAL double xl, xll, xlr, xm, xr; + + static IGRAPH_THREAD_LOCAL double psave = -1.0; + static IGRAPH_THREAD_LOCAL int nsave = -1; + static IGRAPH_THREAD_LOCAL int m; + + double f, f1, f2, u, v, w, w2, x, x1, x2, z, z2; + double p, q, np, g, r, al, alv, amaxp, ffm, ynorm; + int i, ix, k, n; + + if (!R_FINITE(nin)) { + ML_ERR_return_NAN; + } + n = floor(nin + 0.5); + if (n != nin) { + ML_ERR_return_NAN; + } + + if (!R_FINITE(pp) || + /* n=0, p=0, p=1 are not errors */ + n < 0 || pp < 0. || pp > 1.) { + ML_ERR_return_NAN; + } + + if (n == 0 || pp == 0.) { + return 0; + } + if (pp == 1.) { + return n; + } + + p = fmin(pp, 1. - pp); + q = 1. - p; + np = n * p; + r = p / q; + g = r * (n + 1); + + /* Setup, perform only when parameters change [using static (globals): */ + + /* FIXING: Want this thread safe + -- use as little (thread globals) as possible + */ + if (pp != psave || n != nsave) { + psave = pp; + nsave = n; + if (np < 30.0) { + /* inverse cdf logic for mean less than 30 */ + qn = pow(q, (double) n); + goto L_np_small; + } else { + ffm = np + p; + m = ffm; + fm = m; + npq = np * q; + p1 = (int)(2.195 * sqrt(npq) - 4.6 * q) + 0.5; + xm = fm + 0.5; + xl = xm - p1; + xr = xm + p1; + c = 0.134 + 20.5 / (15.3 + fm); + al = (ffm - xl) / (ffm - xl * p); + xll = al * (1.0 + 0.5 * al); + al = (xr - ffm) / (xr * q); + xlr = al * (1.0 + 0.5 * al); + p2 = p1 * (1.0 + c + c); + p3 = p2 + c / xll; + p4 = p3 + c / xlr; + } + } else if (n == nsave) { + if (np < 30.0) { + goto L_np_small; + } + } + + /*-------------------------- np = n*p >= 30 : ------------------- */ + repeat { + u = igraph_rng_get_unif01(rng) * p4; + v = igraph_rng_get_unif01(rng); + /* triangular region */ + if (u <= p1) { + ix = xm - p1 * v + u; + goto finis; + } + /* parallelogram region */ + if (u <= p2) { + x = xl + (u - p1) / c; + v = v * c + 1.0 - fabs(xm - x) / p1; + if (v > 1.0 || v <= 0.) { + continue; + } + ix = x; + } else { + if (u > p3) { /* right tail */ + ix = xr - log(v) / xlr; + if (ix > n) { + continue; + } + v = v * (u - p3) * xlr; + } else {/* left tail */ + ix = xl + log(v) / xll; + if (ix < 0) { + continue; + } + v = v * (u - p2) * xll; + } + } + /* determine appropriate way to perform accept/reject test */ + k = abs(ix - m); + if (k <= 20 || k >= npq / 2 - 1) { + /* explicit evaluation */ + f = 1.0; + if (m < ix) { + for (i = m + 1; i <= ix; i++) { + f *= (g / i - r); + } + } else if (m != ix) { + for (i = ix + 1; i <= m; i++) { + f /= (g / i - r); + } + } + if (v <= f) { + goto finis; + } + } else { + /* squeezing using upper and lower bounds on log(f(x)) */ + amaxp = (k / npq) * ((k * (k / 3. + 0.625) + 0.1666666666666) / npq + 0.5); + ynorm = -k * k / (2.0 * npq); + alv = log(v); + if (alv < ynorm - amaxp) { + goto finis; + } + if (alv <= ynorm + amaxp) { + /* Stirling's formula to machine accuracy */ + /* for the final acceptance/rejection test */ + x1 = ix + 1; + f1 = fm + 1.0; + z = n + 1 - fm; + w = n - ix + 1.0; + z2 = z * z; + x2 = x1 * x1; + f2 = f1 * f1; + w2 = w * w; + if (alv <= xm * log(f1 / x1) + (n - m + 0.5) * log(z / w) + (ix - m) * log(w * p / (x1 * q)) + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / f2) / f2) / f2) / f2) / f1 / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / z2) / z2) / z2) / z2) / z / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / x2) / x2) / x2) / x2) / x1 / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / w2) / w2) / w2) / w2) / w / 166320.) { + goto finis; + } + } + } + } + +L_np_small: + /*---------------------- np = n*p < 30 : ------------------------- */ + + repeat { + ix = 0; + f = qn; + u = igraph_rng_get_unif01(rng); + repeat { + if (u < f) { + goto finis; + } + if (ix > 110) { + break; + } + u -= f; + ix++; + f *= (g / ix - r); + } + } +finis: + if (psave > 0.5) { + ix = n - ix; + } + return (double)ix; +} + +igraph_real_t igraph_rexp(igraph_rng_t *rng, double rate) { + igraph_real_t scale = 1.0 / rate; + if (!IGRAPH_FINITE(scale) || scale <= 0.0) { + if (scale == 0.0) { + return 0.0; + } + return IGRAPH_NAN; + } + return scale * igraph_exp_rand(rng); +} + +/* + * Mathlib : A C Library of Special Functions + * Copyright (C) 1998 Ross Ihaka + * Copyright (C) 2000 The R Core Team + * Copyright (C) 2003 The R Foundation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, a copy is available at + * http://www.r-project.org/Licenses/ + * + * SYNOPSIS + * + * double dnorm4(double x, double mu, double sigma, int give_log) + * {dnorm (..) is synonymous and preferred inside R} + * + * DESCRIPTION + * + * Compute the density of the normal distribution. + */ + +double igraph_dnorm(double x, double mu, double sigma, int give_log) { +#ifdef IEEE_754 + if (ISNAN(x) || ISNAN(mu) || ISNAN(sigma)) { + return x + mu + sigma; + } +#endif + if (!R_FINITE(sigma)) { + return R_D__0; + } + if (!R_FINITE(x) && mu == x) { + return ML_NAN; /* x-mu is NaN */ + } + if (sigma <= 0) { + if (sigma < 0) { + ML_ERR_return_NAN; + } + /* sigma == 0 */ + return (x == mu) ? ML_POSINF : R_D__0; + } + x = (x - mu) / sigma; + + if (!R_FINITE(x)) { + return R_D__0; + } + return (give_log ? + -(M_LN_SQRT_2PI + 0.5 * x * x + log(sigma)) : + M_1_SQRT_2PI * exp(-0.5 * x * x) / sigma); + /* M_1_SQRT_2PI = 1 / sqrt(2 * pi) */ +} + +/* This is from nmath/rgamma.c */ + +/* + * Mathlib : A C Library of Special Functions + * Copyright (C) 1998 Ross Ihaka + * Copyright (C) 2000--2008 The R Core Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, a copy is available at + * http://www.r-project.org/Licenses/ + * + * SYNOPSIS + * + * #include + * double rgamma(double a, double scale); + * + * DESCRIPTION + * + * Random variates from the gamma distribution. + * + * REFERENCES + * + * [1] Shape parameter a >= 1. Algorithm GD in: + * + * Ahrens, J.H. and Dieter, U. (1982). + * Generating gamma variates by a modified + * rejection technique. + * Comm. ACM, 25, 47-54. + * + * + * [2] Shape parameter 0 < a < 1. Algorithm GS in: + * + * Ahrens, J.H. and Dieter, U. (1974). + * Computer methods for sampling from gamma, beta, + * poisson and binomial distributions. + * Computing, 12, 223-246. + * + * Input: a = parameter (mean) of the standard gamma distribution. + * Output: a variate from the gamma(a)-distribution + */ + +double igraph_rgamma(igraph_rng_t *rng, double a, double scale) { + /* Constants : */ + const static double sqrt32 = 5.656854; + const static double exp_m1 = 0.36787944117144232159;/* exp(-1) = 1/e */ + + /* Coefficients q[k] - for q0 = sum(q[k]*a^(-k)) + * Coefficients a[k] - for q = q0+(t*t/2)*sum(a[k]*v^k) + * Coefficients e[k] - for exp(q)-1 = sum(e[k]*q^k) + */ + const static double q1 = 0.04166669; + const static double q2 = 0.02083148; + const static double q3 = 0.00801191; + const static double q4 = 0.00144121; + const static double q5 = -7.388e-5; + const static double q6 = 2.4511e-4; + const static double q7 = 2.424e-4; + + const static double a1 = 0.3333333; + const static double a2 = -0.250003; + const static double a3 = 0.2000062; + const static double a4 = -0.1662921; + const static double a5 = 0.1423657; + const static double a6 = -0.1367177; + const static double a7 = 0.1233795; + + /* State variables [FIXME for threading!] :*/ + static double aa = 0.; + static double aaa = 0.; + static double s, s2, d; /* no. 1 (step 1) */ + static double q0, b, si, c;/* no. 2 (step 4) */ + + double e, p, q, r, t, u, v, w, x, ret_val; + + if (!R_FINITE(a) || !R_FINITE(scale) || a < 0.0 || scale <= 0.0) { + if (scale == 0.) { + return 0.; + } + ML_ERR_return_NAN; + } + + if (a < 1.) { /* GS algorithm for parameters a < 1 */ + if (a == 0) { + return 0.; + } + e = 1.0 + exp_m1 * a; + repeat { + p = e * igraph_rng_get_unif01(rng); + if (p >= 1.0) { + x = -log((e - p) / a); + if (igraph_exp_rand(rng) >= (1.0 - a) * log(x)) { + break; + } + } else { + x = exp(log(p) / a); + if (igraph_exp_rand(rng) >= x) { + break; + } + } + } + return scale * x; + } + + /* --- a >= 1 : GD algorithm --- */ + + /* Step 1: Recalculations of s2, s, d if a has changed */ + if (a != aa) { + aa = a; + s2 = a - 0.5; + s = sqrt(s2); + d = sqrt32 - s * 12.0; + } + /* Step 2: t = standard normal deviate, + x = (s,1/2) -normal deviate. */ + + /* immediate acceptance (i) */ + t = igraph_norm_rand(rng); + x = s + 0.5 * t; + ret_val = x * x; + if (t >= 0.0) { + return scale * ret_val; + } + + /* Step 3: u = 0,1 - uniform sample. squeeze acceptance (s) */ + u = igraph_rng_get_unif01(rng); + if (d * u <= t * t * t) { + return scale * ret_val; + } + + /* Step 4: recalculations of q0, b, si, c if necessary */ + + if (a != aaa) { + aaa = a; + r = 1.0 / a; + q0 = ((((((q7 * r + q6) * r + q5) * r + q4) * r + q3) * r + + q2) * r + q1) * r; + + /* Approximation depending on size of parameter a */ + /* The constants in the expressions for b, si and c */ + /* were established by numerical experiments */ + + if (a <= 3.686) { + b = 0.463 + s + 0.178 * s2; + si = 1.235; + c = 0.195 / s - 0.079 + 0.16 * s; + } else if (a <= 13.022) { + b = 1.654 + 0.0076 * s2; + si = 1.68 / s + 0.275; + c = 0.062 / s + 0.024; + } else { + b = 1.77; + si = 0.75; + c = 0.1515 / s; + } + } + /* Step 5: no quotient test if x not positive */ + + if (x > 0.0) { + /* Step 6: calculation of v and quotient q */ + v = t / (s + s); + if (fabs(v) <= 0.25) + q = q0 + 0.5 * t * t * ((((((a7 * v + a6) * v + a5) * v + a4) * v + + a3) * v + a2) * v + a1) * v; + else { + q = q0 - s * t + 0.25 * t * t + (s2 + s2) * log(1.0 + v); + } + + + /* Step 7: quotient acceptance (q) */ + if (log(1.0 - u) <= q) { + return scale * ret_val; + } + } + + repeat { + /* Step 8: e = standard exponential deviate + * u = 0,1 -uniform deviate + * t = (b,si)-double exponential (laplace) sample */ + e = igraph_exp_rand(rng); + u = igraph_rng_get_unif01(rng); + u = u + u - 1.0; + if (u < 0.0) { + t = b - si * e; + } else { + t = b + si * e; + } + /* Step 9: rejection if t < tau(1) = -0.71874483771719 */ + if (t >= -0.71874483771719) { + /* Step 10: calculation of v and quotient q */ + v = t / (s + s); + if (fabs(v) <= 0.25) + q = q0 + 0.5 * t * t * + ((((((a7 * v + a6) * v + a5) * v + a4) * v + a3) * v + + a2) * v + a1) * v; + else { + q = q0 - s * t + 0.25 * t * t + (s2 + s2) * log(1.0 + v); + } + /* Step 11: hat acceptance (h) */ + /* (if q not positive go to step 8) */ + if (q > 0.0) { + w = expm1(q); + /* ^^^^^ original code had approximation with rel.err < 2e-7 */ + /* if t is rejected sample again at step 8 */ + if (c * fabs(u) <= w * exp(e - 0.5 * t * t)) { + break; + } + } + } + } /* repeat .. until `t' is accepted */ + x = s + 0.5 * t; + return scale * x * x; +} + +#endif + +int igraph_rng_get_dirichlet(igraph_rng_t *rng, + const igraph_vector_t *alpha, + igraph_vector_t *result) { + + igraph_integer_t len = igraph_vector_size(alpha); + igraph_integer_t j; + igraph_real_t sum = 0.0; + + if (len < 2) { + IGRAPH_ERROR("Dirichlet parameter vector too short, must " + "have at least two entries", IGRAPH_EINVAL); + } + if (igraph_vector_min(alpha) <= 0) { + IGRAPH_ERROR("Dirichlet concentration parameters must be positive", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_resize(result, len)); + + RNG_BEGIN(); + + for (j = 0; j < len; j++) { + VECTOR(*result)[j] = igraph_rng_get_gamma(rng, VECTOR(*alpha)[j], 1.0); + sum += VECTOR(*result)[j]; + } + for (j = 0; j < len; j++) { + VECTOR(*result)[j] /= sum; + } + + RNG_END(); + + return 0; +} + +/********************************************************** + * Testing purposes * + *********************************************************/ + +/* int main() { */ + +/* int i; */ + +/* RNG_BEGIN(); */ + +/* for (i=0; i<1000; i++) { */ +/* printf("%li ", RNG_INTEGER(1,10)); */ +/* } */ +/* printf("\n"); */ + +/* for (i=0; i<1000; i++) { */ +/* printf("%f ", RNG_UNIF(0,1)); */ +/* } */ +/* printf("\n"); */ + +/* for (i=0; i<1000; i++) { */ +/* printf("%f ", RNG_NORMAL(0,5)); */ +/* } */ +/* printf("\n"); */ + +/* RNG_END(); */ + +/* return 0; */ +/* } */ diff --git a/src/random_walk.c b/src/random_walk.c new file mode 100644 index 0000000..71fcd1b --- /dev/null +++ b/src/random_walk.c @@ -0,0 +1,287 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2014 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_paths.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_random.h" +#include "igraph_memory.h" +#include "igraph_interrupt_internal.h" + +/** + * \function igraph_random_walk + * Perform a random walk on a graph + * + * Performs a random walk with a given length on a graph, from the given + * start vertex. Edge directions are (potentially) considered, depending on + * the \p mode argument. + * + * \param graph The input graph, it can be directed or undirected. + * Multiple edges are respected, so are loop edges. + * \param walk An allocated vector, the result is stored here. + * It will be resized as needed. + * \param start The start vertex for the walk. + * \param steps The number of steps to take. If the random walk gets + * stuck, then the \p stuck argument specifies what happens. + * \param mode How to walk along the edges in directed graphs. + * \c IGRAPH_OUT means following edge directions, \c IGRAPH_IN means + * going opposite the edge directions, \c IGRAPH_ALL means ignoring + * edge directions. This argument is ignored for undirected graphs. + * \param stuck What to do if the random walk gets stuck. + * \c IGRAPH_RANDOM_WALK_STUCK_RETURN means that the function returns + * with a shorter walk; \c IGRAPH_RANDOM_WALK_STUCK_ERROR means + * that an error is reported. In both cases \p walk is truncated + * to contain the actual interrupted walk. + * \return Error code. + * + * Time complexity: O(l + d), where \c l is the length of the + * walk, and \c d is the total degree of the visited nodes. + */ + + +int igraph_random_walk(const igraph_t *graph, igraph_vector_t *walk, + igraph_integer_t start, igraph_neimode_t mode, + igraph_integer_t steps, + igraph_random_walk_stuck_t stuck) { + + /* TODO: + - multiple walks potentially from multiple start vertices + - weights + */ + + igraph_lazy_adjlist_t adj; + igraph_integer_t vc = igraph_vcount(graph); + igraph_integer_t i; + + if (start < 0 || start >= vc) { + IGRAPH_ERROR("Invalid start vertex", IGRAPH_EINVAL); + } + if (steps < 0) { + IGRAPH_ERROR("Invalid number of steps", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adj, mode, + IGRAPH_DONT_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adj); + + IGRAPH_CHECK(igraph_vector_resize(walk, steps)); + + RNG_BEGIN(); + + VECTOR(*walk)[0] = start; + for (i = 1; i < steps; i++) { + igraph_vector_t *neis; + igraph_integer_t nn; + neis = igraph_lazy_adjlist_get(&adj, start); + nn = igraph_vector_size(neis); + + if (IGRAPH_UNLIKELY(nn == 0)) { + igraph_vector_resize(walk, i); + if (stuck == IGRAPH_RANDOM_WALK_STUCK_RETURN) { + break; + } else { + IGRAPH_ERROR("Random walk got stuck", IGRAPH_ERWSTUCK); + } + } + start = VECTOR(*walk)[i] = VECTOR(*neis)[ RNG_INTEGER(0, nn - 1) ]; + } + + RNG_END(); + + igraph_lazy_adjlist_destroy(&adj); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + + +/* Used as item destructor for 'cdfs' in igraph_random_edge_walk(). */ +static void vec_destr(igraph_vector_t *vec) { + if (vec != NULL) { + igraph_vector_destroy(vec); + } +} + + +/** + * \function igraph_random_edge_walk + * \brief Perform a random walk on a graph and return the traversed edges + * + * Performs a random walk with a given length on a graph, from the given + * start vertex. Edge directions are (potentially) considered, depending on + * the \p mode argument. + * + * \param graph The input graph, it can be directed or undirected. + * Multiple edges are respected, so are loop edges. + * \param weights A vector of non-negative edge weights. + * It is assumed that at least one strictly positive weight is found among the + * outgoing edges of each vertex. If it is a NULL pointer, all edges are considered + * to have equal weight. + * \param edgewalk An initialized vector; the indices of traversed edges are stored here. + * It will be resized as needed. + * \param start The start vertex for the walk. + * \param steps The number of steps to take. If the random walk gets + * stuck, then the \p stuck argument specifies what happens. + * \param mode How to walk along the edges in directed graphs. + * \c IGRAPH_OUT means following edge directions, \c IGRAPH_IN means + * going opposite the edge directions, \c IGRAPH_ALL means ignoring + * edge directions. This argument is ignored for undirected graphs. + * \param stuck What to do if the random walk gets stuck. + * \c IGRAPH_RANDOM_WALK_STUCK_RETURN means that the function returns + * with a shorter walk; \c IGRAPH_RANDOM_WALK_STUCK_ERROR means + * that an error is reported. In both cases, \p edgewalk is truncated + * to contain the actual interrupted walk. + * + * \return Error code. + * + */ +int igraph_random_edge_walk(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_vector_t *edgewalk, + igraph_integer_t start, igraph_neimode_t mode, + igraph_integer_t steps, + igraph_random_walk_stuck_t stuck) { + igraph_integer_t vc = igraph_vcount(graph); + igraph_integer_t ec = igraph_ecount(graph); + igraph_integer_t i; + igraph_inclist_t il; + igraph_vector_t weight_temp; + igraph_vector_ptr_t cdfs; /* cumulative distribution vectors for each node, used for weighted choice */ + + /* the fourth igraph_neimode_t value, IGRAPH_TOTAL, is disallowed */ + if (! (mode == IGRAPH_ALL || mode == IGRAPH_IN || mode == IGRAPH_OUT)) { + IGRAPH_ERROR("Invalid mode parameter", IGRAPH_EINVMODE); + } + + /* ref switch statement at end of main loop */ + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (start < 0 || start >= vc) { + IGRAPH_ERROR("Invalid start vertex", IGRAPH_EINVAL); + } + + if (steps < 0) { + IGRAPH_ERROR("Invalid number of steps", IGRAPH_EINVAL); + } + + if (weights) { + if (igraph_vector_size(weights) != ec) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + if (igraph_vector_min(weights) < 0) { + IGRAPH_ERROR("Weights must be non-negative", IGRAPH_EINVAL); + } + } + + IGRAPH_CHECK(igraph_vector_resize(edgewalk, steps)); + + IGRAPH_CHECK(igraph_inclist_init(graph, &il, mode)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + + IGRAPH_VECTOR_INIT_FINALLY(&weight_temp, 0); + + /* cdf vectors will be computed lazily */ + IGRAPH_CHECK(igraph_vector_ptr_init(&cdfs, vc)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &cdfs); + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&cdfs, vec_destr); + for (i = 0; i < vc; ++i) { + VECTOR(cdfs)[i] = NULL; + } + + RNG_BEGIN(); + + for (i = 0; i < steps; ++i) { + long degree, edge, idx; + igraph_vector_int_t *edges = igraph_inclist_get(&il, start); + + degree = igraph_vector_int_size(edges); + + /* are we stuck? */ + if (IGRAPH_UNLIKELY(degree == 0)) { + igraph_vector_resize(edgewalk, i); /* can't fail since size is reduced, skip IGRAPH_CHECK */ + if (stuck == IGRAPH_RANDOM_WALK_STUCK_RETURN) { + break; + } else { + IGRAPH_ERROR("Random walk got stuck", IGRAPH_ERWSTUCK); + } + } + + if (weights) { /* weighted: choose an out-edge with probability proportional to its weight */ + igraph_real_t r; + igraph_vector_t **cd = (igraph_vector_t **) & (VECTOR(cdfs)[start]); + + /* compute out-edge cdf for this node if not already done */ + if (IGRAPH_UNLIKELY(! *cd)) { + long j; + + *cd = igraph_malloc(sizeof(igraph_vector_t)); + if (*cd == NULL) { + IGRAPH_ERROR("random edge walk failed", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(*cd, degree)); + + IGRAPH_CHECK(igraph_vector_resize(&weight_temp, degree)); + for (j = 0; j < degree; ++j) { + VECTOR(weight_temp)[j] = VECTOR(*weights)[ VECTOR(*edges)[j] ]; + } + + IGRAPH_CHECK(igraph_vector_cumsum(*cd, &weight_temp)); + } + + r = RNG_UNIF(0, VECTOR( **cd )[degree - 1]); + igraph_vector_binsearch(*cd, r, &idx); + } else { /* unweighted: choose an out-edge at random */ + idx = RNG_INTEGER(0, degree - 1); + } + + edge = VECTOR(*edges)[idx]; + VECTOR(*edgewalk)[i] = edge; + + /* travel along edge in a direction specified by 'mode' */ + /* note: 'mode' is always set to IGRAPH_ALL for undirected graphs */ + switch (mode) { + case IGRAPH_OUT: + start = IGRAPH_TO(graph, edge); + break; + case IGRAPH_IN: + start = IGRAPH_FROM(graph, edge); + break; + case IGRAPH_ALL: + start = IGRAPH_OTHER(graph, edge, start); + break; + } + + IGRAPH_ALLOW_INTERRUPTION(); + } + + RNG_END(); + + igraph_vector_ptr_destroy_all(&cdfs); + igraph_vector_destroy(&weight_temp); + igraph_inclist_destroy(&il); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} diff --git a/src/sbm.c b/src/sbm.c new file mode 100644 index 0000000..d08c9aa --- /dev/null +++ b/src/sbm.c @@ -0,0 +1,607 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph R library. + Copyright (C) 2003-2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_interface.h" +#include "igraph_vector.h" +#include "igraph_matrix.h" +#include "igraph_random.h" +#include "igraph_constructors.h" +#include "igraph_games.h" + +#include /* for DBL_EPSILON */ +#include /* for sqrt */ + +/** + * \function igraph_sbm_game + * Sample from a stochastic block model + * + * This function samples graphs from a stochastic block + * model by (doing the equivalent of) Bernoulli + * trials for each potential edge with the probabilities + * given by the Bernoulli rate matrix, \p pref_matrix. + * See Faust, K., & Wasserman, S. (1992a). Blockmodels: + * Interpretation and evaluation. Social Networks, 14, 5-–61. + * + * + * The order of the vertex ids in the generated graph corresponds to + * the \p block_sizes argument. + * + * \param graph The output graph. + * \param n Number of vertices. + * \param pref_matrix The matrix giving the Bernoulli rates. + * This is a KxK matrix, where K is the number of groups. + * The probability of creating an edge between vertices from + * groups i and j is given by element (i,j). + * \param block_sizes An integer vector giving the number of + * vertices in each group. + * \param directed Boolean, whether to create a directed graph. If + * this argument is false, then \p pref_matrix must be symmetric. + * \param loops Boolean, whether to create self-loops. + * \return Error code. + * + * Time complexity: O(|V|+|E|+K^2), where |V| is the number of + * vertices, |E| is the number of edges, and K is the number of + * groups. + * + * \sa \ref igraph_erdos_renyi_game() for a simple Bernoulli graph. + * + */ + +int igraph_sbm_game(igraph_t *graph, igraph_integer_t n, + const igraph_matrix_t *pref_matrix, + const igraph_vector_int_t *block_sizes, + igraph_bool_t directed, igraph_bool_t loops) { + + int no_blocks = igraph_matrix_nrow(pref_matrix); + int from, to, fromoff = 0; + igraph_real_t minp, maxp; + igraph_vector_t edges; + + /* ------------------------------------------------------------ */ + /* Check arguments */ + /* ------------------------------------------------------------ */ + + if (igraph_matrix_ncol(pref_matrix) != no_blocks) { + IGRAPH_ERROR("Preference matrix is not square", + IGRAPH_NONSQUARE); + } + + igraph_matrix_minmax(pref_matrix, &minp, &maxp); + if (minp < 0 || maxp > 1) { + IGRAPH_ERROR("Connection probabilities must in [0,1]", IGRAPH_EINVAL); + } + + if (n < 0) { + IGRAPH_ERROR("Number of vertices must be non-negative", IGRAPH_EINVAL); + } + + if (!directed && !igraph_matrix_is_symmetric(pref_matrix)) { + IGRAPH_ERROR("Preference matrix must be symmetric for undirected graphs", + IGRAPH_EINVAL); + } + + if (igraph_vector_int_size(block_sizes) != no_blocks) { + IGRAPH_ERROR("Invalid block size vector length", IGRAPH_EINVAL); + } + + if (igraph_vector_int_min(block_sizes) < 0) { + IGRAPH_ERROR("Block size must be non-negative", IGRAPH_EINVAL); + } + + if (igraph_vector_int_sum(block_sizes) != n) { + IGRAPH_ERROR("Block sizes must sum up to number of vertices", + IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + RNG_BEGIN(); + + for (from = 0; from < no_blocks; from++) { + double fromsize = VECTOR(*block_sizes)[from]; + int start = directed ? 0 : from; + int i, tooff = 0; + for (i = 0; i < start; i++) { + tooff += VECTOR(*block_sizes)[i]; + } + for (to = start; to < no_blocks; to++) { + double tosize = VECTOR(*block_sizes)[to]; + igraph_real_t prob = MATRIX(*pref_matrix, from, to); + double maxedges, last = RNG_GEOM(prob); + if (directed && loops) { + maxedges = fromsize * tosize; + while (last < maxedges) { + int vto = floor(last / fromsize); + int vfrom = last - (igraph_real_t)vto * fromsize; + igraph_vector_push_back(&edges, fromoff + vfrom); + igraph_vector_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += 1; + } + } else if (directed && !loops && from != to) { + maxedges = fromsize * tosize; + while (last < maxedges) { + int vto = floor(last / fromsize); + int vfrom = last - (igraph_real_t)vto * fromsize; + igraph_vector_push_back(&edges, fromoff + vfrom); + igraph_vector_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += 1; + } + } else if (directed && !loops && from == to) { + maxedges = fromsize * (fromsize - 1); + while (last < maxedges) { + int vto = floor(last / fromsize); + int vfrom = last - (igraph_real_t)vto * fromsize; + if (vfrom == vto) { + vto = fromsize - 1; + } + igraph_vector_push_back(&edges, fromoff + vfrom); + igraph_vector_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += 1; + } + } else if (!directed && loops && from != to) { + maxedges = fromsize * tosize; + while (last < maxedges) { + int vto = floor(last / fromsize); + int vfrom = last - (igraph_real_t)vto * fromsize; + igraph_vector_push_back(&edges, fromoff + vfrom); + igraph_vector_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += 1; + } + } else if (!directed && loops && from == to) { + maxedges = fromsize * (fromsize + 1) / 2.0; + while (last < maxedges) { + long int vto = floor((sqrt(8 * last + 1) - 1) / 2); + long int vfrom = last - (((igraph_real_t)vto) * (vto + 1)) / 2; + igraph_vector_push_back(&edges, fromoff + vfrom); + igraph_vector_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += 1; + } + } else if (!directed && !loops && from != to) { + maxedges = fromsize * tosize; + while (last < maxedges) { + int vto = floor(last / fromsize); + int vfrom = last - (igraph_real_t)vto * fromsize; + igraph_vector_push_back(&edges, fromoff + vfrom); + igraph_vector_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += 1; + } + } else { /*!directed && !loops && from==to */ + maxedges = fromsize * (fromsize - 1) / 2.0; + while (last < maxedges) { + int vto = floor((sqrt(8 * last + 1) + 1) / 2); + int vfrom = last - (((igraph_real_t)vto) * (vto - 1)) / 2; + igraph_vector_push_back(&edges, fromoff + vfrom); + igraph_vector_push_back(&edges, tooff + vto); + last += RNG_GEOM(prob); + last += 1; + } + } + + tooff += tosize; + } + fromoff += fromsize; + } + + RNG_END(); + + igraph_create(graph, &edges, n, directed); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_hsbm_game + * Hierarchical stochastic block model + * + * The function generates a random graph according to the hierarchical + * stochastic block model. + * + * \param graph The generated graph is stored here. + * \param n The number of vertices in the graph. + * \param m The number of vertices per block. n/m must be integer. + * \param rho The fraction of vertices per cluster, + * within a block. Must sum up to 1, and rho * m must be integer + * for all elements of rho. + * \param C A square, symmetric numeric matrix, the Bernoulli rates for + * the clusters within a block. Its size must mach the size of the + * \code{rho} vector. + * \param p The Bernoulli rate of connections between + * vertices in different blocks. + * \return Error code. + * + * \sa \ref igraph_sbm_game() for the classic stochastic block model, + * \ref igraph_hsbm_list_game() for a more general version. + */ + +int igraph_hsbm_game(igraph_t *graph, igraph_integer_t n, + igraph_integer_t m, const igraph_vector_t *rho, + const igraph_matrix_t *C, igraph_real_t p) { + + int b, i, k = igraph_vector_size(rho); + igraph_vector_t csizes; + igraph_real_t sq_dbl_epsilon = sqrt(DBL_EPSILON); + int no_blocks = n / m; + igraph_vector_t edges; + int offset = 0; + + if (n < 1) { + IGRAPH_ERROR("`n' must be positive for HSBM", IGRAPH_EINVAL); + } + if (m < 1) { + IGRAPH_ERROR("`m' must be positive for HSBM", IGRAPH_EINVAL); + } + if ((long) n % (long) m) { + IGRAPH_ERROR("`n' must be a multiple of `m' for HSBM", IGRAPH_EINVAL); + } + if (!igraph_vector_isininterval(rho, 0, 1)) { + IGRAPH_ERROR("`rho' must be between zero and one for HSBM", + IGRAPH_EINVAL); + } + if (igraph_matrix_min(C) < 0 || igraph_matrix_max(C) > 1) { + IGRAPH_ERROR("`C' must be between zero and one for HSBM", IGRAPH_EINVAL); + } + if (fabs(igraph_vector_sum(rho) - 1.0) > sq_dbl_epsilon) { + IGRAPH_ERROR("`rho' must sum up to 1 for HSBM", IGRAPH_EINVAL); + } + if (igraph_matrix_nrow(C) != k || igraph_matrix_ncol(C) != k) { + IGRAPH_ERROR("`C' dimensions must match `rho' dimensions in HSBM", + IGRAPH_EINVAL); + } + if (!igraph_matrix_is_symmetric(C)) { + IGRAPH_ERROR("`C' must be a symmetric matrix", IGRAPH_EINVAL); + } + if (p < 0 || p > 1) { + IGRAPH_ERROR("`p' must be a probability for HSBM", IGRAPH_EINVAL); + } + for (i = 0; i < k; i++) { + igraph_real_t s = VECTOR(*rho)[i] * m; + if (fabs(round(s) - s) > sq_dbl_epsilon) { + IGRAPH_ERROR("`rho' * `m' is not integer in HSBM", IGRAPH_EINVAL); + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&csizes, k); + for (i = 0; i < k; i++) { + VECTOR(csizes)[i] = round(VECTOR(*rho)[i] * m); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + RNG_BEGIN(); + + /* Block models first */ + + for (b = 0; b < no_blocks; b++) { + int from, to, fromoff = 0; + + for (from = 0; from < k; from++) { + int fromsize = VECTOR(csizes)[from]; + int i, tooff = 0; + for (i = 0; i < from; i++) { + tooff += VECTOR(csizes)[i]; + } + for (to = from; to < k; to++) { + int tosize = VECTOR(csizes)[to]; + igraph_real_t prob = MATRIX(*C, from, to); + igraph_real_t maxedges; + igraph_real_t last = RNG_GEOM(prob); + if (from != to) { + maxedges = fromsize * tosize; + while (last < maxedges) { + int vto = floor(last / fromsize); + int vfrom = last - (igraph_real_t)vto * fromsize; + igraph_vector_push_back(&edges, offset + fromoff + vfrom); + igraph_vector_push_back(&edges, offset + tooff + vto); + last += RNG_GEOM(prob); + last += 1; + } + } else { /* from==to */ + maxedges = fromsize * (fromsize - 1) / 2.0; + while (last < maxedges) { + int vto = floor((sqrt(8 * last + 1) + 1) / 2); + int vfrom = last - (((igraph_real_t)vto) * (vto - 1)) / 2; + igraph_vector_push_back(&edges, offset + fromoff + vfrom); + igraph_vector_push_back(&edges, offset + tooff + vto); + last += RNG_GEOM(prob); + last += 1; + } + } + + tooff += tosize; + } + fromoff += fromsize; + } + + offset += m; + } + + /* And now the rest, if not a special case */ + + if (p == 1) { + int fromoff = 0, tooff = m; + for (b = 0; b < no_blocks; b++) { + igraph_real_t fromsize = m; + igraph_real_t tosize = n - tooff; + int from, to; + for (from = 0; from < fromsize; from++) { + for (to = 0; to < tosize; to++) { + igraph_vector_push_back(&edges, fromoff + from); + igraph_vector_push_back(&edges, tooff + to); + } + } + fromoff += m; + tooff += m; + } + } else if (p > 0) { + int fromoff = 0, tooff = m; + for (b = 0; b < no_blocks; b++) { + igraph_real_t fromsize = m; + igraph_real_t tosize = n - tooff; + igraph_real_t maxedges = fromsize * tosize; + igraph_real_t last = RNG_GEOM(p); + while (last < maxedges) { + int vto = floor(last / fromsize); + int vfrom = last - (igraph_real_t) vto * fromsize; + igraph_vector_push_back(&edges, fromoff + vfrom); + igraph_vector_push_back(&edges, tooff + vto); + last += RNG_GEOM(p); + last += 1; + } + + fromoff += m; + tooff += m; + } + } + + RNG_END(); + + igraph_create(graph, &edges, n, /*directed=*/ 0); + + igraph_vector_destroy(&edges); + igraph_vector_destroy(&csizes); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_hsbm_list_game + * Hierarchical stochastic block model, more general version + * + * The function generates a random graph according to the hierarchical + * stochastic block model. + * + * \param graph The generated graph is stored here. + * \param n The number of vertices in the graph. + * \param mlist An integer vector of block sizes. + * \param rholist A list of rho vectors (\c igraph_vector_t objects), one + * for each block. + * \param Clist A list of square matrices (\c igraph_matrix_t objects), + * one for each block, giving the Bernoulli rates of connections + * within the block. + * \param p The Bernoulli rate of connections between + * vertices in different blocks. + * \return Error code. + * + * \sa \ref igraph_sbm_game() for the classic stochastic block model, + * \ref igraph_hsbm_game() for a simpler general version. + */ + +int igraph_hsbm_list_game(igraph_t *graph, igraph_integer_t n, + const igraph_vector_int_t *mlist, + const igraph_vector_ptr_t *rholist, + const igraph_vector_ptr_t *Clist, + igraph_real_t p) { + + int i, no_blocks = igraph_vector_ptr_size(rholist); + igraph_real_t sq_dbl_epsilon = sqrt(DBL_EPSILON); + igraph_vector_t csizes, edges; + int b, offset = 0; + + if (n < 1) { + IGRAPH_ERROR("`n' must be positive for HSBM", IGRAPH_EINVAL); + } + if (no_blocks == 0) { + IGRAPH_ERROR("`rholist' empty for HSBM", IGRAPH_EINVAL); + } + if (igraph_vector_ptr_size(Clist) != no_blocks && + igraph_vector_int_size(mlist) != no_blocks) { + IGRAPH_ERROR("`rholist' must have same length as `Clist' and `m' " + "for HSBM", IGRAPH_EINVAL); + } + if (p < 0 || p > 1) { + IGRAPH_ERROR("`p' must be a probability for HSBM", IGRAPH_EINVAL); + } + /* Checks for m's */ + if (igraph_vector_int_sum(mlist) != n) { + IGRAPH_ERROR("`m' must sum up to `n' for HSBM", IGRAPH_EINVAL); + } + if (igraph_vector_int_min(mlist) < 1) { + IGRAPH_ERROR("`m' must be positive for HSBM", IGRAPH_EINVAL); + } + /* Checks for the rhos */ + for (i = 0; i < no_blocks; i++) { + const igraph_vector_t *rho = VECTOR(*rholist)[i]; + if (!igraph_vector_isininterval(rho, 0, 1)) { + IGRAPH_ERROR("`rho' must be between zero and one for HSBM", + IGRAPH_EINVAL); + } + if (fabs(igraph_vector_sum(rho) - 1.0) > sq_dbl_epsilon) { + IGRAPH_ERROR("`rho' must sum up to 1 for HSBM", IGRAPH_EINVAL); + } + } + /* Checks for the Cs */ + for (i = 0; i < no_blocks; i++) { + const igraph_matrix_t *C = VECTOR(*Clist)[i]; + if (igraph_matrix_min(C) < 0 || igraph_matrix_max(C) > 1) { + IGRAPH_ERROR("`C' must be between zero and one for HSBM", + IGRAPH_EINVAL); + } + if (!igraph_matrix_is_symmetric(C)) { + IGRAPH_ERROR("`C' must be a symmetric matrix", IGRAPH_EINVAL); + } + } + /* Check that C and rho sizes match */ + for (i = 0; i < no_blocks; i++) { + const igraph_vector_t *rho = VECTOR(*rholist)[i]; + const igraph_matrix_t *C = VECTOR(*Clist)[i]; + int k = igraph_vector_size(rho); + if (igraph_matrix_nrow(C) != k || igraph_matrix_ncol(C) != k) { + IGRAPH_ERROR("`C' dimensions must match `rho' dimensions in HSBM", + IGRAPH_EINVAL); + } + } + /* Check that rho * m is integer */ + for (i = 0; i < no_blocks; i++) { + const igraph_vector_t *rho = VECTOR(*rholist)[i]; + igraph_real_t m = VECTOR(*mlist)[i]; + int j, k = igraph_vector_size(rho); + for (j = 0; j < k; j++) { + igraph_real_t s = VECTOR(*rho)[j] * m; + if (fabs(round(s) - s) > sq_dbl_epsilon) { + IGRAPH_ERROR("`rho' * `m' is not integer in HSBM", IGRAPH_EINVAL); + } + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&csizes, 0); + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + RNG_BEGIN(); + + /* Block models first */ + + for (b = 0; b < no_blocks; b++) { + int from, to, fromoff = 0; + const igraph_vector_t *rho = VECTOR(*rholist)[b]; + const igraph_matrix_t *C = VECTOR(*Clist)[b]; + igraph_real_t m = VECTOR(*mlist)[b]; + int k = igraph_vector_size(rho); + + igraph_vector_resize(&csizes, k); + for (i = 0; i < k; i++) { + VECTOR(csizes)[i] = round(VECTOR(*rho)[i] * m); + } + + for (from = 0; from < k; from++) { + int fromsize = VECTOR(csizes)[from]; + int i, tooff = 0; + for (i = 0; i < from; i++) { + tooff += VECTOR(csizes)[i]; + } + for (to = from; to < k; to++) { + int tosize = VECTOR(csizes)[to]; + igraph_real_t prob = MATRIX(*C, from, to); + igraph_real_t maxedges; + igraph_real_t last = RNG_GEOM(prob); + if (from != to) { + maxedges = fromsize * tosize; + while (last < maxedges) { + int vto = floor(last / fromsize); + int vfrom = last - (igraph_real_t)vto * fromsize; + igraph_vector_push_back(&edges, offset + fromoff + vfrom); + igraph_vector_push_back(&edges, offset + tooff + vto); + last += RNG_GEOM(prob); + last += 1; + } + } else { /* from==to */ + maxedges = fromsize * (fromsize - 1) / 2.0; + while (last < maxedges) { + int vto = floor((sqrt(8 * last + 1) + 1) / 2); + int vfrom = last - (((igraph_real_t)vto) * (vto - 1)) / 2; + igraph_vector_push_back(&edges, offset + fromoff + vfrom); + igraph_vector_push_back(&edges, offset + tooff + vto); + last += RNG_GEOM(prob); + last += 1; + } + } + + tooff += tosize; + } + fromoff += fromsize; + } + + offset += m; + } + + /* And now the rest, if not a special case */ + + if (p == 1) { + int fromoff = 0, tooff = VECTOR(*mlist)[0]; + for (b = 0; b < no_blocks; b++) { + igraph_real_t fromsize = VECTOR(*mlist)[b]; + igraph_real_t tosize = n - tooff; + int from, to; + for (from = 0; from < fromsize; from++) { + for (to = 0; to < tosize; to++) { + igraph_vector_push_back(&edges, fromoff + from); + igraph_vector_push_back(&edges, tooff + to); + } + } + fromoff += fromsize; + if (b + 1 < no_blocks) { + tooff += VECTOR(*mlist)[b + 1]; + } + } + } else if (p > 0) { + int fromoff = 0, tooff = VECTOR(*mlist)[0]; + for (b = 0; b < no_blocks; b++) { + igraph_real_t fromsize = VECTOR(*mlist)[b]; + igraph_real_t tosize = n - tooff; + igraph_real_t maxedges = fromsize * tosize; + igraph_real_t last = RNG_GEOM(p); + while (last < maxedges) { + int vto = floor(last / fromsize); + int vfrom = last - (igraph_real_t) vto * fromsize; + igraph_vector_push_back(&edges, fromoff + vfrom); + igraph_vector_push_back(&edges, tooff + vto); + last += RNG_GEOM(p); + last += 1; + } + + fromoff += fromsize; + if (b + 1 < no_blocks) { + tooff += VECTOR(*mlist)[b + 1]; + } + } + } + + RNG_END(); + + igraph_create(graph, &edges, n, /*directed=*/ 0); + + igraph_vector_destroy(&edges); + igraph_vector_destroy(&csizes); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} diff --git a/src/scan.c b/src/scan.c new file mode 100644 index 0000000..594aa47 --- /dev/null +++ b/src/scan.c @@ -0,0 +1,880 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2013 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_scan.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include "igraph_memory.h" +#include "igraph_interrupt_internal.h" +#include "igraph_arpack.h" +#include "igraph_eigen.h" +#include "igraph_centrality.h" +#include "igraph_operators.h" +#include "igraph_dqueue.h" +#include "igraph_stack.h" + +/** + * \section about_local_scan + * + * + * The scan statistic is a summary of the locality statistics that is computed + * from the local neighborhood of each vertex. For details, see + * Priebe, C. E., Conroy, J. M., Marchette, D. J., Park, Y. (2005). + * Scan Statistics on Enron Graphs. Computational and Mathematical Organization Theory. + * + */ + +/** + * \function igraph_local_scan_0 + * Local scan-statistics, k=0 + * + * K=0 scan-statistics is arbitrarily defined as the vertex degree for + * unweighted, and the vertex strength for weighted graphs. See \ref + * igraph_degree() and \ref igraph_strength(). + * + * \param graph The input graph + * \param res An initialized vector, the results are stored here. + * \param weights Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + */ + +int igraph_local_scan_0(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + if (weights) { + igraph_strength(graph, res, igraph_vss_all(), mode, /*loops=*/ 1, + weights); + } else { + igraph_degree(graph, res, igraph_vss_all(), mode, /*loops=*/ 1); + } + return 0; +} + +/* From triangles.c */ +/* TODO add to private header */ +int igraph_i_trans4_al_simplify(igraph_adjlist_t *al, + const igraph_vector_int_t *rank); + +/* This removes loop, multiple edges and edges that point + "backwards" according to the rank vector. It works on + edge lists */ + +static int igraph_i_trans4_il_simplify(const igraph_t *graph, igraph_inclist_t *il, + const igraph_vector_int_t *rank) { + + long int i; + long int n = il->length; + igraph_vector_int_t mark; + igraph_vector_int_init(&mark, n); + IGRAPH_FINALLY(igraph_vector_int_destroy, &mark); + + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &il->incs[i]; + int j, l = igraph_vector_int_size(v); + int irank = VECTOR(*rank)[i]; + VECTOR(mark)[i] = i + 1; + for (j = 0; j < l; /* nothing */) { + long int edge = (long int) VECTOR(*v)[j]; + long int e = IGRAPH_OTHER(graph, edge, i); + if (VECTOR(*rank)[e] > irank && VECTOR(mark)[e] != i + 1) { + VECTOR(mark)[e] = i + 1; + j++; + } else { + VECTOR(*v)[j] = igraph_vector_int_tail(v); + igraph_vector_int_pop_back(v); + l--; + } + } + } + + igraph_vector_int_destroy(&mark); + IGRAPH_FINALLY_CLEAN(1); + return 0; + +} + +/* This one handles both weighted and unweighted cases */ + +static int igraph_i_local_scan_1_directed(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + int no_of_nodes = igraph_vcount(graph); + igraph_inclist_t incs; + int i, node; + + igraph_vector_int_t neis; + + IGRAPH_CHECK(igraph_inclist_init(graph, &incs, mode)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs); + + igraph_vector_int_init(&neis, no_of_nodes); + IGRAPH_FINALLY(igraph_vector_int_destroy, &neis); + + igraph_vector_resize(res, no_of_nodes); + igraph_vector_null(res); + + for (node = 0; node < no_of_nodes; node++) { + igraph_vector_int_t *edges1 = igraph_inclist_get(&incs, node); + int edgeslen1 = igraph_vector_int_size(edges1); + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Mark neighbors and self*/ + VECTOR(neis)[node] = node + 1; + for (i = 0; i < edgeslen1; i++) { + int e = VECTOR(*edges1)[i]; + int nei = IGRAPH_OTHER(graph, e, node); + igraph_real_t w = weights ? VECTOR(*weights)[e] : 1; + VECTOR(neis)[nei] = node + 1; + VECTOR(*res)[node] += w; + } + + /* Crawl neighbors */ + for (i = 0; i < edgeslen1; i++) { + int e2 = VECTOR(*edges1)[i]; + int nei = IGRAPH_OTHER(graph, e2, node); + igraph_vector_int_t *edges2 = igraph_inclist_get(&incs, nei); + int j, edgeslen2 = igraph_vector_int_size(edges2); + for (j = 0; j < edgeslen2; j++) { + int e2 = VECTOR(*edges2)[j]; + int nei2 = IGRAPH_OTHER(graph, e2, nei); + igraph_real_t w2 = weights ? VECTOR(*weights)[e2] : 1; + if (VECTOR(neis)[nei2] == node + 1) { + VECTOR(*res)[node] += w2; + } + } + } + + } /* node < no_of_nodes */ + + igraph_vector_int_destroy(&neis); + igraph_inclist_destroy(&incs); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +static int igraph_i_local_scan_1_directed_all(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights) { + + int no_of_nodes = igraph_vcount(graph); + igraph_inclist_t incs; + int i, node; + + igraph_vector_int_t neis; + + IGRAPH_CHECK(igraph_inclist_init(graph, &incs, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs); + + igraph_vector_int_init(&neis, no_of_nodes); + IGRAPH_FINALLY(igraph_vector_int_destroy, &neis); + + igraph_vector_resize(res, no_of_nodes); + igraph_vector_null(res); + + for (node = 0; node < no_of_nodes; node++) { + igraph_vector_int_t *edges1 = igraph_inclist_get(&incs, node); + int edgeslen1 = igraph_vector_int_size(edges1); + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Mark neighbors. We also count the edges that are incident to ego. + Note that this time we do not mark ego, because we don't want to + double count its incident edges later, when we are going over the + incident edges of ego's neighbors. */ + for (i = 0; i < edgeslen1; i++) { + int e = VECTOR(*edges1)[i]; + int nei = IGRAPH_OTHER(graph, e, node); + igraph_real_t w = weights ? VECTOR(*weights)[e] : 1; + VECTOR(neis)[nei] = node + 1; + VECTOR(*res)[node] += w; + } + + /* Crawl neighbors. We make sure that each neighbor of 'node' is + only crawed once. We count all qualifying edges of ego, and + then unmark ego to avoid double counting. */ + for (i = 0; i < edgeslen1; i++) { + int e2 = VECTOR(*edges1)[i]; + int nei = IGRAPH_OTHER(graph, e2, node); + igraph_vector_int_t *edges2; + int j, edgeslen2; + if (VECTOR(neis)[nei] != node + 1) { + continue; + } + edges2 = igraph_inclist_get(&incs, nei); + edgeslen2 = igraph_vector_int_size(edges2); + for (j = 0; j < edgeslen2; j++) { + int e2 = VECTOR(*edges2)[j]; + int nei2 = IGRAPH_OTHER(graph, e2, nei); + igraph_real_t w2 = weights ? VECTOR(*weights)[e2] : 1; + if (VECTOR(neis)[nei2] == node + 1) { + VECTOR(*res)[node] += w2; + } + } + VECTOR(neis)[nei] = 0; + } + + } /* node < no_of_nodes */ + + igraph_vector_int_destroy(&neis); + igraph_inclist_destroy(&incs); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +static int igraph_i_local_scan_1_sumweights(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights) { + + long int no_of_nodes = igraph_vcount(graph); + long int node, i, j, nn; + igraph_inclist_t allinc; + igraph_vector_int_t *neis1, *neis2; + long int neilen1, neilen2; + long int *neis; + long int maxdegree; + + igraph_vector_int_t order; + igraph_vector_int_t rank; + igraph_vector_t degree, *edge1 = °ree; /* reuse degree as edge1 */ + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + igraph_vector_int_init(&order, no_of_nodes); + IGRAPH_FINALLY(igraph_vector_int_destroy, &order); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS)); + maxdegree = (long int) igraph_vector_max(°ree) + 1; + igraph_vector_order1_int(°ree, &order, maxdegree); + igraph_vector_int_init(&rank, no_of_nodes); + IGRAPH_FINALLY(igraph_vector_int_destroy, &rank); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(rank)[ VECTOR(order)[i] ] = no_of_nodes - i - 1; + } + + IGRAPH_CHECK(igraph_inclist_init(graph, &allinc, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &allinc); + IGRAPH_CHECK(igraph_i_trans4_il_simplify(graph, &allinc, &rank)); + + neis = igraph_Calloc(no_of_nodes, long int); + if (neis == 0) { + IGRAPH_ERROR("undirected local transitivity failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, neis); + + IGRAPH_CHECK(igraph_strength(graph, res, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS, weights)); + + for (nn = no_of_nodes - 1; nn >= 0; nn--) { + node = VECTOR(order)[nn]; + + IGRAPH_ALLOW_INTERRUPTION(); + + neis1 = igraph_inclist_get(&allinc, node); + neilen1 = igraph_vector_int_size(neis1); + + /* Mark the neighbors of the node */ + for (i = 0; i < neilen1; i++) { + int edge = VECTOR(*neis1)[i]; + int nei = IGRAPH_OTHER(graph, edge, node); + VECTOR(*edge1)[nei] = VECTOR(*weights)[edge]; + neis[nei] = node + 1; + } + + for (i = 0; i < neilen1; i++) { + long int edge = VECTOR(*neis1)[i]; + long int nei = IGRAPH_OTHER(graph, edge, node); + igraph_real_t w = VECTOR(*weights)[edge]; + neis2 = igraph_inclist_get(&allinc, nei); + neilen2 = igraph_vector_int_size(neis2); + for (j = 0; j < neilen2; j++) { + long int edge2 = VECTOR(*neis2)[j]; + long int nei2 = IGRAPH_OTHER(graph, edge2, nei); + igraph_real_t w2 = VECTOR(*weights)[edge2]; + if (neis[nei2] == node + 1) { + VECTOR(*res)[node] += w2; + VECTOR(*res)[nei2] += w; + VECTOR(*res)[nei] += VECTOR(*edge1)[nei2]; + } + } + } + } + + igraph_free(neis); + igraph_inclist_destroy(&allinc); + igraph_vector_int_destroy(&rank); + igraph_vector_destroy(°ree); + igraph_vector_int_destroy(&order); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +/** + * \function igraph_local_scan_1_ecount + * Local scan-statistics, k=1, edge count and sum of weights + * + * Count the number of edges or the sum the edge weights in the + * 1-neighborhood of vertices. + * + * \param graph The input graph + * \param res An initialized vector, the results are stored here. + * \param weights Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + */ + +int igraph_local_scan_1_ecount(const igraph_t *graph, igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + if (igraph_is_directed(graph)) { + if (mode != IGRAPH_ALL) { + return igraph_i_local_scan_1_directed(graph, res, weights, mode); + } else { + return igraph_i_local_scan_1_directed_all(graph, res, weights); + } + } else { + if (weights) { + return igraph_i_local_scan_1_sumweights(graph, res, weights); + } else { + +#define TRIEDGES +#include "triangles_template.h" +#undef TRIEDGES + + } + } + + return 0; +} + +static int igraph_i_local_scan_0_them_w(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode) { + + igraph_t is; + igraph_vector_t map2; + int i, m; + + if (!weights_them) { + IGRAPH_ERROR("Edge weights not given for weighted scan-0", + IGRAPH_EINVAL); + } + if (igraph_vector_size(weights_them) != igraph_ecount(them)) { + IGRAPH_ERROR("Invalid weights length for scan-0", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&map2, 0); + igraph_intersection(&is, us, them, /*map1=*/ 0, &map2); + IGRAPH_FINALLY(igraph_destroy, &is); + + /* Rewrite the map as edge weights */ + m = igraph_vector_size(&map2); + for (i = 0; i < m; i++) { + VECTOR(map2)[i] = VECTOR(*weights_them)[ (int) VECTOR(map2)[i] ]; + } + + igraph_strength(&is, res, igraph_vss_all(), mode, IGRAPH_LOOPS, + /*weights=*/ &map2); + + igraph_destroy(&is); + igraph_vector_destroy(&map2); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_local_scan_0_them + * Local THEM scan-statistics, k=0 + * + * K=0 scan-statistics is arbitrarily defined as the vertex degree for + * unweighted, and the vertex strength for weighted graphs. See \ref + * igraph_degree() and \ref igraph_strength(). + * + * \param us The input graph, to use to extract the neighborhoods. + * \param them The input graph to use for the actually counting. + * \param res An initialized vector, the results are stored here. + * \param weights_them Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + */ + +int igraph_local_scan_0_them(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode) { + + igraph_t is; + + if (igraph_vcount(us) != igraph_vcount(them)) { + IGRAPH_ERROR("Number of vertices don't match in scan-0", IGRAPH_EINVAL); + } + if (igraph_is_directed(us) != igraph_is_directed(them)) { + IGRAPH_ERROR("Directedness don't match in scan-0", IGRAPH_EINVAL); + } + + if (weights_them) { + return igraph_i_local_scan_0_them_w(us, them, res, weights_them, mode); + } + + igraph_intersection(&is, us, them, /*edgemap1=*/ 0, /*edgemap2=*/ 0); + IGRAPH_FINALLY(igraph_destroy, &is); + + igraph_degree(&is, res, igraph_vss_all(), mode, IGRAPH_LOOPS); + + igraph_destroy(&is); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_local_scan_1_ecount_them + * Local THEM scan-statistics, k=1, edge count and sum of weights + * + * Count the number of edges or the sum the edge weights in the + * 1-neighborhood of vertices. + * + * \param us The input graph to extract the neighborhoods. + * \param them The input graph to perform the counting. + * \param weights_them Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + * \sa \ref igraph_local_scan_1_ecount() for the US statistics. + */ + +int igraph_local_scan_1_ecount_them(const igraph_t *us, const igraph_t *them, + igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode) { + + int no_of_nodes = igraph_vcount(us); + igraph_adjlist_t adj_us; + igraph_inclist_t incs_them; + igraph_vector_int_t neis; + int node; + + if (igraph_vcount(them) != no_of_nodes) { + IGRAPH_ERROR("Number of vertices must match in scan-1", IGRAPH_EINVAL); + } + if (igraph_is_directed(us) != igraph_is_directed(them)) { + IGRAPH_ERROR("Directedness must match in scan-1", IGRAPH_EINVAL); + } + if (weights_them && + igraph_vector_size(weights_them) != igraph_ecount(them)) { + IGRAPH_ERROR("Invalid weight vector length in scan-1 (them)", + IGRAPH_EINVAL); + } + + igraph_adjlist_init(us, &adj_us, mode); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adj_us); + igraph_adjlist_simplify(&adj_us); + igraph_inclist_init(them, &incs_them, mode); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs_them); + + igraph_vector_int_init(&neis, no_of_nodes); + IGRAPH_FINALLY(igraph_vector_int_destroy, &neis); + + igraph_vector_resize(res, no_of_nodes); + igraph_vector_null(res); + + for (node = 0; node < no_of_nodes; node++) { + igraph_vector_int_t *neis_us = igraph_adjlist_get(&adj_us, node); + igraph_vector_int_t *edges1_them = igraph_inclist_get(&incs_them, node); + int len1_us = igraph_vector_int_size(neis_us); + int len1_them = igraph_vector_int_size(edges1_them); + int i; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* Mark neighbors and self in us */ + VECTOR(neis)[node] = node + 1; + for (i = 0; i < len1_us; i++) { + int nei = VECTOR(*neis_us)[i]; + VECTOR(neis)[nei] = node + 1; + } + + /* Crawl neighbors in them, first ego */ + for (i = 0; i < len1_them; i++) { + int e = VECTOR(*edges1_them)[i]; + int nei = IGRAPH_OTHER(them, e, node); + if (VECTOR(neis)[nei] == node + 1) { + igraph_real_t w = weights_them ? VECTOR(*weights_them)[e] : 1; + VECTOR(*res)[node] += w; + } + } + /* Then the rest */ + for (i = 0; i < len1_us; i++) { + int nei = VECTOR(*neis_us)[i]; + igraph_vector_int_t *edges2_them = igraph_inclist_get(&incs_them, nei); + int j, len2_them = igraph_vector_int_size(edges2_them); + for (j = 0; j < len2_them; j++) { + int e2 = VECTOR(*edges2_them)[j]; + int nei2 = IGRAPH_OTHER(them, e2, nei); + if (VECTOR(neis)[nei2] == node + 1) { + igraph_real_t w = weights_them ? VECTOR(*weights_them)[e2] : 1; + VECTOR(*res)[node] += w; + } + } + } + + /* For undirected, it was double counted */ + if (mode == IGRAPH_ALL || ! igraph_is_directed(us)) { + VECTOR(*res)[node] /= 2.0; + } + + } /* node < no_of_nodes */ + + igraph_vector_int_destroy(&neis); + igraph_inclist_destroy(&incs_them); + igraph_adjlist_destroy(&adj_us); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_local_scan_k_ecount + * Local scan-statistics, general function, edge count and sum of weights + * + * Count the number of edges or the sum the edge weights in the + * k-neighborhood of vertices. + * + * \param graph The input graph + * \param k The size of the neighborhood, non-negative integer. + * The k=0 case is special, see \ref igraph_local_scan_0(). + * \param res An initialized vector, the results are stored here. + * \param weights Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + */ + +int igraph_local_scan_k_ecount(const igraph_t *graph, int k, + igraph_vector_t *res, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + int no_of_nodes = igraph_vcount(graph); + int node; + igraph_dqueue_int_t Q; + igraph_vector_int_t marked; + igraph_inclist_t incs; + + if (k < 0) { + IGRAPH_ERROR("k must be non-negative in k-scan", IGRAPH_EINVAL); + } + if (weights && igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length in k-scan", IGRAPH_EINVAL); + } + + if (k == 0) { + return igraph_local_scan_0(graph, res, weights, mode); + } + if (k == 1) { + return igraph_local_scan_1_ecount(graph, res, weights, mode); + } + + /* We do a BFS form each node, and simply count the number + of edges on the way */ + + IGRAPH_CHECK(igraph_dqueue_int_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &Q); + IGRAPH_CHECK(igraph_vector_int_init(&marked, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &marked); + IGRAPH_CHECK(igraph_inclist_init(graph, &incs, mode)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + + for (node = 0 ; node < no_of_nodes ; node++) { + igraph_dqueue_int_push(&Q, node); + igraph_dqueue_int_push(&Q, 0); + VECTOR(marked)[node] = node + 1; + while (!igraph_dqueue_int_empty(&Q)) { + int act = igraph_dqueue_int_pop(&Q); + int dist = igraph_dqueue_int_pop(&Q) + 1; + igraph_vector_int_t *edges = igraph_inclist_get(&incs, act); + int i, edgeslen = igraph_vector_int_size(edges); + for (i = 0; i < edgeslen; i++) { + int edge = VECTOR(*edges)[i]; + int nei = IGRAPH_OTHER(graph, edge, act); + if (dist <= k || VECTOR(marked)[nei] == node + 1) { + igraph_real_t w = weights ? VECTOR(*weights)[edge] : 1; + VECTOR(*res)[node] += w; + } + if (dist <= k && VECTOR(marked)[nei] != node + 1) { + igraph_dqueue_int_push(&Q, nei); + igraph_dqueue_int_push(&Q, dist); + VECTOR(marked)[nei] = node + 1; + } + } + } + + if (mode == IGRAPH_ALL || ! igraph_is_directed(graph)) { + VECTOR(*res)[node] /= 2.0; + } + + } /* node < no_of_nodes */ + + igraph_inclist_destroy(&incs); + igraph_vector_int_destroy(&marked); + igraph_dqueue_int_destroy(&Q); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_local_scan_k_ecount_them + * Local THEM scan-statistics, general function, edge count and sum of weights + * + * Count the number of edges or the sum the edge weights in the + * k-neighborhood of vertices. + * + * \param us The input graph to extract the neighborhoods. + * \param them The input graph to perform the counting. + * \param k The size of the neighborhood, non-negative integer. + * The k=0 case is special, see \ref igraph_local_scan_0_them(). + * \param weights_them Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param mode Type of the neighborhood, \c IGRAPH_OUT means outgoing, + * \c IGRAPH_IN means incoming and \c IGRAPH_ALL means all edges. + * \return Error code. + * + * \sa \ref igraph_local_scan_1_ecount() for the US statistics. + */ + +int igraph_local_scan_k_ecount_them(const igraph_t *us, const igraph_t *them, + int k, igraph_vector_t *res, + const igraph_vector_t *weights_them, + igraph_neimode_t mode) { + + int no_of_nodes = igraph_vcount(us); + int node; + igraph_dqueue_int_t Q; + igraph_vector_int_t marked; + igraph_stack_int_t ST; + igraph_inclist_t incs_us, incs_them; + + if (igraph_vcount(them) != no_of_nodes) { + IGRAPH_ERROR("Number of vertices must match in scan-k", IGRAPH_EINVAL); + } + if (igraph_is_directed(us) != igraph_is_directed(them)) { + IGRAPH_ERROR("Directedness must match in scan-k", IGRAPH_EINVAL); + } + if (k < 0) { + IGRAPH_ERROR("k must be non-negative in k-scan", IGRAPH_EINVAL); + } + if (weights_them && + igraph_vector_size(weights_them) != igraph_ecount(them)) { + IGRAPH_ERROR("Invalid weight vector length in k-scan (them)", + IGRAPH_EINVAL); + } + + if (k == 0) { + return igraph_local_scan_0_them(us, them, res, weights_them, mode); + } + if (k == 1) { + return igraph_local_scan_1_ecount_them(us, them, res, weights_them, mode); + } + + /* We mark the nodes in US in a BFS. Then we check the outgoing edges + of all marked nodes in THEM. */ + + IGRAPH_CHECK(igraph_dqueue_int_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_int_destroy, &Q); + IGRAPH_CHECK(igraph_vector_int_init(&marked, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &marked); + IGRAPH_CHECK(igraph_inclist_init(us, &incs_us, mode)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs_us); + IGRAPH_CHECK(igraph_inclist_init(them, &incs_them, mode)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs_them); + IGRAPH_CHECK(igraph_stack_int_init(&ST, 100)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &ST); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + + for (node = 0; node < no_of_nodes; node++) { + + /* BFS to mark the nodes in US */ + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, node)); + IGRAPH_CHECK(igraph_dqueue_int_push(&Q, 0)); + IGRAPH_CHECK(igraph_stack_int_push(&ST, node)); + VECTOR(marked)[node] = node + 1; + while (!igraph_dqueue_int_empty(&Q)) { + int act = igraph_dqueue_int_pop(&Q); + int dist = igraph_dqueue_int_pop(&Q) + 1; + igraph_vector_int_t *edges = igraph_inclist_get(&incs_us, act); + int i, edgeslen = igraph_vector_int_size(edges); + for (i = 0; i < edgeslen; i++) { + int edge = VECTOR(*edges)[i]; + int nei = IGRAPH_OTHER(us, edge, act); + if (dist <= k && VECTOR(marked)[nei] != node + 1) { + igraph_dqueue_int_push(&Q, nei); + igraph_dqueue_int_push(&Q, dist); + VECTOR(marked)[nei] = node + 1; + igraph_stack_int_push(&ST, nei); + } + } + } + + /* Now check the edges of all nodes in THEM */ + while (!igraph_stack_int_empty(&ST)) { + int act = igraph_stack_int_pop(&ST); + igraph_vector_int_t *edges = igraph_inclist_get(&incs_them, act); + int i, edgeslen = igraph_vector_int_size(edges); + for (i = 0; i < edgeslen; i++) { + int edge = VECTOR(*edges)[i]; + int nei = IGRAPH_OTHER(them, edge, act); + if (VECTOR(marked)[nei] == node + 1) { + igraph_real_t w = weights_them ? VECTOR(*weights_them)[edge] : 1; + VECTOR(*res)[node] += w; + } + } + } + + if (mode == IGRAPH_ALL || ! igraph_is_directed(us)) { + VECTOR(*res)[node] /= 2; + } + + } /* node < no_of_nodes */ + + igraph_stack_int_destroy(&ST); + igraph_inclist_destroy(&incs_them); + igraph_inclist_destroy(&incs_us); + igraph_vector_int_destroy(&marked); + igraph_dqueue_int_destroy(&Q); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +/** + * \function igraph_local_scan_neighborhood_ecount + * Local scan-statistics with pre-calculated neighborhoods + * + * Count the number of edges, or sum the edge weigths in + * neighborhoods given as a parameter. + * + * \param graph The graph to perform the counting/summing in. + * \param res Initialized vector, the result is stored here. + * \param weights Weight vector for weighted graphs, null pointer for + * unweighted graphs. + * \param neighborhoods List of igraph_vector_int_t + * objects, the neighborhoods, one for each vertex in the + * graph. + * \return Error code. + */ + +int igraph_local_scan_neighborhood_ecount(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vector_t *weights, + const igraph_vector_ptr_t *neighborhoods) { + + int node, no_of_nodes = igraph_vcount(graph); + igraph_inclist_t incs; + igraph_vector_int_t marked; + igraph_bool_t directed = igraph_is_directed(graph); + + if (weights && igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length in local scan", IGRAPH_EINVAL); + } + if (igraph_vector_ptr_size(neighborhoods) != no_of_nodes) { + IGRAPH_ERROR("Invalid neighborhood list length in local scan", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_int_init(&marked, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &marked); + IGRAPH_CHECK(igraph_inclist_init(graph, &incs, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incs); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + + for (node = 0; node < no_of_nodes; node++) { + igraph_vector_int_t *nei = VECTOR(*neighborhoods)[node]; + int i, neilen = igraph_vector_int_size(nei); + VECTOR(marked)[node] = node + 1; + for (i = 0; i < neilen; i++) { + int vertex = VECTOR(*nei)[i]; + if (vertex < 0 || vertex >= no_of_nodes) { + IGRAPH_ERROR("Invalid vertex id in neighborhood list in local scan", + IGRAPH_EINVAL); + } + VECTOR(marked)[vertex] = node + 1; + } + + for (i = 0; i < neilen; i++) { + int vertex = VECTOR(*nei)[i]; + igraph_vector_int_t *edges = igraph_inclist_get(&incs, vertex); + int j, edgeslen = igraph_vector_int_size(edges); + for (j = 0; j < edgeslen; j++) { + int edge = VECTOR(*edges)[j]; + int nei2 = IGRAPH_OTHER(graph, edge, vertex); + if (VECTOR(marked)[nei2] == node + 1) { + igraph_real_t w = weights ? VECTOR(*weights)[edge] : 1; + VECTOR(*res)[node] += w; + } + } + } + if (!directed) { + VECTOR(*res)[node] /= 2.0; + } + } + + igraph_inclist_destroy(&incs); + igraph_vector_int_destroy(&marked); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} diff --git a/src/scg.c b/src/scg.c new file mode 100644 index 0000000..64a2340 --- /dev/null +++ b/src/scg.c @@ -0,0 +1,2293 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2011-12 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + * SCGlib : A C library for the spectral coarse graining of matrices + * as described in the paper: Shrinking Matrices while preserving their + * eigenpairs with Application to the Spectral Coarse Graining of Graphs. + * Preprint available at + * + * Copyright (C) 2008 David Morton de Lachapelle + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * DESCRIPTION + * ----------- + * The grouping function takes as argument 'nev' eigenvectors and + * and tries to minimize the eigenpair shifts induced by the coarse + * graining (Section 5 of the above reference). The eigenvectors are + * stored in a 'nev'x'n' matrix 'v'. + * The 'algo' parameter can take the following values + * 1 -> Optimal method (sec. 5.3.1) + * 2 -> Intervals+k-means (sec. 5.3.3) + * 3 -> Intervals (sec. 5.3.2) + * 4 -> Exact SCG (sec. 5.4.1--last paragraph) + * 'nt' is a vector of length 'nev' giving either the size of the + * partitions (if algo = 1) or the number of intervals to cut the + * eigenvectors if algo = 2 or algo = 3. When algo = 4 this parameter + * is ignored. 'maxiter' fixes the maximum number of iterations of + * the k-means algorithm, and is only considered when algo = 2. + * All the algorithms try to find a minimizing partition of + * ||v_i-Pv_i|| where P is a problem-specific projector and v_i denotes + * the eigenvectors stored in v. The final partition is worked out + * as decribed in Method 1 of Section 5.4.2. + * 'matrix' provides the type of SCG (i.e. the form of P). So far, + * the options are those described in section 6, that is: + * 1 -> Symmetric (sec. 6.1) + * 2 -> Laplacian (sec. 6.2) + * 3 -> Stochastic (sec. 6.3) + * In the stochastic case, a valid distribution probability 'p' must be + * provided. In all other cases, 'p' is ignored and can be set to NULL. + * The group labels in the final partition are given in 'gr' as positive + * consecutive integers starting from 0. + */ + +#include "igraph_scg.h" +#include "igraph_eigen.h" +#include "igraph_interface.h" +#include "igraph_structural.h" +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_memory.h" + +#include "scg_headers.h" + +#include "math.h" + +/** + * \section about_scg + * + * + * The SCG functions provide a framework, called Spectral Coarse Graining + * (SCG), for reducing large graphs while preserving their + * spectral-related features, that is features + * closely related with the eigenvalues and eigenvectors of a graph + * matrix (which for now can be the adjacency, the stochastic, or the + * Laplacian matrix). + * + * + * + * Common examples of such features comprise the first-passage-time of + * random walkers on Markovian graphs, thermodynamic properties of + * lattice models in statistical physics (e.g. Ising model), and the + * epidemic threshold of epidemic network models (SIR and SIS models). + * + * + * + * SCG differs from traditional clustering schemes by producing a + * coarse-grained graph (not just a partition of + * the vertices), representative of the original one. As shown in [1], + * Principal Component Analysis can be viewed as a particular SCG, + * called exact SCG, where the matrix to be + * coarse-grained is the covariance matrix of some data set. + * + * + * + * SCG should be of interest to practitioners of various + * fields dealing with problems where matrix eigenpairs play an important + * role, as for instance is the case of dynamical processes on networks. + * + * + *
    SCG in brief + * + * The main idea of SCG is to operate on a matrix a shrinkage operation + * specifically designed to preserve some of the matrix eigenpairs while + * not altering other important matrix features (such as its structure). + * Mathematically, this idea was expressed as follows. Consider a + * (complex) n x n matrix M and form the product + *
    + * M'=LMR*, + *
    + * where n' < n and L, R are from C[n'xn]} and are such + * that LR*=I[n'] (R* denotes the conjugate transpose of R). Under + * these assumptions, it can be shown that P=R*L is an n'-rank + * projector and that, if (lambda, v) is a (right) + * eigenpair of M (i.e. Mv=lambda v} and P is orthogonal, there exists + * an eigenvalue lambda' of M' such that + *
    + * |lambda-lambda'| <= const ||e[P](v)|| + * [1+O(||e[P](v)||2)], + *
    + * where ||e[P](v)||=||v-Pv||. Hence, if P (or equivalently + * L, R) is chosen so as to make ||e[P](v)|| as small as possible, one + * can preserve to any desired level the original eigenvalue + * lambda in the coarse-grained matrix M'; + * under extra assumptions on M, this result can be generalized to + * eigenvectors [1]. This leads to the following generic definition of a + * SCG problem. + *
    + * + * + * Given M (C[nxn]) and (lambda, v), a (right) eigenpair of M to be + * preserved by the coarse graining, the problem is to find a projector + * P' solving + *
    + * min(||e[P](v)||, p in Omega), + *
    + * where Omega is a set of projectors in C[nxn] described by some + * ad hoc constraints c[1], ..., c[r] + * (e.g. c[1]: P in R[nxn], c[2]: P=t(P), c[3]: P[i,j] >= 0}, etc). + *
    + * + * + * Choosing pertinent constraints to solve the SCG problem is of great + * importance in applications. For instance, in the absence of + * constraints the SCG problem is solved trivially by + * P'=vv* (v is assumed normalized). We have designed a particular + * constraint, called homogeneous mixing, which + * ensures that vertices belonging to the same group are merged + * consistently from a physical point of view (see [1] for + * details). Under this constraint the SCG problem reduces to finding + * the partition of 1, ..., n (labeling the original vertices) + * minimizing + *
    + * ||e[P](v)||2 = + * sum([v(i)-(Pv)(i)]2; + * alpha=1,...,n', i in alpha), + *
    + * where alpha denotes a group (i.e. a block) in a partition of + * {1, ..., n}, and |alpha| is the number of elements in alpha. + *
    + * + * + * If M is symmetric or stochastic, for instance, then it may be + * desirable (or mandatory) to choose L, R so that M' is symmetric or + * stochastic as well. This structural constraint + * has led to the construction of particular semi-projectors for + * symmetric [1], stochastic [3] and Laplacian [2] matrices, that are + * made available. + * + * + * + * In short, the coarse graining of matrices and graphs involves: + * \olist + * \oli Retrieving a matrix or a graph matrix M from the + * problem. + * \oli Computing the eigenpairs of M to be preserved in the + * coarse-grained graph or matrix. + * \oli Setting some problem-specific constraints (e.g. dimension of + * the coarse-grained object). + * \oli Solving the constrained SCG problem, that is finding P'. + * \oli Computing from P' two semi-projectors L' and R' + * (e.g. following the method proposed in [1]). + * \oli Working out the product M'=L'MR'* and, if needed, defining + * from M' a coarse-grained graph. + * \endolist + * + *
    + * + *
    Functions for performing SCG + * + * The main functions are \ref igraph_scg_adjacency(), \ref + * igraph_scg_laplacian() and \ref igraph_scg_stochastic(). + * These functions handle all the steps involved in the + * Spectral Coarse Graining (SCG) of some particular matrices and graphs + * as described above and in reference [1]. In more details, + * they compute some prescribed eigenpairs of a matrix or a + * graph matrix, (for now adjacency, Laplacian and stochastic matrices are + * available), work out an optimal partition to preserve the eigenpairs, + * and finally output a coarse-grained matrix or graph along with other + * useful information. + * + * + * + * These steps can also be carried out independently: (1) Use + * \ref igraph_get_adjacency(), \ref igraph_get_sparsemat(), + * \ref igraph_laplacian(), \ref igraph_get_stochastic() or \ref + * igraph_get_stochastic_sparsemat() to compute a matrix M. + * (2) Work out some prescribed eigenpairs of M e.g. by + * means of \ref igraph_arpack_rssolve() or \ref + * igraph_arpack_rnsolve(). (3) Invoke one the four + * algorithms of the function \ref igraph_scg_grouping() to get a + * partition that will preserve the eigenpairs in the coarse-grained + * matrix. (4) Compute the semi-projectors L and R using + * \ref igraph_scg_semiprojectors() and from there the coarse-grained + * matrix M'=LMR*. If necessary, construct a coarse-grained graph from + * M' (e.g. as in [1]). + * + *
    + * + *
    References + * + * [1] D. Morton de Lachapelle, D. Gfeller, and P. De Los Rios, + * Shrinking Matrices while Preserving their Eigenpairs with Application + * to the Spectral Coarse Graining of Graphs. Submitted to + * SIAM Journal on Matrix Analysis and + * Applications, 2008. + * http://people.epfl.ch/david.morton + * + * + * [2] D. Gfeller, and P. De Los Rios, Spectral Coarse Graining and + * Synchronization in Oscillator Networks. + * Physical Review Letters, + * 100(17), 2008. + * http://arxiv.org/abs/0708.2055 + * + * + * [3] D. Gfeller, and P. De Los Rios, Spectral Coarse Graining of Complex + * Networks, Physical Review Letters, + * 99(3), 2007. + * http://arxiv.org/abs/0706.0812 + * + *
    + */ + +/** + * \function igraph_scg_grouping + * \brief SCG problem solver + * + * This function solves the Spectral Coarse Graining (SCG) problem; + * either exactly, or approximately but faster. + * + *
    + * The algorithm \c IGRAPH_SCG_OPTIMUM solves exactly the SCG problem + * for each eigenvector in \p V. The running time of this algorithm is + * O(max(nt) m^2) for the symmetric and laplacian matrix problems + * It is O(m^3) for the stochastic problem. Here m is the number + * of rows in \p V. In all three cases, the memory usage is O(m^2). + * + * + * The algorithms \c IGRAPH_SCG_INTERV and \c IGRAPH_SCG_INTERV_KM solve + * approximately the SCG problem by performing a (for now) constant + * binning of the components of the eigenvectors, that is \p nt + * VECTOR(nt_vec)[i]) constant-size bins are used to + * partition V[,i]. When \p algo is \c + * IGRAPH_SCG_INTERV_KM, the (Lloyd) k-means algorithm is + * run on each partition obtained by \c IGRAPH_SCG_INTERV to improve + * accuracy. + * + * + * Once a minimizing partition (either exact or approximate) has been + * found for each eigenvector, the final grouping is worked out as + * follows: two vertices are grouped together in the final partition if + * they are grouped together in each minimizing partition. In general the + * size of the final partition is not known in advance when the number + * of columns in \p V is larger than one. + * + * + * Finally, the algorithm \c IGRAPH_SCG_EXACT groups the vertices with + * equal components in each eigenvector. The last three algorithms + * essentially have linear running time and memory load. + * + * \param V The matrix of eigenvectors to be preserved by coarse + * graining, each column is an eigenvector. + * \param groups Pointer to an initialized vector, the result of the + * SCG is stored here. + * \param nt Positive integer. When \p algo is \c IGRAPH_SCG_OPTIMUM, + * it gives the number of groups to partition each eigenvector + * separately. When \p algo is \c IGRAPH_SCG_INTERV or \c + * IGRAPH_SCG_INTERV_KM, it gives the number of intervals to + * partition each eigenvector. This is ignored when \p algo is \c + * IGRAPH_SCG_EXACT. + * \param nt_vec A numeric vector of length one or the length must + * match the number of eigenvectors given in \p V, or a \c NULL + * pointer. If not \c NULL, then this argument gives the number of + * groups or intervals, and \p nt is ignored. Different number of + * groups or intervals can be specified for each eigenvector. + * \param mtype The type of semi-projectors used in the SCG. Possible + * values are \c IGRAPH_SCG_SYMMETRIC, \c IGRAPH_SCG_STOCHASTIC and + * \c IGRAPH_SCG_LAPLACIAN. + * \param algo The algorithm to solve the SCG problem. Possible + * values: \c IGRAPH_SCG_OPTIMUM, \c IGRAPH_SCG_INTERV_KM, \c + * IGRAPH_SCG_INTERV and \c IGRAPH_SCG_EXACT. Please see the + * details about them above. + * \param p A probability vector, or \c NULL. This argument must be + * given if \p mtype is \c IGRAPH_SCG_STOCHASTIC, but it is ignored + * otherwise. For the stochastic case it gives the stationary + * probability distribution of a Markov chain, the one specified by + * the graph/matrix under study. + * \param maxiter A positive integer giving the number of iterations + * of the k-means algorithm when \p algo is \c + * IGRAPH_SCG_INTERV_KM. It is ignored in other cases. A reasonable + * (initial) value for this argument is 100. + * \return Error code. + * + * Time complexity: see description above. + * + * \sa \ref igraph_scg_adjacency(), \ref igraph_scg_laplacian(), \ref + * igraph_scg_stochastic(). + * + * \example examples/simple/igraph_scg_grouping.c + * \example examples/simple/igraph_scg_grouping2.c + * \example examples/simple/igraph_scg_grouping3.c + * \example examples/simple/igraph_scg_grouping4.c + */ + +int igraph_scg_grouping(const igraph_matrix_t *V, + igraph_vector_t *groups, + igraph_integer_t nt, + const igraph_vector_t *nt_vec, + igraph_scg_matrix_t mtype, + igraph_scg_algorithm_t algo, + const igraph_vector_t *p, + igraph_integer_t maxiter) { + + int no_of_nodes = (int) igraph_matrix_nrow(V); + int nev = (int) igraph_matrix_ncol(V); + igraph_matrix_int_t gr_mat; + int i; + + if (nt_vec && igraph_vector_size(nt_vec) != 1 && + igraph_vector_size(nt_vec) != nev) { + IGRAPH_ERROR("Invalid length for interval specification", IGRAPH_EINVAL); + } + if (nt_vec && igraph_vector_size(nt_vec) == 1) { + nt = (igraph_integer_t) VECTOR(*nt_vec)[0]; + nt_vec = 0; + } + + if (!nt_vec && algo != IGRAPH_SCG_EXACT) { + if (nt <= 1 || nt >= no_of_nodes) { + IGRAPH_ERROR("Invalid interval specification", IGRAPH_EINVAL); + } + } else if (algo != IGRAPH_SCG_EXACT) { + igraph_real_t min, max; + igraph_vector_minmax(nt_vec, &min, &max); + if (min <= 1 || max >= no_of_nodes) { + IGRAPH_ERROR("Invalid interval specification", IGRAPH_EINVAL); + } + } + + if (mtype == IGRAPH_SCG_STOCHASTIC && !p) { + IGRAPH_ERROR("`p' must be given for the stochastic matrix case", + IGRAPH_EINVAL); + } + + if (p && igraph_vector_size(p) != no_of_nodes) { + IGRAPH_ERROR("Invalid `p' vector size", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_resize(groups, no_of_nodes)); + +#define INVEC(i) (nt_vec ? VECTOR(*nt_vec)[i] : nt) + + IGRAPH_CHECK(igraph_matrix_int_init(&gr_mat, no_of_nodes, nev)); + IGRAPH_FINALLY(igraph_matrix_int_destroy, &gr_mat); + + switch (algo) { + case IGRAPH_SCG_OPTIMUM: + for (i = 0; i < nev; i++) { + IGRAPH_CHECK(igraph_i_optimal_partition(&MATRIX(*V, 0, i), + &MATRIX(gr_mat, 0, i), + no_of_nodes, (int) INVEC(i), + mtype, + p ? VECTOR(*p) : 0, 0)); + } + break; + case IGRAPH_SCG_INTERV_KM: + for (i = 0; i < nev; i++) { + igraph_vector_t tmpv; + igraph_vector_view(&tmpv, &MATRIX(*V, 0, i), no_of_nodes); + IGRAPH_CHECK(igraph_i_intervals_plus_kmeans(&tmpv, + &MATRIX(gr_mat, 0, i), + no_of_nodes, (int) INVEC(i), + maxiter)); + } + break; + case IGRAPH_SCG_INTERV: + for (i = 0; i < nev; i++) { + igraph_vector_t tmpv; + igraph_vector_view(&tmpv, &MATRIX(*V, 0, i), no_of_nodes); + IGRAPH_CHECK(igraph_i_intervals_method(&tmpv, + &MATRIX(gr_mat, 0, i), + no_of_nodes, (int) INVEC(i))); + } + break; + case IGRAPH_SCG_EXACT: + for (i = 0; i < nev; i++) { + IGRAPH_CHECK(igraph_i_exact_coarse_graining(&MATRIX(*V, 0, i), + &MATRIX(gr_mat, 0, i), + no_of_nodes)); + } + break; + } + +#undef INVEC + + if (nev == 1) { + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*groups)[i] = MATRIX(gr_mat, i, 0); + } + } else { + igraph_i_scg_groups_t *g = igraph_Calloc(no_of_nodes, + igraph_i_scg_groups_t); + int gr_nb = 0; + + IGRAPH_CHECK(igraph_matrix_int_transpose(&gr_mat)); + for (i = 0; i < no_of_nodes; i++) { + g[i].ind = i; + g[i].n = nev; + g[i].gr = &MATRIX(gr_mat, 0, i); + } + + qsort(g, (size_t) no_of_nodes, sizeof(igraph_i_scg_groups_t), + igraph_i_compare_groups); + VECTOR(*groups)[g[0].ind] = gr_nb; + for (i = 1; i < no_of_nodes; i++) { + if (igraph_i_compare_groups(&g[i], &g[i - 1]) != 0) { + gr_nb++; + } + VECTOR(*groups)[g[i].ind] = gr_nb; + } + igraph_Free(g); + } + + igraph_matrix_int_destroy(&gr_mat); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_scg_semiprojectors_sym(const igraph_vector_t *groups, + igraph_matrix_t *L, + igraph_matrix_t *R, + igraph_sparsemat_t *Lsparse, + igraph_sparsemat_t *Rsparse, + int no_of_groups, + int no_of_nodes) { + + igraph_vector_t tab; + int i; + + IGRAPH_VECTOR_INIT_FINALLY(&tab, no_of_groups); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(tab)[ (int) VECTOR(*groups)[i] ] += 1; + } + for (i = 0; i < no_of_groups; i++) { + VECTOR(tab)[i] = sqrt(VECTOR(tab)[i]); + } + + if (L) { + IGRAPH_CHECK(igraph_matrix_resize(L, no_of_groups, no_of_nodes)); + igraph_matrix_null(L); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + MATRIX(*L, g, i) = 1 / VECTOR(tab)[g]; + } + } + + if (R) { + if (L) { + IGRAPH_CHECK(igraph_matrix_update(R, L)); + } else { + IGRAPH_CHECK(igraph_matrix_resize(R, no_of_groups, no_of_nodes)); + igraph_matrix_null(R); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + MATRIX(*R, g, i) = 1 / VECTOR(tab)[g]; + } + } + } + + if (Lsparse) { + IGRAPH_CHECK(igraph_sparsemat_init(Lsparse, no_of_groups, no_of_nodes, + /* nzmax= */ no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + IGRAPH_CHECK(igraph_sparsemat_entry(Lsparse, g, i, 1 / VECTOR(tab)[g])); + } + } + + if (Rsparse) { + IGRAPH_CHECK(igraph_sparsemat_init(Rsparse, no_of_groups, no_of_nodes, + /* nzmax= */ no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + IGRAPH_CHECK(igraph_sparsemat_entry(Rsparse, g, i, 1 / VECTOR(tab)[g])); + } + } + + igraph_vector_destroy(&tab); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_scg_semiprojectors_lap(const igraph_vector_t *groups, + igraph_matrix_t *L, + igraph_matrix_t *R, + igraph_sparsemat_t *Lsparse, + igraph_sparsemat_t *Rsparse, + int no_of_groups, + int no_of_nodes, + igraph_scg_norm_t norm) { + + igraph_vector_t tab; + int i; + + IGRAPH_VECTOR_INIT_FINALLY(&tab, no_of_groups); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(tab)[ (int) VECTOR(*groups)[i] ] += 1; + } + for (i = 0; i < no_of_groups; i++) { + VECTOR(tab)[i] = VECTOR(tab)[i]; + } + + if (norm == IGRAPH_SCG_NORM_ROW) { + if (L) { + IGRAPH_CHECK(igraph_matrix_resize(L, no_of_groups, no_of_nodes)); + igraph_matrix_null(L); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + MATRIX(*L, g, i) = 1.0 / VECTOR(tab)[g]; + } + } + if (R) { + IGRAPH_CHECK(igraph_matrix_resize(R, no_of_groups, no_of_nodes)); + igraph_matrix_null(R); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + MATRIX(*R, g, i) = 1.0; + } + } + if (Lsparse) { + IGRAPH_CHECK(igraph_sparsemat_init(Lsparse, no_of_groups, no_of_nodes, + /* nzmax= */ no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + IGRAPH_CHECK(igraph_sparsemat_entry(Lsparse, g, i, + 1.0 / VECTOR(tab)[g])); + } + } + if (Rsparse) { + IGRAPH_CHECK(igraph_sparsemat_init(Rsparse, no_of_groups, no_of_nodes, + /* nzmax= */ no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + IGRAPH_CHECK(igraph_sparsemat_entry(Rsparse, g, i, 1.0)); + } + } + } else { + if (L) { + IGRAPH_CHECK(igraph_matrix_resize(L, no_of_groups, no_of_nodes)); + igraph_matrix_null(L); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + MATRIX(*L, g, i) = 1.0; + } + } + if (R) { + IGRAPH_CHECK(igraph_matrix_resize(R, no_of_groups, no_of_nodes)); + igraph_matrix_null(R); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + MATRIX(*R, g, i) = 1.0 / VECTOR(tab)[g]; + } + } + if (Lsparse) { + IGRAPH_CHECK(igraph_sparsemat_init(Lsparse, no_of_groups, no_of_nodes, + /* nzmax= */ no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + IGRAPH_CHECK(igraph_sparsemat_entry(Lsparse, g, i, 1.0)); + } + } + if (Rsparse) { + IGRAPH_CHECK(igraph_sparsemat_init(Rsparse, no_of_groups, no_of_nodes, + /* nzmax= */ no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + IGRAPH_CHECK(igraph_sparsemat_entry(Rsparse, g, i, + 1.0 / VECTOR(tab)[g])); + } + } + + } + + igraph_vector_destroy(&tab); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_scg_semiprojectors_sto(const igraph_vector_t *groups, + igraph_matrix_t *L, + igraph_matrix_t *R, + igraph_sparsemat_t *Lsparse, + igraph_sparsemat_t *Rsparse, + int no_of_groups, + int no_of_nodes, + const igraph_vector_t *p, + igraph_scg_norm_t norm) { + + igraph_vector_t pgr, pnormed; + int i; + + IGRAPH_VECTOR_INIT_FINALLY(&pgr, no_of_groups); + IGRAPH_VECTOR_INIT_FINALLY(&pnormed, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + VECTOR(pgr)[g] += VECTOR(*p)[i]; + } + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + VECTOR(pnormed)[i] = VECTOR(*p)[i] / VECTOR(pgr)[g]; + } + + if (norm == IGRAPH_SCG_NORM_ROW) { + if (L) { + IGRAPH_CHECK(igraph_matrix_resize(L, no_of_groups, no_of_nodes)); + igraph_matrix_null(L); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + MATRIX(*L, g, i) = VECTOR(pnormed)[i]; + } + } + if (R) { + IGRAPH_CHECK(igraph_matrix_resize(R, no_of_groups, no_of_nodes)); + igraph_matrix_null(R); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + MATRIX(*R, g, i) = 1.0; + } + } + if (Lsparse) { + IGRAPH_CHECK(igraph_sparsemat_init(Lsparse, no_of_groups, no_of_nodes, + /* nzmax= */ no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + IGRAPH_CHECK(igraph_sparsemat_entry(Lsparse, g, i, + VECTOR(pnormed)[i])); + } + } + if (Rsparse) { + IGRAPH_CHECK(igraph_sparsemat_init(Rsparse, no_of_groups, no_of_nodes, + /* nzmax= */ no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + IGRAPH_CHECK(igraph_sparsemat_entry(Rsparse, g, i, 1.0)); + } + } + } else { + if (L) { + IGRAPH_CHECK(igraph_matrix_resize(L, no_of_groups, no_of_nodes)); + igraph_matrix_null(L); + for (i = 0; i < no_of_nodes; i++) { + int g = (int ) VECTOR(*groups)[i]; + MATRIX(*L, g, i) = 1.0; + } + } + if (R) { + IGRAPH_CHECK(igraph_matrix_resize(R, no_of_groups, no_of_nodes)); + igraph_matrix_null(R); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + MATRIX(*R, g, i) = VECTOR(pnormed)[i]; + } + } + if (Lsparse) { + IGRAPH_CHECK(igraph_sparsemat_init(Lsparse, no_of_groups, no_of_nodes, + /* nzmax= */ no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + IGRAPH_CHECK(igraph_sparsemat_entry(Lsparse, g, i, 1.0)); + } + } + if (Rsparse) { + IGRAPH_CHECK(igraph_sparsemat_init(Rsparse, no_of_groups, no_of_nodes, + /* nzmax= */ no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + int g = (int) VECTOR(*groups)[i]; + IGRAPH_CHECK(igraph_sparsemat_entry(Rsparse, g, i, + VECTOR(pnormed)[i])); + } + } + } + + + igraph_vector_destroy(&pnormed); + igraph_vector_destroy(&pgr); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_scg_semiprojectors + * \brief Compute SCG semi-projectors for a given partition + * + * The three types of semi-projectors are defined as follows. + * Let gamma(j) label the group of vertex j in a partition of all the + * vertices. + * + * + * The symmetric semi-projectors are defined as + *
    + * L[alpha,j] = R[alpha,j] = 1/sqrt(|alpha|) delta[alpha,gamma(j)], + *
    + * the (row) Laplacian semi-projectors as + *
    + * L[alpha,j] = 1/|alpha| delta[alpha,gamma(j)] + *
    + * and + *
    + * R[alpha,j] = delta[alpha,gamma(j)], + *
    + * and the (row) stochastic semi-projectors as + *
    + * L[alpha,j] = p[1][j] / sum(p[1][k]; k in gamma(j)) + * delta[alpha,gamma(j)] + *
    + * and + *
    + * R[alpha,j] = delta[alpha,gamma(j)], + *
    + * where p[1] is the (left) eigenvector associated with the + * one-eigenvalue of the stochastic matrix. L and R are + * defined in a symmetric way when \p norm is \c + * IGRAPH_SCG_NORM_COL. All these semi-projectors verify various + * properties described in the reference. + * \param groups A vector of integers, giving the group label of every + * vertex in the partition. Group labels should start at zero and + * should be sequential. + * \param mtype The type of semi-projectors. For now \c + * IGRAPH_SCG_SYMMETRIC, \c IGRAPH_SCG_STOCHASTIC and \c + * IGRAP_SCG_LAPLACIAN are supported. + * \param L If not a \c NULL pointer, then it must be a pointer to + * an initialized matrix. The left semi-projector is stored here. + * \param R If not a \c NULL pointer, then it must be a pointer to + * an initialized matrix. The right semi-projector is stored here. + * \param Lsparse If not a \c NULL pointer, then it must be a pointer + * to an uninitialized sparse matrix. The left semi-projector is + * stored here. + * \param Rsparse If not a \c NULL pointer, then it must be a pointer + * to an uninitialized sparse matrix. The right semi-projector is + * stored here. + * \param p \c NULL, or a probability vector of the same length as \p + * groups. \p p is the stationary probability distribution of a + * Markov chain when \p mtype is \c IGRAPH_SCG_STOCHASTIC. This + * argument is ignored in all other cases. + * \param norm Either \c IGRAPH_SCG_NORM_ROW or \c IGRAPH_SCG_NORM_COL. + * Specifies whether the rows or the columns of the Laplacian + * matrix sum up to zero, or whether the rows or the columns of the + * stochastic matrix sum up to one. + * \return Error code. + * + * Time complexity: TODO. + * + * \sa \ref igraph_scg_adjacency(), \ref igraph_scg_stochastic() and + * \ref igraph_scg_laplacian(), \ref igraph_scg_grouping(). + * + * \example examples/simple/igraph_scg_semiprojectors.c + * \example examples/simple/igraph_scg_semiprojectors2.c + * \example examples/simple/igraph_scg_semiprojectors3.c + */ + +int igraph_scg_semiprojectors(const igraph_vector_t *groups, + igraph_scg_matrix_t mtype, + igraph_matrix_t *L, + igraph_matrix_t *R, + igraph_sparsemat_t *Lsparse, + igraph_sparsemat_t *Rsparse, + const igraph_vector_t *p, + igraph_scg_norm_t norm) { + + int no_of_nodes = (int) igraph_vector_size(groups); + int no_of_groups; + igraph_real_t min, max; + + igraph_vector_minmax(groups, &min, &max); + no_of_groups = (int) max + 1; + + if (min < 0 || max >= no_of_nodes) { + IGRAPH_ERROR("Invalid membership vector", IGRAPH_EINVAL); + } + + if (mtype == IGRAPH_SCG_STOCHASTIC && !p) { + IGRAPH_ERROR("`p' must be given for the stochastic matrix case", + IGRAPH_EINVAL); + } + + if (p && igraph_vector_size(p) != no_of_nodes) { + IGRAPH_ERROR("Invalid `p' vector length, should match number of vertices", + IGRAPH_EINVAL); + } + + switch (mtype) { + case IGRAPH_SCG_SYMMETRIC: + IGRAPH_CHECK(igraph_i_scg_semiprojectors_sym(groups, L, R, Lsparse, + Rsparse, no_of_groups, + no_of_nodes)); + break; + + case IGRAPH_SCG_LAPLACIAN: + IGRAPH_CHECK(igraph_i_scg_semiprojectors_lap(groups, L, R, Lsparse, + Rsparse, no_of_groups, + no_of_nodes, norm)); + break; + + case IGRAPH_SCG_STOCHASTIC: + IGRAPH_CHECK(igraph_i_scg_semiprojectors_sto(groups, L, R, Lsparse, + Rsparse, no_of_groups, + no_of_nodes, p, norm)); + break; + } + + return 0; +} + +/** + * \function igraph_scg_norm_eps + * Calculate SCG residuals + * + * Computes |v[i]-Pv[i]|, where v[i] is the i-th eigenvector in \p V + * and P is the projector corresponding to the \p mtype argument. + * + * \param V The matrix of eigenvectors to be preserved by coarse + * graining, each column is an eigenvector. + * \param groups A vector of integers, giving the group label of every + * vertex in the partition. Group labels should start at zero and + * should be sequential. + * \param eps Pointer to a real value, the result is stored here. + * \param mtype The type of semi-projectors. For now \c + * IGRAPH_SCG_SYMMETRIC, \c IGRAPH_SCG_STOCHASTIC and \c + * IGRAP_SCG_LAPLACIAN are supported. + * \param p \c NULL, or a probability vector of the same length as \p + * groups. \p p is the stationary probability distribution of a + * Markov chain when \p mtype is \c IGRAPH_SCG_STOCHASTIC. This + * argument is ignored in all other cases. + * \param norm Either \c IGRAPH_SCG_NORM_ROW or \c IGRAPH_SCG_NORM_COL. + * Specifies whether the rows or the columns of the Laplacian + * matrix sum up to zero, or whether the rows or the columns of the + * stochastic matrix sum up to one. + * \return Error code. + * + * Time complexity: TODO. + * + * \sa \ref igraph_scg_adjacency(), \ref igraph_scg_stochastic() and + * \ref igraph_scg_laplacian(), \ref igraph_scg_grouping(), \ref + * igraph_scg_semiprojectors(). + */ + +int igraph_scg_norm_eps(const igraph_matrix_t *V, + const igraph_vector_t *groups, + igraph_vector_t *eps, + igraph_scg_matrix_t mtype, + const igraph_vector_t *p, + igraph_scg_norm_t norm) { + + int no_of_nodes = (int) igraph_vector_size(groups); + int no_of_groups; + int no_of_vectors = (int) igraph_matrix_ncol(V); + igraph_real_t min, max; + igraph_sparsemat_t Lsparse, Rsparse, Lsparse2, Rsparse2, Rsparse3, proj; + igraph_vector_t x, res; + int k, i; + + if (igraph_matrix_nrow(V) != no_of_nodes) { + IGRAPH_ERROR("Eigenvector length and group vector length do not match", + IGRAPH_EINVAL); + } + + igraph_vector_minmax(groups, &min, &max); + no_of_groups = (int) max + 1; + + if (min < 0 || max >= no_of_nodes) { + IGRAPH_ERROR("Invalid membership vector", IGRAPH_EINVAL); + } + + if (mtype == IGRAPH_SCG_STOCHASTIC && !p) { + IGRAPH_ERROR("`p' must be given for the stochastic matrix case", + IGRAPH_EINVAL); + } + + if (p && igraph_vector_size(p) != no_of_nodes) { + IGRAPH_ERROR("Invalid `p' vector length, should match number of vertices", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_scg_semiprojectors(groups, mtype, /* L= */ 0, + /* R= */ 0, &Lsparse, &Rsparse, p, + norm)); + + IGRAPH_FINALLY(igraph_sparsemat_destroy, &Lsparse); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &Rsparse); + + IGRAPH_CHECK(igraph_sparsemat_compress(&Lsparse, &Lsparse2)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &Lsparse2); + IGRAPH_CHECK(igraph_sparsemat_compress(&Rsparse, &Rsparse2)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &Rsparse2); + IGRAPH_CHECK(igraph_sparsemat_transpose(&Rsparse2, &Rsparse3, + /*values=*/ 1)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &Rsparse3); + + IGRAPH_CHECK(igraph_sparsemat_multiply(&Rsparse3, &Lsparse2, &proj)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &proj); + + IGRAPH_VECTOR_INIT_FINALLY(&res, no_of_nodes); + IGRAPH_CHECK(igraph_vector_resize(eps, no_of_vectors)); + + for (k = 0; k < no_of_vectors; k++) { + igraph_vector_view(&x, &MATRIX(*V, 0, k), no_of_nodes); + igraph_vector_null(&res); + IGRAPH_CHECK(igraph_sparsemat_gaxpy(&proj, &x, &res)); + VECTOR(*eps)[k] = 0.0; + for (i = 0; i < no_of_nodes; i++) { + igraph_real_t di = MATRIX(*V, i, k) - VECTOR(res)[i]; + VECTOR(*eps)[k] += di * di; + } + VECTOR(*eps)[k] = sqrt(VECTOR(*eps)[k]); + } + + igraph_vector_destroy(&res); + igraph_sparsemat_destroy(&proj); + igraph_sparsemat_destroy(&Rsparse3); + igraph_sparsemat_destroy(&Rsparse2); + igraph_sparsemat_destroy(&Lsparse2); + igraph_sparsemat_destroy(&Rsparse); + igraph_sparsemat_destroy(&Lsparse); + IGRAPH_FINALLY_CLEAN(7); + + return 0; +} + +static int igraph_i_matrix_laplacian(const igraph_matrix_t *matrix, + igraph_matrix_t *mymatrix, + igraph_scg_norm_t norm) { + + igraph_vector_t degree; + int i, j, n = (int) igraph_matrix_nrow(matrix); + IGRAPH_CHECK(igraph_matrix_resize(mymatrix, n, n)); + + IGRAPH_VECTOR_INIT_FINALLY(°ree, n); + + if (norm == IGRAPH_SCG_NORM_ROW) { + IGRAPH_CHECK(igraph_matrix_rowsum(matrix, °ree)); + } else { + IGRAPH_CHECK(igraph_matrix_colsum(matrix, °ree)); + } + for (i = 0; i < n; i++) { + VECTOR(degree)[i] -= MATRIX(*matrix, i, i); + } + + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + MATRIX(*mymatrix, i, j) = - MATRIX(*matrix, i, j); + } + MATRIX(*mymatrix, i, i) = VECTOR(degree)[i]; + } + + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_sparsemat_laplacian(const igraph_sparsemat_t *sparse, + igraph_sparsemat_t *mysparse, + igraph_scg_norm_t norm) { + + igraph_vector_t degree; + int i, n = (int) igraph_sparsemat_nrow(sparse); + int nzmax = igraph_sparsemat_nzmax(sparse); + igraph_sparsemat_iterator_t it; + + IGRAPH_CHECK(igraph_sparsemat_init(mysparse, n, n, nzmax + n)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, mysparse); + igraph_sparsemat_iterator_init(&it, (igraph_sparsemat_t *) sparse); + + IGRAPH_VECTOR_INIT_FINALLY(°ree, n); + for (igraph_sparsemat_iterator_reset(&it); + !igraph_sparsemat_iterator_end(&it); + igraph_sparsemat_iterator_next(&it)) { + int row = igraph_sparsemat_iterator_row(&it); + int col = igraph_sparsemat_iterator_col(&it); + if (row != col) { + igraph_real_t val = igraph_sparsemat_iterator_get(&it); + if (norm == IGRAPH_SCG_NORM_ROW) { + VECTOR(degree)[row] += val; + } else { + VECTOR(degree)[col] += val; + } + } + } + + /* Diagonal */ + for (i = 0; i < n; i++) { + igraph_sparsemat_entry(mysparse, i, i, VECTOR(degree)[i]); + } + + /* And the rest, filter out diagonal elements */ + for (igraph_sparsemat_iterator_reset(&it); + !igraph_sparsemat_iterator_end(&it); + igraph_sparsemat_iterator_next(&it)) { + int row = igraph_sparsemat_iterator_row(&it); + int col = igraph_sparsemat_iterator_col(&it); + if (row != col) { + igraph_real_t val = igraph_sparsemat_iterator_get(&it); + igraph_sparsemat_entry(mysparse, row, col, -val); + } + } + + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(2); /* + mysparse */ + + return 0; +} + +static int igraph_i_matrix_stochastic(const igraph_matrix_t *matrix, + igraph_matrix_t *mymatrix, + igraph_scg_norm_t norm) { + + int i, j, n = (int) igraph_matrix_nrow(matrix); + IGRAPH_CHECK(igraph_matrix_copy(mymatrix, matrix)); + + if (norm == IGRAPH_SCG_NORM_ROW) { + for (i = 0; i < n; i++) { + igraph_real_t sum = 0.0; + for (j = 0; j < n; j++) { + sum += MATRIX(*matrix, i, j); + } + if (sum == 0) { + IGRAPH_WARNING("Zero degree vertices"); + } + for (j = 0; j < n; j++) { + MATRIX(*mymatrix, i, j) = MATRIX(*matrix, i, j) / sum; + } + } + } else { + for (i = 0; i < n; i++) { + igraph_real_t sum = 0.0; + for (j = 0; j < n; j++) { + sum += MATRIX(*matrix, j, i); + } + if (sum == 0) { + IGRAPH_WARNING("Zero degree vertices"); + } + for (j = 0; j < n; j++) { + MATRIX(*mymatrix, j, i) = MATRIX(*matrix, j, i) / sum; + } + } + } + + return 0; +} + +/* TODO prototype; function is defined in conversion.c */ +int igraph_i_normalize_sparsemat(igraph_sparsemat_t *sparsemat, + igraph_bool_t column_wise); + +static int igraph_i_sparsemat_stochastic(const igraph_sparsemat_t *sparse, + igraph_sparsemat_t *mysparse, + igraph_scg_norm_t norm) { + + IGRAPH_CHECK(igraph_sparsemat_copy(mysparse, sparse)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, mysparse); + IGRAPH_CHECK(igraph_i_normalize_sparsemat(mysparse, + norm == IGRAPH_SCG_NORM_COL)); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_scg_get_result(igraph_scg_matrix_t type, + const igraph_matrix_t *matrix, + const igraph_sparsemat_t *sparsemat, + const igraph_sparsemat_t *Lsparse, + const igraph_sparsemat_t *Rsparse_t, + igraph_t *scg_graph, + igraph_matrix_t *scg_matrix, + igraph_sparsemat_t *scg_sparsemat, + igraph_bool_t directed) { + + /* We need to calculate either scg_matrix (if input is dense), or + scg_sparsemat (if input is sparse). For the latter we might need + to temporarily use another matrix. */ + + + if (matrix) { + igraph_matrix_t *my_scg_matrix = scg_matrix, v_scg_matrix; + igraph_matrix_t tmp; + igraph_sparsemat_t *myLsparse = (igraph_sparsemat_t *) Lsparse, v_Lsparse; + + if (!scg_matrix) { + my_scg_matrix = &v_scg_matrix; + IGRAPH_CHECK(igraph_matrix_init(my_scg_matrix, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, my_scg_matrix); + } + + if (!igraph_sparsemat_is_cc(Lsparse)) { + myLsparse = &v_Lsparse; + IGRAPH_CHECK(igraph_sparsemat_compress(Lsparse, myLsparse)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, myLsparse); + } + + IGRAPH_CHECK(igraph_matrix_init(&tmp, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_destroy, &tmp); + IGRAPH_CHECK(igraph_sparsemat_dense_multiply(matrix, Rsparse_t, &tmp)); + IGRAPH_CHECK(igraph_sparsemat_multiply_by_dense(myLsparse, &tmp, + my_scg_matrix)); + igraph_matrix_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + if (scg_sparsemat) { + IGRAPH_CHECK(igraph_matrix_as_sparsemat(scg_sparsemat, my_scg_matrix, + /* tol= */ 0)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, scg_sparsemat); + } + + if (scg_graph) { + if (type != IGRAPH_SCG_LAPLACIAN) { + IGRAPH_CHECK(igraph_weighted_adjacency(scg_graph, my_scg_matrix, + directed ? + IGRAPH_ADJ_DIRECTED : + IGRAPH_ADJ_UNDIRECTED, + "weight", /*loops=*/ 1)); + } else { + int i, j, n = (int) igraph_matrix_nrow(my_scg_matrix); + igraph_matrix_t tmp; + IGRAPH_MATRIX_INIT_FINALLY(&tmp, n, n); + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + MATRIX(tmp, i, j) = -MATRIX(*my_scg_matrix, i, j); + } + MATRIX(tmp, i, i) = 0; + } + IGRAPH_CHECK(igraph_weighted_adjacency(scg_graph, &tmp, directed ? + IGRAPH_ADJ_DIRECTED : + IGRAPH_ADJ_UNDIRECTED, + "weight", /*loops=*/ 0)); + igraph_matrix_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + } + IGRAPH_FINALLY(igraph_destroy, scg_graph); + } + + if (scg_graph) { + IGRAPH_FINALLY_CLEAN(1); + } + if (scg_sparsemat) { + IGRAPH_FINALLY_CLEAN(1); + } + + if (!igraph_sparsemat_is_cc(Lsparse)) { + igraph_sparsemat_destroy(myLsparse); + IGRAPH_FINALLY_CLEAN(1); + } + + if (!scg_matrix) { + igraph_matrix_destroy(my_scg_matrix); + IGRAPH_FINALLY_CLEAN(1); + } + + } else { /* sparsemat */ + igraph_sparsemat_t *my_scg_sparsemat = scg_sparsemat, v_scg_sparsemat; + igraph_sparsemat_t tmp, *mysparsemat = (igraph_sparsemat_t *) sparsemat, + v_sparsemat, *myLsparse = (igraph_sparsemat_t *) Lsparse, v_Lsparse; + if (!scg_sparsemat) { + my_scg_sparsemat = &v_scg_sparsemat; + } + if (!igraph_sparsemat_is_cc(sparsemat)) { + mysparsemat = &v_sparsemat; + IGRAPH_CHECK(igraph_sparsemat_compress(sparsemat, mysparsemat)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, mysparsemat); + } + if (!igraph_sparsemat_is_cc(Lsparse)) { + myLsparse = &v_Lsparse; + IGRAPH_CHECK(igraph_sparsemat_compress(Lsparse, myLsparse)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, myLsparse); + } + IGRAPH_CHECK(igraph_sparsemat_multiply(mysparsemat, Rsparse_t, + &tmp)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmp); + IGRAPH_CHECK(igraph_sparsemat_multiply(myLsparse, &tmp, + my_scg_sparsemat)); + igraph_sparsemat_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, my_scg_sparsemat); + + if (scg_matrix) { + IGRAPH_CHECK(igraph_sparsemat_as_matrix(scg_matrix, my_scg_sparsemat)); + } + if (scg_graph) { + if (type != IGRAPH_SCG_LAPLACIAN) { + IGRAPH_CHECK(igraph_weighted_sparsemat(scg_graph, my_scg_sparsemat, + directed, "weight", + /*loops=*/ 1)); + } else { + igraph_sparsemat_t tmp; + IGRAPH_CHECK(igraph_sparsemat_copy(&tmp, my_scg_sparsemat)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmp); + IGRAPH_CHECK(igraph_sparsemat_neg(&tmp)); + IGRAPH_CHECK(igraph_weighted_sparsemat(scg_graph, &tmp, directed, + "weight", /*loops=*/ 0)); + igraph_sparsemat_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + } + IGRAPH_FINALLY(igraph_destroy, scg_graph); + } + + if (scg_graph) { + IGRAPH_FINALLY_CLEAN(1); + } + if (!scg_sparsemat) { + igraph_sparsemat_destroy(my_scg_sparsemat); + } + IGRAPH_FINALLY_CLEAN(1); /* my_scg_sparsemat */ + if (!igraph_sparsemat_is_cc(Lsparse)) { + igraph_sparsemat_destroy(myLsparse); + IGRAPH_FINALLY_CLEAN(1); + } + if (!igraph_sparsemat_is_cc(sparsemat)) { + igraph_sparsemat_destroy(mysparsemat); + IGRAPH_FINALLY_CLEAN(1); + } + } + + return 0; +} + +static int igraph_i_scg_common_checks(const igraph_t *graph, + const igraph_matrix_t *matrix, + const igraph_sparsemat_t *sparsemat, + const igraph_vector_t *ev, + igraph_integer_t nt, + const igraph_vector_t *nt_vec, + const igraph_matrix_t *vectors, + const igraph_matrix_complex_t *vectors_cmplx, + const igraph_vector_t *groups, + const igraph_t *scg_graph, + const igraph_matrix_t *scg_matrix, + const igraph_sparsemat_t *scg_sparsemat, + const igraph_vector_t *p, + igraph_real_t *evmin, igraph_real_t *evmax) { + + int no_of_nodes = -1; + igraph_real_t min, max; + int no_of_ev = (int) igraph_vector_size(ev); + + if ( (graph ? 1 : 0) + (matrix ? 1 : 0) + (sparsemat ? 1 : 0) != 1 ) { + IGRAPH_ERROR("Give exactly one of `graph', `matrix' and `sparsemat'", + IGRAPH_EINVAL); + } + + if (graph) { + no_of_nodes = igraph_vcount(graph); + } else if (matrix) { + no_of_nodes = (int) igraph_matrix_nrow(matrix); + } else if (sparsemat) { + no_of_nodes = (int) igraph_sparsemat_nrow(sparsemat); + } + + if ((matrix && igraph_matrix_ncol(matrix) != no_of_nodes) || + (sparsemat && igraph_sparsemat_ncol(sparsemat) != no_of_nodes)) { + IGRAPH_ERROR("Matrix must be square", IGRAPH_NONSQUARE); + } + + igraph_vector_minmax(ev, evmin, evmax); + if (*evmin < 0 || *evmax >= no_of_nodes) { + IGRAPH_ERROR("Invalid eigenvectors given", IGRAPH_EINVAL); + } + + if (!nt_vec && (nt <= 1 || nt >= no_of_nodes)) { + IGRAPH_ERROR("Invalid interval specification", IGRAPH_EINVAL); + } + + if (nt_vec) { + if (igraph_vector_size(nt_vec) != 1 && + igraph_vector_size(nt_vec) != no_of_ev) { + IGRAPH_ERROR("Invalid length for interval specification", + IGRAPH_EINVAL); + } + igraph_vector_minmax(nt_vec, &min, &max); + if (min <= 1 || max >= no_of_nodes) { + IGRAPH_ERROR("Invalid interval specification", IGRAPH_EINVAL); + } + } + + if (vectors && igraph_matrix_size(vectors) != 0 && + (igraph_matrix_ncol(vectors) != no_of_ev || + igraph_matrix_nrow(vectors) != no_of_nodes)) { + IGRAPH_ERROR("Invalid eigenvector matrix size", IGRAPH_EINVAL); + } + + if (vectors_cmplx && igraph_matrix_complex_size(vectors_cmplx) != 0 && + (igraph_matrix_complex_ncol(vectors_cmplx) != no_of_ev || + igraph_matrix_complex_nrow(vectors_cmplx) != no_of_nodes)) { + IGRAPH_ERROR("Invalid eigenvector matrix size", IGRAPH_EINVAL); + } + + if (groups && igraph_vector_size(groups) != 0 && + igraph_vector_size(groups) != no_of_nodes) { + IGRAPH_ERROR("Invalid `groups' vector size", IGRAPH_EINVAL); + } + + if ( (scg_graph != 0) + (scg_matrix != 0) + (scg_sparsemat != 0) == 0 ) { + IGRAPH_ERROR("No output is requested, please give at least one of " + "`scg_graph', `scg_matrix' and `scg_sparsemat'", + IGRAPH_EINVAL); + } + + if (p && igraph_vector_size(p) != 0 && + igraph_vector_size(p) != no_of_nodes) { + IGRAPH_ERROR("Invalid `p' vector size", IGRAPH_EINVAL); + } + + return 0; +} + +/** + * \function igraph_scg_adjacency + * Spectral coarse graining, symmetric case. + * + * This function handles all the steps involved in the Spectral Coarse + * Graining (SCG) of some matrices and graphs as described in the + * reference below. + * + * \param graph The input graph. Exactly one of \p graph, \p matrix + * and \p sparsemat must be given, the other two must be \c NULL + * pointers. + * \param matrix The input matrix. Exactly one of \p graph, \p matrix + * and \p sparsemat must be given, the other two must be \c NULL + * pointers. + * \param sparsemat The input sparse matrix. Exactly one of \p graph, + * \p matrix and \p sparsemat must be given, the other two must be + * \c NULL pointers. + * \param ev A vector of positive integers giving the indexes of the + * eigenpairs to be preserved. 1 designates the eigenvalue with + * largest algebraic value, 2 the one with second largest algebraic + * value, etc. + * \param nt Positive integer. When \p algo is \c IGRAPH_SCG_OPTIMUM, + * it gives the number of groups to partition each eigenvector + * separately. When \p algo is \c IGRAPH_SCG_INTERV or \c + * IGRAPH_SCG_INTERV_KM, it gives the number of intervals to + * partition each eigenvector. This is ignored when \p algo is \c + * IGRAPH_SCG_EXACT. + * \param nt_vec A numeric vector of length one or the length must + * match the number of eigenvectors given in \p V, or a \c NULL + * pointer. If not \c NULL, then this argument gives the number of + * groups or intervals, and \p nt is ignored. Different number of + * groups or intervals can be specified for each eigenvector. + * \param algo The algorithm to solve the SCG problem. Possible + * values: \c IGRAPH_SCG_OPTIMUM, \c IGRAPH_SCG_INTERV_KM, \c + * IGRAPH_SCG_INTERV and \c IGRAPH_SCG_EXACT. Please see the + * details about them above. + * \param values If this is not \c NULL and the eigenvectors are + * re-calculated, then the eigenvalues are stored here. + * \param vectors If this is not \c NULL, and not a zero-length + * matrix, then it is interpreted as the eigenvectors to use for + * the coarse-graining. Otherwise the eigenvectors are + * re-calculated, and they are stored here. (If this is not \c NULL.) + * \param groups If this is not \c NULL, and not a zero-length vector, + * then it is interpreted as the vector of group labels. (Group + * labels are integers from zero and are sequential.) Otherwise + * group labels are re-calculated and stored here, if this argument + * is not a null pointer. + * \param use_arpack Whether to use ARPACK for solving the + * eigenproblem. Currently ARPACK is not implemented. + * \param maxiter A positive integer giving the number of iterations + * of the k-means algorithm when \p algo is \c + * IGRAPH_SCG_INTERV_KM. It is ignored in other cases. A reasonable + * (initial) value for this argument is 100. + * \param scg_graph If not a \c NULL pointer, then the coarse-grained + * graph is returned here. + * \param scg_matrix If not a \c NULL pointer, then it must be an + * initialied matrix, and the coarse-grained matrix is returned + * here. + * \param scg_sparsemat If not a \c NULL pointer, then the coarse + * grained matrix is returned here, in sparse matrix form. + * \param L If not a \c NULL pointer, then it must be an initialized + * matrix and the left semi-projector is returned here. + * \param R If not a \c NULL pointer, then it must be an initialized + * matrix and the right semi-projector is returned here. + * \param Lsparse If not a \c NULL pointer, then the left + * semi-projector is returned here. + * \param Rsparse If not a \c NULL pointer, then the right + * semi-projector is returned here. + * \return Error code. + * + * Time complexity: TODO. + * + * \sa \ref igraph_scg_grouping(), \ref igraph_scg_semiprojectors(), + * \ref igraph_scg_stochastic() and \ref igraph_scg_laplacian(). + * + * \example examples/simple/scg.c + */ + +int igraph_scg_adjacency(const igraph_t *graph, + const igraph_matrix_t *matrix, + const igraph_sparsemat_t *sparsemat, + const igraph_vector_t *ev, + igraph_integer_t nt, + const igraph_vector_t *nt_vec, + igraph_scg_algorithm_t algo, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_vector_t *groups, + igraph_bool_t use_arpack, + igraph_integer_t maxiter, + igraph_t *scg_graph, + igraph_matrix_t *scg_matrix, + igraph_sparsemat_t *scg_sparsemat, + igraph_matrix_t *L, + igraph_matrix_t *R, + igraph_sparsemat_t *Lsparse, + igraph_sparsemat_t *Rsparse) { + + igraph_sparsemat_t *mysparsemat = (igraph_sparsemat_t*) sparsemat, + real_sparsemat; + int no_of_ev = (int) igraph_vector_size(ev); + /* eigenvectors are calculated and returned */ + igraph_bool_t do_vectors = vectors && igraph_matrix_size(vectors) == 0; + /* groups are calculated */ + igraph_bool_t do_groups = !groups || igraph_vector_size(groups) == 0; + /* eigenvectors are not returned but must be calculated for groups */ + igraph_bool_t tmp_vectors = !do_vectors && do_groups; + /* need temporary vector for groups */ + igraph_bool_t tmp_groups = !groups; + igraph_matrix_t myvectors; + igraph_vector_t mygroups; + igraph_bool_t tmp_lsparse = !Lsparse, tmp_rsparse = !Rsparse; + igraph_sparsemat_t myLsparse, myRsparse, tmpsparse, Rsparse_t; + int no_of_nodes; + igraph_real_t evmin, evmax; + igraph_bool_t directed; + + /* --------------------------------------------------------------------*/ + /* Argument checks */ + + IGRAPH_CHECK(igraph_i_scg_common_checks(graph, matrix, sparsemat, + ev, nt, nt_vec, + vectors, 0, groups, scg_graph, + scg_matrix, scg_sparsemat, + /*p=*/ 0, &evmin, &evmax)); + + if (graph) { + no_of_nodes = igraph_vcount(graph); + directed = igraph_is_directed(graph); + } else if (matrix) { + no_of_nodes = (int) igraph_matrix_nrow(matrix); + directed = !igraph_matrix_is_symmetric(matrix); + } else { + no_of_nodes = (int) igraph_sparsemat_nrow(sparsemat); + directed = !igraph_sparsemat_is_symmetric(sparsemat); + } + + /* -------------------------------------------------------------------- */ + /* Convert graph, if needed */ + + if (graph) { + mysparsemat = &real_sparsemat; + IGRAPH_CHECK(igraph_get_sparsemat(graph, mysparsemat)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, mysparsemat); + } + + /* -------------------------------------------------------------------- */ + /* Compute eigenpairs, if needed */ + if (tmp_vectors) { + vectors = &myvectors; + IGRAPH_MATRIX_INIT_FINALLY(vectors, no_of_nodes, no_of_ev); + } + + if (do_vectors || tmp_vectors) { + igraph_arpack_options_t options; + igraph_eigen_which_t which; + igraph_matrix_t tmp; + igraph_vector_t tmpev; + igraph_vector_t tmpeval; + int i; + + which.pos = IGRAPH_EIGEN_SELECT; + which.il = (int) (no_of_nodes - evmax + 1); + which.iu = (int) (no_of_nodes - evmin + 1); + + if (values) { + IGRAPH_VECTOR_INIT_FINALLY(&tmpeval, 0); + } + IGRAPH_CHECK(igraph_matrix_init(&tmp, no_of_nodes, + which.iu - which.il + 1)); + IGRAPH_FINALLY(igraph_matrix_destroy, &tmp); + IGRAPH_CHECK(igraph_eigen_matrix_symmetric(matrix, mysparsemat, + /* fun= */ 0, no_of_nodes, + /* extra= */ 0, + /* algorithm= */ + use_arpack ? + IGRAPH_EIGEN_ARPACK : + IGRAPH_EIGEN_LAPACK, &which, + &options, /*storage=*/ 0, + values ? &tmpeval : 0, + &tmp)); + IGRAPH_VECTOR_INIT_FINALLY(&tmpev, no_of_ev); + for (i = 0; i < no_of_ev; i++) { + VECTOR(tmpev)[i] = evmax - VECTOR(*ev)[i]; + } + if (values) { + IGRAPH_CHECK(igraph_vector_index(&tmpeval, values, &tmpev)); + } + IGRAPH_CHECK(igraph_matrix_select_cols(&tmp, vectors, &tmpev)); + igraph_vector_destroy(&tmpev); + igraph_matrix_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); + if (values) { + igraph_vector_destroy(&tmpeval); + IGRAPH_FINALLY_CLEAN(1); + } + } + + /* -------------------------------------------------------------------- */ + /* Work out groups, if needed */ + if (tmp_groups) { + groups = &mygroups; + IGRAPH_VECTOR_INIT_FINALLY((igraph_vector_t*)groups, no_of_nodes); + } + if (do_groups) { + IGRAPH_CHECK(igraph_scg_grouping(vectors, (igraph_vector_t*)groups, + nt, nt_vec, + IGRAPH_SCG_SYMMETRIC, algo, + /*p=*/ 0, maxiter)); + } + + /* -------------------------------------------------------------------- */ + /* Perform coarse graining */ + if (tmp_lsparse) { + Lsparse = &myLsparse; + } + if (tmp_rsparse) { + Rsparse = &myRsparse; + } + IGRAPH_CHECK(igraph_scg_semiprojectors(groups, IGRAPH_SCG_SYMMETRIC, + L, R, Lsparse, Rsparse, /*p=*/ 0, + IGRAPH_SCG_NORM_ROW)); + if (tmp_groups) { + igraph_vector_destroy((igraph_vector_t*) groups); + IGRAPH_FINALLY_CLEAN(1); + } + if (tmp_vectors) { + igraph_matrix_destroy(vectors); + IGRAPH_FINALLY_CLEAN(1); + } + if (Rsparse) { + IGRAPH_FINALLY(igraph_sparsemat_destroy, Rsparse); + } + if (Lsparse) { + IGRAPH_FINALLY(igraph_sparsemat_destroy, Lsparse); + } + + /* -------------------------------------------------------------------- */ + /* Compute coarse grained matrix/graph/sparse matrix */ + IGRAPH_CHECK(igraph_sparsemat_compress(Rsparse, &tmpsparse)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmpsparse); + IGRAPH_CHECK(igraph_sparsemat_transpose(&tmpsparse, &Rsparse_t, + /*values=*/ 1)); + igraph_sparsemat_destroy(&tmpsparse); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &Rsparse_t); + + IGRAPH_CHECK(igraph_i_scg_get_result(IGRAPH_SCG_SYMMETRIC, + matrix, mysparsemat, + Lsparse, &Rsparse_t, + scg_graph, scg_matrix, + scg_sparsemat, directed)); + + /* -------------------------------------------------------------------- */ + /* Clean up */ + + igraph_sparsemat_destroy(&Rsparse_t); + IGRAPH_FINALLY_CLEAN(1); + if (Lsparse) { + IGRAPH_FINALLY_CLEAN(1); + } + if (Rsparse) { + IGRAPH_FINALLY_CLEAN(1); + } + + if (graph) { + igraph_sparsemat_destroy(mysparsemat); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_scg_stochastic + * Spectral coarse graining, stochastic case. + * + * This function handles all the steps involved in the Spectral Coarse + * Graining (SCG) of some matrices and graphs as described in the + * reference below. + * + * \param graph The input graph. Exactly one of \p graph, \p matrix + * and \p sparsemat must be given, the other two must be \c NULL + * pointers. + * \param matrix The input matrix. Exactly one of \p graph, \p matrix + * and \p sparsemat must be given, the other two must be \c NULL + * pointers. + * \param sparsemat The input sparse matrix. Exactly one of \p graph, + * \p matrix and \p sparsemat must be given, the other two must be + * \c NULL pointers. + * \param ev A vector of positive integers giving the indexes of the + * eigenpairs to be preserved. 1 designates the eigenvalue with + * largest magnitude, 2 the one with second largest magnitude, etc. + * \param nt Positive integer. When \p algo is \c IGRAPH_SCG_OPTIMUM, + * it gives the number of groups to partition each eigenvector + * separately. When \p algo is \c IGRAPH_SCG_INTERV or \c + * IGRAPH_SCG_INTERV_KM, it gives the number of intervals to + * partition each eigenvector. This is ignored when \p algo is \c + * IGRAPH_SCG_EXACT. + * \param nt_vec A numeric vector of length one or the length must + * match the number of eigenvectors given in \p V, or a \c NULL + * pointer. If not \c NULL, then this argument gives the number of + * groups or intervals, and \p nt is ignored. Different number of + * groups or intervals can be specified for each eigenvector. + * \param algo The algorithm to solve the SCG problem. Possible + * values: \c IGRAPH_SCG_OPTIMUM, \c IGRAPH_SCG_INTERV_KM, \c + * IGRAPH_SCG_INTERV and \c IGRAPH_SCG_EXACT. Please see the + * details about them above. + * \param norm Either \c IGRAPH_SCG_NORM_ROW or \c IGRAPH_SCG_NORM_COL. + * Specifies whether the rows or the columns of the + * stochastic matrix sum up to one. + * \param values If this is not \c NULL and the eigenvectors are + * re-calculated, then the eigenvalues are stored here. + * \param vectors If this is not \c NULL, and not a zero-length + * matrix, then it is interpreted as the eigenvectors to use for + * the coarse-graining. Otherwise the eigenvectors are + * re-calculated, and they are stored here. (If this is not \c NULL.) + * \param groups If this is not \c NULL, and not a zero-length vector, + * then it is interpreted as the vector of group labels. (Group + * labels are integers from zero and are sequential.) Otherwise + * group labels are re-calculated and stored here, if this argument + * is not a null pointer. + * \param p If this is not \c NULL, and not zero length, then it is + * interpreted as the stationary probability distribution of the + * Markov chain corresponding to the input matrix/graph. Its length + * must match the number of vertices in the input graph (or number + * of rows in the input matrix). If not given, then the stationary + * distribution is calculated and stored here. (Unless this + * argument is a \c NULL pointer, in which case it is not stored.) + * \param use_arpack Whether to use ARPACK for solving the + * eigenproblem. Currently ARPACK is not implemented. + * \param maxiter A positive integer giving the number of iterations + * of the k-means algorithm when \p algo is \c + * IGRAPH_SCG_INTERV_KM. It is ignored in other cases. A reasonable + * (initial) value for this argument is 100. + * \param scg_graph If not a \c NULL pointer, then the coarse-grained + * graph is returned here. + * \param scg_matrix If not a \c NULL pointer, then it must be an + * initialied matrix, and the coarse-grained matrix is returned + * here. + * \param scg_sparsemat If not a \c NULL pointer, then the coarse + * grained matrix is returned here, in sparse matrix form. + * \param L If not a \c NULL pointer, then it must be an initialized + * matrix and the left semi-projector is returned here. + * \param R If not a \c NULL pointer, then it must be an initialized + * matrix and the right semi-projector is returned here. + * \param Lsparse If not a \c NULL pointer, then the left + * semi-projector is returned here. + * \param Rsparse If not a \c NULL pointer, then the right + * semi-projector is returned here. + * \return Error code. + * + * Time complexity: TODO. + * + * \sa \ref igraph_scg_grouping(), \ref igraph_scg_semiprojectors(), + * \ref igraph_scg_adjacency() and \ref igraph_scg_laplacian(). + * + * \example examples/simple/scg2.c + */ + +int igraph_scg_stochastic(const igraph_t *graph, + const igraph_matrix_t *matrix, + const igraph_sparsemat_t *sparsemat, + const igraph_vector_t *ev, + igraph_integer_t nt, + const igraph_vector_t *nt_vec, + igraph_scg_algorithm_t algo, + igraph_scg_norm_t norm, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors, + igraph_vector_t *groups, + igraph_vector_t *p, + igraph_bool_t use_arpack, + igraph_integer_t maxiter, + igraph_t *scg_graph, + igraph_matrix_t *scg_matrix, + igraph_sparsemat_t *scg_sparsemat, + igraph_matrix_t *L, + igraph_matrix_t *R, + igraph_sparsemat_t *Lsparse, + igraph_sparsemat_t *Rsparse) { + + igraph_matrix_t *mymatrix = (igraph_matrix_t*) matrix, real_matrix; + igraph_sparsemat_t *mysparsemat = (igraph_sparsemat_t*) sparsemat, + real_sparsemat; + int no_of_nodes; + igraph_real_t evmin, evmax; + igraph_arpack_options_t options; + igraph_eigen_which_t which; + /* eigenvectors are calculated and returned */ + igraph_bool_t do_vectors = vectors && igraph_matrix_complex_size(vectors) == 0; + /* groups are calculated */ + igraph_bool_t do_groups = !groups || igraph_vector_size(groups) == 0; + igraph_bool_t tmp_groups = !groups; + /* eigenvectors are not returned but must be calculated for groups */ + igraph_bool_t tmp_vectors = !do_vectors && do_groups; + igraph_matrix_complex_t myvectors; + igraph_vector_t mygroups; + igraph_bool_t do_p = !p || igraph_vector_size(p) == 0; + igraph_vector_t *myp = (igraph_vector_t *) p, real_p; + int no_of_ev = (int) igraph_vector_size(ev); + igraph_bool_t tmp_lsparse = !Lsparse, tmp_rsparse = !Rsparse; + igraph_sparsemat_t myLsparse, myRsparse, tmpsparse, Rsparse_t; + + /* --------------------------------------------------------------------*/ + /* Argument checks */ + + IGRAPH_CHECK(igraph_i_scg_common_checks(graph, matrix, sparsemat, + ev, nt, nt_vec, + 0, vectors, groups, scg_graph, + scg_matrix, scg_sparsemat, p, + &evmin, &evmax)); + + if (graph) { + no_of_nodes = igraph_vcount(graph); + } else if (matrix) { + no_of_nodes = (int) igraph_matrix_nrow(matrix); + } else { + no_of_nodes = (int) igraph_sparsemat_nrow(sparsemat); + } + + /* -------------------------------------------------------------------- */ + /* Convert graph, if needed */ + + if (graph) { + mysparsemat = &real_sparsemat; + IGRAPH_CHECK(igraph_get_stochastic_sparsemat(graph, mysparsemat, + norm == IGRAPH_SCG_NORM_COL)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, mysparsemat); + } else if (matrix) { + mymatrix = &real_matrix; + IGRAPH_CHECK(igraph_i_matrix_stochastic(matrix, mymatrix, norm)); + IGRAPH_FINALLY(igraph_matrix_destroy, mymatrix); + } else { /* sparsemat */ + mysparsemat = &real_sparsemat; + IGRAPH_CHECK(igraph_i_sparsemat_stochastic(sparsemat, mysparsemat, norm)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, mysparsemat); + } + + /* -------------------------------------------------------------------- */ + /* Compute eigenpairs, if needed */ + + if (tmp_vectors) { + vectors = &myvectors; + IGRAPH_CHECK(igraph_matrix_complex_init(vectors, no_of_nodes, no_of_ev)); + IGRAPH_FINALLY(igraph_matrix_complex_destroy, vectors); + } + + if (do_vectors || tmp_vectors) { + igraph_matrix_complex_t tmp; + igraph_vector_t tmpev; + igraph_vector_complex_t tmpeval; + int i; + + which.pos = IGRAPH_EIGEN_SELECT; + which.il = (int) (no_of_nodes - evmax + 1); + which.iu = (int) (no_of_nodes - evmin + 1); + + if (values) { + IGRAPH_CHECK(igraph_vector_complex_init(&tmpeval, 0)); + IGRAPH_FINALLY(igraph_vector_complex_destroy, &tmpeval); + } + IGRAPH_CHECK(igraph_matrix_complex_init(&tmp, no_of_nodes, + which.iu - which.il + 1)); + IGRAPH_FINALLY(igraph_matrix_complex_destroy, &tmp); + IGRAPH_CHECK(igraph_eigen_matrix(mymatrix, mysparsemat, /*fun=*/ 0, + no_of_nodes, /*extra=*/ 0, use_arpack ? + IGRAPH_EIGEN_ARPACK : + IGRAPH_EIGEN_LAPACK, &which, &options, + /*storage=*/ 0, + values ? &tmpeval : 0, &tmp)); + + IGRAPH_VECTOR_INIT_FINALLY(&tmpev, no_of_ev); + for (i = 0; i < no_of_ev; i++) { + VECTOR(tmpev)[i] = evmax - VECTOR(*ev)[i]; + } + if (values) { + IGRAPH_CHECK(igraph_vector_complex_index(&tmpeval, values, &tmpev)); + } + IGRAPH_CHECK(igraph_matrix_complex_select_cols(&tmp, vectors, &tmpev)); + igraph_vector_destroy(&tmpev); + igraph_matrix_complex_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); + if (values) { + igraph_vector_complex_destroy(&tmpeval); + IGRAPH_FINALLY_CLEAN(1); + } + } + + /* Compute p if not supplied */ + if (do_p) { + igraph_eigen_which_t w; + igraph_matrix_complex_t tmp; + igraph_arpack_options_t o; + igraph_matrix_t trans, *mytrans = &trans; + igraph_sparsemat_t sparse_trans, *mysparse_trans = &sparse_trans; + int i; + igraph_arpack_options_init(&o); + if (!p) { + IGRAPH_VECTOR_INIT_FINALLY(&real_p, no_of_nodes); + myp = &real_p; + } else { + IGRAPH_CHECK(igraph_vector_resize(p, no_of_nodes)); + } + IGRAPH_CHECK(igraph_matrix_complex_init(&tmp, 0, 0)); + IGRAPH_FINALLY(igraph_matrix_complex_destroy, &tmp); + w.pos = IGRAPH_EIGEN_LR; + w.howmany = 1; + + if (mymatrix) { + IGRAPH_CHECK(igraph_matrix_copy(&trans, mymatrix)); + IGRAPH_FINALLY(igraph_matrix_destroy, &trans); + IGRAPH_CHECK(igraph_matrix_transpose(&trans)); + mysparse_trans = 0; + } else { + IGRAPH_CHECK(igraph_sparsemat_transpose(mysparsemat, &sparse_trans, + /*values=*/ 1)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, mysparse_trans); + mytrans = 0; + } + + IGRAPH_CHECK(igraph_eigen_matrix(mytrans, mysparse_trans, /*fun=*/ 0, + no_of_nodes, /*extra=*/ 0, /*algorith=*/ + use_arpack ? + IGRAPH_EIGEN_ARPACK : + IGRAPH_EIGEN_LAPACK, &w, &o, + /*storage=*/ 0, /*values=*/ 0, &tmp)); + + if (mymatrix) { + igraph_matrix_destroy(&trans); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_sparsemat_destroy(mysparse_trans); + IGRAPH_FINALLY_CLEAN(1); + } + + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*myp)[i] = fabs(IGRAPH_REAL(MATRIX(tmp, i, 0))); + } + igraph_matrix_complex_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + } + + /* -------------------------------------------------------------------- */ + /* Work out groups, if needed */ + /* TODO: use complex part as well */ + if (tmp_groups) { + groups = &mygroups; + IGRAPH_VECTOR_INIT_FINALLY((igraph_vector_t*)groups, no_of_nodes); + } + if (do_groups) { + igraph_matrix_t tmp; + IGRAPH_MATRIX_INIT_FINALLY(&tmp, 0, 0); + IGRAPH_CHECK(igraph_matrix_complex_real(vectors, &tmp)); + IGRAPH_CHECK(igraph_scg_grouping(&tmp, (igraph_vector_t*)groups, + nt, nt_vec, + IGRAPH_SCG_STOCHASTIC, algo, + myp, maxiter)); + igraph_matrix_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + } + + /* -------------------------------------------------------------------- */ + /* Perform coarse graining */ + if (tmp_lsparse) { + Lsparse = &myLsparse; + } + if (tmp_rsparse) { + Rsparse = &myRsparse; + } + IGRAPH_CHECK(igraph_scg_semiprojectors(groups, IGRAPH_SCG_STOCHASTIC, + L, R, Lsparse, Rsparse, myp, norm)); + if (tmp_groups) { + igraph_vector_destroy((igraph_vector_t*) groups); + IGRAPH_FINALLY_CLEAN(1); + } + if (!p && do_p) { + igraph_vector_destroy(myp); + IGRAPH_FINALLY_CLEAN(1); + } + if (tmp_vectors) { + igraph_matrix_complex_destroy(vectors); + IGRAPH_FINALLY_CLEAN(1); + } + if (Rsparse) { + IGRAPH_FINALLY(igraph_sparsemat_destroy, Rsparse); + } + if (Lsparse) { + IGRAPH_FINALLY(igraph_sparsemat_destroy, Lsparse); + } + + /* -------------------------------------------------------------------- */ + /* Compute coarse grained matrix/graph/sparse matrix */ + IGRAPH_CHECK(igraph_sparsemat_compress(Rsparse, &tmpsparse)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmpsparse); + IGRAPH_CHECK(igraph_sparsemat_transpose(&tmpsparse, &Rsparse_t, + /*values=*/ 1)); + igraph_sparsemat_destroy(&tmpsparse); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &Rsparse_t); + + IGRAPH_CHECK(igraph_i_scg_get_result(IGRAPH_SCG_STOCHASTIC, + mymatrix, mysparsemat, + Lsparse, &Rsparse_t, + scg_graph, scg_matrix, + scg_sparsemat, /*directed=*/ 1)); + + /* -------------------------------------------------------------------- */ + /* Clean up */ + + igraph_sparsemat_destroy(&Rsparse_t); + IGRAPH_FINALLY_CLEAN(1); + if (Lsparse) { + IGRAPH_FINALLY_CLEAN(1); + } + if (Rsparse) { + IGRAPH_FINALLY_CLEAN(1); + } + + if (graph) { + igraph_sparsemat_destroy(mysparsemat); + IGRAPH_FINALLY_CLEAN(1); + } else if (matrix) { + igraph_matrix_destroy(mymatrix); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_sparsemat_destroy(mysparsemat); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_scg_laplacian + * Spectral coarse graining, laplacian matrix. + * This function handles all the steps involved in the Spectral Coarse + * Graining (SCG) of some matrices and graphs as described in the + * reference below. + * + * \param graph The input graph. Exactly one of \p graph, \p matrix + * and \p sparsemat must be given, the other two must be \c NULL + * pointers. + * \param matrix The input matrix. Exactly one of \p graph, \p matrix + * and \p sparsemat must be given, the other two must be \c NULL + * pointers. + * \param sparsemat The input sparse matrix. Exactly one of \p graph, + * \p matrix and \p sparsemat must be given, the other two must be + * \c NULL pointers. + * \param ev A vector of positive integers giving the indexes of the + * eigenpairs to be preserved. 1 designates the eigenvalue with + * largest magnitude, 2 the one with second largest magnitude, etc. + * \param nt Positive integer. When \p algo is \c IGRAPH_SCG_OPTIMUM, + * it gives the number of groups to partition each eigenvector + * separately. When \p algo is \c IGRAPH_SCG_INTERV or \c + * IGRAPH_SCG_INTERV_KM, it gives the number of intervals to + * partition each eigenvector. This is ignored when \p algo is \c + * IGRAPH_SCG_EXACT. + * \param nt_vec A numeric vector of length one or the length must + * match the number of eigenvectors given in \p V, or a \c NULL + * pointer. If not \c NULL, then this argument gives the number of + * groups or intervals, and \p nt is ignored. Different number of + * groups or intervals can be specified for each eigenvector. + * \param algo The algorithm to solve the SCG problem. Possible + * values: \c IGRAPH_SCG_OPTIMUM, \c IGRAPH_SCG_INTERV_KM, \c + * IGRAPH_SCG_INTERV and \c IGRAPH_SCG_EXACT. Please see the + * details about them above. + * \param norm Either \c IGRAPH_SCG_NORM_ROW or \c IGRAPH_SCG_NORM_COL. + * Specifies whether the rows or the columns of the Laplacian + * matrix sum up to zero. + * \param direction Whether to work with left or right eigenvectors. + * Possible values: \c IGRAPH_SCG_DIRECTION_DEFAULT, \c + * IGRAPH_SCG_DIRECTION_LEFT, \c IGRAPH_SCG_DIRECTION_RIGHT. This + * argument is currently ignored and right eigenvectors are always + * used. + * \param values If this is not \c NULL and the eigenvectors are + * re-calculated, then the eigenvalues are stored here. + * \param vectors If this is not \c NULL, and not a zero-length + * matrix, then it is interpreted as the eigenvectors to use for + * the coarse-graining. Otherwise the eigenvectors are + * re-calculated, and they are stored here. (If this is not \c NULL.) + * \param groups If this is not \c NULL, and not a zero-length vector, + * then it is interpreted as the vector of group labels. (Group + * labels are integers from zero and are sequential.) Otherwise + * group labels are re-calculated and stored here, if this argument + * is not a null pointer. + * \param use_arpack Whether to use ARPACK for solving the + * eigenproblem. Currently ARPACK is not implemented. + * \param maxiter A positive integer giving the number of iterations + * of the k-means algorithm when \p algo is \c + * IGRAPH_SCG_INTERV_KM. It is ignored in other cases. A reasonable + * (initial) value for this argument is 100. + * \param scg_graph If not a \c NULL pointer, then the coarse-grained + * graph is returned here. + * \param scg_matrix If not a \c NULL pointer, then it must be an + * initialied matrix, and the coarse-grained matrix is returned + * here. + * \param scg_sparsemat If not a \c NULL pointer, then the coarse + * grained matrix is returned here, in sparse matrix form. + * \param L If not a \c NULL pointer, then it must be an initialized + * matrix and the left semi-projector is returned here. + * \param R If not a \c NULL pointer, then it must be an initialized + * matrix and the right semi-projector is returned here. + * \param Lsparse If not a \c NULL pointer, then the left + * semi-projector is returned here. + * \param Rsparse If not a \c NULL pointer, then the right + * semi-projector is returned here. + * \return Error code. + * + * Time complexity: TODO. + * + * \sa \ref igraph_scg_grouping(), \ref igraph_scg_semiprojectors(), + * \ref igraph_scg_stochastic() and \ref igraph_scg_adjacency(). + * + * \example examples/simple/scg3.c + */ + +int igraph_scg_laplacian(const igraph_t *graph, + const igraph_matrix_t *matrix, + const igraph_sparsemat_t *sparsemat, + const igraph_vector_t *ev, + igraph_integer_t nt, + const igraph_vector_t *nt_vec, + igraph_scg_algorithm_t algo, + igraph_scg_norm_t norm, + igraph_scg_direction_t direction, + igraph_vector_complex_t *values, + igraph_matrix_complex_t *vectors, + igraph_vector_t *groups, + igraph_bool_t use_arpack, + igraph_integer_t maxiter, + igraph_t *scg_graph, + igraph_matrix_t *scg_matrix, + igraph_sparsemat_t *scg_sparsemat, + igraph_matrix_t *L, + igraph_matrix_t *R, + igraph_sparsemat_t *Lsparse, + igraph_sparsemat_t *Rsparse) { + + igraph_matrix_t *mymatrix = (igraph_matrix_t*) matrix, real_matrix; + igraph_sparsemat_t *mysparsemat = (igraph_sparsemat_t*) sparsemat, + real_sparsemat; + int no_of_nodes; + igraph_real_t evmin, evmax; + igraph_arpack_options_t options; + igraph_eigen_which_t which; + /* eigenvectors are calculated and returned */ + igraph_bool_t do_vectors = vectors && igraph_matrix_complex_size(vectors) == 0; + /* groups are calculated */ + igraph_bool_t do_groups = !groups || igraph_vector_size(groups) == 0; + igraph_bool_t tmp_groups = !groups; + /* eigenvectors are not returned but must be calculated for groups */ + igraph_bool_t tmp_vectors = !do_vectors && do_groups; + igraph_matrix_complex_t myvectors; + igraph_vector_t mygroups; + int no_of_ev = (int) igraph_vector_size(ev); + igraph_bool_t tmp_lsparse = !Lsparse, tmp_rsparse = !Rsparse; + igraph_sparsemat_t myLsparse, myRsparse, tmpsparse, Rsparse_t; + + /* --------------------------------------------------------------------*/ + /* Argument checks */ + + IGRAPH_CHECK(igraph_i_scg_common_checks(graph, matrix, sparsemat, + ev, nt, nt_vec, + 0, vectors, groups, scg_graph, + scg_matrix, scg_sparsemat, /*p=*/ 0, + &evmin, &evmax)); + + if (graph) { + no_of_nodes = igraph_vcount(graph); + } else if (matrix) { + no_of_nodes = (int) igraph_matrix_nrow(matrix); + } else { + no_of_nodes = (int) igraph_sparsemat_nrow(sparsemat); + } + + /* -------------------------------------------------------------------- */ + /* Convert graph, if needed, get Laplacian matrix */ + + if (graph) { + mysparsemat = &real_sparsemat; + IGRAPH_CHECK(igraph_sparsemat_init(mysparsemat, 0, 0, 0)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, mysparsemat); + IGRAPH_CHECK(igraph_laplacian(graph, 0, mysparsemat, /*normalized=*/ 0, + /*weights=*/ 0)); + } else if (matrix) { + mymatrix = &real_matrix; + IGRAPH_MATRIX_INIT_FINALLY(mymatrix, no_of_nodes, no_of_nodes); + IGRAPH_CHECK(igraph_i_matrix_laplacian(matrix, mymatrix, norm)); + } else { /* sparsemat */ + mysparsemat = &real_sparsemat; + IGRAPH_CHECK(igraph_i_sparsemat_laplacian(sparsemat, mysparsemat, + norm == IGRAPH_SCG_NORM_COL)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, mysparsemat); + } + + /* -------------------------------------------------------------------- */ + /* Compute eigenpairs, if needed */ + + if (tmp_vectors) { + vectors = &myvectors; + IGRAPH_CHECK(igraph_matrix_complex_init(vectors, no_of_nodes, no_of_ev)); + IGRAPH_FINALLY(igraph_matrix_complex_destroy, vectors); + } + + if (do_vectors || tmp_vectors) { + igraph_matrix_complex_t tmp; + igraph_vector_t tmpev; + igraph_vector_complex_t tmpeval; + int i; + + which.pos = IGRAPH_EIGEN_SELECT; + which.il = (int) (no_of_nodes - evmax + 1); + which.iu = (int) (no_of_nodes - evmin + 1); + + if (values) { + IGRAPH_CHECK(igraph_vector_complex_init(&tmpeval, 0)); + IGRAPH_FINALLY(igraph_vector_complex_destroy, &tmpeval); + } + IGRAPH_CHECK(igraph_matrix_complex_init(&tmp, no_of_nodes, + which.iu - which.il + 1)); + IGRAPH_FINALLY(igraph_matrix_complex_destroy, &tmp); + IGRAPH_CHECK(igraph_eigen_matrix(mymatrix, mysparsemat, /*fun=*/ 0, + no_of_nodes, /*extra=*/ 0, use_arpack ? + IGRAPH_EIGEN_ARPACK : + IGRAPH_EIGEN_LAPACK, &which, &options, + /*storage=*/ 0, + values ? &tmpeval : 0, &tmp)); + + IGRAPH_VECTOR_INIT_FINALLY(&tmpev, no_of_ev); + for (i = 0; i < no_of_ev; i++) { + VECTOR(tmpev)[i] = evmax - VECTOR(*ev)[i]; + } + if (values) { + IGRAPH_CHECK(igraph_vector_complex_index(&tmpeval, values, &tmpev)); + } + IGRAPH_CHECK(igraph_matrix_complex_select_cols(&tmp, vectors, &tmpev)); + igraph_vector_destroy(&tmpev); + igraph_matrix_complex_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(2); + if (values) { + igraph_vector_complex_destroy(&tmpeval); + IGRAPH_FINALLY_CLEAN(1); + } + } + + /* -------------------------------------------------------------------- */ + /* Work out groups, if needed */ + /* TODO: use complex part as well */ + if (tmp_groups) { + groups = &mygroups; + IGRAPH_VECTOR_INIT_FINALLY((igraph_vector_t*)groups, no_of_nodes); + } + if (do_groups) { + igraph_matrix_t tmp; + IGRAPH_MATRIX_INIT_FINALLY(&tmp, 0, 0); + IGRAPH_CHECK(igraph_matrix_complex_real(vectors, &tmp)); + IGRAPH_CHECK(igraph_scg_grouping(&tmp, (igraph_vector_t*)groups, + nt, nt_vec, + IGRAPH_SCG_LAPLACIAN, algo, + /*p=*/ 0, maxiter)); + igraph_matrix_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + } + + /* -------------------------------------------------------------------- */ + /* Perform coarse graining */ + if (tmp_lsparse) { + Lsparse = &myLsparse; + } + if (tmp_rsparse) { + Rsparse = &myRsparse; + } + IGRAPH_CHECK(igraph_scg_semiprojectors(groups, IGRAPH_SCG_LAPLACIAN, + L, R, Lsparse, Rsparse, /*p=*/ 0, + norm)); + if (tmp_groups) { + igraph_vector_destroy((igraph_vector_t*) groups); + IGRAPH_FINALLY_CLEAN(1); + } + if (tmp_vectors) { + igraph_matrix_complex_destroy(vectors); + IGRAPH_FINALLY_CLEAN(1); + } + if (Rsparse) { + IGRAPH_FINALLY(igraph_sparsemat_destroy, Rsparse); + } + if (Lsparse) { + IGRAPH_FINALLY(igraph_sparsemat_destroy, Lsparse); + } + + /* -------------------------------------------------------------------- */ + /* Compute coarse grained matrix/graph/sparse matrix */ + IGRAPH_CHECK(igraph_sparsemat_compress(Rsparse, &tmpsparse)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmpsparse); + IGRAPH_CHECK(igraph_sparsemat_transpose(&tmpsparse, &Rsparse_t, + /*values=*/ 1)); + igraph_sparsemat_destroy(&tmpsparse); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &Rsparse_t); + + IGRAPH_CHECK(igraph_i_scg_get_result(IGRAPH_SCG_LAPLACIAN, + mymatrix, mysparsemat, + Lsparse, &Rsparse_t, + scg_graph, scg_matrix, + scg_sparsemat, /*directed=*/ 1)); + + /* -------------------------------------------------------------------- */ + /* Clean up */ + + igraph_sparsemat_destroy(&Rsparse_t); + IGRAPH_FINALLY_CLEAN(1); + if (Lsparse) { + IGRAPH_FINALLY_CLEAN(1); + } + if (Rsparse) { + IGRAPH_FINALLY_CLEAN(1); + } + + if (graph) { + igraph_sparsemat_destroy(mysparsemat); + IGRAPH_FINALLY_CLEAN(1); + } else if (matrix) { + igraph_matrix_destroy(mymatrix); + IGRAPH_FINALLY_CLEAN(1); + } else { + igraph_sparsemat_destroy(mysparsemat); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} diff --git a/src/scg_approximate_methods.c b/src/scg_approximate_methods.c new file mode 100644 index 0000000..ea45537 --- /dev/null +++ b/src/scg_approximate_methods.c @@ -0,0 +1,172 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2011-12 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + * SCGlib : A C library for the spectral coarse graining of matrices + * as described in the paper: Shrinking Matrices while preserving their + * eigenpairs with Application to the Spectral Coarse Graining of Graphs. + * Preprint available at + * + * Copyright (C) 2008 David Morton de Lachapelle + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * DESCRIPTION + * ----------- + * The intervals_method and intervals_plus_kmeans implements the + * methods of sec. 5.3.2 and sec. 5.3.3 of the above reference. + * They take an eigenvector 'v' as parameter and a vector 'breaks' + * of length 'nb', which provide the intervals used to cut 'v'. + * Then all components of 'v' that fall into the same interval are + * assigned the same group label in 'gr'. The group labels are + * positive consecutive integers starting from 0. + * The intervals_method function is adapted from bincode of the R + * base package. + * The intervals_plus_kmeans is initialized with regularly-spaced + * breaks, which rougly corresponds to the intervals_method. Then + * kmeans minimizes iteratively the objective function until it gets + * stuck in a (usually) local minimum, or until 'itermax' is reached. + * So far, the breaks_computation function allows computation of + * constant bins, as used in intervals_method, and of equidistant + * centers as used in intervals_plus_kmeans. + */ + +#include "scg_headers.h" +#include "igraph_error.h" +#include "igraph_types.h" +#include "igraph_vector.h" + +int igraph_i_intervals_plus_kmeans(const igraph_vector_t *v, int *gr, + int n, int n_interv, + int maxiter) { + int i; + igraph_vector_t centers; + + IGRAPH_VECTOR_INIT_FINALLY(¢ers, n_interv); + + igraph_i_breaks_computation(v, ¢ers, n_interv, 2); + IGRAPH_CHECK(igraph_i_kmeans_Lloyd(v, n, 1, ¢ers, n_interv, gr, + maxiter)); + + /*renumber the groups*/ + for (i = 0; i < n; i++) { + gr[i] = gr[i] - 1; + } + + igraph_vector_destroy(¢ers); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +int igraph_i_intervals_method(const igraph_vector_t *v, int *gr, int n, + int n_interv) { + int i, lo, hi, new; + const int lft = 1; + const int include_border = 1; + igraph_vector_t breaks; + + IGRAPH_VECTOR_INIT_FINALLY(&breaks, n_interv + 1); + + IGRAPH_CHECK(igraph_i_breaks_computation(v, &breaks, n_interv + 1, 1)); + + for (i = 0; i < n; i++) { + lo = 0; + hi = n_interv; + if (VECTOR(*v)[i] < VECTOR(breaks)[lo] || + VECTOR(breaks)[hi] < VECTOR(*v)[i] || + (VECTOR(*v)[i] == VECTOR(breaks)[lft ? hi : lo] && !include_border)) { + /* Do nothing */ + } else { + while (hi - lo >= 2) { + new = (hi + lo) / 2; + if (VECTOR(*v)[i] > VECTOR(breaks)[new] || + (lft && VECTOR(*v)[i] == VECTOR(breaks)[new])) { + lo = new; + } else { + hi = new; + } + } + gr[i] = lo; + } + } + igraph_vector_destroy(&breaks); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +int igraph_i_breaks_computation(const igraph_vector_t *v, + igraph_vector_t *breaks, + int nb, int method) { + int i; + igraph_real_t eps, vmin, vmax; + igraph_vector_minmax(v, &vmin, &vmax); + + if (vmax == vmin) { + IGRAPH_ERROR("There is only one (repeated) value in argument 'v' " + "of bin_size_computation()", IGRAPH_EINVAL); + } + + if (nb < 2) { + IGRAPH_ERROR("'nb' in bin_size_computation() must be >= 2", + IGRAPH_EINVAL); + } + + switch (method) { + case 1: /* constant bins for fixed-size intervals method */ + eps = (vmax - vmin) / (igraph_real_t)(nb - 1); + VECTOR(*breaks)[0] = vmin; + for (i = 1; i < nb - 1; i++) { + VECTOR(*breaks)[i] = VECTOR(*breaks)[i - 1] + eps; + } + VECTOR(*breaks)[nb - 1] = vmax; + break; + case 2: /* equidistant centers for kmeans */ + eps = (vmax - vmin) / (igraph_real_t)nb; + VECTOR(*breaks)[0] = vmin + eps / 2.; + for (i = 1; i < nb; i++) { + VECTOR(*breaks)[i] = VECTOR(*breaks)[i - 1] + eps; + } + break; + /* TODO: implement logarithmic binning for power-law-like distributions */ + default: + IGRAPH_ERROR("Internal SCG error, this should ot happen", + IGRAPH_FAILURE); + } + + return 0; +} diff --git a/src/scg_exact_scg.c b/src/scg_exact_scg.c new file mode 100644 index 0000000..a339cfd --- /dev/null +++ b/src/scg_exact_scg.c @@ -0,0 +1,68 @@ +/* + * SCGlib : A C library for the spectral coarse graining of matrices + * as described in the paper: Shrinking Matrices while preserving their + * eigenpairs with Application to the Spectral Coarse Graining of Graphs. + * Preprint available at + * + * Copyright (C) 2008 David Morton de Lachapelle + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * DESCRIPTION + * ----------- + * The exact_coarse_graining function labels all the objects whose + * components in 'v' are equal. The result is stored in 'gr'. Labels + * are positive consecutive integers starting from 0. + * See also Section 5.4.1 (last paragraph) of the above reference. + */ + +#include "scg_headers.h" +#include "igraph_memory.h" +#include + +int igraph_i_exact_coarse_graining(const igraph_real_t *v, + int *gr, const int n) { + int i, gr_nb; + igraph_i_scg_indval_t *w = igraph_Calloc(n, igraph_i_scg_indval_t); + + if (!w) { + IGRAPH_ERROR("SCG error", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, w); + + for (i = 0; i < n; i++) { + w[i].val = v[i]; + w[i].ind = i; + } + + qsort(w, (size_t) n, sizeof(igraph_i_scg_indval_t), igraph_i_compare_ind_val); + + gr_nb = 0; + gr[w[0].ind] = gr_nb; + for (i = 1; i < n; i++) { + if ( fabs(w[i].val - w[i - 1].val) > 1e-14 ) { + gr_nb++; + } + gr[w[i].ind] = gr_nb; + } + + igraph_Free(w); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + + diff --git a/src/scg_headers.h b/src/scg_headers.h new file mode 100644 index 0000000..090caae --- /dev/null +++ b/src/scg_headers.h @@ -0,0 +1,128 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2011-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* + * SCGlib : A C library for the spectral coarse graining of matrices + * as described in the paper: Shrinking Matrices while preserving their + * eigenpairs with Application to the Spectral Coarse Graining of Graphs. + * Preprint available at + * + * Copyright (C) 2008 David Morton de Lachapelle + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * DESCRIPTION + * ----------- + * This file contains the headers of the library SCGlib. + * For use with R software define + * the constant R_COMPIL and refer to the R documentation to compile + * a dynamic library. The scg_r_wrapper function should be useful. + */ + +#ifndef SCG_HEADERS_H +#define SCG_HEADERS_H + +#include "igraph_types.h" +#include "igraph_vector.h" + +#include +#include + +typedef struct ind_val { + int ind; + igraph_real_t val; +} igraph_i_scg_indval_t; + +int igraph_i_compare_ind_val(const void *a, const void *b); + +typedef struct groups { + int ind; + int n; + int* gr; +} igraph_i_scg_groups_t; + +/*------------------------------------------------- +------------DEFINED IN scg_approximate_methods.c--- +---------------------------------------------------*/ + +int igraph_i_breaks_computation(const igraph_vector_t *v, + igraph_vector_t *breaks, int nb, + int method); +int igraph_i_intervals_plus_kmeans(const igraph_vector_t *v, int *gr, + int n, int n_interv, + int maxiter); +int igraph_i_intervals_method(const igraph_vector_t *v, int *gr, + int n, int n_interv); + +/*------------------------------------------------- +------------DEFINED IN scg_optimal_method.c-------- +---------------------------------------------------*/ + +int igraph_i_cost_matrix(igraph_real_t *Cv, const igraph_i_scg_indval_t *vs, + int n, int matrix, const igraph_vector_t *ps); +int igraph_i_optimal_partition(const igraph_real_t *v, int *gr, int n, int nt, + int matrix, const igraph_real_t *p, + igraph_real_t *value); + +/*------------------------------------------------- +------------DEFINED IN scg_kmeans.c---------------- +---------------------------------------------------*/ + +int igraph_i_kmeans_Lloyd(const igraph_vector_t *x, int n, + int p, igraph_vector_t *centers, + int k, int *cl, int maxiter); + +/*------------------------------------------------- +------------DEFINED IN scg_exact_scg.c------------- +---------------------------------------------------*/ + +int igraph_i_exact_coarse_graining(const igraph_real_t *v, int *gr, + int n); + +/*------------------------------------------------- +------------DEFINED IN scg_utils.c----------------- +---------------------------------------------------*/ + +int igraph_i_compare_groups(const void *a, const void *b); +int igraph_i_compare_real(const void *a, const void *b); +int igraph_i_compare_int(const void *a, const void *b); + +igraph_real_t *igraph_i_real_sym_matrix(int size); +#define igraph_i_real_sym_mat_get(S,i,j) S[i+j*(j+1)/2] +#define igraph_i_real_sym_mat_set(S,i,j,val) S[i+j*(j+1)/2] = val +#define igraph_i_free_real_sym_matrix(S) igraph_Free(S) + +#endif diff --git a/src/scg_kmeans.c b/src/scg_kmeans.c new file mode 100644 index 0000000..02b0018 --- /dev/null +++ b/src/scg_kmeans.c @@ -0,0 +1,103 @@ +/* + * SCGlib : A C library for the spectral coarse graining of matrices + * as described in the paper: Shrinking Matrices while preserving their + * eigenpairs with Application to the Spectral Coarse Graining of Graphs. + * Preprint available at + * + * Copyright (C) 2008 David Morton de Lachapelle + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * DESCRIPTION + * ----------- + * The kmeans_Lloyd function is adapted from the R-stats package. + * It perfoms Lloyd's k-means clustering on a p x n data matrix + * stored row-wise in a vector 'x'. 'cen' contains k initial centers. + * The group label to which each object belongs is stored in 'cl'. + * Labels are positive consecutive integers starting from 0. + * See also Section 5.3.3 of the above reference. + */ + +#include "scg_headers.h" + +int igraph_i_kmeans_Lloyd(const igraph_vector_t *x, int n, int p, + igraph_vector_t *cen, int k, int *cl, int maxiter) { + + int iter, i, j, c, it, inew = 0; + igraph_real_t best, dd, tmp; + int updated; + igraph_vector_int_t nc; + + IGRAPH_CHECK(igraph_vector_int_init(&nc, k)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &nc); + + for (i = 0; i < n; i++) { + cl[i] = -1; + } + for (iter = 0; iter < maxiter; iter++) { + updated = 0; + for (i = 0; i < n; i++) { + /* find nearest centre for each point */ + best = IGRAPH_INFINITY; + for (j = 0; j < k; j++) { + dd = 0.0; + for (c = 0; c < p; c++) { + tmp = VECTOR(*x)[i + n * c] - VECTOR(*cen)[j + k * c]; + dd += tmp * tmp; + } + if (dd < best) { + best = dd; + inew = j + 1; + } + } + if (cl[i] != inew) { + updated = 1; + cl[i] = inew; + } + } + if (!updated) { + break; + } + + /* update each centre */ + for (j = 0; j < k * p; j++) { + VECTOR(*cen)[j] = 0.0; + } + for (j = 0; j < k; j++) { + VECTOR(nc)[j] = 0; + } + for (i = 0; i < n; i++) { + it = cl[i] - 1; + VECTOR(nc)[it]++; + for (c = 0; c < p; c++) { + VECTOR(*cen)[it + c * k] += VECTOR(*x)[i + c * n]; + } + } + for (j = 0; j < k * p; j++) { + VECTOR(*cen)[j] /= VECTOR(nc)[j % k]; + } + } + igraph_vector_int_destroy(&nc); + IGRAPH_FINALLY_CLEAN(1); + + /* convervenge check */ + if (iter >= maxiter - 1) { + IGRAPH_ERROR("Lloyd k-means did not converge", IGRAPH_FAILURE); + } + + return 0; +} + diff --git a/src/scg_optimal_method.c b/src/scg_optimal_method.c new file mode 100644 index 0000000..85e9809 --- /dev/null +++ b/src/scg_optimal_method.c @@ -0,0 +1,240 @@ +/* + * SCGlib : A C library for the spectral coarse graining of matrices + * as described in the paper: Shrinking Matrices while preserving their + * eigenpairs with Application to the Spectral Coarse Graining of Graphs. + * Preprint available at + * + * Copyright (C) 2008 David Morton de Lachapelle + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * DESCRIPTION + * ----------- + * This file implements algorithm 5.8 of the above reference. + * The optimal_partition function returns the minimizing partition + * with size 'nt' of the objective function ||v-Pv||, where P is + * a problem-specific projector. So far, Symmetric (matrix=1), + * Laplacian (matrix=2) and Stochastic (matrix=3) projectors + * have been implemented (the cost_matrix function below). + * In the stochastic case, 'p' is expected to be a valid propability + * vector. In all other cases, 'p' is ignored and can be set to NULL. + * The group labels are given in 'gr' as positive consecutive integers + * starting from 0. + */ + +#include "scg_headers.h" +#include "igraph_error.h" +#include "igraph_memory.h" +#include "igraph_matrix.h" +#include "igraph_vector.h" + +int igraph_i_optimal_partition(const igraph_real_t *v, int *gr, int n, + int nt, int matrix, const igraph_real_t *p, + igraph_real_t *value) { + + int i, non_ties, q, j, l, part_ind, col; + igraph_i_scg_indval_t *vs = igraph_Calloc(n, igraph_i_scg_indval_t); + igraph_real_t *Cv, temp, sumOfSquares; + igraph_vector_t ps; + igraph_matrix_t F; + igraph_matrix_int_t Q; + + /*----------------------------------------------- + -----Sorts v and counts non-ties----------------- + -----------------------------------------------*/ + + if (!vs) { + IGRAPH_ERROR("SCG error", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, vs); + + for (i = 0; i < n; i++) { + vs[i].val = v[i]; + vs[i].ind = i; + } + + qsort(vs, (size_t) n, sizeof(igraph_i_scg_indval_t), + igraph_i_compare_ind_val); + + non_ties = 1; + for (i = 1; i < n; i++) { + if (vs[i].val < vs[i - 1].val - 1e-14 || + vs[i].val > vs[i - 1].val + 1e-14) { + non_ties++; + } + } + + if (nt >= non_ties) { + IGRAPH_ERROR("`Invalid number of intervals, should be smaller than " + "number of unique values in V", IGRAPH_EINVAL); + } + + /*------------------------------------------------ + ------Computes Cv, the matrix of costs------------ + ------------------------------------------------*/ + Cv = igraph_i_real_sym_matrix(n); + if (!Cv) { + IGRAPH_ERROR("SCG error", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, Cv); + + /* if stochastic SCG orders p */ + if (matrix == 3) { + IGRAPH_VECTOR_INIT_FINALLY(&ps, n); + for (i = 0; i < n; i++) { + VECTOR(ps)[i] = p[vs[i].ind]; + } + } + + IGRAPH_CHECK(igraph_i_cost_matrix(Cv, vs, n, matrix, &ps)); + if (matrix == 3) { + igraph_vector_destroy(&ps); + IGRAPH_FINALLY_CLEAN(1); + } + /*------------------------------------------------- + -------Fills up matrices F and Q------------------- + -------------------------------------------------*/ + /*here j also is a counter but the use of unsigned variables + is to be proscribed in "for (unsigned int j=...;j>=0;j--)", + for such loops never ends!*/ + + IGRAPH_MATRIX_INIT_FINALLY(&F, nt, n); + IGRAPH_CHECK(igraph_matrix_int_init(&Q, nt, n)); + IGRAPH_FINALLY(igraph_matrix_destroy, &Q); + + for (i = 0; i < n; i++) { + MATRIX(Q, 0, i)++; + } + for (i = 0; i < nt; i++) { + MATRIX(Q, i, i) = i + 1; + } + + for (i = 0; i < n; i++) { + MATRIX(F, 0, i) = igraph_i_real_sym_mat_get(Cv, 0, i); + } + + for (i = 1; i < nt; i++) + for (j = i + 1; j < n; j++) { + MATRIX(F, i, j) = MATRIX(F, i - 1, i - 1) + igraph_i_real_sym_mat_get(Cv, i, j); + MATRIX(Q, i, j) = 2; + + for (q = i - 1; q <= j - 1; q++) { + temp = MATRIX(F, i - 1, q) + igraph_i_real_sym_mat_get(Cv, q + 1, j); + if (temp < MATRIX(F, i, j)) { + MATRIX(F, i, j) = temp; + MATRIX(Q, i, j) = q + 2; + } + } + } + igraph_i_free_real_sym_matrix(Cv); + IGRAPH_FINALLY_CLEAN(1); + + /*-------------------------------------------------- + -------Back-tracks through Q to work out the groups- + --------------------------------------------------*/ + part_ind = nt; + col = n - 1; + + for (j = nt - 1; j >= 0; j--) { + for (i = MATRIX(Q, j, col) - 1; i <= col; i++) { + gr[vs[i].ind] = part_ind - 1; + } + if (MATRIX(Q, j, col) != 2) { + col = MATRIX(Q, j, col) - 2; + part_ind -= 1; + } else { + if (j > 1) { + for (l = 0; l <= (j - 1); l++) { + gr[vs[l].ind] = l; + } + break; + } else { + col = MATRIX(Q, j, col) - 2; + part_ind -= 1; + } + } + } + + sumOfSquares = MATRIX(F, nt - 1, n - 1); + + igraph_matrix_destroy(&F); + igraph_matrix_int_destroy(&Q); + igraph_Free(vs); + IGRAPH_FINALLY_CLEAN(3); + + if (value) { + *value = sumOfSquares; + } + return 0; +} + +int igraph_i_cost_matrix(igraph_real_t*Cv, const igraph_i_scg_indval_t *vs, + int n, int matrix, const igraph_vector_t *ps) { + + /* if symmetric of Laplacian SCG -> same Cv */ + if (matrix == 1 || matrix == 2) { + int i, j; + igraph_vector_t w, w2; + + IGRAPH_VECTOR_INIT_FINALLY(&w, n + 1); + IGRAPH_VECTOR_INIT_FINALLY(&w2, n + 1); + + VECTOR(w)[1] = vs[0].val; + VECTOR(w2)[1] = vs[0].val * vs[0].val; + + for (i = 2; i <= n; i++) { + VECTOR(w)[i] = VECTOR(w)[i - 1] + vs[i - 1].val; + VECTOR(w2)[i] = VECTOR(w2)[i - 1] + vs[i - 1].val * vs[i - 1].val; + } + + for (i = 0; i < n; i++) { + for (j = i + 1; j < n; j++) { + igraph_real_t v = (VECTOR(w2)[j + 1] - VECTOR(w2)[i]) - + (VECTOR(w)[j + 1] - VECTOR(w)[i]) * (VECTOR(w)[j + 1] - VECTOR(w)[i]) / + (j - i + 1); + igraph_i_real_sym_mat_set(Cv, i, j, v); + } + } + + igraph_vector_destroy(&w); + igraph_vector_destroy(&w2); + IGRAPH_FINALLY_CLEAN(2); + } + /* if stochastic */ + /* TODO: optimize it to O(n^2) instead of O(n^3) (as above) */ + if (matrix == 3) { + int i, j, k; + igraph_real_t t1, t2; + for (i = 0; i < n; i++) { + for (j = i + 1; j < n; j++) { + t1 = t2 = 0; + for (k = i; k < j; k++) { + t1 += VECTOR(*ps)[k]; + t2 += VECTOR(*ps)[k] * vs[k].val; + } + t1 = t2 / t1; + t2 = 0; + for (k = i; k < j; k++) { + t2 += (vs[k].val - t1) * (vs[k].val - t1); + } + igraph_i_real_sym_mat_set(Cv, i, j, t2); + } + } + } + + return 0; +} + diff --git a/src/scg_utils.c b/src/scg_utils.c new file mode 100644 index 0000000..18258bc --- /dev/null +++ b/src/scg_utils.c @@ -0,0 +1,93 @@ +/* + * SCGlib : A C library for the spectral coarse graining of matrices + * as described in the paper: Shrinking Matrices while preserving their + * eigenpairs with Application to the Spectral Coarse Graining of Graphs. + * Preprint available at + * + * Copyright (C) 2008 David Morton de Lachapelle + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * DESCRIPTION + * ----------- + * This files contains the data structures and error handing + * functions used throughout the SCGlib. + */ + +#include "scg_headers.h" +#include "igraph_error.h" +#include "igraph_memory.h" + +/*to be used with qsort and struct ind_val arrays */ +int igraph_i_compare_ind_val(const void *a, const void *b) { + igraph_i_scg_indval_t *arg1 = (igraph_i_scg_indval_t *) a; + igraph_i_scg_indval_t *arg2 = (igraph_i_scg_indval_t *) b; + + if ( arg1->val < arg2->val ) { + return -1; + } else if ( arg1->val == arg2->val ) { + return 0; + } else { + return 1; + } +} + +/*to be used with qsort and struct groups*/ +int igraph_i_compare_groups(const void *a, const void *b) { + igraph_i_scg_groups_t *arg1 = (igraph_i_scg_groups_t *) a; + igraph_i_scg_groups_t *arg2 = (igraph_i_scg_groups_t *) b; + int i; + for (i = 0; i < arg1->n; i++) { + if (arg1->gr[i] > arg2->gr[i]) { + return 1; + } else if (arg1->gr[i] < arg2->gr[i]) { + return -1; + } + } + return 0; +} + +/*to be used with qsort and real_vectors */ +int igraph_i_compare_real(const void *a, const void *b) { + igraph_real_t arg1 = * (igraph_real_t *) a; + igraph_real_t arg2 = * (igraph_real_t *) b; + + if (arg1 < arg2) { + return -1; + } else if (arg1 == arg2) { + return 0; + } else { + return 1; + } +} + +/*to be used with qsort and integer vectors */ +int igraph_i_compare_int(const void *a, const void *b) { + int arg1 = * (int *) a; + int arg2 = * (int *) b; + return (arg1 - arg2); +} + +/* allocate a igraph_real_t symmetrix matrix with dimension size x size + in vector format*/ +igraph_real_t *igraph_i_real_sym_matrix(const int size) { + igraph_real_t *S = igraph_Calloc(size * (size + 1) / 2, igraph_real_t); + if (!S) { + igraph_error("allocation failure in real_sym_matrix()", + __FILE__, __LINE__, IGRAPH_ENOMEM); + } + return S; +} diff --git a/src/separators.c b/src/separators.c new file mode 100644 index 0000000..a893a4c --- /dev/null +++ b/src/separators.c @@ -0,0 +1,833 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_separators.h" +#include "igraph_memory.h" +#include "igraph_adjlist.h" +#include "igraph_dqueue.h" +#include "igraph_vector.h" +#include "igraph_interface.h" +#include "igraph_flow.h" +#include "igraph_components.h" +#include "igraph_structural.h" +#include "igraph_interrupt_internal.h" + +static int igraph_i_is_separator(const igraph_t *graph, + igraph_vit_t *vit, + long int except, + igraph_bool_t *res, + igraph_vector_bool_t *removed, + igraph_dqueue_t *Q, + igraph_vector_t *neis, + long int no_of_nodes) { + + long int start = 0; + + if (IGRAPH_VIT_SIZE(*vit) >= no_of_nodes - 1) { + /* Just need to check that we really have at least n-1 vertices in it */ + igraph_vector_bool_t hit; + long int nohit = 0; + IGRAPH_CHECK(igraph_vector_bool_init(&hit, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &hit); + for (IGRAPH_VIT_RESET(*vit); + !IGRAPH_VIT_END(*vit); + IGRAPH_VIT_NEXT(*vit)) { + long int v = IGRAPH_VIT_GET(*vit); + if (!VECTOR(hit)[v]) { + nohit++; + VECTOR(hit)[v] = 1; + } + } + igraph_vector_bool_destroy(&hit); + IGRAPH_FINALLY_CLEAN(1); + if (nohit >= no_of_nodes - 1) { + *res = 0; + return 0; + } + } + + /* Remove the given vertices from the graph, do a breadth-first + search and check the number of components */ + + if (except < 0) { + for (IGRAPH_VIT_RESET(*vit); + !IGRAPH_VIT_END(*vit); + IGRAPH_VIT_NEXT(*vit)) { + VECTOR(*removed)[ (long int) IGRAPH_VIT_GET(*vit) ] = 1; + } + } else { + /* There is an exception */ + long int i; + for (i = 0, IGRAPH_VIT_RESET(*vit); + i < except; + i++, IGRAPH_VIT_NEXT(*vit)) { + VECTOR(*removed)[ (long int) IGRAPH_VIT_GET(*vit) ] = 1; + } + for (IGRAPH_VIT_NEXT(*vit); + !IGRAPH_VIT_END(*vit); + IGRAPH_VIT_NEXT(*vit)) { + VECTOR(*removed)[ (long int) IGRAPH_VIT_GET(*vit) ] = 1; + } + } + + /* Look for the first node that is not removed */ + while (start < no_of_nodes && VECTOR(*removed)[start]) { + start++; + } + + if (start == no_of_nodes) { + IGRAPH_ERROR("All vertices are included in the separator", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_dqueue_push(Q, start)); + VECTOR(*removed)[start] = 1; + while (!igraph_dqueue_empty(Q)) { + long int node = (long int) igraph_dqueue_pop(Q); + long int j, n; + IGRAPH_CHECK(igraph_neighbors(graph, neis, (igraph_integer_t) node, IGRAPH_ALL)); + n = igraph_vector_size(neis); + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + if (!VECTOR(*removed)[nei]) { + IGRAPH_CHECK(igraph_dqueue_push(Q, nei)); + VECTOR(*removed)[nei] = 1; + } + } + } + + /* Look for the next node that was neighter removed, not visited */ + while (start < no_of_nodes && VECTOR(*removed)[start]) { + start++; + } + + /* If there is another component, then we have a separator */ + *res = (start < no_of_nodes); + + return 0; +} + +/** + * \function igraph_is_separator + * Decides whether the removal of a set of vertices disconnects the graph + * + * \param graph The input graph. It may be directed, but edge + * directions are ignored. + * \param condidate The candidate separator. It must not contain all + * vertices. + * \param res Pointer to a boolean variable, the result is stored here. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number vertices and edges. + * + * \example examples/simple/igraph_is_separator.c + */ + +int igraph_is_separator(const igraph_t *graph, + const igraph_vs_t candidate, + igraph_bool_t *res) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_bool_t removed; + igraph_dqueue_t Q; + igraph_vector_t neis; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vit_create(graph, candidate, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_CHECK(igraph_vector_bool_init(&removed, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &removed); + IGRAPH_CHECK(igraph_dqueue_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &Q); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + IGRAPH_CHECK(igraph_i_is_separator(graph, &vit, -1, res, &removed, + &Q, &neis, no_of_nodes)); + + igraph_vector_destroy(&neis); + igraph_dqueue_destroy(&Q); + igraph_vector_bool_destroy(&removed); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +/** + * \function igraph_is_minimal_separator + * Decides whether a set of vertices is a minimal separator + * + * A set of vertices is a minimal separator, if the removal of the + * vertices disconnects the graph, and this is not true for any subset + * of the set. + * + *
    This implementation first checks that the given + * candidate is a separator, by calling \ref + * igraph_is_separator(). If it is a separator, then it checks that + * each subset of size n-1, where n is the size of the candidate, is + * not a separator. + * \param graph The input graph. It may be directed, but edge + * directions are ignored. + * \param candidate Pointer to a vector of long integers, the + * candidate minimal separator. + * \param res Pointer to a boolean variable, the result is stored + * here. + * \return Error code. + * + * Time complexity: O(n(|V|+|E|)), |V| is the number of vertices, |E| + * is the number of edges, n is the number vertices in the candidate + * separator. + * + * \example examples/simple/igraph_is_minimal_separator.c + */ + +int igraph_is_minimal_separator(const igraph_t *graph, + const igraph_vs_t candidate, + igraph_bool_t *res) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_bool_t removed; + igraph_dqueue_t Q; + igraph_vector_t neis; + long int candsize; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vit_create(graph, candidate, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + candsize = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_vector_bool_init(&removed, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &removed); + IGRAPH_CHECK(igraph_dqueue_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &Q); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + /* Is it a separator at all? */ + IGRAPH_CHECK(igraph_i_is_separator(graph, &vit, -1, res, &removed, + &Q, &neis, no_of_nodes)); + if (!(*res)) { + /* Not a separator at all, nothing to do, *res is already set */ + } else if (candsize == 0) { + /* Nothing to do, minimal, *res is already set */ + } else { + /* General case, we need to remove each vertex from 'candidate' + * and check whether the remainder is a separator. If this is + * false for all vertices, then 'candidate' is a minimal + * separator. + */ + long int i; + for (i = 0, *res = 0; i < candsize && (!*res); i++) { + igraph_vector_bool_null(&removed); + IGRAPH_CHECK(igraph_i_is_separator(graph, &vit, i, res, &removed, + &Q, &neis, no_of_nodes)); + } + (*res) = (*res) ? 0 : 1; /* opposite */ + } + + igraph_vector_destroy(&neis); + igraph_dqueue_destroy(&Q); + igraph_vector_bool_destroy(&removed); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +/* --------------------------------------------------------------------*/ + +#define UPDATEMARK() do { \ + (*mark)++; \ + if (!(*mark)) { \ + igraph_vector_null(leaveout); \ + (*mark)=1; \ + } \ + } while (0) + +static int igraph_i_clusters_leaveout(const igraph_adjlist_t *adjlist, + igraph_vector_t *components, + igraph_vector_t *leaveout, + unsigned long int *mark, + igraph_dqueue_t *Q) { + + /* Another trick: we use the same 'leaveout' vector to mark the + * vertices that were already found in the BFS + */ + + long int i, no_of_nodes = igraph_adjlist_size(adjlist); + + igraph_dqueue_clear(Q); + igraph_vector_clear(components); + + for (i = 0; i < no_of_nodes; i++) { + + if (VECTOR(*leaveout)[i] == *mark) { + continue; + } + + VECTOR(*leaveout)[i] = *mark; + igraph_dqueue_push(Q, i); + igraph_vector_push_back(components, i); + + while (!igraph_dqueue_empty(Q)) { + long int act_node = (long int) igraph_dqueue_pop(Q); + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, act_node); + long int j, n = igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + if (VECTOR(*leaveout)[nei] == *mark) { + continue; + } + IGRAPH_CHECK(igraph_dqueue_push(Q, nei)); + VECTOR(*leaveout)[nei] = *mark; + igraph_vector_push_back(components, nei); + } + } + + igraph_vector_push_back(components, -1); + } + + UPDATEMARK(); + + return 0; +} + +static igraph_bool_t igraph_i_separators_newsep(const igraph_vector_ptr_t *comps, + const igraph_vector_t *newc) { + + long int co, nocomps = igraph_vector_ptr_size(comps); + + for (co = 0; co < nocomps; co++) { + igraph_vector_t *act = VECTOR(*comps)[co]; + if (igraph_vector_all_e(act, newc)) { + return 0; + } + } + + /* If not found, then it is new */ + return 1; +} + +static int igraph_i_separators_store(igraph_vector_ptr_t *separators, + const igraph_adjlist_t *adjlist, + igraph_vector_t *components, + igraph_vector_t *leaveout, + unsigned long int *mark, + igraph_vector_t *sorter) { + + /* We need to stote N(C), the neighborhood of C, but only if it is + * not already stored among the separators. + */ + + long int cptr = 0, next, complen = igraph_vector_size(components); + + while (cptr < complen) { + long int saved = cptr; + igraph_vector_clear(sorter); + + /* Calculate N(C) for the next C */ + + while ( (next = (long int) VECTOR(*components)[cptr++]) != -1) { + VECTOR(*leaveout)[next] = *mark; + } + cptr = saved; + + while ( (next = (long int) VECTOR(*components)[cptr++]) != -1) { + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, next); + long int j, nn = igraph_vector_int_size(neis); + for (j = 0; j < nn; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + if (VECTOR(*leaveout)[nei] != *mark) { + igraph_vector_push_back(sorter, nei); + VECTOR(*leaveout)[nei] = *mark; + } + } + } + igraph_vector_sort(sorter); + + UPDATEMARK(); + + /* Add it to the list of separators, if it is new */ + + if (igraph_i_separators_newsep(separators, sorter)) { + igraph_vector_t *newc = igraph_Calloc(1, igraph_vector_t); + if (!newc) { + IGRAPH_ERROR("Cannot calculate minimal separators", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newc); + igraph_vector_copy(newc, sorter); + IGRAPH_FINALLY(igraph_vector_destroy, newc); + IGRAPH_CHECK(igraph_vector_ptr_push_back(separators, newc)); + IGRAPH_FINALLY_CLEAN(2); + } + } /* while cptr < complen */ + + return 0; +} + +static void igraph_i_separators_free(igraph_vector_ptr_t *separators) { + long int i, n = igraph_vector_ptr_size(separators); + for (i = 0; i < n; i++) { + igraph_vector_t *vec = VECTOR(*separators)[i]; + if (vec) { + igraph_vector_destroy(vec); + igraph_Free(vec); + } + } +} + +/** + * \function igraph_all_minimal_st_separators + * List all vertex sets that are minimal (s,t) separators for some s and t + * + * This function lists all vertex sets that are minimal (s,t) + * separators for some (s,t) vertex pair. + * + * See more about the implemented algorithm in + * Anne Berry, Jean-Paul Bordat and Olivier Cogis: Generating All the + * Minimal Separators of a Graph, In: Peter Widmayer, Gabriele Neyer + * and Stephan Eidenbenz (editors): Graph-theoretic concepts in + * computer science, 1665, 167--172, 1999. Springer. + * + * \param graph The input graph. It may be directed, but edge + * directions are ignored. + * \param separators An initialized pointer vector, the separators + * are stored here. It is a list of pointers to igraph_vector_t + * objects. Each vector will contain the ids of the vertices in + * the separator. + * To free all memory allocated for \c separators, you need call + * \ref igraph_vector_destroy() and then \ref igraph_free() on + * each element, before destroying the pointer vector itself. + * \return Error code. + * + * Time complexity: O(n|V|^3), |V| is the number of vertices, n is the + * number of separators. + * + * \example examples/simple/igraph_minimal_separators.c + */ + +int igraph_all_minimal_st_separators(const igraph_t *graph, + igraph_vector_ptr_t *separators) { + + /* + * Some notes about the tricks used here. For finding the components + * of the graph after removing some vertices, we do the + * following. First we mark the vertices with the actual mark stamp + * (mark), then run breadth-first search on the graph, but not + * considering the marked vertices. Then we increase the mark. If + * there is integer overflow here, then we zero out the mark and set + * it to one. (We might as well just always zero it out.) + * + * For each separator the vertices are stored in vertex id order. + * This facilitates the comparison of the separators when we find a + * potential new candidate. + * + * To keep track of which separator we already used as a basis, we + * keep a boolean vector (already_tried). The try_next pointer show + * the next separator to try as a basis. + */ + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t leaveout; + igraph_vector_bool_t already_tried; + long int try_next = 0; + unsigned long int mark = 1; + long int v; + + igraph_adjlist_t adjlist; + igraph_vector_t components; + igraph_dqueue_t Q; + igraph_vector_t sorter; + + igraph_vector_ptr_clear(separators); + IGRAPH_FINALLY(igraph_i_separators_free, separators); + + IGRAPH_CHECK(igraph_vector_init(&leaveout, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_destroy, &leaveout); + IGRAPH_CHECK(igraph_vector_bool_init(&already_tried, 0)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &already_tried); + IGRAPH_CHECK(igraph_vector_init(&components, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, &components); + IGRAPH_CHECK(igraph_vector_reserve(&components, no_of_nodes * 2)); + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + IGRAPH_CHECK(igraph_dqueue_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &Q); + IGRAPH_CHECK(igraph_vector_init(&sorter, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, &sorter); + IGRAPH_CHECK(igraph_vector_reserve(&sorter, no_of_nodes)); + + /* --------------------------------------------------------------- + * INITIALIZATION, we check whether the neighborhoods of the + * vertices separate the graph. The ones that do will form the + * initial basis. + */ + + for (v = 0; v < no_of_nodes; v++) { + + /* Mark v and its neighbors */ + igraph_vector_int_t *neis = igraph_adjlist_get(&adjlist, v); + long int i, n = igraph_vector_int_size(neis); + VECTOR(leaveout)[v] = mark; + for (i = 0; i < n; i++) { + long int nei = (long int) VECTOR(*neis)[i]; + VECTOR(leaveout)[nei] = mark; + } + + /* Find the components */ + IGRAPH_CHECK(igraph_i_clusters_leaveout(&adjlist, &components, &leaveout, + &mark, &Q)); + + /* Store the corresponding separators, N(C) for each component C */ + IGRAPH_CHECK(igraph_i_separators_store(separators, &adjlist, &components, + &leaveout, &mark, &sorter)); + + } + + /* --------------------------------------------------------------- + * GENERATION, we need to use all already found separators as + * basis and see if they generate more separators + */ + + while (try_next < igraph_vector_ptr_size(separators)) { + igraph_vector_t *basis = VECTOR(*separators)[try_next]; + long int b, basislen = igraph_vector_size(basis); + for (b = 0; b < basislen; b++) { + + /* Remove N(x) U basis */ + long int x = (long int) VECTOR(*basis)[b]; + igraph_vector_int_t *neis = igraph_adjlist_get(&adjlist, x); + long int i, n = igraph_vector_int_size(neis); + for (i = 0; i < basislen; i++) { + long int sn = (long int) VECTOR(*basis)[i]; + VECTOR(leaveout)[sn] = mark; + } + for (i = 0; i < n; i++) { + long int nei = (long int) VECTOR(*neis)[i]; + VECTOR(leaveout)[nei] = mark; + } + + /* Find the components */ + IGRAPH_CHECK(igraph_i_clusters_leaveout(&adjlist, &components, + &leaveout, &mark, &Q)); + + /* Store the corresponding separators, N(C) for each component C */ + IGRAPH_CHECK(igraph_i_separators_store(separators, &adjlist, + &components, &leaveout, &mark, + &sorter)); + } + + try_next++; + } + + /* --------------------------------------------------------------- */ + + igraph_vector_destroy(&sorter); + igraph_dqueue_destroy(&Q); + igraph_adjlist_destroy(&adjlist); + igraph_vector_destroy(&components); + igraph_vector_bool_destroy(&already_tried); + igraph_vector_destroy(&leaveout); + IGRAPH_FINALLY_CLEAN(7); /* +1 for separators */ + + return 0; +} + +#undef UPDATEMARK + +static int igraph_i_minimum_size_separators_append(igraph_vector_ptr_t *old, + igraph_vector_ptr_t *new) { + + long int olen = igraph_vector_ptr_size(old); + long int nlen = igraph_vector_ptr_size(new); + long int i; + + for (i = 0; i < nlen; i++) { + igraph_vector_t *newvec = VECTOR(*new)[i]; + long int j; + for (j = 0; j < olen; j++) { + igraph_vector_t *oldvec = VECTOR(*old)[j]; + if (igraph_vector_all_e(oldvec, newvec)) { + break; + } + } + if (j == olen) { + IGRAPH_CHECK(igraph_vector_ptr_push_back(old, newvec)); + olen++; + } else { + igraph_vector_destroy(newvec); + igraph_free(newvec); + } + VECTOR(*new)[i] = 0; + } + igraph_vector_ptr_clear(new); + + return 0; +} + +static int igraph_i_minimum_size_separators_topkdeg(const igraph_t *graph, + igraph_vector_t *res, + long int k) { + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t deg, order; + long int i; + + IGRAPH_VECTOR_INIT_FINALLY(°, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&order, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), IGRAPH_ALL, + /*loops=*/ 0)); + + IGRAPH_CHECK(igraph_vector_order1(°, &order, no_of_nodes)); + IGRAPH_CHECK(igraph_vector_resize(res, k)); + for (i = 0; i < k; i++) { + VECTOR(*res)[i] = VECTOR(order)[no_of_nodes - 1 - i]; + } + + igraph_vector_destroy(&order); + igraph_vector_destroy(°); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +static void igraph_i_separators_stcuts_free(igraph_vector_ptr_t *p) { + long int i, n = igraph_vector_ptr_size(p); + for (i = 0; i < n; i++) { + igraph_vector_t *v = VECTOR(*p)[i]; + if (v) { + igraph_vector_destroy(v); + igraph_free(v); + VECTOR(*p)[i] = 0; + } + } + igraph_vector_ptr_destroy(p); +} + +/** + * \function igraph_minimum_size_separators + * Find all minimum size separating vertex sets + * + * This function lists all separator vertex sets of minimum size. + * A vertex set is a separator if its removal disconnects the graph. + * + * The implementation is based on the following paper: + * Arkady Kanevsky: Finding all minimum-size separating vertex sets in + * a graph, Networks 23, 533--541, 1993. + * + * \param graph The input graph, which must be undirected. + * \param separators An initialized pointer vector, the separators + * are stored here. It is a list of pointers to igraph_vector_t + * objects. Each vector will contain the ids of the vertices in + * the separator. + * To free all memory allocated for \c separators, you need call + * \ref igraph_vector_destroy() and then \ref igraph_free() on + * each element, before destroying the pointer vector itself. + * \return Error code. + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_minimum_size_separators.c + */ + +int igraph_minimum_size_separators(const igraph_t *graph, + igraph_vector_ptr_t *separators) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_integer_t conn; long int k; + igraph_vector_t X; + long int i, j; + igraph_bool_t issepX; + igraph_t Gbar; + igraph_vector_t phi; + igraph_t graph_copy; + igraph_vector_t capacity; + igraph_maxflow_stats_t stats; + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Minimum size separators currently only works on undirected graphs", + IGRAPH_EINVAL); + } + + igraph_vector_ptr_clear(separators); + IGRAPH_FINALLY(igraph_i_separators_free, separators); + + /* ---------------------------------------------------------------- */ + /* 1 Find the vertex connectivity of 'graph' */ + IGRAPH_CHECK(igraph_vertex_connectivity(graph, &conn, + /* checks= */ 1)); k = conn; + + /* Special cases for low connectivity, two exits here! */ + if (conn == 0) { + /* Nothing to do */ + IGRAPH_FINALLY_CLEAN(1); /* separators */ + return 0; + } else if (conn == 1) { + igraph_vector_t ap; + long int i, n; + IGRAPH_VECTOR_INIT_FINALLY(&ap, 0); + IGRAPH_CHECK(igraph_articulation_points(graph, &ap)); + n = igraph_vector_size(&ap); + IGRAPH_CHECK(igraph_vector_ptr_resize(separators, n)); + igraph_vector_ptr_null(separators); + for (i = 0; i < n; i++) { + igraph_vector_t *v = igraph_Calloc(1, igraph_vector_t); + if (!v) { + IGRAPH_ERROR("Minimum size separators failed", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(v, 1); + VECTOR(*v)[0] = VECTOR(ap)[i]; + VECTOR(*separators)[i] = v; + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&ap); + IGRAPH_FINALLY_CLEAN(2); /* +1 for separators */ + return 0; + } else if (conn == no_of_nodes - 1) { + long int k; + IGRAPH_CHECK(igraph_vector_ptr_resize(separators, no_of_nodes)); + igraph_vector_ptr_null(separators); + for (i = 0; i < no_of_nodes; i++) { + igraph_vector_t *v = igraph_Calloc(1, igraph_vector_t); + if (!v) { + IGRAPH_ERROR("Cannot list minimum size separators", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(v, no_of_nodes - 1); + for (j = 0, k = 0; j < no_of_nodes; j++) { + if (j != i) { + VECTOR(*v)[k++] = j; + } + } + VECTOR(*separators)[i] = v; + IGRAPH_FINALLY_CLEAN(1); + } + IGRAPH_FINALLY_CLEAN(1); /* separators */ + return 0; + } + + /* Work on a copy of 'graph' */ + IGRAPH_CHECK(igraph_copy(&graph_copy, graph)); + IGRAPH_FINALLY(igraph_destroy, &graph_copy); + + /* ---------------------------------------------------------------- */ + /* 2 Find k vertices with the largest degrees (x1;..,xk). Check + if these k vertices form a separating k-set of G */ + IGRAPH_CHECK(igraph_vector_init(&X, conn)); + IGRAPH_FINALLY(igraph_vector_destroy, &X); + IGRAPH_CHECK(igraph_i_minimum_size_separators_topkdeg(graph, &X, k)); + IGRAPH_CHECK(igraph_is_separator(&graph_copy, igraph_vss_vector(&X), + &issepX)); + if (issepX) { + igraph_vector_t *v = igraph_Calloc(1, igraph_vector_t); + if (!v) { + IGRAPH_ERROR("Cannot find minimal size separators", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(v, k); + for (i = 0; i < k; i++) { + VECTOR(*v)[i] = VECTOR(X)[i]; + } + IGRAPH_CHECK(igraph_vector_ptr_push_back(separators, v)); + IGRAPH_FINALLY_CLEAN(1); + } + + /* Create Gbar, the Even-Tarjan reduction of graph */ + IGRAPH_VECTOR_INIT_FINALLY(&capacity, 0); + IGRAPH_CHECK(igraph_even_tarjan_reduction(&graph_copy, &Gbar, &capacity)); + IGRAPH_FINALLY(igraph_destroy, &Gbar); + + IGRAPH_VECTOR_INIT_FINALLY(&phi, no_of_edges); + + /* ---------------------------------------------------------------- */ + /* 3 If v[j] != x[i] and v[j] is not adjacent to x[i] then */ + for (i = 0; i < k; i++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + for (j = 0; j < no_of_nodes; j++) { + long int ii = (long int) VECTOR(X)[i]; + igraph_real_t phivalue; + igraph_bool_t conn; + + if (ii == j) { + continue; /* the same vertex */ + } + igraph_are_connected(&graph_copy, (igraph_integer_t) ii, + (igraph_integer_t) j, &conn); + if (conn) { + continue; /* they are connected */ + } + + /* --------------------------------------------------------------- */ + /* 4 Compute a maximum flow phi in Gbar from x[i] to v[j]. + If |phi|=k, then */ + IGRAPH_CHECK(igraph_maxflow(&Gbar, &phivalue, &phi, /*cut=*/ 0, + /*partition=*/ 0, /*partition2=*/ 0, + /* source= */ + (igraph_integer_t) (ii + no_of_nodes), + /* target= */ (igraph_integer_t) j, + &capacity, &stats)); + + if (phivalue == k) { + + /* ------------------------------------------------------------- */ + /* 5-6-7. Find all k-sets separating x[i] and v[j]. */ + igraph_vector_ptr_t stcuts; + IGRAPH_CHECK(igraph_vector_ptr_init(&stcuts, 0)); + IGRAPH_FINALLY(igraph_i_separators_stcuts_free, &stcuts); + IGRAPH_CHECK(igraph_all_st_mincuts(&Gbar, /*value=*/ 0, + /*cuts=*/ &stcuts, + /*partition1s=*/ 0, + /*source=*/ (igraph_integer_t) + (ii + no_of_nodes), + /*target=*/ (igraph_integer_t) j, + /*capacity=*/ &capacity)); + + IGRAPH_CHECK(igraph_i_minimum_size_separators_append(separators, + &stcuts)); + igraph_vector_ptr_destroy(&stcuts); + IGRAPH_FINALLY_CLEAN(1); + + } /* if phivalue == k */ + + /* --------------------------------------------------------------- */ + /* 8 Add edge (x[i],v[j]) to G. */ + IGRAPH_CHECK(igraph_add_edge(&graph_copy, (igraph_integer_t) ii, + (igraph_integer_t) j)); + IGRAPH_CHECK(igraph_add_edge(&Gbar, (igraph_integer_t) (ii + no_of_nodes), + (igraph_integer_t) j)); + IGRAPH_CHECK(igraph_add_edge(&Gbar, (igraph_integer_t) (j + no_of_nodes), + (igraph_integer_t) ii)); + IGRAPH_CHECK(igraph_vector_push_back(&capacity, no_of_nodes)); + IGRAPH_CHECK(igraph_vector_push_back(&capacity, no_of_nodes)); + + } /* for j + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_epidemics.h" +#include "igraph_random.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_psumtree.h" +#include "igraph_memory.h" +#include "igraph_structural.h" + +int igraph_sir_init(igraph_sir_t *sir) { + igraph_vector_init(&sir->times, 1); + IGRAPH_FINALLY(igraph_vector_destroy, &sir->times); + igraph_vector_int_init(&sir->no_s, 1); + IGRAPH_FINALLY(igraph_vector_int_destroy, &sir->no_s); + igraph_vector_int_init(&sir->no_i, 1); + IGRAPH_FINALLY(igraph_vector_int_destroy, &sir->no_i); + igraph_vector_int_init(&sir->no_r, 1); + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +/** + * \function igraph_sir_destroy + * Deallocate memory associated with a SIR simulation run + * + * \param sir The \ref igraph_sir_t object storing the simulation. + */ + +void igraph_sir_destroy(igraph_sir_t *sir) { + igraph_vector_destroy(&sir->times); + igraph_vector_int_destroy(&sir->no_s); + igraph_vector_int_destroy(&sir->no_i); + igraph_vector_int_destroy(&sir->no_r); +} + +static void igraph_i_sir_destroy(igraph_vector_ptr_t *v) { + int i, n = igraph_vector_ptr_size(v); + for (i = 0; i < n; i++) { + igraph_sir_t *s = VECTOR(*v)[i]; + if (s) { + igraph_sir_destroy(s); + } + } +} + +#define S_S 0 +#define S_I 1 +#define S_R 2 + +/** + * \function igraph_sir + * Perform a number of SIR epidemics model runs on a graph + * + * The SIR model is a simple model from epidemiology. The individuals + * of the population might be in three states: susceptible, infected + * and recovered. Recovered people are assumed to be immune to the + * disease. Susceptibles become infected with a rate that depends on + * their number of infected neigbors. Infected people become recovered + * with a constant rate. See these parameters below. + * + * + * This function runs multiple simulations, all starting with a + * single uniformly randomly chosen infected individual. A simulation + * is stopped when no infected individuals are left. + * + * \param graph The graph to perform the model on. For directed graphs + * edge directions are ignored and a warning is given. + * \param beta The rate of infection of an individual that is + * susceptible and has a single infected neighbor. + * The infection rate of a susceptible individual with n + * infected neighbors is n times beta. Formally + * this is the rate parameter of an exponential distribution. + * \param gamma The rate of recovery of an infected individual. + * Formally, this is the rate parameter of an exponential + * distribution. + * \param no_sim The number of simulation runs to perform. + * \param result The result of the simulation is stored here, + * in a list of \ref igraph_sir_t objects. To deallocate + * memory, the user needs to call \ref igraph_sir_destroy on + * each element, before destroying the pointer vector itself. + * \return Error code. + * + * Time complexity: O(no_sim * (|V| + |E| log(|V|))). + */ + +int igraph_sir(const igraph_t *graph, igraph_real_t beta, + igraph_real_t gamma, igraph_integer_t no_sim, + igraph_vector_ptr_t *result) { + + int infected; + igraph_vector_int_t status; + igraph_adjlist_t adjlist; + int no_of_nodes = igraph_vcount(graph); + int i, j, ns, ni, nr; + igraph_vector_int_t *neis; + igraph_psumtree_t tree; + igraph_real_t psum; + int neilen; + igraph_bool_t simple; + + if (no_of_nodes == 0) { + IGRAPH_ERROR("Cannot run SIR model on empty graph", IGRAPH_EINVAL); + } + if (igraph_is_directed(graph)) { + IGRAPH_WARNING("Edge directions are ignored in SIR model"); + } + if (beta < 0) { + IGRAPH_ERROR("Beta must be non-negative in SIR model", IGRAPH_EINVAL); + } + if (gamma < 0) { + IGRAPH_ERROR("Gamma must be non-negative in SIR model", IGRAPH_EINVAL); + } + if (no_sim <= 0) { + IGRAPH_ERROR("Number of SIR simulations must be positive", IGRAPH_EINVAL); + } + + igraph_is_simple(graph, &simple); + if (!simple) { + IGRAPH_ERROR("SIR model only works with simple graphs", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_int_init(&status, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &status); + IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); + IGRAPH_CHECK(igraph_psumtree_init(&tree, no_of_nodes)); + IGRAPH_FINALLY(igraph_psumtree_destroy, &tree); + + IGRAPH_CHECK(igraph_vector_ptr_resize(result, no_sim)); + igraph_vector_ptr_null(result); + IGRAPH_FINALLY(igraph_i_sir_destroy, result); + for (i = 0; i < no_sim; i++) { + igraph_sir_t *sir = igraph_Calloc(1, igraph_sir_t); + if (!sir) { + IGRAPH_ERROR("Cannot run SIR model", IGRAPH_ENOMEM); + } + igraph_sir_init(sir); + VECTOR(*result)[i] = sir; + } + + RNG_BEGIN(); + + for (j = 0; j < no_sim; j++) { + + igraph_sir_t *sir = VECTOR(*result)[j]; + igraph_vector_t *times_v = &sir->times; + igraph_vector_int_t *no_s_v = &sir->no_s; + igraph_vector_int_t *no_i_v = &sir->no_i; + igraph_vector_int_t *no_r_v = &sir->no_r; + + infected = RNG_INTEGER(0, no_of_nodes - 1); + + /* Initially infected */ + igraph_vector_int_null(&status); + VECTOR(status)[infected] = S_I; + ns = no_of_nodes - 1; + ni = 1; + nr = 0; + + VECTOR(*times_v)[0] = 0.0; + VECTOR(*no_s_v)[0] = ns; + VECTOR(*no_i_v)[0] = ni; + VECTOR(*no_r_v)[0] = nr; + + if (igraph_psumtree_sum(&tree) != 0) { + igraph_psumtree_reset(&tree); + } + + /* Rates */ + igraph_psumtree_update(&tree, infected, gamma); + neis = igraph_adjlist_get(&adjlist, infected); + neilen = igraph_vector_int_size(neis); + for (i = 0; i < neilen; i++) { + int nei = VECTOR(*neis)[i]; + igraph_psumtree_update(&tree, nei, beta); + } + + while (ni > 0) { + igraph_real_t tt; + igraph_real_t r; + long int vchange; + + psum = igraph_psumtree_sum(&tree); + tt = igraph_rng_get_exp(igraph_rng_default(), psum); + r = RNG_UNIF(0, psum); + + igraph_psumtree_search(&tree, &vchange, r); + neis = igraph_adjlist_get(&adjlist, vchange); + neilen = igraph_vector_int_size(neis); + + if (VECTOR(status)[vchange] == S_I) { + VECTOR(status)[vchange] = S_R; + ni--; nr++; + igraph_psumtree_update(&tree, vchange, 0.0); + for (i = 0; i < neilen; i++) { + int nei = VECTOR(*neis)[i]; + if (VECTOR(status)[nei] == S_S) { + igraph_real_t rate = igraph_psumtree_get(&tree, nei); + igraph_psumtree_update(&tree, nei, rate - beta); + } + } + + } else { /* S_S */ + VECTOR(status)[vchange] = S_I; + ns--; ni++; + igraph_psumtree_update(&tree, vchange, gamma); + for (i = 0; i < neilen; i++) { + int nei = VECTOR(*neis)[i]; + if (VECTOR(status)[nei] == S_S) { + igraph_real_t rate = igraph_psumtree_get(&tree, nei); + igraph_psumtree_update(&tree, nei, rate + beta); + } + } + } + + igraph_vector_push_back(times_v, tt + igraph_vector_tail(times_v)); + igraph_vector_int_push_back(no_s_v, ns); + igraph_vector_int_push_back(no_i_v, ni); + igraph_vector_int_push_back(no_r_v, nr); + + } /* psum > 0 */ + + } /* j < no_sim */ + + RNG_END(); + + igraph_psumtree_destroy(&tree); + igraph_adjlist_destroy(&adjlist); + igraph_vector_int_destroy(&status); + IGRAPH_FINALLY_CLEAN(4); /* + result */ + + return 0; +} diff --git a/src/spanning_trees.c b/src/spanning_trees.c new file mode 100644 index 0000000..0bba103 --- /dev/null +++ b/src/spanning_trees.c @@ -0,0 +1,520 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2011 Gabor Csardi + Rue de l'Industrie 5, Lausanne 1005, Switzerland + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_structural.h" +#include "igraph_dqueue.h" +#include "igraph_interface.h" +#include "igraph_interrupt_internal.h" +#include "igraph_memory.h" +#include "igraph_adjlist.h" +#include "igraph_random.h" +#include "igraph_components.h" +#include "igraph_progress.h" +#include "igraph_types_internal.h" + +static int igraph_i_minimum_spanning_tree_unweighted(const igraph_t *graph, + igraph_vector_t *result); +static int igraph_i_minimum_spanning_tree_prim(const igraph_t *graph, + igraph_vector_t *result, const igraph_vector_t *weights); + +/** + * \ingroup structural + * \function igraph_minimum_spanning_tree + * \brief Calculates one minimum spanning tree of a graph. + * + * + * If the graph has more minimum spanning trees (this is always the + * case, except if it is a forest) this implementation returns only + * the same one. + * + * + * Directed graphs are considered as undirected for this computation. + * + * + * If the graph is not connected then its minimum spanning forest is + * returned. This is the set of the minimum spanning trees of each + * component. + * + * \param graph The graph object. + * \param res An initialized vector, the IDs of the edges that constitute + * a spanning tree will be returned here. Use + * \ref igraph_subgraph_edges() to extract the spanning tree as + * a separate graph object. + * \param weights A vector containing the weights of the edges + * in the same order as the simple edge iterator visits them + * (i.e. in increasing order of edge IDs). + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * + * Time complexity: O(|V|+|E|) for the unweighted case, O(|E| log |V|) + * for the weighted case. |V| is the number of vertices, |E| the + * number of edges in the graph. + * + * \sa \ref igraph_minimum_spanning_tree_unweighted() and + * \ref igraph_minimum_spanning_tree_prim() if you only need the + * tree as a separate graph object. + * + * \example examples/simple/igraph_minimum_spanning_tree.c + */ +int igraph_minimum_spanning_tree(const igraph_t* graph, + igraph_vector_t* res, const igraph_vector_t* weights) { + if (weights == 0) { + IGRAPH_CHECK(igraph_i_minimum_spanning_tree_unweighted(graph, res)); + } else { + IGRAPH_CHECK(igraph_i_minimum_spanning_tree_prim(graph, res, weights)); + } + return IGRAPH_SUCCESS; +} + +/** + * \ingroup structural + * \function igraph_minimum_spanning_tree_unweighted + * \brief Calculates one minimum spanning tree of an unweighted graph. + * + * + * If the graph has more minimum spanning trees (this is always the + * case, except if it is a forest) this implementation returns only + * the same one. + * + * + * Directed graphs are considered as undirected for this computation. + * + * + * If the graph is not connected then its minimum spanning forest is + * returned. This is the set of the minimum spanning trees of each + * component. + * \param graph The graph object. + * \param mst The minimum spanning tree, another graph object. Do + * \em not initialize this object before passing it to + * this function, but be sure to call \ref igraph_destroy() on it if + * you don't need it any more. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * + * Time complexity: O(|V|+|E|), + * |V| is the + * number of vertices, |E| the number + * of edges in the graph. + * + * \sa \ref igraph_minimum_spanning_tree_prim() for weighted graphs, + * \ref igraph_minimum_spanning_tree() if you need the IDs of the + * edges that constitute the spanning tree. + */ + +int igraph_minimum_spanning_tree_unweighted(const igraph_t *graph, + igraph_t *mst) { + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, igraph_vcount(graph) - 1); + IGRAPH_CHECK(igraph_i_minimum_spanning_tree_unweighted(graph, &edges)); + IGRAPH_CHECK(igraph_subgraph_edges(graph, mst, + igraph_ess_vector(&edges), /* delete_vertices = */ 0)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \ingroup structural + * \function igraph_minimum_spanning_tree_prim + * \brief Calculates one minimum spanning tree of a weighted graph. + * + * + * This function uses Prim's method for carrying out the computation, + * see Prim, R.C.: Shortest connection networks and some + * generalizations, Bell System Technical + * Journal, Vol. 36, + * 1957, 1389--1401. + * + * + * If the graph has more than one minimum spanning tree, the current + * implementation returns always the same one. + * + * + * Directed graphs are considered as undirected for this computation. + * + * + * If the graph is not connected then its minimum spanning forest is + * returned. This is the set of the minimum spanning trees of each + * component. + * + * \param graph The graph object. + * \param mst The result of the computation, a graph object containing + * the minimum spanning tree of the graph. + * Do \em not initialize this object before passing it to + * this function, but be sure to call \ref igraph_destroy() on it if + * you don't need it any more. + * \param weights A vector containing the weights of the edges + * in the same order as the simple edge iterator visits them + * (i.e. in increasing order of edge IDs). + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory. + * \c IGRAPH_EINVAL, length of weight vector does not + * match number of edges. + * + * Time complexity: O(|E| log |V|), + * |V| is the number of vertices, + * |E| the number of edges in the + * graph. + * + * \sa \ref igraph_minimum_spanning_tree_unweighted() for unweighted graphs, + * \ref igraph_minimum_spanning_tree() if you need the IDs of the + * edges that constitute the spanning tree. + * + * \example examples/simple/igraph_minimum_spanning_tree.c + */ + +int igraph_minimum_spanning_tree_prim(const igraph_t *graph, igraph_t *mst, + const igraph_vector_t *weights) { + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, igraph_vcount(graph) - 1); + IGRAPH_CHECK(igraph_i_minimum_spanning_tree_prim(graph, &edges, weights)); + IGRAPH_CHECK(igraph_subgraph_edges(graph, mst, + igraph_ess_vector(&edges), /* delete_vertices = */ 0)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + + +static int igraph_i_minimum_spanning_tree_unweighted(const igraph_t* graph, igraph_vector_t* res) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + char *already_added; + char *added_edges; + + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + igraph_vector_t tmp = IGRAPH_VECTOR_NULL; + long int i, j; + + igraph_vector_clear(res); + + added_edges = igraph_Calloc(no_of_edges, char); + if (added_edges == 0) { + IGRAPH_ERROR("unweighted spanning tree failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added_edges); + already_added = igraph_Calloc(no_of_nodes, char); + if (already_added == 0) { + IGRAPH_ERROR("unweighted spanning tree failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, already_added); + IGRAPH_VECTOR_INIT_FINALLY(&tmp, 0); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + + for (i = 0; i < no_of_nodes; i++) { + if (already_added[i] > 0) { + continue; + } + + IGRAPH_ALLOW_INTERRUPTION(); + + already_added[i] = 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, i)); + while (! igraph_dqueue_empty(&q)) { + long int act_node = (long int) igraph_dqueue_pop(&q); + IGRAPH_CHECK(igraph_incident(graph, &tmp, (igraph_integer_t) act_node, + IGRAPH_ALL)); + for (j = 0; j < igraph_vector_size(&tmp); j++) { + long int edge = (long int) VECTOR(tmp)[j]; + if (added_edges[edge] == 0) { + igraph_integer_t from, to; + igraph_edge(graph, (igraph_integer_t) edge, &from, &to); + if (act_node == to) { + to = from; + } + if (already_added[(long int) to] == 0) { + already_added[(long int) to] = 1; + added_edges[edge] = 1; + IGRAPH_CHECK(igraph_vector_push_back(res, edge)); + IGRAPH_CHECK(igraph_dqueue_push(&q, to)); + } + } + } + } + } + + igraph_dqueue_destroy(&q); + igraph_Free(already_added); + igraph_vector_destroy(&tmp); + igraph_Free(added_edges); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + +static int igraph_i_minimum_spanning_tree_prim( + const igraph_t* graph, igraph_vector_t* res, const igraph_vector_t *weights) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + char *already_added; + char *added_edges; + + igraph_d_indheap_t heap = IGRAPH_D_INDHEAP_NULL; + igraph_integer_t mode = IGRAPH_ALL; + + igraph_vector_t adj; + + long int i, j; + + igraph_vector_clear(res); + + if (weights == 0) { + return igraph_i_minimum_spanning_tree_unweighted(graph, res); + } + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weights length", IGRAPH_EINVAL); + } + + added_edges = igraph_Calloc(no_of_edges, char); + if (added_edges == 0) { + IGRAPH_ERROR("prim spanning tree failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added_edges); + already_added = igraph_Calloc(no_of_nodes, char); + if (already_added == 0) { + IGRAPH_ERROR("prim spanning tree failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, already_added); + IGRAPH_CHECK(igraph_d_indheap_init(&heap, 0)); + IGRAPH_FINALLY(igraph_d_indheap_destroy, &heap); + IGRAPH_VECTOR_INIT_FINALLY(&adj, 0); + + for (i = 0; i < no_of_nodes; i++) { + if (already_added[i] > 0) { + continue; + } + IGRAPH_ALLOW_INTERRUPTION(); + + already_added[i] = 1; + /* add all edges of the first vertex */ + igraph_incident(graph, &adj, (igraph_integer_t) i, (igraph_neimode_t) mode); + for (j = 0; j < igraph_vector_size(&adj); j++) { + long int edgeno = (long int) VECTOR(adj)[j]; + igraph_integer_t edgefrom, edgeto; + long int neighbor; + igraph_edge(graph, (igraph_integer_t) edgeno, &edgefrom, &edgeto); + neighbor = edgefrom != i ? edgefrom : edgeto; + if (already_added[neighbor] == 0) { + IGRAPH_CHECK(igraph_d_indheap_push(&heap, -VECTOR(*weights)[edgeno], i, + edgeno)); + } + } + + while (! igraph_d_indheap_empty(&heap)) { + /* Get minimal edge */ + long int from, edge; + igraph_integer_t tmp, to; + igraph_d_indheap_max_index(&heap, &from, &edge); + igraph_edge(graph, (igraph_integer_t) edge, &tmp, &to); + + /* Erase it */ + igraph_d_indheap_delete_max(&heap); + + /* Is this edge already included? */ + if (added_edges[edge] == 0) { + if (from == to) { + to = tmp; + } + /* Does it point to a visited node? */ + if (already_added[(long int)to] == 0) { + already_added[(long int)to] = 1; + added_edges[edge] = 1; + IGRAPH_CHECK(igraph_vector_push_back(res, edge)); + /* add all outgoing edges */ + igraph_incident(graph, &adj, to, (igraph_neimode_t) mode); + for (j = 0; j < igraph_vector_size(&adj); j++) { + long int edgeno = (long int) VECTOR(adj)[j]; + igraph_integer_t edgefrom, edgeto; + long int neighbor; + igraph_edge(graph, (igraph_integer_t) edgeno, &edgefrom, &edgeto); + neighbor = edgefrom != to ? edgefrom : edgeto; + if (already_added[neighbor] == 0) { + IGRAPH_CHECK(igraph_d_indheap_push(&heap, -VECTOR(*weights)[edgeno], to, + edgeno)); + } + } + } /* for */ + } /* if !already_added */ + } /* while in the same component */ + } /* for all nodes */ + + igraph_d_indheap_destroy(&heap); + igraph_Free(already_added); + igraph_vector_destroy(&adj); + igraph_Free(added_edges); + IGRAPH_FINALLY_CLEAN(4); + + return IGRAPH_SUCCESS; +} + + +/* igraph_random_spanning_tree */ + +/* Loop-erased random walk (LERW) implementation. + * res must be an initialized vector. The edge IDs of the spanning tree + * will be added to the end of it. res will not be cleared before doing this. + * + * The walk is started from vertex start. comp_size must be the size of the connected + * component containing start. + */ +static int igraph_i_lerw(const igraph_t *graph, igraph_vector_t *res, igraph_integer_t start, + igraph_integer_t comp_size, igraph_vector_bool_t *visited, const igraph_inclist_t *il) { + igraph_integer_t visited_count; + + IGRAPH_CHECK(igraph_vector_reserve(res, igraph_vector_size(res) + comp_size - 1)); + + RNG_BEGIN(); + + VECTOR(*visited)[start] = 1; + visited_count = 1; + + while (visited_count < comp_size) { + long degree, edge; + igraph_vector_int_t *edges; + + edges = igraph_inclist_get(il, start); + + /* choose a random edge */ + degree = igraph_vector_int_size(edges); + edge = VECTOR(*edges)[ RNG_INTEGER(0, degree - 1) ]; + + /* set 'start' to the next vertex */ + start = IGRAPH_OTHER(graph, edge, start); + + /* if the next vertex hasn't been visited yet, register the edge we just traversed */ + if (! VECTOR(*visited)[start]) { + IGRAPH_CHECK(igraph_vector_push_back(res, edge)); + VECTOR(*visited)[start] = 1; + visited_count++; + } + + IGRAPH_ALLOW_INTERRUPTION(); + } + + RNG_END(); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_random_spanning_tree + * \brief Uniformly sample the spanning trees of a graph + * + * Performs a loop-erased random walk on the graph to uniformly sample + * its spanning trees. Edge directions are ignored. + * + * + * Multi-graphs are supported, and edge multiplicities will affect the sampling + * frequency. For example, consider the 3-cycle graph 1=2-3-1, with two edges + * between vertices 1 and 2. Due to these parallel edges, the trees 1-2-3 + * and 3-1-2 will be sampled with multiplicity 2, while the tree + * 2-3-1 will be sampled with multiplicity 1. + * + * \param graph The input graph. Edge directions are ignored. + * \param res An initialized vector, the IDs of the edges that constitute + * a spanning tree will be returned here. Use + * \ref igraph_subgraph_edges() to extract the spanning tree as + * a separate graph object. + * \param vid This parameter is relevant if the graph is not connected. + * If negative, a random spanning forest of all components will be + * generated. Otherwise, it should be the ID of a vertex. A random + * spanning tree of the component containing the vertex will be + * generated. + * + * \return Error code. + * + * \sa \ref igraph_minimum_spanning_tree(), \ref igraph_random_walk() + * + */ +int igraph_random_spanning_tree(const igraph_t *graph, igraph_vector_t *res, igraph_integer_t vid) { + igraph_inclist_t il; + igraph_vector_bool_t visited; + igraph_integer_t vcount = igraph_vcount(graph); + + if (vid >= vcount) { + IGRAPH_ERROR("Invalid vertex id given for random spanning tree", IGRAPH_EINVVID); + } + + IGRAPH_CHECK(igraph_inclist_init(graph, &il, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &il); + + IGRAPH_CHECK(igraph_vector_bool_init(&visited, vcount)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &visited); + + igraph_vector_clear(res); + + if (vid < 0) { /* generate random spanning forest: consider each component separately */ + igraph_vector_t membership, csize; + igraph_integer_t comp_count; + igraph_integer_t i; + + IGRAPH_VECTOR_INIT_FINALLY(&membership, 0); + IGRAPH_VECTOR_INIT_FINALLY(&csize, 0); + + IGRAPH_CHECK(igraph_clusters(graph, &membership, &csize, &comp_count, IGRAPH_WEAK)); + + /* for each component ... */ + for (i = 0; i < comp_count; ++i) { + /* ... find a vertex to start the LERW from */ + igraph_integer_t j = 0; + while (VECTOR(membership)[j] != i) { + ++j; + } + + IGRAPH_CHECK(igraph_i_lerw(graph, res, j, (igraph_integer_t) VECTOR(csize)[i], &visited, &il)); + } + + igraph_vector_destroy(&membership); + igraph_vector_destroy(&csize); + IGRAPH_FINALLY_CLEAN(2); + } else { /* consider the component containing vid */ + igraph_vector_t comp_vertices; + igraph_integer_t comp_size; + + /* we measure the size of the component */ + IGRAPH_VECTOR_INIT_FINALLY(&comp_vertices, 0); + IGRAPH_CHECK(igraph_subcomponent(graph, &comp_vertices, vid, IGRAPH_ALL)); + comp_size = (igraph_integer_t) igraph_vector_size(&comp_vertices); + igraph_vector_destroy(&comp_vertices); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_i_lerw(graph, res, vid, comp_size, &visited, &il)); + } + + igraph_vector_bool_destroy(&visited); + igraph_inclist_destroy(&il); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + diff --git a/src/sparsemat.c b/src/sparsemat.c new file mode 100644 index 0000000..0ed4678 --- /dev/null +++ b/src/sparsemat.c @@ -0,0 +1,3057 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2009-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "config.h" + +#include "cs/cs.h" + +#include "igraph_sparsemat.h" +#include "igraph_error.h" +#include "igraph_interface.h" +#include "igraph_constructors.h" +#include "igraph_memory.h" +#include "igraph_vector_ptr.h" +#include "igraph_attributes.h" + +#include + +/** + * \section about_sparsemat About sparse matrices + * + * + * The igraph_sparsemat_t data type stores sparse matrices, + * i.e. matrices in which the majority of the elements are zero. + * + * + * The data type is essentially a wrapper to some of the + * functions in the CXSparse library, by Tim Davis, see + * http://faculty.cse.tamu.edu/davis/suitesparse.html + * + * + * + * Matrices can be stored in two formats: triplet and + * column-compressed. The triplet format is intended for sparse matrix + * initialization, as it is easy to add new (non-zero) elements to + * it. Most of the computations are done on sparse matrices in + * column-compressed format, after the user has converted the triplet + * matrix to column-compressed, via \ref igraph_sparsemat_compress(). + * + * + * + * Both formats are dynamic, in the sense that new elements can be + * added to them, possibly resulting the allocation of more memory. + * + * + * + * Row and column indices follow the C convention and are zero-based. + * + * + * + * \example examples/simple/igraph_sparsemat.c + * \example examples/simple/igraph_sparsemat2.c + * \example examples/simple/igraph_sparsemat3.c + * \example examples/simple/igraph_sparsemat4.c + * \example examples/simple/igraph_sparsemat5.c + * \example examples/simple/igraph_sparsemat6.c + * \example examples/simple/igraph_sparsemat7.c + * \example examples/simple/igraph_sparsemat8.c + * + */ + +/** + * \function igraph_sparsemat_init + * Initialize a sparse matrix, in triplet format + * + * This is the most common way to create a sparse matrix, together + * with the \ref igraph_sparsemat_entry() function, which can be used to + * add the non-zero elements one by one. Once done, the user can call + * \ref igraph_sparsemat_compress() to convert the matrix to + * column-compressed, to allow computations with it. + * + * The user must call \ref igraph_sparsemat_destroy() on + * the matrix to deallocate the memory, once the matrix is no more + * needed. + * \param A Pointer to a not yet initialized sparse matrix. + * \param rows The number of rows in the matrix. + * \param cols The number of columns. + * \param nzmax The maximum number of non-zero elements in the + * matrix. It is not compulsory to get this right, but it is + * useful for the allocation of the proper amount of memory. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_init(igraph_sparsemat_t *A, int rows, int cols, int nzmax) { + + if (rows < 0) { + IGRAPH_ERROR("Negative number of rows", IGRAPH_EINVAL); + } + if (cols < 0) { + IGRAPH_ERROR("Negative number of columns", IGRAPH_EINVAL); + } + + A->cs = cs_spalloc( rows, cols, nzmax, /*values=*/ 1, + /*triplet=*/ 1); + if (!A->cs) { + IGRAPH_ERROR("Cannot allocate memory for sparse matrix", IGRAPH_ENOMEM); + } + + return 0; +} + +/** + * \function igraph_sparsemat_copy + * Copy a sparse matrix + * + * Create a sparse matrix object, by copying another one. The source + * matrix can be either in triplet or column-compressed format. + * + * + * Exactly the same amount of memory will be allocated to the + * copy matrix, as it is currently for the original one. + * \param to Pointer to an uninitialized sparse matrix, the copy will + * be created here. + * \param from The sparse matrix to copy. + * \return Error code. + * + * Time complexity: O(n+nzmax), the number of columns plus the maximum + * number of non-zero elements. + */ + +int igraph_sparsemat_copy(igraph_sparsemat_t *to, + const igraph_sparsemat_t *from) { + + int ne = from->cs->nz == -1 ? from->cs->n + 1 : from->cs->nzmax; + + to->cs = cs_spalloc(from->cs->m, from->cs->n, from->cs->nzmax, + /*values=*/ 1, + /*triplet=*/ igraph_sparsemat_is_triplet(from)); + + to->cs->nzmax = from->cs->nzmax; + to->cs->m = from->cs->m; + to->cs->n = from->cs->n; + to->cs->nz = from->cs->nz; + + memcpy(to->cs->p, from->cs->p, sizeof(int) * (size_t) ne); + memcpy(to->cs->i, from->cs->i, sizeof(int) * (size_t) (from->cs->nzmax)); + memcpy(to->cs->x, from->cs->x, sizeof(double) * (size_t) (from->cs->nzmax)); + + return 0; +} + +/** + * \function igraph_sparsemat_destroy + * Deallocate memory used by a sparse matrix + * + * One destroyed, the sparse matrix must be initialized again, before + * calling any other operation on it. + * \param A The sparse matrix to destroy. + * + * Time complexity: O(1). + */ + +void igraph_sparsemat_destroy(igraph_sparsemat_t *A) { + cs_spfree(A->cs); +} + +/** + * \function igraph_sparsemat_realloc + * Allocate more (or less) memory for a sparse matrix + * + * Sparse matrices automatically allocate more memory, as needed. To + * control memory allocation, the user can call this function, to + * allocate memory for a given number of non-zero elements. + * \param A The sparse matrix, it can be in triplet or + * column-compressed format. + * \param nzmax The new maximum number of non-zero elements. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_realloc(igraph_sparsemat_t *A, int nzmax) { + return !cs_sprealloc(A->cs, nzmax); +} + +/** + * \function igraph_sparsemat_nrow + * Number of rows + * + * \param A The input matrix, in triplet or column-compressed format. + * \return The number of rows in the \p A matrix. + * + * Time complexity: O(1). + */ + +long int igraph_sparsemat_nrow(const igraph_sparsemat_t *A) { + return A->cs->m; +} + +/** + * \function igraph_sparsemat_ncol + * Number of columns. + * + * \param A The input matrix, in triplet or column-compressed format. + * \return The number of columns in the \p A matrix. + * + * Time complexity: O(1). + */ + +long int igraph_sparsemat_ncol(const igraph_sparsemat_t *A) { + return A->cs->n; +} + +/** + * \function igraph_sparsemat_type + * Type of a sparse matrix (triplet or column-compressed) + * + * Gives whether a sparse matrix is stored in the triplet format or in + * column-compressed format. + * \param A The input matrix. + * \return Either \c IGRAPH_SPARSEMAT_CC or \c + * IGRAPH_SPARSEMAT_TRIPLET. + * + * Time complexity: O(1). + */ + +igraph_sparsemat_type_t igraph_sparsemat_type(const igraph_sparsemat_t *A) { + return A->cs->nz < 0 ? IGRAPH_SPARSEMAT_CC : IGRAPH_SPARSEMAT_TRIPLET; +} + +/** + * \function igraph_sparsemat_is_triplet + * Is this sparse matrix in triplet format? + * + * Decides whether a sparse matrix is in triplet format. + * \param A The input matrix. + * \return One if the input matrix is in triplet format, zero + * otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t igraph_sparsemat_is_triplet(const igraph_sparsemat_t *A) { + return A->cs->nz >= 0; +} + +/** + * \function igraph_sparsemat_is_cc + * Is this sparse matrix in column-compressed format? + * + * Decides whether a sparse matrix is in column-compressed format. + * \param A The input matrix. + * \return One if the input matrix is in column-compressed format, zero + * otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t igraph_sparsemat_is_cc(const igraph_sparsemat_t *A) { + return A->cs->nz < 0; +} + +/** + * \function igraph_sparsemat_permute + * Permute the rows and columns of a sparse matrix + * + * \param A The input matrix, it must be in column-compressed format. + * \param p Integer vector, giving the permutation of the rows. + * \param q Integer vector, the permutation of the columns. + * \param res Pointer to an uninitialized sparse matrix, the result is + * stored here. + * \return Error code. + * + * Time complexity: O(m+n+nz), the number of rows plus the number of + * columns plus the number of non-zero elements in the matrix. + */ + +int igraph_sparsemat_permute(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res) { + + long int nrow = A->cs->m, ncol = A->cs->n; + igraph_vector_int_t pinv; + long int i; + + if (nrow != igraph_vector_int_size(p)) { + IGRAPH_ERROR("Invalid row permutation length", IGRAPH_FAILURE); + } + if (ncol != igraph_vector_int_size(q)) { + IGRAPH_ERROR("Invalid column permutation length", IGRAPH_FAILURE); + } + + /* We invert the permutation by hand */ + IGRAPH_CHECK(igraph_vector_int_init(&pinv, nrow)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &pinv); + for (i = 0; i < nrow; i++) { + VECTOR(pinv)[ VECTOR(*p)[i] ] = (int) i; + } + + /* And call the permutation routine */ + if (! (res->cs = cs_permute(A->cs, VECTOR(pinv), VECTOR(*q), /*values=*/ 1))) { + IGRAPH_ERROR("Cannot index sparse matrix", IGRAPH_FAILURE); + } + + igraph_vector_int_destroy(&pinv); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_sparsemat_index_rows(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + igraph_sparsemat_t *res, + igraph_real_t *constres) { + + igraph_sparsemat_t II, II2; + long int nrow = A->cs->m; + long int idx_rows = igraph_vector_int_size(p); + long int k; + + /* Create index matrix */ + IGRAPH_CHECK(igraph_sparsemat_init(&II2, (int) idx_rows, (int) nrow, + (int) idx_rows)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &II2); + for (k = 0; k < idx_rows; k++) { + igraph_sparsemat_entry(&II2, (int) k, VECTOR(*p)[k], 1.0); + } + IGRAPH_CHECK(igraph_sparsemat_compress(&II2, &II)); + igraph_sparsemat_destroy(&II2); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &II); + + /* Multiply */ + IGRAPH_CHECK(igraph_sparsemat_multiply(&II, A, res)); + igraph_sparsemat_destroy(&II); + IGRAPH_FINALLY_CLEAN(1); + + if (constres) { + if (res->cs->p[1] != 0) { + *constres = res->cs->x[0]; + } else { + *constres = 0.0; + } + } + + return 0; +} + +static int igraph_i_sparsemat_index_cols(const igraph_sparsemat_t *A, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res, + igraph_real_t *constres) { + + igraph_sparsemat_t JJ, JJ2; + long int ncol = A->cs->n; + long int idx_cols = igraph_vector_int_size(q); + long int k; + + /* Create index matrix */ + IGRAPH_CHECK(igraph_sparsemat_init(&JJ2, (int) ncol, (int) idx_cols, + (int) idx_cols)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &JJ2); + for (k = 0; k < idx_cols; k++) { + igraph_sparsemat_entry(&JJ2, VECTOR(*q)[k], (int) k, 1.0); + } + IGRAPH_CHECK(igraph_sparsemat_compress(&JJ2, &JJ)); + igraph_sparsemat_destroy(&JJ2); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &JJ); + + /* Multiply */ + IGRAPH_CHECK(igraph_sparsemat_multiply(A, &JJ, res)); + igraph_sparsemat_destroy(&JJ); + IGRAPH_FINALLY_CLEAN(1); + + if (constres) { + if (res->cs->p [1] != 0) { + *constres = res->cs->x [0]; + } else { + *constres = 0.0; + } + } + + return 0; +} + +/** + * \function igraph_sparsemat_index + * Index a sparse matrix, extract a submatrix, or a single element + * + * This function serves two purposes. First, it can extract + * submatrices from a sparse matrix. Second, as a special case, it can + * extract a single element from a sparse matrix. + * \param A The input matrix, it must be in column-compressed format. + * \param p An integer vector, or a null pointer. The selected row + * index or indices. A null pointer selects all rows. + * \param q An integer vector, or a null pointer. The selected column + * index or indices. A null pointer selects all columns. + * \param res Pointer to an uninitialized sparse matrix, or a null + * pointer. If not a null pointer, then the selected submatrix is + * stored here. + * \param constres Pointer to a real variable or a null pointer. If + * not a null pointer, then the first non-zero element in the + * selected submatrix is stored here, if there is one. Otherwise + * zero is stored here. This behavior is handy if one + * wants to select a single entry from the matrix. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_index(const igraph_sparsemat_t *A, + const igraph_vector_int_t *p, + const igraph_vector_int_t *q, + igraph_sparsemat_t *res, + igraph_real_t *constres) { + + igraph_sparsemat_t II, JJ, II2, JJ2, tmp; + long int nrow = A->cs->m; + long int ncol = A->cs->n; + long int idx_rows = p ? igraph_vector_int_size(p) : -1; + long int idx_cols = q ? igraph_vector_int_size(q) : -1; + long int k; + + igraph_sparsemat_t *myres = res, mres; + + if (!p && !q) { + IGRAPH_ERROR("No index vectors", IGRAPH_EINVAL); + } + + if (!res && (idx_rows != 1 || idx_cols != 1)) { + IGRAPH_ERROR("Sparse matrix indexing: must give `res' if not a " + "single element is selected", IGRAPH_EINVAL); + } + + if (!q) { + return igraph_i_sparsemat_index_rows(A, p, res, constres); + } + if (!p) { + return igraph_i_sparsemat_index_cols(A, q, res, constres); + } + + if (!res) { + myres = &mres; + } + + /* Create first index matrix */ + IGRAPH_CHECK(igraph_sparsemat_init(&II2, (int) idx_rows, (int) nrow, + (int) idx_rows)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &II2); + for (k = 0; k < idx_rows; k++) { + igraph_sparsemat_entry(&II2, (int) k, VECTOR(*p)[k], 1.0); + } + IGRAPH_CHECK(igraph_sparsemat_compress(&II2, &II)); + igraph_sparsemat_destroy(&II2); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &II); + + /* Create second index matrix */ + IGRAPH_CHECK(igraph_sparsemat_init(&JJ2, (int) ncol, (int) idx_cols, + (int) idx_cols)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &JJ2); + for (k = 0; k < idx_cols; k++) { + igraph_sparsemat_entry(&JJ2, VECTOR(*q)[k], (int) k, 1.0); + } + IGRAPH_CHECK(igraph_sparsemat_compress(&JJ2, &JJ)); + igraph_sparsemat_destroy(&JJ2); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &JJ); + + /* Multiply */ + IGRAPH_CHECK(igraph_sparsemat_multiply(&II, A, &tmp)); + igraph_sparsemat_destroy(&II); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmp); + IGRAPH_CHECK(igraph_sparsemat_multiply(&tmp, &JJ, myres)); + igraph_sparsemat_destroy(&tmp); + igraph_sparsemat_destroy(&JJ); + IGRAPH_FINALLY_CLEAN(2); + + if (constres) { + if (myres->cs->p [1] != 0) { + *constres = myres->cs->x [0]; + } else { + *constres = 0.0; + } + } + + if (!res) { + igraph_sparsemat_destroy(myres); + } + + return 0; +} + +/** + * \function igraph_sparsemat_entry + * Add an element to a sparse matrix + * + * This function can be used to add the entries to a sparse matrix, + * after initializing it with \ref igraph_sparsemat_init(). + * \param A The input matrix, it must be in triplet format. + * \param row The row index of the entry to add. + * \param col The column index of the entry to add. + * \param elem The value of the entry. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_entry(igraph_sparsemat_t *A, int row, int col, + igraph_real_t elem) { + + if (!cs_entry(A->cs, row, col, elem)) { + IGRAPH_ERROR("Cannot add entry to sparse matrix", + IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_compress + * Compress a sparse matrix, i.e. convert it to column-compress format + * + * Almost all sparse matrix operations require that the matrix is in + * column-compressed format. + * \param A The input matrix, it must be in triplet format. + * \param res Pointer to an uninitialized sparse matrix object, the + * compressed version of \p A is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_compress(const igraph_sparsemat_t *A, + igraph_sparsemat_t *res) { + + if (! (res->cs = cs_compress(A->cs)) ) { + IGRAPH_ERROR("Cannot compress sparse matrix", IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_transpose + * Transpose a sparse matrix + * + * \param A The input matrix, column-compressed or triple format. + * \param res Pointer to an uninitialized sparse matrix, the result is + * stored here. + * \param values If this is non-zero, the matrix transpose is + * calculated the normal way. If it is zero, then only the pattern + * of the input matrix is stored in the result, the values are not. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_transpose(const igraph_sparsemat_t *A, + igraph_sparsemat_t *res, + int values) { + + if (A->cs->nz < 0) { + /* column-compressed */ + if (! (res->cs = cs_transpose(A->cs, values)) ) { + IGRAPH_ERROR("Cannot transpose sparse matrix", IGRAPH_FAILURE); + } + } else { + /* triplets */ + int *tmp; + IGRAPH_CHECK(igraph_sparsemat_copy(res, A)); + tmp = res->cs->p; + res->cs->p = res->cs->i; + res->cs->i = tmp; + } + return 0; +} + +static +igraph_bool_t +igraph_i_sparsemat_is_symmetric_cc(const igraph_sparsemat_t *A) { + igraph_sparsemat_t t, tt; + igraph_bool_t res; + int nz; + + IGRAPH_CHECK(igraph_sparsemat_transpose(A, &t, /*values=*/ 1)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &t); + IGRAPH_CHECK(igraph_sparsemat_dupl(&t)); + IGRAPH_CHECK(igraph_sparsemat_transpose(&t, &tt, /*values=*/ 1)); + igraph_sparsemat_destroy(&t); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tt); + IGRAPH_CHECK(igraph_sparsemat_transpose(&tt, &t, /*values=*/ 1)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &t); + + nz = t.cs->p[t.cs->n]; + res = memcmp(t.cs->i, tt.cs->i, sizeof(int) * (size_t) nz) == 0; + res = res && memcmp(t.cs->p, tt.cs->p, sizeof(int) * + (size_t)(t.cs->n + 1)) == 0; + res = res && memcmp(t.cs->x, tt.cs->x, sizeof(igraph_real_t) * (size_t)nz) == 0; + + igraph_sparsemat_destroy(&t); + igraph_sparsemat_destroy(&tt); + IGRAPH_FINALLY_CLEAN(2); + + return res; +} + +static +igraph_bool_t +igraph_i_sparsemat_is_symmetric_triplet(const igraph_sparsemat_t *A) { + igraph_sparsemat_t tmp; + igraph_bool_t res; + IGRAPH_CHECK(igraph_sparsemat_compress(A, &tmp)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmp); + res = igraph_i_sparsemat_is_symmetric_cc(&tmp); + igraph_sparsemat_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + return res; +} + +igraph_bool_t igraph_sparsemat_is_symmetric(const igraph_sparsemat_t *A) { + + if (A->cs->m != A->cs->n) { + return 0; + } + + if (A->cs->nz < 0) { + return igraph_i_sparsemat_is_symmetric_cc(A); + } else { + return igraph_i_sparsemat_is_symmetric_triplet(A); + } +} + +/** + * \function igraph_sparsemat_dupl + * Remove duplicate elements from a sparse matrix + * + * It is possible that a column-compressed sparse matrix stores a + * single matrix entry in multiple pieces. The entry is then the sum + * of all its pieces. (Some functions create matrices like this.) This + * function eliminates the multiple pieces. + * \param A The input matrix, in column-compressed format. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_dupl(igraph_sparsemat_t *A) { + + if (!cs_dupl(A->cs)) { + IGRAPH_ERROR("Cannot remove duplicates from sparse matrix", + IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_fkeep + * Filter the elements of a sparse matrix + * + * This function can be used to filter the (non-zero) elements of a + * sparse matrix. For all entries, it calls the supplied function and + * depending on the return values either keeps, or deleted the element + * from the matrix. + * \param A The input matrix, in column-compressed format. + * \param fkeep The filter function. It must take four arguments: the + * first is an \c int, the row index of the entry, the second is + * another \c int, the column index. The third is \c igraph_real_t, + * the value of the entry. The fourth element is a \c void pointer, + * the \p other argument is passed here. The function must return + * an \c int. If this is zero, then the entry is deleted, otherwise + * it is kept. + * \param other A \c void pointer that is passed to the filtering + * function. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_fkeep(igraph_sparsemat_t *A, + int (*fkeep)(int, int, igraph_real_t, void*), + void *other) { + + if (!cs_fkeep(A->cs, fkeep, other)) { + IGRAPH_ERROR("Cannot filter sparse matrix", IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_dropzeros + * Drop the zero elements from a sparse matrix + * + * As a result of matrix operations, some of the entries in a sparse + * matrix might be zero. This function removes these entries. + * \param A The input matrix, it must be in column-compressed format. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_dropzeros(igraph_sparsemat_t *A) { + + if (!cs_dropzeros(A->cs)) { + IGRAPH_ERROR("Cannot drop zeros from sparse matrix", IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_droptol + * Drop the almost zero elements of a sparse matrix + * + * This function is similar to \ref igraph_sparsemat_dropzeros(), but it + * also drops entries that are closer to zero than the given tolerance + * threshold. + * \param A The input matrix, it must be in column-compressed format. + * \param tol Real number, giving the tolerance threshold. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_droptol(igraph_sparsemat_t *A, igraph_real_t tol) { + + if (!cs_droptol(A->cs, tol)) { + IGRAPH_ERROR("Cannot drop (almost) zeros from sparse matrix", + IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_multiply + * Matrix multiplication + * + * Multiplies two sparse matrices. + * \param A The first input matrix (left hand side), in + * column-compressed format. + * \param B The second input matrix (right hand side), in + * column-compressed format. + * \param res Pointer to an uninitialized sparse matrix, the result is + * stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_multiply(const igraph_sparsemat_t *A, + const igraph_sparsemat_t *B, + igraph_sparsemat_t *res) { + + if (! (res->cs = cs_multiply(A->cs, B->cs))) { + IGRAPH_ERROR("Cannot multiply matrices", IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_add + * Sum of two sparse matrices + * + * \param A The first input matrix, in column-compressed format. + * \param B The second input matrix, in column-compressed format. + * \param alpha Real scalar, \p A is multiplied by \p alpha before the + * addition. + * \param beta Real scalar, \p B is multiplied by \p beta before the + * addition. + * \param res Pointer to an uninitialized sparse matrix, the result + * is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_add(const igraph_sparsemat_t *A, + const igraph_sparsemat_t *B, + igraph_real_t alpha, + igraph_real_t beta, + igraph_sparsemat_t *res) { + + if (! (res->cs = cs_add(A->cs, B->cs, alpha, beta))) { + IGRAPH_ERROR("Cannot add matrices", IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_gaxpy + * Matrix-vector product, added to another vector. + * + * \param A The input matrix, in column-compressed format. + * \param x The input vector, its size must match the number of + * columns in \p A. + * \param res This vector is added to the matrix-vector product + * and it is overwritten by the result. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_gaxpy(const igraph_sparsemat_t *A, + const igraph_vector_t *x, + igraph_vector_t *res) { + + if (A->cs->n != igraph_vector_size(x) || + A->cs->m != igraph_vector_size(res)) { + IGRAPH_ERROR("Invalid matrix/vector size for multiplication", + IGRAPH_EINVAL); + } + + if (! (cs_gaxpy(A->cs, VECTOR(*x), VECTOR(*res)))) { + IGRAPH_ERROR("Cannot perform sparse matrix vector multiplication", + IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_lsolve + * Solve a lower-triangular linear system + * + * Solve the Lx=b linear equation system, where the L coefficient + * matrix is square and lower-triangular, with a zero-free diagonal. + * \param L The input matrix, in column-compressed format. + * \param b The right hand side of the linear system. + * \param res An initialized vector, the result is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_lsolve(const igraph_sparsemat_t *L, + const igraph_vector_t *b, + igraph_vector_t *res) { + + if (L->cs->m != L->cs->n) { + IGRAPH_ERROR("Cannot perform lower triangular solve", IGRAPH_NONSQUARE); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (! cs_lsolve(L->cs, VECTOR(*res))) { + IGRAPH_ERROR("Cannot perform lower triangular solve", IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_ltsolve + * Solve an upper-triangular linear system + * + * Solve the L'x=b linear equation system, where the L + * matrix is square and lower-triangular, with a zero-free diagonal. + * \param L The input matrix, in column-compressed format. + * \param b The right hand side of the linear system. + * \param res An initialized vector, the result is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_ltsolve(const igraph_sparsemat_t *L, + const igraph_vector_t *b, + igraph_vector_t *res) { + + if (L->cs->m != L->cs->n) { + IGRAPH_ERROR("Cannot perform transposed lower triangular solve", + IGRAPH_NONSQUARE); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (!cs_ltsolve(L->cs, VECTOR(*res))) { + IGRAPH_ERROR("Cannot perform lower triangular solve", IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_usolve + * Solve an upper-triangular linear system + * + * Solves the Ux=b upper triangular system. + * \param U The input matrix, in column-compressed format. + * \param b The right hand side of the linear system. + * \param res An initialized vector, the result is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_usolve(const igraph_sparsemat_t *U, + const igraph_vector_t *b, + igraph_vector_t *res) { + + if (U->cs->m != U->cs->n) { + IGRAPH_ERROR("Cannot perform upper triangular solve", IGRAPH_NONSQUARE); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (! cs_usolve(U->cs, VECTOR(*res))) { + IGRAPH_ERROR("Cannot perform upper triangular solve", IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_utsolve + * Solve a lower-triangular linear system + * + * This is the same as \ref igraph_sparsemat_usolve(), but U'x=b is + * solved, where the apostrophe denotes the transpose. + * \param U The input matrix, in column-compressed format. + * \param b The right hand side of the linear system. + * \param res An initialized vector, the result is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_utsolve(const igraph_sparsemat_t *U, + const igraph_vector_t *b, + igraph_vector_t *res) { + + if (U->cs->m != U->cs->n) { + IGRAPH_ERROR("Cannot perform transposed upper triangular solve", + IGRAPH_NONSQUARE); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (!cs_utsolve(U->cs, VECTOR(*res))) { + IGRAPH_ERROR("Cannot perform transposed upper triangular solve", + IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_cholsol + * Solve a symmetric linear system via Cholesky decomposition + * + * Solve Ax=b, where A is a symmetric positive definite matrix. + * \param A The input matrix, in column-compressed format. + * \param v The right hand side. + * \param res An initialized vector, the result is stored here. + * \param order An integer giving the ordering method to use for the + * factorization. Zero is the natural ordering; if it is one, then + * the fill-reducing minimum-degree ordering of A+A' is used. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_cholsol(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res, + int order) { + + if (A->cs->m != A->cs->n) { + IGRAPH_ERROR("Cannot perform sparse symmetric solve", + IGRAPH_NONSQUARE); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (! cs_cholsol(order, A->cs, VECTOR(*res))) { + IGRAPH_ERROR("Cannot perform sparse symmetric solve", IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_lusol + * Solve a linear system via LU decomposition + * + * Solve Ax=b, via LU factorization of A. + * \param A The input matrix, in column-compressed format. + * \param b The right hand side of the equation. + * \param res An initialized vector, the result is stored here. + * \param order The ordering method to use, zero means the natural + * ordering, one means the fill-reducing minimum-degree ordering of + * A+A', two means the ordering of A'*A, after removing the dense + * rows from A. Three means the ordering of A'*A. + * \param tol Real number, the tolerance limit to use for the numeric + * LU factorization. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_lusol(const igraph_sparsemat_t *A, + const igraph_vector_t *b, + igraph_vector_t *res, + int order, + igraph_real_t tol) { + + if (A->cs->m != A->cs->n) { + IGRAPH_ERROR("Cannot perform LU solve", + IGRAPH_NONSQUARE); + } + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + if (! cs_lusol(order, A->cs, VECTOR(*res), tol)) { + IGRAPH_ERROR("Cannot perform LU solve", IGRAPH_FAILURE); + } + + return 0; +} + +static int igraph_i_sparsemat_cc(igraph_t *graph, const igraph_sparsemat_t *A, + igraph_bool_t directed) { + + igraph_vector_t edges; + long int no_of_nodes = A->cs->m; + long int no_of_edges = A->cs->p[A->cs->n]; + int *p = A->cs->p; + int *i = A->cs->i; + long int from = 0; + long int to = 0; + long int e = 0; + + if (no_of_nodes != A->cs->n) { + IGRAPH_ERROR("Cannot create graph object", IGRAPH_NONSQUARE); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + + while (*p < no_of_edges) { + while (to < * (p + 1)) { + if (directed || from >= *i) { + VECTOR(edges)[e++] = from; + VECTOR(edges)[e++] = (*i); + } + to++; + i++; + } + from++; + p++; + } + igraph_vector_resize(&edges, e); + + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) no_of_nodes, + directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_sparsemat_triplet(igraph_t *graph, const igraph_sparsemat_t *A, + igraph_bool_t directed) { + + igraph_vector_t edges; + long int no_of_nodes = A->cs->m; + long int no_of_edges = A->cs->nz; + int *i = A->cs->p; + int *j = A->cs->i; + long int e; + + if (no_of_nodes != A->cs->n) { + IGRAPH_ERROR("Cannot create graph object", IGRAPH_NONSQUARE); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + + for (e = 0; e < 2 * no_of_edges; i++, j++) { + if (directed || *i >= *j) { + VECTOR(edges)[e++] = (*i); + VECTOR(edges)[e++] = (*j); + } + } + igraph_vector_resize(&edges, e); + + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) no_of_nodes, + directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_sparsemat + * Create an igraph graph from a sparse matrix + * + * One edge is created for each non-zero entry in the matrix. If you + * have a symmetric matrix, and want to create an undirected graph, + * then delete the entries in the upper diagonal first, or call \ref + * igraph_simplify() on the result graph to eliminate the multiple + * edges. + * \param graph Pointer to an uninitialized igraph_t object, the + * graphs is stored here. + * \param A The input matrix, in triplet or column-compressed format. + * \param directed Boolean scalar, whether to create a directed + * graph. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat(igraph_t *graph, const igraph_sparsemat_t *A, + igraph_bool_t directed) { + + if (A->cs->nz < 0) { + return (igraph_i_sparsemat_cc(graph, A, directed)); + } else { + return (igraph_i_sparsemat_triplet(graph, A, directed)); + } +} + +static int igraph_i_weighted_sparsemat_cc(const igraph_sparsemat_t *A, + igraph_bool_t directed, const char *attr, + igraph_bool_t loops, + igraph_vector_t *edges, + igraph_vector_t *weights) { + + long int no_of_edges = A->cs->p[A->cs->n]; + int *p = A->cs->p; + int *i = A->cs->i; + igraph_real_t *x = A->cs->x; + long int from = 0; + long int to = 0; + long int e = 0, w = 0; + + IGRAPH_UNUSED(attr); + + igraph_vector_resize(edges, no_of_edges * 2); + igraph_vector_resize(weights, no_of_edges); + + while (*p < no_of_edges) { + while (to < * (p + 1)) { + if ( (loops || from != *i) && (directed || from >= *i) && *x != 0) { + VECTOR(*edges)[e++] = (*i); + VECTOR(*edges)[e++] = from; + VECTOR(*weights)[w++] = (*x); + } + to++; + i++; + x++; + } + from++; + p++; + } + + igraph_vector_resize(edges, e); + igraph_vector_resize(weights, w); + + return 0; +} + +static int igraph_i_weighted_sparsemat_triplet(const igraph_sparsemat_t *A, + igraph_bool_t directed, + const char *attr, + igraph_bool_t loops, + igraph_vector_t *edges, + igraph_vector_t *weights) { + + IGRAPH_UNUSED(A); IGRAPH_UNUSED(directed); IGRAPH_UNUSED(attr); + IGRAPH_UNUSED(loops); IGRAPH_UNUSED(edges); IGRAPH_UNUSED(weights); + + /* TODO */ + IGRAPH_ERROR("Triplet matrices are not implemented", + IGRAPH_UNIMPLEMENTED); + return 0; +} + +int igraph_weighted_sparsemat(igraph_t *graph, const igraph_sparsemat_t *A, + igraph_bool_t directed, const char *attr, + igraph_bool_t loops) { + + igraph_vector_t edges, weights; + int pot_edges = A->cs->nz < 0 ? A->cs->p[A->cs->n] : A->cs->nz; + const char* default_attr = "weight"; + igraph_vector_ptr_t attr_vec; + igraph_attribute_record_t attr_rec; + long int no_of_nodes = A->cs->m; + + if (no_of_nodes != A->cs->n) { + IGRAPH_ERROR("Cannot create graph object", IGRAPH_NONSQUARE); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, pot_edges * 2); + IGRAPH_VECTOR_INIT_FINALLY(&weights, pot_edges); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&attr_vec, 1); + + if (A->cs->nz < 0) { + IGRAPH_CHECK(igraph_i_weighted_sparsemat_cc(A, directed, attr, loops, + &edges, &weights)); + } else { + IGRAPH_CHECK(igraph_i_weighted_sparsemat_triplet(A, directed, attr, + loops, &edges, + &weights)); + } + + /* Prepare attribute record */ + attr_rec.name = attr ? attr : default_attr; + attr_rec.type = IGRAPH_ATTRIBUTE_NUMERIC; + attr_rec.value = &weights; + VECTOR(attr_vec)[0] = &attr_rec; + + /* Create graph */ + IGRAPH_CHECK(igraph_empty(graph, (igraph_integer_t) no_of_nodes, directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + if (igraph_vector_size(&edges) > 0) { + IGRAPH_CHECK(igraph_add_edges(graph, &edges, &attr_vec)); + } + IGRAPH_FINALLY_CLEAN(1); + + /* Cleanup */ + igraph_vector_destroy(&edges); + igraph_vector_destroy(&weights); + igraph_vector_ptr_destroy(&attr_vec); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_get_sparsemat + * Convert an igraph graph to a sparse matrix + * + * If the graph is undirected, then a symmetric matrix is created. + * \param graph The input graph. + * \param res Pointer to an uninitialized sparse matrix. The result + * will be stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_get_sparsemat(const igraph_t *graph, igraph_sparsemat_t *res) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + long int nzmax = directed ? no_of_edges : no_of_edges * 2; + long int i; + + IGRAPH_CHECK(igraph_sparsemat_init(res, (igraph_integer_t) no_of_nodes, + (igraph_integer_t) no_of_nodes, + (igraph_integer_t) nzmax)); + + for (i = 0; i < no_of_edges; i++) { + long int from = IGRAPH_FROM(graph, i); + long int to = IGRAPH_TO(graph, i); + IGRAPH_CHECK(igraph_sparsemat_entry(res, (int) from, (int) to, 1.0)); + if (!directed && from != to) { + IGRAPH_CHECK(igraph_sparsemat_entry(res, (int) to, (int) from, 1.0)); + } + } + + return 0; +} + +#define CHECK(x) if ((x)<0) { IGRAPH_ERROR("Cannot write to file", IGRAPH_EFILE); } + +/** + * \function igraph_sparsemat_print + * Print a sparse matrix to a file + * + * Only the non-zero entries are printed. This function serves more as + * a debugging utility, as currently there is no function that could + * read back the printed matrix from the file. + * \param A The input matrix, triplet or column-compressed format. + * \param outstream The stream to print it to. + * \return Error code. + * + * Time complexity: O(nz) for triplet matrices, O(n+nz) for + * column-compressed matrices. nz is the number of non-zero elements, + * n is the number columns in the matrix. + */ + +int igraph_sparsemat_print(const igraph_sparsemat_t *A, + FILE *outstream) { + + if (A->cs->nz < 0) { + /* CC */ + int j, p; + for (j = 0; j < A->cs->n; j++) { + CHECK(fprintf(outstream, "col %i: locations %i to %i\n", + j, A->cs->p[j], A->cs->p[j + 1] - 1)); + for (p = A->cs->p[j]; p < A->cs->p[j + 1]; p++) { + CHECK(fprintf(outstream, "%i : %g\n", A->cs->i[p], A->cs->x[p])); + } + } + } else { + /* Triplet */ + int p; + for (p = 0; p < A->cs->nz; p++) { + CHECK(fprintf(outstream, "%i %i : %g\n", + A->cs->i[p], A->cs->p[p], A->cs->x[p])); + } + } + + return 0; +} + +#undef CHECK + +static int igraph_i_sparsemat_eye_triplet(igraph_sparsemat_t *A, int n, int nzmax, + igraph_real_t value) { + long int i; + + IGRAPH_CHECK(igraph_sparsemat_init(A, n, n, nzmax)); + + for (i = 0; i < n; i++) { + igraph_sparsemat_entry(A, (int) i, (int) i, value); + } + + return 0; +} + +static int igraph_i_sparsemat_eye_cc(igraph_sparsemat_t *A, int n, + igraph_real_t value) { + long int i; + + if (! (A->cs = cs_spalloc(n, n, n, /*values=*/ 1, /*triplet=*/ 0)) ) { + IGRAPH_ERROR("Cannot create eye sparse matrix", IGRAPH_FAILURE); + } + + for (i = 0; i < n; i++) { + A->cs->p [i] = (int) i; + A->cs->i [i] = (int) i; + A->cs->x [i] = value; + } + A->cs->p [n] = n; + + return 0; +} + +/** + * \function igraph_sparsemat_eye + * Create a sparse identity matrix + * + * \param A An uninitialized sparse matrix, the result is stored + * here. + * \param n The number of rows and number of columns in the matrix. + * \param nzmax The maximum number of non-zero elements, this + * essentially gives the amount of memory that will be allocated for + * matrix elements. + * \param value The value to store in the diagonal. + * \param compress Whether to create a column-compressed matrix. If + * false, then a triplet matrix is created. + * \return Error code. + * + * Time complexity: O(n). + */ + +int igraph_sparsemat_eye(igraph_sparsemat_t *A, int n, int nzmax, + igraph_real_t value, + igraph_bool_t compress) { + if (compress) { + return (igraph_i_sparsemat_eye_cc(A, n, value)); + } else { + return (igraph_i_sparsemat_eye_triplet(A, n, nzmax, value)); + } +} + +static int igraph_i_sparsemat_diag_triplet(igraph_sparsemat_t *A, int nzmax, + const igraph_vector_t *values) { + + int i, n = (int) igraph_vector_size(values); + + IGRAPH_CHECK(igraph_sparsemat_init(A, n, n, nzmax)); + + for (i = 0; i < n; i++) { + igraph_sparsemat_entry(A, i, i, VECTOR(*values)[i]); + } + + return 0; + +} + +static int igraph_i_sparsemat_diag_cc(igraph_sparsemat_t *A, + const igraph_vector_t *values) { + + int i, n = (int) igraph_vector_size(values); + + if (! (A->cs = cs_spalloc(n, n, n, /*values=*/ 1, /*triplet=*/ 0)) ) { + IGRAPH_ERROR("Cannot create eye sparse matrix", IGRAPH_FAILURE); + } + + for (i = 0; i < n; i++) { + A->cs->p [i] = i; + A->cs->i [i] = i; + A->cs->x [i] = VECTOR(*values)[i]; + } + A->cs->p [n] = n; + + return 0; + +} + +/** + * \function igraph_sparsemat_diag + * Create a sparse diagonal matrix + * + * \param A An uninitialized sparse matrix, the result is stored + * here. + * \param nzmax The maximum number of non-zero elements, this + * essentially gives the amount of memory that will be allocated for + * matrix elements. + * \param values The values to store in the diagonal, the size of the + * matrix defined by the length of this vector. + * \param compress Whether to create a column-compressed matrix. If + * false, then a triplet matrix is created. + * \return Error code. + * + * Time complexity: O(n), the length of the diagonal vector. + */ + +int igraph_sparsemat_diag(igraph_sparsemat_t *A, int nzmax, + const igraph_vector_t *values, + igraph_bool_t compress) { + + if (compress) { + return (igraph_i_sparsemat_diag_cc(A, values)); + } else { + return (igraph_i_sparsemat_diag_triplet(A, nzmax, values)); + } +} + +static int igraph_i_sparsemat_arpack_multiply(igraph_real_t *to, + const igraph_real_t *from, + int n, + void *extra) { + igraph_sparsemat_t *A = extra; + igraph_vector_t vto, vfrom; + igraph_vector_view(&vto, to, n); + igraph_vector_view(&vfrom, from, n); + igraph_vector_null(&vto); + IGRAPH_CHECK(igraph_sparsemat_gaxpy(A, &vfrom, &vto)); + return 0; +} + +typedef struct igraph_i_sparsemat_arpack_rssolve_data_t { + igraph_sparsemat_symbolic_t *dis; + igraph_sparsemat_numeric_t *din; + igraph_real_t tol; + igraph_sparsemat_solve_t method; +} igraph_i_sparsemat_arpack_rssolve_data_t; + +static int igraph_i_sparsemat_arpack_solve(igraph_real_t *to, + const igraph_real_t *from, + int n, + void *extra) { + + igraph_i_sparsemat_arpack_rssolve_data_t *data = extra; + igraph_vector_t vfrom, vto; + + igraph_vector_view(&vfrom, from, n); + igraph_vector_view(&vto, to, n); + + if (data->method == IGRAPH_SPARSEMAT_SOLVE_LU) { + IGRAPH_CHECK(igraph_sparsemat_luresol(data->dis, data->din, &vfrom, + &vto)); + } else if (data->method == IGRAPH_SPARSEMAT_SOLVE_QR) { + IGRAPH_CHECK(igraph_sparsemat_qrresol(data->dis, data->din, &vfrom, + &vto)); + + } + + return 0; +} + +/** + * \function igraph_sparsemat_arpack_rssolve + * Eigenvalues and eigenvectors of a symmetric sparse matrix via ARPACK + * + * \param The input matrix, must be column-compressed. + * \param options It is passed to \ref igraph_arpack_rssolve(). See + * \ref igraph_arpack_options_t for the details. If \c mode is 1, + * then ARPACK uses regular mode, if \c mode is 3, then shift and + * invert mode is used and the \c sigma structure member defines + * the shift. + * \param storage Storage for ARPACK. See \ref + * igraph_arpack_rssolve() and \ref igraph_arpack_storage_t for + * details. + * \param values An initialized vector or a null pointer, the + * eigenvalues are stored here. + * \param vectors An initialised matrix, or a null pointer, the + * eigenvectors are stored here, in the columns. + * \param solvemethod The method to solve the linear system, if \c + * mode is 3, i.e. the shift and invert mode is used. + * Possible values: + * \clist + * \cli IGRAPH_SPARSEMAT_SOLVE_LU + * The linear system is solved using LU decomposition. + * \cli IGRAPH_SPARSEMAT_SOLVE_QR + * The linear system is solved using QR decomposition. + * \endclist + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_arpack_rssolve(const igraph_sparsemat_t *A, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_vector_t *values, + igraph_matrix_t *vectors, + igraph_sparsemat_solve_t solvemethod) { + + int n = (int) igraph_sparsemat_nrow(A); + + if (n != igraph_sparsemat_ncol(A)) { + IGRAPH_ERROR("Non-square matrix for ARPACK", IGRAPH_NONSQUARE); + } + + options->n = n; + + if (options->mode == 1) { + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_sparsemat_arpack_multiply, + (void*) A, options, storage, + values, vectors)); + } else if (options->mode == 3) { + igraph_real_t sigma = options->sigma; + igraph_sparsemat_t OP, eye; + igraph_sparsemat_symbolic_t symb; + igraph_sparsemat_numeric_t num; + igraph_i_sparsemat_arpack_rssolve_data_t data; + /*-----------------------------------*/ + /* We need to factor the (A-sigma*I) */ + /*-----------------------------------*/ + + /* Create (A-sigma*I) */ + IGRAPH_CHECK(igraph_sparsemat_eye(&eye, /*n=*/ n, /*nzmax=*/ n, + /*value=*/ -sigma, /*compress=*/ 1)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &eye); + IGRAPH_CHECK(igraph_sparsemat_add(/*A=*/ A, /*B=*/ &eye, /*alpha=*/ 1.0, + /*beta=*/ 1.0, /*res=*/ &OP)); + igraph_sparsemat_destroy(&eye); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &OP); + + if (solvemethod == IGRAPH_SPARSEMAT_SOLVE_LU) { + /* Symbolic analysis */ + IGRAPH_CHECK(igraph_sparsemat_symblu(/*order=*/ 0, &OP, &symb)); + IGRAPH_FINALLY(igraph_sparsemat_symbolic_destroy, &symb); + /* Numeric LU factorization */ + IGRAPH_CHECK(igraph_sparsemat_lu(&OP, &symb, &num, /*tol=*/ 0)); + IGRAPH_FINALLY(igraph_sparsemat_numeric_destroy, &num); + } else if (solvemethod == IGRAPH_SPARSEMAT_SOLVE_QR) { + /* Symbolic analysis */ + IGRAPH_CHECK(igraph_sparsemat_symbqr(/*order=*/ 0, &OP, &symb)); + IGRAPH_FINALLY(igraph_sparsemat_symbolic_destroy, &symb); + /* Numeric QR factorization */ + IGRAPH_CHECK(igraph_sparsemat_qr(&OP, &symb, &num)); + IGRAPH_FINALLY(igraph_sparsemat_numeric_destroy, &num); + } + + data.dis = &symb; + data.din = # + data.tol = options->tol; + data.method = solvemethod; + IGRAPH_CHECK(igraph_arpack_rssolve(igraph_i_sparsemat_arpack_solve, + (void*) &data, options, storage, + values, vectors)); + + igraph_sparsemat_numeric_destroy(&num); + igraph_sparsemat_symbolic_destroy(&symb); + igraph_sparsemat_destroy(&OP); + IGRAPH_FINALLY_CLEAN(3); + } + + return 0; +} + +/** + * \function igraph_sparsemat_arpack_rnsolve + * Eigenvalues and eigenvectors of a nonsymmetric sparse matrix via ARPACK + * + * Eigenvalues and/or eigenvectors of a nonsymmetric sparse matrix. + * \param A The input matrix, in column-compressed mode. + * \param options ARPACK options, it is passed to \ref + * igraph_arpack_rnsolve(). See also \ref igraph_arpack_options_t + * for details. + * \param storage Storage for ARPACK, this is passed to \ref + * igraph_arpack_rnsolve(). See \ref igraph_arpack_storage_t for + * details. + * \param values An initialized matrix, or a null pointer. If not a + * null pointer, then the eigenvalues are stored here, the first + * column is the real part, the second column is the imaginary + * part. + * \param vectors An initialized matrix, or a null pointer. If not a + * null pointer, then the eigenvectors are stored here, please see + * \ref igraph_arpack_rnsolve() for the format. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_arpack_rnsolve(const igraph_sparsemat_t *A, + igraph_arpack_options_t *options, + igraph_arpack_storage_t *storage, + igraph_matrix_t *values, + igraph_matrix_t *vectors) { + + int n = (int) igraph_sparsemat_nrow(A); + + if (n != igraph_sparsemat_ncol(A)) { + IGRAPH_ERROR("Non-square matrix for ARPACK", IGRAPH_NONSQUARE); + } + + options->n = n; + + return igraph_arpack_rnsolve(igraph_i_sparsemat_arpack_multiply, + (void*) A, options, storage, + values, vectors); +} + +/** + * \function igraph_sparsemat_symbqr + * Symbolic QR decomposition + * + * QR decomposition of sparse matrices involves two steps, the first + * is calling this function, and then \ref + * igraph_sparsemat_qr(). + * \param order The ordering to use: 0 means natural ordering, 1 means + * minimum degree ordering of A+A', 2 is minimum degree ordering of + * A'A after removing the dense rows from A, and 3 is the minimum + * degree ordering of A'A. + * \param A The input matrix, in column-compressed format. + * \param dis The result of the symbolic analysis is stored here. Once + * not needed anymore, it must be destroyed by calling \ref + * igraph_sparsemat_symbolic_destroy(). + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_symbqr(long int order, const igraph_sparsemat_t *A, + igraph_sparsemat_symbolic_t *dis) { + + dis->symbolic = cs_sqr((int) order, A->cs, /*qr=*/ 1); + if (!dis->symbolic) { + IGRAPH_ERROR("Cannot do symbolic QR decomposition", IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_symblu + * Symbolic LU decomposition + * + * LU decomposition of sparse matrices involves two steps, the first + * is calling this function, and then \ref igraph_sparsemat_lu(). + * \param order The ordering to use: 0 means natural ordering, 1 means + * minimum degree ordering of A+A', 2 is minimum degree ordering of + * A'A after removing the dense rows from A, and 3 is the minimum + * degree ordering of A'A. + * \param A The input matrix, in column-compressed format. + * \param dis The result of the symbolic analysis is stored here. Once + * not needed anymore, it must be destroyed by calling \ref + * igraph_sparsemat_symbolic_destroy(). + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_symblu(long int order, const igraph_sparsemat_t *A, + igraph_sparsemat_symbolic_t *dis) { + + dis->symbolic = cs_sqr((int) order, A->cs, /*qr=*/ 0); + if (!dis->symbolic) { + IGRAPH_ERROR("Cannot do symbolic LU decomposition", IGRAPH_FAILURE); + } + + return 0; +} + +/** + * \function igraph_sparsemat_lu + * LU decomposition of a sparse matrix + * + * Performs numeric sparse LU decomposition of a matrix. + * \param A The input matrix, in column-compressed format. + * \param dis The symbolic analysis for LU decomposition, coming from + * a call to the \ref igraph_sparsemat_symblu() function. + * \param din The numeric decomposition, the result is stored here. It + * can be used to solve linear systems with changing right hand + * side vectors, by calling \ref igraph_sparsemat_luresol(). Once + * not needed any more, it must be destroyed by calling \ref + * igraph_sparsemat_symbolic_destroy() on it. + * \param tol The tolerance for the numeric LU decomposition. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_lu(const igraph_sparsemat_t *A, + const igraph_sparsemat_symbolic_t *dis, + igraph_sparsemat_numeric_t *din, double tol) { + din->numeric = cs_lu(A->cs, dis->symbolic, tol); + if (!din->numeric) { + IGRAPH_ERROR("Cannot do LU decomposition", IGRAPH_FAILURE); + } + return 0; +} + +/** + * \function igraph_sparsemat_qr + * QR decomposition of a sparse matrix + * + * Numeric QR decomposition of a sparse matrix. + * \param A The input matrix, in column-compressed format. + * \param dis The result of the symbolic QR analysis, from the + * function \ref igraph_sparsemat_symbqr(). + * \param din The result of the decomposition is stored here, it can + * be used to solve many linear systems with the same coefficient + * matrix and changing right hand sides, using the \ref + * igraph_sparsemat_qrresol() function. Once not needed any more, + * one should call \ref igraph_sparsemat_numeric_destroy() on it to + * free the allocated memory. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_qr(const igraph_sparsemat_t *A, + const igraph_sparsemat_symbolic_t *dis, + igraph_sparsemat_numeric_t *din) { + din->numeric = cs_qr(A->cs, dis->symbolic); + if (!din->numeric) { + IGRAPH_ERROR("Cannot do QR decomposition", IGRAPH_FAILURE); + } + return 0; +} + +/** + * \function igraph_sparsemat_luresol + * Solve linear system using a precomputed LU decomposition + * + * Uses the LU decomposition of a matrix to solve linear systems. + * \param dis The symbolic analysis of the coefficient matrix, the + * result of \ref igraph_sparsemat_symblu(). + * \param din The LU decomposition, the result of a call to \ref + * igraph_sparsemat_lu(). + * \param b A vector that defines the right hand side of the linear + * equation system. + * \param res An initialized vector, the solution of the linear system + * is stored here. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_luresol(const igraph_sparsemat_symbolic_t *dis, + const igraph_sparsemat_numeric_t *din, + const igraph_vector_t *b, + igraph_vector_t *res) { + int n = din->numeric->L->n; + igraph_real_t *workspace; + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + workspace = igraph_Calloc(n, igraph_real_t); + if (!workspace) { + IGRAPH_ERROR("Cannot LU (re)solve sparse matrix", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, workspace); + + if (!cs_ipvec(din->numeric->pinv, VECTOR(*res), workspace, n)) { + IGRAPH_ERROR("Cannot LU (re)solve sparse matrix", IGRAPH_FAILURE); + } + if (!cs_lsolve(din->numeric->L, workspace)) { + IGRAPH_ERROR("Cannot LU (re)solve sparse matrix", IGRAPH_FAILURE); + } + if (!cs_usolve(din->numeric->U, workspace)) { + IGRAPH_ERROR("Cannot LU (re)solve sparse matrix", IGRAPH_FAILURE); + } + if (!cs_ipvec(dis->symbolic->q, workspace, VECTOR(*res), n)) { + IGRAPH_ERROR("Cannot LU (re)solve sparse matrix", IGRAPH_FAILURE); + } + + igraph_Free(workspace); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_sparsemat_qrresol + * Solve a linear system using a precomputed QR decomposition + * + * Solves a linear system using a QR decomposition of its coefficient + * matrix. + * \param dis Symbolic analysis of the coefficient matrix, the result + * of \ref igraph_sparsemat_symbqr(). + * \param din The QR decomposition of the coefficient matrix, the + * result of \ref igraph_sparsemat_qr(). + * \param b Vector, giving the right hand side of the linear equation + * system. + * \param res An initialized vector, the solution is stored here. It + * is resized as needed. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_qrresol(const igraph_sparsemat_symbolic_t *dis, + const igraph_sparsemat_numeric_t *din, + const igraph_vector_t *b, + igraph_vector_t *res) { + int n = din->numeric->L->n; + igraph_real_t *workspace; + int k; + + if (res != b) { + IGRAPH_CHECK(igraph_vector_update(res, b)); + } + + workspace = igraph_Calloc(dis->symbolic ? dis->symbolic->m2 : 1, + igraph_real_t); + if (!workspace) { + IGRAPH_ERROR("Cannot QR (re)solve sparse matrix", IGRAPH_FAILURE); + } + IGRAPH_FINALLY(igraph_free, workspace); + + if (!cs_ipvec(dis->symbolic->pinv, VECTOR(*res), workspace, n)) { + IGRAPH_ERROR("Cannot QR (re)solve sparse matrix", IGRAPH_FAILURE); + } + for (k = 0; k < n; k++) { + if (!cs_happly(din->numeric->L, k, din->numeric->B[k], workspace)) { + IGRAPH_ERROR("Cannot QR (re)solve sparse matrix", IGRAPH_FAILURE); + } + } + if (!cs_usolve(din->numeric->U, workspace)) { + IGRAPH_ERROR("Cannot QR (re)solve sparse matrix", IGRAPH_FAILURE); + } + if (!cs_ipvec(dis->symbolic->q, workspace, VECTOR(*res), n)) { + IGRAPH_ERROR("Cannot QR (re)solve sparse matrix", IGRAPH_FAILURE); + } + + igraph_Free(workspace); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_sparsemat_symbolic_destroy + * Deallocate memory for a symbolic decomposition + * + * Frees the memory allocated by \ref igraph_sparsemat_symbqr() or + * \ref igraph_sparsemat_symblu(). + * \param dis The symbolic analysis. + * + * Time complexity: O(1). + */ + +void igraph_sparsemat_symbolic_destroy(igraph_sparsemat_symbolic_t *dis) { + cs_sfree(dis->symbolic); + dis->symbolic = 0; +} + +/** + * \function igraph_sparsemat_numeric_destroy + * Deallocate memory for a numeric decomposition + * + * Frees the memoty allocated by \ref igraph_sparsemat_qr() or \ref + * igraph_sparsemat_lu(). + * \param din The LU or QR decomposition. + * + * Time complexity: O(1). + */ + +void igraph_sparsemat_numeric_destroy(igraph_sparsemat_numeric_t *din) { + cs_nfree(din->numeric); + din->numeric = 0; +} + +/** + * \function igraph_matrix_as_sparsemat + * Convert a dense matrix to a sparse matrix + * + * \param res An uninitialized sparse matrix, the result is stored + * here. + * \param mat The dense input matrix. + * \param tol Real scalar, the tolerance. Values closer than \p tol to + * zero are considered as zero, and will not be included in the + * sparse matrix. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the dense + * matrix. + */ + +int igraph_matrix_as_sparsemat(igraph_sparsemat_t *res, + const igraph_matrix_t *mat, + igraph_real_t tol) { + int nrow = (int) igraph_matrix_nrow(mat); + int ncol = (int) igraph_matrix_ncol(mat); + int i, j, nzmax = 0; + + for (i = 0; i < nrow; i++) { + for (j = 0; j < ncol; j++) { + if (fabs(MATRIX(*mat, i, j)) > tol) { + nzmax++; + } + } + } + + IGRAPH_CHECK(igraph_sparsemat_init(res, nrow, ncol, nzmax)); + + for (i = 0; i < nrow; i++) { + for (j = 0; j < ncol; j++) { + if (fabs(MATRIX(*mat, i, j)) > tol) { + IGRAPH_CHECK(igraph_sparsemat_entry(res, i, j, MATRIX(*mat, i, j))); + } + } + } + + return 0; +} + +static int igraph_i_sparsemat_as_matrix_cc(igraph_matrix_t *res, + const igraph_sparsemat_t *spmat) { + + int nrow = (int) igraph_sparsemat_nrow(spmat); + int ncol = (int) igraph_sparsemat_ncol(spmat); + int *p = spmat->cs->p; + int *i = spmat->cs->i; + igraph_real_t *x = spmat->cs->x; + int nzmax = spmat->cs->nzmax; + int from = 0, to = 0; + + IGRAPH_CHECK(igraph_matrix_resize(res, nrow, ncol)); + igraph_matrix_null(res); + + while (*p < nzmax) { + while (to < * (p + 1)) { + MATRIX(*res, *i, from) += *x; + to++; + i++; + x++; + } + from++; + p++; + } + + return 0; +} + +static int igraph_i_sparsemat_as_matrix_triplet(igraph_matrix_t *res, + const igraph_sparsemat_t *spmat) { + int nrow = (int) igraph_sparsemat_nrow(spmat); + int ncol = (int) igraph_sparsemat_ncol(spmat); + int *i = spmat->cs->p; + int *j = spmat->cs->i; + igraph_real_t *x = spmat->cs->x; + int nz = spmat->cs->nz; + int e; + + IGRAPH_CHECK(igraph_matrix_resize(res, nrow, ncol)); + igraph_matrix_null(res); + + for (e = 0; e < nz; e++, i++, j++, x++) { + MATRIX(*res, *j, *i) += *x; + } + + return 0; +} + +/** + * \function igraph_sparsemat_as_matrix + * Convert a sparse matrix to a dense matrix + * + * \param res Pointer to an initialized matrix, the result is stored + * here. It will be resized to the required size. + * \param spmat The input sparse matrix, in triplet or + * column-compressed format. + * \return Error code. + * + * Time complexity: O(mn), the number of elements in the dense + * matrix. + */ + +int igraph_sparsemat_as_matrix(igraph_matrix_t *res, + const igraph_sparsemat_t *spmat) { + if (spmat->cs->nz < 0) { + return (igraph_i_sparsemat_as_matrix_cc(res, spmat)); + } else { + return (igraph_i_sparsemat_as_matrix_triplet(res, spmat)); + } +} + +/** + * \function igraph_sparsemat_max + * Maximum of a sparse matrix + * + * \param A The input matrix, column-compressed. + * \return The maximum in the input matrix, or \c IGRAPH_NEGINFINITY + * if the matrix has zero elements. + * + * Time complexity: TODO. + */ + +igraph_real_t igraph_sparsemat_max(igraph_sparsemat_t *A) { + int i, n; + igraph_real_t *ptr; + igraph_real_t res; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ptr = A->cs->x; + n = A->cs->nz == -1 ? A->cs->p[A->cs->n] : A->cs->nz; + if (n == 0) { + return IGRAPH_NEGINFINITY; + } + res = *ptr; + for (i = 1; i < n; i++, ptr++) { + if (*ptr > res) { + res = *ptr; + } + } + return res; +} + +/* TODO: CC matrix don't actually need _dupl, + because the elements are right beside each other. + Same for max and minmax. */ + +/** + * \function igraph_sparsemat_min + * Minimum of a sparse matrix + * + * \param A The input matrix, column-compressed. + * \return The minimum in the input matrix, or \c IGRAPH_POSINFINITY + * if the matrix has zero elements. + * + * Time complexity: TODO. + */ + +igraph_real_t igraph_sparsemat_min(igraph_sparsemat_t *A) { + int i, n; + igraph_real_t *ptr; + igraph_real_t res; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ptr = A->cs->x; + n = A->cs->nz == -1 ? A->cs->p[A->cs->n] : A->cs->nz; + if (n == 0) { + return IGRAPH_POSINFINITY; + } + res = *ptr; + for (i = 1; i < n; i++, ptr++) { + if (*ptr < res) { + res = *ptr; + } + } + return res; +} + +/** + * \function igraph_sparsemat_minmax + * Minimum and maximum of a sparse matrix + * + * \param A The input matrix, column-compressed. + * \param min The minimum in the input matrix is stored here, or \c + * IGRAPH_POSINFINITY if the matrix has zero elements. + * \param max The maximum in the input matrix is stored here, or \c + * IGRAPH_NEGINFINITY if the matrix has zero elements. + * \return Error code. + * + * Time complexity: TODO. + */ + + +int igraph_sparsemat_minmax(igraph_sparsemat_t *A, + igraph_real_t *min, igraph_real_t *max) { + int i, n; + igraph_real_t *ptr; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ptr = A->cs->x; + n = A->cs->nz == -1 ? A->cs->p[A->cs->n] : A->cs->nz; + if (n == 0) { + *min = IGRAPH_POSINFINITY; + *max = IGRAPH_NEGINFINITY; + return 0; + } + *min = *max = *ptr; + for (i = 1; i < n; i++, ptr++) { + if (*ptr > *max) { + *max = *ptr; + } else if (*ptr < *min) { + *min = *ptr; + } + } + return 0; +} + +/** + * \function igraph_sparsemat_count_nonzero + * Count nonzero elements of a sparse matrix + * + * \param A The input matrix, column-compressed. + * \return Error code. + * + * Time complexity: TODO. + */ + +long int igraph_sparsemat_count_nonzero(igraph_sparsemat_t *A) { + int i, n; + int res = 0; + igraph_real_t *ptr; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ptr = A->cs->x; + n = A->cs->nz == -1 ? A->cs->p[A->cs->n] : A->cs->nz; + if (n == 0) { + return 0; + } + for (i = 0; i < n; i++, ptr++) { + if (*ptr) { + res++; + } + } + return res; +} + +/** + * \function igraph_sparsemat_count_nonzerotol + * Count nonzero elements of a sparse matrix, ignoring elements close to zero + * + * Count the number of matrix entries that are closer to zero than \p + * tol. + * \param The input matrix, column-compressed. + * \param Real scalar, the tolerance. + * \return Error code. + * + * Time complexity: TODO. + */ + +long int igraph_sparsemat_count_nonzerotol(igraph_sparsemat_t *A, + igraph_real_t tol) { + int i, n; + int res = 0; + igraph_real_t *ptr; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ptr = A->cs->x; + n = A->cs->nz == -1 ? A->cs->p[A->cs->n] : A->cs->nz; + if (n == 0) { + return 0; + } + for (i = 0; i < n; i++, ptr++) { + if (*ptr < - tol || *ptr > tol) { + res++; + } + } + return res; +} + +static int igraph_i_sparsemat_rowsums_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + int i; + int *pi = A->cs->i; + double *px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_null(res); + + for (i = 0; i < A->cs->nz; i++, pi++, px++) { + VECTOR(*res)[ *pi ] += *px; + } + + return 0; +} + +static int igraph_i_sparsemat_rowsums_cc(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + int ne = A->cs->p[A->cs->n]; + double *px = A->cs->x; + int *pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_null(res); + + for (; pi < A->cs->i + ne; pi++, px++) { + VECTOR(*res)[ *pi ] += *px; + } + + return 0; +} + +/** + * \function igraph_sparsemat_rowsums + * Row-wise sums. + * + * \param A The input matrix, in triplet or column-compressed format. + * \param res An initialized vector, the result is stored here. It + * will be resized as needed. + * \return Error code. + * + * Time complexity: O(nz), the number of non-zero elements. + */ + +int igraph_sparsemat_rowsums(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_rowsums_triplet(A, res); + } else { + return igraph_i_sparsemat_rowsums_cc(A, res); + } +} + +static int igraph_i_sparsemat_rowmins_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + int i; + int *pi = A->cs->i; + double *px = A->cs->x; + double inf = IGRAPH_INFINITY; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_fill(res, inf); + + for (i = 0; i < A->cs->nz; i++, pi++, px++) { + if (*px < VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + } + } + + return 0; +} + +static int igraph_i_sparsemat_rowmins_cc(igraph_sparsemat_t *A, + igraph_vector_t *res) { + int ne; + double *px; + int *pi; + double inf = IGRAPH_INFINITY; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ne = A->cs->p[A->cs->n]; + px = A->cs->x; + pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_fill(res, inf); + + for (; pi < A->cs->i + ne; pi++, px++) { + if (*px < VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + } + } + + return 0; +} + +int igraph_sparsemat_rowmins(igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_rowmins_triplet(A, res); + } else { + return igraph_i_sparsemat_rowmins_cc(A, res); + } +} + + +static int igraph_i_sparsemat_rowmaxs_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + int i; + int *pi = A->cs->i; + double *px = A->cs->x; + double inf = IGRAPH_NEGINFINITY; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_fill(res, inf); + + for (i = 0; i < A->cs->nz; i++, pi++, px++) { + if (*px > VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + } + } + + return 0; +} + +static int igraph_i_sparsemat_rowmaxs_cc(igraph_sparsemat_t *A, + igraph_vector_t *res) { + int ne; + double *px; + int *pi; + double inf = IGRAPH_NEGINFINITY; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + ne = A->cs->p[A->cs->n]; + px = A->cs->x; + pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + igraph_vector_fill(res, inf); + + for (; pi < A->cs->i + ne; pi++, px++) { + if (*px > VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + } + } + + return 0; +} + +int igraph_sparsemat_rowmaxs(igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_rowmaxs_triplet(A, res); + } else { + return igraph_i_sparsemat_rowmaxs_cc(A, res); + } +} + +static int igraph_i_sparsemat_colmins_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + int i; + int *pp = A->cs->p; + double *px = A->cs->x; + double inf = IGRAPH_INFINITY; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->n)); + igraph_vector_fill(res, inf); + + for (i = 0; i < A->cs->nz; i++, pp++, px++) { + if (*px < VECTOR(*res)[ *pp ]) { + VECTOR(*res)[ *pp ] = *px; + } + } + + return 0; +} + +static int igraph_i_sparsemat_colmins_cc(igraph_sparsemat_t *A, + igraph_vector_t *res) { + int n; + double *px; + int *pp; + int *pi; + double *pr; + double inf = IGRAPH_INFINITY; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + n = A->cs->n; + px = A->cs->x; + pp = A->cs->p; + pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, n)); + igraph_vector_fill(res, inf); + pr = VECTOR(*res); + + for (; pp < A->cs->p + n; pp++, pr++) { + for (; pi < A->cs->i + * (pp + 1); pi++, px++) { + if (*px < *pr) { + *pr = *px; + } + } + } + return 0; +} + +int igraph_sparsemat_colmins(igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_colmins_triplet(A, res); + } else { + return igraph_i_sparsemat_colmins_cc(A, res); + } +} + +static int igraph_i_sparsemat_colmaxs_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + int i; + int *pp = A->cs->p; + double *px = A->cs->x; + double inf = IGRAPH_NEGINFINITY; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->n)); + igraph_vector_fill(res, inf); + + for (i = 0; i < A->cs->nz; i++, pp++, px++) { + if (*px > VECTOR(*res)[ *pp ]) { + VECTOR(*res)[ *pp ] = *px; + } + } + + return 0; +} + +static int igraph_i_sparsemat_colmaxs_cc(igraph_sparsemat_t *A, + igraph_vector_t *res) { + int n; + double *px; + int *pp; + int *pi; + double *pr; + double inf = IGRAPH_NEGINFINITY; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + n = A->cs->n; + px = A->cs->x; + pp = A->cs->p; + pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, n)); + igraph_vector_fill(res, inf); + pr = VECTOR(*res); + + for (; pp < A->cs->p + n; pp++, pr++) { + for (; pi < A->cs->i + * (pp + 1); pi++, px++) { + if (*px > *pr) { + *pr = *px; + } + } + } + return 0; +} + +int igraph_sparsemat_colmaxs(igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_colmaxs_triplet(A, res); + } else { + return igraph_i_sparsemat_colmaxs_cc(A, res); + } +} + +static int igraph_i_sparsemat_which_min_rows_triplet(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + int i; + int *pi = A->cs->i; + int *pp = A->cs->p; + double *px = A->cs->x; + double inf = IGRAPH_INFINITY; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + IGRAPH_CHECK(igraph_vector_int_resize(pos, A->cs->m)); + igraph_vector_fill(res, inf); + igraph_vector_int_null(pos); + + for (i = 0; i < A->cs->nz; i++, pi++, px++, pp++) { + if (*px < VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + VECTOR(*pos)[ *pi ] = *pp; + } + } + + return 0; +} + +static int igraph_i_sparsemat_which_min_rows_cc(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + int n; + double *px; + int *pp; + int *pi; + double inf = IGRAPH_INFINITY; + int j; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + n = A->cs->n; + px = A->cs->x; + pp = A->cs->p; + pi = A->cs->i; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->m)); + IGRAPH_CHECK(igraph_vector_int_resize(pos, A->cs->m)); + igraph_vector_fill(res, inf); + igraph_vector_int_null(pos); + + for (j = 0; pp < A->cs->p + n; pp++, j++) { + for (; pi < A->cs->i + * (pp + 1); pi++, px++) { + if (*px < VECTOR(*res)[ *pi ]) { + VECTOR(*res)[ *pi ] = *px; + VECTOR(*pos)[ *pi ] = j; + } + } + } + + return 0; +} + +int igraph_sparsemat_which_min_rows(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_which_min_rows_triplet(A, res, pos); + } else { + return igraph_i_sparsemat_which_min_rows_cc(A, res, pos); + } +} + +static int igraph_i_sparsemat_which_min_cols_triplet(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + + int i; + int *pi = A->cs->i; + int *pp = A->cs->p; + double *px = A->cs->x; + double inf = IGRAPH_INFINITY; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->n)); + IGRAPH_CHECK(igraph_vector_int_resize(pos, A->cs->n)); + igraph_vector_fill(res, inf); + igraph_vector_int_null(pos); + + for (i = 0; i < A->cs->nz; i++, pi++, pp++, px++) { + if (*px < VECTOR(*res)[ *pp ]) { + VECTOR(*res)[ *pp ] = *px; + VECTOR(*pos)[ *pp ] = *pi; + } + } + + return 0; +} + +static int igraph_i_sparsemat_which_min_cols_cc(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + int n, j, p; + double *px; + double *pr; + int *ppos; + double inf = IGRAPH_INFINITY; + + IGRAPH_CHECK(igraph_sparsemat_dupl(A)); + + n = A->cs->n; + px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, n)); + igraph_vector_fill(res, inf); + pr = VECTOR(*res); + IGRAPH_CHECK(igraph_vector_int_resize(pos, n)); + igraph_vector_int_null(pos); + ppos = VECTOR(*pos); + + for (j = 0; j < A->cs->n; j++, pr++, ppos++) { + for (p = A->cs->p[j]; p < A->cs->p[j + 1]; p++, px++) { + if (*px < *pr) { + *pr = *px; + *ppos = A->cs->i[p]; + } + } + } + return 0; +} + +int igraph_sparsemat_which_min_cols(igraph_sparsemat_t *A, + igraph_vector_t *res, + igraph_vector_int_t *pos) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_which_min_cols_triplet(A, res, pos); + } else { + return igraph_i_sparsemat_which_min_cols_cc(A, res, pos); + } +} + +static int igraph_i_sparsemat_colsums_triplet(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + int i; + int *pp = A->cs->p; + double *px = A->cs->x; + + IGRAPH_CHECK(igraph_vector_resize(res, A->cs->n)); + igraph_vector_null(res); + + for (i = 0; i < A->cs->nz; i++, pp++, px++) { + VECTOR(*res)[ *pp ] += *px; + } + + return 0; +} + +static int igraph_i_sparsemat_colsums_cc(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + int n = A->cs->n; + double *px = A->cs->x; + int *pp = A->cs->p; + int *pi = A->cs->i; + double *pr; + + IGRAPH_CHECK(igraph_vector_resize(res, n)); + igraph_vector_null(res); + pr = VECTOR(*res); + + for (; pp < A->cs->p + n; pp++, pr++) { + for (; pi < A->cs->i + * (pp + 1); pi++, px++) { + *pr += *px; + } + } + return 0; +} + +/** + * \function igraph_sparsemat_colsums + * Column-wise sums + * + * \param A The input matrix, in triplet or column-compressed format. + * \param res An initialized vector, the result is stored here. It + * will be resized as needed. + * \return Error code. + * + * Time complexity: O(nz) for triplet matrices, O(nz+n) for + * column-compressed ones, nz is the number of non-zero elements, n is + * the number of columns. + */ + +int igraph_sparsemat_colsums(const igraph_sparsemat_t *A, + igraph_vector_t *res) { + if (igraph_sparsemat_is_triplet(A)) { + return igraph_i_sparsemat_colsums_triplet(A, res); + } else { + return igraph_i_sparsemat_colsums_cc(A, res); + } +} + +/** + * \function igraph_sparsemat_scale + * Scale a sparse matrix + * + * Multiplies all elements of a sparse matrix, by the given scalar. + * \param A The input matrix. + * \param by The scaling factor. + * \return Error code. + * + * Time complexity: O(nz), the number of non-zero elements in the + * matrix. + */ + +int igraph_sparsemat_scale(igraph_sparsemat_t *A, igraph_real_t by) { + + double *px = A->cs->x; + int n = A->cs->nz == -1 ? A->cs->p[A->cs->n] : A->cs->nz; + double *stop = px + n; + + for (; px < stop; px++) { + *px *= by; + } + + return 0; +} + +/** + * \function igraph_sparsemat_add_rows + * Add rows to a sparse matrix + * + * The current matrix elements are retained and all elements in the + * new rows are zero. + * \param A The input matrix, in triplet or column-compressed format. + * \param n The number of rows to add. + * \return Error code. + * + * Time complexity: O(1). + */ + +int igraph_sparsemat_add_rows(igraph_sparsemat_t *A, long int n) { + A->cs->m += n; + return 0; +} + +/** + * \function igraph_sparsemat_add_cols + * Add columns to a sparse matrix + * + * The current matrix elements are retained, and all elements in the + * new columns are zero. + * \param A The input matrix, in triplet or column-compressed format. + * \param n The number of columns to add. + * \return Error code. + * + * Time complexity: TODO. + */ + +int igraph_sparsemat_add_cols(igraph_sparsemat_t *A, long int n) { + if (igraph_sparsemat_is_triplet(A)) { + A->cs->n += n; + } else { + int *newp = realloc(A->cs->p, sizeof(int) * (size_t) (A->cs->n + n + 1)); + int i; + if (!newp) { + IGRAPH_ERROR("Cannot add columns to sparse matrix", IGRAPH_ENOMEM); + } + if (newp != A->cs->p) { + A->cs->p = newp; + } + for (i = A->cs->n + 1; i < A->cs->n + n + 1; i++) { + A->cs->p[i] = A->cs->p[i - 1]; + } + A->cs->n += n; + } + return 0; +} + +/** + * \function igraph_sparsemat_resize + * Resize a sparse matrix + * + * This function resizes a sparse matrix. The resized sparse matrix + * will be empty. + * + * \param A The initialized sparse matrix to resize. + * \param nrow The new number of rows. + * \param ncol The new number of columns. + * \param nzmax The new maximum number of elements. + * \return Error code. + * + * Time complexity: O(nzmax), the maximum number of non-zero elements. + */ + +int igraph_sparsemat_resize(igraph_sparsemat_t *A, long int nrow, + long int ncol, int nzmax) { + + if (A->cs->nz < 0) { + igraph_sparsemat_t tmp; + IGRAPH_CHECK(igraph_sparsemat_init(&tmp, (int) nrow, (int) ncol, nzmax)); + igraph_sparsemat_destroy(A); + *A = tmp; + } else { + IGRAPH_CHECK(igraph_sparsemat_realloc(A, nzmax)); + A->cs->m = (int) nrow; + A->cs->n = (int) ncol; + A->cs->nz = 0; + } + return 0; +} + +int igraph_sparsemat_nonzero_storage(const igraph_sparsemat_t *A) { + if (A->cs->nz < 0) { + return A->cs->p[A->cs->n]; + } else { + return A->cs->nz; + } +} + +int igraph_sparsemat_getelements(const igraph_sparsemat_t *A, + igraph_vector_int_t *i, + igraph_vector_int_t *j, + igraph_vector_t *x) { + int nz = A->cs->nz; + if (nz < 0) { + nz = A->cs->p[A->cs->n]; + IGRAPH_CHECK(igraph_vector_int_resize(i, nz)); + IGRAPH_CHECK(igraph_vector_int_resize(j, A->cs->n + 1)); + IGRAPH_CHECK(igraph_vector_resize(x, nz)); + memcpy(VECTOR(*i), A->cs->i, (size_t) nz * sizeof(int)); + memcpy(VECTOR(*j), A->cs->p, (size_t) (A->cs->n + 1) * sizeof(int)); + memcpy(VECTOR(*x), A->cs->x, (size_t) nz * sizeof(igraph_real_t)); + } else { + IGRAPH_CHECK(igraph_vector_int_resize(i, nz)); + IGRAPH_CHECK(igraph_vector_int_resize(j, nz)); + IGRAPH_CHECK(igraph_vector_resize(x, nz)); + memcpy(VECTOR(*i), A->cs->i, (size_t) nz * sizeof(int)); + memcpy(VECTOR(*j), A->cs->p, (size_t) nz * sizeof(int)); + memcpy(VECTOR(*x), A->cs->x, (size_t) nz * sizeof(igraph_real_t)); + } + return 0; +} + +int igraph_sparsemat_scale_rows(igraph_sparsemat_t *A, + const igraph_vector_t *fact) { + int *i = A->cs->i; + igraph_real_t *x = A->cs->x; + int no_of_edges = A->cs->nz < 0 ? A->cs->p[A->cs->n] : A->cs->nz; + int e; + + for (e = 0; e < no_of_edges; e++, x++, i++) { + igraph_real_t f = VECTOR(*fact)[*i]; + (*x) *= f; + } + + return 0; +} + +static int igraph_i_sparsemat_scale_cols_cc(igraph_sparsemat_t *A, + const igraph_vector_t *fact) { + int *i = A->cs->i; + igraph_real_t *x = A->cs->x; + int no_of_edges = A->cs->p[A->cs->n]; + int e; + int c = 0; /* actual column */ + + for (e = 0; e < no_of_edges; e++, x++, i++) { + igraph_real_t f; + while (c < A->cs->n && A->cs->p[c + 1] == e) { + c++; + } + f = VECTOR(*fact)[c]; + (*x) *= f; + } + + return 0; +} + +static int igraph_i_sparsemat_scale_cols_triplet(igraph_sparsemat_t *A, + const igraph_vector_t *fact) { + int *j = A->cs->p; + igraph_real_t *x = A->cs->x; + int no_of_edges = A->cs->nz; + int e; + + for (e = 0; e < no_of_edges; e++, x++, j++) { + igraph_real_t f = VECTOR(*fact)[*j]; + (*x) *= f; + } + + return 0; +} + +int igraph_sparsemat_scale_cols(igraph_sparsemat_t *A, + const igraph_vector_t *fact) { + if (A->cs->nz < 0) { + return igraph_i_sparsemat_scale_cols_cc(A, fact); + } else { + return igraph_i_sparsemat_scale_cols_triplet(A, fact); + } +} + +int igraph_sparsemat_multiply_by_dense(const igraph_sparsemat_t *A, + const igraph_matrix_t *B, + igraph_matrix_t *res) { + + int m = (int) igraph_sparsemat_nrow(A); + int n = (int) igraph_sparsemat_ncol(A); + int p = (int) igraph_matrix_ncol(B); + int i; + + if (igraph_matrix_nrow(B) != n) { + IGRAPH_ERROR("Invalid dimensions in sparse-dense matrix product", + IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, m, p)); + igraph_matrix_null(res); + + for (i = 0; i < p; i++) { + if (!(cs_gaxpy(A->cs, &MATRIX(*B, 0, i), &MATRIX(*res, 0, i)))) { + IGRAPH_ERROR("Cannot perform sparse-dense matrix multiplication", + IGRAPH_FAILURE); + } + } + + return 0; +} + +int igraph_sparsemat_dense_multiply(const igraph_matrix_t *A, + const igraph_sparsemat_t *B, + igraph_matrix_t *res) { + int m = (int) igraph_matrix_nrow(A); + int n = (int) igraph_matrix_ncol(A); + int p = (int) igraph_sparsemat_ncol(B); + int r, c; + int *Bp = B->cs->p; + + if (igraph_sparsemat_nrow(B) != n) { + IGRAPH_ERROR("Invalid dimensions in dense-sparse matrix product", + IGRAPH_EINVAL); + } + + if (!igraph_sparsemat_is_cc(B)) { + IGRAPH_ERROR("Dense-sparse product is only implemented for " + "column-compressed sparse matrices", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_matrix_resize(res, m, p)); + igraph_matrix_null(res); + + for (c = 0; c < p; c++) { + for (r = 0; r < m; r++) { + int idx = *Bp; + while (idx < * (Bp + 1)) { + MATRIX(*res, r, c) += MATRIX(*A, r, B->cs->i[idx]) * B->cs->x[idx]; + idx++; + } + } + Bp++; + } + + return 0; +} + +int igraph_i_sparsemat_view(igraph_sparsemat_t *A, int nzmax, int m, int n, + int *p, int *i, double *x, int nz) { + + A->cs = cs_calloc(1, sizeof(cs_di)); + A->cs->nzmax = nzmax; + A->cs->m = m; + A->cs->n = n; + A->cs->p = p; + A->cs->i = i; + A->cs->x = x; + A->cs->nz = nz; + + return 0; +} + +int igraph_sparsemat_sort(const igraph_sparsemat_t *A, + igraph_sparsemat_t *sorted) { + + igraph_sparsemat_t tmp; + + IGRAPH_CHECK(igraph_sparsemat_transpose(A, &tmp, /*values=*/ 1)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmp); + IGRAPH_CHECK(igraph_sparsemat_transpose(&tmp, sorted, /*values=*/ 1)); + igraph_sparsemat_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +int igraph_sparsemat_getelements_sorted(const igraph_sparsemat_t *A, + igraph_vector_int_t *i, + igraph_vector_int_t *j, + igraph_vector_t *x) { + if (A->cs->nz < 0) { + igraph_sparsemat_t tmp; + IGRAPH_CHECK(igraph_sparsemat_sort(A, &tmp)); + IGRAPH_FINALLY(igraph_sparsemat_destroy, &tmp); + IGRAPH_CHECK(igraph_sparsemat_getelements(&tmp, i, j, x)); + igraph_sparsemat_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + } else { + IGRAPH_CHECK(igraph_sparsemat_getelements(A, i, j, x)); + } + + return 0; +} + +int igraph_sparsemat_nzmax(const igraph_sparsemat_t *A) { + return A->cs->nzmax; +} + +int igraph_sparsemat_neg(igraph_sparsemat_t *A) { + int i, nz = A->cs->nz == -1 ? A->cs->p[A->cs->n] : A->cs->nz; + igraph_real_t *px = A->cs->x; + + for (i = 0; i < nz; i++, px++) { + *px = - (*px); + } + + return 0; +} + +int igraph_sparsemat_iterator_init(igraph_sparsemat_iterator_t *it, + igraph_sparsemat_t *sparsemat) { + + it->mat = sparsemat; + igraph_sparsemat_iterator_reset(it); + return 0; +} + +int igraph_sparsemat_iterator_reset(igraph_sparsemat_iterator_t *it) { + it->pos = 0; + if (!igraph_sparsemat_is_triplet(it->mat)) { + it->col = 0; + while (it->col < it->mat->cs->n && + it->mat->cs->p[it->col + 1] == it->pos) { + it->col ++; + } + } + return 0; +} + +igraph_bool_t +igraph_sparsemat_iterator_end(const igraph_sparsemat_iterator_t *it) { + int nz = it->mat->cs->nz == -1 ? it->mat->cs->p[it->mat->cs->n] : + it->mat->cs->nz; + return it->pos >= nz; +} + +int igraph_sparsemat_iterator_row(const igraph_sparsemat_iterator_t *it) { + return it->mat->cs->i[it->pos]; +} + +int igraph_sparsemat_iterator_col(const igraph_sparsemat_iterator_t *it) { + if (igraph_sparsemat_is_triplet(it->mat)) { + return it->mat->cs->p[it->pos]; + } else { + return it->col; + } +} + +igraph_real_t +igraph_sparsemat_iterator_get(const igraph_sparsemat_iterator_t *it) { + return it->mat->cs->x[it->pos]; +} + +int igraph_sparsemat_iterator_next(igraph_sparsemat_iterator_t *it) { + it->pos += 1; + while (it->col < it->mat->cs->n && + it->mat->cs->p[it->col + 1] == it->pos) { + it->col++; + } + return it->pos; +} + +int igraph_sparsemat_iterator_idx(const igraph_sparsemat_iterator_t *it) { + return it->pos; +} diff --git a/src/spectral_properties.c b/src/spectral_properties.c new file mode 100644 index 0000000..1b87833 --- /dev/null +++ b/src/spectral_properties.c @@ -0,0 +1,436 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_structural.h" +#include "igraph_interface.h" +#include "config.h" +#include + +static int igraph_i_weighted_laplacian(const igraph_t *graph, igraph_matrix_t *res, + igraph_sparsemat_t *sparseres, + igraph_bool_t normalized, + const igraph_vector_t *weights) { + + igraph_eit_t edgeit; + int no_of_nodes = (int) igraph_vcount(graph); + int no_of_edges = (int) igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + igraph_vector_t degree; + long int i; + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid edge weight vector length", IGRAPH_EINVAL); + } + + if (res) { + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, no_of_nodes)); + igraph_matrix_null(res); + } + if (sparseres) { + int nz = directed ? no_of_edges + no_of_nodes : + no_of_edges * 2 + no_of_nodes; + igraph_sparsemat_resize(sparseres, no_of_nodes, no_of_nodes, nz); + } + + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(0), &edgeit)); + IGRAPH_FINALLY(igraph_eit_destroy, &edgeit); + + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + + if (directed) { + + if (!normalized) { + + while (!IGRAPH_EIT_END(edgeit)) { + long int edge = IGRAPH_EIT_GET(edgeit); + long int from = IGRAPH_FROM(graph, edge); + long int to = IGRAPH_TO (graph, edge); + igraph_real_t weight = VECTOR(*weights)[edge]; + if (from != to) { + if (res) { + MATRIX(*res, from, to) -= weight; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, (int) from, (int)to, + -weight)); + } + VECTOR(degree)[from] += weight; + } + IGRAPH_EIT_NEXT(edgeit); + } + + /* And the diagonal */ + for (i = 0; i < no_of_nodes; i++) { + if (res) { + MATRIX(*res, i, i) = VECTOR(degree)[i]; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, (int) i, (int) i, + VECTOR(degree)[i])); + } + } + + } else { /* normalized */ + + while (!IGRAPH_EIT_END(edgeit)) { + long int edge = IGRAPH_EIT_GET(edgeit); + long int from = IGRAPH_FROM(graph, edge); + long int to = IGRAPH_TO (graph, edge); + igraph_real_t weight = VECTOR(*weights)[edge]; + if (from != to) { + VECTOR(degree)[from] += weight; + } + IGRAPH_EIT_NEXT(edgeit); + } + + for (i = 0; i < no_of_nodes; i++) { + int t = VECTOR(degree)[i] > 0 ? 1 : 0; + if (res) { + MATRIX(*res, i, i) = t; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, (int) i, (int) i, t)); + } + } + + IGRAPH_EIT_RESET(edgeit); + while (!IGRAPH_EIT_END(edgeit)) { + long int edge = IGRAPH_EIT_GET(edgeit); + long int from = IGRAPH_FROM(graph, edge); + long int to = IGRAPH_TO (graph, edge); + igraph_real_t weight = VECTOR(*weights)[edge]; + if (from != to) { + igraph_real_t t = weight / VECTOR(degree)[from]; + if (res) { + MATRIX(*res, from, to) -= t; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, (int) from, (int) to, + -t)); + } + } + IGRAPH_EIT_NEXT(edgeit); + } + + } + + } else { /* undirected */ + + if (!normalized) { + + while (!IGRAPH_EIT_END(edgeit)) { + long int edge = IGRAPH_EIT_GET(edgeit); + long int from = IGRAPH_FROM(graph, edge); + long int to = IGRAPH_TO (graph, edge); + igraph_real_t weight = VECTOR(*weights)[edge]; + if (from != to) { + if (res) { + MATRIX(*res, from, to) -= weight; + MATRIX(*res, to, from) -= weight; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, (int) from, (int) to, + -weight)); + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, (int) to, (int) from, + -weight)); + } + VECTOR(degree)[from] += weight; + VECTOR(degree)[to] += weight; + } + IGRAPH_EIT_NEXT(edgeit); + } + + /* And the diagonal */ + for (i = 0; i < no_of_nodes; i++) { + if (res) { + MATRIX(*res, i, i) = VECTOR(degree)[i]; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, (int) i, (int) i, + VECTOR(degree)[i])); + } + } + + } else { /* normalized */ + + while (!IGRAPH_EIT_END(edgeit)) { + long int edge = IGRAPH_EIT_GET(edgeit); + long int from = IGRAPH_FROM(graph, edge); + long int to = IGRAPH_TO (graph, edge); + igraph_real_t weight = VECTOR(*weights)[edge]; + if (from != to) { + VECTOR(degree)[from] += weight; + VECTOR(degree)[to] += weight; + } + IGRAPH_EIT_NEXT(edgeit); + } + + for (i = 0; i < no_of_nodes; i++) { + int t = VECTOR(degree)[i] > 0 ? 1 : 0; + if (res) { + MATRIX(*res, i, i) = t; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, (int) i, (int) i, t)); + } + VECTOR(degree)[i] = sqrt(VECTOR(degree)[i]); + } + + IGRAPH_EIT_RESET(edgeit); + while (!IGRAPH_EIT_END(edgeit)) { + long int edge = IGRAPH_EIT_GET(edgeit); + long int from = IGRAPH_FROM(graph, edge); + long int to = IGRAPH_TO (graph, edge); + igraph_real_t weight = VECTOR(*weights)[edge]; + if (from != to) { + double diff = weight / (VECTOR(degree)[from] * VECTOR(degree)[to]); + if (res) { + MATRIX(*res, from, to) -= diff; + MATRIX(*res, to, from) -= diff; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, (int) from, (int) to, + -diff)); + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, (int) to, (int) from, + -diff)); + } + } + IGRAPH_EIT_NEXT(edgeit); + } + + } + + } + + igraph_vector_destroy(°ree); + igraph_eit_destroy(&edgeit); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_laplacian + * \brief Returns the Laplacian matrix of a graph + * + * + * The graph Laplacian matrix is similar to an adjacency matrix but + * contains -1's instead of 1's and the vertex degrees are included in + * the diagonal. So the result for edge i--j is -1 if i!=j and is equal + * to the degree of vertex i if i==j. igraph_laplacian will work on a + * directed graph; in this case, the diagonal will contain the out-degrees. + * Loop edges will be ignored. + * + * + * The normalized version of the Laplacian matrix has 1 in the diagonal and + * -1/sqrt(d[i]d[j]) if there is an edge from i to j. + * + * + * The first version of this function was written by Vincent Matossian. + * \param graph Pointer to the graph to convert. + * \param res Pointer to an initialized matrix object, the result is + * stored here. It will be resized if needed. + * If it is a null pointer, then it is ignored. + * At least one of \p res and \p sparseres must be a non-null pointer. + * \param sparseres Pointer to an initialized sparse matrix object, the + * result is stored here, if it is not a null pointer. + * At least one of \p res and \p sparseres must be a non-null pointer. + * \param normalized Whether to create a normalized Laplacian matrix. + * \param weights An optional vector containing edge weights, to calculate + * the weighted Laplacian matrix. Set it to a null pointer to + * calculate the unweighted Laplacian. + * \return Error code. + * + * Time complexity: O(|V||V|), + * |V| is the + * number of vertices in the graph. + * + * \example examples/simple/igraph_laplacian.c + */ + +int igraph_laplacian(const igraph_t *graph, igraph_matrix_t *res, + igraph_sparsemat_t *sparseres, + igraph_bool_t normalized, + const igraph_vector_t *weights) { + + igraph_eit_t edgeit; + int no_of_nodes = (int) igraph_vcount(graph); + int no_of_edges = (int) igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + int from, to; + igraph_integer_t ffrom, fto; + igraph_vector_t degree; + int i; + + if (!res && !sparseres) { + IGRAPH_ERROR("Laplacian: give at least one of `res' or `sparseres'", + IGRAPH_EINVAL); + } + + if (weights) { + return igraph_i_weighted_laplacian(graph, res, sparseres, normalized, + weights); + } + + if (res) { + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, no_of_nodes)); + igraph_matrix_null(res); + } + if (sparseres) { + int nz = directed ? no_of_edges + no_of_nodes : + no_of_edges * 2 + no_of_nodes; + IGRAPH_CHECK(igraph_sparsemat_resize(sparseres, no_of_nodes, + no_of_nodes, nz)); + } + IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(0), &edgeit)); + IGRAPH_FINALLY(igraph_eit_destroy, &edgeit); + + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_NO_LOOPS)); + + if (directed) { + if (!normalized) { + for (i = 0; i < no_of_nodes; i++) { + if (res) { + MATRIX(*res, i, i) = VECTOR(degree)[i]; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, i, i, + VECTOR(degree)[i])); + } + } + while (!IGRAPH_EIT_END(edgeit)) { + igraph_edge(graph, IGRAPH_EIT_GET(edgeit), &ffrom, &fto); + from = ffrom; + to = fto; + if (from != to) { + if (res) { + MATRIX(*res, from, to) -= 1; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, from, to, -1.0)); + } + } + IGRAPH_EIT_NEXT(edgeit); + } + } else { + for (i = 0; i < no_of_nodes; i++) { + int t = VECTOR(degree)[i] > 0 ? 1 : 0; + if (res) { + MATRIX(*res, i, i) = t; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, i, i, t)); + } + if (VECTOR(degree)[i] > 0) { + VECTOR(degree)[i] = 1.0 / VECTOR(degree)[i]; + } + } + + while (!IGRAPH_EIT_END(edgeit)) { + igraph_edge(graph, IGRAPH_EIT_GET(edgeit), &ffrom, &fto); + from = ffrom; to = fto; + if (from != to) { + if (res) { + MATRIX(*res, from, to) -= VECTOR(degree)[from]; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, from, to, + -VECTOR(degree)[from])); + } + } + IGRAPH_EIT_NEXT(edgeit); + } + } + + } else { + + if (!normalized) { + for (i = 0; i < no_of_nodes; i++) { + if (res) { + MATRIX(*res, i, i) = VECTOR(degree)[i]; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, i, i, + VECTOR(degree)[i])); + } + } + + while (!IGRAPH_EIT_END(edgeit)) { + igraph_edge(graph, IGRAPH_EIT_GET(edgeit), &ffrom, &fto); + from = ffrom; + to = fto; + + if (from != to) { + if (res) { + MATRIX(*res, to, from) -= 1; + MATRIX(*res, from, to) -= 1; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, to, from, -1.0)); + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, from, to, -1.0)); + } + } + + IGRAPH_EIT_NEXT(edgeit); + } + } else { + for (i = 0; i < no_of_nodes; i++) { + int t = VECTOR(degree)[i] > 0 ? 1 : 0; + if (res) { + MATRIX(*res, i, i) = t; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, i, i, t)); + } + VECTOR(degree)[i] = sqrt(VECTOR(degree)[i]); + } + + while (!IGRAPH_EIT_END(edgeit)) { + igraph_edge(graph, IGRAPH_EIT_GET(edgeit), &ffrom, &fto); + from = ffrom; to = fto; + if (from != to) { + double diff = 1.0 / (VECTOR(degree)[from] * VECTOR(degree)[to]); + if (res) { + MATRIX(*res, from, to) -= diff; + MATRIX(*res, to, from) -= diff; + } + if (sparseres) { + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, from, to, -diff)); + IGRAPH_CHECK(igraph_sparsemat_entry(sparseres, to, from, -diff)); + } + } + IGRAPH_EIT_NEXT(edgeit); + } + } + + } + + igraph_vector_destroy(°ree); + igraph_eit_destroy(&edgeit); + IGRAPH_FINALLY_CLEAN(2); + return 0; +} diff --git a/src/spmatrix.c b/src/spmatrix.c new file mode 100644 index 0000000..0e5af3c --- /dev/null +++ b/src/spmatrix.c @@ -0,0 +1,1050 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_spmatrix.h" +#include "igraph_error.h" +#include "config.h" + +#include +#include /* memcpy & co. */ + +/** + * \section igraph_spmatrix_constructor_and_destructor Sparse matrix constructors + * and destructors. + */ + +/** + * \ingroup matrix + * \function igraph_spmatrix_init + * \brief Initializes a sparse matrix. + * + * + * Every sparse matrix needs to be initialized before using it, this is done + * by calling this function. A matrix has to be destroyed if it is not + * needed any more, see \ref igraph_spmatrix_destroy(). + * \param m Pointer to a not yet initialized sparse matrix object to be + * initialized. + * \param nrow The number of rows in the matrix. + * \param ncol The number of columns in the matrix. + * \return Error code. + * + * Time complexity: operating system dependent. + */ + +int igraph_spmatrix_init(igraph_spmatrix_t *m, long int nrow, long int ncol) { + assert(m != NULL); + IGRAPH_VECTOR_INIT_FINALLY(&m->ridx, 0); + IGRAPH_VECTOR_INIT_FINALLY(&m->cidx, ncol + 1); + IGRAPH_VECTOR_INIT_FINALLY(&m->data, 0); + IGRAPH_FINALLY_CLEAN(3); + m->nrow = nrow; + m->ncol = ncol; + return 0; +} + +/** + * \ingroup matrix + * \function igraph_spmatrix_destroy + * \brief Destroys a sparse matrix object. + * + * + * This function frees all the memory allocated for a sparse matrix + * object. The destroyed object needs to be reinitialized before using + * it again. + * \param m The matrix to destroy. + * + * Time complexity: operating system dependent. + */ + +void igraph_spmatrix_destroy(igraph_spmatrix_t *m) { + assert(m != NULL); + igraph_vector_destroy(&m->ridx); + igraph_vector_destroy(&m->cidx); + igraph_vector_destroy(&m->data); +} + +/** + * \ingroup matrix + * \function igraph_spmatrix_copy + * \brief Copies a sparse matrix. + * + * + * Creates a sparse matrix object by copying another one. + * \param to Pointer to an uninitialized sparse matrix object. + * \param from The initialized sparse matrix object to copy. + * \return Error code, \c IGRAPH_ENOMEM if there + * isn't enough memory to allocate the new sparse matrix. + * + * Time complexity: O(n), the number + * of elements in the matrix. + */ + +int igraph_spmatrix_copy(igraph_spmatrix_t *to, const igraph_spmatrix_t *from) { + assert(from != NULL); + assert(to != NULL); + to->nrow = from->nrow; + to->ncol = from->ncol; + IGRAPH_CHECK(igraph_vector_copy(&to->ridx, &from->ridx)); + IGRAPH_CHECK(igraph_vector_copy(&to->cidx, &from->cidx)); + IGRAPH_CHECK(igraph_vector_copy(&to->data, &from->data)); + return 0; +} + +/** + * \section igraph_spmatrix_accessing_elements Accessing elements of a sparse matrix + */ + +/** + * \ingroup matrix + * \function igraph_spmatrix_e + * \brief Accessing an element of a sparse matrix. + * + * Note that there are no range checks right now. + * \param m The matrix object. + * \param row The index of the row, starting with zero. + * \param col The index of the column, starting with zero. + * + * Time complexity: O(log n), where n is the number of nonzero elements in + * the requested column. + */ +igraph_real_t igraph_spmatrix_e(const igraph_spmatrix_t *m, + long int row, long int col) { + long int start, end; + + assert(m != NULL); + start = (long) VECTOR(m->cidx)[col]; + end = (long) VECTOR(m->cidx)[col + 1] - 1; + + if (end < start) { + return 0; + } + /* Elements residing in column col are between m->data[start] and + * m->data[end], inclusive, ordered by row index */ + while (start < end - 1) { + long int mid = (start + end) / 2; + if (VECTOR(m->ridx)[mid] > row) { + end = mid; + } else if (VECTOR(m->ridx)[mid] < row) { + start = mid; + } else { + start = mid; + break; + } + } + + if (VECTOR(m->ridx)[start] == row) { + return VECTOR(m->data)[start]; + } + if (VECTOR(m->ridx)[start] != row && VECTOR(m->ridx)[end] == row) { + return VECTOR(m->data)[end]; + } + return 0; +} + + +/** + * \ingroup matrix + * \function igraph_spmatrix_set + * \brief Setting an element of a sparse matrix. + * + * Note that there are no range checks right now. + * \param m The matrix object. + * \param row The index of the row, starting with zero. + * \param col The index of the column, starting with zero. + * \param value The new value. + * + * Time complexity: O(log n), where n is the number of nonzero elements in + * the requested column. + */ +int igraph_spmatrix_set(igraph_spmatrix_t *m, long int row, long int col, + igraph_real_t value) { + long int start, end; + + assert(m != NULL); + start = (long) VECTOR(m->cidx)[col]; + end = (long) VECTOR(m->cidx)[col + 1] - 1; + + if (end < start) { + /* First element in the column */ + if (value == 0.0) { + return 0; + } + IGRAPH_CHECK(igraph_vector_insert(&m->ridx, start, row)); + IGRAPH_CHECK(igraph_vector_insert(&m->data, start, value)); + for (start = col + 1; start < m->ncol + 1; start++) { + VECTOR(m->cidx)[start]++; + } + return 0; + } + + /* Elements residing in column col are between m->data[start] and + * m->data[end], inclusive, ordered by row index */ + while (start < end - 1) { + long int mid = (start + end) / 2; + if (VECTOR(m->ridx)[mid] > row) { + end = mid; + } else if (VECTOR(m->ridx)[mid] < row) { + start = mid; + } else { + start = mid; + break; + } + } + + if (VECTOR(m->ridx)[start] == row) { + /* Overwriting a value - or deleting it if it has been overwritten by zero */ + if (value == 0) { + igraph_vector_remove(&m->ridx, start); + igraph_vector_remove(&m->data, start); + for (start = col + 1; start < m->ncol + 1; start++) { + VECTOR(m->cidx)[start]--; + } + } else { + VECTOR(m->data)[start] = value; + } + return 0; + } else if (VECTOR(m->ridx)[end] == row) { + /* Overwriting a value - or deleting it if it has been overwritten by zero */ + if (value == 0) { + igraph_vector_remove(&m->ridx, end); + igraph_vector_remove(&m->data, end); + for (start = col + 1; start < m->ncol + 1; start++) { + VECTOR(m->cidx)[start]--; + } + } else { + VECTOR(m->data)[end] = value; + } + return 0; + } + + /* New element has to be inserted, but only if not a zero is + * being written into the matrix */ + if (value != 0.0) { + if (VECTOR(m->ridx)[end] < row) { + IGRAPH_CHECK(igraph_vector_insert(&m->ridx, end + 1, row)); + IGRAPH_CHECK(igraph_vector_insert(&m->data, end + 1, value)); + } else if (VECTOR(m->ridx)[start] < row) { + IGRAPH_CHECK(igraph_vector_insert(&m->ridx, start + 1, row)); + IGRAPH_CHECK(igraph_vector_insert(&m->data, start + 1, value)); + } else { + IGRAPH_CHECK(igraph_vector_insert(&m->ridx, start, row)); + IGRAPH_CHECK(igraph_vector_insert(&m->data, start, value)); + } + for (start = col + 1; start < m->ncol + 1; start++) { + VECTOR(m->cidx)[start]++; + } + } + return 0; +} + + +/** + * \ingroup matrix + * \function igraph_spmatrix_add_e + * \brief Adding a real value to an element of a sparse matrix. + * + * Note that there are no range checks right now. This is implemented to avoid + * double lookup of a given element in the matrix by using \ref igraph_spmatrix_e() + * and \ref igraph_spmatrix_set() consecutively. + * + * \param m The matrix object. + * \param row The index of the row, starting with zero. + * \param col The index of the column, starting with zero. + * \param value The value to add. + * + * Time complexity: O(log n), where n is the number of nonzero elements in + * the requested column. + */ +int igraph_spmatrix_add_e(igraph_spmatrix_t *m, long int row, long int col, + igraph_real_t value) { + long int start, end; + + assert(m != NULL); + start = (long) VECTOR(m->cidx)[col]; + end = (long) VECTOR(m->cidx)[col + 1] - 1; + + if (end < start) { + /* First element in the column */ + if (value == 0.0) { + return 0; + } + IGRAPH_CHECK(igraph_vector_insert(&m->ridx, start, row)); + IGRAPH_CHECK(igraph_vector_insert(&m->data, start, value)); + for (start = col + 1; start < m->ncol + 1; start++) { + VECTOR(m->cidx)[start]++; + } + return 0; + } + + /* Elements residing in column col are between m->data[start] and + * m->data[end], inclusive, ordered by row index */ + while (start < end - 1) { + long int mid = (start + end) / 2; + if (VECTOR(m->ridx)[mid] > row) { + end = mid; + } else if (VECTOR(m->ridx)[mid] < row) { + start = mid; + } else { + start = mid; + break; + } + } + + if (VECTOR(m->ridx)[start] == row) { + /* Overwriting a value */ + if (VECTOR(m->data)[start] == -1) { + igraph_vector_remove(&m->ridx, start); + igraph_vector_remove(&m->data, start); + for (start = col + 1; start < m->ncol + 1; start++) { + VECTOR(m->cidx)[start]--; + } + } else { + VECTOR(m->data)[start] += value; + } + return 0; + } else if (VECTOR(m->ridx)[end] == row) { + /* Overwriting a value */ + if (VECTOR(m->data)[end] == -1) { + igraph_vector_remove(&m->ridx, end); + igraph_vector_remove(&m->data, end); + for (start = col + 1; start < m->ncol + 1; start++) { + VECTOR(m->cidx)[start]--; + } + } else { + VECTOR(m->data)[end] += value; + } + return 0; + } + + /* New element has to be inserted, but only if not a zero is + * being added to a zero element of the matrix */ + if (value != 0.0) { + if (VECTOR(m->ridx)[end] < row) { + IGRAPH_CHECK(igraph_vector_insert(&m->ridx, end + 1, row)); + IGRAPH_CHECK(igraph_vector_insert(&m->data, end + 1, value)); + } else if (VECTOR(m->ridx)[start] < row) { + IGRAPH_CHECK(igraph_vector_insert(&m->ridx, start + 1, row)); + IGRAPH_CHECK(igraph_vector_insert(&m->data, start + 1, value)); + } else { + IGRAPH_CHECK(igraph_vector_insert(&m->ridx, start, row)); + IGRAPH_CHECK(igraph_vector_insert(&m->data, start, value)); + } + for (start = col + 1; start < m->ncol + 1; start++) { + VECTOR(m->cidx)[start]++; + } + } + return 0; +} + +/** + * \function igraph_spmatrix_add_col_values + * \brief Adds the values of a column to another column. + * + * \param to The index of the column to be added to + * \param from The index of the column to be added + * \return Error code. + */ +int igraph_spmatrix_add_col_values(igraph_spmatrix_t *m, long int to, long int from) { + long int i; + /* TODO: I think this implementation could be speeded up if I don't use + * igraph_spmatrix_add_e directly -- but maybe it's not worth the fuss */ + for (i = (long int) VECTOR(m->cidx)[from]; i < VECTOR(m->cidx)[from + 1]; i++) { + IGRAPH_CHECK(igraph_spmatrix_add_e(m, (long int) VECTOR(m->ridx)[i], + to, VECTOR(m->data)[i])); + } + + return 0; +} + + +/** + * \ingroup matrix + * \function igraph_spmatrix_resize + * \brief Resizes a sparse matrix. + * + * + * This function resizes a sparse matrix by adding more elements to it. + * The matrix retains its data even after resizing it, except for the data + * which lies outside the new boundaries (if the new size is smaller). + * \param m Pointer to an already initialized sparse matrix object. + * \param nrow The number of rows in the resized matrix. + * \param ncol The number of columns in the resized matrix. + * \return Error code. + * + * Time complexity: O(n). + * n is the number of elements in the old matrix. + */ + +int igraph_spmatrix_resize(igraph_spmatrix_t *m, long int nrow, long int ncol) { + long int i, j, ci, ei, mincol; + assert(m != NULL); + /* Iterating through the matrix data and deleting unnecessary data. */ + /* At the same time, we create the new indices as well */ + if (nrow < m->nrow) { + ei = j = 0; + mincol = (m->ncol < ncol) ? m->ncol : ncol; + for (ci = 0; ci < mincol; ci++) { + for (; ei < VECTOR(m->cidx)[ci + 1]; ei++) { + if (VECTOR(m->ridx)[ei] < nrow) { + VECTOR(m->ridx)[j] = VECTOR(m->ridx)[ei]; + VECTOR(m->data)[j] = VECTOR(m->data)[ei]; + j++; + } + } + VECTOR(m->cidx)[ci] = j; + } + /* Contract the row index and the data vector */ + IGRAPH_CHECK(igraph_vector_resize(&m->ridx, j)); + IGRAPH_CHECK(igraph_vector_resize(&m->cidx, j)); + } + /* Updating cidx */ + IGRAPH_CHECK(igraph_vector_resize(&m->cidx, ncol + 1)); + for (i = m->ncol + 1; i < ncol + 1; i++) { + VECTOR(m->cidx)[i] = VECTOR(m->cidx)[m->ncol]; + } + m->nrow = nrow; + m->ncol = ncol; + return 0; +} + +/** + * \ingroup matrix + * \function igraph_spmatrix_count_nonzero + * \brief The number of non-zero elements in a sparse matrix. + * + * \param m Pointer to an initialized sparse matrix object. + * \return The size of the matrix. + * + * Time complexity: O(1). + */ + +long int igraph_spmatrix_count_nonzero(const igraph_spmatrix_t *m) { + assert(m != NULL); + return igraph_vector_size(&m->data); +} + + +/** + * \ingroup matrix + * \function igraph_spmatrix_size + * \brief The number of elements in a sparse matrix. + * + * \param m Pointer to an initialized sparse matrix object. + * \return The size of the matrix. + * + * Time complexity: O(1). + */ + +long int igraph_spmatrix_size(const igraph_spmatrix_t *m) { + assert(m != NULL); + return (m->nrow) * (m->ncol); +} + +/** + * \ingroup matrix + * \function igraph_spmatrix_nrow + * \brief The number of rows in a sparse matrix. + * + * \param m Pointer to an initialized sparse matrix object. + * \return The number of rows in the matrix. + * + * Time complexity: O(1). + */ + +long int igraph_spmatrix_nrow(const igraph_spmatrix_t *m) { + assert(m != NULL); + return m->nrow; +} + +/** + * \ingroup matrix + * \function igraph_spmatrix_ncol + * \brief The number of columns in a sparse matrix. + * + * \param m Pointer to an initialized sparse matrix object. + * \return The number of columns in the sparse matrix. + * + * Time complexity: O(1). + */ + +long int igraph_spmatrix_ncol(const igraph_spmatrix_t *m) { + assert(m != NULL); + return m->ncol; +} + +/** + * \ingroup matrix + * \brief Copies a sparse matrix to a regular C array. + * + * + * The matrix is copied columnwise, as this is the format most + * programs and languages use. + * The C array should be of sufficient size, there are (of course) no + * range checks done. + * \param m Pointer to an initialized sparse matrix object. + * \param to Pointer to a C array, the place to copy the data to. + * \return Error code. + * + * Time complexity: O(n), + * n is the number of + * elements in the matrix. + */ + +int igraph_spmatrix_copy_to(const igraph_spmatrix_t *m, igraph_real_t *to) { + long int c, dest_idx, idx; + + memset(to, 0, sizeof(igraph_real_t) * (size_t) igraph_spmatrix_size(m)); + for (c = 0, dest_idx = 0; c < m->ncol; c++, dest_idx += m->nrow) { + for (idx = (long int) VECTOR(m->cidx)[c]; idx < VECTOR(m->cidx)[c + 1]; idx++) { + to[dest_idx + (long)VECTOR(m->ridx)[idx]] = VECTOR(m->data)[idx]; + } + } + return 0; +} + +/** + * \ingroup matrix + * \brief Sets all element in a sparse matrix to zero. + * + * \param m Pointer to an initialized matrix object. + * \return Error code, always returns with success. + * + * Time complexity: O(n), + * n is the number of columns in the matrix + */ + +int igraph_spmatrix_null(igraph_spmatrix_t *m) { + assert(m != NULL); + igraph_vector_clear(&m->data); + igraph_vector_clear(&m->ridx); + igraph_vector_null(&m->cidx); + return 0; +} + +/** + * \ingroup matrix + * \function igraph_spmatrix_add_cols + * \brief Adds columns to a sparse matrix. + * \param m The sparse matrix object. + * \param n The number of columns to add. + * \return Error code. + * + * Time complexity: O(1). + */ + +int igraph_spmatrix_add_cols(igraph_spmatrix_t *m, long int n) { + igraph_spmatrix_resize(m, m->nrow, m->ncol + n); + return 0; +} + +/** + * \ingroup matrix + * \function igraph_spmatrix_add_rows + * \brief Adds rows to a sparse matrix. + * \param m The sparse matrix object. + * \param n The number of rows to add. + * \return Error code. + * + * Time complexity: O(1). + */ + +int igraph_spmatrix_add_rows(igraph_spmatrix_t *m, long int n) { + igraph_spmatrix_resize(m, m->nrow + n, m->ncol); + return 0; +} + +/** + * \function igraph_spmatrix_clear_row + * \brief Clears a row in the matrix (sets all of its elements to zero) + * \param m The matrix. + * \param row The index of the row to be cleared. + * + * Time complexity: O(n), the number of nonzero elements in the matrix. + */ + +int igraph_spmatrix_clear_row(igraph_spmatrix_t *m, long int row) { + long int ci, ei, i, j, nremove = 0, nremove_old = 0; + igraph_vector_t permvec; + + assert(m != NULL); + IGRAPH_VECTOR_INIT_FINALLY(&permvec, igraph_vector_size(&m->data)); + for (ci = 0, i = 0, j = 1; ci < m->ncol; ci++) { + for (ei = (long int) VECTOR(m->cidx)[ci]; ei < VECTOR(m->cidx)[ci + 1]; ei++) { + if (VECTOR(m->ridx)[ei] == row) { + /* this element will be deleted, so all elements in cidx from the + * column index of this element will have to be decreased by one */ + nremove++; + } else { + /* this element will be kept */ + VECTOR(permvec)[i] = j; + j++; + } + i++; + } + if (ci > 0) { + VECTOR(m->cidx)[ci] -= nremove_old; + } + nremove_old = nremove; + } + VECTOR(m->cidx)[m->ncol] -= nremove; + igraph_vector_permdelete(&m->ridx, &permvec, nremove); + igraph_vector_permdelete(&m->data, &permvec, nremove); + igraph_vector_destroy(&permvec); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +int igraph_i_spmatrix_clear_row_fast(igraph_spmatrix_t *m, long int row) { + long int ei, n; + + assert(m != NULL); + n = igraph_vector_size(&m->data); + for (ei = 0; ei < n; ei++) { + if (VECTOR(m->ridx)[ei] == row) { + VECTOR(m->data)[ei] = 0.0; + } + } + return 0; +} + +int igraph_i_spmatrix_cleanup(igraph_spmatrix_t *m) { + long int ci, ei, i, j, nremove = 0, nremove_old = 0; + igraph_vector_t permvec; + + assert(m != NULL); + IGRAPH_VECTOR_INIT_FINALLY(&permvec, igraph_vector_size(&m->data)); + for (ci = 0, i = 0, j = 1; ci < m->ncol; ci++) { + for (ei = (long int) VECTOR(m->cidx)[ci]; ei < VECTOR(m->cidx)[ci + 1]; ei++) { + if (VECTOR(m->data)[ei] == 0.0) { + /* this element will be deleted, so all elements in cidx from the + * column index of this element will have to be decreased by one */ + nremove++; + } else { + /* this element will be kept */ + VECTOR(permvec)[i] = j; + j++; + } + i++; + } + if (ci > 0) { + VECTOR(m->cidx)[ci] -= nremove_old; + } + nremove_old = nremove; + } + VECTOR(m->cidx)[m->ncol] -= nremove; + igraph_vector_permdelete(&m->ridx, &permvec, nremove); + igraph_vector_permdelete(&m->data, &permvec, nremove); + igraph_vector_destroy(&permvec); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_spmatrix_clear_col + * \brief Clears a column in the matrix (sets all of its elements to zero) + * \param m The matrix. + * \param col The index of the column to be cleared. + * \return Error code. The current implementation always succeeds. + * + * Time complexity: TODO + */ + +int igraph_spmatrix_clear_col(igraph_spmatrix_t *m, long int col) { + long int i, n; + assert(m != NULL); + n = (long)VECTOR(m->cidx)[col + 1] - (long)VECTOR(m->cidx)[col]; + if (n == 0) { + return 0; + } + igraph_vector_remove_section(&m->ridx, (long int) VECTOR(m->cidx)[col], + (long int) VECTOR(m->cidx)[col + 1]); + igraph_vector_remove_section(&m->data, (long int) VECTOR(m->cidx)[col], + (long int) VECTOR(m->cidx)[col + 1]); + for (i = col + 1; i <= m->ncol; i++) { + VECTOR(m->cidx)[i] -= n; + } + return 0; +} + +/** + * \function igraph_spmatrix_scale + * \brief Multiplies each element of the sparse matrix by a constant. + * \param m The matrix. + * \param by The constant. + * + * Time complexity: O(n), the number of elements in the matrix. + */ + +void igraph_spmatrix_scale(igraph_spmatrix_t *m, igraph_real_t by) { + assert(m != NULL); + igraph_vector_scale(&m->data, by); +} + +/** + * \function igraph_spmatrix_colsums + * \brief Calculates the column sums of the matrix. + * \param m The matrix. + * \param res An initialized \c igraph_vector_t, the result will be stored here. + * The vector will be resized as needed. + * + * Time complexity: O(n), the number of nonzero elements in the matrix. + */ + +int igraph_spmatrix_colsums(const igraph_spmatrix_t *m, igraph_vector_t *res) { + long int i, c; + assert(m != NULL); + IGRAPH_CHECK(igraph_vector_resize(res, m->ncol)); + igraph_vector_null(res); + for (c = 0; c < m->ncol; c++) { + for (i = (long int) VECTOR(m->cidx)[c]; i < VECTOR(m->cidx)[c + 1]; i++) { + VECTOR(*res)[c] += VECTOR(m->data)[i]; + } + } + return 0; +} + +/** + * \function igraph_spmatrix_rowsums + * \brief Calculates the row sums of the matrix. + * \param m The matrix. + * \param res An initialized \c igraph_vector_t, the result will be stored here. + * The vector will be resized as needed. + * + * Time complexity: O(n), the number of nonzero elements in the matrix. + */ + +int igraph_spmatrix_rowsums(const igraph_spmatrix_t *m, igraph_vector_t *res) { + long int i, n; + assert(m != NULL); + + IGRAPH_CHECK(igraph_vector_resize(res, m->nrow)); + n = igraph_vector_size(&m->data); + igraph_vector_null(res); + for (i = 0; i < n; i++) { + VECTOR(*res)[(long int)VECTOR(m->ridx)[i]] += VECTOR(m->data)[i]; + } + return 0; +} + +/** + * \function igraph_spmatrix_max_nonzero + * \brief Returns the maximum nonzero element of a matrix. + * If the matrix is empty, zero is returned. + * + * \param m the matrix object. + * \param ridx the row index of the maximum element if not \c NULL. + * \param cidx the column index of the maximum element if not \c NULL. + * + * Time complexity: O(n), the number of nonzero elements in the matrix. + */ +igraph_real_t igraph_spmatrix_max_nonzero(const igraph_spmatrix_t *m, + igraph_real_t *ridx, igraph_real_t *cidx) { + igraph_real_t res; + long int i, n, maxidx; + + assert(m != NULL); + n = igraph_vector_size(&m->data); + if (n == 0) { + return 0.0; + } + + maxidx = -1; + for (i = 0; i < n; i++) + if (VECTOR(m->data)[i] != 0.0 && + (maxidx == -1 || VECTOR(m->data)[i] >= VECTOR(m->data)[maxidx])) { + maxidx = i; + } + + if (maxidx == -1) { + return 0.0; + } + + res = VECTOR(m->data)[maxidx]; + if (ridx != 0) { + *ridx = VECTOR(m->ridx)[maxidx]; + } + if (cidx != 0) { + igraph_vector_binsearch(&m->cidx, maxidx, &i); + while (VECTOR(m->cidx)[i + 1] == VECTOR(m->cidx)[i]) { + i++; + } + *cidx = (igraph_real_t)i; + } + return res; +} + +/** + * \function igraph_spmatrix_max + * \brief Returns the maximum element of a matrix. + * If the matrix is empty, zero is returned. + * + * \param m the matrix object. + * \param ridx the row index of the maximum element if not \c NULL. + * \param cidx the column index of the maximum element if not \c NULL. + * + * Time complexity: O(n), the number of nonzero elements in the matrix. + */ +igraph_real_t igraph_spmatrix_max(const igraph_spmatrix_t *m, + igraph_real_t *ridx, igraph_real_t *cidx) { + igraph_real_t res; + long int i, j, k, maxidx; + + assert(m != NULL); + i = igraph_vector_size(&m->data); + if (i == 0) { + return 0.0; + } + + maxidx = (long)igraph_vector_which_max(&m->data); + res = VECTOR(m->data)[maxidx]; + if (res >= 0.0 || i == m->nrow * m->ncol) { + if (ridx != 0) { + *ridx = VECTOR(m->ridx)[maxidx]; + } + if (cidx != 0) { + igraph_vector_binsearch(&m->cidx, maxidx, &i); + i--; + while (i < m->ncol - 1 && VECTOR(m->cidx)[i + 1] == VECTOR(m->cidx)[i]) { + i++; + } + *cidx = (igraph_real_t)i; + } + return res; + } + /* the maximal nonzero element is negative and there is at least a + * single zero + */ + res = 0.0; + if (cidx != 0 || ridx != 0) { + for (i = 0; i < m->ncol; i++) { + if (VECTOR(m->cidx)[i + 1] - VECTOR(m->cidx)[i] < m->nrow) { + if (cidx != 0) { + *cidx = i; + } + if (ridx != 0) { + for (j = (long int) VECTOR(m->cidx)[i], k = 0; + j < VECTOR(m->cidx)[i + 1]; j++, k++) { + if (VECTOR(m->ridx)[j] != k) { + *ridx = k; + break; + } + } + } + break; + } + } + } + + return res; +} + +int igraph_i_spmatrix_get_col_nonzero_indices(const igraph_spmatrix_t *m, + igraph_vector_t *res, long int col) { + long int i, n; + assert(m != NULL); + n = (long int) (VECTOR(m->cidx)[col + 1] - VECTOR(m->cidx)[col]); + IGRAPH_CHECK(igraph_vector_resize(res, n)); + for (i = (long int) VECTOR(m->cidx)[col], n = 0; + i < VECTOR(m->cidx)[col + 1]; i++, n++) + if (VECTOR(m->data)[i] != 0.0) { + VECTOR(*res)[n] = VECTOR(m->ridx)[i]; + } + return 0; +} + + +/** + * \section igraph_spmatrix_iterating Iterating over the non-zero elements of a sparse matrix + * + * The \type igraph_spmatrix_iter_t type represents an iterator that can + * be used to step over the non-zero elements of a sparse matrix in columnwise + * order efficiently. In general, you shouldn't modify the elements of the matrix + * while iterating over it; doing so will probably invalidate the iterator, but + * there are no checks to prevent you from doing this. + * + * To access the row index of the current element of the iterator, use its + * \c ri field. Similarly, the \c ci field stores the column index of the current + * element and the \c value field stores the value of the element. + */ + +/** + * \function igraph_spmatrix_iter_create + * \brief Creates a sparse matrix iterator corresponding to the given matrix. + * + * \param mit pointer to the matrix iterator being initialized + * \param m pointer to the matrix we will be iterating over + * \return Error code. The current implementation is always successful. + * + * Time complexity: O(1). + */ +int igraph_spmatrix_iter_create(igraph_spmatrix_iter_t *mit, const igraph_spmatrix_t *m) { + mit->m = m; + IGRAPH_CHECK(igraph_spmatrix_iter_reset(mit)); + return 0; +} + +/** + * \function igraph_spmatrix_iter_reset + * \brief Resets a sparse matrix iterator. + * + * + * After resetting, the iterator will point to the first nonzero element (if any). + * + * \param mit pointer to the matrix iterator being reset + * \return Error code. The current implementation is always successful. + * + * Time complexity: O(1). + */ +int igraph_spmatrix_iter_reset(igraph_spmatrix_iter_t *mit) { + assert(mit->m); + + if (igraph_spmatrix_count_nonzero(mit->m) == 0) { + mit->pos = mit->ri = mit->ci = -1L; + mit->value = -1; + return 0; + } + + mit->ci = 0; + mit->pos = -1; + + IGRAPH_CHECK(igraph_spmatrix_iter_next(mit)); + + return 0; +} + +/** + * \function igraph_spmatrix_iter_next + * \brief Moves a sparse matrix iterator to the next nonzero element. + * + * + * You should call this function only if \ref igraph_spmatrix_iter_end() + * returns FALSE (0). + * + * \param mit pointer to the matrix iterator being moved + * \return Error code. The current implementation is always successful. + * + * Time complexity: O(1). + */ +int igraph_spmatrix_iter_next(igraph_spmatrix_iter_t *mit) { + mit->pos++; + + if (igraph_spmatrix_iter_end(mit)) { + return 0; + } + + mit->ri = (long int)VECTOR(mit->m->ridx)[mit->pos]; + mit->value = VECTOR(mit->m->data)[mit->pos]; + + while (VECTOR(mit->m->cidx)[mit->ci + 1] <= mit->pos) { + mit->ci++; + } + + return 0; +} + +/** + * \function igraph_spmatrix_iter_end + * \brief Checks whether there are more elements in the iterator. + * + * + * You should call this function before calling \ref igraph_spmatrix_iter_next() + * to make sure you have more elements in the iterator. + * + * \param mit pointer to the matrix iterator being checked + * \return TRUE (1) if there are more elements in the iterator, + * FALSE (0) otherwise. + * + * Time complexity: O(1). + */ +igraph_bool_t igraph_spmatrix_iter_end(igraph_spmatrix_iter_t *mit) { + return mit->pos >= igraph_spmatrix_count_nonzero(mit->m); +} + +/** + * \function igraph_spmatrix_iter_destroy + * \brief Frees the memory used by the iterator. + * + * + * The current implementation does not allocate any memory upon + * creation, so this function does nothing. However, since there is + * no guarantee that future implementations will not allocate any + * memory in \ref igraph_spmatrix_iter_create(), you are still + * required to call this function whenever you are done with the + * iterator. + * + * \param mit pointer to the matrix iterator being destroyed + * + * Time complexity: O(1). + */ +void igraph_spmatrix_iter_destroy(igraph_spmatrix_iter_t *mit) { + IGRAPH_UNUSED(mit); + /* Nothing to do at the moment */ +} + +#ifndef USING_R +/** + * \function igraph_spmatrix_print + * \brief Prints a sparse matrix. + * + * Prints a sparse matrix to the standard output. Only the non-zero entries + * are printed. + * + * \return Error code. + * + * Time complexity: O(n), the number of non-zero elements. + */ +int igraph_spmatrix_print(const igraph_spmatrix_t* matrix) { + return igraph_spmatrix_fprint(matrix, stdout); +} +#endif + +/** + * \function igraph_spmatrix_fprint + * \brief Prints a sparse matrix to the given file. + * + * Prints a sparse matrix to the given file. Only the non-zero entries + * are printed. + * + * \return Error code. + * + * Time complexity: O(n), the number of non-zero elements. + */ +int igraph_spmatrix_fprint(const igraph_spmatrix_t* matrix, FILE *file) { + igraph_spmatrix_iter_t mit; + + IGRAPH_CHECK(igraph_spmatrix_iter_create(&mit, matrix)); + IGRAPH_FINALLY(igraph_spmatrix_iter_destroy, &mit); + while (!igraph_spmatrix_iter_end(&mit)) { + fprintf(file, "[%ld, %ld] = %.4f\n", (long int)mit.ri, + (long int)mit.ci, mit.value); + igraph_spmatrix_iter_next(&mit); + } + igraph_spmatrix_iter_destroy(&mit); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + + diff --git a/src/st-cuts.c b/src/st-cuts.c new file mode 100644 index 0000000..970f1c9 --- /dev/null +++ b/src/st-cuts.c @@ -0,0 +1,1550 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_flow.h" +#include "igraph_flow_internal.h" +#include "igraph_error.h" +#include "igraph_memory.h" +#include "igraph_constants.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include "igraph_constructors.h" +#include "igraph_structural.h" +#include "igraph_components.h" +#include "igraph_math.h" +#include "igraph_dqueue.h" +#include "igraph_visitor.h" +#include "igraph_marked_queue.h" +#include "igraph_stack.h" +#include "igraph_estack.h" +#include "config.h" + +/* + * \function igraph_even_tarjan_reduction + * Even-Tarjan reduction of a graph + * + * \example examples/simple/even_tarjan.c + */ + +int igraph_even_tarjan_reduction(const igraph_t *graph, igraph_t *graphbar, + igraph_vector_t *capacity) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + + long int new_no_of_nodes = no_of_nodes * 2; + long int new_no_of_edges = no_of_nodes + no_of_edges * 2; + + igraph_vector_t edges; + long int edgeptr = 0, capptr = 0; + long int i; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, new_no_of_edges * 2); + + if (capacity) { + IGRAPH_CHECK(igraph_vector_resize(capacity, new_no_of_edges)); + } + + /* Every vertex 'i' is replaced by two vertices, i' and i'' */ + /* id[i'] := id[i] ; id[i''] := id[i] + no_of_nodes */ + + /* One edge for each original vertex, for i, we add (i',i'') */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = i + no_of_nodes; + if (capacity) { + VECTOR(*capacity)[capptr++] = 1.0; + } + } + + /* Two news edges for each original edge + (from,to) becomes (from'',to'), (to'',from') */ + for (i = 0; i < no_of_edges; i++) { + long int from = IGRAPH_FROM(graph, i); + long int to = IGRAPH_TO(graph, i); + VECTOR(edges)[edgeptr++] = from + no_of_nodes; + VECTOR(edges)[edgeptr++] = to; + VECTOR(edges)[edgeptr++] = to + no_of_nodes; + VECTOR(edges)[edgeptr++] = from; + if (capacity) { + VECTOR(*capacity)[capptr++] = no_of_nodes; /* TODO: should be Inf */ + VECTOR(*capacity)[capptr++] = no_of_nodes; /* TODO: should be Inf */ + } + } + + IGRAPH_CHECK(igraph_create(graphbar, &edges, (igraph_integer_t) + new_no_of_nodes, IGRAPH_DIRECTED)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +int igraph_i_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + igraph_vector_t *residual_capacity, + const igraph_vector_t *flow, + igraph_vector_t *tmp) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + long int i, no_new_edges = 0; + long int edgeptr = 0, capptr = 0; + + for (i = 0; i < no_of_edges; i++) { + if (VECTOR(*flow)[i] < VECTOR(*capacity)[i]) { + no_new_edges++; + } + } + + IGRAPH_CHECK(igraph_vector_resize(tmp, no_new_edges * 2)); + if (residual_capacity) { + IGRAPH_CHECK(igraph_vector_resize(residual_capacity, no_new_edges)); + } + + for (i = 0; i < no_of_edges; i++) { + if (VECTOR(*capacity)[i] - VECTOR(*flow)[i] > 0) { + long int from = IGRAPH_FROM(graph, i); + long int to = IGRAPH_TO(graph, i); + igraph_real_t c = VECTOR(*capacity)[i]; + VECTOR(*tmp)[edgeptr++] = from; + VECTOR(*tmp)[edgeptr++] = to; + if (residual_capacity) { + VECTOR(*residual_capacity)[capptr++] = c; + } + } + } + + IGRAPH_CHECK(igraph_create(residual, tmp, (igraph_integer_t) no_of_nodes, + IGRAPH_DIRECTED)); + + return 0; +} + +int igraph_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + igraph_vector_t *residual_capacity, + const igraph_vector_t *flow) { + + igraph_vector_t tmp; + long int no_of_edges = igraph_ecount(graph); + + if (igraph_vector_size(capacity) != no_of_edges) { + IGRAPH_ERROR("Invalid `capacity' vector size", IGRAPH_EINVAL); + } + if (igraph_vector_size(flow) != no_of_edges) { + IGRAPH_ERROR("Invalid `flow' vector size", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&tmp, 0); + + IGRAPH_CHECK(igraph_i_residual_graph(graph, capacity, residual, + residual_capacity, flow, &tmp)); + + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +int igraph_i_reverse_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + const igraph_vector_t *flow, + igraph_vector_t *tmp) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + long int i, no_new_edges = 0; + long int edgeptr = 0; + + for (i = 0; i < no_of_edges; i++) { + igraph_real_t cap = capacity ? VECTOR(*capacity)[i] : 1.0; + if (VECTOR(*flow)[i] > 0) { + no_new_edges++; + } + if (VECTOR(*flow)[i] < cap) { + no_new_edges++; + } + } + + IGRAPH_CHECK(igraph_vector_resize(tmp, no_new_edges * 2)); + + for (i = 0; i < no_of_edges; i++) { + long int from = IGRAPH_FROM(graph, i); + long int to = IGRAPH_TO(graph, i); + igraph_real_t cap = capacity ? VECTOR(*capacity)[i] : 1.0; + if (VECTOR(*flow)[i] > 0) { + VECTOR(*tmp)[edgeptr++] = from; + VECTOR(*tmp)[edgeptr++] = to; + } + if (VECTOR(*flow)[i] < cap) { + VECTOR(*tmp)[edgeptr++] = to; + VECTOR(*tmp)[edgeptr++] = from; + } + } + + IGRAPH_CHECK(igraph_create(residual, tmp, (igraph_integer_t) no_of_nodes, + IGRAPH_DIRECTED)); + + return 0; +} + +int igraph_reverse_residual_graph(const igraph_t *graph, + const igraph_vector_t *capacity, + igraph_t *residual, + const igraph_vector_t *flow) { + igraph_vector_t tmp; + long int no_of_edges = igraph_ecount(graph); + + if (capacity && igraph_vector_size(capacity) != no_of_edges) { + IGRAPH_ERROR("Invalid `capacity' vector size", IGRAPH_EINVAL); + } + if (igraph_vector_size(flow) != no_of_edges) { + IGRAPH_ERROR("Invalid `flow' vector size", IGRAPH_EINVAL); + } + IGRAPH_VECTOR_INIT_FINALLY(&tmp, 0); + + IGRAPH_CHECK(igraph_i_reverse_residual_graph(graph, capacity, residual, + flow, &tmp)); + + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +typedef struct igraph_i_dbucket_t { + igraph_vector_long_t head; + igraph_vector_long_t next; +} igraph_i_dbucket_t; + +static int igraph_i_dbucket_init(igraph_i_dbucket_t *buck, long int size) { + IGRAPH_CHECK(igraph_vector_long_init(&buck->head, size)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &buck->head); + IGRAPH_CHECK(igraph_vector_long_init(&buck->next, size)); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +static void igraph_i_dbucket_destroy(igraph_i_dbucket_t *buck) { + igraph_vector_long_destroy(&buck->head); + igraph_vector_long_destroy(&buck->next); +} + +static int igraph_i_dbucket_insert(igraph_i_dbucket_t *buck, long int bid, + long int elem) { + /* Note: we can do this, since elem is not in any buckets */ + VECTOR(buck->next)[elem] = VECTOR(buck->head)[bid]; + VECTOR(buck->head)[bid] = elem + 1; + return 0; +} + +static long int igraph_i_dbucket_empty(const igraph_i_dbucket_t *buck, + long int bid) { + return VECTOR(buck->head)[bid] == 0; +} + +static long int igraph_i_dbucket_delete(igraph_i_dbucket_t *buck, long int bid) { + long int elem = VECTOR(buck->head)[bid] - 1; + VECTOR(buck->head)[bid] = VECTOR(buck->next)[elem]; + return elem; +} + +static int igraph_i_dominator_LINK(long int v, long int w, + igraph_vector_long_t *ancestor) { + VECTOR(*ancestor)[w] = v + 1; + return 0; +} + +/* TODO: don't always reallocate path */ + +static int igraph_i_dominator_COMPRESS(long int v, + igraph_vector_long_t *ancestor, + igraph_vector_long_t *label, + igraph_vector_long_t *semi) { + igraph_stack_long_t path; + long int w = v; + long int top, pretop; + + IGRAPH_CHECK(igraph_stack_long_init(&path, 10)); + IGRAPH_FINALLY(igraph_stack_long_destroy, &path); + + while (VECTOR(*ancestor)[w] != 0) { + IGRAPH_CHECK(igraph_stack_long_push(&path, w)); + w = VECTOR(*ancestor)[w] - 1; + } + + top = igraph_stack_long_pop(&path); + while (!igraph_stack_long_empty(&path)) { + pretop = igraph_stack_long_pop(&path); + + if (VECTOR(*semi)[VECTOR(*label)[top]] < + VECTOR(*semi)[VECTOR(*label)[pretop]]) { + VECTOR(*label)[pretop] = VECTOR(*label)[top]; + } + VECTOR(*ancestor)[pretop] = VECTOR(*ancestor)[top]; + + top = pretop; + } + + igraph_stack_long_destroy(&path); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static long int igraph_i_dominator_EVAL(long int v, + igraph_vector_long_t *ancestor, + igraph_vector_long_t *label, + igraph_vector_long_t *semi) { + if (VECTOR(*ancestor)[v] == 0) { + return v; + } else { + igraph_i_dominator_COMPRESS(v, ancestor, label, semi); + return VECTOR(*label)[v]; + } +} + +/* TODO: implement the faster version. */ + +/** + * \function igraph_dominator_tree + * Calculates the dominator tree of a flowgraph + * + * A flowgraph is a directed graph with a distinguished start (or + * root) vertex r, such that for any vertex v, there is a path from r + * to v. A vertex v dominates another vertex w (not equal to v), if + * every path from r to w contains v. Vertex v is the immediate + * dominator or w, v=idom(w), if v dominates w and every other + * dominator of w dominates v. The edges {(idom(w), w)| w is not r} + * form a directed tree, rooted at r, called the dominator tree of the + * graph. Vertex v dominates vertex w if and only if v is an ancestor + * of w in the dominator tree. + * + * This function implements the Lengauer-Tarjan algorithm + * to construct the dominator tree of a directed graph. For details + * please see Thomas Lengauer, Robert Endre Tarjan: A fast algorithm + * for finding dominators in a flowgraph, ACM Transactions on + * Programming Languages and Systems (TOPLAS) I/1, 121--141, 1979. + * + * \param graph A directed graph. If it is not a flowgraph, and it + * contains some vertices not reachable from the root vertex, + * then these vertices will be collected in the \c leftout + * vector. + * \param root The id of the root (or source) vertex, this will be the + * root of the tree. + * \param dom Pointer to an initialized vector or a null pointer. If + * not a null pointer, then the immediate dominator of each + * vertex will be stored here. For vertices that are not + * reachable from the root, NaN is stored here. For + * the root vertex itself, -1 is added. + * \param domtree Pointer to an uninitialized igraph_t, or NULL. If + * not a null pointer, then the dominator tree is returned + * here. The graph contains the vertices that are unreachable + * from the root (if any), these will be isolates. + * \param leftout Pointer to an initialized vector object, or NULL. If + * not NULL, then the ids of the vertices that are unreachable + * from the root vertex (and thus not part of the dominator + * tree) are stored here. + * \param mode Constant, must be \c IGRAPH_IN or \c IGRAPH_OUT. If it + * is \c IGRAPH_IN, then all directions are considered as + * opposite to the original one in the input graph. + * \return Error code. + * + * Time complexity: very close to O(|E|+|V|), linear in the number of + * edges and vertices. More precisely, it is O(|V|+|E|alpha(|E|,|V|)), + * where alpha(|E|,|V|) is a functional inverse of Ackermann's + * function. + * + * \example examples/simple/dominator_tree.c + */ + +int igraph_dominator_tree(const igraph_t *graph, + igraph_integer_t root, + igraph_vector_t *dom, + igraph_t *domtree, + igraph_vector_t *leftout, + igraph_neimode_t mode) { + + long int no_of_nodes = igraph_vcount(graph); + + igraph_adjlist_t succ, pred; + igraph_vector_t parent; + igraph_vector_long_t semi; /* +1 always */ + igraph_vector_t vertex; /* +1 always */ + igraph_i_dbucket_t bucket; + igraph_vector_long_t ancestor; + igraph_vector_long_t label; + + igraph_neimode_t invmode = mode == IGRAPH_IN ? IGRAPH_OUT : IGRAPH_IN; + + long int i; + + igraph_vector_t vdom, *mydom = dom; + + long int component_size = 0; + + if (root < 0 || root >= no_of_nodes) { + IGRAPH_ERROR("Invalid root vertex id for dominator tree", + IGRAPH_EINVAL); + } + + if (!igraph_is_directed(graph)) { + IGRAPH_ERROR("Dominator tree of an undirected graph requested", + IGRAPH_EINVAL); + } + + if (mode == IGRAPH_ALL) { + IGRAPH_ERROR("Invalid neighbor mode for dominator tree", + IGRAPH_EINVAL); + } + + if (dom) { + IGRAPH_CHECK(igraph_vector_resize(dom, no_of_nodes)); + } else { + mydom = &vdom; + IGRAPH_VECTOR_INIT_FINALLY(mydom, no_of_nodes); + } + igraph_vector_fill(mydom, IGRAPH_NAN); + + IGRAPH_CHECK(igraph_vector_init(&parent, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_destroy, &parent); + IGRAPH_CHECK(igraph_vector_long_init(&semi, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &semi); + IGRAPH_CHECK(igraph_vector_init(&vertex, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_destroy, &vertex); + IGRAPH_CHECK(igraph_vector_long_init(&ancestor, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &ancestor); + IGRAPH_CHECK(igraph_vector_long_init_seq(&label, 0, no_of_nodes - 1)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &label); + IGRAPH_CHECK(igraph_adjlist_init(graph, &succ, mode)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &succ); + IGRAPH_CHECK(igraph_adjlist_init(graph, &pred, invmode)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &pred); + IGRAPH_CHECK(igraph_i_dbucket_init(&bucket, no_of_nodes)); + IGRAPH_FINALLY(igraph_i_dbucket_destroy, &bucket); + + /* DFS first, to set semi, vertex and parent, step 1 */ + + IGRAPH_CHECK(igraph_dfs(graph, root, mode, /*unreachable=*/ 0, + /*order=*/ &vertex, + /*order_out=*/ 0, /*father=*/ &parent, + /*dist=*/ 0, /*in_callback=*/ 0, + /*out_callback=*/ 0, /*extra=*/ 0)); + + for (i = 0; i < no_of_nodes; i++) { + if (IGRAPH_FINITE(VECTOR(vertex)[i])) { + long int t = (long int) VECTOR(vertex)[i]; + VECTOR(semi)[t] = component_size + 1; + VECTOR(vertex)[component_size] = t + 1; + component_size++; + } + } + if (leftout) { + long int n = no_of_nodes - component_size; + long int p = 0, j; + IGRAPH_CHECK(igraph_vector_resize(leftout, n)); + for (j = 0; j < no_of_nodes && p < n; j++) { + if (!IGRAPH_FINITE(VECTOR(parent)[j])) { + VECTOR(*leftout)[p++] = j; + } + } + } + + /* We need to go over 'pred' because it should contain only the + edges towards the target vertex. */ + for (i = 0; i < no_of_nodes; i++) { + igraph_vector_int_t *v = igraph_adjlist_get(&pred, i); + long int j, n = igraph_vector_int_size(v); + for (j = 0; j < n; ) { + long int v2 = (long int) VECTOR(*v)[j]; + if (IGRAPH_FINITE(VECTOR(parent)[v2])) { + j++; + } else { + VECTOR(*v)[j] = VECTOR(*v)[n - 1]; + igraph_vector_int_pop_back(v); + n--; + } + } + } + + /* Now comes the main algorithm, steps 2 & 3 */ + + for (i = component_size - 1; i > 0; i--) { + long int w = (long int) VECTOR(vertex)[i] - 1; + igraph_vector_int_t *predw = igraph_adjlist_get(&pred, w); + long int j, n = igraph_vector_int_size(predw); + for (j = 0; j < n; j++) { + long int v = (long int) VECTOR(*predw)[j]; + long int u = igraph_i_dominator_EVAL(v, &ancestor, &label, &semi); + if (VECTOR(semi)[u] < VECTOR(semi)[w]) { + VECTOR(semi)[w] = VECTOR(semi)[u]; + } + } + igraph_i_dbucket_insert(&bucket, (long int) + VECTOR(vertex)[ VECTOR(semi)[w] - 1 ] - 1, w); + igraph_i_dominator_LINK((long int) VECTOR(parent)[w], w, &ancestor); + while (!igraph_i_dbucket_empty(&bucket, (long int) VECTOR(parent)[w])) { + long int v = igraph_i_dbucket_delete(&bucket, (long int) VECTOR(parent)[w]); + long int u = igraph_i_dominator_EVAL(v, &ancestor, &label, &semi); + VECTOR(*mydom)[v] = VECTOR(semi)[u] < VECTOR(semi)[v] ? u : + VECTOR(parent)[w]; + } + } + + /* Finally, step 4 */ + + for (i = 1; i < component_size; i++) { + long int w = (long int) VECTOR(vertex)[i] - 1; + if (VECTOR(*mydom)[w] != VECTOR(vertex)[VECTOR(semi)[w] - 1] - 1) { + VECTOR(*mydom)[w] = VECTOR(*mydom)[(long int)VECTOR(*mydom)[w]]; + } + } + VECTOR(*mydom)[(long int)root] = -1; + + igraph_i_dbucket_destroy(&bucket); + igraph_adjlist_destroy(&pred); + igraph_adjlist_destroy(&succ); + igraph_vector_long_destroy(&label); + igraph_vector_long_destroy(&ancestor); + igraph_vector_destroy(&vertex); + igraph_vector_long_destroy(&semi); + igraph_vector_destroy(&parent); + IGRAPH_FINALLY_CLEAN(8); + + if (domtree) { + igraph_vector_t edges; + long int ptr = 0; + IGRAPH_VECTOR_INIT_FINALLY(&edges, component_size * 2 - 2); + for (i = 0; i < no_of_nodes; i++) { + if (i != root && IGRAPH_FINITE(VECTOR(*mydom)[i])) { + if (mode == IGRAPH_OUT) { + VECTOR(edges)[ptr++] = VECTOR(*mydom)[i]; + VECTOR(edges)[ptr++] = i; + } else { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = VECTOR(*mydom)[i]; + } + } + } + IGRAPH_CHECK(igraph_create(domtree, &edges, (igraph_integer_t) no_of_nodes, + IGRAPH_DIRECTED)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_I_ATTRIBUTE_DESTROY(domtree); + IGRAPH_I_ATTRIBUTE_COPY(domtree, graph, /*graph=*/ 1, /*vertex=*/ 1, + /*edge=*/ 0); + } + + if (!dom) { + igraph_vector_destroy(&vdom); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +typedef struct igraph_i_all_st_cuts_minimal_dfs_data_t { + igraph_stack_t *stack; + igraph_vector_bool_t *nomark; + const igraph_vector_bool_t *GammaX; + long int root; + const igraph_vector_t *map; +} igraph_i_all_st_cuts_minimal_dfs_data_t; + +static igraph_bool_t igraph_i_all_st_cuts_minimal_dfs_incb( + const igraph_t *graph, + igraph_integer_t vid, + igraph_integer_t dist, + void *extra) { + + igraph_i_all_st_cuts_minimal_dfs_data_t *data = extra; + igraph_stack_t *stack = data->stack; + igraph_vector_bool_t *nomark = data->nomark; + const igraph_vector_bool_t *GammaX = data->GammaX; + const igraph_vector_t *map = data->map; + long int realvid = (long int) VECTOR(*map)[(long int)vid]; + + IGRAPH_UNUSED(graph); IGRAPH_UNUSED(dist); + + if (VECTOR(*GammaX)[(long int)realvid]) { + if (!igraph_stack_empty(stack)) { + long int top = (long int) igraph_stack_top(stack); + VECTOR(*nomark)[top] = 1; /* we just found a smaller one */ + } + igraph_stack_push(stack, realvid); /* TODO: error check */ + } + + return 0; +} + +static igraph_bool_t igraph_i_all_st_cuts_minimal_dfs_otcb( + const igraph_t *graph, + igraph_integer_t vid, + igraph_integer_t dist, + void *extra) { + igraph_i_all_st_cuts_minimal_dfs_data_t *data = extra; + igraph_stack_t *stack = data->stack; + const igraph_vector_t *map = data->map; + long int realvid = (long int) VECTOR(*map)[(long int)vid]; + + IGRAPH_UNUSED(graph); IGRAPH_UNUSED(dist); + + if (!igraph_stack_empty(stack) && + igraph_stack_top(stack) == realvid) { + igraph_stack_pop(stack); + } + + return 0; +} + +static int igraph_i_all_st_cuts_minimal(const igraph_t *graph, + const igraph_t *domtree, + long int root, + const igraph_marked_queue_t *X, + const igraph_vector_bool_t *GammaX, + const igraph_vector_t *invmap, + igraph_vector_t *minimal) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_stack_t stack; + igraph_vector_bool_t nomark; + igraph_i_all_st_cuts_minimal_dfs_data_t data; + long int i; + + IGRAPH_UNUSED(X); + + IGRAPH_CHECK(igraph_stack_init(&stack, 10)); + IGRAPH_FINALLY(igraph_stack_destroy, &stack); + IGRAPH_CHECK(igraph_vector_bool_init(&nomark, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &nomark); + + data.stack = &stack; + data.nomark = &nomark; + data.GammaX = GammaX; + data.root = root; + data.map = invmap; + + /* We mark all GammaX elements as minimal first. + TODO: actually, we could just use GammaX to return the minimal + elements. */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(nomark)[i] = VECTOR(*GammaX)[i] == 0 ? 1 : 0; + } + + /* We do a reverse DFS from root. If, along a path we find a GammaX + vertex after (=below) another GammaX vertex, we mark the higher + one as non-minimal. */ + + IGRAPH_CHECK(igraph_dfs(domtree, (igraph_integer_t) root, IGRAPH_IN, + /*unreachable=*/ 0, /*order=*/ 0, + /*order_out=*/ 0, /*father=*/ 0, + /*dist=*/ 0, /*in_callback=*/ + igraph_i_all_st_cuts_minimal_dfs_incb, + /*out_callback=*/ + igraph_i_all_st_cuts_minimal_dfs_otcb, + /*extra=*/ &data)); + + igraph_vector_clear(minimal); + for (i = 0; i < no_of_nodes; i++) { + if (!VECTOR(nomark)[i]) { + IGRAPH_CHECK(igraph_vector_push_back(minimal, i)); + } + } + + igraph_vector_bool_destroy(&nomark); + igraph_stack_destroy(&stack); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/* not 'static' because used in igraph_all_st_cuts.c test program */ +int igraph_i_all_st_cuts_pivot(const igraph_t *graph, + const igraph_marked_queue_t *S, + const igraph_estack_t *T, + long int source, + long int target, + long int *v, + igraph_vector_t *Isv, + void *arg) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_t Sbar; + igraph_vector_t Sbar_map, Sbar_invmap; + igraph_vector_t keep; + igraph_t domtree; + igraph_vector_t leftout; + long int i, nomin, n; + long int root; + igraph_vector_t M; + igraph_vector_bool_t GammaS; + igraph_vector_t Nuv; + igraph_vector_t Isv_min; + igraph_vector_t GammaS_vec; + long int Sbar_size; + + IGRAPH_UNUSED(arg); + + /* We need to create the graph induced by Sbar */ + IGRAPH_VECTOR_INIT_FINALLY(&Sbar_map, 0); + IGRAPH_VECTOR_INIT_FINALLY(&Sbar_invmap, 0); + + IGRAPH_VECTOR_INIT_FINALLY(&keep, 0); + for (i = 0; i < no_of_nodes; i++) { + if (!igraph_marked_queue_iselement(S, i)) { + IGRAPH_CHECK(igraph_vector_push_back(&keep, i)); + } + } + Sbar_size = igraph_vector_size(&keep); + + IGRAPH_CHECK(igraph_induced_subgraph_map(graph, &Sbar, + igraph_vss_vector(&keep), + IGRAPH_SUBGRAPH_AUTO, + /* map= */ &Sbar_map, + /* invmap= */ &Sbar_invmap)); + igraph_vector_destroy(&keep); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_FINALLY(igraph_destroy, &Sbar); + + root = (long int) VECTOR(Sbar_map)[target] - 1; + + /* -------------------------------------------------------------*/ + /* Construct the dominator tree of Sbar */ + + IGRAPH_VECTOR_INIT_FINALLY(&leftout, 0); + IGRAPH_CHECK(igraph_dominator_tree(&Sbar, (igraph_integer_t) root, + /*dom=*/ 0, &domtree, + &leftout, IGRAPH_IN)); + IGRAPH_FINALLY(igraph_destroy, &domtree); + + /* -------------------------------------------------------------*/ + /* Identify the set M of minimal elements of Gamma(S) with respect + to the dominator relation. */ + + /* First we create GammaS */ + /* TODO: use the adjacency list, instead of neighbors() */ + IGRAPH_CHECK(igraph_vector_bool_init(&GammaS, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &GammaS); + if (igraph_marked_queue_size(S) == 0) { + VECTOR(GammaS)[(long int) VECTOR(Sbar_map)[source] - 1] = 1; + } else { + for (i = 0; i < no_of_nodes; i++) { + if (igraph_marked_queue_iselement(S, i)) { + igraph_vector_t neis; + long int j; + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) i, + IGRAPH_OUT)); + n = igraph_vector_size(&neis); + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neis)[j]; + if (!igraph_marked_queue_iselement(S, nei)) { + VECTOR(GammaS)[nei] = 1; + } + } + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + } + } + } + + /* Relabel left out vertices (set K in Provan & Shier) to + correspond to node labelling of graph instead of SBar. + At the same time ensure that GammaS is a proper subset of + L, where L are the nodes in the dominator tree. */ + n = igraph_vector_size(&leftout); + for (i = 0; i < n; i++) { + VECTOR(leftout)[i] = VECTOR(Sbar_invmap)[(long int)VECTOR(leftout)[i]]; + VECTOR(GammaS)[(long int)VECTOR(leftout)[i]] = 0; + } + + IGRAPH_VECTOR_INIT_FINALLY(&M, 0); + if (igraph_ecount(&domtree) > 0) { + IGRAPH_CHECK(igraph_i_all_st_cuts_minimal(graph, &domtree, root, S, + &GammaS, &Sbar_invmap, &M)); + } + + igraph_vector_clear(Isv); + IGRAPH_VECTOR_INIT_FINALLY(&Nuv, 0); + IGRAPH_VECTOR_INIT_FINALLY(&Isv_min, 0); + IGRAPH_VECTOR_INIT_FINALLY(&GammaS_vec, 0); + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(GammaS)[i]) { + IGRAPH_CHECK(igraph_vector_push_back(&GammaS_vec, i)); + } + } + + nomin = igraph_vector_size(&M); + for (i = 0; i < nomin; i++) { + /* -------------------------------------------------------------*/ + /* For each v in M find the set Nu(v)=dom(Sbar, v)-K + Nu(v) contains all vertices that are dominated by v, for every + v, this is a subtree of the dominator tree, rooted at v. The + different subtrees are disjoint. */ + long int min = (long int) VECTOR(Sbar_map)[(long int) VECTOR(M)[i] ] - 1; + long int nuvsize, isvlen, j; + IGRAPH_CHECK(igraph_dfs(&domtree, (igraph_integer_t) min, IGRAPH_IN, + /*unreachable=*/ 0, /*order=*/ &Nuv, + /*order_out=*/ 0, /*father=*/ 0, /*dist=*/ 0, + /*in_callback=*/ 0, /*out_callback=*/ 0, + /*extra=*/ 0)); + /* Remove the NAN values from the end of the vector */ + for (nuvsize = 0; nuvsize < Sbar_size; nuvsize++) { + igraph_real_t t = VECTOR(Nuv)[nuvsize]; + if (IGRAPH_FINITE(t)) { + VECTOR(Nuv)[nuvsize] = VECTOR(Sbar_invmap)[(long int) t]; + } else { + break; + } + } + igraph_vector_resize(&Nuv, nuvsize); + + /* -------------------------------------------------------------*/ + /* By a BFS search of determine I(S,v)-K. + I(S,v) contains all vertices that are in Nu(v) and that are + reachable from Gamma(S) via a path in Nu(v). */ + IGRAPH_CHECK(igraph_bfs(graph, /*root=*/ -1, /*roots=*/ &GammaS_vec, + /*mode=*/ IGRAPH_OUT, /*unreachable=*/ 0, + /*restricted=*/ &Nuv, + /*order=*/ &Isv_min, /*rank=*/ 0, + /*father=*/ 0, /*pred=*/ 0, /*succ=*/ 0, + /*dist=*/ 0, /*callback=*/ 0, /*extra=*/ 0)); + for (isvlen = 0; isvlen < no_of_nodes; isvlen++) { + if (!IGRAPH_FINITE(VECTOR(Isv_min)[isvlen])) { + break; + } + } + igraph_vector_resize(&Isv_min, isvlen); + + /* -------------------------------------------------------------*/ + /* For each c in M check whether Isv-K is included in Tbar. If + such a v is found, compute Isv={x|v[Nu(v) U K]x} and return v and + Isv; otherwise return Isv={}. */ + for (j = 0; j < isvlen; j++) { + long int v = (long int) VECTOR(Isv_min)[j]; + if (igraph_estack_iselement(T, v) || v == target) { + break; + } + } + /* We might have found one */ + if (j == isvlen) { + *v = (long int) VECTOR(M)[i]; + /* Calculate real Isv */ + IGRAPH_CHECK(igraph_vector_append(&Nuv, &leftout)); + IGRAPH_CHECK(igraph_bfs(graph, /*root=*/ (igraph_integer_t) *v, + /*roots=*/ 0, /*mode=*/ IGRAPH_OUT, + /*unreachable=*/ 0, /*restricted=*/ &Nuv, + /*order=*/ &Isv_min, /*rank=*/ 0, + /*father=*/ 0, /*pred=*/ 0, /*succ=*/ 0, + /*dist=*/ 0, /*callback=*/ 0, /*extra=*/ 0)); + for (isvlen = 0; isvlen < no_of_nodes; isvlen++) { + if (!IGRAPH_FINITE(VECTOR(Isv_min)[isvlen])) { + break; + } + } + igraph_vector_resize(&Isv_min, isvlen); + igraph_vector_update(Isv, &Isv_min); + + break; + } + } + + igraph_vector_destroy(&GammaS_vec); + igraph_vector_destroy(&Isv_min); + igraph_vector_destroy(&Nuv); + IGRAPH_FINALLY_CLEAN(3); + + igraph_vector_destroy(&M); + igraph_vector_bool_destroy(&GammaS); + igraph_destroy(&domtree); + igraph_vector_destroy(&leftout); + igraph_destroy(&Sbar); + igraph_vector_destroy(&Sbar_map); + igraph_vector_destroy(&Sbar_invmap); + IGRAPH_FINALLY_CLEAN(7); + + return 0; +} + +/* TODO: This is a temporary recursive version, without proper error + handling */ + +int igraph_provan_shier_list(const igraph_t *graph, + igraph_marked_queue_t *S, + igraph_estack_t *T, + long int source, + long int target, + igraph_vector_ptr_t *result, + igraph_provan_shier_pivot_t *pivot, + void *pivot_arg) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t Isv; + long int v = 0; + long int i, n; + + igraph_vector_init(&Isv, 0); + + pivot(graph, S, T, source, target, &v, &Isv, pivot_arg); + if (igraph_vector_size(&Isv) == 0) { + if (igraph_marked_queue_size(S) != 0 && + igraph_marked_queue_size(S) != no_of_nodes) { + igraph_vector_t *vec = igraph_Calloc(1, igraph_vector_t); + igraph_vector_init(vec, igraph_marked_queue_size(S)); + igraph_marked_queue_as_vector(S, vec); + IGRAPH_CHECK(igraph_vector_ptr_push_back(result, vec)); + } + } else { + /* Put v into T */ + igraph_estack_push(T, v); + + /* Go down left in the search tree */ + igraph_provan_shier_list(graph, S, T, source, target, + result, pivot, pivot_arg); + + /* Take out v from T */ + igraph_estack_pop(T); + + /* Add Isv to S */ + igraph_marked_queue_start_batch(S); + n = igraph_vector_size(&Isv); + for (i = 0; i < n; i++) { + if (!igraph_marked_queue_iselement(S, (long int) VECTOR(Isv)[i])) { + igraph_marked_queue_push(S, (long int) VECTOR(Isv)[i]); + } + } + + /* Go down right in the search tree */ + + igraph_provan_shier_list(graph, S, T, source, target, + result, pivot, pivot_arg); + + /* Take out Isv from S */ + igraph_marked_queue_pop_back_batch(S); + } + + igraph_vector_destroy(&Isv); + + return 0; +} + +/** + * \function igraph_all_st_cuts + * List all edge-cuts between two vertices in a directed graph + * + * This function lists all edge-cuts between a source and a target + * vertex. Every cut is listed exactly once. The implemented algorithm + * is described in JS Provan and DR Shier: A Paradigm for listing + * (s,t)-cuts in graphs, Algorithmica 15, 351--372, 1996. + * + * \param graph The input graph, is must be directed. + * \param cuts An initialized pointer vector, the cuts are stored + * here. It is a list of pointers to igraph_vector_t + * objects. Each vector will contain the ids of the edges in + * the cut. This argument is ignored if it is a null pointer. + * To free all memory allocated for \c cuts, you need call + * \ref igraph_vector_destroy() and then \ref igraph_free() on + * each element, before destroying the pointer vector itself. + * \param partition1s An initialized pointer vector, the list of + * vertex sets, generating the actual edge cuts, are stored + * here. Each vector contains a set of vertex ids. If X is such + * a set, then all edges going from X to the complement of X + * form an (s,t) edge-cut in the graph. This argument is + * ignored if it is a null pointer. + * To free all memory allocated for \c partition1s, you need call + * \ref igraph_vector_destroy() and then \ref igraph_free() on + * each element, before destroying the pointer vector itself. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \return Error code. + * + * Time complexity: O(n(|V|+|E|)), where |V| is the number of + * vertices, |E| is the number of edges, and n is the number of cuts. + * + * \example examples/simple/igraph_all_st_cuts.c + */ + +int igraph_all_st_cuts(const igraph_t *graph, + igraph_vector_ptr_t *cuts, + igraph_vector_ptr_t *partition1s, + igraph_integer_t source, + igraph_integer_t target) { + + /* S is a special stack, in which elements are pushed in batches. + It is then possible to remove the whole batch in one step. + + T is a stack with an is-element operation. + Every element is included at most once. + */ + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_marked_queue_t S; + igraph_estack_t T; + igraph_vector_ptr_t *mypartition1s = partition1s, vpartition1s; + long int i, nocuts; + + if (!igraph_is_directed(graph)) { + IGRAPH_ERROR("Listing all s-t cuts only implemented for " + "directed graphs", IGRAPH_UNIMPLEMENTED); + } + + if (!partition1s) { + mypartition1s = &vpartition1s; + IGRAPH_CHECK(igraph_vector_ptr_init(mypartition1s, 0)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, mypartition1s); + } else { + igraph_vector_ptr_clear(mypartition1s); + } + + IGRAPH_CHECK(igraph_marked_queue_init(&S, no_of_nodes)); + IGRAPH_FINALLY(igraph_marked_queue_destroy, &S); + IGRAPH_CHECK(igraph_estack_init(&T, no_of_nodes, 0)); + IGRAPH_FINALLY(igraph_estack_destroy, &T); + + if (cuts) { + igraph_vector_ptr_clear(cuts); + } + + /* We call it with S={}, T={} */ + IGRAPH_CHECK(igraph_provan_shier_list(graph, &S, &T, + source, target, mypartition1s, + igraph_i_all_st_cuts_pivot, + /*pivot_arg=*/ 0)); + + nocuts = igraph_vector_ptr_size(mypartition1s); + + if (cuts) { + igraph_vector_long_t inS; + IGRAPH_CHECK(igraph_vector_long_init(&inS, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &inS); + IGRAPH_CHECK(igraph_vector_ptr_resize(cuts, nocuts)); + for (i = 0; i < nocuts; i++) { + igraph_vector_t *cut; + igraph_vector_t *part = VECTOR(*mypartition1s)[i]; + long int cutsize = 0; + long int j, partlen = igraph_vector_size(part); + /* Mark elements */ + for (j = 0; j < partlen; j++) { + long int v = (long int) VECTOR(*part)[j]; + VECTOR(inS)[v] = i + 1; + } + /* Check how many edges */ + for (j = 0; j < no_of_edges; j++) { + long int from = IGRAPH_FROM(graph, j); + long int to = IGRAPH_TO(graph, j); + long int pfrom = VECTOR(inS)[from]; + long int pto = VECTOR(inS)[to]; + if (pfrom == i + 1 && pto != i + 1) { + cutsize++; + } + } + /* Add the edges */ + cut = igraph_Calloc(1, igraph_vector_t); + if (!cut) { + IGRAPH_ERROR("Cannot calculate s-t cuts", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(cut, cutsize); + cutsize = 0; + for (j = 0; j < no_of_edges; j++) { + long int from = IGRAPH_FROM(graph, j); + long int to = IGRAPH_TO(graph, j); + long int pfrom = VECTOR(inS)[from]; + long int pto = VECTOR(inS)[to]; + if ((pfrom == i + 1 && pto != i + 1)) { + VECTOR(*cut)[cutsize++] = j; + } + } + VECTOR(*cuts)[i] = cut; + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_long_destroy(&inS); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_estack_destroy(&T); + igraph_marked_queue_destroy(&S); + IGRAPH_FINALLY_CLEAN(2); + + if (!partition1s) { + for (i = 0; i < nocuts; i++) { + igraph_vector_t *cut = VECTOR(*mypartition1s)[i]; + igraph_vector_destroy(cut); + igraph_free(cut); + VECTOR(*mypartition1s)[i] = 0; + } + igraph_vector_ptr_destroy(mypartition1s); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/* We need to find the minimal active elements of Sbar. I.e. all + active Sbar elements 'v', s.t. there is no other 'w' active Sbar + element from which 'v' is reachable. (Not necessarily through + active vertices.) + + We calculate the in-degree of all vertices in Sbar first. Then we + look at the vertices with zero in-degree. If these are active, + then they are minimal. If they are are not active, then we remove + them from the graph, and check whether they resulted in more + zero-indegree vertices. +*/ + +static int igraph_i_all_st_mincuts_minimal(const igraph_t *Sbar, + const igraph_vector_bool_t *active, + const igraph_vector_t *invmap, + igraph_vector_t *minimal) { + + long int no_of_nodes = igraph_vcount(Sbar); + igraph_vector_t indeg; + long int i, minsize; + igraph_vector_t neis; + + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INIT_FINALLY(&indeg, no_of_nodes); + + IGRAPH_CHECK(igraph_degree(Sbar, &indeg, igraph_vss_all(), + IGRAPH_IN, /*loops=*/ 1)); + +#define ACTIVE(x) (VECTOR(*active)[(long int)VECTOR(*invmap)[(x)]]) +#define ZEROIN(x) (VECTOR(indeg)[(x)]==0) + + for (i = 0; i < no_of_nodes; i++) { + if (!ACTIVE(i)) { + long int j, n; + IGRAPH_CHECK(igraph_neighbors(Sbar, &neis, (igraph_integer_t) i, + IGRAPH_OUT)); + n = igraph_vector_size(&neis); + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neis)[j]; + VECTOR(indeg)[nei] -= 1; + } + } + } + + for (minsize = 0, i = 0; i < no_of_nodes; i++) { + if (ACTIVE(i) && ZEROIN(i)) { + minsize++; + } + } + + IGRAPH_CHECK(igraph_vector_resize(minimal, minsize)); + + for (minsize = 0, i = 0; i < no_of_nodes; i++) { + if (ACTIVE(i) && ZEROIN(i)) { + VECTOR(*minimal)[minsize++] = i; + } + } + +#undef ACTIVE +#undef ZEROIN + + igraph_vector_destroy(&indeg); + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +typedef struct igraph_i_all_st_mincuts_data_t { + const igraph_vector_bool_t *active; +} igraph_i_all_st_mincuts_data_t; + +static int igraph_i_all_st_mincuts_pivot(const igraph_t *graph, + const igraph_marked_queue_t *S, + const igraph_estack_t *T, + long int source, + long int target, + long int *v, + igraph_vector_t *Isv, + void *arg) { + + igraph_i_all_st_mincuts_data_t *data = arg; + const igraph_vector_bool_t *active = data->active; + + long int no_of_nodes = igraph_vcount(graph); + long int i, j; + igraph_vector_t Sbar_map, Sbar_invmap; + igraph_vector_t keep; + igraph_t Sbar; + igraph_vector_t M; + long int nomin; + + IGRAPH_UNUSED(source); IGRAPH_UNUSED(target); + + if (igraph_marked_queue_size(S) == no_of_nodes) { + igraph_vector_clear(Isv); + return 0; + } + + /* Create the graph induced by Sbar */ + IGRAPH_VECTOR_INIT_FINALLY(&Sbar_map, 0); + IGRAPH_VECTOR_INIT_FINALLY(&Sbar_invmap, 0); + + IGRAPH_VECTOR_INIT_FINALLY(&keep, 0); + for (i = 0; i < no_of_nodes; i++) { + if (!igraph_marked_queue_iselement(S, i)) { + IGRAPH_CHECK(igraph_vector_push_back(&keep, i)); + } + } + + /* TODO: it is not even necessary to create Sbar explicitly, we + just need to find the M elements efficiently. See the + Provan-Shier paper for details. */ + IGRAPH_CHECK(igraph_induced_subgraph_map(graph, &Sbar, + igraph_vss_vector(&keep), + IGRAPH_SUBGRAPH_AUTO, + /* map= */ &Sbar_map, + /* invmap= */ &Sbar_invmap)); + IGRAPH_FINALLY(igraph_destroy, &Sbar); + + /* ------------------------------------------------------------- */ + /* Identify the set M of minimal elements that are active */ + IGRAPH_VECTOR_INIT_FINALLY(&M, 0); + IGRAPH_CHECK(igraph_i_all_st_mincuts_minimal(&Sbar, active, + &Sbar_invmap, &M)); + + /* ------------------------------------------------------------- */ + /* Now find a minimal element that is not in T */ + igraph_vector_clear(Isv); + nomin = igraph_vector_size(&M); + for (i = 0; i < nomin; i++) { + long int min = (long int) VECTOR(Sbar_invmap)[ (long int) VECTOR(M)[i] ]; + if (min != target) + if (!igraph_estack_iselement(T, min)) { + break; + } + } + if (i != nomin) { + /* OK, we found a pivot element. I(S,v) contains all elements + that can reach the pivot element */ + igraph_vector_t Isv_min; + IGRAPH_VECTOR_INIT_FINALLY(&Isv_min, 0); + *v = (long int) VECTOR(Sbar_invmap)[ (long int) VECTOR(M)[i] ]; + /* TODO: restricted == keep ? */ + IGRAPH_CHECK(igraph_bfs(graph, /*root=*/ (igraph_integer_t) *v,/*roots=*/ 0, + /*mode=*/ IGRAPH_IN, /*unreachable=*/ 0, + /*restricted=*/ &keep, /*order=*/ &Isv_min, + /*rank=*/ 0, /*father=*/ 0, /*pred=*/ 0, + /*succ=*/ 0, /*dist=*/ 0, /*callback=*/ 0, + /*extra=*/ 0)); + for (j = 0; j < no_of_nodes; j++) { + igraph_real_t u = VECTOR(Isv_min)[j]; + if (!IGRAPH_FINITE(u)) { + break; + } + if (!igraph_estack_iselement(T, u)) { + IGRAPH_CHECK(igraph_vector_push_back(Isv, u)); + } + } + igraph_vector_destroy(&Isv_min); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&M); + igraph_destroy(&Sbar); + igraph_vector_destroy(&keep); + igraph_vector_destroy(&Sbar_invmap); + igraph_vector_destroy(&Sbar_map); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +/** + * \function igraph_all_st_mincuts + * All minimum s-t cuts of a directed graph + * + * This function lists all minimum edge cuts between two vertices, in a + * directed graph. The implemented algorithm + * is described in JS Provan and DR Shier: A Paradigm for listing + * (s,t)-cuts in graphs, Algorithmica 15, 351--372, 1996. + * + * \param graph The input graph, it must be directed. + * \param value Pointer to a real number, the value of the minimum cut + * is stored here, unless it is a null pointer. + * \param cuts An initialized pointer vector, the cuts are stored + * here. It is a list of pointers to igraph_vector_t + * objects. Each vector will contain the ids of the edges in + * the cut. This argument is ignored if it is a null pointer. + * To free all memory allocated for \c cuts, you need call + * \ref igraph_vector_destroy() and then \ref igraph_free() on + * each element, before destroying the pointer vector itself. + * \param partition1s An initialized pointer vector, the list of + * vertex sets, generating the actual edge cuts, are stored + * here. Each vector contains a set of vertex ids. If X is such + * a set, then all edges going from X to the complement of X + * form an (s,t) edge-cut in the graph. This argument is + * ignored if it is a null pointer. + * \param source The id of the source vertex. + * \param target The id of the target vertex. + * \param capacity Vector of edge capacities. If this is a null + * pointer, then all edges are assumed to have capacity one. + * \return Error code. + * + * Time complexity: O(n(|V|+|E|))+O(F), where |V| is the number of + * vertices, |E| is the number of edges, and n is the number of cuts; + * O(F) is the time complexity of the maximum flow algorithm, see \ref + * igraph_maxflow(). + * + * \example examples/simple/igraph_all_st_mincuts.c + */ + +int igraph_all_st_mincuts(const igraph_t *graph, igraph_real_t *value, + igraph_vector_ptr_t *cuts, + igraph_vector_ptr_t *partition1s, + igraph_integer_t source, + igraph_integer_t target, + const igraph_vector_t *capacity) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_vector_t flow; + igraph_t residual; + igraph_vector_t NtoL; + long int newsource, newtarget; + igraph_marked_queue_t S; + igraph_estack_t T; + igraph_i_all_st_mincuts_data_t pivot_data; + igraph_vector_bool_t VE1bool; + igraph_vector_t VE1; + long int VE1size = 0; + long int i, nocuts; + igraph_integer_t proj_nodes; + igraph_vector_t revmap_ptr, revmap_next; + igraph_vector_ptr_t closedsets; + igraph_vector_ptr_t *mypartition1s = partition1s, vpartition1s; + igraph_maxflow_stats_t stats; + + /* -------------------------------------------------------------------- */ + /* Error checks */ + if (!igraph_is_directed(graph)) { + IGRAPH_ERROR("S-t cuts can only be listed in directed graphs", + IGRAPH_UNIMPLEMENTED); + } + if (source < 0 || source >= no_of_nodes) { + IGRAPH_ERROR("Invalid `source' vertex", IGRAPH_EINVAL); + } + if (target < 0 || target >= no_of_nodes) { + IGRAPH_ERROR("Invalid `target' vertex", IGRAPH_EINVAL); + } + if (source == target) { + IGRAPH_ERROR("`source' and 'target' are the same vertex", IGRAPH_EINVAL); + } + + if (!partition1s) { + mypartition1s = &vpartition1s; + IGRAPH_CHECK(igraph_vector_ptr_init(mypartition1s, 0)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, mypartition1s); + } + + /* -------------------------------------------------------------------- */ + /* We need to calculate the maximum flow first */ + IGRAPH_VECTOR_INIT_FINALLY(&flow, 0); + IGRAPH_CHECK(igraph_maxflow(graph, value, &flow, /*cut=*/ 0, + /*partition1=*/ 0, /*partition2=*/ 0, + /*source=*/ source, /*target=*/ target, + capacity, &stats)); + + /* -------------------------------------------------------------------- */ + /* Then we need the reverse residual graph */ + IGRAPH_CHECK(igraph_reverse_residual_graph(graph, capacity, &residual, + &flow)); + IGRAPH_FINALLY(igraph_destroy, &residual); + + /* -------------------------------------------------------------------- */ + /* We shrink it to its strongly connected components */ + IGRAPH_VECTOR_INIT_FINALLY(&NtoL, 0); + IGRAPH_CHECK(igraph_clusters(&residual, /*membership=*/ &NtoL, + /*csize=*/ 0, /*no=*/ &proj_nodes, + IGRAPH_STRONG)); + IGRAPH_CHECK(igraph_contract_vertices(&residual, /*mapping=*/ &NtoL, + /*vertex_comb=*/ 0)); + IGRAPH_CHECK(igraph_simplify(&residual, /*multiple=*/ 1, /*loops=*/ 1, + /*edge_comb=*/ 0)); + + newsource = (long int) VECTOR(NtoL)[(long int)source]; + newtarget = (long int) VECTOR(NtoL)[(long int)target]; + + /* TODO: handle the newsource == newtarget case */ + + /* -------------------------------------------------------------------- */ + /* Determine the active vertices in the projection */ + IGRAPH_VECTOR_INIT_FINALLY(&VE1, 0); + IGRAPH_CHECK(igraph_vector_bool_init(&VE1bool, proj_nodes)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &VE1bool); + for (i = 0; i < no_of_edges; i++) { + if (VECTOR(flow)[i] > 0) { + long int from = IGRAPH_FROM(graph, i); + long int to = IGRAPH_TO(graph, i); + long int pfrom = (long int) VECTOR(NtoL)[from]; + long int pto = (long int) VECTOR(NtoL)[to]; + if (!VECTOR(VE1bool)[pfrom]) { + VECTOR(VE1bool)[pfrom] = 1; + VE1size++; + } + if (!VECTOR(VE1bool)[pto]) { + VECTOR(VE1bool)[pto] = 1; + VE1size++; + } + } + } + IGRAPH_CHECK(igraph_vector_reserve(&VE1, VE1size)); + for (i = 0; i < proj_nodes; i++) { + if (VECTOR(VE1bool)[i]) { + igraph_vector_push_back(&VE1, i); + } + } + + if (cuts) { + igraph_vector_ptr_clear(cuts); + } + if (partition1s) { + igraph_vector_ptr_clear(partition1s); + } + + /* -------------------------------------------------------------------- */ + /* Everything is ready, list the cuts, using the right PIVOT + function */ + IGRAPH_CHECK(igraph_marked_queue_init(&S, no_of_nodes)); + IGRAPH_FINALLY(igraph_marked_queue_destroy, &S); + IGRAPH_CHECK(igraph_estack_init(&T, no_of_nodes, 0)); + IGRAPH_FINALLY(igraph_estack_destroy, &T); + + pivot_data.active = &VE1bool; + + IGRAPH_CHECK(igraph_vector_ptr_init(&closedsets, 0)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &closedsets); /* TODO */ + IGRAPH_CHECK(igraph_provan_shier_list(&residual, &S, &T, + newsource, newtarget, &closedsets, + igraph_i_all_st_mincuts_pivot, + &pivot_data)); + + /* Convert the closed sets in the contracted graphs to cutsets in the + original graph */ + IGRAPH_VECTOR_INIT_FINALLY(&revmap_ptr, igraph_vcount(&residual)); + IGRAPH_VECTOR_INIT_FINALLY(&revmap_next, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + long int id = (long int) VECTOR(NtoL)[i]; + VECTOR(revmap_next)[i] = VECTOR(revmap_ptr)[id]; + VECTOR(revmap_ptr)[id] = i + 1; + } + + /* Create partitions in original graph */ + nocuts = igraph_vector_ptr_size(&closedsets); + igraph_vector_ptr_clear(mypartition1s); + IGRAPH_CHECK(igraph_vector_ptr_reserve(mypartition1s, nocuts)); + for (i = 0; i < nocuts; i++) { + igraph_vector_t *supercut = VECTOR(closedsets)[i]; + long int j, supercutsize = igraph_vector_size(supercut); + igraph_vector_t *cut = igraph_Calloc(1, igraph_vector_t); + IGRAPH_VECTOR_INIT_FINALLY(cut, 0); /* TODO: better allocation */ + for (j = 0; j < supercutsize; j++) { + long int vtx = (long int) VECTOR(*supercut)[j]; + long int ovtx = (long int) VECTOR(revmap_ptr)[vtx]; + while (ovtx != 0) { + ovtx--; + IGRAPH_CHECK(igraph_vector_push_back(cut, ovtx)); + ovtx = (long int) VECTOR(revmap_next)[ovtx]; + } + } + igraph_vector_ptr_push_back(mypartition1s, cut); + IGRAPH_FINALLY_CLEAN(1); + + igraph_vector_destroy(supercut); + igraph_free(supercut); + VECTOR(closedsets)[i] = 0; + } + + igraph_vector_destroy(&revmap_next); + igraph_vector_destroy(&revmap_ptr); + igraph_vector_ptr_destroy(&closedsets); + IGRAPH_FINALLY_CLEAN(3); + + /* Create cuts in original graph */ + if (cuts) { + igraph_vector_long_t memb; + IGRAPH_CHECK(igraph_vector_long_init(&memb, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &memb); + IGRAPH_CHECK(igraph_vector_ptr_resize(cuts, nocuts)); + for (i = 0; i < nocuts; i++) { + igraph_vector_t *part = VECTOR(*mypartition1s)[i]; + long int j, n = igraph_vector_size(part); + igraph_vector_t *v; + v = igraph_Calloc(1, igraph_vector_t); + if (!v) { + IGRAPH_ERROR("Cannot list minimum s-t cuts", IGRAPH_ENOMEM); + } + IGRAPH_VECTOR_INIT_FINALLY(v, 0); + for (j = 0; j < n; j++) { + long int vtx = (long int) VECTOR(*part)[j]; + VECTOR(memb)[vtx] = i + 1; + } + for (j = 0; j < no_of_edges; j++) { + if (VECTOR(flow)[j] > 0) { + long int from = IGRAPH_FROM(graph, j); + long int to = IGRAPH_TO(graph, j); + if (VECTOR(memb)[from] == i + 1 && VECTOR(memb)[to] != i + 1) { + IGRAPH_CHECK(igraph_vector_push_back(v, j)); /* TODO: allocation */ + } + } + } + VECTOR(*cuts)[i] = v; + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_long_destroy(&memb); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_estack_destroy(&T); + igraph_marked_queue_destroy(&S); + igraph_vector_bool_destroy(&VE1bool); + igraph_vector_destroy(&VE1); + igraph_vector_destroy(&NtoL); + igraph_destroy(&residual); + igraph_vector_destroy(&flow); + IGRAPH_FINALLY_CLEAN(7); + + if (!partition1s) { + for (i = 0; i < nocuts; i++) { + igraph_vector_t *cut = VECTOR(*mypartition1s)[i]; + igraph_vector_destroy(cut); + igraph_free(cut); + VECTOR(*mypartition1s)[i] = 0; + } + igraph_vector_ptr_destroy(mypartition1s); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + diff --git a/src/stack.pmt b/src/stack.pmt new file mode 100644 index 0000000..daa9e6e --- /dev/null +++ b/src/stack.pmt @@ -0,0 +1,294 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_memory.h" +#include "igraph_error.h" +#include "config.h" + +#include +#include /* memcpy & co. */ +#include + +/** + * \ingroup stack + * \function igraph_stack_init + * \brief Initializes a stack. + * + * The initialized stack is always empty. + * \param s Pointer to an uninitialized stack. + * \param size The number of elements to allocate memory for. + * \return Error code. + * + * Time complexity: O(\p size). + */ + +int FUNCTION(igraph_stack, init) (TYPE(igraph_stack)* s, long int size) { + long int alloc_size = size > 0 ? size : 1; + assert (s != NULL); + if (size < 0) { + size = 0; + } + s->stor_begin = igraph_Calloc(alloc_size, BASE); + if (s->stor_begin == 0) { + IGRAPH_ERROR("stack init failed", IGRAPH_ENOMEM); + } + s->stor_end = s->stor_begin + alloc_size; + s->end = s->stor_begin; + + return 0; +} + +/** + * \ingroup stack + * \function igraph_stack_destroy + * \brief Destroys a stack object. + * + * Deallocate the memory used for a stack. + * It is possible to reinitialize a destroyed stack again by + * \ref igraph_stack_init(). + * \param s The stack to destroy. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_stack, destroy) (TYPE(igraph_stack)* s) { + assert( s != NULL); + if (s->stor_begin != 0) { + igraph_Free(s->stor_begin); + s->stor_begin = NULL; + } +} + +/** + * \ingroup stack + * \function igraph_stack_reserve + * \brief Reserve memory. + * + * Reserve memory for future use. The actual size of the stack is + * unchanged. + * \param s The stack object. + * \param size The number of elements to reserve memory for. If it is + * not bigger than the current size then nothing happens. + * \return Error code. + * + * Time complexity: should be around O(n), the new allocated size of + * the stack. + */ + +int FUNCTION(igraph_stack, reserve) (TYPE(igraph_stack)* s, long int size) { + long int actual_size = FUNCTION(igraph_stack, size)(s); + BASE *tmp; + assert(s != NULL); + assert(s->stor_begin != NULL); + + if (size <= actual_size) { + return 0; + } + + tmp = igraph_Realloc(s->stor_begin, (size_t) size, BASE); + if (tmp == 0) { + IGRAPH_ERROR("stack reserve failed", IGRAPH_ENOMEM); + } + s->stor_begin = tmp; + s->stor_end = s->stor_begin + size; + s->end = s->stor_begin + actual_size; + + return 0; +} + +/** + * \ingroup stack + * \function igraph_stack_empty + * \brief Decides whether a stack object is empty. + * + * \param s The stack object. + * \return Boolean, \c TRUE if the stack is empty, \c FALSE + * otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_stack, empty) (TYPE(igraph_stack)* s) { + assert (s != NULL); + assert (s->stor_begin != NULL); + assert (s->end != NULL); + return s->stor_begin == s->end; +} + +/** + * \ingroup stack + * \function igraph_stack_size + * \brief Returns the number of elements in a stack. + * + * \param s The stack object. + * \return The number of elements in the stack. + * + * Time complexity: O(1). + */ + +long int FUNCTION(igraph_stack, size) (const TYPE(igraph_stack)* s) { + assert (s != NULL); + assert (s->stor_begin != NULL); + return s->end - s->stor_begin; +} + +/** + * \ingroup stack + * \function igraph_stack_clear + * \brief Removes all elements from a stack. + * + * \param s The stack object. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_stack, clear) (TYPE(igraph_stack)* s) { + assert (s != NULL); + assert (s->stor_begin != NULL); + s->end = s->stor_begin; +} + +/** + * \ingroup stack + * \function igraph_stack_push + * \brief Places an element on the top of a stack. + * + * The capacity of the stack is increased, if needed. + * \param s The stack object. + * \param elem The element to push. + * \return Error code. + * + * Time complexity: O(1) is no reallocation is needed, O(n) + * otherwise, but it is ensured that n push operations are performed + * in O(n) time. + */ + +int FUNCTION(igraph_stack, push)(TYPE(igraph_stack)* s, BASE elem) { + assert (s != NULL); + assert (s->stor_begin != NULL); + if (s->end == s->stor_end) { + /* full, allocate more storage */ + + BASE *bigger = NULL, *old = s->stor_begin; + + bigger = igraph_Calloc(2 * FUNCTION(igraph_stack, size)(s) + 1, BASE); + if (bigger == 0) { + IGRAPH_ERROR("stack push failed", IGRAPH_ENOMEM); + } + memcpy(bigger, s->stor_begin, + (size_t) FUNCTION(igraph_stack, size)(s)*sizeof(BASE)); + + s->end = bigger + (s->stor_end - s->stor_begin); + s->stor_end = bigger + 2 * (s->stor_end - s->stor_begin) + 1; + s->stor_begin = bigger; + + *(s->end) = elem; + (s->end) += 1; + + igraph_Free(old); + } else { + *(s->end) = elem; + (s->end) += 1; + } + return 0; +} + +/** + * \ingroup stack + * \function igraph_stack_pop + * \brief Removes and returns an element from the top of a stack. + * + * The stack must contain at least one element, call \ref + * igraph_stack_empty() to make sure of this. + * \param s The stack object. + * \return The removed top element. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_stack, pop) (TYPE(igraph_stack)* s) { + + assert (s != NULL); + assert (s->stor_begin != NULL); + assert (s->end != NULL); + assert (s->end != s->stor_begin); + + (s->end)--; + + return *(s->end); +} + +/** + * \ingroup stack + * \function igraph_stack_top + * \brief Query top element. + * + * Returns the top element of the stack, without removing it. + * The stack must be non-empty. + * \param s The stack. + * \return The top element. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_stack, top) (const TYPE(igraph_stack)* s) { + + assert (s != NULL); + assert (s->stor_begin != NULL); + assert (s->end != NULL); + assert (s->end != s->stor_begin); + + return *(s->end - 1); +} + +#if defined (OUT_FORMAT) +#ifndef USING_R + +int FUNCTION(igraph_stack, print)(const TYPE(igraph_stack) *s) { + long int i, n = FUNCTION(igraph_stack, size)(s); + if (n != 0) { + printf(OUT_FORMAT, s->stor_begin[0]); + } + for (i = 1; i < n; i++) { + printf(" " OUT_FORMAT, s->stor_begin[i]); + } + printf("\n"); + return 0; +} +#endif + +int FUNCTION(igraph_stack, fprint)(const TYPE(igraph_stack) *s, FILE *file) { + long int i, n = FUNCTION(igraph_stack, size)(s); + if (n != 0) { + fprintf(file, OUT_FORMAT, s->stor_begin[0]); + } + for (i = 1; i < n; i++) { + fprintf(file, " " OUT_FORMAT, s->stor_begin[i]); + } + fprintf(file, "\n"); + return 0; +} + +#endif + diff --git a/src/statusbar.c b/src/statusbar.c new file mode 100644 index 0000000..4f015d9 --- /dev/null +++ b/src/statusbar.c @@ -0,0 +1,130 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2010-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_statusbar.h" +#include "igraph_error.h" +#include "config.h" +#include +#include + +static IGRAPH_THREAD_LOCAL igraph_status_handler_t *igraph_i_status_handler = 0; + +/** + * \function igraph_status + * Report status from an igraph function. + * + * It calls the installed status handler function, if there is + * one. Otherwise it does nothing. Note that the standard way to + * report the status from an igraph function is the + * \ref IGRAPH_STATUS or \ref IGRAPH_STATUSF macro, as these + * take care of the termination of the calling function if the + * status handler returns with \c IGRAPH_INTERRUPTED. + * \param message The status message. + * \param data Additional context, with user-defined semantics. + * Existing igraph functions pass a null pointer here. + * \return Error code. If a status handler function was called + * and it did not return with \c IGRAPH_SUCCESS, then + * \c IGRAPH_INTERRUPTED is returned by \c igraph_status(). + * + * Time complexity: O(1). + */ + +int igraph_status(const char *message, void *data) { + if (igraph_i_status_handler) { + if (igraph_i_status_handler(message, data) != IGRAPH_SUCCESS) { + return IGRAPH_INTERRUPTED; + } + } + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_statusf + * Report status, more flexible printf-like version. + * + * This is the more flexible version of \ref igraph_status(), + * that has a syntax similar to the \c printf standard C library function. + * It substitutes the values of the additional arguments into the + * \p message template string and calls \ref igraph_status(). + * \param message Status message template string, the syntax is the same + * as for the \c printf function. + * \param data Additional context, with user-defined semantics. + * Existing igraph functions pass a null pointer here. + * \param ... The additional arguments to fill the template given in the + * \p message argument. + * \return Error code. If a status handler function was called + * and it did not return with \c IGRAPH_SUCCESS, then + * \c IGRAPH_INTERRUPTED is returned by \c igraph_status(). + */ + +int igraph_statusf(const char *message, void *data, ...) { + char buffer[300]; + va_list ap; + va_start(ap, data); + vsnprintf(buffer, sizeof(buffer) - 1, message, ap); + return igraph_status(buffer, data); +} + +#ifndef USING_R + +/** + * \function igraph_status_handler_stderr + * A simple predefined status handler function. + * + * A simple status handler function, that writes the status + * message to the standard errror. + * \param message The status message. + * \param data Additional context, with user-defined semantics. + * Existing igraph functions pass a null pointer here. + * \return Error code. + * + * Time complexity: O(1). + */ + +int igraph_status_handler_stderr(const char *message, void *data) { + IGRAPH_UNUSED(data); + fputs(message, stderr); + return 0; +} +#endif + +/** + * \function igraph_set_status_handler + * Install of uninstall a status handler function. + * + * To uninstall the currently installed status handler, call + * this function with a null pointer. + * \param new_handler The status handler function to install. + * \return The previously installed status handler function. + * + * Time complexity: O(1). + */ + +igraph_status_handler_t * +igraph_set_status_handler(igraph_status_handler_t new_handler) { + igraph_status_handler_t *previous_handler = igraph_i_status_handler; + igraph_i_status_handler = new_handler; + return previous_handler; +} + diff --git a/src/structural_properties.c b/src/structural_properties.c new file mode 100644 index 0000000..45504a8 --- /dev/null +++ b/src/structural_properties.c @@ -0,0 +1,7257 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_structural.h" +#include "igraph_transitivity.h" +#include "igraph_paths.h" +#include "igraph_math.h" +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_progress.h" +#include "igraph_interrupt_internal.h" +#include "igraph_centrality.h" +#include "igraph_components.h" +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_types_internal.h" +#include "igraph_dqueue.h" +#include "igraph_attributes.h" +#include "igraph_neighborhood.h" +#include "igraph_topology.h" +#include "igraph_qsort.h" +#include "config.h" +#include "structural_properties_internal.h" + +#include +#include +#include + +/** + * \section about_structural + * + * These functions usually calculate some structural property + * of a graph, like its diameter, the degree of the nodes, etc. + */ + +/** + * \ingroup structural + * \function igraph_diameter + * \brief Calculates the diameter of a graph (longest geodesic). + * + * \param graph The graph object. + * \param pres Pointer to an integer, if not \c NULL then it will contain + * the diameter (the actual distance). + * \param pfrom Pointer to an integer, if not \c NULL it will be set to the + * source vertex of the diameter path. + * \param pto Pointer to an integer, if not \c NULL it will be set to the + * target vertex of the diameter path. + * \param path Pointer to an initialized vector. If not \c NULL the actual + * longest geodesic path will be stored here. The vector will be + * resized as needed. + * \param directed Boolean, whether to consider directed + * paths. Ignored for undirected graphs. + * \param unconn What to do if the graph is not connected. If + * \c TRUE the longest geodesic within a component + * will be returned, otherwise the number of vertices is + * returned. (The rationale behind the latter is that this is + * always longer than the longest possible diameter in a + * graph.) + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * + * Time complexity: O(|V||E|), the + * number of vertices times the number of edges. + * + * \example examples/simple/igraph_diameter.c + */ + +int igraph_diameter(const igraph_t *graph, igraph_integer_t *pres, + igraph_integer_t *pfrom, igraph_integer_t *pto, + igraph_vector_t *path, + igraph_bool_t directed, igraph_bool_t unconn) { + + long int no_of_nodes = igraph_vcount(graph); + long int i, j, n; + long int *already_added; + long int nodes_reached; + long int from = 0, to = 0; + long int res = 0; + + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + igraph_vector_int_t *neis; + igraph_neimode_t dirmode; + igraph_adjlist_t allneis; + + if (directed) { + dirmode = IGRAPH_OUT; + } else { + dirmode = IGRAPH_ALL; + } + already_added = igraph_Calloc(no_of_nodes, long int); + if (already_added == 0) { + IGRAPH_ERROR("diameter failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, already_added); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, dirmode)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + for (i = 0; i < no_of_nodes; i++) { + nodes_reached = 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, i)); + IGRAPH_CHECK(igraph_dqueue_push(&q, 0)); + already_added[i] = i + 1; + + IGRAPH_PROGRESS("Diameter: ", 100.0 * i / no_of_nodes, NULL); + + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + long int actdist = (long int) igraph_dqueue_pop(&q); + if (actdist > res) { + res = actdist; + from = i; + to = actnode; + } + + neis = igraph_adjlist_get(&allneis, actnode); + n = igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + long int neighbor = (long int) VECTOR(*neis)[j]; + if (already_added[neighbor] == i + 1) { + continue; + } + already_added[neighbor] = i + 1; + nodes_reached++; + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1)); + } + } /* while !igraph_dqueue_empty */ + + /* not connected, return largest possible */ + if (nodes_reached != no_of_nodes && !unconn) { + res = no_of_nodes; + from = -1; + to = -1; + break; + } + } /* for i 0) { + *res /= normfact; + } else { + *res = IGRAPH_NAN; + } + + /* clean */ + igraph_Free(already_added); + igraph_dqueue_destroy(&q); + igraph_adjlist_destroy(&allneis); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_path_length_hist + * Create a histogram of all shortest path lengths. + * + * This function calculates a histogram, by calculating the + * shortest path length between each pair of vertices. For directed + * graphs both directions might be considered and then every pair of vertices + * appears twice in the histogram. + * \param graph The input graph. + * \param res Pointer to an initialized vector, the result is stored + * here. The first (i.e. zeroth) element contains the number of + * shortest paths of length 1, etc. The supplied vector is resized + * as needed. + * \param unconnected Pointer to a real number, the number of + * pairs for which the second vertex is not reachable from the + * first is stored here. + * \param directed Whether to consider directed paths in a directed + * graph (if not zero). This argument is ignored for undirected + * graphs. + * \return Error code. + * + * Time complexity: O(|V||E|), the number of vertices times the number + * of edges. + * + * \sa \ref igraph_average_path_length() and \ref igraph_shortest_paths() + */ + +int igraph_path_length_hist(const igraph_t *graph, igraph_vector_t *res, + igraph_real_t *unconnected, igraph_bool_t directed) { + + long int no_of_nodes = igraph_vcount(graph); + long int i, j, n; + igraph_vector_long_t already_added; + long int nodes_reached; + + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + igraph_vector_int_t *neis; + igraph_neimode_t dirmode; + igraph_adjlist_t allneis; + igraph_real_t unconn = 0; + long int ressize; + + if (directed) { + dirmode = IGRAPH_OUT; + } else { + dirmode = IGRAPH_ALL; + } + + IGRAPH_CHECK(igraph_vector_long_init(&already_added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &already_added); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, dirmode)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + IGRAPH_CHECK(igraph_vector_resize(res, 0)); + ressize = 0; + + for (i = 0; i < no_of_nodes; i++) { + nodes_reached = 1; /* itself */ + IGRAPH_CHECK(igraph_dqueue_push(&q, i)); + IGRAPH_CHECK(igraph_dqueue_push(&q, 0)); + VECTOR(already_added)[i] = i + 1; + + IGRAPH_PROGRESS("Path-hist: ", 100.0 * i / no_of_nodes, NULL); + + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + long int actdist = (long int) igraph_dqueue_pop(&q); + + neis = igraph_adjlist_get(&allneis, actnode); + n = igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + long int neighbor = (long int) VECTOR(*neis)[j]; + if (VECTOR(already_added)[neighbor] == i + 1) { + continue; + } + VECTOR(already_added)[neighbor] = i + 1; + nodes_reached++; + if (actdist + 1 > ressize) { + IGRAPH_CHECK(igraph_vector_resize(res, actdist + 1)); + for (; ressize < actdist + 1; ressize++) { + VECTOR(*res)[ressize] = 0; + } + } + VECTOR(*res)[actdist] += 1; + + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1)); + } + } /* while !igraph_dqueue_empty */ + + unconn += (no_of_nodes - nodes_reached); + + } /* for i + * If there is more than one geodesic between two vertices, this + * function gives only one of them. + * \param graph The graph object. + * \param vertices The result, the ids of the vertices along the paths. + * This is a pointer vector, each element points to a vector + * object. These should be initialized before passing them to + * the function, which will properly clear and/or resize them + * and fill the ids of the vertices along the geodesics from/to + * the vertices. Supply a null pointer here if you don't need + * these vectors. + * \param edges The result, the ids of the edges along the paths. + * This is a pointer vector, each element points to a vector + * object. These should be initialized before passing them to + * the function, which will properly clear and/or resize them + * and fill the ids of the vertices along the geodesics from/to + * the vertices. Supply a null pointer here if you don't need + * these vectors. + * \param from The id of the vertex from/to which the geodesics are + * calculated. + * \param to Vertex sequence with the ids of the vertices to/from which the + * shortest paths will be calculated. A vertex might be given multiple + * times. + * \param mode The type of shortest paths to be used for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing paths are calculated. + * \cli IGRAPH_IN + * the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param predecessors A pointer to an initialized igraph vector or null. + * If not null, a vector containing the predecessor of each vertex in + * the single source shortest path tree is returned here. The + * predecessor of vertex i in the tree is the vertex from which vertex i + * was reached. The predecessor of the start vertex (in the \c from + * argument) is itself by definition. If the predecessor is -1, it means + * that the given vertex was not reached from the source during the + * search. Note that the search terminates if all the vertices in + * \c to are reached. + * \param inbound_edges A pointer to an initialized igraph vector or null. + * If not null, a vector containing the inbound edge of each vertex in + * the single source shortest path tree is returned here. The + * inbound edge of vertex i in the tree is the edge via which vertex i + * was reached. The start vertex and vertices that were not reached + * during the search will have -1 in the corresponding entry of the + * vector. Note that the search terminates if all the vertices in + * \c to are reached. + * + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p from is invalid vertex id, or the length of \p to is + * not the same as the length of \p res. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|V|+|E|), + * |V| is the number of vertices, + * |E| the number of edges in the + * graph. + * + * \sa \ref igraph_shortest_paths() if you only need the path length but + * not the paths themselves. + * + * \example examples/simple/igraph_get_shortest_paths.c + */ + + +int igraph_get_shortest_paths(const igraph_t *graph, + igraph_vector_ptr_t *vertices, + igraph_vector_ptr_t *edges, + igraph_integer_t from, const igraph_vs_t to, + igraph_neimode_t mode, + igraph_vector_long_t *predecessors, + igraph_vector_long_t *inbound_edges) { + + /* TODO: use inclist_t if to is long (longer than 1?) */ + + long int no_of_nodes = igraph_vcount(graph); + long int *father; + + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + + long int i, j; + igraph_vector_t tmp = IGRAPH_VECTOR_NULL; + + igraph_vit_t vit; + + long int to_reach; + long int reached = 0; + + if (from < 0 || from >= no_of_nodes) { + IGRAPH_ERROR("cannot get shortest paths", IGRAPH_EINVVID); + } + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument", IGRAPH_EINVMODE); + } + + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + if (vertices && IGRAPH_VIT_SIZE(vit) != igraph_vector_ptr_size(vertices)) { + IGRAPH_ERROR("Size of the `vertices' and the `to' should match", IGRAPH_EINVAL); + } + if (edges && IGRAPH_VIT_SIZE(vit) != igraph_vector_ptr_size(edges)) { + IGRAPH_ERROR("Size of the `edges' and the `to' should match", IGRAPH_EINVAL); + } + + father = igraph_Calloc(no_of_nodes, long int); + if (father == 0) { + IGRAPH_ERROR("cannot get shortest paths", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, father); + IGRAPH_VECTOR_INIT_FINALLY(&tmp, 0); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + + /* Mark the vertices we need to reach */ + to_reach = IGRAPH_VIT_SIZE(vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + if (father[ (long int) IGRAPH_VIT_GET(vit) ] == 0) { + father[ (long int) IGRAPH_VIT_GET(vit) ] = -1; + } else { + to_reach--; /* this node was given multiple times */ + } + } + + /* Meaning of father[i]: + * + * - If father[i] < 0, it means that vertex i has to be reached and has not + * been reached yet. + * + * - If father[i] = 0, it means that vertex i does not have to be reached and + * it has not been reached yet. + * + * - If father[i] = 1, it means that vertex i is the start vertex. + * + * - Otherwise, father[i] is the ID of the edge from which vertex i was + * reached plus 2. + */ + + IGRAPH_CHECK(igraph_dqueue_push(&q, from + 1)); + if (father[ (long int) from ] < 0) { + reached++; + } + father[ (long int)from ] = 1; + + while (!igraph_dqueue_empty(&q) && reached < to_reach) { + long int act = (long int) igraph_dqueue_pop(&q) - 1; + + IGRAPH_CHECK(igraph_incident(graph, &tmp, (igraph_integer_t) act, mode)); + for (j = 0; j < igraph_vector_size(&tmp); j++) { + long int edge = (long int) VECTOR(tmp)[j]; + long int neighbor = IGRAPH_OTHER(graph, edge, act); + if (father[neighbor] > 0) { + continue; + } else if (father[neighbor] < 0) { + reached++; + } + father[neighbor] = edge + 2; + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor + 1)); + } + } + + if (reached < to_reach) { + IGRAPH_WARNING("Couldn't reach some vertices"); + } + + /* Create `predecessors' if needed */ + if (predecessors) { + IGRAPH_CHECK(igraph_vector_long_resize(predecessors, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + if (father[i] <= 0) { + /* i was not reached */ + VECTOR(*predecessors)[i] = -1; + } else if (father[i] == 1) { + /* i is the start vertex */ + VECTOR(*predecessors)[i] = i; + } else { + /* i was reached via the edge with ID = father[i] - 2 */ + VECTOR(*predecessors)[i] = IGRAPH_OTHER(graph, father[i] - 2, i); + } + } + } + + /* Create `inbound_edges' if needed */ + if (inbound_edges) { + IGRAPH_CHECK(igraph_vector_long_resize(inbound_edges, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + if (father[i] <= 1) { + /* i was not reached or i is the start vertex */ + VECTOR(*inbound_edges)[i] = -1; + } else { + /* i was reached via the edge with ID = father[i] - 2 */ + VECTOR(*inbound_edges)[i] = father[i] - 2; + } + } + } + + /* Create `vertices' and `edges' if needed */ + if (vertices || edges) { + for (IGRAPH_VIT_RESET(vit), j = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), j++) { + long int node = IGRAPH_VIT_GET(vit); + igraph_vector_t *vvec = 0, *evec = 0; + if (vertices) { + vvec = VECTOR(*vertices)[j]; + igraph_vector_clear(vvec); + } + if (edges) { + evec = VECTOR(*edges)[j]; + igraph_vector_clear(evec); + } + + IGRAPH_ALLOW_INTERRUPTION(); + + if (father[node] > 0) { + long int act = node; + long int size = 0; + long int edge; + while (father[act] > 1) { + size++; + edge = father[act] - 2; + act = IGRAPH_OTHER(graph, edge, act); + } + if (vvec) { + IGRAPH_CHECK(igraph_vector_resize(vvec, size + 1)); + VECTOR(*vvec)[size] = node; + } + if (evec) { + IGRAPH_CHECK(igraph_vector_resize(evec, size)); + } + act = node; + while (father[act] > 1) { + size--; + edge = father[act] - 2; + act = IGRAPH_OTHER(graph, edge, act); + if (vvec) { + VECTOR(*vvec)[size] = act; + } + if (evec) { + VECTOR(*evec)[size] = edge; + } + } + } + } + } + + /* Clean */ + igraph_Free(father); + igraph_dqueue_destroy(&q); + igraph_vector_destroy(&tmp); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +/** + * \function igraph_get_shortest_path + * Shortest path from one vertex to another one. + * + * Calculates and returns a single unweighted shortest path from a + * given vertex to another one. If there are more than one shortest + * paths between the two vertices, then an arbitrary one is returned. + * + * This function is a wrapper to \ref + * igraph_get_shortest_paths(), for the special case when only one + * target vertex is considered. + * \param graph The input graph, it can be directed or + * undirected. Directed paths are considered in directed + * graphs. + * \param vertices Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the vertex ids along + * the path are stored here, including the source and target + * vertices. + * \param edges Pointer to an uninitialized vector or a null + * pointer. If not a null pointer, then the edge ids along the + * path are stored here. + * \param from The id of the source vertex. + * \param to The id of the target vertex. + * \param mode A constant specifying how edge directions are + * considered in directed graphs. Valid modes are: + * \c IGRAPH_OUT, follows edge directions; + * \c IGRAPH_IN, follows the opposite directions; and + * \c IGRAPH_ALL, ignores edge directions. This argument is + * ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges in the graph. + * + * \sa \ref igraph_get_shortest_paths() for the version with more target + * vertices. + */ + +int igraph_get_shortest_path(const igraph_t *graph, + igraph_vector_t *vertices, + igraph_vector_t *edges, + igraph_integer_t from, + igraph_integer_t to, + igraph_neimode_t mode) { + + igraph_vector_ptr_t vertices2, *vp = &vertices2; + igraph_vector_ptr_t edges2, *ep = &edges2; + + if (vertices) { + IGRAPH_CHECK(igraph_vector_ptr_init(&vertices2, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &vertices2); + VECTOR(vertices2)[0] = vertices; + } else { + vp = 0; + } + if (edges) { + IGRAPH_CHECK(igraph_vector_ptr_init(&edges2, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &edges2); + VECTOR(edges2)[0] = edges; + } else { + ep = 0; + } + + IGRAPH_CHECK(igraph_get_shortest_paths(graph, vp, ep, from, + igraph_vss_1(to), mode, 0, 0)); + + if (edges) { + igraph_vector_ptr_destroy(&edges2); + IGRAPH_FINALLY_CLEAN(1); + } + if (vertices) { + igraph_vector_ptr_destroy(&vertices2); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +void igraph_i_gasp_paths_destroy(igraph_vector_ptr_t *v); + +void igraph_i_gasp_paths_destroy(igraph_vector_ptr_t *v) { + long int i; + for (i = 0; i < igraph_vector_ptr_size(v); i++) { + if (VECTOR(*v)[i] != 0) { + igraph_vector_destroy(VECTOR(*v)[i]); + igraph_Free(VECTOR(*v)[i]); + } + } + igraph_vector_ptr_destroy(v); +} + +/** + * \function igraph_get_all_shortest_paths + * \brief Finds all shortest paths (geodesics) from a vertex to all other vertices. + * + * \param graph The graph object. + * \param res Pointer to an initialized pointer vector, the result + * will be stored here in igraph_vector_t objects. Each vector + * object contains the vertices along a shortest path from \p from + * to another vertex. The vectors are ordered according to their + * target vertex: first the shortest paths to vertex 0, then to + * vertex 1, etc. No data is included for unreachable vertices. + * \param nrgeo Pointer to an initialized igraph_vector_t object or + * NULL. If not NULL the number of shortest paths from \p from are + * stored here for every vertex in the graph. Note that the values + * will be accurate only for those vertices that are in the target + * vertex sequence (see \p to), since the search terminates as soon + * as all the target vertices have been found. + * \param from The id of the vertex from/to which the geodesics are + * calculated. + * \param to Vertex sequence with the ids of the vertices to/from which the + * shortest paths will be calculated. A vertex might be given multiple + * times. + * \param mode The type of shortest paths to be use for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the lengths of the outgoing paths are calculated. + * \cli IGRAPH_IN + * the lengths of the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p from is invalid vertex id. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Added in version 0.2. + * + * Time complexity: O(|V|+|E|) for most graphs, O(|V|^2) in the worst + * case. + */ + +int igraph_get_all_shortest_paths(const igraph_t *graph, + igraph_vector_ptr_t *res, + igraph_vector_t *nrgeo, + igraph_integer_t from, const igraph_vs_t to, + igraph_neimode_t mode) { + + long int no_of_nodes = igraph_vcount(graph); + long int *geodist; + igraph_vector_ptr_t paths; + igraph_dqueue_t q; + igraph_vector_t *vptr; + igraph_vector_t neis; + igraph_vector_t ptrlist; + igraph_vector_t ptrhead; + long int n, j, i; + long int to_reach, reached = 0, maxdist = 0; + + igraph_vit_t vit; + + if (from < 0 || from >= no_of_nodes) { + IGRAPH_ERROR("cannot get shortest paths", IGRAPH_EINVVID); + } + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument", IGRAPH_EINVMODE); + } + + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + /* paths will store the shortest paths during the search */ + IGRAPH_CHECK(igraph_vector_ptr_init(&paths, 0)); + IGRAPH_FINALLY(igraph_i_gasp_paths_destroy, &paths); + /* neis is a temporary vector holding the neighbors of the + * node being examined */ + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + /* ptrlist stores indices into the paths vector, in the order + * of how they were found. ptrhead is a second-level index that + * will be used to find paths that terminate in a given vertex */ + IGRAPH_VECTOR_INIT_FINALLY(&ptrlist, 0); + /* ptrhead contains indices into ptrlist. + * ptrhead[i] = j means that element #j-1 in ptrlist contains + * the shortest path from the root to node i. ptrhead[i] = 0 + * means that node i was not reached so far */ + IGRAPH_VECTOR_INIT_FINALLY(&ptrhead, no_of_nodes); + /* geodist[i] == 0 if i was not reached yet and it is not in the + * target vertex sequence, or -1 if i was not reached yet and it + * is in the target vertex sequence. Otherwise it is + * one larger than the length of the shortest path from the + * source */ + geodist = igraph_Calloc(no_of_nodes, long int); + if (geodist == 0) { + IGRAPH_ERROR("Cannot calculate shortest paths", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, geodist); + /* dequeue to store the BFS queue -- odd elements are the vertex indices, + * even elements are the distances from the root */ + IGRAPH_CHECK(igraph_dqueue_init(&q, 100)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &q); + + if (nrgeo) { + IGRAPH_CHECK(igraph_vector_resize(nrgeo, no_of_nodes)); + igraph_vector_null(nrgeo); + } + + /* use geodist to count how many vertices we have to reach */ + to_reach = IGRAPH_VIT_SIZE(vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + if (geodist[ (long int) IGRAPH_VIT_GET(vit) ] == 0) { + geodist[ (long int) IGRAPH_VIT_GET(vit) ] = -1; + } else { + to_reach--; /* this node was given multiple times */ + } + } + + if (geodist[ (long int) from ] < 0) { + reached++; + } + + /* from -> from */ + vptr = igraph_Calloc(1, igraph_vector_t); /* TODO: dirty */ + IGRAPH_CHECK(igraph_vector_ptr_push_back(&paths, vptr)); + IGRAPH_CHECK(igraph_vector_init(vptr, 1)); + VECTOR(*vptr)[0] = from; + geodist[(long int)from] = 1; + VECTOR(ptrhead)[(long int)from] = 1; + IGRAPH_CHECK(igraph_vector_push_back(&ptrlist, 0)); + if (nrgeo) { + VECTOR(*nrgeo)[(long int)from] = 1; + } + + /* Init queue */ + IGRAPH_CHECK(igraph_dqueue_push(&q, from)); + IGRAPH_CHECK(igraph_dqueue_push(&q, 0.0)); + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + long int actdist = (long int) igraph_dqueue_pop(&q); + + IGRAPH_ALLOW_INTERRUPTION(); + + if (reached >= to_reach) { + /* all nodes were reached. Since we need all the shortest paths + * to all these nodes, we can stop the search only if the distance + * of the current node to the root is larger than the distance of + * any of the nodes we wanted to reach */ + if (actdist > maxdist) { + /* safety check, maxdist should have been set when we reached the last node */ + if (maxdist < 0) { + IGRAPH_ERROR("possible bug in igraph_get_all_shortest_paths, " + "maxdist is negative", IGRAPH_EINVAL); + } + break; + } + } + + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) actnode, + mode)); + n = igraph_vector_size(&neis); + for (j = 0; j < n; j++) { + long int neighbor = (long int) VECTOR(neis)[j]; + long int fatherptr; + + if (geodist[neighbor] > 0 && + geodist[neighbor] - 1 < actdist + 1) { + /* this node was reached via a shorter path before */ + continue; + } + + /* yay, found another shortest path to neighbor */ + + if (nrgeo) { + /* the number of geodesics leading to neighbor must be + * increased by the number of geodesics leading to actnode */ + VECTOR(*nrgeo)[neighbor] += VECTOR(*nrgeo)[actnode]; + } + if (geodist[neighbor] <= 0) { + /* this node was not reached yet, push it into the queue */ + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1)); + if (geodist[neighbor] < 0) { + reached++; + } + if (reached == to_reach) { + maxdist = actdist; + } + } + geodist[neighbor] = actdist + 2; + + /* copy all existing paths to the parent */ + fatherptr = (long int) VECTOR(ptrhead)[actnode]; + while (fatherptr != 0) { + /* allocate a new igraph_vector_t at the end of paths */ + vptr = igraph_Calloc(1, igraph_vector_t); + IGRAPH_CHECK(igraph_vector_ptr_push_back(&paths, vptr)); + IGRAPH_CHECK(igraph_vector_copy(vptr, VECTOR(paths)[fatherptr - 1])); + IGRAPH_CHECK(igraph_vector_reserve(vptr, actdist + 2)); + IGRAPH_CHECK(igraph_vector_push_back(vptr, neighbor)); + + IGRAPH_CHECK(igraph_vector_push_back(&ptrlist, + VECTOR(ptrhead)[neighbor])); + VECTOR(ptrhead)[neighbor] = igraph_vector_size(&ptrlist); + + fatherptr = (long int) VECTOR(ptrlist)[fatherptr - 1]; + } + } + } + + igraph_dqueue_destroy(&q); + IGRAPH_FINALLY_CLEAN(1); + + /* mark the nodes for which we need the result */ + memset(geodist, 0, sizeof(long int) * (size_t) no_of_nodes); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + geodist[ (long int) IGRAPH_VIT_GET(vit) ] = 1; + } + + /* count the number of paths in the result */ + n = 0; + for (i = 0; i < no_of_nodes; i++) { + long int fatherptr = (long int) VECTOR(ptrhead)[i]; + if (geodist[i] > 0) { + while (fatherptr != 0) { + n++; + fatherptr = (long int) VECTOR(ptrlist)[fatherptr - 1]; + } + } + } + + IGRAPH_CHECK(igraph_vector_ptr_resize(res, n)); + j = 0; + for (i = 0; i < no_of_nodes; i++) { + long int fatherptr = (long int) VECTOR(ptrhead)[i]; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* do we need the paths leading to vertex i? */ + if (geodist[i] > 0) { + /* yes, copy them to the result vector */ + while (fatherptr != 0) { + VECTOR(*res)[j++] = VECTOR(paths)[fatherptr - 1]; + fatherptr = (long int) VECTOR(ptrlist)[fatherptr - 1]; + } + } else { + /* no, free them */ + while (fatherptr != 0) { + igraph_vector_destroy(VECTOR(paths)[fatherptr - 1]); + igraph_Free(VECTOR(paths)[fatherptr - 1]); + fatherptr = (long int) VECTOR(ptrlist)[fatherptr - 1]; + } + } + } + + igraph_Free(geodist); + igraph_vector_destroy(&ptrlist); + igraph_vector_destroy(&ptrhead); + igraph_vector_destroy(&neis); + igraph_vector_ptr_destroy(&paths); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(6); + + return 0; +} + + +/** + * \ingroup structural + * \function igraph_subcomponent + * \brief The vertices in the same component as a given vertex. + * + * \param graph The graph object. + * \param res The result, vector with the ids of the vertices in the + * same component. + * \param vertex The id of the vertex of which the component is + * searched. + * \param mode Type of the component for directed graphs, possible + * values: + * \clist + * \cli IGRAPH_OUT + * the set of vertices reachable \em from the + * \p vertex, + * \cli IGRAPH_IN + * the set of vertices from which the + * \p vertex is reachable. + * \cli IGRAPH_ALL + * the graph is considered as an + * undirected graph. Note that this is \em not the same + * as the union of the previous two. + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p vertex is an invalid vertex id + * \cli IGRAPH_EINVMODE + * invalid mode argument passed. + * \endclist + * + * Time complexity: O(|V|+|E|), + * |V| and + * |E| are the number of vertices and + * edges in the graph. + * + * \sa \ref igraph_subgraph() if you want a graph object consisting only + * a given set of vertices and the edges between them. + */ + +int igraph_subcomponent(const igraph_t *graph, igraph_vector_t *res, igraph_real_t vertex, + igraph_neimode_t mode) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_dqueue_t q = IGRAPH_DQUEUE_NULL; + char *already_added; + long int i; + igraph_vector_t tmp = IGRAPH_VECTOR_NULL; + + if (!IGRAPH_FINITE(vertex) || vertex < 0 || vertex >= no_of_nodes) { + IGRAPH_ERROR("subcomponent failed", IGRAPH_EINVVID); + } + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("invalid mode argument", IGRAPH_EINVMODE); + } + + already_added = igraph_Calloc(no_of_nodes, char); + if (already_added == 0) { + IGRAPH_ERROR("subcomponent failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(free, already_added); /* TODO: hack */ + + igraph_vector_clear(res); + + IGRAPH_VECTOR_INIT_FINALLY(&tmp, 0); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + + IGRAPH_CHECK(igraph_dqueue_push(&q, vertex)); + IGRAPH_CHECK(igraph_vector_push_back(res, vertex)); + already_added[(long int)vertex] = 1; + + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + + IGRAPH_ALLOW_INTERRUPTION(); + + IGRAPH_CHECK(igraph_neighbors(graph, &tmp, (igraph_integer_t) actnode, + mode)); + for (i = 0; i < igraph_vector_size(&tmp); i++) { + long int neighbor = (long int) VECTOR(tmp)[i]; + + if (already_added[neighbor]) { + continue; + } + already_added[neighbor] = 1; + IGRAPH_CHECK(igraph_vector_push_back(res, neighbor)); + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + } + } + + igraph_dqueue_destroy(&q); + igraph_vector_destroy(&tmp); + igraph_Free(already_added); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \ingroup structural + * \function igraph_pagerank_old + * \brief Calculates the Google PageRank for the specified vertices. + * + * This is an old implementation, + * it is provided for compatibility with igraph versions earlier than + * 0.5. Please use the new implementation \ref igraph_pagerank() in + * new projects. + * + * + * From version 0.7 this function is deprecated and its use gives a + * warning message. + * + * + * Please note that the PageRank of a given vertex depends on the PageRank + * of all other vertices, so even if you want to calculate the PageRank for + * only some of the vertices, all of them must be calculated. Requesting + * the PageRank for only some of the vertices does not result in any + * performance increase at all. + * + * + * Since the calculation is an iterative + * process, the algorithm is stopped after a given count of iterations + * or if the PageRank value differences between iterations are less than + * a predefined value. + * + * + * + * For the explanation of the PageRank algorithm, see the following + * webpage: + * http://infolab.stanford.edu/~backrub/google.html , or the + * following reference: + * + * + * + * Sergey Brin and Larry Page: The Anatomy of a Large-Scale Hypertextual + * Web Search Engine. Proceedings of the 7th World-Wide Web Conference, + * Brisbane, Australia, April 1998. + * + * + * \param graph The graph object. + * \param res The result vector containing the PageRank values for the + * given nodes. + * \param vids Vector with the vertex ids + * \param directed Logical, if true directed paths will be considered + * for directed graphs. It is ignored for undirected graphs. + * \param niter The maximum number of iterations to perform + * \param eps The algorithm will consider the calculation as complete + * if the difference of PageRank values between iterations change + * less than this value for every node + * \param damping The damping factor ("d" in the original paper) + * \param old Boolean, whether to use the pre-igraph 0.5 way to + * calculate page rank. Not recommended for new applications, + * only included for compatibility. If this is non-zero then the damping + * factor is not divided by the number of vertices before adding it + * to the weighted page rank scores to calculate the + * new scores. I.e. the formula in the original PageRank paper + * is used. Furthermore, if this is non-zero then the PageRank + * vector is renormalized after each iteration. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex id in + * \p vids. + * + * Time complexity: O(|V|+|E|) per iteration. A handful iterations + * should be enough. Note that if the old-style dumping is used then + * the iteration might not converge at all. + * + * \sa \ref igraph_pagerank() for the new implementation. + */ + +int igraph_pagerank_old(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_bool_t directed, + igraph_integer_t niter, igraph_real_t eps, + igraph_real_t damping, igraph_bool_t old) { + long int no_of_nodes = igraph_vcount(graph); + long int i, j, n, nodes_to_calc; + igraph_real_t *prvec, *prvec_new, *prvec_aux, *prvec_scaled; + igraph_vector_int_t *neis; + igraph_vector_t outdegree; + igraph_neimode_t dirmode; + igraph_adjlist_t allneis; + igraph_real_t maxdiff = eps; + igraph_vit_t vit; + + IGRAPH_WARNING("igraph_pagerank_old is deprecated from igraph 0.7, " + "use igraph_pagerank instead"); + + if (niter <= 0) { + IGRAPH_ERROR("Invalid iteration count", IGRAPH_EINVAL); + } + if (eps <= 0) { + IGRAPH_ERROR("Invalid epsilon value", IGRAPH_EINVAL); + } + if (damping <= 0 || damping >= 1) { + IGRAPH_ERROR("Invalid damping factor", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + igraph_vector_null(res); + + IGRAPH_VECTOR_INIT_FINALLY(&outdegree, no_of_nodes); + + prvec = igraph_Calloc(no_of_nodes, igraph_real_t); + if (prvec == 0) { + IGRAPH_ERROR("pagerank failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, prvec); + + prvec_new = igraph_Calloc(no_of_nodes, igraph_real_t); + if (prvec_new == 0) { + IGRAPH_ERROR("pagerank failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, prvec_new); + + prvec_scaled = igraph_Calloc(no_of_nodes, igraph_real_t); + if (prvec_scaled == 0) { + IGRAPH_ERROR("pagerank failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, prvec_scaled); + + if (directed) { + dirmode = IGRAPH_IN; + } else { + dirmode = IGRAPH_ALL; + } + igraph_adjlist_init(graph, &allneis, dirmode); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + + /* Calculate outdegrees for every node */ + igraph_degree(graph, &outdegree, igraph_vss_all(), + directed ? IGRAPH_OUT : IGRAPH_ALL, 0); + /* Initialize PageRank values */ + for (i = 0; i < no_of_nodes; i++) { + prvec[i] = 1 - damping; + /* The next line is necessary to avoid division by zero in the + * calculation of prvec_scaled. This won't cause any problem, + * since if a node doesn't have any outgoing links, its + * prvec_scaled value won't be used anywhere */ + if (VECTOR(outdegree)[i] == 0) { + VECTOR(outdegree)[i] = 1; + } + } + + /* We will always calculate the new PageRank values into prvec_new + * based on the existing values from prvec. To avoid unnecessary + * copying from prvec_new to prvec at the end of every iteration, + * the pointers are swapped after every iteration */ + while (niter > 0 && maxdiff >= eps) { + igraph_real_t sumfrom = 0, sum = 0; + niter--; + maxdiff = 0; + + /* Calculate the quotient of the actual PageRank value and the + * outdegree for every node */ + sumfrom = 0.0; sum = 0.0; + for (i = 0; i < no_of_nodes; i++) { + sumfrom += prvec[i]; + prvec_scaled[i] = prvec[i] / VECTOR(outdegree)[i]; + } + + /* Calculate new PageRank values based on the old ones */ + for (i = 0; i < no_of_nodes; i++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + prvec_new[i] = 0; + neis = igraph_adjlist_get(&allneis, i); + n = igraph_vector_int_size(neis); + for (j = 0; j < n; j++) { + long int neighbor = (long int) VECTOR(*neis)[j]; + prvec_new[i] += prvec_scaled[neighbor]; + } + prvec_new[i] *= damping; + if (!old) { + prvec_new[i] += (1 - damping) / no_of_nodes; + } else { + prvec_new[i] += (1 - damping); + } + sum += prvec_new[i]; + + } + for (i = 0; i < no_of_nodes; i++) { + if (!old) { + prvec_new[i] /= sum; + } + + if (prvec_new[i] - prvec[i] > maxdiff) { + maxdiff = prvec_new[i] - prvec[i]; + } else if (prvec[i] - prvec_new[i] > maxdiff) { + maxdiff = prvec[i] - prvec_new[i]; + } + } + + /* Swap the vectors */ + prvec_aux = prvec_new; + prvec_new = prvec; + prvec = prvec_aux; + } + + /* Copy results from prvec to res */ + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + long int vid = IGRAPH_VIT_GET(vit); + VECTOR(*res)[i] = prvec[vid]; + } + + igraph_adjlist_destroy(&allneis); + igraph_vit_destroy(&vit); + igraph_vector_destroy(&outdegree); + igraph_Free(prvec); + igraph_Free(prvec_new); + igraph_Free(prvec_scaled); + + IGRAPH_FINALLY_CLEAN(6); + + return 0; +} + +/* Not declared static so that the testsuite can use it, but not part of the public API. */ +int igraph_rewire_core(igraph_t *graph, igraph_integer_t n, igraph_rewiring_t mode, igraph_bool_t use_adjlist) { + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + char message[256]; + igraph_integer_t a, b, c, d, dummy, num_swaps, num_successful_swaps; + igraph_vector_t eids, edgevec, alledges; + igraph_bool_t directed, loops, ok; + igraph_es_t es; + igraph_adjlist_t al; + + if (no_of_nodes < 4) { + IGRAPH_ERROR("graph unsuitable for rewiring", IGRAPH_EINVAL); + } + + directed = igraph_is_directed(graph); + loops = (mode & IGRAPH_REWIRING_SIMPLE_LOOPS); + + RNG_BEGIN(); + + IGRAPH_VECTOR_INIT_FINALLY(&eids, 2); + + if (use_adjlist) { + /* As well as the sorted adjacency list, we maintain an unordered + * list of edges for picking a random edge in constant time. + */ + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + IGRAPH_VECTOR_INIT_FINALLY(&alledges, no_of_edges * 2); + igraph_get_edgelist(graph, &alledges, /*bycol=*/ 0); + } else { + IGRAPH_VECTOR_INIT_FINALLY(&edgevec, 4); + es = igraph_ess_vector(&eids); + } + + /* We don't want the algorithm to get stuck in an infinite loop when + * it can't choose two edges satisfying the conditions. Instead of + * this, we choose two arbitrary edges and if they have endpoints + * in common, we just decrease the number of trials left and continue + * (so unsuccessful rewirings still count as a trial) + */ + + num_swaps = num_successful_swaps = 0; + while (num_swaps < n) { + + IGRAPH_ALLOW_INTERRUPTION(); + if (num_swaps % 1000 == 0) { + snprintf(message, sizeof(message), + "Random rewiring (%.2f%% of the trials were successful)", + num_swaps > 0 ? ((100.0 * num_successful_swaps) / num_swaps) : 0.0); + IGRAPH_PROGRESS(message, (100.0 * num_swaps) / n, 0); + } + + switch (mode) { + case IGRAPH_REWIRING_SIMPLE: + case IGRAPH_REWIRING_SIMPLE_LOOPS: + ok = 1; + + /* Choose two edges randomly */ + VECTOR(eids)[0] = RNG_INTEGER(0, no_of_edges - 1); + do { + VECTOR(eids)[1] = RNG_INTEGER(0, no_of_edges - 1); + } while (VECTOR(eids)[0] == VECTOR(eids)[1]); + + /* Get the endpoints */ + if (use_adjlist) { + a = VECTOR(alledges)[((igraph_integer_t)VECTOR(eids)[0]) * 2]; + b = VECTOR(alledges)[(((igraph_integer_t)VECTOR(eids)[0]) * 2) + 1]; + c = VECTOR(alledges)[((igraph_integer_t)VECTOR(eids)[1]) * 2]; + d = VECTOR(alledges)[(((igraph_integer_t)VECTOR(eids)[1]) * 2) + 1]; + } else { + IGRAPH_CHECK(igraph_edge(graph, (igraph_integer_t) VECTOR(eids)[0], + &a, &b)); + IGRAPH_CHECK(igraph_edge(graph, (igraph_integer_t) VECTOR(eids)[1], + &c, &d)); + } + + /* For an undirected graph, we have two "variants" of each edge, i.e. + * a -- b and b -- a. Since some rewirings can be performed only when we + * "swap" the endpoints, we do it now with probability 0.5 */ + if (!directed && RNG_UNIF01() < 0.5) { + dummy = c; c = d; d = dummy; + if (use_adjlist) { + /* Flip the edge in the unordered edge-list, so the update later on + * hits the correct end. */ + VECTOR(alledges)[((igraph_integer_t)VECTOR(eids)[1]) * 2] = c; + VECTOR(alledges)[(((igraph_integer_t)VECTOR(eids)[1]) * 2) + 1] = d; + } + } + + /* If we do not touch loops, check whether a == b or c == d and disallow + * the swap if needed */ + if (!loops && (a == b || c == d)) { + ok = 0; + } else { + /* Check whether they are suitable for rewiring */ + if (a == c || b == d) { + /* Swapping would have no effect */ + ok = 0; + } else { + /* a != c && b != d */ + /* If a == d or b == c, the swap would generate at least one loop, so + * we disallow them unless we want to have loops */ + ok = loops || (a != d && b != c); + /* Also, if a == b and c == d and we allow loops, doing the swap + * would result in a multiple edge if the graph is undirected */ + ok = ok && (directed || a != b || c != d); + } + } + + /* All good so far. Now check for the existence of a --> d and c --> b to + * disallow the creation of multiple edges */ + if (ok) { + if (use_adjlist) { + if (igraph_adjlist_has_edge(&al, a, d, directed)) { + ok = 0; + } + } else { + IGRAPH_CHECK(igraph_are_connected(graph, a, d, &ok)); + ok = !ok; + } + } + if (ok) { + if (use_adjlist) { + if (igraph_adjlist_has_edge(&al, c, b, directed)) { + ok = 0; + } + } else { + IGRAPH_CHECK(igraph_are_connected(graph, c, b, &ok)); + ok = !ok; + } + } + + /* If we are still okay, we can perform the rewiring */ + if (ok) { + /* printf("Deleting: %ld -> %ld, %ld -> %ld\n", + (long)a, (long)b, (long)c, (long)d); */ + if (use_adjlist) { + // Replace entry in sorted adjlist: + IGRAPH_CHECK(igraph_adjlist_replace_edge(&al, a, b, d, directed)); + IGRAPH_CHECK(igraph_adjlist_replace_edge(&al, c, d, b, directed)); + // Also replace in unsorted edgelist: + VECTOR(alledges)[(((igraph_integer_t)VECTOR(eids)[0]) * 2) + 1] = d; + VECTOR(alledges)[(((igraph_integer_t)VECTOR(eids)[1]) * 2) + 1] = b; + } else { + IGRAPH_CHECK(igraph_delete_edges(graph, es)); + VECTOR(edgevec)[0] = a; VECTOR(edgevec)[1] = d; + VECTOR(edgevec)[2] = c; VECTOR(edgevec)[3] = b; + /* printf("Adding: %ld -> %ld, %ld -> %ld\n", + (long)a, (long)d, (long)c, (long)b); */ + igraph_add_edges(graph, &edgevec, 0); + } + num_successful_swaps++; + } + break; + default: + RNG_END(); + IGRAPH_ERROR("unknown rewiring mode", IGRAPH_EINVMODE); + } + num_swaps++; + } + + if (use_adjlist) { + /* Replace graph edges with the adjlist current state */ + IGRAPH_CHECK(igraph_delete_edges(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID))); + IGRAPH_CHECK(igraph_add_edges(graph, &alledges, 0)); + } + + IGRAPH_PROGRESS("Random rewiring: ", 100.0, 0); + + if (use_adjlist) { + igraph_vector_destroy(&alledges); + igraph_adjlist_destroy(&al); + } else { + igraph_vector_destroy(&edgevec); + } + + igraph_vector_destroy(&eids); + IGRAPH_FINALLY_CLEAN(use_adjlist ? 3 : 2); + + RNG_END(); + + return 0; +} + +/** + * \ingroup structural + * \function igraph_rewire + * \brief Randomly rewires a graph while preserving the degree distribution. + * + * + * This function generates a new graph based on the original one by randomly + * rewiring edges while preserving the original graph's degree distribution. + * Please note that the rewiring is done "in place", so no new graph will + * be allocated. If you would like to keep the original graph intact, use + * \ref igraph_copy() beforehand. + * + * \param graph The graph object to be rewired. + * \param n Number of rewiring trials to perform. + * \param mode The rewiring algorithm to be used. It can be one of the following flags: + * \clist + * \cli IGRAPH_REWIRING_SIMPLE + * Simple rewiring algorithm which chooses two arbitrary edges + * in each step (namely (a,b) and (c,d)) and substitutes them + * with (a,d) and (c,b) if they don't exist. The method will + * neither destroy nor create self-loops. + * \cli IGRAPH_REWIRING_SIMPLE_LOOPS + * Same as \c IGRAPH_REWIRING_SIMPLE but allows the creation or + * destruction of self-loops. + * \endclist + * + * \return Error code: + * \clist + * \cli IGRAPH_EINVMODE + * Invalid rewiring mode. + * \cli IGRAPH_EINVAL + * Graph unsuitable for rewiring (e.g. it has + * less than 4 nodes in case of \c IGRAPH_REWIRING_SIMPLE) + * \cli IGRAPH_ENOMEM + * Not enough memory for temporary data. + * \endclist + * + * Time complexity: TODO. + * + * \example examples/simple/igraph_rewire.c + */ + +#define REWIRE_ADJLIST_THRESHOLD 10 + +int igraph_rewire(igraph_t *graph, igraph_integer_t n, igraph_rewiring_t mode) { + + igraph_bool_t use_adjlist = n >= REWIRE_ADJLIST_THRESHOLD; + return igraph_rewire_core(graph, n, mode, use_adjlist); + +} + +/** + * Subgraph creation, old version: it copies the graph and then deletes + * unneeded vertices. + */ +int igraph_i_subgraph_copy_and_delete(const igraph_t *graph, igraph_t *res, + const igraph_vs_t vids, + igraph_vector_t *map, + igraph_vector_t *invmap) { + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t delete = IGRAPH_VECTOR_NULL; + char *remain; + long int i; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + IGRAPH_VECTOR_INIT_FINALLY(&delete, 0); + remain = igraph_Calloc(no_of_nodes, char); + if (remain == 0) { + IGRAPH_ERROR("subgraph failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(free, remain); /* TODO: hack */ + IGRAPH_CHECK(igraph_vector_reserve(&delete, no_of_nodes - IGRAPH_VIT_SIZE(vit))); + + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + remain[ (long int) IGRAPH_VIT_GET(vit) ] = 1; + } + + for (i = 0; i < no_of_nodes; i++) { + + IGRAPH_ALLOW_INTERRUPTION(); + + if (remain[i] == 0) { + IGRAPH_CHECK(igraph_vector_push_back(&delete, i)); + } + } + + igraph_Free(remain); + IGRAPH_FINALLY_CLEAN(1); + + /* must set res->attr to 0 before calling igraph_copy */ + res->attr = 0; /* Why is this needed? TODO */ + IGRAPH_CHECK(igraph_copy(res, graph)); + IGRAPH_FINALLY(igraph_destroy, res); + IGRAPH_CHECK(igraph_delete_vertices_idx(res, igraph_vss_vector(&delete), + map, invmap)); + + igraph_vector_destroy(&delete); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +/** + * Subgraph creation, new version: creates the new graph instead of + * copying the old one. + */ +int igraph_i_subgraph_create_from_scratch(const igraph_t *graph, + igraph_t *res, + const igraph_vs_t vids, + igraph_vector_t *map, + igraph_vector_t *invmap) { + igraph_bool_t directed = igraph_is_directed(graph); + long int no_of_nodes = igraph_vcount(graph); + long int no_of_new_nodes = 0; + long int i, j, n; + long int to; + igraph_integer_t eid; + igraph_vector_t vids_old2new, vids_new2old; + igraph_vector_t eids_new2old; + igraph_vector_t nei_edges; + igraph_vector_t new_edges; + igraph_vit_t vit; + igraph_vector_t *my_vids_old2new = &vids_old2new, + *my_vids_new2old = &vids_new2old; + + /* The order of initialization is important here, they will be destroyed in the + * opposite order */ + IGRAPH_VECTOR_INIT_FINALLY(&eids_new2old, 0); + if (invmap) { + my_vids_new2old = invmap; + igraph_vector_clear(my_vids_new2old); + } else { + IGRAPH_VECTOR_INIT_FINALLY(&vids_new2old, 0); + } + IGRAPH_VECTOR_INIT_FINALLY(&new_edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&nei_edges, 0); + if (map) { + my_vids_old2new = map; + IGRAPH_CHECK(igraph_vector_resize(map, no_of_nodes)); + igraph_vector_null(map); + } else { + IGRAPH_VECTOR_INIT_FINALLY(&vids_old2new, no_of_nodes); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + /* Calculate the mapping from the old node IDs to the new ones. The other + * igraph_simplify implementation in igraph_i_simplify_copy_and_delete + * ensures that the order of vertex IDs is kept during remapping (i.e. + * if the old ID of vertex A is less than the old ID of vertex B, then + * the same will also be true for the new IDs). To ensure compatibility + * with the other implementation, we have to fetch the vertex IDs into + * a vector first and then sort it. We temporarily use new_edges for that. + */ + IGRAPH_CHECK(igraph_vit_as_vector(&vit, &nei_edges)); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + igraph_vector_sort(&nei_edges); + n = igraph_vector_size(&nei_edges); + for (i = 0; i < n; i++) { + long int vid = (long int) VECTOR(nei_edges)[i]; + if (VECTOR(*my_vids_old2new)[vid] == 0) { + IGRAPH_CHECK(igraph_vector_push_back(my_vids_new2old, vid)); + no_of_new_nodes++; + VECTOR(*my_vids_old2new)[vid] = no_of_new_nodes; + } + } + + /* Create the new edge list */ + for (i = 0; i < no_of_new_nodes; i++) { + long int old_vid = (long int) VECTOR(*my_vids_new2old)[i]; + long int new_vid = i; + + IGRAPH_CHECK(igraph_incident(graph, &nei_edges, old_vid, IGRAPH_OUT)); + n = igraph_vector_size(&nei_edges); + + if (directed) { + for (j = 0; j < n; j++) { + eid = (igraph_integer_t) VECTOR(nei_edges)[j]; + + to = (long int) VECTOR(*my_vids_old2new)[ (long int)IGRAPH_TO(graph, eid) ]; + if (!to) { + continue; + } + + IGRAPH_CHECK(igraph_vector_push_back(&new_edges, new_vid)); + IGRAPH_CHECK(igraph_vector_push_back(&new_edges, to - 1)); + IGRAPH_CHECK(igraph_vector_push_back(&eids_new2old, eid)); + } + } else { + for (j = 0; j < n; j++) { + eid = (igraph_integer_t) VECTOR(nei_edges)[j]; + + if (IGRAPH_FROM(graph, eid) != old_vid) { /* avoid processing edges twice */ + continue; + } + + to = (long int) VECTOR(*my_vids_old2new)[ (long int)IGRAPH_TO(graph, eid) ]; + if (!to) { + continue; + } + + IGRAPH_CHECK(igraph_vector_push_back(&new_edges, new_vid)); + IGRAPH_CHECK(igraph_vector_push_back(&new_edges, to - 1)); + IGRAPH_CHECK(igraph_vector_push_back(&eids_new2old, eid)); + } + } + } + + /* Get rid of some vectors that are not needed anymore */ + if (!map) { + igraph_vector_destroy(&vids_old2new); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&nei_edges); + IGRAPH_FINALLY_CLEAN(1); + + /* Create the new graph */ + IGRAPH_CHECK(igraph_create(res, &new_edges, (igraph_integer_t) + no_of_new_nodes, directed)); + IGRAPH_I_ATTRIBUTE_DESTROY(res); + + /* Now we can also get rid of the new_edges vector */ + igraph_vector_destroy(&new_edges); + IGRAPH_FINALLY_CLEAN(1); + + /* Make sure that the newly created graph is destroyed if something happens from + * now on */ + IGRAPH_FINALLY(igraph_destroy, res); + + /* Copy the graph attributes */ + IGRAPH_CHECK(igraph_i_attribute_copy(res, graph, + /* ga = */ 1, /* va = */ 0, /* ea = */ 0)); + + /* Copy the vertex attributes */ + IGRAPH_CHECK(igraph_i_attribute_permute_vertices(graph, res, + my_vids_new2old)); + + /* Copy the edge attributes */ + IGRAPH_CHECK(igraph_i_attribute_permute_edges(graph, res, &eids_new2old)); + + if (!invmap) { + igraph_vector_destroy(my_vids_new2old); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&eids_new2old); + IGRAPH_FINALLY_CLEAN(2); /* 1 + 1 since we don't need to destroy res */ + + return 0; +} + +/** + * \ingroup structural + * \function igraph_subgraph + * \brief Creates a subgraph induced by the specified vertices. + * + * + * This function is an alias to \ref igraph_induced_subgraph(), it is + * left here to ensure API compatibility with igraph versions prior to 0.6. + * + * + * This function collects the specified vertices and all edges between + * them to a new graph. + * As the vertex ids in a graph always start with zero, this function + * very likely needs to reassign ids to the vertices. + * \param graph The graph object. + * \param res The subgraph, another graph object will be stored here, + * do \em not initialize this object before calling this + * function, and call \ref igraph_destroy() on it if you don't need + * it any more. + * \param vids A vertex selector describing which vertices to keep. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex id in + * \p vids. + * + * Time complexity: O(|V|+|E|), + * |V| and + * |E| are the number of vertices and + * edges in the original graph. + * + * \sa \ref igraph_delete_vertices() to delete the specified set of + * vertices from a graph, the opposite of this function. + */ + +int igraph_subgraph(const igraph_t *graph, igraph_t *res, + const igraph_vs_t vids) { + IGRAPH_WARNING("igraph_subgraph is deprecated from igraph 0.6, " + "use igraph_induced_subgraph instead"); + return igraph_induced_subgraph(graph, res, vids, IGRAPH_SUBGRAPH_AUTO); +} + +/** + * \ingroup structural + * \function igraph_induced_subgraph + * \brief Creates a subgraph induced by the specified vertices. + * + * + * This function collects the specified vertices and all edges between + * them to a new graph. + * As the vertex ids in a graph always start with zero, this function + * very likely needs to reassign ids to the vertices. + * \param graph The graph object. + * \param res The subgraph, another graph object will be stored here, + * do \em not initialize this object before calling this + * function, and call \ref igraph_destroy() on it if you don't need + * it any more. + * \param vids A vertex selector describing which vertices to keep. + * \param impl This parameter selects which implementation should we + * use when constructing the new graph. Basically there are two + * possibilities: \c IGRAPH_SUBGRAPH_COPY_AND_DELETE copies the + * existing graph and deletes the vertices that are not needed + * in the new graph, while \c IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH + * constructs the new graph from scratch without copying the old + * one. The latter is more efficient if you are extracting a + * relatively small subpart of a very large graph, while the + * former is better if you want to extract a subgraph whose size + * is comparable to the size of the whole graph. There is a third + * possibility: \c IGRAPH_SUBGRAPH_AUTO will select one of the + * two methods automatically based on the ratio of the number + * of vertices in the new and the old graph. + * + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVVID, invalid vertex id in + * \p vids. + * + * Time complexity: O(|V|+|E|), + * |V| and + * |E| are the number of vertices and + * edges in the original graph. + * + * \sa \ref igraph_delete_vertices() to delete the specified set of + * vertices from a graph, the opposite of this function. + */ +int igraph_induced_subgraph(const igraph_t *graph, igraph_t *res, + const igraph_vs_t vids, igraph_subgraph_implementation_t impl) { + return igraph_induced_subgraph_map(graph, res, vids, impl, /* map= */ 0, + /* invmap= */ 0); +} + +int igraph_i_induced_subgraph_suggest_implementation( + const igraph_t *graph, const igraph_vs_t vids, + igraph_subgraph_implementation_t *result) { + double ratio; + igraph_integer_t num_vs; + + if (igraph_vs_is_all(&vids)) { + ratio = 1.0; + } else { + IGRAPH_CHECK(igraph_vs_size(graph, &vids, &num_vs)); + ratio = (igraph_real_t) num_vs / igraph_vcount(graph); + } + + /* TODO: needs benchmarking; threshold was chosen totally arbitrarily */ + if (ratio > 0.5) { + *result = IGRAPH_SUBGRAPH_COPY_AND_DELETE; + } else { + *result = IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH; + } + + return 0; +} + +int igraph_induced_subgraph_map(const igraph_t *graph, igraph_t *res, + const igraph_vs_t vids, + igraph_subgraph_implementation_t impl, + igraph_vector_t *map, + igraph_vector_t *invmap) { + + if (impl == IGRAPH_SUBGRAPH_AUTO) { + IGRAPH_CHECK(igraph_i_induced_subgraph_suggest_implementation(graph, vids, &impl)); + } + + switch (impl) { + case IGRAPH_SUBGRAPH_COPY_AND_DELETE: + return igraph_i_subgraph_copy_and_delete(graph, res, vids, map, invmap); + + case IGRAPH_SUBGRAPH_CREATE_FROM_SCRATCH: + return igraph_i_subgraph_create_from_scratch(graph, res, vids, map, + invmap); + + default: + IGRAPH_ERROR("unknown subgraph implementation type", IGRAPH_EINVAL); + } + return 0; +} + +/** + * \ingroup structural + * \function igraph_subgraph_edges + * \brief Creates a subgraph with the specified edges and their endpoints. + * + * + * This function collects the specified edges and their endpoints to a new + * graph. + * As the vertex ids in a graph always start with zero, this function + * very likely needs to reassign ids to the vertices. + * \param graph The graph object. + * \param res The subgraph, another graph object will be stored here, + * do \em not initialize this object before calling this + * function, and call \ref igraph_destroy() on it if you don't need + * it any more. + * \param eids An edge selector describing which edges to keep. + * \param delete_vertices Whether to delete the vertices not incident on any + * of the specified edges as well. If \c FALSE, the number of vertices + * in the result graph will always be equal to the number of vertices + * in the input graph. + * \return Error code: + * \c IGRAPH_ENOMEM, not enough memory for + * temporary data. + * \c IGRAPH_EINVEID, invalid edge id in + * \p eids. + * + * Time complexity: O(|V|+|E|), + * |V| and + * |E| are the number of vertices and + * edges in the original graph. + * + * \sa \ref igraph_delete_edges() to delete the specified set of + * edges from a graph, the opposite of this function. + */ + +int igraph_subgraph_edges(const igraph_t *graph, igraph_t *res, + const igraph_es_t eids, igraph_bool_t delete_vertices) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_vector_t delete = IGRAPH_VECTOR_NULL; + char *vremain, *eremain; + long int i; + igraph_eit_t eit; + + IGRAPH_CHECK(igraph_eit_create(graph, eids, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_VECTOR_INIT_FINALLY(&delete, 0); + vremain = igraph_Calloc(no_of_nodes, char); + if (vremain == 0) { + IGRAPH_ERROR("subgraph_edges failed", IGRAPH_ENOMEM); + } + eremain = igraph_Calloc(no_of_edges, char); + if (eremain == 0) { + IGRAPH_ERROR("subgraph_edges failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(free, vremain); /* TODO: hack */ + IGRAPH_FINALLY(free, eremain); /* TODO: hack */ + IGRAPH_CHECK(igraph_vector_reserve(&delete, no_of_edges - IGRAPH_EIT_SIZE(eit))); + + /* Collect the vertex and edge IDs that will remain */ + for (IGRAPH_EIT_RESET(eit); !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + igraph_integer_t from, to; + long int eid = (long int) IGRAPH_EIT_GET(eit); + IGRAPH_CHECK(igraph_edge(graph, (igraph_integer_t) eid, &from, &to)); + eremain[eid] = vremain[(long int)from] = vremain[(long int)to] = 1; + } + + /* Collect the edge IDs to be deleted */ + for (i = 0; i < no_of_edges; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + if (eremain[i] == 0) { + IGRAPH_CHECK(igraph_vector_push_back(&delete, i)); + } + } + + igraph_Free(eremain); + IGRAPH_FINALLY_CLEAN(1); + + /* Delete the unnecessary edges */ + /* must set res->attr to 0 before calling igraph_copy */ + res->attr = 0; /* Why is this needed? TODO */ + IGRAPH_CHECK(igraph_copy(res, graph)); + IGRAPH_FINALLY(igraph_destroy, res); + IGRAPH_CHECK(igraph_delete_edges(res, igraph_ess_vector(&delete))); + + if (delete_vertices) { + /* Collect the vertex IDs to be deleted */ + igraph_vector_clear(&delete); + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + if (vremain[i] == 0) { + IGRAPH_CHECK(igraph_vector_push_back(&delete, i)); + } + } + } + + igraph_Free(vremain); + IGRAPH_FINALLY_CLEAN(1); + + /* Delete the unnecessary vertices */ + if (delete_vertices) { + IGRAPH_CHECK(igraph_delete_vertices(res, igraph_vss_vector(&delete))); + } + + igraph_vector_destroy(&delete); + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(3); + return 0; +} + +void igraph_i_simplify_free(igraph_vector_ptr_t *p); + +void igraph_i_simplify_free(igraph_vector_ptr_t *p) { + long int i, n = igraph_vector_ptr_size(p); + for (i = 0; i < n; i++) { + igraph_vector_t *v = VECTOR(*p)[i]; + if (v) { + igraph_vector_destroy(v); + } + } + igraph_vector_ptr_destroy(p); +} + +/** + * \ingroup structural + * \function igraph_simplify + * \brief Removes loop and/or multiple edges from the graph. + * + * \param graph The graph object. + * \param multiple Logical, if true, multiple edges will be removed. + * \param loops Logical, if true, loops (self edges) will be removed. + * \param edge_comb What to do with the edge attributes. See the igraph + * manual section about attributes for details. + * \return Error code: + * \c IGRAPH_ENOMEM if we are out of memory. + * + * Time complexity: O(|V|+|E|). + * + * \example examples/simple/igraph_simplify.c + */ + +int igraph_simplify(igraph_t *graph, igraph_bool_t multiple, + igraph_bool_t loops, + const igraph_attribute_combination_t *edge_comb) { + + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + long int edge; + igraph_bool_t attr = edge_comb && igraph_has_attribute_table(); + long int from, to, pfrom = -1, pto = -2; + igraph_t res; + igraph_es_t es; + igraph_eit_t eit; + igraph_vector_t mergeinto; + long int actedge; + + if (!multiple && !loops) + /* nothing to do */ + { + return IGRAPH_SUCCESS; + } + + if (!multiple) { + /* removing loop edges only, this is simple. No need to combine anything + * and the whole process can be done in-place */ + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_ID)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + while (!IGRAPH_EIT_END(eit)) { + edge = IGRAPH_EIT_GET(eit); + from = IGRAPH_FROM(graph, edge); + to = IGRAPH_TO(graph, edge); + if (from == to) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, edge)); + } + IGRAPH_EIT_NEXT(eit); + } + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(2); + + if (igraph_vector_size(&edges) > 0) { + IGRAPH_CHECK(igraph_delete_edges(graph, igraph_ess_vector(&edges))); + } + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; + } + + if (attr) { + IGRAPH_VECTOR_INIT_FINALLY(&mergeinto, no_of_edges); + } + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges * 2)); + + IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_FROM)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + for (actedge = -1; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + edge = IGRAPH_EIT_GET(eit); + from = IGRAPH_FROM(graph, edge); + to = IGRAPH_TO(graph, edge); + + if (loops && from == to) { + /* Loop edge to be removed */ + if (attr) { + VECTOR(mergeinto)[edge] = -1; + } + } else if (multiple && from == pfrom && to == pto) { + /* Multiple edge to be contracted */ + if (attr) { + VECTOR(mergeinto)[edge] = actedge; + } + } else { + /* Edge to be kept */ + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + if (attr) { + actedge++; + VECTOR(mergeinto)[edge] = actedge; + } + } + pfrom = from; pto = to; + } + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(&res, &edges, (igraph_integer_t) no_of_nodes, + igraph_is_directed(graph))); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FINALLY(igraph_destroy, &res); + + IGRAPH_I_ATTRIBUTE_DESTROY(&res); + IGRAPH_I_ATTRIBUTE_COPY(&res, graph, /*graph=*/ 1, + /*vertex=*/ 1, /*edge=*/ 0); + + if (attr) { + igraph_fixed_vectorlist_t vl; + IGRAPH_CHECK(igraph_fixed_vectorlist_convert(&vl, &mergeinto, + actedge + 1)); + IGRAPH_FINALLY(igraph_fixed_vectorlist_destroy, &vl); + + IGRAPH_CHECK(igraph_i_attribute_combine_edges(graph, &res, &vl.v, + edge_comb)); + + igraph_fixed_vectorlist_destroy(&vl); + igraph_vector_destroy(&mergeinto); + IGRAPH_FINALLY_CLEAN(2); + } + + IGRAPH_FINALLY_CLEAN(1); + igraph_destroy(graph); + *graph = res; + + return 0; +} + +/** + * \ingroup structural + * \function igraph_reciprocity + * \brief Calculates the reciprocity of a directed graph. + * + * + * The measure of reciprocity defines the proportion of mutual + * connections, in a directed graph. It is most commonly defined as + * the probability that the opposite counterpart of a directed edge is + * also included in the graph. In adjacency matrix notation: + * sum(i, j, (A.*A')ij) / sum(i, j, Aij), where + * A.*A' is the element-wise product of matrix + * A and its transpose. This measure is + * calculated if the \p mode argument is \c + * IGRAPH_RECIPROCITY_DEFAULT. + * + * + * Prior to igraph version 0.6, another measure was implemented, + * defined as the probability of mutual connection between a vertex + * pair if we know that there is a (possibly non-mutual) connection + * between them. In other words, (unordered) vertex pairs are + * classified into three groups: (1) disconnected, (2) + * non-reciprocally connected, (3) reciprocally connected. + * The result is the size of group (3), divided by the sum of group + * sizes (2)+(3). This measure is calculated if \p mode is \c + * IGRAPH_RECIPROCITY_RATIO. + * + * \param graph The graph object. + * \param res Pointer to an \c igraph_real_t which will contain the result. + * \param ignore_loops Whether to ignore loop edges. + * \param mode Type of reciprocity to calculate, possible values are + * \c IGRAPH_RECIPROCITY_DEFAULT and \c IGRAPH_RECIPROCITY_RATIO, + * please see their description above. + * \return Error code: + * \c IGRAPH_EINVAL: graph has no edges + * \c IGRAPH_ENOMEM: not enough memory for + * temporary data. + * + * Time complexity: O(|V|+|E|), |V| is the number of vertices, + * |E| is the number of edges. + * + * \example examples/simple/igraph_reciprocity.c + */ + +int igraph_reciprocity(const igraph_t *graph, igraph_real_t *res, + igraph_bool_t ignore_loops, + igraph_reciprocity_t mode) { + + igraph_integer_t nonrec = 0, rec = 0, loops = 0; + igraph_vector_t inneis, outneis; + long int i; + long int no_of_nodes = igraph_vcount(graph); + + if (mode != IGRAPH_RECIPROCITY_DEFAULT && + mode != IGRAPH_RECIPROCITY_RATIO) { + IGRAPH_ERROR("Invalid reciprocity type", IGRAPH_EINVAL); + } + + /* THIS IS AN EXIT HERE !!!!!!!!!!!!!! */ + if (!igraph_is_directed(graph)) { + *res = 1.0; + return 0; + } + + IGRAPH_VECTOR_INIT_FINALLY(&inneis, 0); + IGRAPH_VECTOR_INIT_FINALLY(&outneis, 0); + + for (i = 0; i < no_of_nodes; i++) { + long int ip, op; + igraph_neighbors(graph, &inneis, (igraph_integer_t) i, IGRAPH_IN); + igraph_neighbors(graph, &outneis, (igraph_integer_t) i, IGRAPH_OUT); + + ip = op = 0; + while (ip < igraph_vector_size(&inneis) && + op < igraph_vector_size(&outneis)) { + if (VECTOR(inneis)[ip] < VECTOR(outneis)[op]) { + nonrec += 1; + ip++; + } else if (VECTOR(inneis)[ip] > VECTOR(outneis)[op]) { + nonrec += 1; + op++; + } else { + + /* loop edge? */ + if (VECTOR(inneis)[ip] == i) { + loops += 1; + if (!ignore_loops) { + rec += 1; + } + } else { + rec += 1; + } + + ip++; + op++; + } + } + nonrec += (igraph_vector_size(&inneis) - ip) + + (igraph_vector_size(&outneis) - op); + } + + if (mode == IGRAPH_RECIPROCITY_DEFAULT) { + if (ignore_loops) { + *res = (igraph_real_t) rec / (igraph_ecount(graph) - loops); + } else { + *res = (igraph_real_t) rec / (igraph_ecount(graph)); + } + } else if (mode == IGRAPH_RECIPROCITY_RATIO) { + *res = (igraph_real_t) rec / (rec + nonrec); + } + + igraph_vector_destroy(&inneis); + igraph_vector_destroy(&outneis); + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/** + * \function igraph_constraint + * \brief Burt's constraint scores. + * + * + * This function calculates Burt's constraint scores for the given + * vertices, also known as structural holes. + * + * + * Burt's constraint is higher if ego has less, or mutually stronger + * related (i.e. more redundant) contacts. Burt's measure of + * constraint, C[i], of vertex i's ego network V[i], is defined for + * directed and valued graphs, + *
    + * C[i] = sum( sum( (p[i,q] p[q,j])^2, q in V[i], q != i,j ), j in + * V[], j != i) + *
    + * for a graph of order (ie. number of vertices) N, where proportional + * tie strengths are defined as + *
    + * p[i,j]=(a[i,j]+a[j,i]) / sum(a[i,k]+a[k,i], k in V[i], k != i), + *
    + * a[i,j] are elements of A and + * the latter being the graph adjacency matrix. For isolated vertices, + * constraint is undefined. + * + *
    + * Burt, R.S. (2004). Structural holes and good ideas. American + * Journal of Sociology 110, 349-399. + * + * + * The first R version of this function was contributed by Jeroen + * Bruggeman. + * \param graph A graph object. + * \param res Pointer to an initialized vector, the result will be + * stored here. The vector will be resized to have the + * appropriate size for holding the result. + * \param vids Vertex selector containing the vertices for which the + * constraint should be calculated. + * \param weights Vector giving the weights of the edges. If it is + * \c NULL then each edge is supposed to have the same weight. + * \return Error code. + * + * Time complexity: O(|V|+E|+n*d^2), n is the number of vertices for + * which the constraint is calculated and d is the average degree, |V| + * is the number of vertices, |E| the number of edges in the + * graph. If the weights argument is \c NULL then the time complexity + * is O(|V|+n*d^2). + */ + +int igraph_constraint(const igraph_t *graph, igraph_vector_t *res, + igraph_vs_t vids, const igraph_vector_t *weights) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_vit_t vit; + long int nodes_to_calc; + long int a, b, c, i, j, q; + igraph_integer_t edge, from, to, edge2, from2, to2; + + igraph_vector_t contrib; + igraph_vector_t degree; + igraph_vector_t ineis_in, ineis_out, jneis_in, jneis_out; + + if (weights != 0 && igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid length of weight vector", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&contrib, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&ineis_in, 0); + IGRAPH_VECTOR_INIT_FINALLY(&ineis_out, 0); + IGRAPH_VECTOR_INIT_FINALLY(&jneis_in, 0); + IGRAPH_VECTOR_INIT_FINALLY(&jneis_out, 0); + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + if (weights == 0) { + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), + IGRAPH_ALL, IGRAPH_NO_LOOPS)); + } else { + for (a = 0; a < no_of_edges; a++) { + igraph_edge(graph, (igraph_integer_t) a, &from, &to); + if (from != to) { + VECTOR(degree)[(long int) from] += VECTOR(*weights)[a]; + VECTOR(degree)[(long int) to ] += VECTOR(*weights)[a]; + } + } + } + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + igraph_vector_null(res); + + for (a = 0; a < nodes_to_calc; a++, IGRAPH_VIT_NEXT(vit)) { + i = IGRAPH_VIT_GET(vit); + + /* get neighbors of i */ + IGRAPH_CHECK(igraph_incident(graph, &ineis_in, (igraph_integer_t) i, + IGRAPH_IN)); + IGRAPH_CHECK(igraph_incident(graph, &ineis_out, (igraph_integer_t) i, + IGRAPH_OUT)); + + /* NaN for isolates */ + if (igraph_vector_size(&ineis_in) == 0 && + igraph_vector_size(&ineis_out) == 0) { + VECTOR(*res)[a] = IGRAPH_NAN; + } + + /* zero their contribution */ + for (b = 0; b < igraph_vector_size(&ineis_in); b++) { + edge = (igraph_integer_t) VECTOR(ineis_in)[b]; + igraph_edge(graph, edge, &from, &to); + if (to == i) { + to = from; + } + j = to; + VECTOR(contrib)[j] = 0.0; + } + for (b = 0; b < igraph_vector_size(&ineis_out); b++) { + edge = (igraph_integer_t) VECTOR(ineis_out)[b]; + igraph_edge(graph, edge, &from, &to); + if (to == i) { + to = from; + } + j = to; + VECTOR(contrib)[j] = 0.0; + } + + /* add the direct contributions, in-neighbors and out-neighbors */ + for (b = 0; b < igraph_vector_size(&ineis_in); b++) { + edge = (igraph_integer_t) VECTOR(ineis_in)[b]; + igraph_edge(graph, edge, &from, &to); + if (to == i) { + to = from; + } + j = to; + if (i != j) { /* excluding loops */ + if (weights) { + VECTOR(contrib)[j] += + VECTOR(*weights)[(long int)edge] / VECTOR(degree)[i]; + } else { + VECTOR(contrib)[j] += 1.0 / VECTOR(degree)[i]; + } + } + } + if (igraph_is_directed(graph)) { + for (b = 0; b < igraph_vector_size(&ineis_out); b++) { + edge = (igraph_integer_t) VECTOR(ineis_out)[b]; + igraph_edge(graph, edge, &from, &to); + if (to == i) { + to = from; + } + j = to; + if (i != j) { + if (weights) { + VECTOR(contrib)[j] += + VECTOR(*weights)[(long int)edge] / VECTOR(degree)[i]; + } else { + VECTOR(contrib)[j] += 1.0 / VECTOR(degree)[i]; + } + } + } + } + + /* add the indirect contributions, in-in, in-out, out-in, out-out */ + for (b = 0; b < igraph_vector_size(&ineis_in); b++) { + edge = (igraph_integer_t) VECTOR(ineis_in)[b]; + igraph_edge(graph, edge, &from, &to); + if (to == i) { + to = from; + } + j = to; + if (i == j) { + continue; + } + IGRAPH_CHECK(igraph_incident(graph, &jneis_in, (igraph_integer_t) j, + IGRAPH_IN)); + IGRAPH_CHECK(igraph_incident(graph, &jneis_out, (igraph_integer_t) j, + IGRAPH_OUT)); + for (c = 0; c < igraph_vector_size(&jneis_in); c++) { + edge2 = (igraph_integer_t) VECTOR(jneis_in)[c]; + igraph_edge(graph, edge2, &from2, &to2); + if (to2 == j) { + to2 = from2; + } + q = to2; + if (j != q) { + if (weights) { + VECTOR(contrib)[q] += + VECTOR(*weights)[(long int)edge] * + VECTOR(*weights)[(long int)edge2] / + VECTOR(degree)[i] / VECTOR(degree)[j]; + } else { + VECTOR(contrib)[q] += 1 / VECTOR(degree)[i] / VECTOR(degree)[j]; + } + } + } + if (igraph_is_directed(graph)) { + for (c = 0; c < igraph_vector_size(&jneis_out); c++) { + edge2 = (igraph_integer_t) VECTOR(jneis_out)[c]; + igraph_edge(graph, edge2, &from2, &to2); + if (to2 == j) { + to2 = from2; + } + q = to2; + if (j != q) { + if (weights) { + VECTOR(contrib)[q] += + VECTOR(*weights)[(long int)edge] * + VECTOR(*weights)[(long int)edge2] / + VECTOR(degree)[i] / VECTOR(degree)[j]; + } else { + VECTOR(contrib)[q] += 1 / VECTOR(degree)[i] / VECTOR(degree)[j]; + } + } + } + } + } + if (igraph_is_directed(graph)) { + for (b = 0; b < igraph_vector_size(&ineis_out); b++) { + edge = (igraph_integer_t) VECTOR(ineis_out)[b]; + igraph_edge(graph, edge, &from, &to); + if (to == i) { + to = from; + } + j = to; + if (i == j) { + continue; + } + IGRAPH_CHECK(igraph_incident(graph, &jneis_in, (igraph_integer_t) j, + IGRAPH_IN)); + IGRAPH_CHECK(igraph_incident(graph, &jneis_out, (igraph_integer_t) j, + IGRAPH_OUT)); + for (c = 0; c < igraph_vector_size(&jneis_in); c++) { + edge2 = (igraph_integer_t) VECTOR(jneis_in)[c]; + igraph_edge(graph, edge2, &from2, &to2); + if (to2 == j) { + to2 = from2; + } + q = to2; + if (j != q) { + if (weights) { + VECTOR(contrib)[q] += + VECTOR(*weights)[(long int)edge] * + VECTOR(*weights)[(long int)edge2] / + VECTOR(degree)[i] / VECTOR(degree)[j]; + } else { + VECTOR(contrib)[q] += 1 / VECTOR(degree)[i] / VECTOR(degree)[j]; + } + } + } + for (c = 0; c < igraph_vector_size(&jneis_out); c++) { + edge2 = (igraph_integer_t) VECTOR(jneis_out)[c]; + igraph_edge(graph, edge2, &from2, &to2); + if (to2 == j) { + to2 = from2; + } + q = to2; + if (j != q) { + if (weights) { + VECTOR(contrib)[q] += + VECTOR(*weights)[(long int)edge] * + VECTOR(*weights)[(long int)edge2] / + VECTOR(degree)[i] / VECTOR(degree)[j]; + } else { + VECTOR(contrib)[q] += 1 / VECTOR(degree)[i] / VECTOR(degree)[j]; + } + } + } + } + } + + /* squared sum of the contributions */ + for (b = 0; b < igraph_vector_size(&ineis_in); b++) { + edge = (igraph_integer_t) VECTOR(ineis_in)[b]; + igraph_edge(graph, edge, &from, &to); + if (to == i) { + to = from; + } + j = to; + if (i == j) { + continue; + } + VECTOR(*res)[a] += VECTOR(contrib)[j] * VECTOR(contrib)[j]; + VECTOR(contrib)[j] = 0.0; + } + if (igraph_is_directed(graph)) { + for (b = 0; b < igraph_vector_size(&ineis_out); b++) { + edge = (igraph_integer_t) VECTOR(ineis_out)[b]; + igraph_edge(graph, edge, &from, &to); + if (to == i) { + to = from; + } + j = to; + if (i == j) { + continue; + } + VECTOR(*res)[a] += VECTOR(contrib)[j] * VECTOR(contrib)[j]; + VECTOR(contrib)[j] = 0.0; + } + } + } + + igraph_vit_destroy(&vit); + igraph_vector_destroy(&jneis_out); + igraph_vector_destroy(&jneis_in); + igraph_vector_destroy(&ineis_out); + igraph_vector_destroy(&ineis_in); + igraph_vector_destroy(°ree); + igraph_vector_destroy(&contrib); + IGRAPH_FINALLY_CLEAN(7); + + return 0; +} + +/** + * \function igraph_maxdegree + * \brief Calculate the maximum degree in a graph (or set of vertices). + * + * + * The largest in-, out- or total degree of the specified vertices is + * calculated. + * \param graph The input graph. + * \param res Pointer to an integer (\c igraph_integer_t), the result + * will be stored here. + * \param vids Vector giving the vertex IDs for which the maximum degree will + * be calculated. + * \param mode Defines the type of the degree. + * \c IGRAPH_OUT, out-degree, + * \c IGRAPH_IN, in-degree, + * \c IGRAPH_ALL, total degree (sum of the + * in- and out-degree). + * This parameter is ignored for undirected graphs. + * \param loops Boolean, gives whether the self-loops should be + * counted. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex id. + * \c IGRAPH_EINVMODE: invalid mode argument. + * + * Time complexity: O(v) if + * loops is + * TRUE, and + * O(v*d) + * otherwise. v is the number + * vertices for which the degree will be calculated, and + * d is their (average) degree. + */ + +int igraph_maxdegree(const igraph_t *graph, igraph_integer_t *res, + igraph_vs_t vids, igraph_neimode_t mode, + igraph_bool_t loops) { + + igraph_vector_t tmp; + + IGRAPH_VECTOR_INIT_FINALLY(&tmp, 0); + + igraph_degree(graph, &tmp, vids, mode, loops); + *res = (igraph_integer_t) igraph_vector_max(&tmp); + + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_density + * Calculate the density of a graph. + * + * The density of a graph is simply the ratio number of + * edges and the number of possible edges. Note that density is + * ill-defined for graphs with multiple and/or loop edges, so consider + * calling \ref igraph_simplify() on the graph if you know that it + * contains multiple or loop edges. + * \param graph The input graph object. + * \param res Pointer to a real number, the result will be stored + * here. + * \param loops Logical constant, whether to include loops in the + * calculation. If this constant is TRUE then + * loop edges are thought to be possible in the graph (this does not + * necessarily mean that the graph really contains any loops). If + * this is FALSE then the result is only correct if the graph does not + * contain loops. + * \return Error code. + * + * Time complexity: O(1). + */ + +int igraph_density(const igraph_t *graph, igraph_real_t *res, + igraph_bool_t loops) { + + igraph_integer_t no_of_nodes = igraph_vcount(graph); + igraph_real_t no_of_edges = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + + if (no_of_nodes == 0) { + *res = IGRAPH_NAN; + return 0; + } + + if (!loops) { + if (no_of_nodes == 1) { + *res = IGRAPH_NAN; + } else if (directed) { + *res = no_of_edges / no_of_nodes / (no_of_nodes - 1); + } else { + *res = no_of_edges / no_of_nodes * 2.0 / (no_of_nodes - 1); + } + } else { + if (directed) { + *res = no_of_edges / no_of_nodes / no_of_nodes; + } else { + *res = no_of_edges / no_of_nodes * 2.0 / (no_of_nodes + 1); + } + } + + return 0; +} + +/** + * \function igraph_neighborhood_size + * \brief Calculates the size of the neighborhood of a given vertex. + * + * The neighborhood of a given order of a vertex includes all vertices + * which are closer to the vertex than the order. Ie. order 0 is + * always the vertex itself, order 1 is the vertex plus its immediate + * neighbors, order 2 is order 1 plus the immediate neighbors of the + * vertices in order 1, etc. + * + * This function calculates the size of the neighborhood + * of the given order for the given vertices. + * \param graph The input graph. + * \param res Pointer to an initialized vector, the result will be + * stored here. It will be resized as needed. + * \param vids The vertices for which the calculation is performed. + * \param order Integer giving the order of the neighborhood. + * \param mode Specifies how to use the direction of the edges if a + * directed graph is analyzed. For \c IGRAPH_OUT only the outgoing + * edges are followed, so all vertices reachable from the source + * vertex in at most \c order steps are counted. For \c IGRAPH_IN + * all vertices from which the source vertex is reachable in at most + * \c order steps are counted. \c IGRAPH_ALL ignores the direction + * of the edges. This argument is ignored for undirected graphs. + * \param mindist The minimum distance to include a vertex in the counting. + * If this is one, then the starting vertex is not counted. If this is + * two, then its neighbors are not counted, either, etc. + * \return Error code. + * + * \sa \ref igraph_neighborhood() for calculating the actual neighborhood, + * \ref igraph_neighborhood_graphs() for creating separate graphs from + * the neighborhoods. + * + * Time complexity: O(n*d*o), where n is the number vertices for which + * the calculation is performed, d is the average degree, o is the order. + */ + +int igraph_neighborhood_size(const igraph_t *graph, igraph_vector_t *res, + igraph_vs_t vids, igraph_integer_t order, + igraph_neimode_t mode, + igraph_integer_t mindist) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_dqueue_t q; + igraph_vit_t vit; + long int i, j; + long int *added; + igraph_vector_t neis; + + if (order < 0) { + IGRAPH_ERROR("Negative order in neighborhood size", IGRAPH_EINVAL); + } + + if (mindist < 0 || mindist > order) { + IGRAPH_ERROR("Minimum distance should be between zero and order", + IGRAPH_EINVAL); + } + + added = igraph_Calloc(no_of_nodes, long int); + if (added == 0) { + IGRAPH_ERROR("Cannot calculate neighborhood size", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_VIT_SIZE(vit))); + + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + long int node = IGRAPH_VIT_GET(vit); + long int size = mindist == 0 ? 1 : 0; + added[node] = i + 1; + igraph_dqueue_clear(&q); + if (order > 0) { + igraph_dqueue_push(&q, node); + igraph_dqueue_push(&q, 0); + } + + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + long int actdist = (long int) igraph_dqueue_pop(&q); + long int n; + igraph_neighbors(graph, &neis, (igraph_integer_t) actnode, mode); + n = igraph_vector_size(&neis); + + if (actdist < order - 1) { + /* we add them to the q */ + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1)); + if (actdist + 1 >= mindist) { + size++; + } + } + } + } else { + /* we just count them, but don't add them */ + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (actdist + 1 >= mindist) { + size++; + } + } + } + } + + } /* while q not empty */ + + VECTOR(*res)[i] = size; + } /* for VIT, i */ + + igraph_vector_destroy(&neis); + igraph_vit_destroy(&vit); + igraph_dqueue_destroy(&q); + igraph_Free(added); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +/** + * \function igraph_neighborhood + * Calculate the neighborhood of vertices. + * + * The neighborhood of a given order of a vertex includes all vertices + * which are closer to the vertex than the order. Ie. order 0 is + * always the vertex itself, order 1 is the vertex plus its immediate + * neighbors, order 2 is order 1 plus the immediate neighbors of the + * vertices in order 1, etc. + * + * This function calculates the vertices within the + * neighborhood of the specified vertices. + * \param graph The input graph. + * \param res An initialized pointer vector. Note that the objects + * (pointers) in the vector will \em not be freed, but the pointer + * vector will be resized as needed. The result of the calculation + * will be stored here in \c vector_t objects. + * \param vids The vertices for which the calculation is performed. + * \param order Integer giving the order of the neighborhood. + * \param mode Specifies how to use the direction of the edges if a + * directed graph is analyzed. For \c IGRAPH_OUT only the outgoing + * edges are followed, so all vertices reachable from the source + * vertex in at most \c order steps are included. For \c IGRAPH_IN + * all vertices from which the source vertex is reachable in at most + * \c order steps are included. \c IGRAPH_ALL ignores the direction + * of the edges. This argument is ignored for undirected graphs. + * \param mindist The minimum distance to include a vertex in the counting. + * If this is one, then the starting vertex is not counted. If this is + * two, then its neighbors are not counted, either, etc. + * \return Error code. + * + * \sa \ref igraph_neighborhood_size() to calculate the size of the + * neighborhood, \ref igraph_neighborhood_graphs() for creating + * graphs from the neighborhoods. + * + * Time complexity: O(n*d*o), n is the number of vertices for which + * the calculation is performed, d is the average degree, o is the + * order. + */ + +int igraph_neighborhood(const igraph_t *graph, igraph_vector_ptr_t *res, + igraph_vs_t vids, igraph_integer_t order, + igraph_neimode_t mode, igraph_integer_t mindist) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_dqueue_t q; + igraph_vit_t vit; + long int i, j; + long int *added; + igraph_vector_t neis; + igraph_vector_t tmp; + igraph_vector_t *newv; + + if (order < 0) { + IGRAPH_ERROR("Negative order in neighborhood size", IGRAPH_EINVAL); + } + + if (mindist < 0 || mindist > order) { + IGRAPH_ERROR("Minimum distance should be between zero and order", + IGRAPH_EINVAL); + } + + added = igraph_Calloc(no_of_nodes, long int); + if (added == 0) { + IGRAPH_ERROR("Cannot calculate neighborhood size", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INIT_FINALLY(&tmp, 0); + IGRAPH_CHECK(igraph_vector_ptr_resize(res, IGRAPH_VIT_SIZE(vit))); + + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + long int node = IGRAPH_VIT_GET(vit); + added[node] = i + 1; + igraph_vector_clear(&tmp); + if (mindist == 0) { + IGRAPH_CHECK(igraph_vector_push_back(&tmp, node)); + } + if (order > 0) { + igraph_dqueue_push(&q, node); + igraph_dqueue_push(&q, 0); + } + + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + long int actdist = (long int) igraph_dqueue_pop(&q); + long int n; + igraph_neighbors(graph, &neis, (igraph_integer_t) actnode, mode); + n = igraph_vector_size(&neis); + + if (actdist < order - 1) { + /* we add them to the q */ + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1)); + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_push_back(&tmp, nei)); + } + } + } + } else { + /* we just count them but don't add them to q */ + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_push_back(&tmp, nei)); + } + } + } + } + + } /* while q not empty */ + + newv = igraph_Calloc(1, igraph_vector_t); + if (newv == 0) { + IGRAPH_ERROR("Cannot calculate neighborhood", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newv); + IGRAPH_CHECK(igraph_vector_copy(newv, &tmp)); + VECTOR(*res)[i] = newv; + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&tmp); + igraph_vector_destroy(&neis); + igraph_vit_destroy(&vit); + igraph_dqueue_destroy(&q); + igraph_Free(added); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +/** + * \function igraph_neighborhood_graphs + * Create graphs from the neighborhood(s) of some vertex/vertices. + * + * The neighborhood of a given order of a vertex includes all vertices + * which are closer to the vertex than the order. Ie. order 0 is + * always the vertex itself, order 1 is the vertex plus its immediate + * neighbors, order 2 is order 1 plus the immediate neighbors of the + * vertices in order 1, etc. + * + * This function finds every vertex in the neighborhood + * of a given parameter vertex and creates a graph from these + * vertices. + * + * The first version of this function was written by + * Vincent Matossian, thanks Vincent. + * \param graph The input graph. + * \param res Pointer to a pointer vector, the result will be stored + * here, ie. \c res will contain pointers to \c igraph_t + * objects. It will be resized if needed but note that the + * objects in the pointer vector will not be freed. + * \param vids The vertices for which the calculation is performed. + * \param order Integer giving the order of the neighborhood. + * \param mode Specifies how to use the direction of the edges if a + * directed graph is analyzed. For \c IGRAPH_OUT only the outgoing + * edges are followed, so all vertices reachable from the source + * vertex in at most \c order steps are counted. For \c IGRAPH_IN + * all vertices from which the source vertex is reachable in at most + * \c order steps are counted. \c IGRAPH_ALL ignores the direction + * of the edges. This argument is ignored for undirected graphs. + * \param mindist The minimum distance to include a vertex in the counting. + * If this is one, then the starting vertex is not counted. If this is + * two, then its neighbors are not counted, either, etc. + * \return Error code. + * + * \sa \ref igraph_neighborhood_size() for calculating the neighborhood + * sizes only, \ref igraph_neighborhood() for calculating the + * neighborhoods (but not creating graphs). + * + * Time complexity: O(n*(|V|+|E|)), where n is the number vertices for + * which the calculation is performed, |V| and |E| are the number of + * vertices and edges in the original input graph. + */ + +int igraph_neighborhood_graphs(const igraph_t *graph, igraph_vector_ptr_t *res, + igraph_vs_t vids, igraph_integer_t order, + igraph_neimode_t mode, + igraph_integer_t mindist) { + long int no_of_nodes = igraph_vcount(graph); + igraph_dqueue_t q; + igraph_vit_t vit; + long int i, j; + long int *added; + igraph_vector_t neis; + igraph_vector_t tmp; + igraph_t *newg; + + if (order < 0) { + IGRAPH_ERROR("Negative order in neighborhood size", IGRAPH_EINVAL); + } + + if (mindist < 0 || mindist > order) { + IGRAPH_ERROR("Minimum distance should be between zero and order", + IGRAPH_EINVAL); + } + + added = igraph_Calloc(no_of_nodes, long int); + if (added == 0) { + IGRAPH_ERROR("Cannot calculate neighborhood size", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INIT_FINALLY(&tmp, 0); + IGRAPH_CHECK(igraph_vector_ptr_resize(res, IGRAPH_VIT_SIZE(vit))); + + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + long int node = IGRAPH_VIT_GET(vit); + added[node] = i + 1; + igraph_vector_clear(&tmp); + if (mindist == 0) { + IGRAPH_CHECK(igraph_vector_push_back(&tmp, node)); + } + if (order > 0) { + igraph_dqueue_push(&q, node); + igraph_dqueue_push(&q, 0); + } + + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + long int actdist = (long int) igraph_dqueue_pop(&q); + long int n; + igraph_neighbors(graph, &neis, (igraph_integer_t) actnode, mode); + n = igraph_vector_size(&neis); + + if (actdist < order - 1) { + /* we add them to the q */ + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1)); + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_push_back(&tmp, nei)); + } + } + } + } else { + /* we just count them but don't add them to q */ + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (actdist + 1 >= mindist) { + IGRAPH_CHECK(igraph_vector_push_back(&tmp, nei)); + } + } + } + } + + } /* while q not empty */ + + newg = igraph_Calloc(1, igraph_t); + if (newg == 0) { + IGRAPH_ERROR("Cannot create neighborhood graph", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, newg); + if (igraph_vector_size(&tmp) < no_of_nodes) { + IGRAPH_CHECK(igraph_induced_subgraph(graph, newg, + igraph_vss_vector(&tmp), + IGRAPH_SUBGRAPH_AUTO)); + } else { + IGRAPH_CHECK(igraph_copy(newg, graph)); + } + VECTOR(*res)[i] = newg; + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&tmp); + igraph_vector_destroy(&neis); + igraph_vit_destroy(&vit); + igraph_dqueue_destroy(&q); + igraph_Free(added); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +/** + * \function igraph_topological_sorting + * \brief Calculate a possible topological sorting of the graph. + * + * + * A topological sorting of a directed acyclic graph is a linear ordering + * of its nodes where each node comes before all nodes to which it has + * edges. Every DAG has at least one topological sort, and may have many. + * This function returns a possible topological sort among them. If the + * graph is not acyclic (it has at least one cycle), a partial topological + * sort is returned and a warning is issued. + * + * \param graph The input graph. + * \param res Pointer to a vector, the result will be stored here. + * It will be resized if needed. + * \param mode Specifies how to use the direction of the edges. + * For \c IGRAPH_OUT, the sorting order ensures that each node comes + * before all nodes to which it has edges, so nodes with no incoming + * edges go first. For \c IGRAPH_IN, it is quite the opposite: each + * node comes before all nodes from which it receives edges. Nodes + * with no outgoing edges go first. + * \return Error code. + * + * Time complexity: O(|V|+|E|), where |V| and |E| are the number of + * vertices and edges in the original input graph. + * + * \sa \ref igraph_is_dag() if you are only interested in whether a given + * graph is a DAG or not, or \ref igraph_feedback_arc_set() to find a + * set of edges whose removal makes the graph a DAG. + * + * \example examples/simple/igraph_topological_sorting.c + */ +int igraph_topological_sorting(const igraph_t* graph, igraph_vector_t *res, + igraph_neimode_t mode) { + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t degrees, neis; + igraph_dqueue_t sources; + igraph_neimode_t deg_mode; + long int node, i, j; + + if (mode == IGRAPH_ALL || !igraph_is_directed(graph)) { + IGRAPH_ERROR("topological sorting does not make sense for undirected graphs", IGRAPH_EINVAL); + } else if (mode == IGRAPH_OUT) { + deg_mode = IGRAPH_IN; + } else if (mode == IGRAPH_IN) { + deg_mode = IGRAPH_OUT; + } else { + IGRAPH_ERROR("invalid mode", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(°rees, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_dqueue_init(&sources, 0)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &sources); + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), deg_mode, 0)); + + igraph_vector_clear(res); + + /* Do we have nodes with no incoming vertices? */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(degrees)[i] == 0) { + IGRAPH_CHECK(igraph_dqueue_push(&sources, i)); + } + } + + /* Take all nodes with no incoming vertices and remove them */ + while (!igraph_dqueue_empty(&sources)) { + igraph_real_t tmp = igraph_dqueue_pop(&sources); node = (long) tmp; + /* Add the node to the result vector */ + igraph_vector_push_back(res, node); + /* Exclude the node from further source searches */ + VECTOR(degrees)[node] = -1; + /* Get the neighbors and decrease their degrees by one */ + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) node, mode)); + j = igraph_vector_size(&neis); + for (i = 0; i < j; i++) { + VECTOR(degrees)[(long)VECTOR(neis)[i]]--; + if (VECTOR(degrees)[(long)VECTOR(neis)[i]] == 0) { + IGRAPH_CHECK(igraph_dqueue_push(&sources, VECTOR(neis)[i])); + } + } + } + + if (igraph_vector_size(res) < no_of_nodes) { + IGRAPH_WARNING("graph contains a cycle, partial result is returned"); + } + + igraph_vector_destroy(°rees); + igraph_vector_destroy(&neis); + igraph_dqueue_destroy(&sources); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_is_dag + * Checks whether a graph is a directed acyclic graph (DAG) or not. + * + * + * A directed acyclic graph (DAG) is a directed graph with no cycles. + * + * \param graph The input graph. + * \param res Pointer to a boolean constant, the result + * is stored here. + * \return Error code. + * + * Time complexity: O(|V|+|E|), where |V| and |E| are the number of + * vertices and edges in the original input graph. + * + * \sa \ref igraph_topological_sorting() to get a possible topological + * sorting of a DAG. + */ +int igraph_is_dag(const igraph_t* graph, igraph_bool_t *res) { + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t degrees, neis; + igraph_dqueue_t sources; + long int node, i, j, nei, vertices_left; + + if (!igraph_is_directed(graph)) { + *res = 0; + return IGRAPH_SUCCESS; + } + + IGRAPH_VECTOR_INIT_FINALLY(°rees, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_dqueue_init(&sources, 0)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &sources); + IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), IGRAPH_OUT, 1)); + + vertices_left = no_of_nodes; + + /* Do we have nodes with no incoming edges? */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(degrees)[i] == 0) { + IGRAPH_CHECK(igraph_dqueue_push(&sources, i)); + } + } + + /* Take all nodes with no incoming edges and remove them */ + while (!igraph_dqueue_empty(&sources)) { + igraph_real_t tmp = igraph_dqueue_pop(&sources); node = (long) tmp; + /* Exclude the node from further source searches */ + VECTOR(degrees)[node] = -1; + vertices_left--; + /* Get the neighbors and decrease their degrees by one */ + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) node, + IGRAPH_IN)); + j = igraph_vector_size(&neis); + for (i = 0; i < j; i++) { + nei = (long)VECTOR(neis)[i]; + if (nei == node) { + continue; + } + VECTOR(degrees)[nei]--; + if (VECTOR(degrees)[nei] == 0) { + IGRAPH_CHECK(igraph_dqueue_push(&sources, nei)); + } + } + } + + *res = (vertices_left == 0); + if (vertices_left < 0) { + IGRAPH_WARNING("vertices_left < 0 in igraph_is_dag, possible bug"); + } + + igraph_vector_destroy(°rees); + igraph_vector_destroy(&neis); + igraph_dqueue_destroy(&sources); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_is_simple + * \brief Decides whether the input graph is a simple graph. + * + * + * A graph is a simple graph if it does not contain loop edges and + * multiple edges. + * + * \param graph The input graph. + * \param res Pointer to a boolean constant, the result + * is stored here. + * \return Error code. + * + * \sa \ref igraph_is_loop() and \ref igraph_is_multiple() to + * find the loops and multiple edges, \ref igraph_simplify() to + * get rid of them, or \ref igraph_has_multiple() to decide whether + * there is at least one multiple edge. + * + * Time complexity: O(|V|+|E|). + */ + +int igraph_is_simple(const igraph_t *graph, igraph_bool_t *res) { + long int vc = igraph_vcount(graph); + long int ec = igraph_ecount(graph); + + if (vc == 0 || ec == 0) { + *res = 1; + } else { + igraph_vector_t neis; + long int i, j, n; + igraph_bool_t found = 0; + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + for (i = 0; i < vc; i++) { + igraph_neighbors(graph, &neis, (igraph_integer_t) i, IGRAPH_OUT); + n = igraph_vector_size(&neis); + for (j = 0; j < n; j++) { + if (VECTOR(neis)[j] == i) { + found = 1; break; + } + if (j > 0 && VECTOR(neis)[j - 1] == VECTOR(neis)[j]) { + found = 1; break; + } + } + } + *res = !found; + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_has_loop + * \brief Returns whether the graph has at least one loop edge. + * + * + * A loop edge is an edge from a vertex to itself. + * \param graph The input graph. + * \param res Pointer to an initialized boolean vector for storing the result. + * + * \sa \ref igraph_simplify() to get rid of loop edges. + * + * Time complexity: O(e), the number of edges to check. + * + * \example examples/simple/igraph_has_loop.c + */ + +int igraph_has_loop(const igraph_t *graph, igraph_bool_t *res) { + long int i, m = igraph_ecount(graph); + + *res = 0; + + for (i = 0; i < m; i++) { + if (IGRAPH_FROM(graph, i) == IGRAPH_TO(graph, i)) { + *res = 1; + break; + } + } + + return 0; +} + +/** + * \function igraph_is_loop + * \brief Find the loop edges in a graph. + * + * + * A loop edge is an edge from a vertex to itself. + * \param graph The input graph. + * \param res Pointer to an initialized boolean vector for storing the result, + * it will be resized as needed. + * \param es The edges to check, for all edges supply \ref igraph_ess_all() here. + * \return Error code. + * + * \sa \ref igraph_simplify() to get rid of loop edges. + * + * Time complexity: O(e), the number of edges to check. + * + * \example examples/simple/igraph_is_loop.c + */ + +int igraph_is_loop(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es) { + igraph_eit_t eit; + long int i; + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_CHECK(igraph_vector_bool_resize(res, IGRAPH_EIT_SIZE(eit))); + + for (i = 0; !IGRAPH_EIT_END(eit); i++, IGRAPH_EIT_NEXT(eit)) { + long int e = IGRAPH_EIT_GET(eit); + VECTOR(*res)[i] = (IGRAPH_FROM(graph, e) == IGRAPH_TO(graph, e)) ? 1 : 0; + } + + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_has_multiple + * \brief Check whether the graph has at least one multiple edge. + * + * + * An edge is a multiple edge if there is another + * edge with the same head and tail vertices in the graph. + * + * \param graph The input graph. + * \param res Pointer to a boolean variable, the result will be stored here. + * \return Error code. + * + * \sa \ref igraph_count_multiple(), \ref igraph_is_multiple() and \ref igraph_simplify(). + * + * Time complexity: O(e*d), e is the number of edges to check and d is the + * average degree (out-degree in directed graphs) of the vertices at the + * tail of the edges. + * + * \example examples/simple/igraph_has_multiple.c + */ + +int igraph_has_multiple(const igraph_t *graph, igraph_bool_t *res) { + long int vc = igraph_vcount(graph); + long int ec = igraph_ecount(graph); + igraph_bool_t directed = igraph_is_directed(graph); + + if (vc == 0 || ec == 0) { + *res = 0; + } else { + igraph_vector_t neis; + long int i, j, n; + igraph_bool_t found = 0; + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + for (i = 0; i < vc && !found; i++) { + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) i, + IGRAPH_OUT)); + n = igraph_vector_size(&neis); + for (j = 1; j < n; j++) { + if (VECTOR(neis)[j - 1] == VECTOR(neis)[j]) { + /* If the graph is undirected, loop edges appear twice in the neighbor + * list, so check the next item as well */ + if (directed) { + /* Directed, so this is a real multiple edge */ + found = 1; break; + } else if (VECTOR(neis)[j - 1] != i) { + /* Undirected, but not a loop edge */ + found = 1; break; + } else if (j < n - 1 && VECTOR(neis)[j] == VECTOR(neis)[j + 1]) { + /* Undirected, loop edge, multiple times */ + found = 1; break; + } + } + } + } + *res = found; + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_is_multiple + * \brief Find the multiple edges in a graph. + * + * + * An edge is a multiple edge if there is another + * edge with the same head and tail vertices in the graph. + * + * + * Note that this function returns true only for the second or more + * appearances of the multiple edges. + * \param graph The input graph. + * \param res Pointer to a boolean vector, the result will be stored + * here. It will be resized as needed. + * \param es The edges to check. Supply \ref igraph_ess_all() if you want + * to check all edges. + * \return Error code. + * + * \sa \ref igraph_count_multiple(), \ref igraph_has_multiple() and \ref igraph_simplify(). + * + * Time complexity: O(e*d), e is the number of edges to check and d is the + * average degree (out-degree in directed graphs) of the vertices at the + * tail of the edges. + * + * \example examples/simple/igraph_is_multiple.c + */ + +int igraph_is_multiple(const igraph_t *graph, igraph_vector_bool_t *res, + igraph_es_t es) { + igraph_eit_t eit; + long int i; + igraph_lazy_inclist_t inclist; + + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_vector_bool_resize(res, IGRAPH_EIT_SIZE(eit))); + + for (i = 0; !IGRAPH_EIT_END(eit); i++, IGRAPH_EIT_NEXT(eit)) { + long int e = IGRAPH_EIT_GET(eit); + long int from = IGRAPH_FROM(graph, e); + long int to = IGRAPH_TO(graph, e); + igraph_vector_t *neis = igraph_lazy_inclist_get(&inclist, + (igraph_integer_t) from); + long int j, n = igraph_vector_size(neis); + VECTOR(*res)[i] = 0; + for (j = 0; j < n; j++) { + long int e2 = (long int) VECTOR(*neis)[j]; + long int to2 = IGRAPH_OTHER(graph, e2, from); + if (to2 == to && e2 < e) { + VECTOR(*res)[i] = 1; + } + } + } + + igraph_lazy_inclist_destroy(&inclist); + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/** + * \function igraph_count_multiple + * \brief Count the number of appearances of the edges in a graph. + * + * + * If the graph has no multiple edges then the result vector will be + * filled with ones. + * (An edge is a multiple edge if there is another + * edge with the same head and tail vertices in the graph.) + * + * + * \param graph The input graph. + * \param res Pointer to a vector, the result will be stored + * here. It will be resized as needed. + * \param es The edges to check. Supply \ref igraph_ess_all() if you want + * to check all edges. + * \return Error code. + * + * \sa \ref igraph_is_multiple() and \ref igraph_simplify(). + * + * Time complexity: O(e*d), e is the number of edges to check and d is the + * average degree (out-degree in directed graphs) of the vertices at the + * tail of the edges. + */ + + +int igraph_count_multiple(const igraph_t *graph, igraph_vector_t *res, igraph_es_t es) { + igraph_eit_t eit; + long int i; + igraph_lazy_inclist_t inclist; + + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, IGRAPH_OUT)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + IGRAPH_CHECK(igraph_vector_resize(res, IGRAPH_EIT_SIZE(eit))); + + for (i = 0; !IGRAPH_EIT_END(eit); i++, IGRAPH_EIT_NEXT(eit)) { + long int e = IGRAPH_EIT_GET(eit); + long int from = IGRAPH_FROM(graph, e); + long int to = IGRAPH_TO(graph, e); + igraph_vector_t *neis = igraph_lazy_inclist_get(&inclist, + (igraph_integer_t) from); + long int j, n = igraph_vector_size(neis); + VECTOR(*res)[i] = 0; + for (j = 0; j < n; j++) { + long int e2 = (long int) VECTOR(*neis)[j]; + long int to2 = IGRAPH_OTHER(graph, e2, from); + if (to2 == to) { + VECTOR(*res)[i] += 1; + } + } + /* for loop edges, divide the result by two */ + if (to == from) { + VECTOR(*res)[i] /= 2; + } + } + + igraph_lazy_inclist_destroy(&inclist); + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +/** + * \function igraph_girth + * \brief The girth of a graph is the length of the shortest circle in it. + * + * + * The current implementation works for undirected graphs only, + * directed graphs are treated as undirected graphs. Loop edges and + * multiple edges are ignored. + * + * If the graph is a forest (ie. acyclic), then zero is returned. + * + * This implementation is based on Alon Itai and Michael Rodeh: + * Finding a minimum circuit in a graph + * \emb Proceedings of the ninth annual ACM symposium on Theory of + * computing \eme, 1-10, 1977. The first implementation of this + * function was done by Keith Briggs, thanks Keith. + * \param graph The input graph. + * \param girth Pointer to an integer, if not \c NULL then the result + * will be stored here. + * \param circle Pointer to an initialized vector, the vertex ids in + * the shortest circle will be stored here. If \c NULL then it is + * ignored. + * \return Error code. + * + * Time complexity: O((|V|+|E|)^2), |V| is the number of vertices, |E| + * is the number of edges in the general case. If the graph has no + * circles at all then the function needs O(|V|+|E|) time to realize + * this and then it stops. + * + * \example examples/simple/igraph_girth.c + */ + +int igraph_girth(const igraph_t *graph, igraph_integer_t *girth, + igraph_vector_t *circle) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_dqueue_t q; + igraph_lazy_adjlist_t adjlist; + long int mincirc = LONG_MAX, minvertex = 0; + long int node; + igraph_bool_t triangle = 0; + igraph_vector_t *neis; + igraph_vector_long_t level; + long int stoplevel = no_of_nodes + 1; + igraph_bool_t anycircle = 0; + long int t1 = 0, t2 = 0; + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_ALL, + IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + IGRAPH_CHECK(igraph_vector_long_init(&level, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &level); + + for (node = 0; !triangle && node < no_of_nodes; node++) { + + /* Are there circles in this graph at all? */ + if (node == 1 && anycircle == 0) { + igraph_bool_t conn; + IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK)); + if (conn) { + /* No, there are none */ + break; + } + } + + anycircle = 0; + igraph_dqueue_clear(&q); + igraph_vector_long_null(&level); + IGRAPH_CHECK(igraph_dqueue_push(&q, node)); + VECTOR(level)[node] = 1; + + IGRAPH_ALLOW_INTERRUPTION(); + + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + long int actlevel = VECTOR(level)[actnode]; + long int i, n; + + if (actlevel >= stoplevel) { + break; + } + + neis = igraph_lazy_adjlist_get(&adjlist, (igraph_integer_t) actnode); + n = igraph_vector_size(neis); + for (i = 0; i < n; i++) { + long int nei = (long int) VECTOR(*neis)[i]; + long int neilevel = VECTOR(level)[nei]; + if (neilevel != 0) { + if (neilevel == actlevel - 1) { + continue; + } else { + /* found circle */ + stoplevel = neilevel; + anycircle = 1; + if (actlevel < mincirc) { + /* Is it a minimum circle? */ + mincirc = actlevel + neilevel - 1; + minvertex = node; + t1 = actnode; t2 = nei; + if (neilevel == 2) { + /* Is it a triangle? */ + triangle = 1; + } + } + if (neilevel == actlevel) { + break; + } + } + } else { + igraph_dqueue_push(&q, nei); + VECTOR(level)[nei] = actlevel + 1; + } + } + + } /* while q !empty */ + } /* node */ + + if (girth) { + if (mincirc == LONG_MAX) { + *girth = mincirc = 0; + } else { + *girth = (igraph_integer_t) mincirc; + } + } + + /* Store the actual circle, if needed */ + if (circle) { + IGRAPH_CHECK(igraph_vector_resize(circle, mincirc)); + if (mincirc != 0) { + long int i, n, idx = 0; + igraph_dqueue_clear(&q); + igraph_vector_long_null(&level); /* used for father pointers */ +#define FATHER(x) (VECTOR(level)[(x)]) + IGRAPH_CHECK(igraph_dqueue_push(&q, minvertex)); + FATHER(minvertex) = minvertex; + while (FATHER(t1) == 0 || FATHER(t2) == 0) { + long int actnode = (long int) igraph_dqueue_pop(&q); + neis = igraph_lazy_adjlist_get(&adjlist, (igraph_integer_t) actnode); + n = igraph_vector_size(neis); + for (i = 0; i < n; i++) { + long int nei = (long int) VECTOR(*neis)[i]; + if (FATHER(nei) == 0) { + FATHER(nei) = actnode + 1; + igraph_dqueue_push(&q, nei); + } + } + } /* while q !empty */ + /* Ok, now use FATHER to create the path */ + while (t1 != minvertex) { + VECTOR(*circle)[idx++] = t1; + t1 = FATHER(t1) - 1; + } + VECTOR(*circle)[idx] = minvertex; + idx = mincirc - 1; + while (t2 != minvertex) { + VECTOR(*circle)[idx--] = t2; + t2 = FATHER(t2) - 1; + } + } /* anycircle */ + } /* circle */ +#undef FATHER + + igraph_vector_long_destroy(&level); + igraph_dqueue_destroy(&q); + igraph_lazy_adjlist_destroy(&adjlist); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +int igraph_i_linegraph_undirected(const igraph_t *graph, igraph_t *linegraph); + +int igraph_i_linegraph_directed(const igraph_t *graph, igraph_t *linegraph); + +/* Note to self: tried using adjacency lists instead of igraph_incident queries, + * with minimal performance improvements on a graph with 70K vertices and 360K + * edges. (1.09s instead of 1.10s). I think it's not worth the fuss. */ +int igraph_i_linegraph_undirected(const igraph_t *graph, igraph_t *linegraph) { + long int no_of_edges = igraph_ecount(graph); + long int i, j, n; + igraph_vector_t adjedges, adjedges2; + igraph_vector_t edges; + long int prev = -1; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&adjedges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&adjedges2, 0); + + for (i = 0; i < no_of_edges; i++) { + long int from = IGRAPH_FROM(graph, i); + long int to = IGRAPH_TO(graph, i); + + IGRAPH_ALLOW_INTERRUPTION(); + + if (from != prev) { + IGRAPH_CHECK(igraph_incident(graph, &adjedges, (igraph_integer_t) from, + IGRAPH_ALL)); + } + n = igraph_vector_size(&adjedges); + for (j = 0; j < n; j++) { + long int e = (long int) VECTOR(adjedges)[j]; + if (e < i) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, e)); + } + } + + IGRAPH_CHECK(igraph_incident(graph, &adjedges2, (igraph_integer_t) to, + IGRAPH_ALL)); + n = igraph_vector_size(&adjedges2); + for (j = 0; j < n; j++) { + long int e = (long int) VECTOR(adjedges2)[j]; + if (e < i) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, e)); + } + } + + prev = from; + } + + igraph_vector_destroy(&adjedges); + igraph_vector_destroy(&adjedges2); + IGRAPH_FINALLY_CLEAN(2); + + igraph_create(linegraph, &edges, (igraph_integer_t) no_of_edges, + igraph_is_directed(graph)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +int igraph_i_linegraph_directed(const igraph_t *graph, igraph_t *linegraph) { + long int no_of_edges = igraph_ecount(graph); + long int i, j, n; + igraph_vector_t adjedges; + igraph_vector_t edges; + long int prev = -1; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&adjedges, 0); + + for (i = 0; i < no_of_edges; i++) { + long int from = IGRAPH_FROM(graph, i); + + IGRAPH_ALLOW_INTERRUPTION(); + + if (from != prev) { + IGRAPH_CHECK(igraph_incident(graph, &adjedges, (igraph_integer_t) from, + IGRAPH_IN)); + } + n = igraph_vector_size(&adjedges); + for (j = 0; j < n; j++) { + long int e = (long int) VECTOR(adjedges)[j]; + IGRAPH_CHECK(igraph_vector_push_back(&edges, e)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + } + + prev = from; + } + + igraph_vector_destroy(&adjedges); + IGRAPH_FINALLY_CLEAN(1); + igraph_create(linegraph, &edges, (igraph_integer_t) no_of_edges, igraph_is_directed(graph)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_linegraph + * \brief Create the line graph of a graph. + * + * The line graph L(G) of a G undirected graph is defined as follows. + * L(G) has one vertex for each edge in G and two vertices in L(G) are connected + * by an edge if their corresponding edges share an end point. + * + * + * The line graph L(G) of a G directed graph is slightly different, + * L(G) has one vertex for each edge in G and two vertices in L(G) are connected + * by a directed edge if the target of the first vertex's corresponding edge + * is the same as the source of the second vertex's corresponding edge. + * + * + * Edge \em i in the original graph will correspond to vertex \em i + * in the line graph. + * + * + * The first version of this function was contributed by Vincent Matossian, + * thanks. + * \param graph The input graph, may be directed or undirected. + * \param linegraph Pointer to an uninitialized graph object, the + * result is stored here. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of edges plus the number of vertices. + */ + +int igraph_linegraph(const igraph_t *graph, igraph_t *linegraph) { + + if (igraph_is_directed(graph)) { + return igraph_i_linegraph_directed(graph, linegraph); + } else { + return igraph_i_linegraph_undirected(graph, linegraph); + } +} + +/** + * \function igraph_add_edge + * \brief Adds a single edge to a graph. + * + * + * For directed graphs the edge points from \p from to \p to. + * + * + * Note that if you want to add many edges to a big graph, then it is + * inefficient to add them one by one, it is better to collect them into + * a vector and add all of them via a single \ref igraph_add_edges() call. + * \param igraph The graph. + * \param from The id of the first vertex of the edge. + * \param to The id of the second vertex of the edge. + * \return Error code. + * + * \sa \ref igraph_add_edges() to add many edges, \ref + * igraph_delete_edges() to remove edges and \ref + * igraph_add_vertices() to add vertices. + * + * Time complexity: O(|V|+|E|), the number of edges plus the number of + * vertices. + */ + +int igraph_add_edge(igraph_t *graph, igraph_integer_t from, igraph_integer_t to) { + + igraph_vector_t edges; + int ret; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 2); + + VECTOR(edges)[0] = from; + VECTOR(edges)[1] = to; + IGRAPH_CHECK(ret = igraph_add_edges(graph, &edges, 0)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return ret; +} + +/* + * \example examples/simple/graph_convergence_degree.c + */ + +int igraph_convergence_degree(const igraph_t *graph, igraph_vector_t *result, + igraph_vector_t *ins, igraph_vector_t *outs) { + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + long int i, j, k, n; + long int *geodist; + igraph_vector_int_t *eids; + igraph_vector_t *ins_p, *outs_p, ins_v, outs_v; + igraph_dqueue_t q; + igraph_inclist_t inclist; + igraph_bool_t directed = igraph_is_directed(graph); + + if (result != 0) { + IGRAPH_CHECK(igraph_vector_resize(result, no_of_edges)); + } + IGRAPH_CHECK(igraph_dqueue_init(&q, 100)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &q); + + if (ins == 0) { + ins_p = &ins_v; + IGRAPH_VECTOR_INIT_FINALLY(ins_p, no_of_edges); + } else { + ins_p = ins; + IGRAPH_CHECK(igraph_vector_resize(ins_p, no_of_edges)); + igraph_vector_null(ins_p); + } + + if (outs == 0) { + outs_p = &outs_v; + IGRAPH_VECTOR_INIT_FINALLY(outs_p, no_of_edges); + } else { + outs_p = outs; + IGRAPH_CHECK(igraph_vector_resize(outs_p, no_of_edges)); + igraph_vector_null(outs_p); + } + + geodist = igraph_Calloc(no_of_nodes, long int); + if (geodist == 0) { + IGRAPH_ERROR("Cannot calculate convergence degrees", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, geodist); + + /* Collect shortest paths originating from/to every node to correctly + * determine input field sizes */ + for (k = 0; k < (directed ? 2 : 1); k++) { + igraph_neimode_t neimode = (k == 0) ? IGRAPH_OUT : IGRAPH_IN; + igraph_real_t *vec; + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, neimode)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + vec = (k == 0) ? VECTOR(*ins_p) : VECTOR(*outs_p); + for (i = 0; i < no_of_nodes; i++) { + igraph_dqueue_clear(&q); + memset(geodist, 0, sizeof(long int) * (size_t) no_of_nodes); + geodist[i] = 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, i)); + IGRAPH_CHECK(igraph_dqueue_push(&q, 0.0)); + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + long int actdist = (long int) igraph_dqueue_pop(&q); + IGRAPH_ALLOW_INTERRUPTION(); + eids = igraph_inclist_get(&inclist, actnode); + n = igraph_vector_int_size(eids); + for (j = 0; j < n; j++) { + long int neighbor = IGRAPH_OTHER(graph, VECTOR(*eids)[j], actnode); + if (geodist[neighbor] != 0) { + /* we've already seen this node, another shortest path? */ + if (geodist[neighbor] - 1 == actdist + 1) { + /* Since this edge is in the BFS tree rooted at i, we must + * increase either the size of the infield or the outfield */ + if (!directed) { + if (actnode < neighbor) { + VECTOR(*ins_p)[(long int)VECTOR(*eids)[j]] += 1; + } else { + VECTOR(*outs_p)[(long int)VECTOR(*eids)[j]] += 1; + } + } else { + vec[(long int)VECTOR(*eids)[j]] += 1; + } + } else if (geodist[neighbor] - 1 < actdist + 1) { + continue; + } + } else { + /* we haven't seen this node yet */ + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1)); + /* Since this edge is in the BFS tree rooted at i, we must + * increase either the size of the infield or the outfield */ + if (!directed) { + if (actnode < neighbor) { + VECTOR(*ins_p)[(long int)VECTOR(*eids)[j]] += 1; + } else { + VECTOR(*outs_p)[(long int)VECTOR(*eids)[j]] += 1; + } + } else { + vec[(long int)VECTOR(*eids)[j]] += 1; + } + geodist[neighbor] = actdist + 2; + } + } + } + } + + igraph_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(1); + } + + if (result != 0) { + for (i = 0; i < no_of_edges; i++) + VECTOR(*result)[i] = (VECTOR(*ins_p)[i] - VECTOR(*outs_p)[i]) / + (VECTOR(*ins_p)[i] + VECTOR(*outs_p)[i]); + if (!directed) { + for (i = 0; i < no_of_edges; i++) + if (VECTOR(*result)[i] < 0) { + VECTOR(*result)[i] = -VECTOR(*result)[i]; + } + } + } + + if (ins == 0) { + igraph_vector_destroy(ins_p); + IGRAPH_FINALLY_CLEAN(1); + } + if (outs == 0) { + igraph_vector_destroy(outs_p); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_free(geodist); + igraph_dqueue_destroy(&q); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_shortest_paths_dijkstra + * Weighted shortest paths from some sources. + * + * This function is Dijkstra's algorithm to find the weighted + * shortest paths to all vertices from a single source. (It is run + * independently for the given sources.) It uses a binary heap for + * efficient implementation. + * + * \param graph The input graph, can be directed. + * \param res The result, a matrix. A pointer to an initialized matrix + * should be passed here. The matrix will be resized as needed. + * Each row contains the distances from a single source, to the + * vertices given in the \c to argument. + * Unreachable vertices has distance + * \c IGRAPH_INFINITY. + * \param from The source vertices. + * \param to The target vertices. It is not allowed to include a + * vertex twice or more. + * \param weights The edge weights. They must be all non-negative for + * Dijkstra's algorithm to work. An error code is returned if there + * is a negative edge weight in the weight vector. If this is a null + * pointer, then the + * unweighted version, \ref igraph_shortest_paths() is called. + * \param mode For directed graphs; whether to follow paths along edge + * directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or + * ignore edge directions completely (\c IGRAPH_ALL). It is ignored + * for undirected graphs. + * \return Error code. + * + * Time complexity: O(s*|E|log|E|+|V|), where |V| is the number of + * vertices, |E| the number of edges and s the number of sources. + * + * \sa \ref igraph_shortest_paths() for a (slightly) faster unweighted + * version or \ref igraph_shortest_paths_bellman_ford() for a weighted + * variant that works in the presence of negative edge weights (but no + * negative loops). + * + * \example examples/simple/dijkstra.c + */ + +int igraph_shortest_paths_dijkstra(const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + /* Implementation details. This is the basic Dijkstra algorithm, + with a binary heap. The heap is indexed, i.e. it stores not only + the distances, but also which vertex they belong to. + + From now on we use a 2-way heap, so the distances can be queried + directly from the heap. + + Dirty tricks: + - the opposite of the distance is stored in the heap, as it is a + maximum heap and we need a minimum heap. + - we don't use IGRAPH_INFINITY in the res matrix during the + computation, as IGRAPH_FINITE() might involve a function call + and we want to spare that. -1 will denote infinity instead. + */ + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_2wheap_t Q; + igraph_vit_t fromvit, tovit; + long int no_of_from, no_of_to; + igraph_lazy_inclist_t inclist; + long int i, j; + igraph_real_t my_infinity = IGRAPH_INFINITY; + igraph_bool_t all_to; + igraph_vector_t indexv; + + if (!weights) { + return igraph_shortest_paths(graph, res, from, to, mode); + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Weight vector length does not match", IGRAPH_EINVAL); + } + if (igraph_vector_min(weights) < 0) { + IGRAPH_ERROR("Weight vector must be non-negative", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vit_create(graph, from, &fromvit)); + IGRAPH_FINALLY(igraph_vit_destroy, &fromvit); + no_of_from = IGRAPH_VIT_SIZE(fromvit); + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + if ( (all_to = igraph_vs_is_all(&to)) ) { + no_of_to = no_of_nodes; + } else { + IGRAPH_VECTOR_INIT_FINALLY(&indexv, no_of_nodes); + IGRAPH_CHECK(igraph_vit_create(graph, to, &tovit)); + IGRAPH_FINALLY(igraph_vit_destroy, &tovit); + no_of_to = IGRAPH_VIT_SIZE(tovit); + for (i = 0; !IGRAPH_VIT_END(tovit); IGRAPH_VIT_NEXT(tovit)) { + long int v = IGRAPH_VIT_GET(tovit); + if (VECTOR(indexv)[v]) { + IGRAPH_ERROR("Duplicate vertices in `to', this is not allowed", + IGRAPH_EINVAL); + } + VECTOR(indexv)[v] = ++i; + } + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_from, no_of_to)); + igraph_matrix_fill(res, my_infinity); + + for (IGRAPH_VIT_RESET(fromvit), i = 0; + !IGRAPH_VIT_END(fromvit); + IGRAPH_VIT_NEXT(fromvit), i++) { + + long int reached = 0; + long int source = IGRAPH_VIT_GET(fromvit); + igraph_2wheap_clear(&Q); + igraph_2wheap_push_with_index(&Q, source, -1.0); + + while (!igraph_2wheap_empty(&Q)) { + long int minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_deactivate_max(&Q); + igraph_vector_t *neis; + long int nlen; + + if (all_to) { + MATRIX(*res, i, minnei) = mindist - 1.0; + } else { + if (VECTOR(indexv)[minnei]) { + MATRIX(*res, i, (long int)(VECTOR(indexv)[minnei] - 1)) = mindist - 1.0; + reached++; + if (reached == no_of_to) { + igraph_2wheap_clear(&Q); + break; + } + } + } + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_lazy_inclist_get(&inclist, (igraph_integer_t) minnei); + nlen = igraph_vector_size(neis); + for (j = 0; j < nlen; j++) { + long int edge = (long int) VECTOR(*neis)[j]; + long int tto = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_bool_t active = igraph_2wheap_has_active(&Q, tto); + igraph_bool_t has = igraph_2wheap_has_elem(&Q, tto); + igraph_real_t curdist = active ? -igraph_2wheap_get(&Q, tto) : 0.0; + if (!has) { + /* This is the first non-infinite distance */ + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, -altdist)); + } else if (altdist < curdist) { + /* This is a shorter path */ + IGRAPH_CHECK(igraph_2wheap_modify(&Q, tto, -altdist)); + } + } + + } /* !igraph_2wheap_empty(&Q) */ + + } /* !IGRAPH_VIT_END(fromvit) */ + + if (!all_to) { + igraph_vit_destroy(&tovit); + igraph_vector_destroy(&indexv); + IGRAPH_FINALLY_CLEAN(2); + } + + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + igraph_vit_destroy(&fromvit); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \ingroup structural + * \function igraph_get_shortest_paths_dijkstra + * \brief Calculates the weighted shortest paths from/to one vertex. + * + * + * If there is more than one path with the smallest weight between two vertices, this + * function gives only one of them. + * \param graph The graph object. + * \param vertices The result, the ids of the vertices along the paths. + * This is a pointer vector, each element points to a vector + * object. These should be initialized before passing them to + * the function, which will properly clear and/or resize them + * and fill the ids of the vertices along the geodesics from/to + * the vertices. Supply a null pointer here if you don't need + * these vectors. Normally, either this argument, or the \c + * edges should be non-null, but no error or warning is given + * if they are both null pointers. + * \param edges The result, the ids of the edges along the paths. + * This is a pointer vector, each element points to a vector + * object. These should be initialized before passing them to + * the function, which will properly clear and/or resize them + * and fill the ids of the vertices along the geodesics from/to + * the vertices. Supply a null pointer here if you don't need + * these vectors. Normally, either this argument, or the \c + * vertices should be non-null, but no error or warning is given + * if they are both null pointers. + * \param from The id of the vertex from/to which the geodesics are + * calculated. + * \param to Vertex sequence with the ids of the vertices to/from which the + * shortest paths will be calculated. A vertex might be given multiple + * times. + * \param weights a vector holding the edge weights. All weights must be + * positive. + * \param mode The type of shortest paths to be use for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing paths are calculated. + * \cli IGRAPH_IN + * the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \param predecessors A pointer to an initialized igraph vector or null. + * If not null, a vector containing the predecessor of each vertex in + * the single source shortest path tree is returned here. The + * predecessor of vertex i in the tree is the vertex from which vertex i + * was reached. The predecessor of the start vertex (in the \c from + * argument) is itself by definition. If the predecessor is -1, it means + * that the given vertex was not reached from the source during the + * search. Note that the search terminates if all the vertices in + * \c to are reached. + * \param inbound_edges A pointer to an initialized igraph vector or null. + * If not null, a vector containing the inbound edge of each vertex in + * the single source shortest path tree is returned here. The + * inbound edge of vertex i in the tree is the edge via which vertex i + * was reached. The start vertex and vertices that were not reached + * during the search will have -1 in the corresponding entry of the + * vector. Note that the search terminates if all the vertices in + * \c to are reached. + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p from is invalid vertex id, or the length of \p to is + * not the same as the length of \p res. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|E|log|E|+|V|), where |V| is the number of + * vertices and |E| is the number of edges + * + * \sa \ref igraph_shortest_paths_dijkstra() if you only need the path length but + * not the paths themselves, \ref igraph_get_shortest_paths() if all edge + * weights are equal. + * + * \example examples/simple/igraph_get_shortest_paths_dijkstra.c + */ +int igraph_get_shortest_paths_dijkstra(const igraph_t *graph, + igraph_vector_ptr_t *vertices, + igraph_vector_ptr_t *edges, + igraph_integer_t from, + igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode, + igraph_vector_long_t *predecessors, + igraph_vector_long_t *inbound_edges) { + /* Implementation details. This is the basic Dijkstra algorithm, + with a binary heap. The heap is indexed, i.e. it stores not only + the distances, but also which vertex they belong to. The other + mapping, i.e. getting the distance for a vertex is not in the + heap (that would by the double-indexed heap), but in the result + matrix. + + Dirty tricks: + - the opposite of the distance is stored in the heap, as it is a + maximum heap and we need a minimum heap. + - we don't use IGRAPH_INFINITY in the distance vector during the + computation, as IGRAPH_FINITE() might involve a function call + and we want to spare that. So we store distance+1.0 instead of + distance, and zero denotes infinity. + - `parents' assigns the inbound edge IDs of all vertices in the + shortest path tree to the vertices. In this implementation, the + edge ID + 1 is stored, zero means unreachable vertices. + */ + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_vit_t vit; + igraph_2wheap_t Q; + igraph_lazy_inclist_t inclist; + igraph_vector_t dists; + long int *parents; + igraph_bool_t *is_target; + long int i, to_reach; + + if (!weights) { + return igraph_get_shortest_paths(graph, vertices, edges, from, to, mode, + predecessors, inbound_edges); + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Weight vector length does not match", IGRAPH_EINVAL); + } + if (igraph_vector_min(weights) < 0) { + IGRAPH_ERROR("Weight vector must be non-negative", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + if (vertices && IGRAPH_VIT_SIZE(vit) != igraph_vector_ptr_size(vertices)) { + IGRAPH_ERROR("Size of `vertices' and `to' should match", IGRAPH_EINVAL); + } + if (edges && IGRAPH_VIT_SIZE(vit) != igraph_vector_ptr_size(edges)) { + IGRAPH_ERROR("Size of `edges' and `to' should match", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + IGRAPH_VECTOR_INIT_FINALLY(&dists, no_of_nodes); + igraph_vector_fill(&dists, -1.0); + + parents = igraph_Calloc(no_of_nodes, long int); + if (parents == 0) { + IGRAPH_ERROR("Can't calculate shortest paths", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, parents); + is_target = igraph_Calloc(no_of_nodes, igraph_bool_t); + if (is_target == 0) { + IGRAPH_ERROR("Can't calculate shortest paths", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, is_target); + + /* Mark the vertices we need to reach */ + to_reach = IGRAPH_VIT_SIZE(vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + if (!is_target[ (long int) IGRAPH_VIT_GET(vit) ]) { + is_target[ (long int) IGRAPH_VIT_GET(vit) ] = 1; + } else { + to_reach--; /* this node was given multiple times */ + } + } + + VECTOR(dists)[(long int)from] = 0.0; /* zero distance */ + parents[(long int)from] = 0; + igraph_2wheap_push_with_index(&Q, from, 0); + + while (!igraph_2wheap_empty(&Q) && to_reach > 0) { + long int nlen, minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_delete_max(&Q); + igraph_vector_t *neis; + + IGRAPH_ALLOW_INTERRUPTION(); + + if (is_target[minnei]) { + is_target[minnei] = 0; + to_reach--; + } + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_lazy_inclist_get(&inclist, (igraph_integer_t) minnei); + nlen = igraph_vector_size(neis); + for (i = 0; i < nlen; i++) { + long int edge = (long int) VECTOR(*neis)[i]; + long int tto = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = VECTOR(dists)[tto]; + if (curdist < 0) { + /* This is the first finite distance */ + VECTOR(dists)[tto] = altdist; + parents[tto] = edge + 1; + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, -altdist)); + } else if (altdist < curdist) { + /* This is a shorter path */ + VECTOR(dists)[tto] = altdist; + parents[tto] = edge + 1; + IGRAPH_CHECK(igraph_2wheap_modify(&Q, tto, -altdist)); + } + } + } /* !igraph_2wheap_empty(&Q) */ + + if (to_reach > 0) { + IGRAPH_WARNING("Couldn't reach some vertices"); + } + + /* Create `predecessors' if needed */ + if (predecessors) { + IGRAPH_CHECK(igraph_vector_long_resize(predecessors, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + if (i == from) { + /* i is the start vertex */ + VECTOR(*predecessors)[i] = i; + } else if (parents[i] <= 0) { + /* i was not reached */ + VECTOR(*predecessors)[i] = -1; + } else { + /* i was reached via the edge with ID = parents[i] - 1 */ + VECTOR(*predecessors)[i] = IGRAPH_OTHER(graph, parents[i] - 1, i); + } + } + } + + /* Create `inbound_edges' if needed */ + if (inbound_edges) { + IGRAPH_CHECK(igraph_vector_long_resize(inbound_edges, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + if (parents[i] <= 0) { + /* i was not reached */ + VECTOR(*inbound_edges)[i] = -1; + } else { + /* i was reached via the edge with ID = parents[i] - 1 */ + VECTOR(*inbound_edges)[i] = parents[i] - 1; + } + } + } + + /* Reconstruct the shortest paths based on vertex and/or edge IDs */ + if (vertices || edges) { + for (IGRAPH_VIT_RESET(vit), i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + long int node = IGRAPH_VIT_GET(vit); + long int size, act, edge; + igraph_vector_t *vvec = 0, *evec = 0; + if (vertices) { + vvec = VECTOR(*vertices)[i]; + igraph_vector_clear(vvec); + } + if (edges) { + evec = VECTOR(*edges)[i]; + igraph_vector_clear(evec); + } + + IGRAPH_ALLOW_INTERRUPTION(); + + size = 0; + act = node; + while (parents[act]) { + size++; + edge = parents[act] - 1; + act = IGRAPH_OTHER(graph, edge, act); + } + if (vvec) { + IGRAPH_CHECK(igraph_vector_resize(vvec, size + 1)); + VECTOR(*vvec)[size] = node; + } + if (evec) { + IGRAPH_CHECK(igraph_vector_resize(evec, size)); + } + act = node; + while (parents[act]) { + edge = parents[act] - 1; + act = IGRAPH_OTHER(graph, edge, act); + size--; + if (vvec) { + VECTOR(*vvec)[size] = act; + } + if (evec) { + VECTOR(*evec)[size] = edge; + } + } + } + } + + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + igraph_vector_destroy(&dists); + igraph_Free(is_target); + igraph_Free(parents); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(6); + + return 0; +} + +/** + * \function igraph_get_shortest_path_dijkstra + * Weighted shortest path from one vertex to another one. + * + * Calculates a single (positively) weighted shortest path from + * a single vertex to another one, using Dijkstra's algorithm. + * + * This function is a special case (and a wrapper) to + * \ref igraph_get_shortest_paths_dijkstra(). + * + * \param graph The input graph, it can be directed or undirected. + * \param vertices Pointer to an initialized vector or a null + * pointer. If not a null pointer, then the vertex ids along + * the path are stored here, including the source and target + * vertices. + * \param edges Pointer to an uninitialized vector or a null + * pointer. If not a null pointer, then the edge ids along the + * path are stored here. + * \param from The id of the source vertex. + * \param to The id of the target vertex. + * \param weights Vector of edge weights, in the order of edge + * ids. They must be non-negative, otherwise the algorithm does + * not work. + * \param mode A constant specifying how edge directions are + * considered in directed graphs. \c IGRAPH_OUT follows edge + * directions, \c IGRAPH_IN follows the opposite directions, + * and \c IGRAPH_ALL ignores edge directions. This argument is + * ignored for undirected graphs. + * \return Error code. + * + * Time complexity: O(|E|log|E|+|V|), |V| is the number of vertices, + * |E| is the number of edges in the graph. + * + * \sa \ref igraph_get_shortest_paths_dijkstra() for the version with + * more target vertices. + */ + +int igraph_get_shortest_path_dijkstra(const igraph_t *graph, + igraph_vector_t *vertices, + igraph_vector_t *edges, + igraph_integer_t from, + igraph_integer_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + + igraph_vector_ptr_t vertices2, *vp = &vertices2; + igraph_vector_ptr_t edges2, *ep = &edges2; + + if (vertices) { + IGRAPH_CHECK(igraph_vector_ptr_init(&vertices2, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &vertices2); + VECTOR(vertices2)[0] = vertices; + } else { + vp = 0; + } + if (edges) { + IGRAPH_CHECK(igraph_vector_ptr_init(&edges2, 1)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &edges2); + VECTOR(edges2)[0] = edges; + } else { + ep = 0; + } + + IGRAPH_CHECK(igraph_get_shortest_paths_dijkstra(graph, vp, ep, + from, igraph_vss_1(to), + weights, mode, 0, 0)); + + if (edges) { + igraph_vector_ptr_destroy(&edges2); + IGRAPH_FINALLY_CLEAN(1); + } + if (vertices) { + igraph_vector_ptr_destroy(&vertices2); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +int igraph_i_vector_tail_cmp(const void* path1, const void* path2); + +/* Compares two paths based on their last elements. Required by + * igraph_get_all_shortest_paths_dijkstra to put the final result + * in order. Assumes that both paths are pointers to igraph_vector_t + * objects and that they are not empty + */ +int igraph_i_vector_tail_cmp(const void* path1, const void* path2) { + return (int) (igraph_vector_tail(*(const igraph_vector_t**)path1) - + igraph_vector_tail(*(const igraph_vector_t**)path2)); +} + +/** + * \ingroup structural + * \function igraph_get_all_shortest_paths_dijkstra + * \brief Finds all shortest paths (geodesics) from a vertex to all other vertices. + * + * \param graph The graph object. + * \param res Pointer to an initialized pointer vector, the result + * will be stored here in igraph_vector_t objects. Each vector + * object contains the vertices along a shortest path from \p from + * to another vertex. The vectors are ordered according to their + * target vertex: first the shortest paths to vertex 0, then to + * vertex 1, etc. No data is included for unreachable vertices. + * \param nrgeo Pointer to an initialized igraph_vector_t object or + * NULL. If not NULL the number of shortest paths from \p from are + * stored here for every vertex in the graph. Note that the values + * will be accurate only for those vertices that are in the target + * vertex sequence (see \p to), since the search terminates as soon + * as all the target vertices have been found. + * \param from The id of the vertex from/to which the geodesics are + * calculated. + * \param to Vertex sequence with the ids of the vertices to/from which the + * shortest paths will be calculated. A vertex might be given multiple + * times. + * \param weights a vector holding the edge weights. All weights must be + * non-negative. + * \param mode The type of shortest paths to be use for the + * calculation in directed graphs. Possible values: + * \clist + * \cli IGRAPH_OUT + * the outgoing paths are calculated. + * \cli IGRAPH_IN + * the incoming paths are calculated. + * \cli IGRAPH_ALL + * the directed graph is considered as an + * undirected one for the computation. + * \endclist + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * not enough memory for temporary data. + * \cli IGRAPH_EINVVID + * \p from is invalid vertex id, or the length of \p to is + * not the same as the length of \p res. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|E|log|E|+|V|), where |V| is the number of + * vertices and |E| is the number of edges + * + * \sa \ref igraph_shortest_paths_dijkstra() if you only need the path + * length but not the paths themselves, \ref igraph_get_all_shortest_paths() + * if all edge weights are equal. + * + * \example examples/simple/igraph_get_all_shortest_paths_dijkstra.c + */ +int igraph_get_all_shortest_paths_dijkstra(const igraph_t *graph, + igraph_vector_ptr_t *res, + igraph_vector_t *nrgeo, + igraph_integer_t from, igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + /* Implementation details: see igraph_get_shortest_paths_dijkstra, + it's basically the same. + */ + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_vit_t vit; + igraph_2wheap_t Q; + igraph_lazy_inclist_t inclist; + igraph_vector_t dists, order; + igraph_vector_ptr_t parents; + unsigned char *is_target; + long int i, n, to_reach; + + if (!weights) { + return igraph_get_all_shortest_paths(graph, res, nrgeo, from, to, mode); + } + + if (res == 0 && nrgeo == 0) { + return IGRAPH_SUCCESS; + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Weight vector length does not match", IGRAPH_EINVAL); + } + if (igraph_vector_min(weights) < 0) { + IGRAPH_ERROR("Weight vector must be non-negative", IGRAPH_EINVAL); + } + + /* parents stores a vector for each vertex, listing the parent vertices + * of each vertex in the traversal */ + IGRAPH_CHECK(igraph_vector_ptr_init(&parents, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &parents); + igraph_vector_ptr_set_item_destructor(&parents, (igraph_finally_func_t*)igraph_vector_destroy); + for (i = 0; i < no_of_nodes; i++) { + igraph_vector_t* parent_vec; + parent_vec = igraph_Calloc(1, igraph_vector_t); + if (parent_vec == 0) { + IGRAPH_ERROR("cannot run igraph_get_all_shortest_paths", IGRAPH_ENOMEM); + } + IGRAPH_CHECK(igraph_vector_init(parent_vec, 0)); + VECTOR(parents)[i] = parent_vec; + } + + /* distance of each vertex from the root */ + IGRAPH_VECTOR_INIT_FINALLY(&dists, no_of_nodes); + igraph_vector_fill(&dists, -1.0); + + /* order lists the order of vertices in which they were found during + * the traversal */ + IGRAPH_VECTOR_INIT_FINALLY(&order, 0); + + /* boolean array to mark whether a given vertex is a target or not */ + is_target = igraph_Calloc(no_of_nodes, unsigned char); + if (is_target == 0) { + IGRAPH_ERROR("Can't calculate shortest paths", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, is_target); + + /* two-way heap storing vertices and distances */ + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + + /* lazy adjacency edge list to query neighbours efficiently */ + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + /* Mark the vertices we need to reach */ + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + to_reach = IGRAPH_VIT_SIZE(vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + if (!is_target[ (long int) IGRAPH_VIT_GET(vit) ]) { + is_target[ (long int) IGRAPH_VIT_GET(vit) ] = 1; + } else { + to_reach--; /* this node was given multiple times */ + } + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + VECTOR(dists)[(long int)from] = 0.0; /* zero distance */ + igraph_2wheap_push_with_index(&Q, from, 0); + + while (!igraph_2wheap_empty(&Q) && to_reach > 0) { + long int nlen, minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_delete_max(&Q); + igraph_vector_t *neis; + + IGRAPH_ALLOW_INTERRUPTION(); + + /* + printf("Reached vertex %ld, is_target[%ld] = %d, %ld to go\n", + minnei, minnei, (int)is_target[minnei], to_reach - is_target[minnei]); + */ + + if (is_target[minnei]) { + is_target[minnei] = 0; + to_reach--; + } + + /* Mark that we have reached this vertex */ + IGRAPH_CHECK(igraph_vector_push_back(&order, minnei)); + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_lazy_inclist_get(&inclist, (igraph_integer_t) minnei); + nlen = igraph_vector_size(neis); + for (i = 0; i < nlen; i++) { + long int edge = (long int) VECTOR(*neis)[i]; + long int tto = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_real_t curdist = VECTOR(dists)[tto]; + igraph_vector_t *parent_vec; + + if (curdist < 0) { + /* This is the first non-infinite distance */ + VECTOR(dists)[tto] = altdist; + parent_vec = (igraph_vector_t*)VECTOR(parents)[tto]; + IGRAPH_CHECK(igraph_vector_push_back(parent_vec, minnei)); + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, -altdist)); + } else if (altdist == curdist && VECTOR(*weights)[edge] > 0) { + /* This is an alternative path with exactly the same length. + * Note that we consider this case only if the edge via which we + * reached the node has a nonzero weight; otherwise we could create + * infinite loops in undirected graphs by traversing zero-weight edges + * back-and-forth */ + parent_vec = (igraph_vector_t*)VECTOR(parents)[tto]; + IGRAPH_CHECK(igraph_vector_push_back(parent_vec, minnei)); + } else if (altdist < curdist) { + /* This is a shorter path */ + VECTOR(dists)[tto] = altdist; + parent_vec = (igraph_vector_t*)VECTOR(parents)[tto]; + igraph_vector_clear(parent_vec); + IGRAPH_CHECK(igraph_vector_push_back(parent_vec, minnei)); + IGRAPH_CHECK(igraph_2wheap_modify(&Q, tto, -altdist)); + } + } + } /* !igraph_2wheap_empty(&Q) */ + + if (to_reach > 0) { + IGRAPH_WARNING("Couldn't reach some vertices"); + } + + /* we don't need these anymore */ + igraph_lazy_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + IGRAPH_FINALLY_CLEAN(2); + + /* + printf("Order:\n"); + igraph_vector_print(&order); + + printf("Parent vertices:\n"); + for (i = 0; i < no_of_nodes; i++) { + if (igraph_vector_size(VECTOR(parents)[i]) > 0) { + printf("[%ld]: ", (long int)i); + igraph_vector_print(VECTOR(parents)[i]); + } + } + */ + + if (nrgeo) { + IGRAPH_CHECK(igraph_vector_resize(nrgeo, no_of_nodes)); + igraph_vector_null(nrgeo); + + /* Theoretically, we could calculate nrgeo in parallel with the traversal. + * However, that way we would have to check whether nrgeo is null or not + * every time we want to update some element in nrgeo. Since we need the + * order vector anyway for building the final result, we could just as well + * build nrgeo here. + */ + VECTOR(*nrgeo)[(long int)from] = 1; + n = igraph_vector_size(&order); + for (i = 1; i < n; i++) { + long int node, j, k; + igraph_vector_t *parent_vec; + + node = (long int)VECTOR(order)[i]; + /* now, take the parent vertices */ + parent_vec = (igraph_vector_t*)VECTOR(parents)[node]; + k = igraph_vector_size(parent_vec); + for (j = 0; j < k; j++) { + VECTOR(*nrgeo)[node] += VECTOR(*nrgeo)[(long int)VECTOR(*parent_vec)[j]]; + } + } + } + + if (res) { + igraph_vector_t *path, *paths_index, *parent_vec; + igraph_stack_t stack; + long int j, node; + + /* a shortest path from the starting vertex to vertex i can be + * obtained by calculating the shortest paths from the "parents" + * of vertex i in the traversal. Knowing which of the vertices + * are "targets" (see is_target), we can collect for which other + * vertices do we need to calculate the shortest paths. We reuse + * is_target for that; is_target = 0 means that we don't need the + * vertex, is_target = 1 means that the vertex is a target (hence + * we need it), is_target = 2 means that the vertex is not a target + * but it stands between a shortest path between the root and one + * of the targets + */ + if (igraph_vs_is_all(&to)) { + memset(is_target, 1, sizeof(unsigned char) * (size_t) no_of_nodes); + } else { + memset(is_target, 0, sizeof(unsigned char) * (size_t) no_of_nodes); + + IGRAPH_CHECK(igraph_stack_init(&stack, 0)); + IGRAPH_FINALLY(igraph_stack_destroy, &stack); + + /* Add the target vertices to the queue */ + IGRAPH_CHECK(igraph_vit_create(graph, to, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + for (IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit)) { + i = (long int) IGRAPH_VIT_GET(vit); + if (!is_target[i]) { + is_target[i] = 1; + IGRAPH_CHECK(igraph_stack_push(&stack, i)); + } + } + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + while (!igraph_stack_empty(&stack)) { + /* For each parent of node i, get its parents */ + igraph_real_t el = igraph_stack_pop(&stack); + parent_vec = (igraph_vector_t*)VECTOR(parents)[(long int) el]; + i = igraph_vector_size(parent_vec); + + for (j = 0; j < i; j++) { + /* For each parent, check if it's already in the stack. + * If not, push it and mark it in is_target */ + n = (long int) VECTOR(*parent_vec)[j]; + if (!is_target[n]) { + is_target[n] = 2; + IGRAPH_CHECK(igraph_stack_push(&stack, n)); + } + } + } + igraph_stack_destroy(&stack); + IGRAPH_FINALLY_CLEAN(1); + } + + /* now, reconstruct the shortest paths from the parent list in the + * order we've found the nodes during the traversal. + * dists is being re-used as a vector where element i tells the + * index in res where the shortest paths leading to vertex i + * start, plus one (so that zero means that there are no paths + * for a given vertex). + */ + paths_index = &dists; + n = igraph_vector_size(&order); + igraph_vector_null(paths_index); + + /* clear the paths vector */ + igraph_vector_ptr_clear(res); + igraph_vector_ptr_set_item_destructor(res, + (igraph_finally_func_t*)igraph_vector_destroy); + + /* by definition, the shortest path leading to the starting vertex + * consists of the vertex itself only */ + path = igraph_Calloc(1, igraph_vector_t); + if (path == 0) + IGRAPH_ERROR("cannot run igraph_get_all_shortest_paths_dijkstra", + IGRAPH_ENOMEM); + IGRAPH_FINALLY(igraph_free, path); + IGRAPH_CHECK(igraph_vector_init(path, 1)); + IGRAPH_CHECK(igraph_vector_ptr_push_back(res, path)); + IGRAPH_FINALLY_CLEAN(1); /* ownership of path passed to res */ + VECTOR(*path)[0] = from; + VECTOR(*paths_index)[(long int)from] = 1; + + for (i = 1; i < n; i++) { + long int m, path_count; + igraph_vector_t *parent_path; + + node = (long int) VECTOR(order)[i]; + + /* if we don't need the shortest paths for this node (because + * it is not standing in a shortest path between the source + * node and any of the target nodes), skip it */ + if (!is_target[node]) { + continue; + } + + IGRAPH_ALLOW_INTERRUPTION(); + + /* we are calculating the shortest paths of node now. */ + /* first, we update the paths_index */ + path_count = igraph_vector_ptr_size(res); + VECTOR(*paths_index)[node] = path_count + 1; + /* res_end = (igraph_vector_t*)&(VECTOR(*res)[path_count]); */ + + /* now, take the parent vertices */ + parent_vec = (igraph_vector_t*)VECTOR(parents)[node]; + m = igraph_vector_size(parent_vec); + + /* + printf("Calculating shortest paths to vertex %ld\n", node); + printf("Parents are: "); + igraph_vector_print(parent_vec); + */ + + for (j = 0; j < m; j++) { + /* for each parent, copy the shortest paths leading to that parent + * and add the current vertex in the end */ + long int parent_node = (long int) VECTOR(*parent_vec)[j]; + long int parent_path_idx = (long int) VECTOR(*paths_index)[parent_node] - 1; + /* + printf(" Considering parent: %ld\n", parent_node); + printf(" Paths to parent start at index %ld in res\n", parent_path_idx); + */ + assert(parent_path_idx >= 0); + for (; parent_path_idx < path_count; parent_path_idx++) { + parent_path = (igraph_vector_t*)VECTOR(*res)[parent_path_idx]; + if (igraph_vector_tail(parent_path) != parent_node) { + break; + } + + path = igraph_Calloc(1, igraph_vector_t); + if (path == 0) + IGRAPH_ERROR("cannot run igraph_get_all_shortest_paths_dijkstra", + IGRAPH_ENOMEM); + IGRAPH_FINALLY(igraph_free, path); + IGRAPH_CHECK(igraph_vector_copy(path, parent_path)); + IGRAPH_CHECK(igraph_vector_ptr_push_back(res, path)); + IGRAPH_FINALLY_CLEAN(1); /* ownership of path passed to res */ + IGRAPH_CHECK(igraph_vector_push_back(path, node)); + } + } + } + + /* remove the destructor from the path vector */ + igraph_vector_ptr_set_item_destructor(res, 0); + + /* free those paths from the result vector which we won't need */ + n = igraph_vector_ptr_size(res); + j = 0; + for (i = 0; i < n; i++) { + igraph_real_t tmp; + path = (igraph_vector_t*)VECTOR(*res)[i]; + tmp = igraph_vector_tail(path); + if (is_target[(long int)tmp] == 1) { + /* we need this path, keep it */ + VECTOR(*res)[j] = path; + j++; + } else { + /* we don't need this path, free it */ + igraph_vector_destroy(path); free(path); + } + } + IGRAPH_CHECK(igraph_vector_ptr_resize(res, j)); + + /* sort the paths by the target vertices */ + igraph_vector_ptr_sort(res, igraph_i_vector_tail_cmp); + } + + /* free the allocated memory */ + igraph_vector_destroy(&order); + igraph_Free(is_target); + igraph_vector_destroy(&dists); + igraph_vector_ptr_destroy_all(&parents); + IGRAPH_FINALLY_CLEAN(4); + + return 0; +} + +/** + * \function igraph_shortest_paths_bellman_ford + * Weighted shortest paths from some sources allowing negative weights. + * + * This function is the Bellman-Ford algorithm to find the weighted + * shortest paths to all vertices from a single source. (It is run + * independently for the given sources.). If there are no negative + * weights, you are better off with \ref igraph_shortest_paths_dijkstra() . + * + * \param graph The input graph, can be directed. + * \param res The result, a matrix. A pointer to an initialized matrix + * should be passed here, the matrix will be resized if needed. + * Each row contains the distances from a single source, to all + * vertices in the graph, in the order of vertex ids. For unreachable + * vertices the matrix contains \c IGRAPH_INFINITY. + * \param from The source vertices. + * \param weights The edge weights. There mustn't be any closed loop in + * the graph that has a negative total weight (since this would allow + * us to decrease the weight of any path containing at least a single + * vertex of this loop infinitely). If this is a null pointer, then the + * unweighted version, \ref igraph_shortest_paths() is called. + * \param mode For directed graphs; whether to follow paths along edge + * directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or + * ignore edge directions completely (\c IGRAPH_ALL). It is ignored + * for undirected graphs. + * \return Error code. + * + * Time complexity: O(s*|E|*|V|), where |V| is the number of + * vertices, |E| the number of edges and s the number of sources. + * + * \sa \ref igraph_shortest_paths() for a faster unweighted version + * or \ref igraph_shortest_paths_dijkstra() if you do not have negative + * edge weights. + * + * \example examples/simple/bellman_ford.c + */ + +int igraph_shortest_paths_bellman_ford(const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights, + igraph_neimode_t mode) { + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_lazy_inclist_t inclist; + long int i, j, k; + long int no_of_from, no_of_to; + igraph_dqueue_t Q; + igraph_vector_t clean_vertices; + igraph_vector_t num_queued; + igraph_vit_t fromvit, tovit; + igraph_real_t my_infinity = IGRAPH_INFINITY; + igraph_bool_t all_to; + igraph_vector_t dist; + + /* + - speedup: a vertex is marked clean if its distance from the source + did not change during the last phase. Neighbors of a clean vertex + are not relaxed again, since it would mean no change in the + shortest path values. Dirty vertices are queued. Negative loops can + be detected by checking whether a vertex has been queued at least + n times. + */ + if (!weights) { + return igraph_shortest_paths(graph, res, from, to, mode); + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Weight vector length does not match", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vit_create(graph, from, &fromvit)); + IGRAPH_FINALLY(igraph_vit_destroy, &fromvit); + no_of_from = IGRAPH_VIT_SIZE(fromvit); + + IGRAPH_DQUEUE_INIT_FINALLY(&Q, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&clean_vertices, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&num_queued, no_of_nodes); + IGRAPH_CHECK(igraph_lazy_inclist_init(graph, &inclist, mode)); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &inclist); + + if ( (all_to = igraph_vs_is_all(&to)) ) { + no_of_to = no_of_nodes; + } else { + IGRAPH_CHECK(igraph_vit_create(graph, to, &tovit)); + IGRAPH_FINALLY(igraph_vit_destroy, &tovit); + no_of_to = IGRAPH_VIT_SIZE(tovit); + } + + IGRAPH_VECTOR_INIT_FINALLY(&dist, no_of_nodes); + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_from, no_of_to)); + + for (IGRAPH_VIT_RESET(fromvit), i = 0; + !IGRAPH_VIT_END(fromvit); + IGRAPH_VIT_NEXT(fromvit), i++) { + long int source = IGRAPH_VIT_GET(fromvit); + + igraph_vector_fill(&dist, my_infinity); + VECTOR(dist)[source] = 0; + igraph_vector_null(&clean_vertices); + igraph_vector_null(&num_queued); + + /* Fill the queue with vertices to be checked */ + for (j = 0; j < no_of_nodes; j++) { + IGRAPH_CHECK(igraph_dqueue_push(&Q, j)); + } + + while (!igraph_dqueue_empty(&Q)) { + igraph_vector_t *neis; + long int nlen; + + j = (long int) igraph_dqueue_pop(&Q); + VECTOR(clean_vertices)[j] = 1; + VECTOR(num_queued)[j] += 1; + if (VECTOR(num_queued)[j] > no_of_nodes) { + IGRAPH_ERROR("cannot run Bellman-Ford algorithm", IGRAPH_ENEGLOOP); + } + + /* If we cannot get to j in finite time yet, there is no need to relax + * its edges */ + if (!IGRAPH_FINITE(VECTOR(dist)[j])) { + continue; + } + + neis = igraph_lazy_inclist_get(&inclist, (igraph_integer_t) j); + nlen = igraph_vector_size(neis); + + for (k = 0; k < nlen; k++) { + long int nei = (long int) VECTOR(*neis)[k]; + long int target = IGRAPH_OTHER(graph, nei, j); + if (VECTOR(dist)[target] > VECTOR(dist)[j] + VECTOR(*weights)[nei]) { + /* relax the edge */ + VECTOR(dist)[target] = VECTOR(dist)[j] + VECTOR(*weights)[nei]; + if (VECTOR(clean_vertices)[target]) { + VECTOR(clean_vertices)[target] = 0; + IGRAPH_CHECK(igraph_dqueue_push(&Q, target)); + } + } + } + } + + /* Copy it to the result */ + if (all_to) { + igraph_matrix_set_row(res, &dist, i); + } else { + for (IGRAPH_VIT_RESET(tovit), j = 0; !IGRAPH_VIT_END(tovit); + IGRAPH_VIT_NEXT(tovit), j++) { + long int v = IGRAPH_VIT_GET(tovit); + MATRIX(*res, i, j) = VECTOR(dist)[v]; + } + } + } + + igraph_vector_destroy(&dist); + IGRAPH_FINALLY_CLEAN(1); + + if (!all_to) { + igraph_vit_destroy(&tovit); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vit_destroy(&fromvit); + igraph_dqueue_destroy(&Q); + igraph_vector_destroy(&clean_vertices); + igraph_vector_destroy(&num_queued); + igraph_lazy_inclist_destroy(&inclist); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +/** + * \function igraph_shortest_paths_johnson + * Calculate shortest paths from some sources using Johnson's algorithm. + * + * See Wikipedia at http://en.wikipedia.org/wiki/Johnson's_algorithm + * for Johnson's algorithm. This algorithm works even if the graph + * contains negative edge weights, and it is worth using it if we + * calculate the shortest paths from many sources. + * + * If no edge weights are supplied, then the unweighted + * version, \ref igraph_shortest_paths() is called. + * + * If all the supplied edge weights are non-negative, + * then Dijkstra's algorithm is used by calling + * \ref igraph_shortest_paths_dijkstra(). + * + * \param graph The input graph, typically it is directed. + * \param res Pointer to an initialized matrix, the result will be + * stored here, one line for each source vertex, one column for each + * target vertex. + * \param from The source vertices. + * \param to The target vertices. It is not allowed to include a + * vertex twice or more. + * \param weights Optional edge weights. If it is a null-pointer, then + * the unweighted breadth-first search based \ref + * igraph_shortest_paths() will be called. + * \return Error code. + * + * Time complexity: O(s|V|log|V|+|V||E|), |V| and |E| are the number + * of vertices and edges, s is the number of source vertices. + * + * \sa \ref igraph_shortest_paths() for a faster unweighted version + * or \ref igraph_shortest_paths_dijkstra() if you do not have negative + * edge weights, \ref igraph_shortest_paths_bellman_ford() if you only + * need to calculate shortest paths from a couple of sources. + */ + +int igraph_shortest_paths_johnson(const igraph_t *graph, + igraph_matrix_t *res, + const igraph_vs_t from, + const igraph_vs_t to, + const igraph_vector_t *weights) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_t newgraph; + igraph_vector_t edges, newweights; + igraph_matrix_t bfres; + long int i, ptr; + long int nr, nc; + igraph_vit_t fromvit; + + /* If no weights, then we can just run the unweighted version */ + if (!weights) { + return igraph_shortest_paths(graph, res, from, to, IGRAPH_OUT); + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Weight vector length does not match", IGRAPH_EINVAL); + } + + /* If no negative weights, then we can run Dijkstra's algorithm */ + if (igraph_vector_min(weights) >= 0) { + return igraph_shortest_paths_dijkstra(graph, res, from, to, + weights, IGRAPH_OUT); + } + + if (!igraph_is_directed(graph)) { + IGRAPH_ERROR("Johnson's shortest path: undirected graph and negative weight", + IGRAPH_EINVAL); + } + + /* ------------------------------------------------------------ */ + /* -------------------- Otherwise proceed --------------------- */ + + IGRAPH_MATRIX_INIT_FINALLY(&bfres, 0, 0); + IGRAPH_VECTOR_INIT_FINALLY(&newweights, 0); + + IGRAPH_CHECK(igraph_empty(&newgraph, (igraph_integer_t) no_of_nodes + 1, + igraph_is_directed(graph))); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + + /* Add a new node to the graph, plus edges from it to all the others. */ + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2 + no_of_nodes * 2); + igraph_get_edgelist(graph, &edges, /*bycol=*/ 0); + igraph_vector_resize(&edges, no_of_edges * 2 + no_of_nodes * 2); + for (i = 0, ptr = no_of_edges * 2; i < no_of_nodes; i++) { + VECTOR(edges)[ptr++] = no_of_nodes; + VECTOR(edges)[ptr++] = i; + } + IGRAPH_CHECK(igraph_add_edges(&newgraph, &edges, 0)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_CHECK(igraph_vector_reserve(&newweights, no_of_edges + no_of_nodes)); + igraph_vector_update(&newweights, weights); + igraph_vector_resize(&newweights, no_of_edges + no_of_nodes); + for (i = no_of_edges; i < no_of_edges + no_of_nodes; i++) { + VECTOR(newweights)[i] = 0; + } + + /* Run Bellmann-Ford algorithm on the new graph, starting from the + new vertex. */ + + IGRAPH_CHECK(igraph_shortest_paths_bellman_ford(&newgraph, &bfres, + igraph_vss_1((igraph_integer_t) no_of_nodes), + igraph_vss_all(), &newweights, IGRAPH_OUT)); + + igraph_destroy(&newgraph); + IGRAPH_FINALLY_CLEAN(1); + + /* Now the edges of the original graph are reweighted, using the + values from the BF algorithm. Instead of w(u,v) we will have + w(u,v) + h(u) - h(v) */ + + igraph_vector_resize(&newweights, no_of_edges); + for (i = 0; i < no_of_edges; i++) { + long int ffrom = IGRAPH_FROM(graph, i); + long int tto = IGRAPH_TO(graph, i); + VECTOR(newweights)[i] += MATRIX(bfres, 0, ffrom) - MATRIX(bfres, 0, tto); + } + + /* Run Dijkstra's algorithm on the new weights */ + IGRAPH_CHECK(igraph_shortest_paths_dijkstra(graph, res, from, + to, &newweights, + IGRAPH_OUT)); + + igraph_vector_destroy(&newweights); + IGRAPH_FINALLY_CLEAN(1); + + /* Reweight the shortest paths */ + nr = igraph_matrix_nrow(res); + nc = igraph_matrix_ncol(res); + + IGRAPH_CHECK(igraph_vit_create(graph, from, &fromvit)); + IGRAPH_FINALLY(igraph_vit_destroy, &fromvit); + + for (i = 0; i < nr; i++, IGRAPH_VIT_NEXT(fromvit)) { + long int v1 = IGRAPH_VIT_GET(fromvit); + if (igraph_vs_is_all(&to)) { + long int v2; + for (v2 = 0; v2 < nc; v2++) { + igraph_real_t sub = MATRIX(bfres, 0, v1) - MATRIX(bfres, 0, v2); + MATRIX(*res, i, v2) -= sub; + } + } else { + long int j; + igraph_vit_t tovit; + IGRAPH_CHECK(igraph_vit_create(graph, to, &tovit)); + IGRAPH_FINALLY(igraph_vit_destroy, &tovit); + for (j = 0, IGRAPH_VIT_RESET(tovit); j < nc; j++, IGRAPH_VIT_NEXT(tovit)) { + long int v2 = IGRAPH_VIT_GET(tovit); + igraph_real_t sub = MATRIX(bfres, 0, v1) - MATRIX(bfres, 0, v2); + MATRIX(*res, i, v2) -= sub; + } + igraph_vit_destroy(&tovit); + IGRAPH_FINALLY_CLEAN(1); + } + } + + igraph_vit_destroy(&fromvit); + igraph_matrix_destroy(&bfres); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_unfold_tree + * Unfolding a graph into a tree, by possibly multiplicating its vertices. + * + * A graph is converted into a tree (or forest, if it is unconnected), + * by performing a breadth-first search on it, and replicating + * vertices that were found a second, third, etc. time. + * \param graph The input graph, it can be either directed or + * undirected. + * \param tree Pointer to an uninitialized graph object, the result is + * stored here. + * \param mode For directed graphs; whether to follow paths along edge + * directions (\c IGRAPH_OUT), or the opposite (\c IGRAPH_IN), or + * ignore edge directions completely (\c IGRAPH_ALL). It is ignored + * for undirected graphs. + * \param roots A numeric vector giving the root vertex, or vertices + * (if the graph is not connected), to start from. + * \param vertex_index Pointer to an initialized vector, or a null + * pointer. If not a null pointer, then a mapping from the vertices + * in the new graph to the ones in the original is created here. + * \return Error code. + * + * Time complexity: O(n+m), linear in the number vertices and edges. + * + */ + +int igraph_unfold_tree(const igraph_t *graph, igraph_t *tree, + igraph_neimode_t mode, const igraph_vector_t *roots, + igraph_vector_t *vertex_index) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + long int no_of_roots = igraph_vector_size(roots); + long int tree_vertex_count = no_of_nodes; + + igraph_vector_t edges; + igraph_vector_bool_t seen_vertices; + igraph_vector_bool_t seen_edges; + + igraph_dqueue_t Q; + igraph_vector_t neis; + + long int i, n, r, v_ptr = no_of_nodes; + + /* TODO: handle not-connected graphs, multiple root vertices */ + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + igraph_vector_reserve(&edges, no_of_edges * 2); + IGRAPH_DQUEUE_INIT_FINALLY(&Q, 100); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&seen_vertices, no_of_nodes); + IGRAPH_VECTOR_BOOL_INIT_FINALLY(&seen_edges, no_of_edges); + + if (vertex_index) { + IGRAPH_CHECK(igraph_vector_resize(vertex_index, no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*vertex_index)[i] = i; + } + } + + for (r = 0; r < no_of_roots; r++) { + + long int root = (long int) VECTOR(*roots)[r]; + VECTOR(seen_vertices)[root] = 1; + igraph_dqueue_push(&Q, root); + + while (!igraph_dqueue_empty(&Q)) { + long int actnode = (long int) igraph_dqueue_pop(&Q); + + IGRAPH_CHECK(igraph_incident(graph, &neis, (igraph_integer_t) actnode, mode)); + n = igraph_vector_size(&neis); + for (i = 0; i < n; i++) { + + long int edge = (long int) VECTOR(neis)[i]; + long int from = IGRAPH_FROM(graph, edge); + long int to = IGRAPH_TO(graph, edge); + long int nei = IGRAPH_OTHER(graph, edge, actnode); + + if (! VECTOR(seen_edges)[edge]) { + + VECTOR(seen_edges)[edge] = 1; + + if (! VECTOR(seen_vertices)[nei]) { + + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + + VECTOR(seen_vertices)[nei] = 1; + IGRAPH_CHECK(igraph_dqueue_push(&Q, nei)); + + } else { + + tree_vertex_count++; + if (vertex_index) { + IGRAPH_CHECK(igraph_vector_push_back(vertex_index, nei)); + } + + if (from == nei) { + igraph_vector_push_back(&edges, v_ptr++); + igraph_vector_push_back(&edges, to); + } else { + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, v_ptr++); + } + } + } + + } /* for i + * + * An undirected graph only has mutual edges, by definition. + * + * + * Edge multiplicity is not considered here, e.g. if there are two + * (A,B) edges and one (B,A) edge, then all three are considered to be + * mutual. + * + * \param graph The input graph. + * \param res Pointer to an initialized vector, the result is stored + * here. + * \param es The sequence of edges to check. Supply + * igraph_ess_all() for all edges, see \ref + * igraph_ess_all(). + * \return Error code. + * + * Time complexity: O(n log(d)), n is the number of edges supplied, d + * is the maximum in-degree of the vertices that are targets of the + * supplied edges. An upper limit of the time complexity is O(n log(|E|)), + * |E| is the number of edges in the graph. + */ + +int igraph_is_mutual(igraph_t *graph, igraph_vector_bool_t *res, igraph_es_t es) { + + igraph_eit_t eit; + igraph_lazy_adjlist_t adjlist; + long int i; + + /* How many edges do we have? */ + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + IGRAPH_CHECK(igraph_vector_bool_resize(res, IGRAPH_EIT_SIZE(eit))); + + /* An undirected graph has mutual edges by definition, + res is already properly resized */ + if (! igraph_is_directed(graph)) { + igraph_vector_bool_fill(res, 1); + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + return 0; + } + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_OUT, IGRAPH_DONT_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + + for (i = 0; ! IGRAPH_EIT_END(eit); i++, IGRAPH_EIT_NEXT(eit)) { + long int edge = IGRAPH_EIT_GET(eit); + long int from = IGRAPH_FROM(graph, edge); + long int to = IGRAPH_TO(graph, edge); + + /* Check whether there is a to->from edge, search for from in the + out-list of to. We don't search an empty vector, because + vector_binsearch seems to have a bug with this. */ + igraph_vector_t *neis = igraph_lazy_adjlist_get(&adjlist, + (igraph_integer_t) to); + if (igraph_vector_empty(neis)) { + VECTOR(*res)[i] = 0; + } else { + VECTOR(*res)[i] = igraph_vector_binsearch2(neis, from); + } + } + + igraph_lazy_adjlist_destroy(&adjlist); + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +int igraph_i_avg_nearest_neighbor_degree_weighted(const igraph_t *graph, + igraph_vs_t vids, + igraph_neimode_t mode, + igraph_neimode_t neighbor_degree_mode, + igraph_vector_t *knn, + igraph_vector_t *knnk, + const igraph_vector_t *weights); + +int igraph_i_avg_nearest_neighbor_degree_weighted(const igraph_t *graph, + igraph_vs_t vids, + igraph_neimode_t mode, + igraph_neimode_t neighbor_degree_mode, + igraph_vector_t *knn, + igraph_vector_t *knnk, + const igraph_vector_t *weights) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t neis, edge_neis; + long int i, j, no_vids; + igraph_vit_t vit; + igraph_vector_t my_knn_v, *my_knn = knn; + igraph_vector_t strength, deg; + igraph_integer_t maxdeg; + igraph_vector_t deghist; + igraph_real_t mynan = IGRAPH_NAN; + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector size", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + no_vids = IGRAPH_VIT_SIZE(vit); + + if (!knn) { + IGRAPH_VECTOR_INIT_FINALLY(&my_knn_v, no_vids); + my_knn = &my_knn_v; + } else { + IGRAPH_CHECK(igraph_vector_resize(knn, no_vids)); + } + + // Get degree of neighbours + IGRAPH_VECTOR_INIT_FINALLY(°, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), + neighbor_degree_mode, IGRAPH_LOOPS)); + IGRAPH_VECTOR_INIT_FINALLY(&strength, no_of_nodes); + + // Get strength of all nodes + IGRAPH_CHECK(igraph_strength(graph, &strength, igraph_vss_all(), + mode, IGRAPH_LOOPS, weights)); + + // Get maximum degree for initialization + IGRAPH_CHECK(igraph_maxdegree(graph, &maxdeg, igraph_vss_all(), + mode, IGRAPH_LOOPS)); + IGRAPH_VECTOR_INIT_FINALLY(&neis, (long int)maxdeg); + IGRAPH_VECTOR_INIT_FINALLY(&edge_neis, (long int)maxdeg); + igraph_vector_resize(&neis, 0); + igraph_vector_resize(&edge_neis, 0); + + if (knnk) { + IGRAPH_CHECK(igraph_vector_resize(knnk, (long int)maxdeg)); + igraph_vector_null(knnk); + IGRAPH_VECTOR_INIT_FINALLY(°hist, (long int)maxdeg); + } + + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_real_t sum = 0.0; + long int v = IGRAPH_VIT_GET(vit); + long int nv; + igraph_real_t str = VECTOR(strength)[v]; + // Get neighbours and incident edges + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) v, mode)); + IGRAPH_CHECK(igraph_incident(graph, &edge_neis, (igraph_integer_t) v, mode)); + nv = igraph_vector_size(&neis); + for (j = 0; j < nv; j++) { + long int nei = (long int) VECTOR(neis)[j]; + long int e = (long int) VECTOR(edge_neis)[j]; + double w = VECTOR(*weights)[e]; + sum += w * VECTOR(deg)[nei]; + } + if (str != 0.0) { + VECTOR(*my_knn)[i] = sum / str; + } else { + VECTOR(*my_knn)[i] = mynan; + } + if (knnk && nv > 0) { + VECTOR(*knnk)[nv - 1] += VECTOR(*my_knn)[i]; + VECTOR(deghist)[nv - 1] += 1; + } + } + + igraph_vector_destroy(&edge_neis); + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(2); + + if (knnk) { + for (i = 0; i < maxdeg; i++) { + igraph_real_t dh = VECTOR(deghist)[i]; + if (dh != 0) { + VECTOR(*knnk)[i] /= dh; + } else { + VECTOR(*knnk)[i] = mynan; + } + } + + igraph_vector_destroy(°hist); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&strength); + igraph_vector_destroy(°); + IGRAPH_FINALLY_CLEAN(2); + + if (!knn) { + igraph_vector_destroy(&my_knn_v); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_avg_nearest_neighbor_degree + * Average neighbor degree. + * + * Calculates the average degree of the neighbors for each vertex (\p knn), and + * optionally, the same quantity as a function of the vertex degree (\p knnk). + * + * + * For isolated vertices \p knn is set to NaN. + * The same is done in \p knnk for vertex degrees that + * don't appear in the graph. + * + * + * The weighted version computes a weighted average of the neighbor degrees as + * + * k_nn_u = 1/s_u sum_v w_uv k_v, + * + * where s_u = sum_v w_uv is the sum of the incident edge weights + * of vertex \c u, i.e. its strength. + * The sum runs over the neighbors \c v of vertex \c u + * as indicated by \p mode. w_uv denotes the weighted adjacency matrix + * and k_v is the neighbors' degree, specified by \p neighbor_degree_mode. + * + * + * Reference: + * A. Barrat, M. Barthélemy, R. Pastor-Satorras, and A. Vespignani, + * The architecture of complex weighted networks, + * Proc. Natl. Acad. Sci. USA 101, 3747 (2004). + * https://dx.doi.org/10.1073/pnas.0400087101 + * + * \param graph The input graph. It may be directed. + * \param vids The vertices for which the calculation is performed. + * \param mode The type of neighbors to consider in directed graphs. + * \c IGRAPH_OUT considers out-neighbors, \c IGRAPH_IN in-neighbors + * and \c IGRAPH_ALL ignores edge directions. + * \param neighbor_degree_mode The type of degree to average in directed graphs. + * \c IGRAPH_OUT averages out-degrees, \c IGRAPH_IN averages in-degrees + * and \c IGRAPH_ALL ignores edge directions for the degree calculation. + * \param vids The vertices for which the calculation is performed. + * \param knn Pointer to an initialized vector, the result will be + * stored here. It will be resized as needed. Supply a \c NULL pointer + * here, if you only want to calculate \c knnk. + * \param knnk Pointer to an initialized vector, the average + * neighbor degree as a function of the vertex degree is stored + * here. The first (zeroth) element is for degree one vertices, + * etc. Supply a \c NULL pointer here if you don't want to calculate + * this. + * \param weights Optional edge weights. Supply a null pointer here + * for the non-weighted version. + * + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \example examples/simple/igraph_knn.c + */ + +int igraph_avg_nearest_neighbor_degree(const igraph_t *graph, + igraph_vs_t vids, + igraph_neimode_t mode, + igraph_neimode_t neighbor_degree_mode, + igraph_vector_t *knn, + igraph_vector_t *knnk, + const igraph_vector_t *weights) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t neis; + long int i, j, no_vids; + igraph_vit_t vit; + igraph_vector_t my_knn_v, *my_knn = knn; + igraph_vector_t deg; + igraph_integer_t maxdeg; + igraph_vector_t deghist; + igraph_real_t mynan = IGRAPH_NAN; + igraph_bool_t simple; + + IGRAPH_CHECK(igraph_is_simple(graph, &simple)); + if (!simple) { + IGRAPH_ERROR("Average nearest neighbor degree works only with " + "simple graphs", IGRAPH_EINVAL); + } + + if (weights) { + return igraph_i_avg_nearest_neighbor_degree_weighted(graph, vids, + mode, neighbor_degree_mode, knn, knnk, weights); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + no_vids = IGRAPH_VIT_SIZE(vit); + + if (!knn) { + IGRAPH_VECTOR_INIT_FINALLY(&my_knn_v, no_vids); + my_knn = &my_knn_v; + } else { + IGRAPH_CHECK(igraph_vector_resize(knn, no_vids)); + } + + IGRAPH_VECTOR_INIT_FINALLY(°, no_of_nodes); + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), + neighbor_degree_mode, IGRAPH_LOOPS)); + igraph_maxdegree(graph, &maxdeg, igraph_vss_all(), mode, IGRAPH_LOOPS); + IGRAPH_VECTOR_INIT_FINALLY(&neis, maxdeg); + igraph_vector_resize(&neis, 0); + + if (knnk) { + IGRAPH_CHECK(igraph_vector_resize(knnk, (long int)maxdeg)); + igraph_vector_null(knnk); + IGRAPH_VECTOR_INIT_FINALLY(°hist, (long int)maxdeg); + } + + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + igraph_real_t sum = 0.0; + long int v = IGRAPH_VIT_GET(vit); + long int nv; + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) v, mode)); + nv = igraph_vector_size(&neis); + for (j = 0; j < nv; j++) { + long int nei = (long int) VECTOR(neis)[j]; + sum += VECTOR(deg)[nei]; + } + if (nv != 0) { + VECTOR(*my_knn)[i] = sum / nv; + } else { + VECTOR(*my_knn)[i] = mynan; + } + if (knnk && nv > 0) { + VECTOR(*knnk)[nv - 1] += VECTOR(*my_knn)[i]; + VECTOR(deghist)[nv - 1] += 1; + } + } + + if (knnk) { + for (i = 0; i < maxdeg; i++) { + long int dh = (long int) VECTOR(deghist)[i]; + if (dh != 0) { + VECTOR(*knnk)[i] /= dh; + } else { + VECTOR(*knnk)[i] = mynan; + } + } + igraph_vector_destroy(°hist); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&neis); + igraph_vector_destroy(°); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(3); + + if (!knn) { + igraph_vector_destroy(&my_knn_v); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \function igraph_strength + * Strength of the vertices, weighted vertex degree in other words. + * + * In a weighted network the strength of a vertex is the sum of the + * weights of all incident edges. In a non-weighted network this is + * exactly the vertex degree. + * \param graph The input graph. + * \param res Pointer to an initialized vector, the result is stored + * here. It will be resized as needed. + * \param vids The vertices for which the calculation is performed. + * \param mode Gives whether to count only outgoing (\c IGRAPH_OUT), + * incoming (\c IGRAPH_IN) edges or both (\c IGRAPH_ALL). + * \param loops A logical scalar, whether to count loop edges as well. + * \param weights A vector giving the edge weights. If this is a NULL + * pointer, then \ref igraph_degree() is called to perform the + * calculation. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number vertices and + * edges. + * + * \sa \ref igraph_degree() for the traditional, non-weighted version. + */ + +int igraph_strength(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, igraph_neimode_t mode, + igraph_bool_t loops, const igraph_vector_t *weights) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vit_t vit; + long int no_vids; + igraph_vector_t neis; + long int i; + + if (!weights) { + return igraph_degree(graph, res, vids, mode, loops); + } + + if (igraph_vector_size(weights) != igraph_ecount(graph)) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + no_vids = IGRAPH_VIT_SIZE(vit); + + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_vector_reserve(&neis, no_of_nodes)); + IGRAPH_CHECK(igraph_vector_resize(res, no_vids)); + igraph_vector_null(res); + + if (loops) { + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + long int vid = IGRAPH_VIT_GET(vit); + long int j, n; + IGRAPH_CHECK(igraph_incident(graph, &neis, (igraph_integer_t) vid, mode)); + n = igraph_vector_size(&neis); + for (j = 0; j < n; j++) { + long int edge = (long int) VECTOR(neis)[j]; + VECTOR(*res)[i] += VECTOR(*weights)[edge]; + } + } + } else { + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + long int vid = IGRAPH_VIT_GET(vit); + long int j, n; + IGRAPH_CHECK(igraph_incident(graph, &neis, (igraph_integer_t) vid, mode)); + n = igraph_vector_size(&neis); + for (j = 0; j < n; j++) { + long int edge = (long int) VECTOR(neis)[j]; + long int from = IGRAPH_FROM(graph, edge); + long int to = IGRAPH_TO(graph, edge); + if (from != to) { + VECTOR(*res)[i] += VECTOR(*weights)[edge]; + } + } + } + } + + igraph_vit_destroy(&vit); + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +/** + * \function igraph_diameter_dijkstra + * Weighted diameter using Dijkstra's algorithm, non-negative weights only. + * + * The diameter of a graph is its longest geodesic. I.e. the + * (weighted) shortest path is calculated for all pairs of vertices + * and the longest one is the diameter. + * \param graph The input graph, can be directed or undirected. + * \param pres Pointer to a real number, if not \c NULL then it will contain + * the diameter (the actual distance). + * \param pfrom Pointer to an integer, if not \c NULL it will be set to the + * source vertex of the diameter path. + * \param pto Pointer to an integer, if not \c NULL it will be set to the + * target vertex of the diameter path. + * \param path Pointer to an initialized vector. If not \c NULL the actual + * longest geodesic path will be stored here. The vector will be + * resized as needed. + * \param directed Boolean, whether to consider directed + * paths. Ignored for undirected graphs. + * \param unconn What to do if the graph is not connected. If + * \c TRUE the longest geodesic within a component + * will be returned, otherwise \c IGRAPH_INFINITY is + * returned. + * \return Error code. + * + * Time complexity: O(|V||E|*log|E|), |V| is the number of vertices, + * |E| is the number of edges. + */ + +int igraph_diameter_dijkstra(const igraph_t *graph, + const igraph_vector_t *weights, + igraph_real_t *pres, + igraph_integer_t *pfrom, + igraph_integer_t *pto, + igraph_vector_t *path, + igraph_bool_t directed, + igraph_bool_t unconn) { + + /* Implementation details. This is the basic Dijkstra algorithm, + with a binary heap. The heap is indexed, i.e. it stores not only + the distances, but also which vertex they belong to. + + From now on we use a 2-way heap, so the distances can be queried + directly from the heap. + + Dirty tricks: + - the opposite of the distance is stored in the heap, as it is a + maximum heap and we need a minimum heap. + - we don't use IGRAPH_INFINITY during the computation, as IGRAPH_FINITE() + might involve a function call and we want to spare that. -1 will denote + infinity instead. + */ + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + + igraph_2wheap_t Q; + igraph_inclist_t inclist; + long int source, j; + igraph_neimode_t dirmode = directed ? IGRAPH_OUT : IGRAPH_ALL; + + long int from = -1, to = -1; + igraph_real_t res = 0; + long int nodes_reached = 0; + + if (!weights) { + igraph_integer_t diameter; + IGRAPH_CHECK(igraph_diameter(graph, &diameter, pfrom, pto, path, directed, unconn)); + if (pres) { + *pres = diameter; + } + return IGRAPH_SUCCESS; + } + + if (weights && igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid weight vector length", IGRAPH_EINVAL); + } + + if (igraph_vector_min(weights) < 0) { + IGRAPH_ERROR("Weight vector must be non-negative", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_2wheap_init(&Q, no_of_nodes)); + IGRAPH_FINALLY(igraph_2wheap_destroy, &Q); + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, dirmode)); + IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); + + for (source = 0; source < no_of_nodes; source++) { + + IGRAPH_PROGRESS("Weighted diameter: ", source * 100.0 / no_of_nodes, NULL); + IGRAPH_ALLOW_INTERRUPTION(); + + igraph_2wheap_clear(&Q); + igraph_2wheap_push_with_index(&Q, source, -1.0); + + nodes_reached = 0.0; + + while (!igraph_2wheap_empty(&Q)) { + long int minnei = igraph_2wheap_max_index(&Q); + igraph_real_t mindist = -igraph_2wheap_deactivate_max(&Q); + igraph_vector_int_t *neis; + long int nlen; + + if (mindist > res) { + res = mindist; from = source; to = minnei; + } + nodes_reached++; + + /* Now check all neighbors of 'minnei' for a shorter path */ + neis = igraph_inclist_get(&inclist, minnei); + nlen = igraph_vector_int_size(neis); + for (j = 0; j < nlen; j++) { + long int edge = (long int) VECTOR(*neis)[j]; + long int tto = IGRAPH_OTHER(graph, edge, minnei); + igraph_real_t altdist = mindist + VECTOR(*weights)[edge]; + igraph_bool_t active = igraph_2wheap_has_active(&Q, tto); + igraph_bool_t has = igraph_2wheap_has_elem(&Q, tto); + igraph_real_t curdist = active ? -igraph_2wheap_get(&Q, tto) : 0.0; + + if (!has) { + /* First finite distance */ + IGRAPH_CHECK(igraph_2wheap_push_with_index(&Q, tto, -altdist)); + } else if (altdist < curdist) { + /* A shorter path */ + IGRAPH_CHECK(igraph_2wheap_modify(&Q, tto, -altdist)); + } + } + + } /* !igraph_2wheap_empty(&Q) */ + + /* not connected, return infinity */ + if (nodes_reached != no_of_nodes && !unconn) { + res = IGRAPH_INFINITY; + from = to = -1; + break; + } + + } /* source < no_of_nodes */ + + /* Compensate for the +1 that we have added to distances */ + res -= 1; + + igraph_inclist_destroy(&inclist); + igraph_2wheap_destroy(&Q); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_PROGRESS("Weighted diameter: ", 100.0, NULL); + + if (pres) { + *pres = res; + } + if (pfrom) { + *pfrom = (igraph_integer_t) from; + } + if (pto) { + *pto = (igraph_integer_t) to; + } + if (path) { + if (!igraph_finite(res)) { + igraph_vector_clear(path); + } else { + igraph_vector_ptr_t tmpptr; + igraph_vector_ptr_init(&tmpptr, 1); + IGRAPH_FINALLY(igraph_vector_ptr_destroy, &tmpptr); + VECTOR(tmpptr)[0] = path; + IGRAPH_CHECK(igraph_get_shortest_paths_dijkstra(graph, + /*vertices=*/ &tmpptr, /*edges=*/ 0, + (igraph_integer_t) from, + igraph_vss_1((igraph_integer_t) to), + weights, dirmode, /*predecessors=*/ 0, + /*inbound_edges=*/ 0)); + igraph_vector_ptr_destroy(&tmpptr); + IGRAPH_FINALLY_CLEAN(1); + } + } + + return 0; +} + +/** + * \function igraph_sort_vertex_ids_by_degree + * \brief Calculate a list of vertex ids sorted by degree of the corresponding vertex. + * + * The list of vertex ids is returned in a vector that is sorted + * in ascending or descending order of vertex degree. + * + * \param graph The input graph. + * \param outvids Pointer to an initialized vector that will be + * resized and will contain the ordered vertex ids. + * \param vids Input vertex selector of vertex ids to include in + * calculation. + * \param mode Defines the type of the degree. + * \c IGRAPH_OUT, out-degree, + * \c IGRAPH_IN, in-degree, + * \c IGRAPH_ALL, total degree (sum of the + * in- and out-degree). + * This parameter is ignored for undirected graphs. + * \param loops Boolean, gives whether the self-loops should be + * counted. + * \param order Specifies whether the ordering should be ascending + * (\c IGRAPH_ASCENDING) or descending (\c IGRAPH_DESCENDING). + * \param only_indices If true, then return a sorted list of indices + * into a vector corresponding to \c vids, rather than a list + * of vertex ids. This parameter is ignored if \c vids is set + * to all vertices via igraph_vs_all() or igraph_vss_all(), + * because in this case the indices and vertex ids are the + * same. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex id. + * \c IGRAPH_EINVMODE: invalid mode argument. + * + */ + +int igraph_sort_vertex_ids_by_degree(const igraph_t *graph, + igraph_vector_t *outvids, + igraph_vs_t vids, + igraph_neimode_t mode, + igraph_bool_t loops, + igraph_order_t order, + igraph_bool_t only_indices) { + long int i; + igraph_vector_t degrees, vs_vec; + IGRAPH_VECTOR_INIT_FINALLY(°rees, 0); + IGRAPH_CHECK(igraph_degree(graph, °rees, vids, mode, loops)); + IGRAPH_CHECK((int) igraph_vector_qsort_ind(°rees, outvids, + order == IGRAPH_DESCENDING)); + if (only_indices || igraph_vs_is_all(&vids) ) { + igraph_vector_destroy(°rees); + IGRAPH_FINALLY_CLEAN(1); + } else { + IGRAPH_VECTOR_INIT_FINALLY(&vs_vec, 0); + IGRAPH_CHECK(igraph_vs_as_vector(graph, vids, &vs_vec)); + for (i = 0; i < igraph_vector_size(outvids); i++) { + VECTOR(*outvids)[i] = VECTOR(vs_vec)[(long int)VECTOR(*outvids)[i]]; + } + igraph_vector_destroy(&vs_vec); + igraph_vector_destroy(°rees); + IGRAPH_FINALLY_CLEAN(2); + } + return 0; +} + +/** + * \function igraph_contract_vertices + * Replace multiple vertices with a single one. + * + * This function creates a new graph, by merging several + * vertices into one. The vertices in the new graph correspond + * to sets of vertices in the input graph. + * \param graph The input graph, it can be directed or + * undirected. + * \param mapping A vector giving the mapping. For each + * vertex in the original graph, it should contain + * its id in the new graph. + * \param vertex_comb What to do with the vertex attributes. + * See the igraph manual section about attributes for + * details. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number + * or vertices plus edges. + */ + +int igraph_contract_vertices(igraph_t *graph, + const igraph_vector_t *mapping, + const igraph_attribute_combination_t + *vertex_comb) { + igraph_vector_t edges; + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_bool_t vattr = vertex_comb && igraph_has_attribute_table(); + igraph_t res; + long int e, last = -1; + long int no_new_vertices; + + if (igraph_vector_size(mapping) != no_of_nodes) { + IGRAPH_ERROR("Invalid mapping vector length", + IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges * 2)); + + if (no_of_nodes > 0) { + last = (long int) igraph_vector_max(mapping); + } + + for (e = 0; e < no_of_edges; e++) { + long int from = IGRAPH_FROM(graph, e); + long int to = IGRAPH_TO(graph, e); + + long int nfrom = (long int) VECTOR(*mapping)[from]; + long int nto = (long int) VECTOR(*mapping)[to]; + + igraph_vector_push_back(&edges, nfrom); + igraph_vector_push_back(&edges, nto); + + if (nfrom > last) { + last = nfrom; + } + if (nto > last) { + last = nto; + } + } + + no_new_vertices = last + 1; + + IGRAPH_CHECK(igraph_create(&res, &edges, (igraph_integer_t) no_new_vertices, + igraph_is_directed(graph))); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_FINALLY(igraph_destroy, &res); + + IGRAPH_I_ATTRIBUTE_DESTROY(&res); + IGRAPH_I_ATTRIBUTE_COPY(&res, graph, /*graph=*/ 1, + /*vertex=*/ 0, /*edge=*/ 1); + + if (vattr) { + long int i; + igraph_vector_ptr_t merges; + igraph_vector_t sizes; + igraph_vector_t *vecs; + + vecs = igraph_Calloc(no_new_vertices, igraph_vector_t); + if (!vecs) { + IGRAPH_ERROR("Cannot combine attributes while contracting" + " vertices", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, vecs); + IGRAPH_CHECK(igraph_vector_ptr_init(&merges, no_new_vertices)); + IGRAPH_FINALLY(igraph_i_simplify_free, &merges); + IGRAPH_VECTOR_INIT_FINALLY(&sizes, no_new_vertices); + + for (i = 0; i < no_of_nodes; i++) { + long int to = (long int) VECTOR(*mapping)[i]; + VECTOR(sizes)[to] += 1; + } + for (i = 0; i < no_new_vertices; i++) { + igraph_vector_t *v = &vecs[i]; + IGRAPH_CHECK(igraph_vector_init(v, (long int) VECTOR(sizes)[i])); + igraph_vector_clear(v); + VECTOR(merges)[i] = v; + } + for (i = 0; i < no_of_nodes; i++) { + long int to = (long int) VECTOR(*mapping)[i]; + igraph_vector_t *v = &vecs[to]; + igraph_vector_push_back(v, i); + } + + IGRAPH_CHECK(igraph_i_attribute_combine_vertices(graph, &res, + &merges, + vertex_comb)); + + igraph_vector_destroy(&sizes); + igraph_i_simplify_free(&merges); + igraph_free(vecs); + IGRAPH_FINALLY_CLEAN(3); + } + + IGRAPH_FINALLY_CLEAN(1); + igraph_destroy(graph); + *graph = res; + + return 0; +} + +/* Create the transitive closure of a tree graph. + This is fairly simple, we just collect all ancestors of a vertex + using a depth-first search. + */ + +int igraph_transitive_closure_dag(const igraph_t *graph, + igraph_t *closure) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t deg; + igraph_vector_t new_edges; + igraph_vector_t ancestors; + long int root; + igraph_vector_t neighbors; + igraph_stack_t path; + igraph_vector_bool_t done; + + if (!igraph_is_directed(graph)) { + IGRAPH_ERROR("Tree transitive closure of a directed graph", + IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&new_edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(°, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&ancestors, 0); + IGRAPH_VECTOR_INIT_FINALLY(&neighbors, 0); + IGRAPH_CHECK(igraph_stack_init(&path, 0)); + IGRAPH_FINALLY(igraph_stack_destroy, &path); + IGRAPH_CHECK(igraph_vector_bool_init(&done, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &done); + + IGRAPH_CHECK(igraph_degree(graph, °, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + +#define STAR (-1) + + for (root = 0; root < no_of_nodes; root++) { + if (VECTOR(deg)[root] != 0) { + continue; + } + IGRAPH_CHECK(igraph_stack_push(&path, root)); + + while (!igraph_stack_empty(&path)) { + long int node = (long int) igraph_stack_top(&path); + if (node == STAR) { + /* Leaving a node */ + long int j, n; + igraph_stack_pop(&path); + node = (long int) igraph_stack_pop(&path); + if (!VECTOR(done)[node]) { + igraph_vector_pop_back(&ancestors); + VECTOR(done)[node] = 1; + } + n = igraph_vector_size(&ancestors); + for (j = 0; j < n; j++) { + IGRAPH_CHECK(igraph_vector_push_back(&new_edges, node)); + IGRAPH_CHECK(igraph_vector_push_back(&new_edges, + VECTOR(ancestors)[j])); + } + } else { + /* Getting into a node */ + long int n, j; + if (!VECTOR(done)[node]) { + IGRAPH_CHECK(igraph_vector_push_back(&ancestors, node)); + } + IGRAPH_CHECK(igraph_neighbors(graph, &neighbors, + (igraph_integer_t) node, IGRAPH_IN)); + n = igraph_vector_size(&neighbors); + IGRAPH_CHECK(igraph_stack_push(&path, STAR)); + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neighbors)[j]; + IGRAPH_CHECK(igraph_stack_push(&path, nei)); + } + } + } + } + +#undef STAR + + igraph_vector_bool_destroy(&done); + igraph_stack_destroy(&path); + igraph_vector_destroy(&neighbors); + igraph_vector_destroy(&ancestors); + igraph_vector_destroy(°); + IGRAPH_FINALLY_CLEAN(5); + + IGRAPH_CHECK(igraph_create(closure, &new_edges, (igraph_integer_t)no_of_nodes, + IGRAPH_DIRECTED)); + + igraph_vector_destroy(&new_edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_diversity + * Structural diversity index of the vertices + * + * This measure was defined in Nathan Eagle, Michael Macy and Rob + * Claxton: Network Diversity and Economic Development, Science 328, + * 1029--1031, 2010. + * + * + * It is simply the (normalized) Shannon entropy of the + * incident edges' weights. D(i)=H(i)/log(k[i]), and + * H(i) = -sum(p[i,j] log(p[i,j]), j=1..k[i]), + * where p[i,j]=w[i,j]/sum(w[i,l], l=1..k[i]), k[i] is the (total) + * degree of vertex i, and w[i,j] is the weight of the edge(s) between + * vertex i and j. + * \param graph The input graph, edge directions are ignored. + * \param weights The edge weights, in the order of the edge ids, must + * have appropriate length. + * \param res An initialized vector, the results are stored here. + * \param vids Vector with the vertex ids for which to calculate the + * measure. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear. + * + */ + +int igraph_diversity(igraph_t *graph, const igraph_vector_t *weights, + igraph_vector_t *res, const igraph_vs_t vids) { + + int no_of_nodes = igraph_vcount(graph); + int no_of_edges = igraph_ecount(graph); + igraph_vector_t incident; + igraph_vit_t vit; + igraph_real_t s, ent, w; + int i, j, k; + + if (!weights) { + IGRAPH_ERROR("Edge weights must be given", IGRAPH_EINVAL); + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid edge weight vector length", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&incident, 10); + + if (igraph_vs_is_all(&vids)) { + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + s = ent = 0.0; + IGRAPH_CHECK(igraph_incident(graph, &incident, i, /*mode=*/ IGRAPH_ALL)); + for (j = 0, k = (int) igraph_vector_size(&incident); j < k; j++) { + w = VECTOR(*weights)[(long int)VECTOR(incident)[j]]; + s += w; + ent += (w * log(w)); + } + VECTOR(*res)[i] = (log(s) - ent / s) / log(k); + } + } else { + IGRAPH_CHECK(igraph_vector_resize(res, 0)); + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + long int v = IGRAPH_VIT_GET(vit); + s = ent = 0.0; + IGRAPH_CHECK(igraph_incident(graph, &incident, (igraph_integer_t) v, + /*mode=*/ IGRAPH_ALL)); + for (j = 0, k = (int) igraph_vector_size(&incident); j < k; j++) { + w = VECTOR(*weights)[(long int)VECTOR(incident)[j]]; + s += w; + ent += (w * log(w)); + } + IGRAPH_CHECK(igraph_vector_push_back(res, (log(s) - ent / s) / log(k))); + } + + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&incident); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +#define SUCCEED { \ + if (res) { \ + *res = 1; \ + } \ + return IGRAPH_SUCCESS; \ + } + +#define FAIL { \ + if (res) { \ + *res = 0; \ + } \ + return IGRAPH_SUCCESS; \ + } + +/** + * \function igraph_is_degree_sequence + * Determines whether a degree sequence is valid. + * + * A sequence of n integers is a valid degree sequence if there exists some + * graph where the degree of the i-th vertex is equal to the i-th element of the + * sequence. Note that the graph may contain multiple or loop edges; if you are + * interested in whether the degrees of some \em simple graph may realize the + * given sequence, use \ref igraph_is_graphical_degree_sequence. + * + * + * In particular, the function checks whether all the degrees are non-negative. + * For undirected graphs, it also checks whether the sum of degrees is even. + * For directed graphs, the function checks whether the lengths of the two + * degree vectors are equal and whether their sums are also equal. These are + * known sufficient and necessary conditions for a degree sequence to be + * valid. + * + * \param out_degrees an integer vector specifying the degree sequence for + * undirected graphs or the out-degree sequence for directed graphs. + * \param in_degrees an integer vector specifying the in-degrees of the + * vertices for directed graphs. For undirected graphs, this must be null. + * \param res pointer to a boolean variable, the result will be stored here + * \return Error code. + * + * Time complexity: O(n), where n is the length of the degree sequence. + */ +int igraph_is_degree_sequence(const igraph_vector_t *out_degrees, + const igraph_vector_t *in_degrees, igraph_bool_t *res) { + /* degrees must be non-negative */ + if (igraph_vector_any_smaller(out_degrees, 0)) { + FAIL; + } + if (in_degrees && igraph_vector_any_smaller(in_degrees, 0)) { + FAIL; + } + + if (in_degrees == 0) { + /* sum of degrees must be even */ + if (((long int)igraph_vector_sum(out_degrees) % 2) != 0) { + FAIL; + } + } else { + /* length of the two degree vectors must be equal */ + if (igraph_vector_size(out_degrees) != igraph_vector_size(in_degrees)) { + FAIL; + } + /* sum of in-degrees must be equal to sum of out-degrees */ + if (igraph_vector_sum(out_degrees) != igraph_vector_sum(in_degrees)) { + FAIL; + } + } + + SUCCEED; + return 0; +} + +int igraph_i_is_graphical_degree_sequence_undirected( + const igraph_vector_t *degrees, igraph_bool_t *res); +int igraph_i_is_graphical_degree_sequence_directed( + const igraph_vector_t *out_degrees, const igraph_vector_t *in_degrees, + igraph_bool_t *res); + +/** + * \function igraph_is_graphical_degree_sequence + * Determines whether a sequence of integers can be a degree sequence of some + * simple graph. + * + * + * References: + * + * + * Hakimi SL: On the realizability of a set of integers as degrees of the + * vertices of a simple graph. J SIAM Appl Math 10:496-506, 1962. + * + * + * PL Erdos, I Miklos and Z Toroczkai: A simple Havel-Hakimi type algorithm + * to realize graphical degree sequences of directed graphs. The Electronic + * Journal of Combinatorics 17(1):R66, 2010. + * + * + * Z Kiraly: Recognizing graphic degree sequences and generating all + * realizations. TR-2011-11, Egervary Research Group, H-1117, Budapest, + * Hungary. ISSN 1587-4451, 2012. + * + * \param out_degrees an integer vector specifying the degree sequence for + * undirected graphs or the out-degree sequence for directed graphs. + * \param in_degrees an integer vector specifying the in-degrees of the + * vertices for directed graphs. For undirected graphs, this must be null. + * \param res pointer to a boolean variable, the result will be stored here + * \return Error code. + * + * Time complexity: O(n log n) for undirected graphs, O(n^2) for directed + * graphs, where n is the length of the degree sequence. + */ +int igraph_is_graphical_degree_sequence(const igraph_vector_t *out_degrees, + const igraph_vector_t *in_degrees, igraph_bool_t *res) { + IGRAPH_CHECK(igraph_is_degree_sequence(out_degrees, in_degrees, res)); + if (!*res) { + FAIL; + } + + if (igraph_vector_size(out_degrees) == 0) { + SUCCEED; + } + + if (in_degrees == 0) { + return igraph_i_is_graphical_degree_sequence_undirected(out_degrees, res); + } else { + return igraph_i_is_graphical_degree_sequence_directed(out_degrees, in_degrees, res); + } +} + +int igraph_i_is_graphical_degree_sequence_undirected( + const igraph_vector_t *degrees, igraph_bool_t *res) { + igraph_vector_t work; + long int w, b, s, c, n, k; + + IGRAPH_CHECK(igraph_vector_copy(&work, degrees)); + IGRAPH_FINALLY(igraph_vector_destroy, &work); + + igraph_vector_sort(&work); + + /* This algorithm is outlined in TR-2011-11 of the Egervary Research Group, + * ISSN 1587-4451. The main loop of the algorithm is O(n) but it is dominated + * by an O(n log n) quicksort; this could in theory be brought down to + * O(n) with binsort but it's probably not worth the fuss. + * + * Variables names are mostly according to the technical report, apart from + * the degrees themselves. w and k are zero-based here; in the technical + * report they are 1-based */ + *res = 1; + n = igraph_vector_size(&work); + w = n - 1; b = 0; s = 0; c = 0; + for (k = 0; k < n; k++) { + b += VECTOR(*degrees)[k]; + c += w; + while (w > k && VECTOR(*degrees)[w] <= k + 1) { + s += VECTOR(*degrees)[w]; + c -= (k + 1); + w--; + } + if (b > c + s) { + *res = 0; + break; + } + if (w == k) { + break; + } + } + + igraph_vector_destroy(&work); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +typedef struct { + const igraph_vector_t* first; + const igraph_vector_t* second; +} igraph_i_qsort_dual_vector_cmp_data_t; + +int igraph_i_qsort_dual_vector_cmp_desc(void* data, const void *p1, const void *p2) { + igraph_i_qsort_dual_vector_cmp_data_t* sort_data = + (igraph_i_qsort_dual_vector_cmp_data_t*)data; + long int index1 = *((long int*)p1); + long int index2 = *((long int*)p2); + if (VECTOR(*sort_data->first)[index1] < VECTOR(*sort_data->first)[index2]) { + return 1; + } + if (VECTOR(*sort_data->first)[index1] > VECTOR(*sort_data->first)[index2]) { + return -1; + } + if (VECTOR(*sort_data->second)[index1] < VECTOR(*sort_data->second)[index2]) { + return 1; + } + if (VECTOR(*sort_data->second)[index1] > VECTOR(*sort_data->second)[index2]) { + return -1; + } + return 0; +} + +int igraph_i_is_graphical_degree_sequence_directed( + const igraph_vector_t *out_degrees, const igraph_vector_t *in_degrees, + igraph_bool_t *res) { + igraph_vector_long_t index_array; + long int i, j, vcount, lhs, rhs; + igraph_i_qsort_dual_vector_cmp_data_t sort_data; + + /* Create an index vector that sorts the vertices by decreasing in-degree */ + vcount = igraph_vector_size(out_degrees); + IGRAPH_CHECK(igraph_vector_long_init_seq(&index_array, 0, vcount - 1)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &index_array); + + /* Set up the auxiliary struct for sorting */ + sort_data.first = in_degrees; + sort_data.second = out_degrees; + + /* Sort the index vector */ + igraph_qsort_r(VECTOR(index_array), vcount, sizeof(long int), &sort_data, + igraph_i_qsort_dual_vector_cmp_desc); + + /* Be optimistic, then check whether the Fulkerson–Chen–Anstee condition + * holds for every k. In particular, for every k in [0; n), it must be true + * that: + * + * \sum_{i=0}^k indegree[i] <= + * \sum_{i=0}^k min(outdegree[i], k) + + * \sum_{i=k+1}^{n-1} min(outdegree[i], k + 1) + */ + +#define INDEGREE(x) (VECTOR(*in_degrees)[VECTOR(index_array)[x]]) +#define OUTDEGREE(x) (VECTOR(*out_degrees)[VECTOR(index_array)[x]]) + + *res = 1; + lhs = 0; + for (i = 0; i < vcount; i++) { + lhs += INDEGREE(i); + + /* It is enough to check for indexes where the in-degree is about to + * decrease in the next step; see "Stronger condition" in the Wikipedia + * entry for the Fulkerson-Chen-Anstee condition */ + if (i != vcount - 1 && INDEGREE(i) == INDEGREE(i + 1)) { + continue; + } + + rhs = 0; + for (j = 0; j <= i; j++) { + rhs += OUTDEGREE(j) < i ? OUTDEGREE(j) : i; + } + for (j = i + 1; j < vcount; j++) { + rhs += OUTDEGREE(j) < (i + 1) ? OUTDEGREE(j) : (i + 1); + } + + if (lhs > rhs) { + *res = 0; + break; + } + } + +#undef INDEGREE +#undef OUTDEGREE + + igraph_vector_long_destroy(&index_array); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +#undef SUCCEED +#undef FAIL + + +/* igraph_is_tree -- check if a graph is a tree */ + +/* count the number of vertices reachable from the root */ +static int igraph_i_is_tree_visitor(igraph_integer_t root, const igraph_adjlist_t *al, igraph_integer_t *visited_count) { + igraph_stack_int_t stack; + igraph_vector_bool_t visited; + long i; + + IGRAPH_CHECK(igraph_vector_bool_init(&visited, igraph_adjlist_size(al))); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &visited); + + IGRAPH_CHECK(igraph_stack_int_init(&stack, 0)); + IGRAPH_FINALLY(igraph_stack_int_destroy, &stack); + + *visited_count = 0; + + /* push the root into the stack */ + IGRAPH_CHECK(igraph_stack_int_push(&stack, root)); + + while (! igraph_stack_int_empty(&stack)) { + igraph_integer_t u; + igraph_vector_int_t *neighbors; + long ncount; + + /* take a vertex from the stack, mark it as visited */ + u = igraph_stack_int_pop(&stack); + if (IGRAPH_LIKELY(! VECTOR(visited)[u])) { + VECTOR(visited)[u] = 1; + *visited_count += 1; + } + + /* register all its yet-unvisited neighbours for future processing */ + neighbors = igraph_adjlist_get(al, u); + ncount = igraph_vector_int_size(neighbors); + for (i = 0; i < ncount; ++i) { + igraph_integer_t v = VECTOR(*neighbors)[i]; + if (! VECTOR(visited)[v]) { + IGRAPH_CHECK(igraph_stack_int_push(&stack, v)); + } + } + } + + igraph_stack_int_destroy(&stack); + igraph_vector_bool_destroy(&visited); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} + + +/** + * \ingroup structural + * \function igraph_is_tree + * \brief Decides whether the graph is a tree. + * + * An undirected graph is a tree if it is connected and has no cycles. + * + * + * In the directed case, a possible additional requirement is that all + * edges are oriented away from a root (out-tree or arborescence) or all edges + * are oriented towards a root (in-tree or anti-arborescence). + * This test can be controlled using the \p mode parameter. + * + * + * By convention, the null graph (i.e. the graph with no vertices) is considered not to be a tree. + * + * \param graph The graph object to analyze. + * \param res Pointer to a logical variable, the result will be stored + * here. + * \param root If not \c NULL, the root node will be stored here. When \p mode + * is \c IGRAPH_ALL or the graph is undirected, any vertex can be the root + * and \p root is set to 0 (the first vertex). When \p mode is \c IGRAPH_OUT + * or \c IGRAPH_IN, the root is set to the vertex with zero in- or out-degree, + * respectively. + * \param mode For a directed graph this specifies whether to test for an + * out-tree, an in-tree or ignore edge directions. The respective + * possible values are: + * \c IGRAPH_OUT, \c IGRAPH_IN, \c IGRAPH_ALL. This argument is + * ignored for undirected graphs. + * \return Error code: + * \c IGRAPH_EINVAL: invalid mode argument. + * + * Time complexity: At most O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa igraph_is_weakly_connected() + * + * \example examples/simple/igraph_tree.c + */ + +int igraph_is_tree(const igraph_t *graph, igraph_bool_t *res, igraph_integer_t *root, igraph_neimode_t mode) { + igraph_adjlist_t al; + igraph_integer_t iroot = 0; + igraph_integer_t visited_count; + igraph_integer_t vcount, ecount; + + vcount = igraph_vcount(graph); + ecount = igraph_ecount(graph); + + /* A tree must have precisely vcount-1 edges. */ + /* By convention, the zero-vertex graph will not be considered a tree. */ + if (ecount != vcount - 1) { + *res = 0; + return IGRAPH_SUCCESS; + } + + /* The single-vertex graph is a tree, provided it has no edges (checked in the previous if (..)) */ + if (vcount == 1) { + *res = 1; + if (root) { + *root = 0; + } + return IGRAPH_SUCCESS; + } + + /* For higher vertex counts we cannot short-circuit due to the possibility + * of loops or multi-edges even when the edge count is correct. */ + + /* Ignore mode for undirected graphs. */ + if (! igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_CHECK(igraph_adjlist_init(graph, &al, mode)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &al); + + /* The main algorithm: + * We find a root and check that all other vertices are reachable from it. + * We have already checked the number of edges, so with the additional + * reachability condition we can verify if the graph is a tree. + * + * For directed graphs, the root is the node with no incoming/outgoing + * connections, depending on 'mode'. For undirected, it is arbitrary, so + * we choose 0. + */ + + *res = 1; /* assume success */ + + switch (mode) { + case IGRAPH_ALL: + iroot = 0; + break; + + case IGRAPH_IN: + case IGRAPH_OUT: { + igraph_vector_t degree; + igraph_integer_t i; + + IGRAPH_CHECK(igraph_vector_init(°ree, 0)); + IGRAPH_FINALLY(igraph_vector_destroy, °ree); + + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), mode == IGRAPH_IN ? IGRAPH_OUT : IGRAPH_IN, /* loops = */ 1)); + + for (i = 0; i < vcount; ++i) + if (VECTOR(degree)[i] == 0) { + break; + } + + /* if no suitable root is found, the graph is not a tree */ + if (i == vcount) { + *res = 0; + } else { + iroot = i; + } + + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + } + + break; + default: + IGRAPH_ERROR("Invalid mode", IGRAPH_EINVMODE); + } + + /* if no suitable root was found, skip visting vertices */ + if (*res) { + IGRAPH_CHECK(igraph_i_is_tree_visitor(iroot, &al, &visited_count)); + *res = visited_count == vcount; + } + + if (root) { + *root = iroot; + } + + igraph_adjlist_destroy(&al); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/structural_properties_internal.h b/src/structural_properties_internal.h new file mode 100644 index 0000000..bcaed02 --- /dev/null +++ b/src/structural_properties_internal.h @@ -0,0 +1,47 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2011-2016 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#ifndef STRUCTURAL_PROPERTIES_INTERNAL_H +#define STRUCTURAL_PROPERTIES_INTERNAL_H + +#include "igraph_constants.h" +#include "igraph_types.h" +#include "igraph_iterators.h" + +int igraph_i_induced_subgraph_suggest_implementation( + const igraph_t *graph, const igraph_vs_t vids, + igraph_subgraph_implementation_t* result +); + +int igraph_i_subgraph_copy_and_delete(const igraph_t *graph, igraph_t *res, + const igraph_vs_t vids, + igraph_vector_t *map, + igraph_vector_t *invmap); + +int igraph_i_subgraph_create_from_scratch(const igraph_t *graph, + igraph_t *res, + const igraph_vs_t vids, + igraph_vector_t *map, + igraph_vector_t *invmap); + +#endif diff --git a/src/structure_generators.c b/src/structure_generators.c new file mode 100644 index 0000000..9a2f826 --- /dev/null +++ b/src/structure_generators.c @@ -0,0 +1,2453 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_constructors.h" +#include "igraph_structural.h" +#include "igraph_memory.h" +#include "igraph_interface.h" +#include "igraph_attributes.h" +#include "igraph_adjlist.h" +#include "igraph_interrupt_internal.h" +#include "igraph_dqueue.h" +#include "config.h" + +#include +#include +#include + +/** + * \section about_generators + * + * Graph generators create graphs. + * + * Almost all functions which create graph objects are documented + * here. The exceptions are \ref igraph_subgraph() and alike, these + * create graphs based on another graph. + */ + + +/** + * \ingroup generators + * \function igraph_create + * \brief Creates a graph with the specified edges. + * + * \param graph An uninitialized graph object. + * \param edges The edges to add, the first two elements are the first + * edge, etc. + * \param n The number of vertices in the graph, if smaller or equal + * to the highest vertex id in the \p edges vector it + * will be increased automatically. So it is safe to give 0 + * here. + * \param directed Boolean, whether to create a directed graph or + * not. If yes, then the first edge points from the first + * vertex id in \p edges to the second, etc. + * \return Error code: + * \c IGRAPH_EINVEVECTOR: invalid edges + * vector (odd number of vertices). + * \c IGRAPH_EINVVID: invalid (negative) + * vertex id. + * + * Time complexity: O(|V|+|E|), + * |V| is the number of vertices, + * |E| the number of edges in the + * graph. + * + * \example examples/simple/igraph_create.c + */ +int igraph_create(igraph_t *graph, const igraph_vector_t *edges, + igraph_integer_t n, igraph_bool_t directed) { + igraph_bool_t has_edges = igraph_vector_size(edges) > 0; + igraph_real_t max = has_edges ? igraph_vector_max(edges) + 1 : 0; + + if (igraph_vector_size(edges) % 2 != 0) { + IGRAPH_ERROR("Invalid (odd) edges vector", IGRAPH_EINVEVECTOR); + } + if (has_edges && !igraph_vector_isininterval(edges, 0, max - 1)) { + IGRAPH_ERROR("Invalid (negative) vertex id", IGRAPH_EINVVID); + } + + IGRAPH_CHECK(igraph_empty(graph, n, directed)); + IGRAPH_FINALLY(igraph_destroy, graph); + if (has_edges) { + igraph_integer_t vc = igraph_vcount(graph); + if (vc < max) { + IGRAPH_CHECK(igraph_add_vertices(graph, (igraph_integer_t) (max - vc), 0)); + } + IGRAPH_CHECK(igraph_add_edges(graph, edges, 0)); + } + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +static int igraph_i_adjacency_directed(igraph_matrix_t *adjmatrix, + igraph_vector_t *edges); +static int igraph_i_adjacency_max(igraph_matrix_t *adjmatrix, + igraph_vector_t *edges); +static int igraph_i_adjacency_upper(igraph_matrix_t *adjmatrix, + igraph_vector_t *edges); +static int igraph_i_adjacency_lower(igraph_matrix_t *adjmatrix, + igraph_vector_t *edges); +static int igraph_i_adjacency_min(igraph_matrix_t *adjmatrix, + igraph_vector_t *edges); + +static int igraph_i_adjacency_directed(igraph_matrix_t *adjmatrix, igraph_vector_t *edges) { + + long int no_of_nodes = igraph_matrix_nrow(adjmatrix); + long int i, j, k; + + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < no_of_nodes; j++) { + long int M = (long int) MATRIX(*adjmatrix, i, j); + for (k = 0; k < M; k++) { + IGRAPH_CHECK(igraph_vector_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(edges, j)); + } + } + } + + return 0; +} + +static int igraph_i_adjacency_max(igraph_matrix_t *adjmatrix, igraph_vector_t *edges) { + + long int no_of_nodes = igraph_matrix_nrow(adjmatrix); + long int i, j, k; + + for (i = 0; i < no_of_nodes; i++) { + for (j = i; j < no_of_nodes; j++) { + long int M1 = (long int) MATRIX(*adjmatrix, i, j); + long int M2 = (long int) MATRIX(*adjmatrix, j, i); + if (M1 < M2) { + M1 = M2; + } + for (k = 0; k < M1; k++) { + IGRAPH_CHECK(igraph_vector_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(edges, j)); + } + } + } + + return 0; +} + +static int igraph_i_adjacency_upper(igraph_matrix_t *adjmatrix, igraph_vector_t *edges) { + + long int no_of_nodes = igraph_matrix_nrow(adjmatrix); + long int i, j, k; + + for (i = 0; i < no_of_nodes; i++) { + for (j = i; j < no_of_nodes; j++) { + long int M = (long int) MATRIX(*adjmatrix, i, j); + for (k = 0; k < M; k++) { + IGRAPH_CHECK(igraph_vector_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(edges, j)); + } + } + } + return 0; +} + +static int igraph_i_adjacency_lower(igraph_matrix_t *adjmatrix, igraph_vector_t *edges) { + + long int no_of_nodes = igraph_matrix_nrow(adjmatrix); + long int i, j, k; + + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j <= i; j++) { + long int M = (long int) MATRIX(*adjmatrix, i, j); + for (k = 0; k < M; k++) { + IGRAPH_CHECK(igraph_vector_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(edges, j)); + } + } + } + return 0; +} + +static int igraph_i_adjacency_min(igraph_matrix_t *adjmatrix, igraph_vector_t *edges) { + + long int no_of_nodes = igraph_matrix_nrow(adjmatrix); + long int i, j, k; + + for (i = 0; i < no_of_nodes; i++) { + for (j = i; j < no_of_nodes; j++) { + long int M1 = (long int) MATRIX(*adjmatrix, i, j); + long int M2 = (long int) MATRIX(*adjmatrix, j, i); + if (M1 > M2) { + M1 = M2; + } + for (k = 0; k < M1; k++) { + IGRAPH_CHECK(igraph_vector_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(edges, j)); + } + } + } + + return 0; +} + +/** + * \ingroup generators + * \function igraph_adjacency + * \brief Creates a graph object from an adjacency matrix. + * + * The order of the vertices in the matrix is preserved, i.e. the vertex + * corresponding to the first row/column will be vertex with id 0, the + * next row is for vertex 1, etc. + * \param graph Pointer to an uninitialized graph object. + * \param adjmatrix The adjacency matrix. How it is interpreted + * depends on the \p mode argument. + * \param mode Constant to specify how the given matrix is interpreted + * as an adjacency matrix. Possible values + * (A(i,j) + * is the element in row i and column + * j in the adjacency matrix + * \p adjmatrix): + * \clist + * \cli IGRAPH_ADJ_DIRECTED + * the graph will be directed and + * an element gives the number of edges between two vertices. + * \cli IGRAPH_ADJ_UNDIRECTED + * this is the same as \c IGRAPH_ADJ_MAX, + * for convenience. + * \cli IGRAPH_ADJ_MAX + * undirected graph will be created + * and the number of edges between vertices + * i and + * j is + * max(A(i,j), A(j,i)). + * \cli IGRAPH_ADJ_MIN + * undirected graph will be created + * with min(A(i,j), A(j,i)) + * edges between vertices + * i and + * j. + * \cli IGRAPH_ADJ_PLUS + * undirected graph will be created + * with A(i,j)+A(j,i) edges + * between vertices + * i and + * j. + * \cli IGRAPH_ADJ_UPPER + * undirected graph will be created, + * only the upper right triangle (including the diagonal) is + * used for the number of edges. + * \cli IGRAPH_ADJ_LOWER + * undirected graph will be created, + * only the lower left triangle (including the diagonal) is + * used for creating the edges. + * \endclist + * \return Error code, + * \c IGRAPH_NONSQUARE: non-square matrix. + * + * Time complexity: O(|V||V|), + * |V| is the number of vertices in the graph. + * + * \example examples/simple/igraph_adjacency.c + */ + +int igraph_adjacency(igraph_t *graph, igraph_matrix_t *adjmatrix, + igraph_adjacency_t mode) { + + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + long int no_of_nodes; + + /* Some checks */ + if (igraph_matrix_nrow(adjmatrix) != igraph_matrix_ncol(adjmatrix)) { + IGRAPH_ERROR("Non-square matrix", IGRAPH_NONSQUARE); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + /* Collect the edges */ + no_of_nodes = igraph_matrix_nrow(adjmatrix); + switch (mode) { + case IGRAPH_ADJ_DIRECTED: + IGRAPH_CHECK(igraph_i_adjacency_directed(adjmatrix, &edges)); + break; + case IGRAPH_ADJ_MAX: + IGRAPH_CHECK(igraph_i_adjacency_max(adjmatrix, &edges)); + break; + case IGRAPH_ADJ_UPPER: + IGRAPH_CHECK(igraph_i_adjacency_upper(adjmatrix, &edges)); + break; + case IGRAPH_ADJ_LOWER: + IGRAPH_CHECK(igraph_i_adjacency_lower(adjmatrix, &edges)); + break; + case IGRAPH_ADJ_MIN: + IGRAPH_CHECK(igraph_i_adjacency_min(adjmatrix, &edges)); + break; + case IGRAPH_ADJ_PLUS: + IGRAPH_CHECK(igraph_i_adjacency_directed(adjmatrix, &edges)); + break; + } + + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) no_of_nodes, + (mode == IGRAPH_ADJ_DIRECTED))); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +static int igraph_i_weighted_adjacency_directed( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops); +static int igraph_i_weighted_adjacency_plus( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops); +static int igraph_i_weighted_adjacency_max( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops); +static int igraph_i_weighted_adjacency_upper( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops); +static int igraph_i_weighted_adjacency_lower( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops); +static int igraph_i_weighted_adjacency_min( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops); + +static int igraph_i_weighted_adjacency_directed( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops) { + + long int no_of_nodes = igraph_matrix_nrow(adjmatrix); + long int i, j; + + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j < no_of_nodes; j++) { + igraph_real_t M = MATRIX(*adjmatrix, i, j); + if (M == 0.0) { + continue; + } + if (i == j && !loops) { + continue; + } + IGRAPH_CHECK(igraph_vector_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + + return 0; +} + +static int igraph_i_weighted_adjacency_plus( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops) { + + long int no_of_nodes = igraph_matrix_nrow(adjmatrix); + long int i, j; + + for (i = 0; i < no_of_nodes; i++) { + for (j = i; j < no_of_nodes; j++) { + igraph_real_t M = MATRIX(*adjmatrix, i, j) + MATRIX(*adjmatrix, j, i); + if (M == 0.0) { + continue; + } + if (i == j && !loops) { + continue; + } + if (i == j) { + M /= 2; + } + IGRAPH_CHECK(igraph_vector_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + + return 0; +} + +static int igraph_i_weighted_adjacency_max( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops) { + + long int no_of_nodes = igraph_matrix_nrow(adjmatrix); + long int i, j; + + for (i = 0; i < no_of_nodes; i++) { + for (j = i; j < no_of_nodes; j++) { + igraph_real_t M1 = MATRIX(*adjmatrix, i, j); + igraph_real_t M2 = MATRIX(*adjmatrix, j, i); + if (M1 < M2) { + M1 = M2; + } + if (M1 == 0.0) { + continue; + } + if (i == j && !loops) { + continue; + } + IGRAPH_CHECK(igraph_vector_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M1)); + } + } + return 0; +} + +static int igraph_i_weighted_adjacency_upper( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops) { + + long int no_of_nodes = igraph_matrix_nrow(adjmatrix); + long int i, j; + + for (i = 0; i < no_of_nodes; i++) { + for (j = i; j < no_of_nodes; j++) { + igraph_real_t M = MATRIX(*adjmatrix, i, j); + if (M == 0.0) { + continue; + } + if (i == j && !loops) { + continue; + } + IGRAPH_CHECK(igraph_vector_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + return 0; +} + +static int igraph_i_weighted_adjacency_lower( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops) { + + long int no_of_nodes = igraph_matrix_nrow(adjmatrix); + long int i, j; + + for (i = 0; i < no_of_nodes; i++) { + for (j = 0; j <= i; j++) { + igraph_real_t M = MATRIX(*adjmatrix, i, j); + if (M == 0.0) { + continue; + } + if (i == j && !loops) { + continue; + } + IGRAPH_CHECK(igraph_vector_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M)); + } + } + return 0; +} + +static int igraph_i_weighted_adjacency_min( + const igraph_matrix_t *adjmatrix, + igraph_vector_t *edges, + igraph_vector_t *weights, + igraph_bool_t loops) { + + long int no_of_nodes = igraph_matrix_nrow(adjmatrix); + long int i, j; + + for (i = 0; i < no_of_nodes; i++) { + for (j = i; j < no_of_nodes; j++) { + igraph_real_t M1 = MATRIX(*adjmatrix, i, j); + igraph_real_t M2 = MATRIX(*adjmatrix, j, i); + if (M1 > M2) { + M1 = M2; + } + if (M1 == 0.0) { + continue; + } + if (i == j && !loops) { + continue; + } + IGRAPH_CHECK(igraph_vector_push_back(edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(edges, j)); + IGRAPH_CHECK(igraph_vector_push_back(weights, M1)); + } + } + + return 0; +} + +/** + * \ingroup generators + * \function igraph_weighted_adjacency + * \brief Creates a graph object from a weighted adjacency matrix. + * + * The order of the vertices in the matrix is preserved, i.e. the vertex + * corresponding to the first row/column will be vertex with id 0, the + * next row is for vertex 1, etc. + * \param graph Pointer to an uninitialized graph object. + * \param adjmatrix The weighted adjacency matrix. How it is interpreted + * depends on the \p mode argument. The common feature is that + * edges with zero weights are considered nonexistent (however, + * negative weights are permitted). + * \param mode Constant to specify how the given matrix is interpreted + * as an adjacency matrix. Possible values + * (A(i,j) + * is the element in row i and column + * j in the adjacency matrix + * \p adjmatrix): + * \clist + * \cli IGRAPH_ADJ_DIRECTED + * the graph will be directed and + * an element gives the weight of the edge between two vertices. + * \cli IGRAPH_ADJ_UNDIRECTED + * this is the same as \c IGRAPH_ADJ_MAX, + * for convenience. + * \cli IGRAPH_ADJ_MAX + * undirected graph will be created + * and the weight of the edge between vertices + * i and + * j is + * max(A(i,j), A(j,i)). + * \cli IGRAPH_ADJ_MIN + * undirected graph will be created + * with edge weight min(A(i,j), A(j,i)) + * between vertices + * i and + * j. + * \cli IGRAPH_ADJ_PLUS + * undirected graph will be created + * with edge weight A(i,j)+A(j,i) + * between vertices + * i and + * j. + * \cli IGRAPH_ADJ_UPPER + * undirected graph will be created, + * only the upper right triangle (including the diagonal) is + * used for the edge weights. + * \cli IGRAPH_ADJ_LOWER + * undirected graph will be created, + * only the lower left triangle (including the diagonal) is + * used for the edge weights. + * \endclist + * \param attr the name of the attribute that will store the edge weights. + * If \c NULL , it will use \c weight as the attribute name. + * \param loops Logical scalar, whether to ignore the diagonal elements + * in the adjacency matrix. + * \return Error code, + * \c IGRAPH_NONSQUARE: non-square matrix. + * + * Time complexity: O(|V||V|), + * |V| is the number of vertices in the graph. + * + * \example examples/simple/igraph_weighted_adjacency.c + */ + +int igraph_weighted_adjacency(igraph_t *graph, igraph_matrix_t *adjmatrix, + igraph_adjacency_t mode, const char* attr, + igraph_bool_t loops) { + + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + igraph_vector_t weights = IGRAPH_VECTOR_NULL; + const char* default_attr = "weight"; + igraph_vector_ptr_t attr_vec; + igraph_attribute_record_t attr_rec; + long int no_of_nodes; + + /* Some checks */ + if (igraph_matrix_nrow(adjmatrix) != igraph_matrix_ncol(adjmatrix)) { + IGRAPH_ERROR("Non-square matrix", IGRAPH_NONSQUARE); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&weights, 0); + IGRAPH_VECTOR_PTR_INIT_FINALLY(&attr_vec, 1); + + /* Collect the edges */ + no_of_nodes = igraph_matrix_nrow(adjmatrix); + switch (mode) { + case IGRAPH_ADJ_DIRECTED: + IGRAPH_CHECK(igraph_i_weighted_adjacency_directed(adjmatrix, &edges, + &weights, loops)); + break; + case IGRAPH_ADJ_MAX: + IGRAPH_CHECK(igraph_i_weighted_adjacency_max(adjmatrix, &edges, + &weights, loops)); + break; + case IGRAPH_ADJ_UPPER: + IGRAPH_CHECK(igraph_i_weighted_adjacency_upper(adjmatrix, &edges, + &weights, loops)); + break; + case IGRAPH_ADJ_LOWER: + IGRAPH_CHECK(igraph_i_weighted_adjacency_lower(adjmatrix, &edges, + &weights, loops)); + break; + case IGRAPH_ADJ_MIN: + IGRAPH_CHECK(igraph_i_weighted_adjacency_min(adjmatrix, &edges, + &weights, loops)); + break; + case IGRAPH_ADJ_PLUS: + IGRAPH_CHECK(igraph_i_weighted_adjacency_plus(adjmatrix, &edges, + &weights, loops)); + break; + } + + /* Prepare attribute record */ + attr_rec.name = attr ? attr : default_attr; + attr_rec.type = IGRAPH_ATTRIBUTE_NUMERIC; + attr_rec.value = &weights; + VECTOR(attr_vec)[0] = &attr_rec; + + /* Create graph */ + IGRAPH_CHECK(igraph_empty(graph, (igraph_integer_t) no_of_nodes, + (mode == IGRAPH_ADJ_DIRECTED))); + IGRAPH_FINALLY(igraph_destroy, graph); + if (igraph_vector_size(&edges) > 0) { + IGRAPH_CHECK(igraph_add_edges(graph, &edges, &attr_vec)); + } + IGRAPH_FINALLY_CLEAN(1); + + /* Cleanup */ + igraph_vector_destroy(&edges); + igraph_vector_destroy(&weights); + igraph_vector_ptr_destroy(&attr_vec); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \ingroup generators + * \function igraph_star + * \brief Creates a \em star graph, every vertex connects only to the center. + * + * \param graph Pointer to an uninitialized graph object, this will + * be the result. + * \param n Integer constant, the number of vertices in the graph. + * \param mode Constant, gives the type of the star graph to + * create. Possible values: + * \clist + * \cli IGRAPH_STAR_OUT + * directed star graph, edges point + * \em from the center to the other vertices. + * \cli IGRAPH_STAR_IN + * directed star graph, edges point + * \em to the center from the other vertices. + * \cli IGRAPH_STAR_MUTUAL + * directed star graph with mutual edges. + * \cli IGRAPH_STAR_UNDIRECTED + * an undirected star graph is + * created. + * \endclist + * \param center Id of the vertex which will be the center of the + * graph. + * \return Error code: + * \clist + * \cli IGRAPH_EINVVID + * invalid number of vertices. + * \cli IGRAPH_EINVAL + * invalid center vertex. + * \cli IGRAPH_EINVMODE + * invalid mode argument. + * \endclist + * + * Time complexity: O(|V|), the + * number of vertices in the graph. + * + * \sa \ref igraph_lattice(), \ref igraph_ring(), \ref igraph_tree() + * for creating other regular structures. + * + * \example examples/simple/igraph_star.c + */ + +int igraph_star(igraph_t *graph, igraph_integer_t n, igraph_star_mode_t mode, + igraph_integer_t center) { + + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + long int i; + + if (n < 0) { + IGRAPH_ERROR("Invalid number of vertices", IGRAPH_EINVVID); + } + if (center < 0 || center > n - 1) { + IGRAPH_ERROR("Invalid center vertex", IGRAPH_EINVAL); + } + if (mode != IGRAPH_STAR_OUT && mode != IGRAPH_STAR_IN && + mode != IGRAPH_STAR_MUTUAL && mode != IGRAPH_STAR_UNDIRECTED) { + IGRAPH_ERROR("invalid mode", IGRAPH_EINVMODE); + } + + if (mode != IGRAPH_STAR_MUTUAL) { + IGRAPH_VECTOR_INIT_FINALLY(&edges, (n - 1) * 2); + } else { + IGRAPH_VECTOR_INIT_FINALLY(&edges, (n - 1) * 2 * 2); + } + + if (mode == IGRAPH_STAR_OUT) { + for (i = 0; i < center; i++) { + VECTOR(edges)[2 * i] = center; + VECTOR(edges)[2 * i + 1] = i; + } + for (i = center + 1; i < n; i++) { + VECTOR(edges)[2 * (i - 1)] = center; + VECTOR(edges)[2 * (i - 1) + 1] = i; + } + } else if (mode == IGRAPH_STAR_MUTUAL) { + for (i = 0; i < center; i++) { + VECTOR(edges)[4 * i] = center; + VECTOR(edges)[4 * i + 1] = i; + VECTOR(edges)[4 * i + 2] = i; + VECTOR(edges)[4 * i + 3] = center; + } + for (i = center + 1; i < n; i++) { + VECTOR(edges)[4 * i - 4] = center; + VECTOR(edges)[4 * i - 3] = i; + VECTOR(edges)[4 * i - 2] = i; + VECTOR(edges)[4 * i - 1] = center; + } + } else { + for (i = 0; i < center; i++) { + VECTOR(edges)[2 * i + 1] = center; + VECTOR(edges)[2 * i] = i; + } + for (i = center + 1; i < n; i++) { + VECTOR(edges)[2 * (i - 1) + 1] = center; + VECTOR(edges)[2 * (i - 1)] = i; + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, 0, + (mode != IGRAPH_STAR_UNDIRECTED))); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \ingroup generators + * \function igraph_lattice + * \brief Creates most kinds of lattices. + * + * \param graph An uninitialized graph object. + * \param dimvector Vector giving the sizes of the lattice in each of + * its dimensions. Ie. the dimension of the lattice will be the + * same as the length of this vector. + * \param nei Integer value giving the distance (number of steps) + * within which two vertices will be connected. + * \param directed Boolean, whether to create a directed graph. The + * direction of the edges is determined by the generation + * algorithm and is unlikely to suit you, so this isn't a very + * useful option. + * \param mutual Boolean, if the graph is directed this gives whether + * to create all connections as mutual. + * \param circular Boolean, defines whether the generated lattice is + * periodic. + * \return Error code: + * \c IGRAPH_EINVAL: invalid (negative) + * dimension vector. + * + * Time complexity: if \p nei is less than two then it is O(|V|+|E|) (as + * far as I remember), |V| and |E| are the number of vertices + * and edges in the generated graph. Otherwise it is O(|V|*d^o+|E|), d + * is the average degree of the graph, o is the \p nei argument. + */ +int igraph_lattice(igraph_t *graph, const igraph_vector_t *dimvector, + igraph_integer_t nei, igraph_bool_t directed, igraph_bool_t mutual, + igraph_bool_t circular) { + + long int dims = igraph_vector_size(dimvector); + long int no_of_nodes = (long int) igraph_vector_prod(dimvector); + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + long int *coords, *weights; + long int i, j; + int carry, pos; + + if (igraph_vector_any_smaller(dimvector, 0)) { + IGRAPH_ERROR("Invalid dimension vector", IGRAPH_EINVAL); + } + + /* init coords & weights */ + + coords = igraph_Calloc(dims, long int); + if (coords == 0) { + IGRAPH_ERROR("lattice failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, coords); + weights = igraph_Calloc(dims, long int); + if (weights == 0) { + IGRAPH_ERROR("lattice failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, weights); + if (dims > 0) { + weights[0] = 1; + for (i = 1; i < dims; i++) { + weights[i] = weights[i - 1] * (long int) VECTOR(*dimvector)[i - 1]; + } + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_nodes * dims + + mutual * directed * no_of_nodes * dims)); + + for (i = 0; i < no_of_nodes; i++) { + IGRAPH_ALLOW_INTERRUPTION(); + for (j = 0; j < dims; j++) { + if (circular || coords[j] != VECTOR(*dimvector)[j] - 1) { + long int new_nei; + if (coords[j] != VECTOR(*dimvector)[j] - 1) { + new_nei = i + weights[j] + 1; + } else { + new_nei = i - (long int) (VECTOR(*dimvector)[j] - 1) * weights[j] + 1; + } + if (new_nei != i + 1 && + (VECTOR(*dimvector)[j] != 2 || coords[j] != 1 || directed)) { + igraph_vector_push_back(&edges, i); /* reserved */ + igraph_vector_push_back(&edges, new_nei - 1); /* reserved */ + } + } /* if circular || coords[j] */ + if (mutual && directed && (circular || coords[j] != 0)) { + long int new_nei; + if (coords[j] != 0) { + new_nei = i - weights[j] + 1; + } else { + new_nei = i + (long int) (VECTOR(*dimvector)[j] - 1) * weights[j] + 1; + } + if (new_nei != i + 1 && + (VECTOR(*dimvector)[j] != 2 || !circular)) { + igraph_vector_push_back(&edges, i); /* reserved */ + igraph_vector_push_back(&edges, new_nei - 1); /* reserved */ + } + } /* if circular || coords[0] */ + } /* for j= 2) { + IGRAPH_CHECK(igraph_connect_neighborhood(graph, nei, IGRAPH_ALL)); + } + + /* clean up */ + igraph_Free(coords); + igraph_Free(weights); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \ingroup generators + * \function igraph_ring + * \brief Creates a \em ring graph, a one dimensional lattice. + * + * An undirected (circular) ring on n vertices is commonly known in graph + * theory as the cycle graph C_n. + * + * \param graph Pointer to an uninitialized graph object. + * \param n The number of vertices in the ring. + * \param directed Logical, whether to create a directed ring. + * \param mutual Logical, whether to create mutual edges in a directed + * ring. It is ignored for undirected graphs. + * \param circular Logical, if false, the ring will be open (this is + * not a real \em ring actually). + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of vertices. + * + * Time complexity: O(|V|), the + * number of vertices in the graph. + * + * \sa \ref igraph_lattice() for generating more general lattices. + * + * \example examples/simple/igraph_ring.c + */ + +int igraph_ring(igraph_t *graph, igraph_integer_t n, igraph_bool_t directed, + igraph_bool_t mutual, igraph_bool_t circular) { + + igraph_vector_t v = IGRAPH_VECTOR_NULL; + + if (n < 0) { + IGRAPH_ERROR("negative number of vertices", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&v, 1); + VECTOR(v)[0] = n; + + IGRAPH_CHECK(igraph_lattice(graph, &v, 1, directed, mutual, circular)); + igraph_vector_destroy(&v); + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \ingroup generators + * \function igraph_tree + * \brief Creates a tree in which almost all vertices have the same number of children. + * + * \param graph Pointer to an uninitialized graph object. + * \param n Integer, the number of vertices in the graph. + * \param children Integer, the number of children of a vertex in the + * tree. + * \param type Constant, gives whether to create a directed tree, and + * if this is the case, also its orientation. Possible values: + * \clist + * \cli IGRAPH_TREE_OUT + * directed tree, the edges point + * from the parents to their children, + * \cli IGRAPH_TREE_IN + * directed tree, the edges point from + * the children to their parents. + * \cli IGRAPH_TREE_UNDIRECTED + * undirected tree. + * \endclist + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of vertices. + * \c IGRAPH_INVMODE: invalid mode argument. + * + * Time complexity: O(|V|+|E|), the + * number of vertices plus the number of edges in the graph. + * + * \sa \ref igraph_lattice(), \ref igraph_star() for creating other regular + * structures; \ref igraph_from_prufer() for creating arbitrary trees; + * \ref igraph_tree_game() for uniform random sampling of trees. + * + * \example examples/simple/igraph_tree.c + */ + +int igraph_tree(igraph_t *graph, igraph_integer_t n, igraph_integer_t children, + igraph_tree_mode_t type) { + + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + long int i, j; + long int idx = 0; + long int to = 1; + + if (n < 0 || children <= 0) { + IGRAPH_ERROR("Invalid number of vertices or children", IGRAPH_EINVAL); + } + if (type != IGRAPH_TREE_OUT && type != IGRAPH_TREE_IN && + type != IGRAPH_TREE_UNDIRECTED) { + IGRAPH_ERROR("Invalid mode argument", IGRAPH_EINVMODE); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 2 * (n - 1)); + + i = 0; + if (type == IGRAPH_TREE_OUT) { + while (idx < 2 * (n - 1)) { + for (j = 0; j < children && idx < 2 * (n - 1); j++) { + VECTOR(edges)[idx++] = i; + VECTOR(edges)[idx++] = to++; + } + i++; + } + } else { + while (idx < 2 * (n - 1)) { + for (j = 0; j < children && idx < 2 * (n - 1); j++) { + VECTOR(edges)[idx++] = to++; + VECTOR(edges)[idx++] = i; + } + i++; + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, type != IGRAPH_TREE_UNDIRECTED)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \ingroup generators + * \function igraph_full + * \brief Creates a full graph (directed or undirected, with or without loops). + * + * + * In a full graph every possible edge is present, every vertex is + * connected to every other vertex. A full graph in \c igraph should be + * distinguished from the concept of complete graphs as used in graph theory. + * If n is a positive integer, then the complete graph K_n on n vertices is + * the undirected simple graph with the following property. For any distinct + * pair (u,v) of vertices in K_n, uv (or equivalently vu) is an edge of K_n. + * In \c igraph, a full graph on n vertices can be K_n, a directed version of + * K_n, or K_n with at least one loop edge. In any case, if F is a full graph + * on n vertices as generated by \c igraph, then K_n is a subgraph of the + * undirected version of F. + * + * \param graph Pointer to an uninitialized graph object. + * \param n Integer, the number of vertices in the graph. + * \param directed Logical, whether to create a directed graph. + * \param loops Logical, whether to include self-edges (loops). + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of vertices. + * + * Time complexity: O(|V|+|E|), + * |V| is the number of vertices, + * |E| the number of edges in the + * graph. Of course this is the same as + * O(|E|)=O(|V||V|) + * here. + * + * \sa \ref igraph_lattice(), \ref igraph_star(), \ref igraph_tree() + * for creating other regular structures. + * + * \example examples/simple/igraph_full.c + */ + +int igraph_full(igraph_t *graph, igraph_integer_t n, igraph_bool_t directed, + igraph_bool_t loops) { + + igraph_vector_t edges = IGRAPH_VECTOR_NULL; + long int i, j; + + if (n < 0) { + IGRAPH_ERROR("invalid number of vertices", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + if (directed && loops) { + IGRAPH_CHECK(igraph_vector_reserve(&edges, n * n)); + for (i = 0; i < n; i++) { + for (j = 0; j < n; j++) { + igraph_vector_push_back(&edges, i); /* reserved */ + igraph_vector_push_back(&edges, j); /* reserved */ + } + } + } else if (directed && !loops) { + IGRAPH_CHECK(igraph_vector_reserve(&edges, n * (n - 1))); + for (i = 0; i < n; i++) { + for (j = 0; j < i; j++) { + igraph_vector_push_back(&edges, i); /* reserved */ + igraph_vector_push_back(&edges, j); /* reserved */ + } + for (j = i + 1; j < n; j++) { + igraph_vector_push_back(&edges, i); /* reserved */ + igraph_vector_push_back(&edges, j); /* reserved */ + } + } + } else if (!directed && loops) { + IGRAPH_CHECK(igraph_vector_reserve(&edges, n * (n + 1) / 2)); + for (i = 0; i < n; i++) { + for (j = i; j < n; j++) { + igraph_vector_push_back(&edges, i); /* reserved */ + igraph_vector_push_back(&edges, j); /* reserved */ + } + } + } else { + IGRAPH_CHECK(igraph_vector_reserve(&edges, n * (n - 1) / 2)); + for (i = 0; i < n; i++) { + for (j = i + 1; j < n; j++) { + igraph_vector_push_back(&edges, i); /* reserved */ + igraph_vector_push_back(&edges, j); /* reserved */ + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_full_citation + * Creates a full citation graph + * + * This is a directed graph, where every i->j edge is + * present if and only if j<i. + * If the \c directed argument is zero then an undirected graph is + * created, and it is just a full graph. + * \param graph Pointer to an uninitialized graph object, the result + * is stored here. + * \param n The number of vertices. + * \param directed Whether to created a directed graph. If zero an + * undirected graph is created. + * \return Error code. + * + * Time complexity: O(|V|^2), as we have many edges. + */ + +int igraph_full_citation(igraph_t *graph, igraph_integer_t n, + igraph_bool_t directed) { + igraph_vector_t edges; + long int i, j, ptr = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, n * (n - 1)); + for (i = 1; i < n; i++) { + for (j = 0; j < i; j++) { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = j; + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_small + * \brief Shorthand to create a short graph, giving the edges as arguments. + * + * + * This function is handy when a relatively small graph needs to be created. + * Instead of giving the edges as a vector, they are given simply as + * arguments and a '-1' needs to be given after the last meaningful + * edge argument. + * + * Note that only graphs which have vertices less than + * the highest value of the 'int' type can be created this way. If you + * give larger values then the result is undefined. + * + * \param graph Pointer to an uninitialized graph object. The result + * will be stored here. + * \param n The number of vertices in the graph; a nonnegative integer. + * \param directed Logical constant; gives whether the graph should be + * directed. Supported values are: + * \clist + * \cli IGRAPH_DIRECTED + * The graph to be created will be \em directed. + * \cli IGRAPH_UNDIRECTED + * The graph to be created will be \em undirected. + * \endclist + * \param ... The additional arguments giving the edges of the + * graph. Don't forget to supply an additional '-1' after the last + * (meaningful) argument. + * \return Error code. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges in the graph to create. + * + * \example examples/simple/igraph_small.c + */ + +int igraph_small(igraph_t *graph, igraph_integer_t n, igraph_bool_t directed, + ...) { + igraph_vector_t edges; + va_list ap; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + va_start(ap, directed); + while (1) { + int num = va_arg(ap, int); + if (num == -1) { + break; + } + igraph_vector_push_back(&edges, num); + } + + IGRAPH_CHECK(igraph_create(graph, &edges, n, directed)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_extended_chordal_ring + * Create an extended chordal ring + * + * An extended chordal ring is a cycle graph with additional chords + * connecting its vertices. + * + * Each row \c L of the matrix \p W specifies a set of chords to be + * inserted, in the following way: vertex \c i will connect to a vertex + * L[(i mod p)] steps ahead of it along the cycle, where + * \c p is the length of \c L. + * In other words, vertex \c i will be connected to vertex + * (i + L[(i mod p)]) mod nodes. + * + * + * See also Kotsis, G: Interconnection Topologies for Parallel Processing + * Systems, PARS Mitteilungen 11, 1-6, 1993. + * + * \param graph Pointer to an uninitialized graph object, the result + * will be stored here. + * \param nodes Integer constant, the number of vertices in the + * graph. It must be at least 3. + * \param W The matrix specifying the extra edges. The number of + * columns should divide the number of total vertices. + * \param directed Whether the graph should be directed. + * \return Error code. + * + * \sa \ref igraph_ring(), \ref igraph_lcf(), \ref igraph_lcf_vector() + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + */ + +int igraph_extended_chordal_ring( + igraph_t *graph, igraph_integer_t nodes, const igraph_matrix_t *W, + igraph_bool_t directed) { + igraph_vector_t edges; + long int period = igraph_matrix_ncol(W); + long int nrow = igraph_matrix_nrow(W); + long int i, j, mpos = 0, epos = 0; + + if (nodes < 3) { + IGRAPH_ERROR("An extended chordal ring has at least 3 nodes", IGRAPH_EINVAL); + } + + if ((long int)nodes % period != 0) { + IGRAPH_ERROR("The period (number of columns in W) should divide the " + "number of nodes", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 2 * (nodes + nodes * nrow)); + + for (i = 0; i < nodes - 1; i++) { + VECTOR(edges)[epos++] = i; + VECTOR(edges)[epos++] = i + 1; + } + VECTOR(edges)[epos++] = nodes - 1; + VECTOR(edges)[epos++] = 0; + + if (nrow > 0) { + for (i = 0; i < nodes; i++) { + for (j = 0; j < nrow; j++) { + long int offset = (long int) MATRIX(*W, j, mpos); + long int v = (i + offset) % nodes; + + if (v < 0) { + v += nodes; /* handle negative offsets */ + } + + VECTOR(edges)[epos++] = i; + VECTOR(edges)[epos++] = v; + + } + mpos++; if (mpos == period) { + mpos = 0; + } + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, nodes, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_connect_neighborhood + * \brief Connects every vertex to its neighborhood + * + * This function adds new edges to the input graph. Each vertex is connected + * to all vertices reachable by at most \p order steps from it + * (unless a connection already existed). In other words, the \p order power of + * the graph is computed. + * + * Note that the input graph is modified in place, no + * new graph is created. Call \ref igraph_copy() if you want to keep + * the original graph as well. + * + * For undirected graphs reachability is always + * symmetric: if vertex A can be reached from vertex B in at + * most \p order steps, then the opposite is also true. Only one + * undirected (A,B) edge will be added in this case. + * \param graph The input graph, this is the output graph as well. + * \param order Integer constant, it gives the distance within which + * the vertices will be connected to the source vertex. + * \param mode Constant, it specifies how the neighborhood search is + * performed for directed graphs. If \c IGRAPH_OUT then vertices + * reachable from the source vertex will be connected, \c IGRAPH_IN + * is the opposite. If \c IGRAPH_ALL then the directed graph is + * considered as an undirected one. + * \return Error code. + * + * \sa \ref igraph_lattice() uses this function to connect the + * neighborhood of the vertices. + * + * Time complexity: O(|V|*d^k), |V| is the number of vertices in the + * graph, d is the average degree and k is the \p order argument. + */ + +int igraph_connect_neighborhood(igraph_t *graph, igraph_integer_t order, + igraph_neimode_t mode) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_dqueue_t q; + igraph_vector_t edges; + long int i, j, in; + long int *added; + igraph_vector_t neis; + + if (order < 0) { + IGRAPH_ERROR("Negative order, cannot connect neighborhood", IGRAPH_EINVAL); + } + + if (order < 2) { + IGRAPH_WARNING("Order smaller than two, graph will be unchanged"); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + added = igraph_Calloc(no_of_nodes, long int); + if (added == 0) { + IGRAPH_ERROR("Cannot connect neighborhood", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + IGRAPH_DQUEUE_INIT_FINALLY(&q, 100); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + for (i = 0; i < no_of_nodes; i++) { + added[i] = i + 1; + igraph_neighbors(graph, &neis, (igraph_integer_t) i, mode); + in = igraph_vector_size(&neis); + if (order > 1) { + for (j = 0; j < in; j++) { + long int nei = (long int) VECTOR(neis)[j]; + added[nei] = i + 1; + igraph_dqueue_push(&q, nei); + igraph_dqueue_push(&q, 1); + } + } + + while (!igraph_dqueue_empty(&q)) { + long int actnode = (long int) igraph_dqueue_pop(&q); + long int actdist = (long int) igraph_dqueue_pop(&q); + long int n; + igraph_neighbors(graph, &neis, (igraph_integer_t) actnode, mode); + n = igraph_vector_size(&neis); + + if (actdist < order - 1) { + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + IGRAPH_CHECK(igraph_dqueue_push(&q, nei)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1)); + if (mode != IGRAPH_ALL || i < nei) { + if (mode == IGRAPH_IN) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, nei)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + } else { + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, nei)); + } + } + } + } + } else { + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(neis)[j]; + if (added[nei] != i + 1) { + added[nei] = i + 1; + if (mode != IGRAPH_ALL || i < nei) { + if (mode == IGRAPH_IN) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, nei)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + } else { + IGRAPH_CHECK(igraph_vector_push_back(&edges, i)); + IGRAPH_CHECK(igraph_vector_push_back(&edges, nei)); + } + } + } + } + } + + } /* while q not empty */ + } /* for i < no_of_nodes */ + + igraph_vector_destroy(&neis); + igraph_dqueue_destroy(&q); + igraph_free(added); + IGRAPH_FINALLY_CLEAN(3); + + IGRAPH_CHECK(igraph_add_edges(graph, &edges, 0)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_de_bruijn + * \brief Generate a de Bruijn graph. + * + * A de Bruijn graph represents relationships between strings. An alphabet + * of \c m letters are used and strings of length \c n are considered. + * A vertex corresponds to every possible string and there is a directed edge + * from vertex \c v to vertex \c w if the string of \c v can be transformed into + * the string of \c w by removing its first letter and appending a letter to it. + * + * + * Please note that the graph will have \c m to the power \c n vertices and + * even more edges, so probably you don't want to supply too big numbers for + * \c m and \c n. + * + * + * De Bruijn graphs have some interesting properties, please see another source, + * eg. Wikipedia for details. + * + * \param graph Pointer to an uninitialized graph object, the result will be + * stored here. + * \param m Integer, the number of letters in the alphabet. + * \param n Integer, the length of the strings. + * \return Error code. + * + * \sa \ref igraph_kautz(). + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number of edges. + */ + +int igraph_de_bruijn(igraph_t *graph, igraph_integer_t m, igraph_integer_t n) { + + /* m - number of symbols */ + /* n - length of strings */ + + long int no_of_nodes, no_of_edges; + igraph_vector_t edges; + long int i, j; + long int mm = m; + + if (m < 0 || n < 0) { + IGRAPH_ERROR("`m' and `n' should be non-negative in a de Bruijn graph", + IGRAPH_EINVAL); + } + + if (n == 0) { + return igraph_empty(graph, 1, IGRAPH_DIRECTED); + } + if (m == 0) { + return igraph_empty(graph, 0, IGRAPH_DIRECTED); + } + + no_of_nodes = (long int) pow(m, n); + no_of_edges = no_of_nodes * m; + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges * 2)); + + for (i = 0; i < no_of_nodes; i++) { + long int basis = (i * mm) % no_of_nodes; + for (j = 0; j < m; j++) { + igraph_vector_push_back(&edges, i); + igraph_vector_push_back(&edges, basis + j); + } + } + + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) no_of_nodes, + IGRAPH_DIRECTED)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_kautz + * \brief Generate a Kautz graph. + * + * A Kautz graph is a labeled graph, vertices are labeled by strings + * of length \c n+1 above an alphabet with \c m+1 letters, with + * the restriction that every two consecutive letters in the string + * must be different. There is a directed edge from a vertex \c v to + * another vertex \c w if it is possible to transform the string of + * \c v into the string of \c w by removing the first letter and + * appending a letter to it. + * + * + * Kautz graphs have some interesting properties, see eg. Wikipedia + * for details. + * + * + * Vincent Matossian wrote the first version of this function in R, + * thanks. + * \param graph Pointer to an uninitialized graph object, the result + * will be stored here. + * \param m Integer, \c m+1 is the number of letters in the alphabet. + * \param n Integer, \c n+1 is the length of the strings. + * \return Error code. + * + * \sa \ref igraph_de_bruijn(). + * + * Time complexity: O(|V|* [(m+1)/m]^n +|E|), in practice it is more + * like O(|V|+|E|). |V| is the number of vertices, |E| is the number + * of edges and \c m and \c n are the corresponding arguments. + */ + +int igraph_kautz(igraph_t *graph, igraph_integer_t m, igraph_integer_t n) { + + /* m+1 - number of symbols */ + /* n+1 - length of strings */ + + long int mm = m; + long int no_of_nodes, no_of_edges; + long int allstrings; + long int i, j, idx = 0; + igraph_vector_t edges; + igraph_vector_long_t digits, table; + igraph_vector_long_t index1, index2; + long int actb = 0; + long int actvalue = 0; + + if (m < 0 || n < 0) { + IGRAPH_ERROR("`m' and `n' should be non-negative in a Kautz graph", + IGRAPH_EINVAL); + } + + if (n == 0) { + return igraph_full(graph, m + 1, IGRAPH_DIRECTED, IGRAPH_NO_LOOPS); + } + if (m == 0) { + return igraph_empty(graph, 0, IGRAPH_DIRECTED); + } + + no_of_nodes = (long int) ((m + 1) * pow(m, n)); + no_of_edges = no_of_nodes * m; + allstrings = (long int) pow(m + 1, n + 1); + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + IGRAPH_CHECK(igraph_vector_long_init(&table, n + 1)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &table); + j = 1; + for (i = n; i >= 0; i--) { + VECTOR(table)[i] = j; + j *= (m + 1); + } + + IGRAPH_CHECK(igraph_vector_long_init(&digits, n + 1)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &digits); + IGRAPH_CHECK(igraph_vector_long_init(&index1, (long int) pow(m + 1, n + 1))); + IGRAPH_FINALLY(igraph_vector_long_destroy, &index1); + IGRAPH_CHECK(igraph_vector_long_init(&index2, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &index2); + + /* Fill the index tables*/ + while (1) { + /* at the beginning of the loop, 0:actb contain the valid prefix */ + /* we might need to fill it to get a valid string */ + long int z = 0; + if (VECTOR(digits)[actb] == 0) { + z = 1; + } + for (actb++; actb <= n; actb++) { + VECTOR(digits)[actb] = z; + actvalue += z * VECTOR(table)[actb]; + z = 1 - z; + } + actb = n; + + /* ok, we have a valid string now */ + VECTOR(index1)[actvalue] = idx + 1; + VECTOR(index2)[idx] = actvalue; + idx++; + + /* finished? */ + if (idx >= no_of_nodes) { + break; + } + + /* not yet, we need a valid prefix now */ + while (1) { + /* try to increase digits at position actb */ + long int next = VECTOR(digits)[actb] + 1; + if (actb != 0 && VECTOR(digits)[actb - 1] == next) { + next++; + } + if (next <= m) { + /* ok, no problem */ + actvalue += (next - VECTOR(digits)[actb]) * VECTOR(table)[actb]; + VECTOR(digits)[actb] = next; + break; + } else { + /* bad luck, try the previous digit */ + actvalue -= VECTOR(digits)[actb] * VECTOR(table)[actb]; + actb--; + } + } + } + + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges * 2)); + + /* Now come the edges at last */ + for (i = 0; i < no_of_nodes; i++) { + long int fromvalue = VECTOR(index2)[i]; + long int lastdigit = fromvalue % (mm + 1); + long int basis = (fromvalue * (mm + 1)) % allstrings; + for (j = 0; j <= m; j++) { + long int tovalue, to; + if (j == lastdigit) { + continue; + } + tovalue = basis + j; + to = VECTOR(index1)[tovalue] - 1; + if (to < 0) { + continue; + } + igraph_vector_push_back(&edges, i); + igraph_vector_push_back(&edges, to); + } + } + + igraph_vector_long_destroy(&index2); + igraph_vector_long_destroy(&index1); + igraph_vector_long_destroy(&digits); + igraph_vector_long_destroy(&table); + IGRAPH_FINALLY_CLEAN(4); + + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) no_of_nodes, + IGRAPH_DIRECTED)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_lcf_vector + * \brief Create a graph from LCF notation + * + * This function is essentially the same as \ref igraph_lcf(), only + * the way for giving the arguments is different. See \ref + * igraph_lcf() for details. + * \param graph Pointer to an uninitialized graph object. + * \param n Integer constant giving the number of vertices. + * \param shifts A vector giving the shifts. + * \param repeats An integer constant giving the number of repeats + * for the shifts. + * \return Error code. + * + * \sa \ref igraph_lcf(), \ref igraph_extended_chordal_ring() + * + * Time complexity: O(|V|+|E|), linear in the number of vertices plus + * the number of edges. + */ + +int igraph_lcf_vector(igraph_t *graph, igraph_integer_t n, + const igraph_vector_t *shifts, + igraph_integer_t repeats) { + + igraph_vector_t edges; + long int no_of_shifts = igraph_vector_size(shifts); + long int ptr = 0, i, sptr = 0; + long int no_of_nodes = n; + long int no_of_edges = n + no_of_shifts * repeats; + + if (repeats < 0) { + IGRAPH_ERROR("number of repeats must be positive", IGRAPH_EINVAL); + } + IGRAPH_VECTOR_INIT_FINALLY(&edges, 2 * no_of_edges); + + if (no_of_nodes > 0) { + /* Create a ring first */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(edges)[ptr++] = i; + VECTOR(edges)[ptr++] = i + 1; + } + VECTOR(edges)[ptr - 1] = 0; + } + + /* Then add the rest */ + while (ptr < 2 * no_of_edges) { + long int sh = (long int) VECTOR(*shifts)[sptr % no_of_shifts]; + long int from = sptr % no_of_nodes; + long int to = (no_of_nodes + sptr + sh) % no_of_nodes; + VECTOR(edges)[ptr++] = from; + VECTOR(edges)[ptr++] = to; + sptr++; + } + + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) no_of_nodes, + IGRAPH_UNDIRECTED)); + IGRAPH_CHECK(igraph_simplify(graph, 1 /* true */, 1 /* true */, NULL)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_lcf + * \brief Create a graph from LCF notation + * + * + * LCF is short for Lederberg-Coxeter-Frucht, it is a concise notation for + * 3-regular Hamiltonian graphs. It consists of three parameters: the + * number of vertices in the graph, a list of shifts giving additional + * edges to a cycle backbone, and another integer giving how many times + * the shifts should be performed. See + * http://mathworld.wolfram.com/LCFNotation.html for details. + * + * \param graph Pointer to an uninitialized graph object. + * \param n Integer, the number of vertices in the graph. + * \param ... The shifts and the number of repeats for the shifts, + * plus an additional 0 to mark the end of the arguments. + * \return Error code. + * + * \sa See \ref igraph_lcf_vector() for a similar function using a + * vector_t instead of the variable length argument list. + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges. + * + * \example examples/simple/igraph_lcf.c + */ + +int igraph_lcf(igraph_t *graph, igraph_integer_t n, ...) { + igraph_vector_t shifts; + igraph_integer_t repeats; + va_list ap; + + IGRAPH_VECTOR_INIT_FINALLY(&shifts, 0); + + va_start(ap, n); + while (1) { + int num = va_arg(ap, int); + if (num == 0) { + break; + } + IGRAPH_CHECK(igraph_vector_push_back(&shifts, num)); + } + if (igraph_vector_size(&shifts) == 0) { + repeats = 0; + } else { + repeats = (igraph_integer_t) igraph_vector_pop_back(&shifts); + } + + IGRAPH_CHECK(igraph_lcf_vector(graph, n, &shifts, repeats)); + igraph_vector_destroy(&shifts); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +const igraph_real_t igraph_i_famous_bull[] = { + 5, 5, 0, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 4 +}; + +const igraph_real_t igraph_i_famous_chvatal[] = { + 12, 24, 0, + 5, 6, 6, 7, 7, 8, 8, 9, 5, 9, 4, 5, 4, 8, 2, 8, 2, 6, 0, 6, 0, 9, 3, 9, 3, 7, + 1, 7, 1, 5, 1, 10, 4, 10, 4, 11, 2, 11, 0, 10, 0, 11, 3, 11, 3, 10, 1, 2 +}; + +const igraph_real_t igraph_i_famous_coxeter[] = { + 28, 42, 0, + 0, 1, 0, 2, 0, 7, 1, 4, 1, 13, 2, 3, 2, 8, 3, 6, 3, 9, 4, 5, 4, 12, 5, 6, 5, + 11, 6, 10, 7, 19, 7, 24, 8, 20, 8, 23, 9, 14, 9, 22, 10, 15, 10, 21, 11, 16, + 11, 27, 12, 17, 12, 26, 13, 18, 13, 25, 14, 17, 14, 18, 15, 18, 15, 19, 16, 19, + 16, 20, 17, 20, 21, 23, 21, 26, 22, 24, 22, 27, 23, 25, 24, 26, 25, 27 +}; + +const igraph_real_t igraph_i_famous_cubical[] = { + 8, 12, 0, + 0, 1, 1, 2, 2, 3, 0, 3, 4, 5, 5, 6, 6, 7, 4, 7, 0, 4, 1, 5, 2, 6, 3, 7 +}; + +const igraph_real_t igraph_i_famous_diamond[] = { + 4, 5, 0, + 0, 1, 0, 2, 1, 2, 1, 3, 2, 3 +}; + +const igraph_real_t igraph_i_famous_dodecahedron[] = { + 20, 30, 0, + 0, 1, 0, 4, 0, 5, 1, 2, 1, 6, 2, 3, 2, 7, 3, 4, 3, 8, 4, 9, 5, 10, 5, 11, 6, + 10, 6, 14, 7, 13, 7, 14, 8, 12, 8, 13, 9, 11, 9, 12, 10, 15, 11, 16, 12, 17, + 13, 18, 14, 19, 15, 16, 15, 19, 16, 17, 17, 18, 18, 19 +}; + +const igraph_real_t igraph_i_famous_folkman[] = { + 20, 40, 0, + 0, 5, 0, 8, 0, 10, 0, 13, 1, 7, 1, 9, 1, 12, 1, 14, 2, 6, 2, 8, 2, 11, 2, 13, + 3, 5, 3, 7, 3, 10, 3, 12, 4, 6, 4, 9, 4, 11, 4, 14, 5, 15, 5, 19, 6, 15, 6, 16, + 7, 16, 7, 17, 8, 17, 8, 18, 9, 18, 9, 19, 10, 15, 10, 19, 11, 15, 11, 16, 12, + 16, 12, 17, 13, 17, 13, 18, 14, 18, 14, 19 +}; + +const igraph_real_t igraph_i_famous_franklin[] = { + 12, 18, 0, + 0, 1, 0, 2, 0, 6, 1, 3, 1, 7, 2, 4, 2, 10, 3, 5, 3, 11, 4, 5, 4, 6, 5, 7, 6, 8, + 7, 9, 8, 9, 8, 11, 9, 10, 10, 11 +}; + +const igraph_real_t igraph_i_famous_frucht[] = { + 12, 18, 0, + 0, 1, 0, 2, 0, 11, 1, 3, 1, 6, 2, 5, 2, 10, 3, 4, 3, 6, 4, 8, 4, 11, 5, 9, 5, + 10, 6, 7, 7, 8, 7, 9, 8, 9, 10, 11 +}; + +const igraph_real_t igraph_i_famous_grotzsch[] = { + 11, 20, 0, + 0, 1, 0, 2, 0, 7, 0, 10, 1, 3, 1, 6, 1, 9, 2, 4, 2, 6, 2, 8, 3, 4, 3, 8, 3, 10, + 4, 7, 4, 9, 5, 6, 5, 7, 5, 8, 5, 9, 5, 10 +}; + +const igraph_real_t igraph_i_famous_heawood[] = { + 14, 21, 0, + 0, 1, 0, 5, 0, 13, 1, 2, 1, 10, 2, 3, 2, 7, 3, 4, 3, 12, 4, 5, 4, 9, 5, 6, 6, + 7, 6, 11, 7, 8, 8, 9, 8, 13, 9, 10, 10, 11, 11, 12, 12, 13 +}; + +const igraph_real_t igraph_i_famous_herschel[] = { + 11, 18, 0, + 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 6, 1, 7, 2, 10, 3, 9, 4, 8, 4, 9, 5, 8, + 5, 10, 6, 8, 6, 9, 7, 8, 7, 10 +}; + +const igraph_real_t igraph_i_famous_house[] = { + 5, 6, 0, + 0, 1, 0, 2, 1, 3, 2, 3, 2, 4, 3, 4 +}; + +const igraph_real_t igraph_i_famous_housex[] = { + 5, 8, 0, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3, 2, 4, 3, 4 +}; + +const igraph_real_t igraph_i_famous_icosahedron[] = { + 12, 30, 0, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 8, 1, 2, 1, 6, 1, 7, 1, 8, 2, 4, 2, 5, 2, 6, 3, 4, + 3, 8, 3, 9, 3, 11, 4, 5, 4, 11, 5, 6, 5, 10, 5, 11, 6, 7, 6, 10, 7, 8, 7, 9, 7, + 10, 8, 9, 9, 10, 9, 11, 10, 11 +}; + +const igraph_real_t igraph_i_famous_krackhardt_kite[] = { + 10, 18, 0, + 0, 1, 0, 2, 0, 3, 0, 5, 1, 3, 1, 4, 1, 6, 2, 3, 2, 5, 3, 4, 3, 5, 3, 6, 4, 6, 5, 6, 5, 7, 6, 7, 7, 8, 8, 9 +}; + +const igraph_real_t igraph_i_famous_levi[] = { + 30, 45, 0, + 0, 1, 0, 7, 0, 29, 1, 2, 1, 24, 2, 3, 2, 11, 3, 4, 3, 16, 4, 5, 4, 21, 5, 6, 5, + 26, 6, 7, 6, 13, 7, 8, 8, 9, 8, 17, 9, 10, 9, 22, 10, 11, 10, 27, 11, 12, 12, + 13, 12, 19, 13, 14, 14, 15, 14, 23, 15, 16, 15, 28, 16, 17, 17, 18, 18, 19, 18, + 25, 19, 20, 20, 21, 20, 29, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29 +}; + +const igraph_real_t igraph_i_famous_mcgee[] = { + 24, 36, 0, + 0, 1, 0, 7, 0, 23, 1, 2, 1, 18, 2, 3, 2, 14, 3, 4, 3, 10, 4, 5, 4, 21, 5, 6, 5, + 17, 6, 7, 6, 13, 7, 8, 8, 9, 8, 20, 9, 10, 9, 16, 10, 11, 11, 12, 11, 23, 12, + 13, 12, 19, 13, 14, 14, 15, 15, 16, 15, 22, 16, 17, 17, 18, 18, 19, 19, 20, 20, + 21, 21, 22, 22, 23 +}; + +const igraph_real_t igraph_i_famous_meredith[] = { + 70, 140, 0, + 0, 4, 0, 5, 0, 6, 1, 4, 1, 5, 1, 6, 2, 4, 2, 5, 2, 6, 3, 4, 3, 5, 3, 6, 7, 11, + 7, 12, 7, 13, 8, 11, 8, 12, 8, 13, 9, 11, 9, 12, 9, 13, 10, 11, 10, 12, 10, 13, + 14, 18, 14, 19, 14, 20, 15, 18, 15, 19, 15, 20, 16, 18, 16, 19, 16, 20, 17, 18, + 17, 19, 17, 20, 21, 25, 21, 26, 21, 27, 22, 25, 22, 26, 22, 27, 23, 25, 23, 26, + 23, 27, 24, 25, 24, 26, 24, 27, 28, 32, 28, 33, 28, 34, 29, 32, 29, 33, 29, 34, + 30, 32, 30, 33, 30, 34, 31, 32, 31, 33, 31, 34, 35, 39, 35, 40, 35, 41, 36, 39, + 36, 40, 36, 41, 37, 39, 37, 40, 37, 41, 38, 39, 38, 40, 38, 41, 42, 46, 42, 47, + 42, 48, 43, 46, 43, 47, 43, 48, 44, 46, 44, 47, 44, 48, 45, 46, 45, 47, 45, 48, + 49, 53, 49, 54, 49, 55, 50, 53, 50, 54, 50, 55, 51, 53, 51, 54, 51, 55, 52, 53, + 52, 54, 52, 55, 56, 60, 56, 61, 56, 62, 57, 60, 57, 61, 57, 62, 58, 60, 58, 61, + 58, 62, 59, 60, 59, 61, 59, 62, 63, 67, 63, 68, 63, 69, 64, 67, 64, 68, 64, 69, + 65, 67, 65, 68, 65, 69, 66, 67, 66, 68, 66, 69, 2, 50, 1, 51, 9, 57, 8, 58, 16, + 64, 15, 65, 23, 36, 22, 37, 30, 43, 29, 44, 3, 21, 7, 24, 14, 31, 0, 17, 10, + 28, 38, 42, 35, 66, 59, 63, 52, 56, 45, 49 +}; + +const igraph_real_t igraph_i_famous_noperfectmatching[] = { + 16, 27, 0, + 0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3, 2, 4, 3, 4, 4, 5, 5, 6, 5, 7, 6, 12, 6, 13, + 7, 8, 7, 9, 8, 9, 8, 10, 8, 11, 9, 10, 9, 11, 10, 11, 12, 13, 12, 14, 12, 15, + 13, 14, 13, 15, 14, 15 +}; + +const igraph_real_t igraph_i_famous_nonline[] = { + 50, 72, 0, + 0, 1, 0, 2, 0, 3, 4, 6, 4, 7, 5, 6, 5, 7, 6, 7, 7, 8, 9, 11, 9, 12, 9, 13, 10, + 11, 10, 12, 10, 13, 11, 12, 11, 13, 12, 13, 14, 15, 15, 16, 15, 17, 16, 17, 16, + 18, 17, 18, 18, 19, 20, 21, 20, 22, 20, 23, 21, 22, 21, 23, 21, 24, 22, 23, 22, + 24, 24, 25, 26, 27, 26, 28, 26, 29, 27, 28, 27, 29, 27, 30, 27, 31, 28, 29, 28, + 30, 28, 31, 30, 31, 32, 34, 32, 35, 32, 36, 33, 34, 33, 35, 33, 37, 34, 35, 36, + 37, 38, 39, 38, 40, 38, 43, 39, 40, 39, 41, 39, 42, 39, 43, 40, 41, 41, 42, 42, + 43, 44, 45, 44, 46, 45, 46, 45, 47, 46, 47, 46, 48, 47, 48, 47, 49, 48, 49 +}; + +const igraph_real_t igraph_i_famous_octahedron[] = { + 6, 12, 0, + 0, 1, 0, 2, 1, 2, 3, 4, 3, 5, 4, 5, 0, 3, 0, 5, 1, 3, 1, 4, 2, 4, 2, 5 +}; + +const igraph_real_t igraph_i_famous_petersen[] = { + 10, 15, 0, + 0, 1, 0, 4, 0, 5, 1, 2, 1, 6, 2, 3, 2, 7, 3, 4, 3, 8, 4, 9, 5, 7, 5, 8, 6, 8, 6, 9, 7, 9 +}; + +const igraph_real_t igraph_i_famous_robertson[] = { + 19, 38, 0, + 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, + 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 0, 18, 0, 4, 4, 9, 9, 13, 13, + 17, 2, 17, 2, 6, 6, 10, 10, 15, 0, 15, 1, 8, 8, 16, 5, 16, 5, 12, 1, 12, 7, 18, + 7, 14, 3, 14, 3, 11, 11, 18 +}; + +const igraph_real_t igraph_i_famous_smallestcyclicgroup[] = { + 9, 15, 0, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 2, 1, 3, 1, 7, 1, 8, 2, 5, 2, 6, 2, 7, 3, 8, + 4, 5, 6, 7 +}; + +const igraph_real_t igraph_i_famous_tetrahedron[] = { + 4, 6, 0, + 0, 3, 1, 3, 2, 3, 0, 1, 1, 2, 0, 2 +}; + +const igraph_real_t igraph_i_famous_thomassen[] = { + 34, 52, 0, + 0, 2, 0, 3, 1, 3, 1, 4, 2, 4, 5, 7, 5, 8, 6, 8, 6, 9, 7, 9, 10, 12, 10, 13, 11, + 13, 11, 14, 12, 14, 15, 17, 15, 18, 16, 18, 16, 19, 17, 19, 9, 19, 4, 14, 24, + 25, 25, 26, 20, 26, 20, 21, 21, 22, 22, 23, 23, 27, 27, 28, 28, 29, 29, 30, 30, + 31, 31, 32, 32, 33, 24, 33, 5, 24, 6, 25, 7, 26, 8, 20, 0, 20, 1, 21, 2, 22, 3, + 23, 10, 27, 11, 28, 12, 29, 13, 30, 15, 30, 16, 31, 17, 32, 18, 33 +}; + +const igraph_real_t igraph_i_famous_tutte[] = { + 46, 69, 0, + 0, 10, 0, 11, 0, 12, 1, 2, 1, 7, 1, 19, 2, 3, 2, 41, 3, 4, 3, 27, 4, 5, 4, 33, + 5, 6, 5, 45, 6, 9, 6, 29, 7, 8, 7, 21, 8, 9, 8, 22, 9, 24, 10, 13, 10, 14, 11, + 26, 11, 28, 12, 30, 12, 31, 13, 15, 13, 21, 14, 15, 14, 18, 15, 16, 16, 17, 16, + 20, 17, 18, 17, 23, 18, 24, 19, 25, 19, 40, 20, 21, 20, 22, 22, 23, 23, 24, 25, + 26, 25, 38, 26, 34, 27, 28, 27, 39, 28, 34, 29, 30, 29, 44, 30, 35, 31, 32, 31, + 35, 32, 33, 32, 42, 33, 43, 34, 36, 35, 37, 36, 38, 36, 39, 37, 42, 37, 44, 38, + 40, 39, 41, 40, 41, 42, 43, 43, 45, 44, 45 +}; + +const igraph_real_t igraph_i_famous_uniquely3colorable[] = { + 12, 22, 0, + 0, 1, 0, 3, 0, 6, 0, 8, 1, 4, 1, 7, 1, 9, 2, 3, 2, 6, 2, 7, 2, 9, 2, 11, 3, 4, + 3, 10, 4, 5, 4, 11, 5, 6, 5, 7, 5, 8, 5, 10, 8, 11, 9, 10 +}; + +const igraph_real_t igraph_i_famous_walther[] = { + 25, 31, 0, + 0, 1, 1, 2, 1, 8, 2, 3, 2, 13, 3, 4, 3, 16, 4, 5, 5, 6, 5, 19, 6, 7, 6, 20, 7, + 21, 8, 9, 8, 13, 9, 10, 9, 22, 10, 11, 10, 20, 11, 12, 13, 14, 14, 15, 14, 23, + 15, 16, 15, 17, 17, 18, 18, 19, 18, 24, 20, 24, 22, 23, 23, 24 +}; + +const igraph_real_t igraph_i_famous_zachary[] = { + 34, 78, 0, + 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, + 0, 10, 0, 11, 0, 12, 0, 13, 0, 17, 0, 19, 0, 21, 0, 31, + 1, 2, 1, 3, 1, 7, 1, 13, 1, 17, 1, 19, 1, 21, 1, 30, + 2, 3, 2, 7, 2, 27, 2, 28, 2, 32, 2, 9, 2, 8, 2, 13, + 3, 7, 3, 12, 3, 13, 4, 6, 4, 10, 5, 6, 5, 10, 5, 16, + 6, 16, 8, 30, 8, 32, 8, 33, 9, 33, 13, 33, 14, 32, 14, 33, + 15, 32, 15, 33, 18, 32, 18, 33, 19, 33, 20, 32, 20, 33, + 22, 32, 22, 33, 23, 25, 23, 27, 23, 32, 23, 33, 23, 29, + 24, 25, 24, 27, 24, 31, 25, 31, 26, 29, 26, 33, 27, 33, + 28, 31, 28, 33, 29, 32, 29, 33, 30, 32, 30, 33, 31, 32, 31, 33, + 32, 33 +}; + +static int igraph_i_famous(igraph_t *graph, const igraph_real_t *data) { + long int no_of_nodes = (long int) data[0]; + long int no_of_edges = (long int) data[1]; + igraph_bool_t directed = (igraph_bool_t) data[2]; + igraph_vector_t edges; + + igraph_vector_view(&edges, data + 3, 2 * no_of_edges); + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) no_of_nodes, + directed)); + return 0; +} + +/** + * \function igraph_famous + * \brief Create a famous graph by simply providing its name + * + * + * The name of the graph can be simply supplied as a string. + * Note that this function creates graphs which don't take any parameters, + * there are separate functions for graphs with parameters, eg. \ref + * igraph_full() for creating a full graph. + * + * + * The following graphs are supported: + * \clist + * \cli Bull + * The bull graph, 5 vertices, 5 edges, resembles the + * head of a bull if drawn properly. + * \cli Chvatal + * This is the smallest triangle-free graph that is + * both 4-chromatic and 4-regular. According to the Grunbaum + * conjecture there exists an m-regular, m-chromatic graph + * with n vertices for every m>1 and n>2. The Chvatal graph + * is an example for m=4 and n=12. It has 24 edges. + * \cli Coxeter + * A non-Hamiltonian cubic symmetric graph with 28 + * vertices and 42 edges. + * \cli Cubical + * The Platonic graph of the cube. A convex regular + * polyhedron with 8 vertices and 12 edges. + * \cli Diamond + * A graph with 4 vertices and 5 edges, resembles a + * schematic diamond if drawn properly. + * \cli Dodecahedral, Dodecahedron + * Another Platonic solid + * with 20 vertices and 30 edges. + * \cli Folkman + * The semisymmetric graph with minimum number of + * vertices, 20 and 40 edges. A semisymmetric graph is + * regular, edge transitive and not vertex transitive. + * \cli Franklin + * This is a graph whose embedding to the Klein + * bottle can be colored with six colors, it is a + * counterexample to the necessity of the Heawood + * conjecture on a Klein bottle. It has 12 vertices and 18 + * edges. + * \cli Frucht + * The Frucht Graph is the smallest cubical graph + * whose automorphism group consists only of the identity + * element. It has 12 vertices and 18 edges. + * \cli Grotzsch + * The Grötzsch graph is a triangle-free graph with + * 11 vertices, 20 edges, and chromatic number 4. It is named after + * German mathematician Herbert Grötzsch, and its existence + * demonstrates that the assumption of planarity is necessary in + * Grötzsch's theorem that every triangle-free planar + * graph is 3-colorable. + * \cli Heawood + * The Heawood graph is an undirected graph with 14 + * vertices and 21 edges. The graph is cubic, and all cycles in the + * graph have six or more edges. Every smaller cubic graph has shorter + * cycles, so this graph is the 6-cage, the smallest cubic graph of + * girth 6. + * \cli Herschel + * The Herschel graph is the smallest + * nonhamiltonian polyhedral graph. It is the + * unique such graph on 11 nodes, and has 18 edges. + * \cli House + * The house graph is a 5-vertex, 6-edge graph, the + * schematic draw of a house if drawn properly, basically a + * triangle on top of a square. + * \cli HouseX + * The same as the house graph with an X in the square. 5 + * vertices and 8 edges. + * \cli Icosahedral, Icosahedron + * A Platonic solid with 12 + * vertices and 30 edges. + * \cli Krackhardt_Kite + * A social network with 10 vertices and 18 edges. + * Krackhardt, D. Assessing the Political Landscape: + * Structure, Cognition, and Power in Organizations. + * Admin. Sci. Quart. 35, 342-369, 1990. + * \cli Levi + * The graph is a 4-arc transitive cubic graph, it has + * 30 vertices and 45 edges. + * \cli McGee + * The McGee graph is the unique 3-regular 7-cage + * graph, it has 24 vertices and 36 edges. + * \cli Meredith + * The Meredith graph is a quartic graph on 70 + * nodes and 140 edges that is a counterexample to the conjecture that + * every 4-regular 4-connected graph is Hamiltonian. + * \cli Noperfectmatching + * A connected graph with 16 vertices and + * 27 edges containing no perfect matching. A matching in a graph + * is a set of pairwise non-incident edges; that is, no two edges + * share a common vertex. A perfect matching is a matching + * which covers all vertices of the graph. + * \cli Nonline + * A graph whose connected components are the 9 + * graphs whose presence as a vertex-induced subgraph in a + * graph makes a nonline graph. It has 50 vertices and 72 edges. + * \cli Octahedral, Octahedron + * Platonic solid with 6 + * vertices and 12 edges. + * \cli Petersen + * A 3-regular graph with 10 vertices and 15 edges. It is + * the smallest hypohamiltonian graph, ie. it is + * non-hamiltonian but removing any single vertex from it makes it + * Hamiltonian. + * \cli Robertson + * The unique (4,5)-cage graph, ie. a 4-regular + * graph of girth 5. It has 19 vertices and 38 edges. + * \cli Smallestcyclicgroup + * A smallest nontrivial graph + * whose automorphism group is cyclic. It has 9 vertices and + * 15 edges. + * \cli Tetrahedral, Tetrahedron + * Platonic solid with 4 + * vertices and 6 edges. + * \cli Thomassen + * The smallest hypotraceable graph, + * on 34 vertices and 52 edges. A hypotracable graph does + * not contain a Hamiltonian path but after removing any + * single vertex from it the remainder always contains a + * Hamiltonian path. A graph containing a Hamiltonian path + * is called traceable. + * \cli Tutte + * Tait's Hamiltonian graph conjecture states that + * every 3-connected 3-regular planar graph is Hamiltonian. + * This graph is a counterexample. It has 46 vertices and 69 + * edges. + * \cli Uniquely3colorable + * Returns a 12-vertex, triangle-free + * graph with chromatic number 3 that is uniquely + * 3-colorable. + * \cli Walther + * An identity graph with 25 vertices and 31 + * edges. An identity graph has a single graph automorphism, + * the trivial one. + * \cli Zachary + * Social network of friendships between 34 members of a + * karate club at a US university in the 1970s. See + * W. W. Zachary, An information flow model for conflict and + * fission in small groups, Journal of Anthropological + * Research 33, 452-473 (1977). + * \endclist + * + * \param graph Pointer to an uninitialized graph object. + * \param name Character constant, the name of the graph to be + * created, it is case insensitive. + * \return Error code, IGRAPH_EINVAL if there is no graph with the + * given name. + * + * \sa Other functions for creating graph structures: + * \ref igraph_ring(), \ref igraph_tree(), \ref igraph_lattice(), \ref + * igraph_full(). + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges in the graph. + */ + +int igraph_famous(igraph_t *graph, const char *name) { + + if (!strcasecmp(name, "bull")) { + return igraph_i_famous(graph, igraph_i_famous_bull); + } else if (!strcasecmp(name, "chvatal")) { + return igraph_i_famous(graph, igraph_i_famous_chvatal); + } else if (!strcasecmp(name, "coxeter")) { + return igraph_i_famous(graph, igraph_i_famous_coxeter); + } else if (!strcasecmp(name, "cubical")) { + return igraph_i_famous(graph, igraph_i_famous_cubical); + } else if (!strcasecmp(name, "diamond")) { + return igraph_i_famous(graph, igraph_i_famous_diamond); + } else if (!strcasecmp(name, "dodecahedral") || + !strcasecmp(name, "dodecahedron")) { + return igraph_i_famous(graph, igraph_i_famous_dodecahedron); + } else if (!strcasecmp(name, "folkman")) { + return igraph_i_famous(graph, igraph_i_famous_folkman); + } else if (!strcasecmp(name, "franklin")) { + return igraph_i_famous(graph, igraph_i_famous_franklin); + } else if (!strcasecmp(name, "frucht")) { + return igraph_i_famous(graph, igraph_i_famous_frucht); + } else if (!strcasecmp(name, "grotzsch")) { + return igraph_i_famous(graph, igraph_i_famous_grotzsch); + } else if (!strcasecmp(name, "heawood")) { + return igraph_i_famous(graph, igraph_i_famous_heawood); + } else if (!strcasecmp(name, "herschel")) { + return igraph_i_famous(graph, igraph_i_famous_herschel); + } else if (!strcasecmp(name, "house")) { + return igraph_i_famous(graph, igraph_i_famous_house); + } else if (!strcasecmp(name, "housex")) { + return igraph_i_famous(graph, igraph_i_famous_housex); + } else if (!strcasecmp(name, "icosahedral") || + !strcasecmp(name, "icosahedron")) { + return igraph_i_famous(graph, igraph_i_famous_icosahedron); + } else if (!strcasecmp(name, "krackhardt_kite")) { + return igraph_i_famous(graph, igraph_i_famous_krackhardt_kite); + } else if (!strcasecmp(name, "levi")) { + return igraph_i_famous(graph, igraph_i_famous_levi); + } else if (!strcasecmp(name, "mcgee")) { + return igraph_i_famous(graph, igraph_i_famous_mcgee); + } else if (!strcasecmp(name, "meredith")) { + return igraph_i_famous(graph, igraph_i_famous_meredith); + } else if (!strcasecmp(name, "noperfectmatching")) { + return igraph_i_famous(graph, igraph_i_famous_noperfectmatching); + } else if (!strcasecmp(name, "nonline")) { + return igraph_i_famous(graph, igraph_i_famous_nonline); + } else if (!strcasecmp(name, "octahedral") || + !strcasecmp(name, "octahedron")) { + return igraph_i_famous(graph, igraph_i_famous_octahedron); + } else if (!strcasecmp(name, "petersen")) { + return igraph_i_famous(graph, igraph_i_famous_petersen); + } else if (!strcasecmp(name, "robertson")) { + return igraph_i_famous(graph, igraph_i_famous_robertson); + } else if (!strcasecmp(name, "smallestcyclicgroup")) { + return igraph_i_famous(graph, igraph_i_famous_smallestcyclicgroup); + } else if (!strcasecmp(name, "tetrahedral") || + !strcasecmp(name, "tetrahedron")) { + return igraph_i_famous(graph, igraph_i_famous_tetrahedron); + } else if (!strcasecmp(name, "thomassen")) { + return igraph_i_famous(graph, igraph_i_famous_thomassen); + } else if (!strcasecmp(name, "tutte")) { + return igraph_i_famous(graph, igraph_i_famous_tutte); + } else if (!strcasecmp(name, "uniquely3colorable")) { + return igraph_i_famous(graph, igraph_i_famous_uniquely3colorable); + } else if (!strcasecmp(name, "walther")) { + return igraph_i_famous(graph, igraph_i_famous_walther); + } else if (!strcasecmp(name, "zachary")) { + return igraph_i_famous(graph, igraph_i_famous_zachary); + } else { + IGRAPH_ERROR("Unknown graph, see documentation", IGRAPH_EINVAL); + } + + return 0; +} + +/** + * \function igraph_adjlist + * Create a graph from an adjacency list + * + * An adjacency list is a list of vectors, containing the neighbors + * of all vertices. For operations that involve many changes to the + * graph structure, it is recommended that you convert the graph into + * an adjacency list via \ref igraph_adjlist_init(), perform the + * modifications (these are cheap for an adjacency list) and then + * recreate the igraph graph via this function. + * + * \param graph Pointer to an uninitialized graph object. + * \param adjlist The adjacency list. + * \param mode Whether or not to create a directed graph. \c IGRAPH_ALL + * means an undirected graph, \c IGRAPH_OUT means a + * directed graph from an out-adjacency list (i.e. each + * list contains the successors of the corresponding + * vertices), \c IGRAPH_IN means a directed graph from an + * in-adjacency list + * \param duplicate Logical, for undirected graphs this specified + * whether each edge is included twice, in the vectors of + * both adjacent vertices. If this is false (0), then it is + * assumed that every edge is included only once. This argument + * is ignored for directed graphs. + * \return Error code. + * + * \sa \ref igraph_adjlist_init() for the opposite operation. + * + * Time complexity: O(|V|+|E|). + * + */ + +int igraph_adjlist(igraph_t *graph, const igraph_adjlist_t *adjlist, + igraph_neimode_t mode, igraph_bool_t duplicate) { + + long int no_of_nodes = igraph_adjlist_size(adjlist); + long int no_of_edges = 0; + long int i; + + igraph_vector_t edges; + long int edgeptr = 0; + + duplicate = duplicate && (mode == IGRAPH_ALL); /* only duplicate if undirected */ + + for (i = 0; i < no_of_nodes; i++) { + no_of_edges += igraph_vector_int_size(igraph_adjlist_get(adjlist, i)); + } + + if (duplicate) { + no_of_edges /= 2; + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 2 * no_of_edges); + + for (i = 0; i < no_of_nodes; i++) { + igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, i); + long int j, n = igraph_vector_int_size(neis); + long int loops = 0; + + for (j = 0; j < n; j++) { + long int nei = (long int) VECTOR(*neis)[j]; + if (nei == i) { + loops++; + } else { + if (! duplicate || nei > i) { + if (edgeptr + 2 > 2 * no_of_edges) { + IGRAPH_ERROR("Invalid adjacency list, most probably not correctly" + " duplicated edges for an undirected graph", IGRAPH_EINVAL); + } + if (mode == IGRAPH_IN) { + VECTOR(edges)[edgeptr++] = nei; + VECTOR(edges)[edgeptr++] = i; + } else { + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = nei; + } + } + } + } + /* loops */ + if (duplicate) { + loops = loops / 2; + } + if (edgeptr + 2 * loops > 2 * no_of_edges) { + IGRAPH_ERROR("Invalid adjacency list, most probably not correctly" + " duplicated edges for an undirected graph", IGRAPH_EINVAL); + } + for (j = 0; j < loops; j++) { + VECTOR(edges)[edgeptr++] = i; + VECTOR(edges)[edgeptr++] = i; + } + } + + if (mode == IGRAPH_ALL) + IGRAPH_CHECK(igraph_create(graph, &edges, + (igraph_integer_t) no_of_nodes, 0)); + else + IGRAPH_CHECK(igraph_create(graph, &edges, + (igraph_integer_t) no_of_nodes, 1)); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + + +/** + * \ingroup generators + * \function igraph_from_prufer + * \brief Generates a tree from a Prüfer sequence + * + * A Prüfer sequence is a unique sequence of integers associated + * with a labelled tree. A tree on n vertices can be represented by a + * sequence of n-2 integers, each between 0 and n-1 (inclusive). + * + * The algorithm used by this function is based on + * Paulius Micikevičius, Saverio Caminiti, Narsingh Deo: + * Linear-time Algorithms for Encoding Trees as Sequences of Node Labels + * + * \param graph Pointer to an uninitialized graph object. + * \param prufer The Prüfer sequence + * \return Error code: + * \clist + * \cli IGRAPH_ENOMEM + * there is not enough memory to perform the operation. + * \cli IGRAPH_EINVAL + * invalid Prüfer sequence given + * \endclist + * + * \sa \ref igraph_tree(), \ref igraph_tree_game() + * + */ + +int igraph_from_prufer(igraph_t *graph, const igraph_vector_int_t *prufer) { + igraph_vector_int_t degree; + igraph_vector_t edges; + long n; + long i, k; + long u, v; /* vertices */ + long ec; + + n = igraph_vector_int_size(prufer) + 2; + + IGRAPH_VECTOR_INT_INIT_FINALLY(°ree, n); /* initializes vector to zeros */ + IGRAPH_VECTOR_INIT_FINALLY(&edges, 2 * (n - 1)); + + /* build out-degree vector (i.e. number of child vertices) and verify Prufer sequence */ + for (i = 0; i < n - 2; ++i) { + long u = VECTOR(*prufer)[i]; + if (u >= n || u < 0) { + IGRAPH_ERROR("Invalid Prufer sequence", IGRAPH_EINVAL); + } + VECTOR(degree)[u] += 1; + } + + v = 0; /* initialize v now, in case Prufer sequence is empty */ + k = 0; /* index into the Prufer vector */ + ec = 0; /* index into the edges vector */ + for (i = 0; i < n; ++i) { + u = i; + + while (k < n - 2 && u <= i && (VECTOR(degree)[u] == 0)) { + /* u is a leaf here */ + + v = VECTOR(*prufer)[k]; /* parent of u */ + + /* add edge */ + VECTOR(edges)[ec++] = v; + VECTOR(edges)[ec++] = u; + + k += 1; + + VECTOR(degree)[v] -= 1; + + u = v; + } + + if (k == n - 2) { + break; + } + } + + /* find u for last edge, v is already set */ + for (u = i + 1; u < n; ++u) + if ((VECTOR(degree)[u] == 0) && u != v) { + break; + } + + /* add last edge */ + VECTOR(edges)[ec++] = v; + VECTOR(edges)[ec++] = u; + + IGRAPH_CHECK(igraph_create(graph, &edges, (igraph_integer_t) n, /* directed = */ 0)); + + igraph_vector_destroy(&edges); + igraph_vector_int_destroy(°ree); + IGRAPH_FINALLY_CLEAN(2); + + return IGRAPH_SUCCESS; +} diff --git a/src/sugiyama.c b/src/sugiyama.c new file mode 100644 index 0000000..94e4f66 --- /dev/null +++ b/src/sugiyama.c @@ -0,0 +1,1340 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_centrality.h" +#include "igraph_components.h" +#include "igraph_constants.h" +#include "igraph_constructors.h" +#include "igraph_datatype.h" +#include "igraph_error.h" +#include "igraph_glpk_support.h" +#include "igraph_interface.h" +#include "igraph_memory.h" +#include "igraph_structural.h" +#include "igraph_types.h" +#include "config.h" + +#include + +/* #define SUGIYAMA_DEBUG */ + +#ifdef _MSC_VER +/* MSVC does not support variadic macros */ +#include +static void debug(const char* fmt, ...) { + va_list args; + va_start(args, fmt); +#ifdef SUGIYAMA_DEBUG + vfprintf(stderr, fmt, args); +#endif + va_end(args); +} +#else +#ifdef SUGIYAMA_DEBUG + #define debug(...) fprintf(stderr, __VA_ARGS__) +#else + #define debug(...) +#endif +#endif + +/* MSVC uses __forceinline instead of inline */ +#ifdef _MSC_VER + #define INLINE __forceinline +#else + #define INLINE inline +#endif + +/* + * Implementation of the Sugiyama layout algorithm as described in: + * + * [1] K. Sugiyama, S. Tagawa and M. Toda, "Methods for Visual Understanding of + * Hierarchical Systems". IEEE Transactions on Systems, Man and Cybernetics + * 11(2):109-125, 1981. + * + * The layering (if not given in advance) is calculated by ... TODO + * + * [2] TODO + * + * The X coordinates of nodes within a layer are calculated using the method of + * Brandes & Köpf: + * + * [3] U. Brandes and B. Köpf, "Fast and Simple Horizontal Coordinate + * Assignment". In: Lecture Notes in Computer Science 2265:31-44, 2002. + * + * Layer compaction is done according to: + * + * [4] N.S. Nikolov and A. Tarassov, "Graph layering by promotion of nodes". + * Journal of Discrete Applied Mathematics, special issue: IV ALIO/EURO + * workshop on applied combinatorial optimization, 154(5). + * + * The steps of the algorithm are as follows: + * + * 1. Cycle removal by finding an approximately minimal feedback arc set + * and reversing the direction of edges in the set. Algorithms for + * finding minimal feedback arc sets are as follows: + * + * - Find a cycle and find its minimum weight edge. Decrease the weight + * of all the edges by w. Remove those edges whose weight became zero. + * Repeat until there are no cycles. Re-introduce removed edges in + * decreasing order of weights, ensuring that no cycles are created. + * + * - Order the vertices somehow and remove edges which point backwards + * in the ordering. Eades et al proposed the following procedure: + * + * 1. Iteratively remove sinks and prepend them to a vertex sequence + * s2. + * + * 2. Iteratively remove sources and append them to a vertex sequence + * s1. + * + * 3. Choose a vertex u s.t. the difference between the number of + * rightward arcs and the number of leftward arcs is the largest, + * remove u and append it to s1. Goto step 1 if there are still + * more vertices. + * + * 4. Concatenate s1 with s2. + * + * This algorithm is known to produce feedback arc sets at most the + * size of m/2 - n/6, where m is the number of edges. Further + * improvements are possible in step 3 which bring down the size of + * the set to at most m/4 for cubic directed graphs, see Eades (1995). + * + * - For undirected graphs, find a maximum weight spanning tree and + * remove all the edges not in the spanning tree. For directed graphs, + * find minimal cuts iteratively and remove edges pointing from A to + * B or from B to A in the cut, depending on which one is smaller. Yes, + * this is time-consuming. + * + * 2. Assigning vertices to layers according to [2]. + * + * 3. Extracting weakly connected components. The remaining steps are + * executed for each component. + * + * 4. Compacting the layering using the method of [4]. TODO + * Steps 2-4 are performed only when no layering is given in advance. + * + * 5. Adding dummy nodes to ensure that each edge spans at most one layer + * only. + * + * 6. Finding an optimal ordering of vertices within a layer using the + * Sugiyama framework [1]. + * + * 7. Assigning horizontal coordinates to each vertex using [3]. + * + * 8. ??? + * + * 9. Profit! + */ + +/** + * Data structure to store a layering of the graph. + */ +typedef struct { + igraph_vector_ptr_t layers; +} igraph_i_layering_t; + +/** + * Initializes a layering. + */ +static int igraph_i_layering_init(igraph_i_layering_t* layering, + const igraph_vector_t* membership) { + long int i, n, num_layers; + + if (igraph_vector_size(membership) == 0) { + num_layers = 0; + } else { + num_layers = (long int) igraph_vector_max(membership) + 1; + } + + IGRAPH_CHECK(igraph_vector_ptr_init(&layering->layers, num_layers)); + IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &layering->layers); + + for (i = 0; i < num_layers; i++) { + igraph_vector_t* vec = igraph_Calloc(1, igraph_vector_t); + IGRAPH_VECTOR_INIT_FINALLY(vec, 0); + VECTOR(layering->layers)[i] = vec; + IGRAPH_FINALLY_CLEAN(1); + } + IGRAPH_VECTOR_PTR_SET_ITEM_DESTRUCTOR(&layering->layers, igraph_vector_destroy); + + n = igraph_vector_size(membership); + for (i = 0; i < n; i++) { + long int l = (long int) VECTOR(*membership)[i]; + igraph_vector_t* vec = VECTOR(layering->layers)[l]; + IGRAPH_CHECK(igraph_vector_push_back(vec, i)); + } + + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * Destroys a layering. + */ +static void igraph_i_layering_destroy(igraph_i_layering_t* layering) { + igraph_vector_ptr_destroy_all(&layering->layers); +} + +/** + * Returns the number of layers in a layering. + */ +static int igraph_i_layering_num_layers(const igraph_i_layering_t* layering) { + return (int) igraph_vector_ptr_size(&layering->layers); +} + +/** + * Returns the list of vertices in a given layer + */ +static igraph_vector_t* igraph_i_layering_get(const igraph_i_layering_t* layering, + long int index) { + return (igraph_vector_t*)VECTOR(layering->layers)[index]; +} + + +/** + * Forward declarations + */ + +static int igraph_i_layout_sugiyama_place_nodes_vertically(const igraph_t* graph, + const igraph_vector_t* weights, igraph_vector_t* membership); +static int igraph_i_layout_sugiyama_order_nodes_horizontally(const igraph_t* graph, + igraph_matrix_t* layout, const igraph_i_layering_t* layering, + long int maxiter); +static int igraph_i_layout_sugiyama_place_nodes_horizontally(const igraph_t* graph, + igraph_matrix_t* layout, const igraph_i_layering_t* layering, + igraph_real_t hgap, igraph_integer_t no_of_real_nodes); + +/** + * Calculated the median of four numbers (not necessarily sorted). + */ +static INLINE igraph_real_t igraph_i_median_4(igraph_real_t x1, + igraph_real_t x2, igraph_real_t x3, igraph_real_t x4) { + igraph_real_t arr[4] = { x1, x2, x3, x4 }; + igraph_vector_t vec; + igraph_vector_view(&vec, arr, 4); + igraph_vector_sort(&vec); + return (arr[1] + arr[2]) / 2.0; +} + + +/** + * \ingroup layout + * \function igraph_layout_sugiyama + * \brief Sugiyama layout algorithm for layered directed acyclic graphs. + * + * + * This layout algorithm is designed for directed acyclic graphs where each + * vertex is assigned to a layer. Layers are indexed from zero, and vertices + * of the same layer will be placed on the same horizontal line. The X coordinates + * of vertices within each layer are decided by the heuristic proposed by + * Sugiyama et al to minimize edge crossings. + * + * + * You can also try to lay out undirected graphs, graphs containing cycles, or + * graphs without an a priori layered assignment with this algorithm. igraph + * will try to eliminate cycles and assign vertices to layers, but there is no + * guarantee on the quality of the layout in such cases. + * + * + * The Sugiyama layout may introduce "bends" on the edges in order to obtain a + * visually more pleasing layout. This is achieved by adding dummy nodes to + * edges spanning more than one layer. The resulting layout assigns coordinates + * not only to the nodes of the original graph but also to the dummy nodes. + * The layout algorithm will also return the extended graph with the dummy nodes. + * An edge in the original graph may either be mapped to a single edge in the + * extended graph or a \em path that starts and ends in the original + * source and target vertex and passes through multiple dummy vertices. In + * such cases, the user may also request the mapping of the edges of the extended + * graph back to the edges of the original graph. + * + * + * For more details, see K. Sugiyama, S. Tagawa and M. Toda, "Methods for Visual + * Understanding of Hierarchical Systems". IEEE Transactions on Systems, Man and + * Cybernetics 11(2):109-125, 1981. + * + * \param graph Pointer to an initialized graph object. + * \param res Pointer to an initialized matrix object. This will contain + * the result and will be resized as needed. The first |V| rows + * of the layout will contain the coordinates of the original graph, + * the remaining rows contain the positions of the dummy nodes. + * Therefore, you can use the result both with \p graph or with + * \p extended_graph. + * \param extended_graph Pointer to an uninitialized graph object or \c NULL. + * The extended graph with the added dummy nodes will be + * returned here. In this graph, each edge points downwards + * to lower layers, spans exactly one layer and the first + * |V| vertices coincide with the vertices of the + * original graph. + * \param extd_to_orig_eids Pointer to a vector or \c NULL. If not \c NULL, the + * mapping from the edge IDs of the extended graph back + * to the edge IDs of the original graph will be stored + * here. + * \param layers The layer index for each vertex or \c NULL if the layers should + * be determined automatically by igraph. + * \param hgap The preferred minimum horizontal gap between vertices in the same + * layer. + * \param vgap The distance between layers. + * \param maxiter Maximum number of iterations in the crossing minimization stage. + * 100 is a reasonable default; if you feel that you have too + * many edge crossings, increase this. + * \param weights Weights of the edges. These are used only if the graph contains + * cycles; igraph will tend to reverse edges with smaller + * weights when breaking the cycles. + */ +int igraph_layout_sugiyama(const igraph_t *graph, igraph_matrix_t *res, + igraph_t *extd_graph, igraph_vector_t *extd_to_orig_eids, + const igraph_vector_t* layers, igraph_real_t hgap, igraph_real_t vgap, + long int maxiter, const igraph_vector_t *weights) { + long int i, j, k, l, m, nei; + long int no_of_nodes = (long int)igraph_vcount(graph); + long int comp_idx; + long int next_extd_vertex_id = no_of_nodes; + igraph_bool_t directed = igraph_is_directed(graph); + igraph_integer_t no_of_components; /* number of components of the original graph */ + igraph_vector_t membership; /* components of the original graph */ + igraph_vector_t extd_edgelist; /* edge list of the extended graph */ + igraph_vector_t layers_own; /* layer indices after having eliminated empty layers */ + igraph_real_t dx = 0, dx2 = 0; /* displacement of the current component on the X axis */ + igraph_vector_t layer_to_y; /* mapping from layer indices to final Y coordinates */ + + if (layers && igraph_vector_size(layers) != no_of_nodes) { + IGRAPH_ERROR("layer vector too short or too long", IGRAPH_EINVAL); + } + + if (extd_graph != 0) { + IGRAPH_VECTOR_INIT_FINALLY(&extd_edgelist, 0); + if (extd_to_orig_eids != 0) { + igraph_vector_clear(extd_to_orig_eids); + } + } + + IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2)); + IGRAPH_VECTOR_INIT_FINALLY(&membership, 0); + IGRAPH_VECTOR_INIT_FINALLY(&layer_to_y, 0); + + /* 1. Find a feedback arc set if we don't have a layering yet. If we do have + * a layering, we can leave all the edges as is as they will be re-oriented + * to point downwards only anyway. */ + if (layers == 0) { + IGRAPH_VECTOR_INIT_FINALLY(&layers_own, no_of_nodes); + IGRAPH_CHECK(igraph_i_layout_sugiyama_place_nodes_vertically( + graph, weights, &layers_own)); + } else { + IGRAPH_CHECK(igraph_vector_copy(&layers_own, layers)); + IGRAPH_FINALLY(igraph_vector_destroy, &layers_own); + } + + /* Normalize layering, eliminate empty layers */ + if (no_of_nodes > 0) { + igraph_vector_t inds; + IGRAPH_VECTOR_INIT_FINALLY(&inds, 0); + IGRAPH_CHECK((int) igraph_vector_qsort_ind(&layers_own, &inds, 0)); + j = -1; dx = VECTOR(layers_own)[(long int)VECTOR(inds)[0]] - 1; + for (i = 0; i < no_of_nodes; i++) { + k = (long int)VECTOR(inds)[i]; + if (VECTOR(layers_own)[k] > dx) { + /* New layer starts here */ + dx = VECTOR(layers_own)[k]; + j++; + IGRAPH_CHECK(igraph_vector_push_back(&layer_to_y, dx * vgap)); + } + VECTOR(layers_own)[k] = j; + } + igraph_vector_destroy(&inds); + IGRAPH_FINALLY_CLEAN(1); + } + + /* 2. Find the connected components. */ + IGRAPH_CHECK(igraph_clusters(graph, &membership, 0, &no_of_components, + IGRAPH_WEAK)); + + /* 3. For each component... */ + dx = 0; + for (comp_idx = 0; comp_idx < no_of_components; comp_idx++) { + /* Extract the edges of the comp_idx'th component and add dummy nodes for edges + * spanning more than one layer. */ + long int component_size, next_new_vertex_id; + igraph_vector_t old2new_vertex_ids; + igraph_vector_t new2old_vertex_ids; + igraph_vector_t new_layers; + igraph_vector_t edgelist; + igraph_vector_t neis; + + IGRAPH_VECTOR_INIT_FINALLY(&edgelist, 0); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INIT_FINALLY(&new2old_vertex_ids, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&old2new_vertex_ids, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&new_layers, 0); + + igraph_vector_fill(&old2new_vertex_ids, -1); + + /* Construct a mapping from the old vertex ids to the new ones */ + for (i = 0, next_new_vertex_id = 0; i < no_of_nodes; i++) { + if (VECTOR(membership)[i] == comp_idx) { + IGRAPH_CHECK(igraph_vector_push_back(&new_layers, VECTOR(layers_own)[i])); + VECTOR(new2old_vertex_ids)[next_new_vertex_id] = i; + VECTOR(old2new_vertex_ids)[i] = next_new_vertex_id; + next_new_vertex_id++; + } + } + component_size = next_new_vertex_id; + + /* Construct a proper layering of the component in new_graph where each edge + * points downwards and spans exactly one layer. */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(membership)[i] != comp_idx) { + continue; + } + + /* Okay, this vertex is in the component we are considering. + * Add the neighbors of this vertex, excluding loops */ + IGRAPH_CHECK(igraph_incident(graph, &neis, (igraph_integer_t) i, + IGRAPH_OUT)); + j = igraph_vector_size(&neis); + for (k = 0; k < j; k++) { + long int eid = (long int) VECTOR(neis)[k]; + if (directed) { + nei = IGRAPH_TO(graph, eid); + } else { + nei = IGRAPH_OTHER(graph, eid, i); + if (nei < i) { /* to avoid considering edges twice */ + continue; + } + } + if (VECTOR(layers_own)[i] == VECTOR(layers_own)[nei]) { + /* Edge goes within the same layer, we don't need this in the + * layered graph, but we need it in the extended graph */ + if (extd_graph != 0) { + IGRAPH_CHECK(igraph_vector_push_back(&extd_edgelist, i)); + IGRAPH_CHECK(igraph_vector_push_back(&extd_edgelist, nei)); + if (extd_to_orig_eids != 0) { + IGRAPH_CHECK(igraph_vector_push_back(extd_to_orig_eids, eid)); + } + } + } else if (VECTOR(layers_own)[i] > VECTOR(layers_own)[nei]) { + /* Edge goes upwards, we have to flip it */ + IGRAPH_CHECK(igraph_vector_push_back(&edgelist, + VECTOR(old2new_vertex_ids)[nei])); + for (l = (long int) VECTOR(layers_own)[nei] + 1; + l < VECTOR(layers_own)[i]; l++) { + IGRAPH_CHECK(igraph_vector_push_back(&new_layers, l)); + IGRAPH_CHECK(igraph_vector_push_back(&edgelist, next_new_vertex_id)); + IGRAPH_CHECK(igraph_vector_push_back(&edgelist, next_new_vertex_id++)); + } + IGRAPH_CHECK(igraph_vector_push_back(&edgelist, + VECTOR(old2new_vertex_ids)[i])); + /* Also add the edge to the extended graph if needed, but this time + * with the proper orientation */ + if (extd_graph != 0) { + IGRAPH_CHECK(igraph_vector_push_back(&extd_edgelist, i)); + next_extd_vertex_id += VECTOR(layers_own)[i] - VECTOR(layers_own)[nei] - 1; + for (l = (long int) VECTOR(layers_own)[i] - 1, m = 1; + l > VECTOR(layers_own)[nei]; l--, m++) { + IGRAPH_CHECK(igraph_vector_push_back(&extd_edgelist, next_extd_vertex_id - m)); + IGRAPH_CHECK(igraph_vector_push_back(&extd_edgelist, next_extd_vertex_id - m)); + if (extd_to_orig_eids != 0) { + IGRAPH_CHECK(igraph_vector_push_back(extd_to_orig_eids, eid)); + } + } + IGRAPH_CHECK(igraph_vector_push_back(&extd_edgelist, nei)); + if (extd_to_orig_eids != 0) { + IGRAPH_CHECK(igraph_vector_push_back(extd_to_orig_eids, eid)); + } + } + } else { + /* Edge goes downwards */ + IGRAPH_CHECK(igraph_vector_push_back(&edgelist, + VECTOR(old2new_vertex_ids)[i])); + for (l = (long int) VECTOR(layers_own)[i] + 1; + l < VECTOR(layers_own)[nei]; l++) { + IGRAPH_CHECK(igraph_vector_push_back(&new_layers, l)); + IGRAPH_CHECK(igraph_vector_push_back(&edgelist, next_new_vertex_id)); + IGRAPH_CHECK(igraph_vector_push_back(&edgelist, next_new_vertex_id++)); + } + IGRAPH_CHECK(igraph_vector_push_back(&edgelist, + VECTOR(old2new_vertex_ids)[nei])); + /* Also add the edge to the extended graph */ + if (extd_graph != 0) { + IGRAPH_CHECK(igraph_vector_push_back(&extd_edgelist, i)); + for (l = (long int) VECTOR(layers_own)[i] + 1; + l < VECTOR(layers_own)[nei]; l++) { + IGRAPH_CHECK(igraph_vector_push_back(&extd_edgelist, next_extd_vertex_id)); + IGRAPH_CHECK(igraph_vector_push_back(&extd_edgelist, next_extd_vertex_id++)); + if (extd_to_orig_eids != 0) { + IGRAPH_CHECK(igraph_vector_push_back(extd_to_orig_eids, eid)); + } + } + IGRAPH_CHECK(igraph_vector_push_back(&extd_edgelist, nei)); + if (extd_to_orig_eids != 0) { + IGRAPH_CHECK(igraph_vector_push_back(extd_to_orig_eids, eid)); + } + } + } + } + } + + /* At this point, we have the subgraph with the dummy nodes and + * edges, so we can run Sugiyama's algorithm on it. */ + { + igraph_matrix_t layout; + igraph_i_layering_t layering; + igraph_t subgraph; + + IGRAPH_CHECK(igraph_matrix_init(&layout, next_new_vertex_id, 2)); + IGRAPH_FINALLY(igraph_matrix_destroy, &layout); + IGRAPH_CHECK(igraph_create(&subgraph, &edgelist, (igraph_integer_t) + next_new_vertex_id, 1)); + IGRAPH_FINALLY(igraph_destroy, &subgraph); + + /* + igraph_vector_print(&edgelist); + igraph_vector_print(&new_layers); + */ + + /* Assign the vertical coordinates */ + for (i = 0; i < next_new_vertex_id; i++) { + MATRIX(layout, i, 1) = VECTOR(new_layers)[i]; + } + + /* Create a layering */ + IGRAPH_CHECK(igraph_i_layering_init(&layering, &new_layers)); + IGRAPH_FINALLY(igraph_i_layering_destroy, &layering); + + /* Find the order in which the nodes within a layer should be placed */ + IGRAPH_CHECK(igraph_i_layout_sugiyama_order_nodes_horizontally(&subgraph, &layout, + &layering, maxiter)); + + /* Assign the horizontal coordinates. This is according to the algorithm + * of Brandes & Köpf */ + IGRAPH_CHECK(igraph_i_layout_sugiyama_place_nodes_horizontally(&subgraph, &layout, + &layering, hgap, (igraph_integer_t) component_size)); + + /* Re-assign rows into the result matrix, and at the same time, */ + /* adjust dx so that the next component does not overlap this one */ + j = next_new_vertex_id - component_size; + k = igraph_matrix_nrow(res); + IGRAPH_CHECK(igraph_matrix_add_rows(res, j)); + dx2 = dx; + for (i = 0; i < component_size; i++) { + l = (long int)VECTOR(new2old_vertex_ids)[i]; + MATRIX(*res, l, 0) = MATRIX(layout, i, 0) + dx; + MATRIX(*res, l, 1) = VECTOR(layer_to_y)[(long)MATRIX(layout, i, 1)]; + if (dx2 < MATRIX(*res, l, 0)) { + dx2 = MATRIX(*res, l, 0); + } + } + for (i = component_size; i < next_new_vertex_id; i++) { + MATRIX(*res, k, 0) = MATRIX(layout, i, 0) + dx; + MATRIX(*res, k, 1) = VECTOR(layer_to_y)[(long)MATRIX(layout, i, 1)]; + if (dx2 < MATRIX(*res, k, 0)) { + dx2 = MATRIX(*res, k, 0); + } + k++; + } + dx = dx2 + hgap; + + igraph_destroy(&subgraph); + igraph_i_layering_destroy(&layering); + igraph_matrix_destroy(&layout); + IGRAPH_FINALLY_CLEAN(3); + } + + igraph_vector_destroy(&new_layers); + igraph_vector_destroy(&old2new_vertex_ids); + igraph_vector_destroy(&new2old_vertex_ids); + igraph_vector_destroy(&edgelist); + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(5); + } + + igraph_vector_destroy(&layers_own); + igraph_vector_destroy(&layer_to_y); + igraph_vector_destroy(&membership); + IGRAPH_FINALLY_CLEAN(3); + + if (extd_graph != 0) { + IGRAPH_CHECK(igraph_create(extd_graph, &extd_edgelist, (igraph_integer_t) + next_extd_vertex_id, igraph_is_directed(graph))); + igraph_vector_destroy(&extd_edgelist); + IGRAPH_FINALLY_CLEAN(1); + } + + return IGRAPH_SUCCESS; +} + +static int igraph_i_layout_sugiyama_place_nodes_vertically(const igraph_t* graph, + const igraph_vector_t* weights, igraph_vector_t* membership) { + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + IGRAPH_CHECK(igraph_vector_resize(membership, no_of_nodes)); + + if (no_of_edges == 0) { + igraph_vector_fill(membership, 0); + return IGRAPH_SUCCESS; + } + +#ifdef HAVE_GLPK + if (igraph_is_directed(graph) && no_of_nodes <= 1000) { + /* Network simplex algorithm of Gansner et al, using the original linear + * programming formulation */ + long int i, j; + igraph_vector_t outdegs, indegs, feedback_edges; + glp_prob *ip; + glp_smcp parm; + + /* Allocate storage and create the problem */ + ip = glp_create_prob(); + IGRAPH_FINALLY(glp_delete_prob, ip); + IGRAPH_VECTOR_INIT_FINALLY(&feedback_edges, 0); + IGRAPH_VECTOR_INIT_FINALLY(&outdegs, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&indegs, no_of_nodes); + + /* Find an approximate feedback edge set */ + IGRAPH_CHECK(igraph_i_feedback_arc_set_eades(graph, &feedback_edges, weights, 0)); + igraph_vector_sort(&feedback_edges); + + /* Calculate in- and out-strengths for the remaining edges */ + IGRAPH_CHECK(igraph_strength(graph, &indegs, igraph_vss_all(), + IGRAPH_IN, 1, weights)); + IGRAPH_CHECK(igraph_strength(graph, &outdegs, igraph_vss_all(), + IGRAPH_IN, 1, weights)); + j = igraph_vector_size(&feedback_edges); + for (i = 0; i < j; i++) { + long int eid = (long int) VECTOR(feedback_edges)[i]; + long int from = IGRAPH_FROM(graph, eid); + long int to = IGRAPH_TO(graph, eid); + VECTOR(outdegs)[from] -= weights ? VECTOR(*weights)[eid] : 1; + VECTOR(indegs)[to] -= weights ? VECTOR(*weights)[eid] : 1; + } + + /* Configure GLPK */ + glp_term_out(GLP_OFF); + glp_init_smcp(&parm); + parm.msg_lev = GLP_MSG_OFF; + parm.presolve = GLP_OFF; + + /* Set up variables and objective function coefficients */ + glp_set_obj_dir(ip, GLP_MIN); + glp_add_cols(ip, (int) no_of_nodes); + IGRAPH_CHECK(igraph_vector_sub(&outdegs, &indegs)); + for (i = 1; i <= no_of_nodes; i++) { + glp_set_col_kind(ip, (int) i, GLP_IV); + glp_set_col_bnds(ip, (int) i, GLP_LO, 0.0, 0.0); + glp_set_obj_coef(ip, (int) i, VECTOR(outdegs)[i - 1]); + } + igraph_vector_destroy(&indegs); + igraph_vector_destroy(&outdegs); + IGRAPH_FINALLY_CLEAN(2); + + /* Add constraints */ + glp_add_rows(ip, (int) no_of_edges); + IGRAPH_CHECK(igraph_vector_push_back(&feedback_edges, -1)); + j = 0; + for (i = 0; i < no_of_edges; i++) { + int ind[3]; + double val[3] = {0, -1, 1}; + ind[1] = IGRAPH_FROM(graph, i) + 1; + ind[2] = IGRAPH_TO(graph, i) + 1; + + if (ind[1] == ind[2]) { + if (VECTOR(feedback_edges)[j] == i) { + j++; + } + continue; + } + + if (VECTOR(feedback_edges)[j] == i) { + /* This is a feedback edge, add it reversed */ + glp_set_row_bnds(ip, (int) i + 1, GLP_UP, -1, -1); + j++; + } else { + glp_set_row_bnds(ip, (int) i + 1, GLP_LO, 1, 1); + } + glp_set_mat_row(ip, (int) i + 1, 2, ind, val); + } + + /* Solve the problem */ + IGRAPH_GLPK_CHECK(glp_simplex(ip, &parm), + "Vertical arrangement step using IP failed"); + + /* The problem is totally unimodular, therefore the output of the simplex + * solver can be converted to an integer solution easily */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*membership)[i] = floor(glp_get_col_prim(ip, (int) i + 1)); + } + + glp_delete_prob(ip); + igraph_vector_destroy(&feedback_edges); + IGRAPH_FINALLY_CLEAN(2); + } else if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_feedback_arc_set_eades(graph, 0, weights, membership)); + } else { + IGRAPH_CHECK(igraph_i_feedback_arc_set_undirected(graph, 0, weights, membership)); + } +#else + if (igraph_is_directed(graph)) { + IGRAPH_CHECK(igraph_i_feedback_arc_set_eades(graph, 0, weights, membership)); + } else { + IGRAPH_CHECK(igraph_i_feedback_arc_set_undirected(graph, 0, weights, membership)); + } +#endif + + return IGRAPH_SUCCESS; +} + +static int igraph_i_layout_sugiyama_calculate_barycenters(const igraph_t* graph, + const igraph_i_layering_t* layering, long int layer_index, + igraph_neimode_t direction, const igraph_matrix_t* layout, + igraph_vector_t* barycenters) { + long int i, j, m, n; + igraph_vector_t* layer_members = igraph_i_layering_get(layering, layer_index); + igraph_vector_t neis; + + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + n = igraph_vector_size(layer_members); + IGRAPH_CHECK(igraph_vector_resize(barycenters, n)); + igraph_vector_null(barycenters); + + for (i = 0; i < n; i++) { + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) + VECTOR(*layer_members)[i], direction)); + m = igraph_vector_size(&neis); + if (m == 0) { + /* No neighbors in this direction. Just use the current X coordinate */ + VECTOR(*barycenters)[i] = MATRIX(*layout, i, 0); + } else { + for (j = 0; j < m; j++) { + VECTOR(*barycenters)[i] += MATRIX(*layout, (long)VECTOR(neis)[j], 0); + } + VECTOR(*barycenters)[i] /= m; + } + } + + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +/** + * Given a properly layered graph where each edge points downwards and spans + * exactly one layer, arranges the nodes in each layer horizontally in a way + * that strives to minimize edge crossings. + */ +static int igraph_i_layout_sugiyama_order_nodes_horizontally(const igraph_t* graph, + igraph_matrix_t* layout, const igraph_i_layering_t* layering, + long int maxiter) { + long int i, n, nei; + long int no_of_vertices = igraph_vcount(graph); + long int no_of_layers = igraph_i_layering_num_layers(layering); + long int iter, layer_index; + igraph_vector_t* layer_members; + igraph_vector_t neis, barycenters, sort_indices; + igraph_bool_t changed; + + /* The first column of the matrix will serve as the ordering */ + /* Start with a first-seen ordering within each layer */ + { + long int *xs = igraph_Calloc(no_of_layers, long int); + if (xs == 0) { + IGRAPH_ERROR("cannot order nodes horizontally", IGRAPH_ENOMEM); + } + for (i = 0; i < no_of_vertices; i++) { + MATRIX(*layout, i, 0) = xs[(long int)MATRIX(*layout, i, 1)]++; + } + free(xs); + } + + IGRAPH_VECTOR_INIT_FINALLY(&barycenters, 0); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INIT_FINALLY(&sort_indices, 0); + + /* Start the effective part of the Sugiyama algorithm */ + iter = 0; changed = 1; + while (changed && iter < maxiter) { + changed = 0; + + /* Phase 1 */ + + /* Moving downwards and sorting by upper barycenters */ + for (layer_index = 1; layer_index < no_of_layers; layer_index++) { + layer_members = igraph_i_layering_get(layering, layer_index); + n = igraph_vector_size(layer_members); + + igraph_i_layout_sugiyama_calculate_barycenters(graph, + layering, layer_index, IGRAPH_IN, layout, &barycenters); + +#ifdef SUGIYAMA_DEBUG + printf("Layer %ld, aligning to upper barycenters\n", layer_index); + printf("Vertices: "); igraph_vector_print(layer_members); + printf("Barycenters: "); igraph_vector_print(&barycenters); +#endif + IGRAPH_CHECK((int) igraph_vector_qsort_ind(&barycenters, + &sort_indices, 0)); + for (i = 0; i < n; i++) { + nei = (long)VECTOR(*layer_members)[(long)VECTOR(sort_indices)[i]]; + VECTOR(barycenters)[i] = nei; + MATRIX(*layout, nei, 0) = i; + } + if (!igraph_vector_all_e(layer_members, &barycenters)) { + IGRAPH_CHECK(igraph_vector_update(layer_members, &barycenters)); +#ifdef SUGIYAMA_DEBUG + printf("New vertex order: "); igraph_vector_print(layer_members); +#endif + changed = 1; + } else { +#ifdef SUGIYAMA_DEBUG + printf("Order did not change.\n"); +#endif + } + } + + /* Moving upwards and sorting by lower barycenters */ + for (layer_index = no_of_layers - 2; layer_index >= 0; layer_index--) { + layer_members = igraph_i_layering_get(layering, layer_index); + n = igraph_vector_size(layer_members); + + igraph_i_layout_sugiyama_calculate_barycenters(graph, + layering, layer_index, IGRAPH_OUT, layout, &barycenters); + +#ifdef SUGIYAMA_DEBUG + printf("Layer %ld, aligning to lower barycenters\n", layer_index); + printf("Vertices: "); igraph_vector_print(layer_members); + printf("Barycenters: "); igraph_vector_print(&barycenters); +#endif + + IGRAPH_CHECK((int) igraph_vector_qsort_ind(&barycenters, + &sort_indices, 0)); + for (i = 0; i < n; i++) { + nei = (long)VECTOR(*layer_members)[(long)VECTOR(sort_indices)[i]]; + VECTOR(barycenters)[i] = nei; + MATRIX(*layout, nei, 0) = i; + } + if (!igraph_vector_all_e(layer_members, &barycenters)) { + IGRAPH_CHECK(igraph_vector_update(layer_members, &barycenters)); +#ifdef SUGIYAMA_DEBUG + printf("New vertex order: "); igraph_vector_print(layer_members); +#endif + changed = 1; + } else { +#ifdef SUGIYAMA_DEBUG + printf("Order did not change.\n"); +#endif + } + } + +#ifdef SUGIYAMA_DEBUG + printf("==== Finished iteration %ld\n", iter); +#endif + + iter++; + } + + igraph_vector_destroy(&barycenters); + igraph_vector_destroy(&neis); + igraph_vector_destroy(&sort_indices); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +#define IS_DUMMY(v) ((v >= no_of_real_nodes)) +#define IS_INNER_SEGMENT(u, v) (IS_DUMMY(u) && IS_DUMMY(v)) +#define X_POS(v) (MATRIX(*layout, v, 0)) + +static int igraph_i_layout_sugiyama_vertical_alignment(const igraph_t* graph, + const igraph_i_layering_t* layering, const igraph_matrix_t* layout, + const igraph_vector_bool_t* ignored_edges, + igraph_bool_t reverse, igraph_bool_t align_right, + igraph_vector_t* roots, igraph_vector_t* align); +static int igraph_i_layout_sugiyama_horizontal_compaction(const igraph_t* graph, + const igraph_vector_t* vertex_to_the_left, + const igraph_vector_t* roots, const igraph_vector_t* align, + igraph_real_t hgap, igraph_vector_t* xs); +static int igraph_i_layout_sugiyama_horizontal_compaction_place_block(long int v, + const igraph_vector_t* vertex_to_the_left, + const igraph_vector_t* roots, const igraph_vector_t* align, + igraph_vector_t* sinks, igraph_vector_t* shifts, + igraph_real_t hgap, igraph_vector_t* xs); + +static int igraph_i_layout_sugiyama_place_nodes_horizontally(const igraph_t* graph, + igraph_matrix_t* layout, const igraph_i_layering_t* layering, + igraph_real_t hgap, igraph_integer_t no_of_real_nodes) { + + long int i, j, k, l, n; + long int no_of_layers = igraph_i_layering_num_layers(layering); + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_vector_t neis1, neis2; + igraph_vector_t xs[4]; + igraph_vector_t roots, align; + igraph_vector_t vertex_to_the_left; + igraph_vector_bool_t ignored_edges; + + /* + { + igraph_vector_t edgelist; + IGRAPH_VECTOR_INIT_FINALLY(&edgelist, 0); + IGRAPH_CHECK(igraph_get_edgelist(graph, &edgelist, 0)); + igraph_vector_print(&edgelist); + igraph_vector_destroy(&edgelist); + IGRAPH_FINALLY_CLEAN(1); + + for (i = 0; i < no_of_layers; i++) { + igraph_vector_t* layer = igraph_i_layering_get(layering, i); + igraph_vector_print(layer); + } + } + */ + + IGRAPH_CHECK(igraph_vector_bool_init(&ignored_edges, no_of_edges)); + IGRAPH_FINALLY(igraph_vector_bool_destroy, &ignored_edges); + + IGRAPH_VECTOR_INIT_FINALLY(&vertex_to_the_left, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&neis1, 0); + IGRAPH_VECTOR_INIT_FINALLY(&neis2, 0); + + /* First, find all type 1 conflicts and mark one of the edges participating + * in the conflict as being ignored. If one of the edges in the conflict + * is a non-inner segment and the other is an inner segment, we ignore the + * non-inner segment as we want to keep inner segments vertical. + */ + for (i = 0; i < no_of_layers - 1; i++) { + igraph_vector_t* vertices = igraph_i_layering_get(layering, i); + n = igraph_vector_size(vertices); + + /* Find all the edges from this layer to the next */ + igraph_vector_clear(&neis1); + for (j = 0; j < n; j++) { + IGRAPH_CHECK(igraph_neighbors(graph, &neis2, (igraph_integer_t) + VECTOR(*vertices)[j], IGRAPH_OUT)); + IGRAPH_CHECK(igraph_vector_append(&neis1, &neis2)); + } + + /* Consider all pairs of edges and check whether they are in a type 1 + * conflict */ + n = igraph_vector_size(&neis1); + for (j = 0; j < n; j++) { + long int u = IGRAPH_FROM(graph, j); + long int v = IGRAPH_TO(graph, j); + igraph_bool_t j_inner = IS_INNER_SEGMENT(u, v); + igraph_bool_t crossing; + + for (k = j + 1; k < n; k++) { + long int w = IGRAPH_FROM(graph, k); + long int x = IGRAPH_TO(graph, k); + if (IS_INNER_SEGMENT(w, x) == j_inner) { + continue; + } + /* Do the u --> v and w --> x edges cross? */ + crossing = (u == w || v == x); + if (!crossing) { + if (X_POS(u) <= X_POS(w)) { + crossing = X_POS(v) >= X_POS(x); + } else { + crossing = X_POS(v) <= X_POS(x); + } + } + if (crossing) { + if (j_inner) { + VECTOR(ignored_edges)[k] = 1; + } else { + VECTOR(ignored_edges)[j] = 1; + } + } + } + } + } + + igraph_vector_destroy(&neis1); + igraph_vector_destroy(&neis2); + IGRAPH_FINALLY_CLEAN(2); + + /* + * Prepare vertex_to_the_left where the ith element stores + * the index of the vertex to the left of vertex i, or i itself if the + * vertex is the leftmost vertex in a layer. + */ + for (i = 0; i < no_of_layers; i++) { + igraph_vector_t* vertices = igraph_i_layering_get(layering, i); + n = igraph_vector_size(vertices); + if (n == 0) { + continue; + } + + k = l = (long int)VECTOR(*vertices)[0]; + VECTOR(vertex_to_the_left)[k] = k; + for (j = 1; j < n; j++) { + k = (long int)VECTOR(*vertices)[j]; + VECTOR(vertex_to_the_left)[k] = l; + l = k; + } + } + + /* Type 1 conflicts found, ignored edges chosen, vertex_to_the_left + * prepared. Run vertical alignment for all four combinations */ + for (i = 0; i < 4; i++) { + IGRAPH_VECTOR_INIT_FINALLY(&xs[i], no_of_nodes); + } + + IGRAPH_VECTOR_INIT_FINALLY(&roots, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&align, no_of_nodes); + + for (i = 0; i < 4; i++) { + IGRAPH_CHECK(igraph_i_layout_sugiyama_vertical_alignment(graph, + layering, layout, &ignored_edges, + /* reverse = */ (igraph_bool_t) i / 2, /* align_right = */ i % 2, + &roots, &align)); + IGRAPH_CHECK(igraph_i_layout_sugiyama_horizontal_compaction(graph, + &vertex_to_the_left, &roots, &align, hgap, &xs[i])); + } + + { + igraph_real_t width, min_width, mins[4], maxs[4], diff; + /* Find the alignment with the minimum width */ + min_width = IGRAPH_INFINITY; j = 0; + for (i = 0; i < 4; i++) { + mins[i] = igraph_vector_min(&xs[i]); + maxs[i] = igraph_vector_max(&xs[i]); + width = maxs[i] - mins[i]; + if (width < min_width) { + min_width = width; + j = i; + } + } + + /* Leftmost alignments: align them s.t. the min X coordinate is equal to + * the minimum X coordinate of the alignment with the smallest width. + * Rightmost alignments: align them s.t. the max X coordinate is equal to + * the max X coordinate of the alignment with the smallest width. + */ + for (i = 0; i < 4; i++) { + if (j == i) { + continue; + } + if (i % 2 == 0) { + /* Leftmost alignment */ + diff = mins[j] - mins[i]; + } else { + /* Rightmost alignment */ + diff = maxs[j] - maxs[i]; + } + igraph_vector_add_constant(&xs[i], diff); + } + } + + /* For every vertex, find the median of the X coordinates in the four + * alignments */ + for (i = 0; i < no_of_nodes; i++) { + X_POS(i) = igraph_i_median_4(VECTOR(xs[0])[i], VECTOR(xs[1])[i], + VECTOR(xs[2])[i], VECTOR(xs[3])[i]); + } + + igraph_vector_destroy(&roots); + igraph_vector_destroy(&align); + IGRAPH_FINALLY_CLEAN(2); + + for (i = 0; i < 4; i++) { + igraph_vector_destroy(&xs[i]); + } + IGRAPH_FINALLY_CLEAN(4); + + igraph_vector_destroy(&vertex_to_the_left); + IGRAPH_FINALLY_CLEAN(1); + + igraph_vector_bool_destroy(&ignored_edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} + +static int igraph_i_layout_sugiyama_vertical_alignment(const igraph_t* graph, + const igraph_i_layering_t* layering, const igraph_matrix_t* layout, + const igraph_vector_bool_t* ignored_edges, + igraph_bool_t reverse, igraph_bool_t align_right, + igraph_vector_t* roots, igraph_vector_t* align) { + long int i, j, k, n, di, dj, i_limit, j_limit, r; + long int no_of_layers = igraph_i_layering_num_layers(layering); + long int no_of_nodes = igraph_vcount(graph); + igraph_neimode_t neimode = (reverse ? IGRAPH_OUT : IGRAPH_IN); + igraph_vector_t neis, xs, inds; + + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_VECTOR_INIT_FINALLY(&xs, 0); + IGRAPH_VECTOR_INIT_FINALLY(&inds, 0); + + IGRAPH_CHECK(igraph_vector_resize(roots, no_of_nodes)); + IGRAPH_CHECK(igraph_vector_resize(align, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*roots)[i] = VECTOR(*align)[i] = i; + } + + /* When reverse = False, we are aligning "upwards" in the tree, hence we + * have to loop i from 1 to no_of_layers-1 (inclusive) and use neimode=IGRAPH_IN. + * When reverse = True, we are aligning "downwards", hence we have to loop + * i from no_of_layers-2 to 0 (inclusive) and use neimode=IGRAPH_OUT. + */ + i = reverse ? (no_of_layers - 2) : 1; + di = reverse ? -1 : 1; + i_limit = reverse ? -1 : no_of_layers; + for (; i != i_limit; i += di) { + igraph_vector_t *layer = igraph_i_layering_get(layering, i); + + /* r = 0 in the paper, but C arrays are indexed from 0 */ + r = align_right ? LONG_MAX : -1; + + /* If align_right is 1, we have to process the layer in reverse order */ + j = align_right ? (igraph_vector_size(layer) - 1) : 0; + dj = align_right ? -1 : 1; + j_limit = align_right ? -1 : igraph_vector_size(layer); + for (; j != j_limit; j += dj) { + long int medians[2]; + long int vertex = (long int) VECTOR(*layer)[j]; + long int pos; + + if (VECTOR(*align)[vertex] != vertex) + /* This vertex is already aligned with some other vertex, + * so there's nothing to do */ + { + continue; + } + + /* Find the neighbors of vertex j in layer i */ + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) vertex, + neimode)); + + n = igraph_vector_size(&neis); + if (n == 0) + /* No neighbors in this direction, continue */ + { + continue; + } + if (n == 1) { + /* Just one neighbor; the median is trivial */ + medians[0] = (long int) VECTOR(neis)[0]; + medians[1] = -1; + } else { + /* Sort the neighbors by their X coordinates */ + IGRAPH_CHECK(igraph_vector_resize(&xs, n)); + for (k = 0; k < n; k++) { + VECTOR(xs)[k] = X_POS((long int)VECTOR(neis)[k]); + } + IGRAPH_CHECK((int) igraph_vector_qsort_ind(&xs, &inds, 0)); + + if (n % 2 == 1) { + /* Odd number of neighbors, so the median is unique */ + medians[0] = (long int) VECTOR(neis)[(long int)VECTOR(inds)[n / 2]]; + medians[1] = -1; + } else { + /* Even number of neighbors, so we have two medians. The order + * depends on whether we are processing the layer in leftmost + * or rightmost fashion. */ + if (align_right) { + medians[0] = (long int) VECTOR(neis)[(long int)VECTOR(inds)[n / 2]]; + medians[1] = (long int) VECTOR(neis)[(long int)VECTOR(inds)[n / 2 - 1]]; + } else { + medians[0] = (long int) VECTOR(neis)[(long int)VECTOR(inds)[n / 2 - 1]]; + medians[1] = (long int) VECTOR(neis)[(long int)VECTOR(inds)[n / 2]]; + } + } + } + + /* Try aligning with the medians */ + for (k = 0; k < 2; k++) { + igraph_integer_t eid; + if (medians[k] < 0) { + continue; + } + if (VECTOR(*align)[vertex] != vertex) { + /* Vertex already aligned, continue */ + continue; + } + /* Is the edge between medians[k] and vertex ignored + * because of a type 1 conflict? */ + IGRAPH_CHECK(igraph_get_eid(graph, &eid, (igraph_integer_t) vertex, + (igraph_integer_t) medians[k], 0, 1)); + if (VECTOR(*ignored_edges)[(long int)eid]) { + continue; + } + /* Okay, align with the median if possible */ + pos = (long int) X_POS(medians[k]); + if ((align_right && r > pos) || (!align_right && r < pos)) { + VECTOR(*align)[medians[k]] = vertex; + VECTOR(*roots)[vertex] = VECTOR(*roots)[medians[k]]; + VECTOR(*align)[vertex] = VECTOR(*roots)[medians[k]]; + r = pos; + } + } + } + } + + igraph_vector_destroy(&inds); + igraph_vector_destroy(&neis); + igraph_vector_destroy(&xs); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +/* + * Runs a horizontal compaction given a vertical alignment (in `align`) + * and the roots (in `roots`). These come out directly from + * igraph_i_layout_sugiyama_vertical_alignment. + * + * Returns the X coordinates for each vertex in `xs`. + * + * `graph` is the input graph, `layering` is the layering on which we operate. + * `hgap` is the preferred horizontal gap between vertices. + */ +static int igraph_i_layout_sugiyama_horizontal_compaction(const igraph_t* graph, + const igraph_vector_t* vertex_to_the_left, + const igraph_vector_t* roots, const igraph_vector_t* align, + igraph_real_t hgap, igraph_vector_t* xs) { + long int i; + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t sinks, shifts, old_xs; + igraph_real_t shift; + + /* Initialization */ + + IGRAPH_VECTOR_INIT_FINALLY(&sinks, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&shifts, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&old_xs, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_resize(xs, no_of_nodes)); + + for (i = 0; i < no_of_nodes; i++) { + VECTOR(sinks)[i] = i; + } + igraph_vector_fill(&shifts, IGRAPH_INFINITY); + igraph_vector_fill(xs, -1); + + /* Calculate the coordinates of the vertices relative to their sinks + * in their own class. At the end of this for loop, xs will contain the + * relative displacement of a vertex from its sink, while the shifts list + * will contain the absolute displacement of the sinks. + * (For the sinks only, of course, the rest is undefined and unused) + */ + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*roots)[i] == i) { + IGRAPH_CHECK( + igraph_i_layout_sugiyama_horizontal_compaction_place_block(i, + vertex_to_the_left, roots, align, &sinks, &shifts, hgap, xs) + ); + } + } + + /* In "sinks", only those indices `i` matter for which `i` is in `roots`. + * All the other values will never be touched. + */ + + /* Calculate the absolute coordinates */ + IGRAPH_CHECK(igraph_vector_update(&old_xs, xs)); + for (i = 0; i < no_of_nodes; i++) { + long int root = (long int) VECTOR(*roots)[i]; + VECTOR(*xs)[i] = VECTOR(old_xs)[root]; + shift = VECTOR(shifts)[(long int)VECTOR(sinks)[root]]; + if (shift < IGRAPH_INFINITY) { + VECTOR(*xs)[i] += shift; + } + } + + igraph_vector_destroy(&sinks); + igraph_vector_destroy(&shifts); + igraph_vector_destroy(&old_xs); + IGRAPH_FINALLY_CLEAN(3); + + return IGRAPH_SUCCESS; +} + +static int igraph_i_layout_sugiyama_horizontal_compaction_place_block(long int v, + const igraph_vector_t* vertex_to_the_left, + const igraph_vector_t* roots, const igraph_vector_t* align, + igraph_vector_t* sinks, igraph_vector_t* shifts, + igraph_real_t hgap, igraph_vector_t* xs) { + long int u, w; + long int u_sink, v_sink; + + if (VECTOR(*xs)[v] >= 0) { + return IGRAPH_SUCCESS; + } + + VECTOR(*xs)[v] = 0; + + w = v; + do { + /* Check whether vertex w is the leftmost in its own layer */ + u = (long int) VECTOR(*vertex_to_the_left)[w]; + if (u != w) { + /* Get the root of u (proceeding all the way upwards in the block) */ + u = (long int) VECTOR(*roots)[u]; + /* Place the block of u recursively */ + IGRAPH_CHECK( + igraph_i_layout_sugiyama_horizontal_compaction_place_block(u, + vertex_to_the_left, roots, align, sinks, shifts, hgap, xs) + ); + + u_sink = (long int) VECTOR(*sinks)[u]; + v_sink = (long int) VECTOR(*sinks)[v]; + /* If v is its own sink yet, set its sink to the sink of u */ + if (v_sink == v) { + VECTOR(*sinks)[v] = v_sink = u_sink; + } + /* If v and u have different sinks (i.e. they are in different classes), + * shift the sink of u so that the two blocks are separated by the + * preferred gap + */ + if (v_sink != u_sink) { + if (VECTOR(*shifts)[u_sink] > VECTOR(*xs)[v] - VECTOR(*xs)[u] - hgap) { + VECTOR(*shifts)[u_sink] = VECTOR(*xs)[v] - VECTOR(*xs)[u] - hgap; + } + } else { + /* v and u have the same sink, i.e. they are in the same class. Make sure + * that v is separated from u by at least hgap. + */ + if (VECTOR(*xs)[v] < VECTOR(*xs)[u] + hgap) { + VECTOR(*xs)[v] = VECTOR(*xs)[u] + hgap; + } + } + } + + /* Follow the alignment */ + w = (long int) VECTOR(*align)[w]; + } while (w != v); + + return IGRAPH_SUCCESS; +} + +#undef IS_INNER_SEGMENT +#undef IS_DUMMY +#undef X_POS + +#ifdef SUGIYAMA_DEBUG + #undef SUGIYAMA_DEBUG +#endif + + diff --git a/src/topology.c b/src/topology.c new file mode 100644 index 0000000..aaf11d7 --- /dev/null +++ b/src/topology.c @@ -0,0 +1,3136 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_topology.h" +#include "igraph_memory.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_interrupt_internal.h" +#include "igraph_constructors.h" +#include "igraph_conversion.h" +#include "igraph_stack.h" +#include "igraph_attributes.h" +#include "igraph_structural.h" +#include "config.h" + +const unsigned int igraph_i_isoclass_3[] = { 0, 1, 1, 3, 1, 5, 6, 7, + 1, 6, 10, 11, 3, 7, 11, 15, + 1, 6, 5, 7, 10, 21, 21, 23, + 6, 25, 21, 27, 11, 27, 30, 31, + 1, 10, 6, 11, 6, 21, 25, 27, + 5, 21, 21, 30, 7, 23, 27, 31, + 3, 11, 7, 15, 11, 30, 27, 31, + 7, 27, 23, 31, 15, 31, 31, 63 + }; + +const unsigned int igraph_i_isoclass_3_idx[] = { 0, 4, 16, 1, 0, 32, 2, 8, 0 }; + +const unsigned int igraph_i_isoclass_4[] = { + 0, 1, 1, 3, 1, 3, 3, 7, 1, 9, 10, 11, 10, + 11, 14, 15, 1, 10, 18, 19, 20, 21, 22, 23, 3, 11, + 19, 27, 21, 29, 30, 31, 1, 10, 20, 21, 18, 19, 22, + 23, 3, 11, 21, 29, 19, 27, 30, 31, 3, 14, 22, 30, + 22, 30, 54, 55, 7, 15, 23, 31, 23, 31, 55, 63, 1, + 10, 9, 11, 10, 14, 11, 15, 18, 73, 73, 75, 76, 77, + 77, 79, 10, 81, 73, 83, 84, 85, 86, 87, 19, 83, 90, + 91, 92, 93, 94, 95, 20, 84, 98, 99, 100, 101, 102, 103, + 22, 86, 106, 107, 108, 109, 110, 111, 21, 85, 106, 115, 116, + 117, 118, 119, 23, 87, 122, 123, 124, 125, 126, 127, 1, 18, + 10, 19, 20, 22, 21, 23, 10, 73, 81, 83, 84, 86, 85, + 87, 9, 73, 73, 90, 98, 106, 106, 122, 11, 75, 83, 91, + 99, 107, 115, 123, 10, 76, 84, 92, 100, 108, 116, 124, 14, + 77, 85, 93, 101, 109, 117, 125, 11, 77, 86, 94, 102, 110, + 118, 126, 15, 79, 87, 95, 103, 111, 119, 127, 3, 19, 11, + 27, 21, 30, 29, 31, 19, 90, 83, 91, 92, 94, 93, 95, + 11, 83, 75, 91, 99, 115, 107, 123, 27, 91, 91, 219, 220, + 221, 221, 223, 21, 92, 99, 220, 228, 229, 230, 231, 30, 94, + 115, 221, 229, 237, 238, 239, 29, 93, 107, 221, 230, 238, 246, + 247, 31, 95, 123, 223, 231, 239, 247, 255, 1, 20, 10, 21, + 18, 22, 19, 23, 20, 98, 84, 99, 100, 102, 101, 103, 10, + 84, 76, 92, 100, 116, 108, 124, 21, 99, 92, 220, 228, 230, + 229, 231, 18, 100, 100, 228, 292, 293, 293, 295, 22, 102, 116, + 230, 293, 301, 302, 303, 19, 101, 108, 229, 293, 302, 310, 311, + 23, 103, 124, 231, 295, 303, 311, 319, 3, 21, 11, 29, 19, + 30, 27, 31, 22, 106, 86, 107, 108, 110, 109, 111, 14, 85, + 77, 93, 101, 117, 109, 125, 30, 115, 94, 221, 229, 238, 237, + 239, 22, 116, 102, 230, 293, 302, 301, 303, 54, 118, 118, 246, + 310, 365, 365, 367, 30, 117, 110, 238, 302, 373, 365, 375, 55, + 119, 126, 247, 311, 375, 382, 383, 3, 22, 14, 30, 22, 54, + 30, 55, 21, 106, 85, 115, 116, 118, 117, 119, 11, 86, 77, + 94, 102, 118, 110, 126, 29, 107, 93, 221, 230, 246, 238, 247, + 19, 108, 101, 229, 293, 310, 302, 311, 30, 110, 117, 238, 302, + 365, 373, 375, 27, 109, 109, 237, 301, 365, 365, 382, 31, 111, + 125, 239, 303, 367, 375, 383, 7, 23, 15, 31, 23, 55, 31, + 63, 23, 122, 87, 123, 124, 126, 125, 127, 15, 87, 79, 95, + 103, 119, 111, 127, 31, 123, 95, 223, 231, 247, 239, 255, 23, + 124, 103, 231, 295, 311, 303, 319, 55, 126, 119, 247, 311, 382, + 375, 383, 31, 125, 111, 239, 303, 375, 367, 383, 63, 127, 127, + 255, 319, 383, 383, 511, 1, 10, 10, 14, 9, 11, 11, 15, + 18, 73, 76, 77, 73, 75, 77, 79, 20, 84, 100, 101, 98, + 99, 102, 103, 22, 86, 108, 109, 106, 107, 110, 111, 10, 81, + 84, 85, 73, 83, 86, 87, 19, 83, 92, 93, 90, 91, 94, + 95, 21, 85, 116, 117, 106, 115, 118, 119, 23, 87, 124, 125, + 122, 123, 126, 127, 18, 76, 73, 77, 73, 77, 75, 79, 292, + 585, 585, 587, 585, 587, 587, 591, 100, 593, 594, 595, 596, 597, + 598, 599, 293, 601, 602, 603, 604, 605, 606, 607, 100, 593, 596, + 597, 594, 595, 598, 599, 293, 601, 604, 605, 602, 603, 606, 607, + 228, 625, 626, 627, 626, 627, 630, 631, 295, 633, 634, 635, 634, + 635, 638, 639, 20, 100, 84, 101, 98, 102, 99, 103, 100, 594, + 593, 595, 596, 598, 597, 599, 98, 596, 596, 659, 660, 661, 661, + 663, 102, 598, 666, 667, 661, 669, 670, 671, 84, 593, 674, 675, + 596, 666, 678, 679, 101, 595, 675, 683, 659, 667, 686, 687, 99, + 597, 678, 686, 661, 670, 694, 695, 103, 599, 679, 687, 663, 671, + 695, 703, 22, 108, 86, 109, 106, 110, 107, 111, 293, 602, 601, + 603, 604, 606, 605, 607, 102, 666, 598, 667, 661, 670, 669, 671, + 301, 729, 729, 731, 732, 733, 733, 735, 116, 737, 678, 739, 626, + 741, 742, 743, 302, 745, 746, 747, 748, 749, 750, 751, 230, 753, + 742, 755, 756, 757, 758, 759, 303, 761, 762, 763, 764, 765, 766, + 767, 10, 84, 81, 85, 73, 86, 83, 87, 100, 596, 593, 597, + 594, 598, 595, 599, 84, 674, 593, 675, 596, 678, 666, 679, 116, + 678, 737, 739, 626, 742, 741, 743, 76, 593, 593, 625, 585, 601, + 601, 633, 108, 666, 737, 753, 602, 729, 745, 761, 92, 675, 737, + 819, 604, 746, 822, 823, 124, 679, 826, 827, 634, 762, 830, 831, + 19, 92, 83, 93, 90, 94, 91, 95, 293, 604, 601, 605, 602, + 606, 603, 607, 101, 675, 595, 683, 659, 686, 667, 687, 302, 746, + 745, 747, 748, 750, 749, 751, 108, 737, 666, 753, 602, 745, 729, + 761, 310, 822, 822, 875, 876, 877, 877, 879, 229, 819, 741, 883, + 748, 885, 886, 887, 311, 823, 830, 891, 892, 893, 894, 895, 21, + 116, 85, 117, 106, 118, 115, 119, 228, 626, 625, 627, 626, 630, + 627, 631, 99, 678, 597, 686, 661, 694, 670, 695, 230, 742, 753, + 755, 756, 758, 757, 759, 92, 737, 675, 819, 604, 822, 746, 823, + 229, 741, 819, 883, 748, 886, 885, 887, 220, 739, 739, 947, 732, + 949, 949, 951, 231, 743, 827, 955, 764, 957, 958, 959, 23, 124, + 87, 125, 122, 126, 123, 127, 295, 634, 633, 635, 634, 638, 635, + 639, 103, 679, 599, 687, 663, 695, 671, 703, 303, 762, 761, 763, + 764, 766, 765, 767, 124, 826, 679, 827, 634, 830, 762, 831, 311, + 830, 823, 891, 892, 894, 893, 895, 231, 827, 743, 955, 764, 958, + 957, 959, 319, 831, 831, 1019, 1020, 1021, 1021, 1023, 1, 18, 20, + 22, 10, 19, 21, 23, 10, 73, 84, 86, 81, 83, 85, 87, + 10, 76, 100, 108, 84, 92, 116, 124, 14, 77, 101, 109, 85, + 93, 117, 125, 9, 73, 98, 106, 73, 90, 106, 122, 11, 75, + 99, 107, 83, 91, 115, 123, 11, 77, 102, 110, 86, 94, 118, + 126, 15, 79, 103, 111, 87, 95, 119, 127, 20, 100, 98, 102, + 84, 101, 99, 103, 100, 594, 596, 598, 593, 595, 597, 599, 84, + 593, 596, 666, 674, 675, 678, 679, 101, 595, 659, 667, 675, 683, + 686, 687, 98, 596, 660, 661, 596, 659, 661, 663, 102, 598, 661, + 669, 666, 667, 670, 671, 99, 597, 661, 670, 678, 686, 694, 695, + 103, 599, 663, 671, 679, 687, 695, 703, 18, 292, 100, 293, 100, + 293, 228, 295, 76, 585, 593, 601, 593, 601, 625, 633, 73, 585, + 594, 602, 596, 604, 626, 634, 77, 587, 595, 603, 597, 605, 627, + 635, 73, 585, 596, 604, 594, 602, 626, 634, 77, 587, 597, 605, + 595, 603, 627, 635, 75, 587, 598, 606, 598, 606, 630, 638, 79, + 591, 599, 607, 599, 607, 631, 639, 22, 293, 102, 301, 116, 302, + 230, 303, 108, 602, 666, 729, 737, 745, 753, 761, 86, 601, 598, + 729, 678, 746, 742, 762, 109, 603, 667, 731, 739, 747, 755, 763, + 106, 604, 661, 732, 626, 748, 756, 764, 110, 606, 670, 733, 741, + 749, 757, 765, 107, 605, 669, 733, 742, 750, 758, 766, 111, 607, + 671, 735, 743, 751, 759, 767, 10, 100, 84, 116, 76, 108, 92, + 124, 84, 596, 674, 678, 593, 666, 675, 679, 81, 593, 593, 737, + 593, 737, 737, 826, 85, 597, 675, 739, 625, 753, 819, 827, 73, + 594, 596, 626, 585, 602, 604, 634, 86, 598, 678, 742, 601, 729, + 746, 762, 83, 595, 666, 741, 601, 745, 822, 830, 87, 599, 679, + 743, 633, 761, 823, 831, 21, 228, 99, 230, 92, 229, 220, 231, + 116, 626, 678, 742, 737, 741, 739, 743, 85, 625, 597, 753, 675, + 819, 739, 827, 117, 627, 686, 755, 819, 883, 947, 955, 106, 626, + 661, 756, 604, 748, 732, 764, 118, 630, 694, 758, 822, 886, 949, + 957, 115, 627, 670, 757, 746, 885, 949, 958, 119, 631, 695, 759, + 823, 887, 951, 959, 19, 293, 101, 302, 108, 310, 229, 311, 92, + 604, 675, 746, 737, 822, 819, 823, 83, 601, 595, 745, 666, 822, + 741, 830, 93, 605, 683, 747, 753, 875, 883, 891, 90, 602, 659, + 748, 602, 876, 748, 892, 94, 606, 686, 750, 745, 877, 885, 893, + 91, 603, 667, 749, 729, 877, 886, 894, 95, 607, 687, 751, 761, + 879, 887, 895, 23, 295, 103, 303, 124, 311, 231, 319, 124, 634, + 679, 762, 826, 830, 827, 831, 87, 633, 599, 761, 679, 823, 743, + 831, 125, 635, 687, 763, 827, 891, 955, 1019, 122, 634, 663, 764, + 634, 892, 764, 1020, 126, 638, 695, 766, 830, 894, 958, 1021, 123, + 635, 671, 765, 762, 893, 957, 1021, 127, 639, 703, 767, 831, 895, + 959, 1023, 3, 19, 21, 30, 11, 27, 29, 31, 19, 90, 92, + 94, 83, 91, 93, 95, 21, 92, 228, 229, 99, 220, 230, 231, + 30, 94, 229, 237, 115, 221, 238, 239, 11, 83, 99, 115, 75, + 91, 107, 123, 27, 91, 220, 221, 91, 219, 221, 223, 29, 93, + 230, 238, 107, 221, 246, 247, 31, 95, 231, 239, 123, 223, 247, + 255, 22, 108, 106, 110, 86, 109, 107, 111, 293, 602, 604, 606, + 601, 603, 605, 607, 116, 737, 626, 741, 678, 739, 742, 743, 302, + 745, 748, 749, 746, 747, 750, 751, 102, 666, 661, 670, 598, 667, + 669, 671, 301, 729, 732, 733, 729, 731, 733, 735, 230, 753, 756, + 757, 742, 755, 758, 759, 303, 761, 764, 765, 762, 763, 766, 767, + 22, 293, 116, 302, 102, 301, 230, 303, 108, 602, 737, 745, 666, + 729, 753, 761, 106, 604, 626, 748, 661, 732, 756, 764, 110, 606, + 741, 749, 670, 733, 757, 765, 86, 601, 678, 746, 598, 729, 742, + 762, 109, 603, 739, 747, 667, 731, 755, 763, 107, 605, 742, 750, + 669, 733, 758, 766, 111, 607, 743, 751, 671, 735, 759, 767, 54, + 310, 118, 365, 118, 365, 246, 367, 310, 876, 822, 877, 822, 877, + 875, 879, 118, 822, 630, 886, 694, 949, 758, 957, 365, 877, 886, + 1755, 949, 1757, 1758, 1759, 118, 822, 694, 949, 630, 886, 758, 957, + 365, 877, 949, 1757, 886, 1755, 1758, 1759, 246, 875, 758, 1758, 758, + 1758, 1782, 1783, 367, 879, 957, 1759, 957, 1759, 1783, 1791, 14, 101, + 85, 117, 77, 109, 93, 125, 101, 659, 675, 686, 595, 667, 683, + 687, 85, 675, 625, 819, 597, 739, 753, 827, 117, 686, 819, 947, + 627, 755, 883, 955, 77, 595, 597, 627, 587, 603, 605, 635, 109, + 667, 739, 755, 603, 731, 747, 763, 93, 683, 753, 883, 605, 747, + 875, 891, 125, 687, 827, 955, 635, 763, 891, 1019, 30, 229, 115, + 238, 94, 237, 221, 239, 302, 748, 746, 750, 745, 749, 747, 751, + 117, 819, 627, 883, 686, 947, 755, 955, 373, 885, 885, 1883, 885, + 1883, 1883, 1887, 110, 741, 670, 757, 606, 749, 733, 765, 365, 886, + 949, 1758, 877, 1755, 1757, 1759, 238, 883, 757, 1907, 750, 1883, 1758, + 1911, 375, 887, 958, 1911, 893, 1917, 1918, 1919, 30, 302, 117, 373, + 110, 365, 238, 375, 229, 748, 819, 885, 741, 886, 883, 887, 115, + 746, 627, 885, 670, 949, 757, 958, 238, 750, 883, 1883, 757, 1758, + 1907, 1911, 94, 745, 686, 885, 606, 877, 750, 893, 237, 749, 947, + 1883, 749, 1755, 1883, 1917, 221, 747, 755, 1883, 733, 1757, 1758, 1918, + 239, 751, 955, 1887, 765, 1759, 1911, 1919, 55, 311, 119, 375, 126, + 382, 247, 383, 311, 892, 823, 893, 830, 894, 891, 895, 119, 823, + 631, 887, 695, 951, 759, 959, 375, 893, 887, 1917, 958, 1918, 1911, + 1919, 126, 830, 695, 958, 638, 894, 766, 1021, 382, 894, 951, 1918, + 894, 2029, 1918, 2031, 247, 891, 759, 1911, 766, 1918, 1783, 2039, 383, + 895, 959, 1919, 1021, 2031, 2039, 2047, 1, 20, 18, 22, 10, 21, + 19, 23, 20, 98, 100, 102, 84, 99, 101, 103, 18, 100, 292, + 293, 100, 228, 293, 295, 22, 102, 293, 301, 116, 230, 302, 303, + 10, 84, 100, 116, 76, 92, 108, 124, 21, 99, 228, 230, 92, + 220, 229, 231, 19, 101, 293, 302, 108, 229, 310, 311, 23, 103, + 295, 303, 124, 231, 311, 319, 10, 84, 73, 86, 81, 85, 83, + 87, 100, 596, 594, 598, 593, 597, 595, 599, 76, 593, 585, 601, + 593, 625, 601, 633, 108, 666, 602, 729, 737, 753, 745, 761, 84, + 674, 596, 678, 593, 675, 666, 679, 116, 678, 626, 742, 737, 739, + 741, 743, 92, 675, 604, 746, 737, 819, 822, 823, 124, 679, 634, + 762, 826, 827, 830, 831, 10, 100, 76, 108, 84, 116, 92, 124, + 84, 596, 593, 666, 674, 678, 675, 679, 73, 594, 585, 602, 596, + 626, 604, 634, 86, 598, 601, 729, 678, 742, 746, 762, 81, 593, + 593, 737, 593, 737, 737, 826, 85, 597, 625, 753, 675, 739, 819, + 827, 83, 595, 601, 745, 666, 741, 822, 830, 87, 599, 633, 761, + 679, 743, 823, 831, 14, 101, 77, 109, 85, 117, 93, 125, 101, + 659, 595, 667, 675, 686, 683, 687, 77, 595, 587, 603, 597, 627, + 605, 635, 109, 667, 603, 731, 739, 755, 747, 763, 85, 675, 597, + 739, 625, 819, 753, 827, 117, 686, 627, 755, 819, 947, 883, 955, + 93, 683, 605, 747, 753, 883, 875, 891, 125, 687, 635, 763, 827, + 955, 891, 1019, 9, 98, 73, 106, 73, 106, 90, 122, 98, 660, + 596, 661, 596, 661, 659, 663, 73, 596, 585, 604, 594, 626, 602, + 634, 106, 661, 604, 732, 626, 756, 748, 764, 73, 596, 594, 626, + 585, 604, 602, 634, 106, 661, 626, 756, 604, 732, 748, 764, 90, + 659, 602, 748, 602, 748, 876, 892, 122, 663, 634, 764, 634, 764, + 892, 1020, 11, 99, 75, 107, 83, 115, 91, 123, 102, 661, 598, + 669, 666, 670, 667, 671, 77, 597, 587, 605, 595, 627, 603, 635, + 110, 670, 606, 733, 741, 757, 749, 765, 86, 678, 598, 742, 601, + 746, 729, 762, 118, 694, 630, 758, 822, 949, 886, 957, 94, 686, + 606, 750, 745, 885, 877, 893, 126, 695, 638, 766, 830, 958, 894, + 1021, 11, 102, 77, 110, 86, 118, 94, 126, 99, 661, 597, 670, + 678, 694, 686, 695, 75, 598, 587, 606, 598, 630, 606, 638, 107, + 669, 605, 733, 742, 758, 750, 766, 83, 666, 595, 741, 601, 822, + 745, 830, 115, 670, 627, 757, 746, 949, 885, 958, 91, 667, 603, + 749, 729, 886, 877, 894, 123, 671, 635, 765, 762, 957, 893, 1021, + 15, 103, 79, 111, 87, 119, 95, 127, 103, 663, 599, 671, 679, + 695, 687, 703, 79, 599, 591, 607, 599, 631, 607, 639, 111, 671, + 607, 735, 743, 759, 751, 767, 87, 679, 599, 743, 633, 823, 761, + 831, 119, 695, 631, 759, 823, 951, 887, 959, 95, 687, 607, 751, + 761, 887, 879, 895, 127, 703, 639, 767, 831, 959, 895, 1023, 3, + 21, 19, 30, 11, 29, 27, 31, 22, 106, 108, 110, 86, 107, + 109, 111, 22, 116, 293, 302, 102, 230, 301, 303, 54, 118, 310, + 365, 118, 246, 365, 367, 14, 85, 101, 117, 77, 93, 109, 125, + 30, 115, 229, 238, 94, 221, 237, 239, 30, 117, 302, 373, 110, + 238, 365, 375, 55, 119, 311, 375, 126, 247, 382, 383, 19, 92, + 90, 94, 83, 93, 91, 95, 293, 604, 602, 606, 601, 605, 603, + 607, 108, 737, 602, 745, 666, 753, 729, 761, 310, 822, 876, 877, + 822, 875, 877, 879, 101, 675, 659, 686, 595, 683, 667, 687, 302, + 746, 748, 750, 745, 747, 749, 751, 229, 819, 748, 885, 741, 883, + 886, 887, 311, 823, 892, 893, 830, 891, 894, 895, 21, 228, 92, + 229, 99, 230, 220, 231, 116, 626, 737, 741, 678, 742, 739, 743, + 106, 626, 604, 748, 661, 756, 732, 764, 118, 630, 822, 886, 694, + 758, 949, 957, 85, 625, 675, 819, 597, 753, 739, 827, 117, 627, + 819, 883, 686, 755, 947, 955, 115, 627, 746, 885, 670, 757, 949, + 958, 119, 631, 823, 887, 695, 759, 951, 959, 30, 229, 94, 237, + 115, 238, 221, 239, 302, 748, 745, 749, 746, 750, 747, 751, 110, + 741, 606, 749, 670, 757, 733, 765, 365, 886, 877, 1755, 949, 1758, + 1757, 1759, 117, 819, 686, 947, 627, 883, 755, 955, 373, 885, 885, + 1883, 885, 1883, 1883, 1887, 238, 883, 750, 1883, 757, 1907, 1758, 1911, + 375, 887, 893, 1917, 958, 1911, 1918, 1919, 11, 99, 83, 115, 75, + 107, 91, 123, 102, 661, 666, 670, 598, 669, 667, 671, 86, 678, + 601, 746, 598, 742, 729, 762, 118, 694, 822, 949, 630, 758, 886, + 957, 77, 597, 595, 627, 587, 605, 603, 635, 110, 670, 741, 757, + 606, 733, 749, 765, 94, 686, 745, 885, 606, 750, 877, 893, 126, + 695, 830, 958, 638, 766, 894, 1021, 27, 220, 91, 221, 91, 221, + 219, 223, 301, 732, 729, 733, 729, 733, 731, 735, 109, 739, 603, + 747, 667, 755, 731, 763, 365, 949, 877, 1757, 886, 1758, 1755, 1759, + 109, 739, 667, 755, 603, 747, 731, 763, 365, 949, 886, 1758, 877, + 1757, 1755, 1759, 237, 947, 749, 1883, 749, 1883, 1755, 1917, 382, 951, + 894, 1918, 894, 1918, 2029, 2031, 29, 230, 93, 238, 107, 246, 221, + 247, 230, 756, 753, 757, 742, 758, 755, 759, 107, 742, 605, 750, + 669, 758, 733, 766, 246, 758, 875, 1758, 758, 1782, 1758, 1783, 93, + 753, 683, 883, 605, 875, 747, 891, 238, 757, 883, 1907, 750, 1758, + 1883, 1911, 221, 755, 747, 1883, 733, 1758, 1757, 1918, 247, 759, 891, + 1911, 766, 1783, 1918, 2039, 31, 231, 95, 239, 123, 247, 223, 255, + 303, 764, 761, 765, 762, 766, 763, 767, 111, 743, 607, 751, 671, + 759, 735, 767, 367, 957, 879, 1759, 957, 1783, 1759, 1791, 125, 827, + 687, 955, 635, 891, 763, 1019, 375, 958, 887, 1911, 893, 1918, 1917, + 1919, 239, 955, 751, 1887, 765, 1911, 1759, 1919, 383, 959, 895, 1919, + 1021, 2039, 2031, 2047, 3, 22, 22, 54, 14, 30, 30, 55, 21, + 106, 116, 118, 85, 115, 117, 119, 19, 108, 293, 310, 101, 229, + 302, 311, 30, 110, 302, 365, 117, 238, 373, 375, 11, 86, 102, + 118, 77, 94, 110, 126, 29, 107, 230, 246, 93, 221, 238, 247, + 27, 109, 301, 365, 109, 237, 365, 382, 31, 111, 303, 367, 125, + 239, 375, 383, 21, 116, 106, 118, 85, 117, 115, 119, 228, 626, + 626, 630, 625, 627, 627, 631, 92, 737, 604, 822, 675, 819, 746, + 823, 229, 741, 748, 886, 819, 883, 885, 887, 99, 678, 661, 694, + 597, 686, 670, 695, 230, 742, 756, 758, 753, 755, 757, 759, 220, + 739, 732, 949, 739, 947, 949, 951, 231, 743, 764, 957, 827, 955, + 958, 959, 19, 293, 108, 310, 101, 302, 229, 311, 92, 604, 737, + 822, 675, 746, 819, 823, 90, 602, 602, 876, 659, 748, 748, 892, + 94, 606, 745, 877, 686, 750, 885, 893, 83, 601, 666, 822, 595, + 745, 741, 830, 93, 605, 753, 875, 683, 747, 883, 891, 91, 603, + 729, 877, 667, 749, 886, 894, 95, 607, 761, 879, 687, 751, 887, + 895, 30, 302, 110, 365, 117, 373, 238, 375, 229, 748, 741, 886, + 819, 885, 883, 887, 94, 745, 606, 877, 686, 885, 750, 893, 237, + 749, 749, 1755, 947, 1883, 1883, 1917, 115, 746, 670, 949, 627, 885, + 757, 958, 238, 750, 757, 1758, 883, 1883, 1907, 1911, 221, 747, 733, + 1757, 755, 1883, 1758, 1918, 239, 751, 765, 1759, 955, 1887, 1911, 1919, + 11, 102, 86, 118, 77, 110, 94, 126, 99, 661, 678, 694, 597, + 670, 686, 695, 83, 666, 601, 822, 595, 741, 745, 830, 115, 670, + 746, 949, 627, 757, 885, 958, 75, 598, 598, 630, 587, 606, 606, + 638, 107, 669, 742, 758, 605, 733, 750, 766, 91, 667, 729, 886, + 603, 749, 877, 894, 123, 671, 762, 957, 635, 765, 893, 1021, 29, + 230, 107, 246, 93, 238, 221, 247, 230, 756, 742, 758, 753, 757, + 755, 759, 93, 753, 605, 875, 683, 883, 747, 891, 238, 757, 750, + 1758, 883, 1907, 1883, 1911, 107, 742, 669, 758, 605, 750, 733, 766, + 246, 758, 758, 1782, 875, 1758, 1758, 1783, 221, 755, 733, 1758, 747, + 1883, 1757, 1918, 247, 759, 766, 1783, 891, 1911, 1918, 2039, 27, 301, + 109, 365, 109, 365, 237, 382, 220, 732, 739, 949, 739, 949, 947, + 951, 91, 729, 603, 877, 667, 886, 749, 894, 221, 733, 747, 1757, + 755, 1758, 1883, 1918, 91, 729, 667, 886, 603, 877, 749, 894, 221, + 733, 755, 1758, 747, 1757, 1883, 1918, 219, 731, 731, 1755, 731, 1755, + 1755, 2029, 223, 735, 763, 1759, 763, 1759, 1917, 2031, 31, 303, 111, + 367, 125, 375, 239, 383, 231, 764, 743, 957, 827, 958, 955, 959, + 95, 761, 607, 879, 687, 887, 751, 895, 239, 765, 751, 1759, 955, + 1911, 1887, 1919, 123, 762, 671, 957, 635, 893, 765, 1021, 247, 766, + 759, 1783, 891, 1918, 1911, 2039, 223, 763, 735, 1759, 763, 1917, 1759, + 2031, 255, 767, 767, 1791, 1019, 1919, 1919, 2047, 7, 23, 23, 55, + 15, 31, 31, 63, 23, 122, 124, 126, 87, 123, 125, 127, 23, + 124, 295, 311, 103, 231, 303, 319, 55, 126, 311, 382, 119, 247, + 375, 383, 15, 87, 103, 119, 79, 95, 111, 127, 31, 123, 231, + 247, 95, 223, 239, 255, 31, 125, 303, 375, 111, 239, 367, 383, + 63, 127, 319, 383, 127, 255, 383, 511, 23, 124, 122, 126, 87, + 125, 123, 127, 295, 634, 634, 638, 633, 635, 635, 639, 124, 826, + 634, 830, 679, 827, 762, 831, 311, 830, 892, 894, 823, 891, 893, + 895, 103, 679, 663, 695, 599, 687, 671, 703, 303, 762, 764, 766, + 761, 763, 765, 767, 231, 827, 764, 958, 743, 955, 957, 959, 319, + 831, 1020, 1021, 831, 1019, 1021, 1023, 23, 295, 124, 311, 103, 303, + 231, 319, 124, 634, 826, 830, 679, 762, 827, 831, 122, 634, 634, + 892, 663, 764, 764, 1020, 126, 638, 830, 894, 695, 766, 958, 1021, + 87, 633, 679, 823, 599, 761, 743, 831, 125, 635, 827, 891, 687, + 763, 955, 1019, 123, 635, 762, 893, 671, 765, 957, 1021, 127, 639, + 831, 895, 703, 767, 959, 1023, 55, 311, 126, 382, 119, 375, 247, + 383, 311, 892, 830, 894, 823, 893, 891, 895, 126, 830, 638, 894, + 695, 958, 766, 1021, 382, 894, 894, 2029, 951, 1918, 1918, 2031, 119, + 823, 695, 951, 631, 887, 759, 959, 375, 893, 958, 1918, 887, 1917, + 1911, 1919, 247, 891, 766, 1918, 759, 1911, 1783, 2039, 383, 895, 1021, + 2031, 959, 1919, 2039, 2047, 15, 103, 87, 119, 79, 111, 95, 127, + 103, 663, 679, 695, 599, 671, 687, 703, 87, 679, 633, 823, 599, + 743, 761, 831, 119, 695, 823, 951, 631, 759, 887, 959, 79, 599, + 599, 631, 591, 607, 607, 639, 111, 671, 743, 759, 607, 735, 751, + 767, 95, 687, 761, 887, 607, 751, 879, 895, 127, 703, 831, 959, + 639, 767, 895, 1023, 31, 231, 123, 247, 95, 239, 223, 255, 303, + 764, 762, 766, 761, 765, 763, 767, 125, 827, 635, 891, 687, 955, + 763, 1019, 375, 958, 893, 1918, 887, 1911, 1917, 1919, 111, 743, 671, + 759, 607, 751, 735, 767, 367, 957, 957, 1783, 879, 1759, 1759, 1791, + 239, 955, 765, 1911, 751, 1887, 1759, 1919, 383, 959, 1021, 2039, 895, + 1919, 2031, 2047, 31, 303, 125, 375, 111, 367, 239, 383, 231, 764, + 827, 958, 743, 957, 955, 959, 123, 762, 635, 893, 671, 957, 765, + 1021, 247, 766, 891, 1918, 759, 1783, 1911, 2039, 95, 761, 687, 887, + 607, 879, 751, 895, 239, 765, 955, 1911, 751, 1759, 1887, 1919, 223, + 763, 763, 1917, 735, 1759, 1759, 2031, 255, 767, 1019, 1919, 767, 1791, + 1919, 2047, 63, 319, 127, 383, 127, 383, 255, 511, 319, 1020, 831, + 1021, 831, 1021, 1019, 1023, 127, 831, 639, 895, 703, 959, 767, 1023, + 383, 1021, 895, 2031, 959, 2039, 1919, 2047, 127, 831, 703, 959, 639, + 895, 767, 1023, 383, 1021, 959, 2039, 895, 2031, 1919, 2047, 255, 1019, + 767, 1919, 767, 1919, 1791, 2047, 511, 1023, 1023, 2047, 1023, 2047, 2047, + 4095 +}; + +const unsigned int igraph_i_isoclass_4_idx[] = { + 0, 8, 64, 512, 1, 0, 128, 1024, 2, 16, 0, 2048, 4, 32, 256, 0 +}; + +const unsigned int igraph_i_isoclass_3u[] = { 0, 1, 1, 3, 1, 3, 3, 7 }; + +const unsigned int igraph_i_isoclass_3u_idx[] = { 0, 1, 2, 1, 0, 4, 2, 4, 0 }; + +const unsigned int igraph_i_isoclass_4u[] = { + 0, 1, 1, 3, 1, 3, 3, 7, 1, 3, 3, 11, 12, 13, 13, 15, 1, 3, 12, 13, 3, 11, 13, 15, 3, 7, + 13, 15, 13, 15, 30, 31, 1, 12, 3, 13, 3, 13, 11, 15, 3, 13, 7, 15, 13, 30, 15, 31, 3, 13, 13, 30, + 7, 15, 15, 31, 11, 15, 15, 31, 15, 31, 31, 63 +}; + +const unsigned int igraph_i_isoclass_4u_idx[] = { + 0, 1, 2, 8, 1, 0, 4, 16, 2, 4, 0, 32, 8, 16, 32, 0 +}; + +const unsigned int igraph_i_isoclass2_3[] = { + 0, 1, 1, 2, 1, 3, 4, 5, 1, 4, 6, 7, 2, 5, 7, 8, 1, 4, 3, 5, 6, 9, 9, 10, 4, 11, + 9, 12, 7, 12, 13, 14, 1, 6, 4, 7, 4, 9, 11, 12, 3, 9, 9, 13, 5, 10, 12, 14, 2, 7, 5, 8, + 7, 13, 12, 14, 5, 12, 10, 14, 8, 14, 14, 15 +}; + +const unsigned int igraph_i_isoclass2_3u[] = { + 0, 1, 1, 2, 1, 2, 2, 3 +}; + +const unsigned int igraph_i_isoclass2_4u[] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 4, 5, 6, 6, 7, 1, 2, 5, 6, 2, 4, 6, 7, 2, 3, + 6, 7, 6, 7, 8, 9, 1, 5, 2, 6, 2, 6, 4, 7, 2, 6, 3, 7, 6, 8, 7, 9, 2, 6, 6, 8, + 3, 7, 7, 9, 4, 7, 7, 9, 7, 9, 9, 10 +}; + +const unsigned int igraph_i_isoclass2_4[] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 4, 5, 6, 5, 6, 7, 8, 1, 5, 9, 10, + 11, 12, 13, 14, 2, 6, 10, 15, 12, 16, 17, 18, 1, 5, 11, 12, 9, 10, 13, 14, + 2, 6, 12, 16, 10, 15, 17, 18, 2, 7, 13, 17, 13, 17, 19, 20, 3, 8, 14, 18, + 14, 18, 20, 21, 1, 5, 4, 6, 5, 7, 6, 8, 9, 22, 22, 23, 24, 25, 25, 26, + 5, 27, 22, 28, 29, 30, 31, 32, 10, 28, 33, 34, 35, 36, 37, 38, 11, 29, 39, 40, + 41, 42, 43, 44, 13, 31, 45, 46, 47, 48, 49, 50, 12, 30, 45, 51, 52, 53, 54, 55, + 14, 32, 56, 57, 58, 59, 60, 61, 1, 9, 5, 10, 11, 13, 12, 14, 5, 22, 27, 28, + 29, 31, 30, 32, 4, 22, 22, 33, 39, 45, 45, 56, 6, 23, 28, 34, 40, 46, 51, 57, + 5, 24, 29, 35, 41, 47, 52, 58, 7, 25, 30, 36, 42, 48, 53, 59, 6, 25, 31, 37, + 43, 49, 54, 60, 8, 26, 32, 38, 44, 50, 55, 61, 2, 10, 6, 15, 12, 17, 16, 18, + 10, 33, 28, 34, 35, 37, 36, 38, 6, 28, 23, 34, 40, 51, 46, 57, 15, 34, 34, 62, + 63, 64, 64, 65, 12, 35, 40, 63, 66, 67, 68, 69, 17, 37, 51, 64, 67, 70, 71, 72, + 16, 36, 46, 64, 68, 71, 73, 74, 18, 38, 57, 65, 69, 72, 74, 75, 1, 11, 5, 12, + 9, 13, 10, 14, 11, 39, 29, 40, 41, 43, 42, 44, 5, 29, 24, 35, 41, 52, 47, 58, + 12, 40, 35, 63, 66, 68, 67, 69, 9, 41, 41, 66, 76, 77, 77, 78, 13, 43, 52, 68, + 77, 79, 80, 81, 10, 42, 47, 67, 77, 80, 82, 83, 14, 44, 58, 69, 78, 81, 83, 84, + 2, 12, 6, 16, 10, 17, 15, 18, 13, 45, 31, 46, 47, 49, 48, 50, 7, 30, 25, 36, + 42, 53, 48, 59, 17, 51, 37, 64, 67, 71, 70, 72, 13, 52, 43, 68, 77, 80, 79, 81, + 19, 54, 54, 73, 82, 85, 85, 86, 17, 53, 49, 71, 80, 87, 85, 88, 20, 55, 60, 74, + 83, 88, 89, 90, 2, 13, 7, 17, 13, 19, 17, 20, 12, 45, 30, 51, 52, 54, 53, 55, + 6, 31, 25, 37, 43, 54, 49, 60, 16, 46, 36, 64, 68, 73, 71, 74, 10, 47, 42, 67, + 77, 82, 80, 83, 17, 49, 53, 71, 80, 85, 87, 88, 15, 48, 48, 70, 79, 85, 85, 89, + 18, 50, 59, 72, 81, 86, 88, 90, 3, 14, 8, 18, 14, 20, 18, 21, 14, 56, 32, 57, + 58, 60, 59, 61, 8, 32, 26, 38, 44, 55, 50, 61, 18, 57, 38, 65, 69, 74, 72, 75, + 14, 58, 44, 69, 78, 83, 81, 84, 20, 60, 55, 74, 83, 89, 88, 90, 18, 59, 50, 72, + 81, 88, 86, 90, 21, 61, 61, 75, 84, 90, 90, 91, 1, 5, 5, 7, 4, 6, 6, 8, + 9, 22, 24, 25, 22, 23, 25, 26, 11, 29, 41, 42, 39, 40, 43, 44, 13, 31, 47, 48, + 45, 46, 49, 50, 5, 27, 29, 30, 22, 28, 31, 32, 10, 28, 35, 36, 33, 34, 37, 38, + 12, 30, 52, 53, 45, 51, 54, 55, 14, 32, 58, 59, 56, 57, 60, 61, 9, 24, 22, 25, + 22, 25, 23, 26, 76, 92, 92, 93, 92, 93, 93, 94, 41, 95, 96, 97, 98, 99, 100, 101, + 77, 102, 103, 104, 105, 106, 107, 108, 41, 95, 98, 99, 96, 97, 100, 101, 77, 102, 105, 106, + 103, 104, 107, 108, 66, 109, 110, 111, 110, 111, 112, 113, 78, 114, 115, 116, 115, 116, 117, 118, + 11, 41, 29, 42, 39, 43, 40, 44, 41, 96, 95, 97, 98, 100, 99, 101, 39, 98, 98, 119, + 120, 121, 121, 122, 43, 100, 123, 124, 121, 125, 126, 127, 29, 95, 128, 129, 98, 123, 130, 131, + 42, 97, 129, 132, 119, 124, 133, 134, 40, 99, 130, 133, 121, 126, 135, 136, 44, 101, 131, 134, + 122, 127, 136, 137, 13, 47, 31, 48, 45, 49, 46, 50, 77, 103, 102, 104, 105, 107, 106, 108, + 43, 123, 100, 124, 121, 126, 125, 127, 79, 138, 138, 139, 140, 141, 141, 142, 52, 143, 130, 144, + 110, 145, 146, 147, 80, 148, 149, 150, 151, 152, 153, 154, 68, 155, 146, 156, 157, 158, 159, 160, + 81, 161, 162, 163, 164, 165, 166, 167, 5, 29, 27, 30, 22, 31, 28, 32, 41, 98, 95, 99, + 96, 100, 97, 101, 29, 128, 95, 129, 98, 130, 123, 131, 52, 130, 143, 144, 110, 146, 145, 147, + 24, 95, 95, 109, 92, 102, 102, 114, 47, 123, 143, 155, 103, 138, 148, 161, 35, 129, 143, 168, + 105, 149, 169, 170, 58, 131, 171, 172, 115, 162, 173, 174, 10, 35, 28, 36, 33, 37, 34, 38, + 77, 105, 102, 106, 103, 107, 104, 108, 42, 129, 97, 132, 119, 133, 124, 134, 80, 149, 148, 150, + 151, 153, 152, 154, 47, 143, 123, 155, 103, 148, 138, 161, 82, 169, 169, 175, 176, 177, 177, 178, + 67, 168, 145, 179, 151, 180, 181, 182, 83, 170, 173, 183, 184, 185, 186, 187, 12, 52, 30, 53, + 45, 54, 51, 55, 66, 110, 109, 111, 110, 112, 111, 113, 40, 130, 99, 133, 121, 135, 126, 136, + 68, 146, 155, 156, 157, 159, 158, 160, 35, 143, 129, 168, 105, 169, 149, 170, 67, 145, 168, 179, + 151, 181, 180, 182, 63, 144, 144, 188, 140, 189, 189, 190, 69, 147, 172, 191, 164, 192, 193, 194, + 14, 58, 32, 59, 56, 60, 57, 61, 78, 115, 114, 116, 115, 117, 116, 118, 44, 131, 101, 134, + 122, 136, 127, 137, 81, 162, 161, 163, 164, 166, 165, 167, 58, 171, 131, 172, 115, 173, 162, 174, + 83, 173, 170, 183, 184, 186, 185, 187, 69, 172, 147, 191, 164, 193, 192, 194, 84, 174, 174, 195, + 196, 197, 197, 198, 1, 9, 11, 13, 5, 10, 12, 14, 5, 22, 29, 31, 27, 28, 30, 32, + 5, 24, 41, 47, 29, 35, 52, 58, 7, 25, 42, 48, 30, 36, 53, 59, 4, 22, 39, 45, + 22, 33, 45, 56, 6, 23, 40, 46, 28, 34, 51, 57, 6, 25, 43, 49, 31, 37, 54, 60, + 8, 26, 44, 50, 32, 38, 55, 61, 11, 41, 39, 43, 29, 42, 40, 44, 41, 96, 98, 100, + 95, 97, 99, 101, 29, 95, 98, 123, 128, 129, 130, 131, 42, 97, 119, 124, 129, 132, 133, 134, + 39, 98, 120, 121, 98, 119, 121, 122, 43, 100, 121, 125, 123, 124, 126, 127, 40, 99, 121, 126, + 130, 133, 135, 136, 44, 101, 122, 127, 131, 134, 136, 137, 9, 76, 41, 77, 41, 77, 66, 78, + 24, 92, 95, 102, 95, 102, 109, 114, 22, 92, 96, 103, 98, 105, 110, 115, 25, 93, 97, 104, + 99, 106, 111, 116, 22, 92, 98, 105, 96, 103, 110, 115, 25, 93, 99, 106, 97, 104, 111, 116, + 23, 93, 100, 107, 100, 107, 112, 117, 26, 94, 101, 108, 101, 108, 113, 118, 13, 77, 43, 79, + 52, 80, 68, 81, 47, 103, 123, 138, 143, 148, 155, 161, 31, 102, 100, 138, 130, 149, 146, 162, + 48, 104, 124, 139, 144, 150, 156, 163, 45, 105, 121, 140, 110, 151, 157, 164, 49, 107, 126, 141, + 145, 152, 158, 165, 46, 106, 125, 141, 146, 153, 159, 166, 50, 108, 127, 142, 147, 154, 160, 167, + 5, 41, 29, 52, 24, 47, 35, 58, 29, 98, 128, 130, 95, 123, 129, 131, 27, 95, 95, 143, + 95, 143, 143, 171, 30, 99, 129, 144, 109, 155, 168, 172, 22, 96, 98, 110, 92, 103, 105, 115, + 31, 100, 130, 146, 102, 138, 149, 162, 28, 97, 123, 145, 102, 148, 169, 173, 32, 101, 131, 147, + 114, 161, 170, 174, 12, 66, 40, 68, 35, 67, 63, 69, 52, 110, 130, 146, 143, 145, 144, 147, + 30, 109, 99, 155, 129, 168, 144, 172, 53, 111, 133, 156, 168, 179, 188, 191, 45, 110, 121, 157, + 105, 151, 140, 164, 54, 112, 135, 159, 169, 181, 189, 192, 51, 111, 126, 158, 149, 180, 189, 193, + 55, 113, 136, 160, 170, 182, 190, 194, 10, 77, 42, 80, 47, 82, 67, 83, 35, 105, 129, 149, + 143, 169, 168, 170, 28, 102, 97, 148, 123, 169, 145, 173, 36, 106, 132, 150, 155, 175, 179, 183, + 33, 103, 119, 151, 103, 176, 151, 184, 37, 107, 133, 153, 148, 177, 180, 185, 34, 104, 124, 152, + 138, 177, 181, 186, 38, 108, 134, 154, 161, 178, 182, 187, 14, 78, 44, 81, 58, 83, 69, 84, + 58, 115, 131, 162, 171, 173, 172, 174, 32, 114, 101, 161, 131, 170, 147, 174, 59, 116, 134, 163, + 172, 183, 191, 195, 56, 115, 122, 164, 115, 184, 164, 196, 60, 117, 136, 166, 173, 186, 193, 197, + 57, 116, 127, 165, 162, 185, 192, 197, 61, 118, 137, 167, 174, 187, 194, 198, 2, 10, 12, 17, + 6, 15, 16, 18, 10, 33, 35, 37, 28, 34, 36, 38, 12, 35, 66, 67, 40, 63, 68, 69, + 17, 37, 67, 70, 51, 64, 71, 72, 6, 28, 40, 51, 23, 34, 46, 57, 15, 34, 63, 64, + 34, 62, 64, 65, 16, 36, 68, 71, 46, 64, 73, 74, 18, 38, 69, 72, 57, 65, 74, 75, + 13, 47, 45, 49, 31, 48, 46, 50, 77, 103, 105, 107, 102, 104, 106, 108, 52, 143, 110, 145, + 130, 144, 146, 147, 80, 148, 151, 152, 149, 150, 153, 154, 43, 123, 121, 126, 100, 124, 125, 127, + 79, 138, 140, 141, 138, 139, 141, 142, 68, 155, 157, 158, 146, 156, 159, 160, 81, 161, 164, 165, + 162, 163, 166, 167, 13, 77, 52, 80, 43, 79, 68, 81, 47, 103, 143, 148, 123, 138, 155, 161, + 45, 105, 110, 151, 121, 140, 157, 164, 49, 107, 145, 152, 126, 141, 158, 165, 31, 102, 130, 149, + 100, 138, 146, 162, 48, 104, 144, 150, 124, 139, 156, 163, 46, 106, 146, 153, 125, 141, 159, 166, + 50, 108, 147, 154, 127, 142, 160, 167, 19, 82, 54, 85, 54, 85, 73, 86, 82, 176, 169, 177, + 169, 177, 175, 178, 54, 169, 112, 181, 135, 189, 159, 192, 85, 177, 181, 199, 189, 200, 201, 202, + 54, 169, 135, 189, 112, 181, 159, 192, 85, 177, 189, 200, 181, 199, 201, 202, 73, 175, 159, 201, + 159, 201, 203, 204, 86, 178, 192, 202, 192, 202, 204, 205, 7, 42, 30, 53, 25, 48, 36, 59, + 42, 119, 129, 133, 97, 124, 132, 134, 30, 129, 109, 168, 99, 144, 155, 172, 53, 133, 168, 188, + 111, 156, 179, 191, 25, 97, 99, 111, 93, 104, 106, 116, 48, 124, 144, 156, 104, 139, 150, 163, + 36, 132, 155, 179, 106, 150, 175, 183, 59, 134, 172, 191, 116, 163, 183, 195, 17, 67, 51, 71, + 37, 70, 64, 72, 80, 151, 149, 153, 148, 152, 150, 154, 53, 168, 111, 179, 133, 188, 156, 191, + 87, 180, 180, 206, 180, 206, 206, 207, 49, 145, 126, 158, 107, 152, 141, 165, 85, 181, 189, 201, + 177, 199, 200, 202, 71, 179, 158, 208, 153, 206, 201, 209, 88, 182, 193, 209, 185, 210, 211, 212, + 17, 80, 53, 87, 49, 85, 71, 88, 67, 151, 168, 180, 145, 181, 179, 182, 51, 149, 111, 180, + 126, 189, 158, 193, 71, 153, 179, 206, 158, 201, 208, 209, 37, 148, 133, 180, 107, 177, 153, 185, + 70, 152, 188, 206, 152, 199, 206, 210, 64, 150, 156, 206, 141, 200, 201, 211, 72, 154, 191, 207, + 165, 202, 209, 212, 20, 83, 55, 88, 60, 89, 74, 90, 83, 184, 170, 185, 173, 186, 183, 187, + 55, 170, 113, 182, 136, 190, 160, 194, 88, 185, 182, 210, 193, 211, 209, 212, 60, 173, 136, 193, + 117, 186, 166, 197, 89, 186, 190, 211, 186, 213, 211, 214, 74, 183, 160, 209, 166, 211, 204, 215, + 90, 187, 194, 212, 197, 214, 215, 216, 1, 11, 9, 13, 5, 12, 10, 14, 11, 39, 41, 43, + 29, 40, 42, 44, 9, 41, 76, 77, 41, 66, 77, 78, 13, 43, 77, 79, 52, 68, 80, 81, + 5, 29, 41, 52, 24, 35, 47, 58, 12, 40, 66, 68, 35, 63, 67, 69, 10, 42, 77, 80, + 47, 67, 82, 83, 14, 44, 78, 81, 58, 69, 83, 84, 5, 29, 22, 31, 27, 30, 28, 32, + 41, 98, 96, 100, 95, 99, 97, 101, 24, 95, 92, 102, 95, 109, 102, 114, 47, 123, 103, 138, + 143, 155, 148, 161, 29, 128, 98, 130, 95, 129, 123, 131, 52, 130, 110, 146, 143, 144, 145, 147, + 35, 129, 105, 149, 143, 168, 169, 170, 58, 131, 115, 162, 171, 172, 173, 174, 5, 41, 24, 47, + 29, 52, 35, 58, 29, 98, 95, 123, 128, 130, 129, 131, 22, 96, 92, 103, 98, 110, 105, 115, + 31, 100, 102, 138, 130, 146, 149, 162, 27, 95, 95, 143, 95, 143, 143, 171, 30, 99, 109, 155, + 129, 144, 168, 172, 28, 97, 102, 148, 123, 145, 169, 173, 32, 101, 114, 161, 131, 147, 170, 174, + 7, 42, 25, 48, 30, 53, 36, 59, 42, 119, 97, 124, 129, 133, 132, 134, 25, 97, 93, 104, + 99, 111, 106, 116, 48, 124, 104, 139, 144, 156, 150, 163, 30, 129, 99, 144, 109, 168, 155, 172, + 53, 133, 111, 156, 168, 188, 179, 191, 36, 132, 106, 150, 155, 179, 175, 183, 59, 134, 116, 163, + 172, 191, 183, 195, 4, 39, 22, 45, 22, 45, 33, 56, 39, 120, 98, 121, 98, 121, 119, 122, + 22, 98, 92, 105, 96, 110, 103, 115, 45, 121, 105, 140, 110, 157, 151, 164, 22, 98, 96, 110, + 92, 105, 103, 115, 45, 121, 110, 157, 105, 140, 151, 164, 33, 119, 103, 151, 103, 151, 176, 184, + 56, 122, 115, 164, 115, 164, 184, 196, 6, 40, 23, 46, 28, 51, 34, 57, 43, 121, 100, 125, + 123, 126, 124, 127, 25, 99, 93, 106, 97, 111, 104, 116, 49, 126, 107, 141, 145, 158, 152, 165, + 31, 130, 100, 146, 102, 149, 138, 162, 54, 135, 112, 159, 169, 189, 181, 192, 37, 133, 107, 153, + 148, 180, 177, 185, 60, 136, 117, 166, 173, 193, 186, 197, 6, 43, 25, 49, 31, 54, 37, 60, + 40, 121, 99, 126, 130, 135, 133, 136, 23, 100, 93, 107, 100, 112, 107, 117, 46, 125, 106, 141, + 146, 159, 153, 166, 28, 123, 97, 145, 102, 169, 148, 173, 51, 126, 111, 158, 149, 189, 180, 193, + 34, 124, 104, 152, 138, 181, 177, 186, 57, 127, 116, 165, 162, 192, 185, 197, 8, 44, 26, 50, + 32, 55, 38, 61, 44, 122, 101, 127, 131, 136, 134, 137, 26, 101, 94, 108, 101, 113, 108, 118, + 50, 127, 108, 142, 147, 160, 154, 167, 32, 131, 101, 147, 114, 170, 161, 174, 55, 136, 113, 160, + 170, 190, 182, 194, 38, 134, 108, 154, 161, 182, 178, 187, 61, 137, 118, 167, 174, 194, 187, 198, + 2, 12, 10, 17, 6, 16, 15, 18, 13, 45, 47, 49, 31, 46, 48, 50, 13, 52, 77, 80, + 43, 68, 79, 81, 19, 54, 82, 85, 54, 73, 85, 86, 7, 30, 42, 53, 25, 36, 48, 59, + 17, 51, 67, 71, 37, 64, 70, 72, 17, 53, 80, 87, 49, 71, 85, 88, 20, 55, 83, 88, + 60, 74, 89, 90, 10, 35, 33, 37, 28, 36, 34, 38, 77, 105, 103, 107, 102, 106, 104, 108, + 47, 143, 103, 148, 123, 155, 138, 161, 82, 169, 176, 177, 169, 175, 177, 178, 42, 129, 119, 133, + 97, 132, 124, 134, 80, 149, 151, 153, 148, 150, 152, 154, 67, 168, 151, 180, 145, 179, 181, 182, + 83, 170, 184, 185, 173, 183, 186, 187, 12, 66, 35, 67, 40, 68, 63, 69, 52, 110, 143, 145, + 130, 146, 144, 147, 45, 110, 105, 151, 121, 157, 140, 164, 54, 112, 169, 181, 135, 159, 189, 192, + 30, 109, 129, 168, 99, 155, 144, 172, 53, 111, 168, 179, 133, 156, 188, 191, 51, 111, 149, 180, + 126, 158, 189, 193, 55, 113, 170, 182, 136, 160, 190, 194, 17, 67, 37, 70, 51, 71, 64, 72, + 80, 151, 148, 152, 149, 153, 150, 154, 49, 145, 107, 152, 126, 158, 141, 165, 85, 181, 177, 199, + 189, 201, 200, 202, 53, 168, 133, 188, 111, 179, 156, 191, 87, 180, 180, 206, 180, 206, 206, 207, + 71, 179, 153, 206, 158, 208, 201, 209, 88, 182, 185, 210, 193, 209, 211, 212, 6, 40, 28, 51, + 23, 46, 34, 57, 43, 121, 123, 126, 100, 125, 124, 127, 31, 130, 102, 149, 100, 146, 138, 162, + 54, 135, 169, 189, 112, 159, 181, 192, 25, 99, 97, 111, 93, 106, 104, 116, 49, 126, 145, 158, + 107, 141, 152, 165, 37, 133, 148, 180, 107, 153, 177, 185, 60, 136, 173, 193, 117, 166, 186, 197, + 15, 63, 34, 64, 34, 64, 62, 65, 79, 140, 138, 141, 138, 141, 139, 142, 48, 144, 104, 150, + 124, 156, 139, 163, 85, 189, 177, 200, 181, 201, 199, 202, 48, 144, 124, 156, 104, 150, 139, 163, + 85, 189, 181, 201, 177, 200, 199, 202, 70, 188, 152, 206, 152, 206, 199, 210, 89, 190, 186, 211, + 186, 211, 213, 214, 16, 68, 36, 71, 46, 73, 64, 74, 68, 157, 155, 158, 146, 159, 156, 160, + 46, 146, 106, 153, 125, 159, 141, 166, 73, 159, 175, 201, 159, 203, 201, 204, 36, 155, 132, 179, + 106, 175, 150, 183, 71, 158, 179, 208, 153, 201, 206, 209, 64, 156, 150, 206, 141, 201, 200, 211, + 74, 160, 183, 209, 166, 204, 211, 215, 18, 69, 38, 72, 57, 74, 65, 75, 81, 164, 161, 165, + 162, 166, 163, 167, 50, 147, 108, 154, 127, 160, 142, 167, 86, 192, 178, 202, 192, 204, 202, 205, + 59, 172, 134, 191, 116, 183, 163, 195, 88, 193, 182, 209, 185, 211, 210, 212, 72, 191, 154, 207, + 165, 209, 202, 212, 90, 194, 187, 212, 197, 215, 214, 216, 2, 13, 13, 19, 7, 17, 17, 20, + 12, 45, 52, 54, 30, 51, 53, 55, 10, 47, 77, 82, 42, 67, 80, 83, 17, 49, 80, 85, + 53, 71, 87, 88, 6, 31, 43, 54, 25, 37, 49, 60, 16, 46, 68, 73, 36, 64, 71, 74, + 15, 48, 79, 85, 48, 70, 85, 89, 18, 50, 81, 86, 59, 72, 88, 90, 12, 52, 45, 54, + 30, 53, 51, 55, 66, 110, 110, 112, 109, 111, 111, 113, 35, 143, 105, 169, 129, 168, 149, 170, + 67, 145, 151, 181, 168, 179, 180, 182, 40, 130, 121, 135, 99, 133, 126, 136, 68, 146, 157, 159, + 155, 156, 158, 160, 63, 144, 140, 189, 144, 188, 189, 190, 69, 147, 164, 192, 172, 191, 193, 194, + 10, 77, 47, 82, 42, 80, 67, 83, 35, 105, 143, 169, 129, 149, 168, 170, 33, 103, 103, 176, + 119, 151, 151, 184, 37, 107, 148, 177, 133, 153, 180, 185, 28, 102, 123, 169, 97, 148, 145, 173, + 36, 106, 155, 175, 132, 150, 179, 183, 34, 104, 138, 177, 124, 152, 181, 186, 38, 108, 161, 178, + 134, 154, 182, 187, 17, 80, 49, 85, 53, 87, 71, 88, 67, 151, 145, 181, 168, 180, 179, 182, + 37, 148, 107, 177, 133, 180, 153, 185, 70, 152, 152, 199, 188, 206, 206, 210, 51, 149, 126, 189, + 111, 180, 158, 193, 71, 153, 158, 201, 179, 206, 208, 209, 64, 150, 141, 200, 156, 206, 201, 211, + 72, 154, 165, 202, 191, 207, 209, 212, 6, 43, 31, 54, 25, 49, 37, 60, 40, 121, 130, 135, + 99, 126, 133, 136, 28, 123, 102, 169, 97, 145, 148, 173, 51, 126, 149, 189, 111, 158, 180, 193, + 23, 100, 100, 112, 93, 107, 107, 117, 46, 125, 146, 159, 106, 141, 153, 166, 34, 124, 138, 181, + 104, 152, 177, 186, 57, 127, 162, 192, 116, 165, 185, 197, 16, 68, 46, 73, 36, 71, 64, 74, + 68, 157, 146, 159, 155, 158, 156, 160, 36, 155, 106, 175, 132, 179, 150, 183, 71, 158, 153, 201, + 179, 208, 206, 209, 46, 146, 125, 159, 106, 153, 141, 166, 73, 159, 159, 203, 175, 201, 201, 204, + 64, 156, 141, 201, 150, 206, 200, 211, 74, 160, 166, 204, 183, 209, 211, 215, 15, 79, 48, 85, + 48, 85, 70, 89, 63, 140, 144, 189, 144, 189, 188, 190, 34, 138, 104, 177, 124, 181, 152, 186, + 64, 141, 150, 200, 156, 201, 206, 211, 34, 138, 124, 181, 104, 177, 152, 186, 64, 141, 156, 201, + 150, 200, 206, 211, 62, 139, 139, 199, 139, 199, 199, 213, 65, 142, 163, 202, 163, 202, 210, 214, + 18, 81, 50, 86, 59, 88, 72, 90, 69, 164, 147, 192, 172, 193, 191, 194, 38, 161, 108, 178, + 134, 182, 154, 187, 72, 165, 154, 202, 191, 209, 207, 212, 57, 162, 127, 192, 116, 185, 165, 197, + 74, 166, 160, 204, 183, 211, 209, 215, 65, 163, 142, 202, 163, 210, 202, 214, 75, 167, 167, 205, + 195, 212, 212, 216, 3, 14, 14, 20, 8, 18, 18, 21, 14, 56, 58, 60, 32, 57, 59, 61, + 14, 58, 78, 83, 44, 69, 81, 84, 20, 60, 83, 89, 55, 74, 88, 90, 8, 32, 44, 55, + 26, 38, 50, 61, 18, 57, 69, 74, 38, 65, 72, 75, 18, 59, 81, 88, 50, 72, 86, 90, + 21, 61, 84, 90, 61, 75, 90, 91, 14, 58, 56, 60, 32, 59, 57, 61, 78, 115, 115, 117, + 114, 116, 116, 118, 58, 171, 115, 173, 131, 172, 162, 174, 83, 173, 184, 186, 170, 183, 185, 187, + 44, 131, 122, 136, 101, 134, 127, 137, 81, 162, 164, 166, 161, 163, 165, 167, 69, 172, 164, 193, + 147, 191, 192, 194, 84, 174, 196, 197, 174, 195, 197, 198, 14, 78, 58, 83, 44, 81, 69, 84, + 58, 115, 171, 173, 131, 162, 172, 174, 56, 115, 115, 184, 122, 164, 164, 196, 60, 117, 173, 186, + 136, 166, 193, 197, 32, 114, 131, 170, 101, 161, 147, 174, 59, 116, 172, 183, 134, 163, 191, 195, + 57, 116, 162, 185, 127, 165, 192, 197, 61, 118, 174, 187, 137, 167, 194, 198, 20, 83, 60, 89, + 55, 88, 74, 90, 83, 184, 173, 186, 170, 185, 183, 187, 60, 173, 117, 186, 136, 193, 166, 197, + 89, 186, 186, 213, 190, 211, 211, 214, 55, 170, 136, 190, 113, 182, 160, 194, 88, 185, 193, 211, + 182, 210, 209, 212, 74, 183, 166, 211, 160, 209, 204, 215, 90, 187, 197, 214, 194, 212, 215, 216, + 8, 44, 32, 55, 26, 50, 38, 61, 44, 122, 131, 136, 101, 127, 134, 137, 32, 131, 114, 170, + 101, 147, 161, 174, 55, 136, 170, 190, 113, 160, 182, 194, 26, 101, 101, 113, 94, 108, 108, 118, + 50, 127, 147, 160, 108, 142, 154, 167, 38, 134, 161, 182, 108, 154, 178, 187, 61, 137, 174, 194, + 118, 167, 187, 198, 18, 69, 57, 74, 38, 72, 65, 75, 81, 164, 162, 166, 161, 165, 163, 167, + 59, 172, 116, 183, 134, 191, 163, 195, 88, 193, 185, 211, 182, 209, 210, 212, 50, 147, 127, 160, + 108, 154, 142, 167, 86, 192, 192, 204, 178, 202, 202, 205, 72, 191, 165, 209, 154, 207, 202, 212, + 90, 194, 197, 215, 187, 212, 214, 216, 18, 81, 59, 88, 50, 86, 72, 90, 69, 164, 172, 193, + 147, 192, 191, 194, 57, 162, 116, 185, 127, 192, 165, 197, 74, 166, 183, 211, 160, 204, 209, 215, + 38, 161, 134, 182, 108, 178, 154, 187, 72, 165, 191, 209, 154, 202, 207, 212, 65, 163, 163, 210, + 142, 202, 202, 214, 75, 167, 195, 212, 167, 205, 212, 216, 21, 84, 61, 90, 61, 90, 75, 91, + 84, 196, 174, 197, 174, 197, 195, 198, 61, 174, 118, 187, 137, 194, 167, 198, 90, 197, 187, 214, + 194, 215, 212, 216, 61, 174, 137, 194, 118, 187, 167, 198, 90, 197, 194, 215, 187, 214, 212, 216, + 75, 195, 167, 212, 167, 212, 205, 216, 91, 198, 198, 216, 198, 216, 216, 217 +}; + +const unsigned int igraph_i_isographs_3[] = { 0, 1, 3, 5, 6, 7, 10, 11, 15, 21, + 23, 25, 27, 30, 31, 63 + }; +const unsigned int igraph_i_isographs_3u[] = { 0, 1, 3, 7 }; +const unsigned int igraph_i_isographs_4[] = { + 0, 1, 3, 7, 9, 10, 11, 14, 15, 18, 19, 20, 21, + 22, 23, 27, 29, 30, 31, 54, 55, 63, 73, 75, 76, 77, + 79, 81, 83, 84, 85, 86, 87, 90, 91, 92, 93, 94, 95, + 98, 99, 100, 101, 102, 103, 106, 107, 108, 109, 110, 111, 115, + 116, 117, 118, 119, 122, 123, 124, 125, 126, 127, 219, 220, 221, + 223, 228, 229, 230, 231, 237, 238, 239, 246, 247, 255, 292, 293, + 295, 301, 302, 303, 310, 311, 319, 365, 367, 373, 375, 382, 383, + 511, 585, 587, 591, 593, 594, 595, 596, 597, 598, 599, 601, 602, + 603, 604, 605, 606, 607, 625, 626, 627, 630, 631, 633, 634, 635, + 638, 639, 659, 660, 661, 663, 666, 667, 669, 670, 671, 674, 675, + 678, 679, 683, 686, 687, 694, 695, 703, 729, 731, 732, 733, 735, + 737, 739, 741, 742, 743, 745, 746, 747, 748, 749, 750, 751, 753, + 755, 756, 757, 758, 759, 761, 762, 763, 764, 765, 766, 767, 819, + 822, 823, 826, 827, 830, 831, 875, 876, 877, 879, 883, 885, 886, + 887, 891, 892, 893, 894, 895, 947, 949, 951, 955, 957, 958, 959, + 1019, 1020, 1021, 1023, 1755, 1757, 1758, 1759, 1782, 1783, 1791, 1883, 1887, + 1907, 1911, 1917, 1918, 1919, 2029, 2031, 2039, 2047, 4095 +}; +const unsigned int igraph_i_isographs_4u[] = { 0, 1, 3, 7, 11, 12, 13, + 15, 30, 31, 63 + }; + +const unsigned int igraph_i_classedges_3[] = { 1, 2, 0, 2, 2, 1, 0, 1, 2, 0, 1, 0 }; +const unsigned int igraph_i_classedges_3u[] = { 1, 2, 0, 2, 0, 1 }; +const unsigned int igraph_i_classedges_4[] = { 2, 3, 1, 3, 0, 3, 3, 2, 1, 2, 0, 2, + 3, 1, 2, 1, 0, 1, 3, 0, 2, 0, 1, 0 + }; +const unsigned int igraph_i_classedges_4u[] = { 2, 3, 1, 3, 0, 3, 1, 2, 0, 2, 0, 1 }; + +/** + * \section about_graph_isomorphism + * + * igraph provides four set of functions to deal with graph + * isomorphism problems. + * + * The \ref igraph_isomorphic() and \ref igraph_subisomorphic() + * functions make up the first set (in addition with the \ref + * igraph_permute_vertices() function). These functions choose the + * algorithm which is best for the supplied input graph. (The choice is + * not very sophisticated though, see their documentation for + * details.) + * + * The VF2 graph (and subgraph) isomorphism algorithm is implemented in + * igraph, these functions are the second set. See \ref + * igraph_isomorphic_vf2() and \ref igraph_subisomorphic_vf2() for + * starters. + * + * Functions for the BLISS algorithm constitute the third set, + * see \ref igraph_isomorphic_bliss(). + * + * Finally, the isomorphism classes of all graphs with three and + * four vertices are precomputed and stored in igraph, so for these + * small graphs there is a very simple fast way to decide isomorphism. + * See \ref igraph_isomorphic_34(). + * + */ + +/** + * \function igraph_isoclass + * \brief Determine the isomorphism class of a graph with 3 or 4 vertices + * + * + * All graphs with a given number of vertices belong to a number of + * isomorphism classes, with every graph in a given class being + * isomorphic to each other. + * + * + * This function gives the isomorphism class (a number) of a + * graph. Two graphs have the same isomorphism class if and only if + * they are isomorphic. + * + * + * The first isomorphism class is numbered zero and it is the empty + * graph, the last isomorphism class is the full graph. The number of + * isomorphism class for directed graphs with three vertices is 16 + * (between 0 and 15), for undirected graph it is only 4. For graphs + * with four vertices it is 218 (directed) and 11 (undirected). + * + * \param graph The graph object. + * \param isoclass Pointer to an integer, the isomorphism class will + * be stored here. + * \return Error code. + * \sa \ref igraph_isomorphic(), \ref igraph_isoclass_subgraph(), + * \ref igraph_isoclass_create(), \ref igraph_motifs_randesu(). + * + * Because of some limitations this function works only for graphs + * with three of four vertices. + * + * + * Time complexity: O(|E|), the number of edges in the graph. + */ + +int igraph_isoclass(const igraph_t *graph, igraph_integer_t *isoclass) { + long int e; + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_integer_t from, to; + unsigned char idx, mul; + const unsigned int *arr_idx, *arr_code; + int code = 0; + + if (no_of_nodes < 3 || no_of_nodes > 4) { + IGRAPH_ERROR("Only implemented for graphs with 3 or 4 vertices", + IGRAPH_UNIMPLEMENTED); + } + + if (igraph_is_directed(graph)) { + if (no_of_nodes == 3) { + arr_idx = igraph_i_isoclass_3_idx; + arr_code = igraph_i_isoclass2_3; + mul = 3; + } else { + arr_idx = igraph_i_isoclass_4_idx; + arr_code = igraph_i_isoclass2_4; + mul = 4; + } + } else { + if (no_of_nodes == 3) { + arr_idx = igraph_i_isoclass_3u_idx; + arr_code = igraph_i_isoclass2_3u; + mul = 3; + } else { + arr_idx = igraph_i_isoclass_4u_idx; + arr_code = igraph_i_isoclass2_4u; + mul = 4; + } + } + + for (e = 0; e < no_of_edges; e++) { + igraph_edge(graph, (igraph_integer_t) e, &from, &to); + idx = (unsigned char) (mul * from + to); + code |= arr_idx[idx]; + } + + *isoclass = (igraph_integer_t) arr_code[code]; + return 0; +} + +/** + * \function igraph_isomorphic + * \brief Decides whether two graphs are isomorphic + * + * + * From Wikipedia: The graph isomorphism problem or GI problem is the + * graph theory problem of determining whether, given two graphs G1 + * and G2, it is possible to permute (or relabel) the vertices of one + * graph so that it is equal to the other. Such a permutation is + * called a graph isomorphism. + * + * This function decides which graph isomorphism algorithm to be + * used based on the input graphs. Right now it does the following: + * \olist + * \oli If one graph is directed and the other undirected then an + * error is triggered. + * \oli If the two graphs does not have the same number of vertices + * and edges it returns with \c FALSE. + * \oli Otherwise, if the graphs have three or four vertices then an O(1) + * algorithm is used with precomputed data. + * \oli Otherwise BLISS is used, see \ref igraph_isomorphic_bliss(). + * \endolist + * + * + * Please call the VF2 and BLISS functions directly if you need + * something more sophisticated, e.g. you need the isomorphic mapping. + * + * \param graph1 The first graph. + * \param graph2 The second graph. + * \param iso Pointer to a logical variable, will be set to TRUE (1) + * if the two graphs are isomorphic, and FALSE (0) otherwise. + * \return Error code. + * \sa \ref igraph_isoclass(), \ref igraph_isoclass_subgraph(), + * \ref igraph_isoclass_create(). + * + * Time complexity: exponential. + */ + +int igraph_isomorphic(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso) { + + long int nodes1 = igraph_vcount(graph1), nodes2 = igraph_vcount(graph2); + long int edges1 = igraph_ecount(graph1), edges2 = igraph_ecount(graph2); + igraph_bool_t dir1 = igraph_is_directed(graph1), dir2 = igraph_is_directed(graph2); + igraph_bool_t loop1, loop2; + + if (dir1 != dir2) { + IGRAPH_ERROR("Cannot compare directed and undirected graphs", IGRAPH_EINVAL); + } else if (nodes1 != nodes2 || edges1 != edges2) { + *iso = 0; + } else if (nodes1 == 3 || nodes1 == 4) { + IGRAPH_CHECK(igraph_has_loop(graph1, &loop1)); + IGRAPH_CHECK(igraph_has_loop(graph2, &loop2)); + if (!loop1 && !loop2) { + IGRAPH_CHECK(igraph_isomorphic_34(graph1, graph2, iso)); + } else { + IGRAPH_CHECK(igraph_isomorphic_bliss(graph1, graph2, NULL, NULL, iso, + 0, 0, /*sh=*/ IGRAPH_BLISS_F, 0, 0)); + } + } else { + IGRAPH_CHECK(igraph_isomorphic_bliss(graph1, graph2, NULL, NULL, iso, + 0, 0, /*sh=*/ IGRAPH_BLISS_F, 0, 0)); + } + + return 0; +} + +/** + * \function igraph_isomorphic_34 + * Graph isomorphism for 3-4 vertices + * + * This function uses precomputed indices to decide isomorphism + * problems for graphs with only 3 or 4 vertices. + * \param graph1 The first input graph. + * \param graph2 The second input graph. Must have the same + * directedness as \p graph1. + * \param iso Pointer to a boolean, the result is stored here. + * \return Error code. + * + * Time complexity: O(1). + */ + +int igraph_isomorphic_34(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso) { + + igraph_integer_t class1, class2; + IGRAPH_CHECK(igraph_isoclass(graph1, &class1)); + IGRAPH_CHECK(igraph_isoclass(graph2, &class2)); + *iso = (class1 == class2); + return 0; +} + +/** + * \function igraph_isoclass_subgraph + * \brief The isomorphism class of a subgraph of a graph. + * + * + * This function is only implemented for subgraphs with three or four + * vertices. + * \param graph The graph object. + * \param vids A vector containing the vertex ids to be considered as + * a subgraph. Each vertex id should be included at most once. + * \param isoclass Pointer to an integer, this will be set to the + * isomorphism class. + * \return Error code. + * \sa \ref igraph_isoclass(), \ref igraph_isomorphic(), + * \ref igraph_isoclass_create(). + * + * Time complexity: O((d+n)*n), d is the average degree in the network, + * and n is the number of vertices in \c vids. + */ + +int igraph_isoclass_subgraph(const igraph_t *graph, igraph_vector_t *vids, + igraph_integer_t *isoclass) { + int nodes = (int) igraph_vector_size(vids); + igraph_bool_t directed = igraph_is_directed(graph); + igraph_vector_t neis; + + unsigned char mul, idx; + const unsigned int *arr_idx, *arr_code; + int code = 0; + + long int i, j, s; + + if (nodes < 3 || nodes > 4) { + IGRAPH_ERROR("Only for three- or four-vertex subgraphs", + IGRAPH_UNIMPLEMENTED); + } + + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + + if (directed) { + if (nodes == 3) { + arr_idx = igraph_i_isoclass_3_idx; + arr_code = igraph_i_isoclass2_3; + mul = 3; + } else { + arr_idx = igraph_i_isoclass_4_idx; + arr_code = igraph_i_isoclass2_4; + mul = 4; + } + } else { + if (nodes == 3) { + arr_idx = igraph_i_isoclass_3u_idx; + arr_code = igraph_i_isoclass2_3u; + mul = 3; + } else { + arr_idx = igraph_i_isoclass_4u_idx; + arr_code = igraph_i_isoclass2_4u; + mul = 4; + } + } + + for (i = 0; i < nodes; i++) { + long int from = (long int) VECTOR(*vids)[i]; + igraph_neighbors(graph, &neis, (igraph_integer_t) from, IGRAPH_OUT); + s = igraph_vector_size(&neis); + for (j = 0; j < s; j++) { + long int nei = (long int) VECTOR(neis)[j], to; + if (igraph_vector_search(vids, 0, nei, &to)) { + idx = (unsigned char) (mul * i + to); + code |= arr_idx[idx]; + } + } + } + + *isoclass = (igraph_integer_t) arr_code[code]; + igraph_vector_destroy(&neis); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_isoclass_create + * \brief Creates a graph from the given isomorphism class. + * + * + * This function is implemented only for graphs with three or four + * vertices. + * \param graph Pointer to an uninitialized graph object. + * \param size The number of vertices to add to the graph. + * \param number The isomorphism class. + * \param directed Logical constant, whether to create a directed + * graph. + * \return Error code. + * \sa \ref igraph_isoclass(), + * \ref igraph_isoclass_subgraph(), + * \ref igraph_isomorphic(). + * + * Time complexity: O(|V|+|E|), the number of vertices plus the number + * of edges in the graph to create. + */ + +int igraph_isoclass_create(igraph_t *graph, igraph_integer_t size, + igraph_integer_t number, igraph_bool_t directed) { + igraph_vector_t edges; + const unsigned int *classedges; + long int power; + long int code; + long int pos; + + if (size < 3 || size > 4) { + IGRAPH_ERROR("Only for graphs with three of four vertices", + IGRAPH_UNIMPLEMENTED); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + + if (directed) { + if (size == 3) { + classedges = igraph_i_classedges_3; + + if (number < 0 || + number >= (int)(sizeof(igraph_i_isographs_3) / sizeof(unsigned int))) { + IGRAPH_ERROR("`number' invalid, cannot create graph", IGRAPH_EINVAL); + } + + code = igraph_i_isographs_3[ (long int) number]; + power = 32; + } else { + classedges = igraph_i_classedges_4; + + if (number < 0 || + number >= (int)(sizeof(igraph_i_isographs_4) / sizeof(unsigned int))) { + IGRAPH_ERROR("`number' invalid, cannot create graph", IGRAPH_EINVAL); + } + + code = igraph_i_isographs_4[ (long int) number]; + power = 2048; + } + } else { + if (size == 3) { + classedges = igraph_i_classedges_3u; + + if (number < 0 || + number >= (int)(sizeof(igraph_i_isographs_3u) / + sizeof(unsigned int))) { + IGRAPH_ERROR("`number' invalid, cannot create graph", IGRAPH_EINVAL); + } + + code = igraph_i_isographs_3u[ (long int) number]; + power = 4; + } else { + classedges = igraph_i_classedges_4u; + + if (number < 0 || + number >= (int)(sizeof(igraph_i_isographs_4u) / + sizeof(unsigned int))) { + IGRAPH_ERROR("`number' invalid, cannot create graph", IGRAPH_EINVAL); + } + + code = igraph_i_isographs_4u[ (long int) number]; + power = 32; + } + } + + pos = 0; + while (code > 0) { + if (code >= power) { + IGRAPH_CHECK(igraph_vector_push_back(&edges, classedges[2 * pos])); + IGRAPH_CHECK(igraph_vector_push_back(&edges, classedges[2 * pos + 1])); + code -= power; + } + power /= 2; + pos++; + } + + IGRAPH_CHECK(igraph_create(graph, &edges, size, directed)); + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \section about_vf2 + * + * + * The VF2 algorithm can search for a subgraph in a larger graph, or check if two + * graphs are isomorphic. See P. Foggia, C. Sansone, M. Vento, An Improved algorithm for + * matching large graphs, Proc. of the 3rd IAPR-TC-15 International + * Workshop on Graph-based Representations, Italy, 2001. + * + * + * + * VF2 supports both vertex and edge-colored graphs, as well as custom vertex or edge + * compatibility functions. + * + * + * + * VF2 works with both directed and undirected graphs. Only simple graphs are supported. + * Self-loops or multi-edges must not be present in the graphs. Currently, the VF2 + * functions do not check that the input graph is simple: it is the responsibility + * of the user to pass in valid input. + * + */ + +/** + * \function igraph_isomorphic_function_vf2 + * The generic VF2 interface + * + * + * This function is an implementation of the VF2 isomorphism algorithm, + * see P. Foggia, C. Sansone, M. Vento, An Improved algorithm for + * matching large graphs, Proc. of the 3rd IAPR-TC-15 International + * Workshop on Graph-based Representations, Italy, 2001. + * + * For using it you need to define a callback function of type + * \ref igraph_isohandler_t. This function will be called whenever VF2 + * finds an isomorphism between the two graphs. The mapping between + * the two graphs will be also provided to this function. If the + * callback returns a nonzero value then the search is continued, + * otherwise it stops. The callback function must not destroy the + * mapping vectors that are passed to it. + * \param graph1 The first input graph. + * \param graph2 The second input graph. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param map12 Pointer to an initialized vector or \c NULL. If not \c + * NULL and the supplied graphs are isomorphic then the permutation + * taking \p graph1 to \p graph is stored here. If not \c NULL and the + * graphs are not isomorphic then a zero-length vector is returned. + * \param map21 This is the same as \p map12, but for the permutation + * taking \p graph2 to \p graph1. + * \param isohandler_fn The callback function to be called if an + * isomorphism is found. See also \ref igraph_isohandler_t. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p isohandler_fn, \p + * node_compat_fn and \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +int igraph_isomorphic_function_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_t *map12, + igraph_vector_t *map21, + igraph_isohandler_t *isohandler_fn, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + long int no_of_nodes = igraph_vcount(graph1); + long int no_of_edges = igraph_ecount(graph1); + igraph_vector_t mycore_1, mycore_2, *core_1 = &mycore_1, *core_2 = &mycore_2; + igraph_vector_t in_1, in_2, out_1, out_2; + long int in_1_size = 0, in_2_size = 0, out_1_size = 0, out_2_size = 0; + igraph_vector_t *inneis_1, *inneis_2, *outneis_1, *outneis_2; + long int matched_nodes = 0; + long int depth; + long int cand1, cand2; + long int last1, last2; + igraph_stack_t path; + igraph_lazy_adjlist_t inadj1, inadj2, outadj1, outadj2; + igraph_vector_t indeg1, indeg2, outdeg1, outdeg2; + + if (igraph_is_directed(graph1) != igraph_is_directed(graph2)) { + IGRAPH_ERROR("Cannot compare directed and undirected graphs", + IGRAPH_EINVAL); + } + + if ( (vertex_color1 && !vertex_color2) || (!vertex_color1 && vertex_color2) ) { + IGRAPH_WARNING("Only one graph is vertex-colored, vertex colors will be ignored"); + vertex_color1 = vertex_color2 = 0; + } + + if ( (edge_color1 && !edge_color2) || (!edge_color1 && edge_color2)) { + IGRAPH_WARNING("Only one graph is edge-colored, edge colors will be ignored"); + edge_color1 = edge_color2 = 0; + } + + if (no_of_nodes != igraph_vcount(graph2) || + no_of_edges != igraph_ecount(graph2)) { + return 0; + } + + if (vertex_color1) { + if (igraph_vector_int_size(vertex_color1) != no_of_nodes || + igraph_vector_int_size(vertex_color2) != no_of_nodes) { + IGRAPH_ERROR("Invalid vertex color vector length", IGRAPH_EINVAL); + } + } + + if (edge_color1) { + if (igraph_vector_int_size(edge_color1) != no_of_edges || + igraph_vector_int_size(edge_color2) != no_of_edges) { + IGRAPH_ERROR("Invalid edge color vector length", IGRAPH_EINVAL); + } + } + + /* Check color distribution */ + if (vertex_color1) { + int ret = 0; + igraph_vector_int_t tmp1, tmp2; + IGRAPH_CHECK(igraph_vector_int_copy(&tmp1, vertex_color1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp1); + IGRAPH_CHECK(igraph_vector_int_copy(&tmp2, vertex_color2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp2); + igraph_vector_int_sort(&tmp1); + igraph_vector_int_sort(&tmp2); + ret = !igraph_vector_int_all_e(&tmp1, &tmp2); + igraph_vector_int_destroy(&tmp1); + igraph_vector_int_destroy(&tmp2); + IGRAPH_FINALLY_CLEAN(2); + if (ret) { + return 0; + } + } + + /* Check edge color distribution */ + if (edge_color1) { + int ret = 0; + igraph_vector_int_t tmp1, tmp2; + IGRAPH_CHECK(igraph_vector_int_copy(&tmp1, edge_color1)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp1); + IGRAPH_CHECK(igraph_vector_int_copy(&tmp2, edge_color2)); + IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp2); + igraph_vector_int_sort(&tmp1); + igraph_vector_int_sort(&tmp2); + ret = !igraph_vector_int_all_e(&tmp1, &tmp2); + igraph_vector_int_destroy(&tmp1); + igraph_vector_int_destroy(&tmp2); + IGRAPH_FINALLY_CLEAN(2); + if (ret) { + return 0; + } + } + + if (map12) { + core_1 = map12; + IGRAPH_CHECK(igraph_vector_resize(core_1, no_of_nodes)); + } else { + IGRAPH_VECTOR_INIT_FINALLY(core_1, no_of_nodes); + } + igraph_vector_fill(core_1, -1); + if (map21) { + core_2 = map21; + IGRAPH_CHECK(igraph_vector_resize(core_2, no_of_nodes)); + igraph_vector_null(core_2); + } else { + IGRAPH_VECTOR_INIT_FINALLY(core_2, no_of_nodes); + } + igraph_vector_fill(core_2, -1); + + IGRAPH_VECTOR_INIT_FINALLY(&in_1, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&in_2, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&out_1, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&out_2, no_of_nodes); + IGRAPH_CHECK(igraph_stack_init(&path, 0)); + IGRAPH_FINALLY(igraph_stack_destroy, &path); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph1, &inadj1, IGRAPH_IN, + IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &inadj1); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph1, &outadj1, IGRAPH_OUT, + IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &outadj1); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph2, &inadj2, IGRAPH_IN, + IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &inadj2); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph2, &outadj2, IGRAPH_OUT, + IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &outadj2); + IGRAPH_VECTOR_INIT_FINALLY(&indeg1, 0); + IGRAPH_VECTOR_INIT_FINALLY(&indeg2, 0); + IGRAPH_VECTOR_INIT_FINALLY(&outdeg1, 0); + IGRAPH_VECTOR_INIT_FINALLY(&outdeg2, 0); + + IGRAPH_CHECK(igraph_stack_reserve(&path, no_of_nodes * 2)); + IGRAPH_CHECK(igraph_degree(graph1, &indeg1, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph2, &indeg2, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph1, &outdeg1, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph2, &outdeg2, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + + depth = 0; last1 = -1; last2 = -1; + while (depth >= 0) { + long int i; + + IGRAPH_ALLOW_INTERRUPTION(); + + cand1 = -1; cand2 = -1; + /* Search for the next pair to try */ + if ((in_1_size != in_2_size) || + (out_1_size != out_2_size)) { + /* step back, nothing to do */ + } else if (out_1_size > 0 && out_2_size > 0) { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes) { + if (VECTOR(out_2)[i] > 0 && VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1 now, it should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes) { + if (VECTOR(out_1)[i] > 0 && VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } else if (in_1_size > 0 && in_2_size > 0) { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes) { + if (VECTOR(in_2)[i] > 0 && VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1 now, should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes) { + if (VECTOR(in_1)[i] > 0 && VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } else { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes) { + if (VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1, should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes) { + if (VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } + + /* Ok, we have cand1, cand2 as candidates. Or not? */ + if (cand1 < 0 || cand2 < 0) { + /**************************************************************/ + /* dead end, step back, if possible. Otherwise we'll terminate */ + if (depth >= 1) { + last2 = (long int) igraph_stack_pop(&path); + last1 = (long int) igraph_stack_pop(&path); + matched_nodes -= 1; + VECTOR(*core_1)[last1] = -1; + VECTOR(*core_2)[last2] = -1; + + if (VECTOR(in_1)[last1] != 0) { + in_1_size += 1; + } + if (VECTOR(out_1)[last1] != 0) { + out_1_size += 1; + } + if (VECTOR(in_2)[last2] != 0) { + in_2_size += 1; + } + if (VECTOR(out_2)[last2] != 0) { + out_2_size += 1; + } + + inneis_1 = igraph_lazy_adjlist_get(&inadj1, (igraph_integer_t) last1); + for (i = 0; i < igraph_vector_size(inneis_1); i++) { + long int node = (long int) VECTOR(*inneis_1)[i]; + if (VECTOR(in_1)[node] == depth) { + VECTOR(in_1)[node] = 0; + in_1_size -= 1; + } + } + outneis_1 = igraph_lazy_adjlist_get(&outadj1, (igraph_integer_t) last1); + for (i = 0; i < igraph_vector_size(outneis_1); i++) { + long int node = (long int) VECTOR(*outneis_1)[i]; + if (VECTOR(out_1)[node] == depth) { + VECTOR(out_1)[node] = 0; + out_1_size -= 1; + } + } + inneis_2 = igraph_lazy_adjlist_get(&inadj2, (igraph_integer_t) last2); + for (i = 0; i < igraph_vector_size(inneis_2); i++) { + long int node = (long int) VECTOR(*inneis_2)[i]; + if (VECTOR(in_2)[node] == depth) { + VECTOR(in_2)[node] = 0; + in_2_size -= 1; + } + } + outneis_2 = igraph_lazy_adjlist_get(&outadj2, (igraph_integer_t) last2); + for (i = 0; i < igraph_vector_size(outneis_2); i++) { + long int node = (long int) VECTOR(*outneis_2)[i]; + if (VECTOR(out_2)[node] == depth) { + VECTOR(out_2)[node] = 0; + out_2_size -= 1; + } + } + + } /* end of stepping back */ + + depth -= 1; + + } else { + /**************************************************************/ + /* step forward if worth, check if worth first */ + long int xin1 = 0, xin2 = 0, xout1 = 0, xout2 = 0; + igraph_bool_t end = 0; + inneis_1 = igraph_lazy_adjlist_get(&inadj1, (igraph_integer_t) cand1); + outneis_1 = igraph_lazy_adjlist_get(&outadj1, (igraph_integer_t) cand1); + inneis_2 = igraph_lazy_adjlist_get(&inadj2, (igraph_integer_t) cand2); + outneis_2 = igraph_lazy_adjlist_get(&outadj2, (igraph_integer_t) cand2); + if (VECTOR(indeg1)[cand1] != VECTOR(indeg2)[cand2] || + VECTOR(outdeg1)[cand1] != VECTOR(outdeg2)[cand2]) { + end = 1; + } + if (vertex_color1 && VECTOR(*vertex_color1)[cand1] != VECTOR(*vertex_color2)[cand2]) { + end = 1; + } + if (node_compat_fn && !node_compat_fn(graph1, graph2, + (igraph_integer_t) cand1, + (igraph_integer_t) cand2, arg)) { + end = 1; + } + + for (i = 0; !end && i < igraph_vector_size(inneis_1); i++) { + long int node = (long int) VECTOR(*inneis_1)[i]; + if (VECTOR(*core_1)[node] >= 0) { + long int node2 = (long int) VECTOR(*core_1)[node]; + /* check if there is a node2->cand2 edge */ + if (!igraph_vector_binsearch2(inneis_2, node2)) { + end = 1; + } else if (edge_color1 || edge_compat_fn) { + igraph_integer_t eid1, eid2; + igraph_get_eid(graph1, &eid1, (igraph_integer_t) node, + (igraph_integer_t) cand1, /*directed=*/ 1, + /*error=*/ 1); + igraph_get_eid(graph2, &eid2, (igraph_integer_t) node2, + (igraph_integer_t) cand2, /*directed=*/ 1, + /*error=*/ 1); + if (edge_color1 && VECTOR(*edge_color1)[(long int)eid1] != + VECTOR(*edge_color2)[(long int)eid2]) { + end = 1; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = 1; + } + } + } else { + if (VECTOR(in_1)[node] != 0) { + xin1++; + } + if (VECTOR(out_1)[node] != 0) { + xout1++; + } + } + } + for (i = 0; !end && i < igraph_vector_size(outneis_1); i++) { + long int node = (long int) VECTOR(*outneis_1)[i]; + if (VECTOR(*core_1)[node] >= 0) { + long int node2 = (long int) VECTOR(*core_1)[node]; + /* check if there is a cand2->node2 edge */ + if (!igraph_vector_binsearch2(outneis_2, node2)) { + end = 1; + } else if (edge_color1 || edge_compat_fn) { + igraph_integer_t eid1, eid2; + igraph_get_eid(graph1, &eid1, (igraph_integer_t) cand1, + (igraph_integer_t) node, /*directed=*/ 1, + /*error=*/ 1); + igraph_get_eid(graph2, &eid2, (igraph_integer_t) cand2, + (igraph_integer_t) node2, /*directed=*/ 1, + /*error=*/ 1); + if (edge_color1 && VECTOR(*edge_color1)[(long int)eid1] != + VECTOR(*edge_color2)[(long int)eid2]) { + end = 1; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = 1; + } + } + } else { + if (VECTOR(in_1)[node] != 0) { + xin1++; + } + if (VECTOR(out_1)[node] != 0) { + xout1++; + } + } + } + for (i = 0; !end && i < igraph_vector_size(inneis_2); i++) { + long int node = (long int) VECTOR(*inneis_2)[i]; + if (VECTOR(*core_2)[node] >= 0) { + long int node2 = (long int) VECTOR(*core_2)[node]; + /* check if there is a node2->cand1 edge */ + if (!igraph_vector_binsearch2(inneis_1, node2)) { + end = 1; + } else if (edge_color1 || edge_compat_fn) { + igraph_integer_t eid1, eid2; + igraph_get_eid(graph1, &eid1, (igraph_integer_t) node2, + (igraph_integer_t) cand1, /*directed=*/ 1, + /*error=*/ 1); + igraph_get_eid(graph2, &eid2, (igraph_integer_t) node, + (igraph_integer_t) cand2, /*directed=*/ 1, + /*error=*/ 1); + if (edge_color1 && VECTOR(*edge_color1)[(long int)eid1] != + VECTOR(*edge_color2)[(long int)eid2]) { + end = 1; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = 1; + } + } + } else { + if (VECTOR(in_2)[node] != 0) { + xin2++; + } + if (VECTOR(out_2)[node] != 0) { + xout2++; + } + } + } + for (i = 0; !end && i < igraph_vector_size(outneis_2); i++) { + long int node = (long int) VECTOR(*outneis_2)[i]; + if (VECTOR(*core_2)[node] >= 0) { + long int node2 = (long int) VECTOR(*core_2)[node]; + /* check if there is a cand1->node2 edge */ + if (!igraph_vector_binsearch2(outneis_1, node2)) { + end = 1; + } else if (edge_color1 || edge_compat_fn) { + igraph_integer_t eid1, eid2; + igraph_get_eid(graph1, &eid1, (igraph_integer_t) cand1, + (igraph_integer_t) node2, /*directed=*/ 1, + /*error=*/ 1); + igraph_get_eid(graph2, &eid2, (igraph_integer_t) cand2, + (igraph_integer_t) node, /*directed=*/ 1, + /*error=*/ 1); + if (edge_color1 && VECTOR(*edge_color1)[(long int)eid1] != + VECTOR(*edge_color2)[(long int)eid2]) { + end = 1; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = 1; + } + } + } else { + if (VECTOR(in_2)[node] != 0) { + xin2++; + } + if (VECTOR(out_2)[node] != 0) { + xout2++; + } + } + } + + if (!end && (xin1 == xin2 && xout1 == xout2)) { + /* Ok, we add the (cand1, cand2) pair to the mapping */ + depth += 1; + IGRAPH_CHECK(igraph_stack_push(&path, cand1)); + IGRAPH_CHECK(igraph_stack_push(&path, cand2)); + matched_nodes += 1; + VECTOR(*core_1)[cand1] = cand2; + VECTOR(*core_2)[cand2] = cand1; + + /* update in_*, out_* */ + if (VECTOR(in_1)[cand1] != 0) { + in_1_size -= 1; + } + if (VECTOR(out_1)[cand1] != 0) { + out_1_size -= 1; + } + if (VECTOR(in_2)[cand2] != 0) { + in_2_size -= 1; + } + if (VECTOR(out_2)[cand2] != 0) { + out_2_size -= 1; + } + + inneis_1 = igraph_lazy_adjlist_get(&inadj1, (igraph_integer_t) cand1); + for (i = 0; i < igraph_vector_size(inneis_1); i++) { + long int node = (long int) VECTOR(*inneis_1)[i]; + if (VECTOR(in_1)[node] == 0 && VECTOR(*core_1)[node] < 0) { + VECTOR(in_1)[node] = depth; + in_1_size += 1; + } + } + outneis_1 = igraph_lazy_adjlist_get(&outadj1, (igraph_integer_t) cand1); + for (i = 0; i < igraph_vector_size(outneis_1); i++) { + long int node = (long int) VECTOR(*outneis_1)[i]; + if (VECTOR(out_1)[node] == 0 && VECTOR(*core_1)[node] < 0) { + VECTOR(out_1)[node] = depth; + out_1_size += 1; + } + } + inneis_2 = igraph_lazy_adjlist_get(&inadj2, (igraph_integer_t) cand2); + for (i = 0; i < igraph_vector_size(inneis_2); i++) { + long int node = (long int) VECTOR(*inneis_2)[i]; + if (VECTOR(in_2)[node] == 0 && VECTOR(*core_2)[node] < 0) { + VECTOR(in_2)[node] = depth; + in_2_size += 1; + } + } + outneis_2 = igraph_lazy_adjlist_get(&outadj2, (igraph_integer_t) cand2); + for (i = 0; i < igraph_vector_size(outneis_2); i++) { + long int node = (long int) VECTOR(*outneis_2)[i]; + if (VECTOR(out_2)[node] == 0 && VECTOR(*core_2)[node] < 0) { + VECTOR(out_2)[node] = depth; + out_2_size += 1; + } + } + last1 = -1; last2 = -1; /* this the first time here */ + } else { + last1 = cand1; + last2 = cand2; + } + + } + + if (matched_nodes == no_of_nodes && isohandler_fn) { + if (!isohandler_fn(core_1, core_2, arg)) { + break; + } + } + } + + igraph_vector_destroy(&outdeg2); + igraph_vector_destroy(&outdeg1); + igraph_vector_destroy(&indeg2); + igraph_vector_destroy(&indeg1); + igraph_lazy_adjlist_destroy(&outadj2); + igraph_lazy_adjlist_destroy(&inadj2); + igraph_lazy_adjlist_destroy(&outadj1); + igraph_lazy_adjlist_destroy(&inadj1); + igraph_stack_destroy(&path); + igraph_vector_destroy(&out_2); + igraph_vector_destroy(&out_1); + igraph_vector_destroy(&in_2); + igraph_vector_destroy(&in_1); + IGRAPH_FINALLY_CLEAN(13); + if (!map21) { + igraph_vector_destroy(core_2); + IGRAPH_FINALLY_CLEAN(1); + } + if (!map12) { + igraph_vector_destroy(core_1); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +typedef struct { + igraph_isocompat_t *node_compat_fn, *edge_compat_fn; + void *arg, *carg; +} igraph_i_iso_cb_data_t; + +static igraph_bool_t igraph_i_isocompat_node_cb( + const igraph_t *graph1, + const igraph_t *graph2, + const igraph_integer_t g1_num, + const igraph_integer_t g2_num, + void *arg) { + igraph_i_iso_cb_data_t *data = arg; + return data->node_compat_fn(graph1, graph2, g1_num, g2_num, data->carg); +} + +static igraph_bool_t igraph_i_isocompat_edge_cb( + const igraph_t *graph1, + const igraph_t *graph2, + const igraph_integer_t g1_num, + const igraph_integer_t g2_num, + void *arg) { + igraph_i_iso_cb_data_t *data = arg; + return data->edge_compat_fn(graph1, graph2, g1_num, g2_num, data->carg); +} + +static igraph_bool_t igraph_i_isomorphic_vf2(igraph_vector_t *map12, + igraph_vector_t *map21, + void *arg) { + igraph_i_iso_cb_data_t *data = arg; + igraph_bool_t *iso = data->arg; + IGRAPH_UNUSED(map12); IGRAPH_UNUSED(map21); + *iso = 1; + return 0; /* don't need to continue */ +} + +/** + * \function igraph_isomorphic_vf2 + * \brief Isomorphism via VF2 + * + * + * This function performs the VF2 algorithm via calling \ref + * igraph_isomorphic_function_vf2(). + * + * Note that this function cannot be used for + * deciding subgraph isomorphism, use \ref igraph_subisomorphic_vf2() + * for that. + * \param graph1 The first graph, may be directed or undirected. + * \param graph2 The second graph. It must have the same directedness + * as \p graph1, otherwise an error is reported. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param iso Pointer to a logical constant, the result of the + * algorithm will be placed here. + * \param map12 Pointer to an initialized vector or a NULL pointer. If not + * a NULL pointer then the mapping from \p graph1 to \p graph2 is + * stored here. If the graphs are not isomorphic then the vector is + * cleared (ie. has zero elements). + * \param map21 Pointer to an initialized vector or a NULL pointer. If not + * a NULL pointer then the mapping from \p graph2 to \p graph1 is + * stored here. If the graphs are not isomorphic then the vector is + * cleared (ie. has zero elements). + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn + * and \p edge_compat_fn. + * \return Error code. + * + * \sa \ref igraph_subisomorphic_vf2(), + * \ref igraph_count_isomorphisms_vf2(), + * \ref igraph_get_isomorphisms_vf2(), + * + * Time complexity: exponential, what did you expect? + * + * \example examples/simple/igraph_isomorphic_vf2.c + */ + +int igraph_isomorphic_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_bool_t *iso, igraph_vector_t *map12, + igraph_vector_t *map21, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, iso, arg }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : 0; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : 0; + *iso = 0; + IGRAPH_CHECK(igraph_isomorphic_function_vf2(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + map12, map21, + (igraph_isohandler_t*) + igraph_i_isomorphic_vf2, + ncb, ecb, &data)); + if (! *iso) { + if (map12) { + igraph_vector_clear(map12); + } + if (map21) { + igraph_vector_clear(map21); + } + } + return 0; +} + +static igraph_bool_t igraph_i_count_isomorphisms_vf2( + const igraph_vector_t *map12, + const igraph_vector_t *map21, + void *arg) { + igraph_i_iso_cb_data_t *data = arg; + igraph_integer_t *count = data->arg; + IGRAPH_UNUSED(map12); IGRAPH_UNUSED(map21); + *count += 1; + return 1; /* always continue */ +} + +/** + * \function igraph_count_isomorphisms_vf2 + * Number of isomorphisms via VF2 + * + * This function counts the number of isomorphic mappings between two + * graphs. It uses the generic \ref igraph_isomorphic_function_vf2() + * function. + * \param graph1 The first input graph, may be directed or undirected. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1, or an error will be reported. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param count Point to an integer, the result will be stored here. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn and + * \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +int igraph_count_isomorphisms_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_integer_t *count, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, + count, arg + }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : 0; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : 0; + *count = 0; + IGRAPH_CHECK(igraph_isomorphic_function_vf2(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + 0, 0, + (igraph_isohandler_t*) + igraph_i_count_isomorphisms_vf2, + ncb, ecb, &data)); + return 0; +} + +static void igraph_i_get_isomorphisms_free(igraph_vector_ptr_t *data) { + long int i, n = igraph_vector_ptr_size(data); + for (i = 0; i < n; i++) { + igraph_vector_t *vec = VECTOR(*data)[i]; + igraph_vector_destroy(vec); + igraph_free(vec); + } +} + +static igraph_bool_t igraph_i_get_isomorphisms_vf2( + const igraph_vector_t *map12, + const igraph_vector_t *map21, + void *arg) { + + igraph_i_iso_cb_data_t *data = arg; + igraph_vector_ptr_t *ptrvector = data->arg; + igraph_vector_t *newvector = igraph_Calloc(1, igraph_vector_t); + IGRAPH_UNUSED(map12); + if (!newvector) { + igraph_error("Out of memory", __FILE__, __LINE__, IGRAPH_ENOMEM); + return 0; /* stop right here */ + } + IGRAPH_FINALLY(igraph_free, newvector); + IGRAPH_CHECK(igraph_vector_copy(newvector, map21)); + IGRAPH_FINALLY(igraph_vector_destroy, newvector); + IGRAPH_CHECK(igraph_vector_ptr_push_back(ptrvector, newvector)); + IGRAPH_FINALLY_CLEAN(2); + + return 1; /* continue finding subisomorphisms */ +} + +/** + * \function igraph_get_isomorphisms_vf2 + * Collect the isomorphic mappings + * + * This function finds all the isomorphic mappings between two + * graphs. It uses the \ref igraph_isomorphic_function_vf2() + * function. Call the function with the same graph as \p graph1 and \p + * graph2 to get automorphisms. + * \param graph1 The first input graph, may be directed or undirected. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1, or an error will be reported. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param maps Pointer vector. On return it is empty if the input graphs + * are no isomorphic. Otherwise it contains pointers to + * igraph_vector_t objects, each vector is an + * isomorphic mapping of \p graph2 to \p graph1. Please note that + * you need to 1) Destroy the vectors via \ref + * igraph_vector_destroy(), 2) free them via + * free() and then 3) call \ref + * igraph_vector_ptr_destroy() on the pointer vector to deallocate all + * memory when \p maps is no longer needed. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn + * and \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +int igraph_get_isomorphisms_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_ptr_t *maps, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, maps, arg }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : 0; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : 0; + + igraph_vector_ptr_clear(maps); + IGRAPH_FINALLY(igraph_i_get_isomorphisms_free, maps); + IGRAPH_CHECK(igraph_isomorphic_function_vf2(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + 0, 0, + (igraph_isohandler_t*) + igraph_i_get_isomorphisms_vf2, + ncb, ecb, &data)); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + + +/** + * \function igraph_subisomorphic + * Decide subgraph isomorphism + * + * Check whether \p graph2 is isomorphic to a subgraph of \p graph1. + * Currently this function just calls \ref igraph_subisomorphic_vf2() + * for all graphs. + * \param graph1 The first input graph, may be directed or + * undirected. This is supposed to be the bigger graph. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph2, or an error is triggered. This is + * supposed to be the smaller graph. + * \param iso Pointer to a boolean, the result is stored here. + * \return Error code. + * + * Time complexity: exponential. + */ + +int igraph_subisomorphic(const igraph_t *graph1, const igraph_t *graph2, + igraph_bool_t *iso) { + + return igraph_subisomorphic_vf2(graph1, graph2, 0, 0, 0, 0, iso, 0, 0, 0, 0, 0); +} + +/** + * \function igraph_subisomorphic_function_vf2 + * Generic VF2 function for subgraph isomorphism problems + * + * This function is the pair of \ref igraph_isomorphic_function_vf2(), + * for subgraph isomorphism problems. It searches for subgraphs of \p + * graph1 which are isomorphic to \p graph2. When it founds an + * isomorphic mapping it calls the supplied callback \p isohandler_fn. + * The mapping (and its inverse) and the additional \p arg argument + * are supplied to the callback. + * \param graph1 The first input graph, may be directed or + * undirected. This is supposed to be the larger graph. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1. This is supposed to be the smaller + * graph. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the subgraph isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param map12 Pointer to a vector or \c NULL. If not \c NULL, then an + * isomorphic mapping from \p graph1 to \p graph2 is stored here. + * \param map21 Pointer to a vector ot \c NULL. If not \c NULL, then + * an isomorphic mapping from \p graph2 to \p graph1 is stored + * here. + * \param isohandler_fn A pointer to a function of type \ref + * igraph_isohandler_t. This will be called whenever a subgraph + * isomorphism is found. If the function returns with a non-zero value + * then the search is continued, otherwise it stops and the function + * returns. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p isohandler_fn, \p + * node_compat_fn and \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +int igraph_subisomorphic_function_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_t *map12, + igraph_vector_t *map21, + igraph_isohandler_t *isohandler_fn, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + long int no_of_nodes1 = igraph_vcount(graph1), + no_of_nodes2 = igraph_vcount(graph2); + long int no_of_edges1 = igraph_ecount(graph1), + no_of_edges2 = igraph_ecount(graph2); + igraph_vector_t mycore_1, mycore_2, *core_1 = &mycore_1, *core_2 = &mycore_2; + igraph_vector_t in_1, in_2, out_1, out_2; + long int in_1_size = 0, in_2_size = 0, out_1_size = 0, out_2_size = 0; + igraph_vector_t *inneis_1, *inneis_2, *outneis_1, *outneis_2; + long int matched_nodes = 0; + long int depth; + long int cand1, cand2; + long int last1, last2; + igraph_stack_t path; + igraph_lazy_adjlist_t inadj1, inadj2, outadj1, outadj2; + igraph_vector_t indeg1, indeg2, outdeg1, outdeg2; + + if (igraph_is_directed(graph1) != igraph_is_directed(graph2)) { + IGRAPH_ERROR("Cannot compare directed and undirected graphs", + IGRAPH_EINVAL); + } + + if (no_of_nodes1 < no_of_nodes2 || + no_of_edges1 < no_of_edges2) { + return 0; + } + + if ( (vertex_color1 && !vertex_color2) || (!vertex_color1 && vertex_color2) ) { + IGRAPH_WARNING("Only one graph is vertex colored, colors will be ignored"); + vertex_color1 = vertex_color2 = 0; + } + + if ( (edge_color1 && !edge_color2) || (!edge_color1 && edge_color2) ) { + IGRAPH_WARNING("Only one graph is edge colored, colors will be ignored"); + edge_color1 = edge_color2 = 0; + } + + if (vertex_color1) { + if (igraph_vector_int_size(vertex_color1) != no_of_nodes1 || + igraph_vector_int_size(vertex_color2) != no_of_nodes2) { + IGRAPH_ERROR("Invalid vertex color vector length", IGRAPH_EINVAL); + } + } + + if (edge_color1) { + if (igraph_vector_int_size(edge_color1) != no_of_edges1 || + igraph_vector_int_size(edge_color2) != no_of_edges2) { + IGRAPH_ERROR("Invalid edge color vector length", IGRAPH_EINVAL); + } + } + + /* Check color distribution */ + if (vertex_color1) { + /* TODO */ + } + + /* Check edge color distribution */ + if (edge_color1) { + /* TODO */ + } + + if (map12) { + core_1 = map12; + IGRAPH_CHECK(igraph_vector_resize(core_1, no_of_nodes1)); + } else { + IGRAPH_VECTOR_INIT_FINALLY(core_1, no_of_nodes1); + } + igraph_vector_fill(core_1, -1); + if (map21) { + core_2 = map21; + IGRAPH_CHECK(igraph_vector_resize(core_2, no_of_nodes2)); + } else { + IGRAPH_VECTOR_INIT_FINALLY(core_2, no_of_nodes2); + } + igraph_vector_fill(core_2, -1); + IGRAPH_VECTOR_INIT_FINALLY(&in_1, no_of_nodes1); + IGRAPH_VECTOR_INIT_FINALLY(&in_2, no_of_nodes2); + IGRAPH_VECTOR_INIT_FINALLY(&out_1, no_of_nodes1); + IGRAPH_VECTOR_INIT_FINALLY(&out_2, no_of_nodes2); + IGRAPH_CHECK(igraph_stack_init(&path, 0)); + IGRAPH_FINALLY(igraph_stack_destroy, &path); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph1, &inadj1, IGRAPH_IN, + IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &inadj1); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph1, &outadj1, IGRAPH_OUT, + IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &outadj1); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph2, &inadj2, IGRAPH_IN, + IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &inadj2); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph2, &outadj2, IGRAPH_OUT, + IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &outadj2); + IGRAPH_VECTOR_INIT_FINALLY(&indeg1, 0); + IGRAPH_VECTOR_INIT_FINALLY(&indeg2, 0); + IGRAPH_VECTOR_INIT_FINALLY(&outdeg1, 0); + IGRAPH_VECTOR_INIT_FINALLY(&outdeg2, 0); + + IGRAPH_CHECK(igraph_stack_reserve(&path, no_of_nodes2 * 2)); + IGRAPH_CHECK(igraph_degree(graph1, &indeg1, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph2, &indeg2, igraph_vss_all(), + IGRAPH_IN, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph1, &outdeg1, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + IGRAPH_CHECK(igraph_degree(graph2, &outdeg2, igraph_vss_all(), + IGRAPH_OUT, IGRAPH_LOOPS)); + + depth = 0; last1 = -1; last2 = -1; + while (depth >= 0) { + long int i; + + IGRAPH_ALLOW_INTERRUPTION(); + + cand1 = -1; cand2 = -1; + /* Search for the next pair to try */ + if ((in_1_size < in_2_size) || + (out_1_size < out_2_size)) { + /* step back, nothing to do */ + } else if (out_1_size > 0 && out_2_size > 0) { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes2) { + if (VECTOR(out_2)[i] > 0 && VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1 now, it should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes1) { + if (VECTOR(out_1)[i] > 0 && VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } else if (in_1_size > 0 && in_2_size > 0) { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes2) { + if (VECTOR(in_2)[i] > 0 && VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1 now, should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes1) { + if (VECTOR(in_1)[i] > 0 && VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } else { + /**************************************************************/ + /* cand2, search not always needed */ + if (last2 >= 0) { + cand2 = last2; + } else { + i = 0; + while (cand2 < 0 && i < no_of_nodes2) { + if (VECTOR(*core_2)[i] < 0) { + cand2 = i; + } + i++; + } + } + /* search for cand1, should be bigger than last1 */ + i = last1 + 1; + while (cand1 < 0 && i < no_of_nodes1) { + if (VECTOR(*core_1)[i] < 0) { + cand1 = i; + } + i++; + } + } + + /* Ok, we have cand1, cand2 as candidates. Or not? */ + if (cand1 < 0 || cand2 < 0) { + /**************************************************************/ + /* dead end, step back, if possible. Otherwise we'll terminate */ + if (depth >= 1) { + last2 = (long int) igraph_stack_pop(&path); + last1 = (long int) igraph_stack_pop(&path); + matched_nodes -= 1; + VECTOR(*core_1)[last1] = -1; + VECTOR(*core_2)[last2] = -1; + + if (VECTOR(in_1)[last1] != 0) { + in_1_size += 1; + } + if (VECTOR(out_1)[last1] != 0) { + out_1_size += 1; + } + if (VECTOR(in_2)[last2] != 0) { + in_2_size += 1; + } + if (VECTOR(out_2)[last2] != 0) { + out_2_size += 1; + } + + inneis_1 = igraph_lazy_adjlist_get(&inadj1, (igraph_integer_t) last1); + for (i = 0; i < igraph_vector_size(inneis_1); i++) { + long int node = (long int) VECTOR(*inneis_1)[i]; + if (VECTOR(in_1)[node] == depth) { + VECTOR(in_1)[node] = 0; + in_1_size -= 1; + } + } + outneis_1 = igraph_lazy_adjlist_get(&outadj1, (igraph_integer_t) last1); + for (i = 0; i < igraph_vector_size(outneis_1); i++) { + long int node = (long int) VECTOR(*outneis_1)[i]; + if (VECTOR(out_1)[node] == depth) { + VECTOR(out_1)[node] = 0; + out_1_size -= 1; + } + } + inneis_2 = igraph_lazy_adjlist_get(&inadj2, (igraph_integer_t) last2); + for (i = 0; i < igraph_vector_size(inneis_2); i++) { + long int node = (long int) VECTOR(*inneis_2)[i]; + if (VECTOR(in_2)[node] == depth) { + VECTOR(in_2)[node] = 0; + in_2_size -= 1; + } + } + outneis_2 = igraph_lazy_adjlist_get(&outadj2, (igraph_integer_t) last2); + for (i = 0; i < igraph_vector_size(outneis_2); i++) { + long int node = (long int) VECTOR(*outneis_2)[i]; + if (VECTOR(out_2)[node] == depth) { + VECTOR(out_2)[node] = 0; + out_2_size -= 1; + } + } + + } /* end of stepping back */ + + depth -= 1; + + } else { + /**************************************************************/ + /* step forward if worth, check if worth first */ + long int xin1 = 0, xin2 = 0, xout1 = 0, xout2 = 0; + igraph_bool_t end = 0; + inneis_1 = igraph_lazy_adjlist_get(&inadj1, (igraph_integer_t) cand1); + outneis_1 = igraph_lazy_adjlist_get(&outadj1, (igraph_integer_t) cand1); + inneis_2 = igraph_lazy_adjlist_get(&inadj2, (igraph_integer_t) cand2); + outneis_2 = igraph_lazy_adjlist_get(&outadj2, (igraph_integer_t) cand2); + if (VECTOR(indeg1)[cand1] < VECTOR(indeg2)[cand2] || + VECTOR(outdeg1)[cand1] < VECTOR(outdeg2)[cand2]) { + end = 1; + } + if (vertex_color1 && VECTOR(*vertex_color1)[cand1] != VECTOR(*vertex_color2)[cand2]) { + end = 1; + } + if (node_compat_fn && !node_compat_fn(graph1, graph2, + (igraph_integer_t) cand1, + (igraph_integer_t) cand2, arg)) { + end = 1; + } + + for (i = 0; !end && i < igraph_vector_size(inneis_1); i++) { + long int node = (long int) VECTOR(*inneis_1)[i]; + if (VECTOR(*core_1)[node] < 0) { + if (VECTOR(in_1)[node] != 0) { + xin1++; + } + if (VECTOR(out_1)[node] != 0) { + xout1++; + } + } + } + for (i = 0; !end && i < igraph_vector_size(outneis_1); i++) { + long int node = (long int) VECTOR(*outneis_1)[i]; + if (VECTOR(*core_1)[node] < 0) { + if (VECTOR(in_1)[node] != 0) { + xin1++; + } + if (VECTOR(out_1)[node] != 0) { + xout1++; + } + } + } + for (i = 0; !end && i < igraph_vector_size(inneis_2); i++) { + long int node = (long int) VECTOR(*inneis_2)[i]; + if (VECTOR(*core_2)[node] >= 0) { + long int node2 = (long int) VECTOR(*core_2)[node]; + /* check if there is a node2->cand1 edge */ + if (!igraph_vector_binsearch2(inneis_1, node2)) { + end = 1; + } else if (edge_color1 || edge_compat_fn) { + igraph_integer_t eid1, eid2; + igraph_get_eid(graph1, &eid1, (igraph_integer_t) node2, + (igraph_integer_t) cand1, /*directed=*/ 1, + /*error=*/ 1); + igraph_get_eid(graph2, &eid2, (igraph_integer_t) node, + (igraph_integer_t) cand2, /*directed=*/ 1, + /*error=*/ 1); + if (edge_color1 && VECTOR(*edge_color1)[(long int)eid1] != + VECTOR(*edge_color2)[(long int)eid2]) { + end = 1; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = 1; + } + } + } else { + if (VECTOR(in_2)[node] != 0) { + xin2++; + } + if (VECTOR(out_2)[node] != 0) { + xout2++; + } + } + } + for (i = 0; !end && i < igraph_vector_size(outneis_2); i++) { + long int node = (long int) VECTOR(*outneis_2)[i]; + if (VECTOR(*core_2)[node] >= 0) { + long int node2 = (long int) VECTOR(*core_2)[node]; + /* check if there is a cand1->node2 edge */ + if (!igraph_vector_binsearch2(outneis_1, node2)) { + end = 1; + } else if (edge_color1 || edge_compat_fn) { + igraph_integer_t eid1, eid2; + igraph_get_eid(graph1, &eid1, (igraph_integer_t) cand1, + (igraph_integer_t) node2, /*directed=*/ 1, + /*error=*/ 1); + igraph_get_eid(graph2, &eid2, (igraph_integer_t) cand2, + (igraph_integer_t) node, /*directed=*/ 1, + /*error=*/ 1); + if (edge_color1 && VECTOR(*edge_color1)[(long int)eid1] != + VECTOR(*edge_color2)[(long int)eid2]) { + end = 1; + } + if (edge_compat_fn && !edge_compat_fn(graph1, graph2, + eid1, eid2, arg)) { + end = 1; + } + } + } else { + if (VECTOR(in_2)[node] != 0) { + xin2++; + } + if (VECTOR(out_2)[node] != 0) { + xout2++; + } + } + } + + if (!end && (xin1 >= xin2 && xout1 >= xout2)) { + /* Ok, we add the (cand1, cand2) pair to the mapping */ + depth += 1; + IGRAPH_CHECK(igraph_stack_push(&path, cand1)); + IGRAPH_CHECK(igraph_stack_push(&path, cand2)); + matched_nodes += 1; + VECTOR(*core_1)[cand1] = cand2; + VECTOR(*core_2)[cand2] = cand1; + + /* update in_*, out_* */ + if (VECTOR(in_1)[cand1] != 0) { + in_1_size -= 1; + } + if (VECTOR(out_1)[cand1] != 0) { + out_1_size -= 1; + } + if (VECTOR(in_2)[cand2] != 0) { + in_2_size -= 1; + } + if (VECTOR(out_2)[cand2] != 0) { + out_2_size -= 1; + } + + inneis_1 = igraph_lazy_adjlist_get(&inadj1, (igraph_integer_t) cand1); + for (i = 0; i < igraph_vector_size(inneis_1); i++) { + long int node = (long int) VECTOR(*inneis_1)[i]; + if (VECTOR(in_1)[node] == 0 && VECTOR(*core_1)[node] < 0) { + VECTOR(in_1)[node] = depth; + in_1_size += 1; + } + } + outneis_1 = igraph_lazy_adjlist_get(&outadj1, (igraph_integer_t) cand1); + for (i = 0; i < igraph_vector_size(outneis_1); i++) { + long int node = (long int) VECTOR(*outneis_1)[i]; + if (VECTOR(out_1)[node] == 0 && VECTOR(*core_1)[node] < 0) { + VECTOR(out_1)[node] = depth; + out_1_size += 1; + } + } + inneis_2 = igraph_lazy_adjlist_get(&inadj2, (igraph_integer_t) cand2); + for (i = 0; i < igraph_vector_size(inneis_2); i++) { + long int node = (long int) VECTOR(*inneis_2)[i]; + if (VECTOR(in_2)[node] == 0 && VECTOR(*core_2)[node] < 0) { + VECTOR(in_2)[node] = depth; + in_2_size += 1; + } + } + outneis_2 = igraph_lazy_adjlist_get(&outadj2, (igraph_integer_t) cand2); + for (i = 0; i < igraph_vector_size(outneis_2); i++) { + long int node = (long int) VECTOR(*outneis_2)[i]; + if (VECTOR(out_2)[node] == 0 && VECTOR(*core_2)[node] < 0) { + VECTOR(out_2)[node] = depth; + out_2_size += 1; + } + } + last1 = -1; last2 = -1; /* this the first time here */ + } else { + last1 = cand1; + last2 = cand2; + } + + } + + if (matched_nodes == no_of_nodes2 && isohandler_fn) { + if (!isohandler_fn(core_1, core_2, arg)) { + break; + } + } + } + + igraph_vector_destroy(&outdeg2); + igraph_vector_destroy(&outdeg1); + igraph_vector_destroy(&indeg2); + igraph_vector_destroy(&indeg1); + igraph_lazy_adjlist_destroy(&outadj2); + igraph_lazy_adjlist_destroy(&inadj2); + igraph_lazy_adjlist_destroy(&outadj1); + igraph_lazy_adjlist_destroy(&inadj1); + igraph_stack_destroy(&path); + igraph_vector_destroy(&out_2); + igraph_vector_destroy(&out_1); + igraph_vector_destroy(&in_2); + igraph_vector_destroy(&in_1); + IGRAPH_FINALLY_CLEAN(13); + if (!map21) { + igraph_vector_destroy(core_2); + IGRAPH_FINALLY_CLEAN(1); + } + if (!map12) { + igraph_vector_destroy(core_1); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +static igraph_bool_t igraph_i_subisomorphic_vf2( + const igraph_vector_t *map12, + const igraph_vector_t *map21, + void *arg) { + igraph_i_iso_cb_data_t *data = arg; + igraph_bool_t *iso = data->arg; + IGRAPH_UNUSED(map12); IGRAPH_UNUSED(map21); + *iso = 1; + return 0; /* stop */ +} + +/** + * \function igraph_subisomorphic_vf2 + * Decide subgraph isomorphism using VF2 + * + * Decides whether a subgraph of \p graph1 is isomorphic to \p + * graph2. It uses \ref igraph_subisomorphic_function_vf2(). + * \param graph1 The first input graph, may be directed or + * undirected. This is supposed to be the larger graph. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1. This is supposed to be the smaller + * graph. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the subgraph isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param iso Pointer to a boolean. The result of the decision problem + * is stored here. + * \param map12 Pointer to a vector or \c NULL. If not \c NULL, then an + * isomorphic mapping from \p graph1 to \p graph2 is stored here. + * \param map21 Pointer to a vector ot \c NULL. If not \c NULL, then + * an isomorphic mapping from \p graph2 to \p graph1 is stored + * here. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn + * and \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +int igraph_subisomorphic_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_bool_t *iso, igraph_vector_t *map12, + igraph_vector_t *map21, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, iso, arg }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : 0; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : 0; + + *iso = 0; + IGRAPH_CHECK(igraph_subisomorphic_function_vf2(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + map12, map21, + (igraph_isohandler_t *) + igraph_i_subisomorphic_vf2, + ncb, ecb, &data)); + if (! *iso) { + if (map12) { + igraph_vector_clear(map12); + } + if (map21) { + igraph_vector_clear(map21); + } + } + return 0; +} + +static igraph_bool_t igraph_i_count_subisomorphisms_vf2( + const igraph_vector_t *map12, + const igraph_vector_t *map21, + void *arg) { + igraph_i_iso_cb_data_t *data = arg; + igraph_integer_t *count = data->arg; + IGRAPH_UNUSED(map12); IGRAPH_UNUSED(map21); + *count += 1; + return 1; /* always continue */ +} + +/** + * \function igraph_count_subisomorphisms_vf2 + * Number of subgraph isomorphisms using VF2 + * + * Count the number of isomorphisms between subgraphs of \p graph1 and + * \p graph2. This function uses \ref + * igraph_subisomorphic_function_vf2(). + * \param graph1 The first input graph, may be directed or + * undirected. This is supposed to be the larger graph. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1. This is supposed to be the smaller + * graph. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the subgraph isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param count Pointer to an integer. The number of subgraph + * isomorphisms is stored here. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn and + * \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +int igraph_count_subisomorphisms_vf2(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_integer_t *count, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, + count, arg + }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : 0; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : 0; + *count = 0; + IGRAPH_CHECK(igraph_subisomorphic_function_vf2(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + 0, 0, + (igraph_isohandler_t*) + igraph_i_count_subisomorphisms_vf2, + ncb, ecb, &data)); + return 0; +} + +static void igraph_i_get_subisomorphisms_free(igraph_vector_ptr_t *data) { + long int i, n = igraph_vector_ptr_size(data); + for (i = 0; i < n; i++) { + igraph_vector_t *vec = VECTOR(*data)[i]; + igraph_vector_destroy(vec); + igraph_free(vec); + } +} + +static igraph_bool_t igraph_i_get_subisomorphisms_vf2( + const igraph_vector_t *map12, + const igraph_vector_t *map21, + void *arg) { + + igraph_i_iso_cb_data_t *data = arg; + igraph_vector_ptr_t *vector = data->arg; + igraph_vector_t *newvector = igraph_Calloc(1, igraph_vector_t); + IGRAPH_UNUSED(map12); + if (!newvector) { + igraph_error("Out of memory", __FILE__, __LINE__, IGRAPH_ENOMEM); + return 0; /* stop right here */ + } + IGRAPH_FINALLY(igraph_free, newvector); + IGRAPH_CHECK(igraph_vector_copy(newvector, map21)); + IGRAPH_FINALLY(igraph_vector_destroy, newvector); + IGRAPH_CHECK(igraph_vector_ptr_push_back(vector, newvector)); + IGRAPH_FINALLY_CLEAN(2); + + return 1; /* continue finding subisomorphisms */ +} + +/** + * \function igraph_get_subisomorphisms_vf2 + * Return all subgraph isomorphic mappings + * + * This function collects all isomorphic mappings of \p graph2 to a + * subgraph of \p graph1. It uses the \ref + * igraph_subisomorphic_function_vf2() function. + * \param graph1 The first input graph, may be directed or + * undirected. This is supposed to be the larger graph. + * \param graph2 The second input graph, it must have the same + * directedness as \p graph1. This is supposed to be the smaller + * graph. + * \param vertex_color1 An optional color vector for the first graph. If + * color vectors are given for both graphs, then the subgraph isomorphism is + * calculated on the colored graphs; i.e. two vertices can match + * only if their color also matches. Supply a null pointer here if + * your graphs are not colored. + * \param vertex_color2 An optional color vector for the second graph. See + * the previous argument for explanation. + * \param edge_color1 An optional edge color vector for the first + * graph. The matching edges in the two graphs must have matching + * colors as well. Supply a null pointer here if your graphs are not + * edge-colored. + * \param edge_color2 The edge color vector for the second graph. + * \param maps Pointer vector. On return it contains pointers to + * igraph_vector_t objects, each vector is an + * isomorphic mapping of \p graph2 to a subgraph of \p graph1. Please note that + * you need to 1) Destroy the vectors via \ref + * igraph_vector_destroy(), 2) free them via + * free() and then 3) call \ref + * igraph_vector_ptr_destroy() on the pointer vector to deallocate all + * memory when \p maps is no longer needed. + * \param node_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two nodes are compatible. + * \param edge_compat_fn A pointer to a function of type \ref + * igraph_isocompat_t. This function will be called by the algorithm to + * determine whether two edges are compatible. + * \param arg Extra argument to supply to functions \p node_compat_fn + * and \p edge_compat_fn. + * \return Error code. + * + * Time complexity: exponential. + */ + +int igraph_get_subisomorphisms_vf2(const igraph_t *graph1, + const igraph_t *graph2, + const igraph_vector_int_t *vertex_color1, + const igraph_vector_int_t *vertex_color2, + const igraph_vector_int_t *edge_color1, + const igraph_vector_int_t *edge_color2, + igraph_vector_ptr_t *maps, + igraph_isocompat_t *node_compat_fn, + igraph_isocompat_t *edge_compat_fn, + void *arg) { + + igraph_i_iso_cb_data_t data = { node_compat_fn, edge_compat_fn, maps, arg }; + igraph_isocompat_t *ncb = node_compat_fn ? igraph_i_isocompat_node_cb : 0; + igraph_isocompat_t *ecb = edge_compat_fn ? igraph_i_isocompat_edge_cb : 0; + + igraph_vector_ptr_clear(maps); + IGRAPH_FINALLY(igraph_i_get_subisomorphisms_free, maps); + IGRAPH_CHECK(igraph_subisomorphic_function_vf2(graph1, graph2, + vertex_color1, vertex_color2, + edge_color1, edge_color2, + 0, 0, + (igraph_isohandler_t*) + igraph_i_get_subisomorphisms_vf2, + ncb, ecb, &data)); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \function igraph_permute_vertices + * Permute the vertices + * + * This function creates a new graph from the input graph by permuting + * its vertices according to the specified mapping. Call this function + * with the output of \ref igraph_canonical_permutation() to create + * the canonical form of a graph. + * \param graph The input graph. + * \param res Pointer to an uninitialized graph object. The new graph + * is created here. + * \param permutation The permutation to apply. Vertex 0 is mapped to + * the first element of the vector, vertex 1 to the second, + * etc. Note that it is not checked that the vector contains every + * element only once, and no range checking is performed either. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in terms of the number of + * vertices and edges. + */ + +int igraph_permute_vertices(const igraph_t *graph, igraph_t *res, + const igraph_vector_t *permutation) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_vector_t edges; + long int i, p = 0; + + if (igraph_vector_size(permutation) != no_of_nodes) { + IGRAPH_ERROR("Permute vertices: invalid permutation vector size", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edges, no_of_edges * 2); + + for (i = 0; i < no_of_edges; i++) { + VECTOR(edges)[p++] = VECTOR(*permutation)[ (long int) IGRAPH_FROM(graph, i) ]; + VECTOR(edges)[p++] = VECTOR(*permutation)[ (long int) IGRAPH_TO(graph, i) ]; + } + + IGRAPH_CHECK(igraph_create(res, &edges, (igraph_integer_t) no_of_nodes, + igraph_is_directed(graph))); + + /* Attributes */ + if (graph->attr) { + igraph_vector_t index; + igraph_vector_t vtypes; + IGRAPH_I_ATTRIBUTE_DESTROY(res); + IGRAPH_I_ATTRIBUTE_COPY(res, graph, /*graph=*/1, /*vertex=*/0, /*edge=*/1); + IGRAPH_VECTOR_INIT_FINALLY(&vtypes, 0); + IGRAPH_CHECK(igraph_i_attribute_get_info(graph, 0, 0, 0, &vtypes, 0, 0)); + if (igraph_vector_size(&vtypes) != 0) { + IGRAPH_VECTOR_INIT_FINALLY(&index, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(index)[ (long int) VECTOR(*permutation)[i] ] = i; + } + IGRAPH_CHECK(igraph_i_attribute_permute_vertices(graph, res, &index)); + igraph_vector_destroy(&index); + IGRAPH_FINALLY_CLEAN(1); + } + igraph_vector_destroy(&vtypes); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \section about_bliss + * + * + * BLISS is a successor of the famous NAUTY algorithm and + * implementation. While using the same ideas in general, with better + * heuristics and data structures BLISS outperforms NAUTY on most + * graphs. + * + * + * + * BLISS was developed and implemented by Tommi Junttila and Petteri Kaski at + * Helsinki University of Technology, Finland. For more information, + * see the BLISS homepage at http://www.tcs.hut.fi/Software/bliss/ and the publication + * Tommi Junttila, Petteri Kaski: "Engineering an Efficient Canonical Labeling + * Tool for Large and Sparse Graphs" at https://doi.org/10.1137/1.9781611972870.13 + * + * + * + * BLISS works with both directed graphs and undirected graphs. It supports graphs with + * self-loops, but not graphs with multi-edges. + * + * + * + * BLISS version 0.73 is included in igraph. + * + */ + +/** + * \function igraph_isomorphic_bliss + * Graph isomorphism via BLISS + * + * This function uses the BLISS graph isomorphism algorithm, a + * successor of the famous NAUTY algorithm and implementation. BLISS + * is open source and licensed according to the GNU GPL. See + * http://www.tcs.hut.fi/Software/bliss/index.html for + * details. Currently the 0.73 version of BLISS is included in igraph. + * + * + * + * \param graph1 The first input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param graph2 The second input graph. Multiple edges between the same nodes + * are not supported and will cause an incorrect result to be returned. + * \param colors1 An optional vertex color vector for the first graph. Supply a + * null pointer if your graph is not colored. + * \param colors2 An optional vertex color vector for the second graph. Supply a + * null pointer if your graph is not colored. + * \param iso Pointer to a boolean, the result is stored here. + * \param map12 A vector or \c NULL pointer. If not \c NULL then an + * isomorphic mapping from \p graph1 to \p graph2 is stored here. + * If the input graphs are not isomorphic then this vector is + * cleared, i.e. it will have length zero. + * \param map21 Similar to \p map12, but for the mapping from \p + * graph2 to \p graph1. + * \param sh Splitting heuristics to be used for the graphs. See + * \ref igraph_bliss_sh_t. + * \param info1 If not \c NULL, information about the canonization of + * the first input graph is stored here. See \ref igraph_bliss_info_t + * for details. Note that if the two graphs have different number + * of vertices or edges, then this is not filled. + * \param info2 Same as \p info1, but for the second graph. + * \return Error code. + * + * Time complexity: exponential, but in practice it is quite fast. + */ + +int igraph_isomorphic_bliss(const igraph_t *graph1, const igraph_t *graph2, + const igraph_vector_int_t *colors1, const igraph_vector_int_t *colors2, + igraph_bool_t *iso, igraph_vector_t *map12, + igraph_vector_t *map21, igraph_bliss_sh_t sh, + igraph_bliss_info_t *info1, igraph_bliss_info_t *info2) { + + long int no_of_nodes = igraph_vcount(graph1); + long int no_of_edges = igraph_ecount(graph1); + igraph_vector_t perm1, perm2; + igraph_vector_t vmap12, *mymap12 = &vmap12; + igraph_vector_t from, to, index; + igraph_vector_t from2, to2, index2; + igraph_bool_t directed; + long int i, j; + + *iso = 0; + if (info1) { + info1->nof_nodes = info1->nof_leaf_nodes = info1->nof_bad_nodes = + info1->nof_canupdates = info1->max_level = info1->nof_generators = -1; + info1->group_size = 0; + } + if (info2) { + info2->nof_nodes = info2->nof_leaf_nodes = info2->nof_bad_nodes = + info2->nof_canupdates = info2->max_level = info2->nof_generators = -1; + info2->group_size = 0; + } + + directed = igraph_is_directed(graph1); + if (igraph_is_directed(graph2) != directed) { + IGRAPH_ERROR("Cannot compare directed and undirected graphs", + IGRAPH_EINVAL); + } + if ((colors1 == NULL || colors2 == NULL) && colors1 != colors2) { + IGRAPH_WARNING("Only one of the graphs is vertex colored, colors will be ignored"); + colors1 = NULL; colors2 = NULL; + } + + if (no_of_nodes != igraph_vcount(graph2) || + no_of_edges != igraph_ecount(graph2)) { + if (map12) { + igraph_vector_clear(map12); + } + if (map21) { + igraph_vector_clear(map21); + } + return 0; + } + + if (map12) { + mymap12 = map12; + } else { + IGRAPH_VECTOR_INIT_FINALLY(mymap12, 0); + } + + IGRAPH_VECTOR_INIT_FINALLY(&perm1, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&perm2, no_of_nodes); + + IGRAPH_CHECK(igraph_canonical_permutation(graph1, colors1, &perm1, sh, info1)); + IGRAPH_CHECK(igraph_canonical_permutation(graph2, colors2, &perm2, sh, info2)); + + IGRAPH_CHECK(igraph_vector_resize(mymap12, no_of_nodes)); + + /* The inverse of perm2 is produced in mymap12 */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*mymap12)[ (long int)VECTOR(perm2)[i] ] = i; + } + /* Now we produce perm2^{-1} o perm1 in perm2 */ + for (i = 0; i < no_of_nodes; i++) { + VECTOR(perm2)[i] = VECTOR(*mymap12)[ (long int) VECTOR(perm1)[i] ]; + } + /* Copy it to mymap12 */ + igraph_vector_update(mymap12, &perm2); + + igraph_vector_destroy(&perm1); + igraph_vector_destroy(&perm2); + IGRAPH_FINALLY_CLEAN(2); + + /* Check isomorphism, we apply the permutation in mymap12 to graph1 + and should get graph2 */ + + IGRAPH_VECTOR_INIT_FINALLY(&from, no_of_edges); + IGRAPH_VECTOR_INIT_FINALLY(&to, no_of_edges); + IGRAPH_VECTOR_INIT_FINALLY(&index, no_of_edges); + IGRAPH_VECTOR_INIT_FINALLY(&from2, no_of_edges * 2); + IGRAPH_VECTOR_INIT_FINALLY(&to2, no_of_edges); + IGRAPH_VECTOR_INIT_FINALLY(&index2, no_of_edges); + + for (i = 0; i < no_of_edges; i++) { + VECTOR(from)[i] = VECTOR(*mymap12)[ (long int) IGRAPH_FROM(graph1, i) ]; + VECTOR(to)[i] = VECTOR(*mymap12)[ (long int) IGRAPH_TO (graph1, i) ]; + if (! directed && VECTOR(from)[i] < VECTOR(to)[i]) { + igraph_real_t tmp = VECTOR(from)[i]; + VECTOR(from)[i] = VECTOR(to)[i]; + VECTOR(to)[i] = tmp; + } + } + igraph_vector_order(&from, &to, &index, no_of_nodes); + + igraph_get_edgelist(graph2, &from2, /*bycol=*/ 1); + for (i = 0, j = no_of_edges; i < no_of_edges; i++, j++) { + VECTOR(to2)[i] = VECTOR(from2)[j]; + if (! directed && VECTOR(from2)[i] < VECTOR(to2)[i]) { + igraph_real_t tmp = VECTOR(from2)[i]; + VECTOR(from2)[i] = VECTOR(to2)[i]; + VECTOR(to2)[i] = tmp; + } + } + igraph_vector_resize(&from2, no_of_edges); + igraph_vector_order(&from2, &to2, &index2, no_of_nodes); + + *iso = 1; + for (i = 0; i < no_of_edges; i++) { + long int i1 = (long int) VECTOR(index)[i]; + long int i2 = (long int) VECTOR(index2)[i]; + if (VECTOR(from)[i1] != VECTOR(from2)[i2] || + VECTOR(to)[i1] != VECTOR(to2)[i2]) { + *iso = 0; + break; + } + } + + /* If the graphs are coloured, we also need to check that applying the + permutation mymap12 to colors1 gives colors2. */ + + if (*iso && colors1 != NULL) { + for (i = 0; i < no_of_nodes; i++) { + if (VECTOR(*colors1)[i] != VECTOR(*colors2)[(long int) VECTOR(*mymap12)[i] ]) { + *iso = 0; + break; + } + } + } + + igraph_vector_destroy(&index2); + igraph_vector_destroy(&to2); + igraph_vector_destroy(&from2); + igraph_vector_destroy(&index); + igraph_vector_destroy(&to); + igraph_vector_destroy(&from); + IGRAPH_FINALLY_CLEAN(6); + + if (*iso) { + /* The inverse of mymap12 */ + if (map21) { + IGRAPH_CHECK(igraph_vector_resize(map21, no_of_nodes)); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(*map21)[ (long int) VECTOR(*mymap12)[i] ] = i; + } + } + } else { + if (map12) { + igraph_vector_clear(map12); + } + if (map21) { + igraph_vector_clear(map21); + } + } + + if (!map12) { + igraph_vector_destroy(mymap12); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + + +/** + * \function igraph_simplify_and_colorize + * \brief Simplify the graph and compute self-loop and edge multiplicities. + * + * + * This function creates a vertex and edge colored simple graph from the input + * graph. The vertex colors are computed as the number of incident self-loops + * to each vertex in the input graph. The edge colors are computed as the number of + * parallel edges in the input graph that were merged to create each edge + * in the simple graph. + * + * + * The resulting colored simple graph is suitable for use by isomorphism checking + * algorithms such as VF2, which only support simple graphs, but can consider + * vertex and edge colors. + * + * \param graph The graph object, typically having self-loops or multi-edges. + * \param res An uninitialized graph object. The result will be stored here + * \param vertex_color Computed vertex colors corresponding to self-loop multiplicities. + * \param edge_color Computed edge colors corresponding to edge multiplicities + * \return Error code. + * + * \sa \ref igraph_simplify(), \ref igraph_isomorphic_vf2(), \ref igraph_subisomorphic_vf2() + * + */ +int igraph_simplify_and_colorize( + const igraph_t *graph, igraph_t *res, + igraph_vector_int_t *vertex_color, igraph_vector_int_t *edge_color) { + igraph_es_t es; + igraph_eit_t eit; + igraph_vector_t edges; + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + long int pto = -1, pfrom = -1; + long int i; + + IGRAPH_CHECK(igraph_es_all(&es, IGRAPH_EDGEORDER_FROM)); + IGRAPH_FINALLY(igraph_es_destroy, &es); + IGRAPH_CHECK(igraph_eit_create(graph, es, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + IGRAPH_VECTOR_INIT_FINALLY(&edges, 0); + IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges * 2)); + + IGRAPH_CHECK(igraph_vector_int_resize(vertex_color, no_of_nodes)); + igraph_vector_int_null(vertex_color); + + IGRAPH_CHECK(igraph_vector_int_resize(edge_color, no_of_edges)); + igraph_vector_int_null(edge_color); + + i = -1; + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + long int edge = IGRAPH_EIT_GET(eit); + long int from = IGRAPH_FROM(graph, edge); + long int to = IGRAPH_TO(graph, edge); + + if (to == from) { + VECTOR(*vertex_color)[to]++; + continue; + } + + if (to == pto && from == pfrom) { + VECTOR(*edge_color)[i]++; + } else { + igraph_vector_push_back(&edges, from); + igraph_vector_push_back(&edges, to); + i++; + VECTOR(*edge_color)[i] = 1; + } + + pfrom = from; pto = to; + } + + igraph_vector_int_resize(edge_color, i + 1); + + igraph_eit_destroy(&eit); + igraph_es_destroy(&es); + IGRAPH_FINALLY_CLEAN(2); + + IGRAPH_CHECK(igraph_create(res, &edges, no_of_nodes, igraph_is_directed(graph))); + + igraph_vector_destroy(&edges); + IGRAPH_FINALLY_CLEAN(1); + + return IGRAPH_SUCCESS; +} diff --git a/src/triangles.c b/src/triangles.c new file mode 100644 index 0000000..2a8037e --- /dev/null +++ b/src/triangles.c @@ -0,0 +1,978 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_transitivity.h" +#include "igraph_interface.h" +#include "igraph_adjlist.h" +#include "igraph_memory.h" +#include "igraph_interrupt_internal.h" +#include "igraph_centrality.h" +#include "igraph_motifs.h" +#include "igraph_structural.h" + +/** + * \function igraph_transitivity_avglocal_undirected + * \brief Average local transitivity (clustering coefficient). + * + * The transitivity measures the probability that two neighbors of a + * vertex are connected. In case of the average local transitivity, + * this probability is calculated for each vertex and then the average + * is taken. Vertices with less than two neighbors require special treatment, + * they will either be left out from the calculation or they will be considered + * as having zero transitivity, depending on the \c mode argument. + * + * + * Note that this measure is different from the global transitivity measure + * (see \ref igraph_transitivity_undirected() ) as it simply takes the + * average local transitivity across the whole network. See the following + * reference for more details: + * + * + * D. J. Watts and S. Strogatz: Collective dynamics of small-world networks. + * Nature 393(6684):440-442 (1998). + * + * + * Clustering coefficient is an alternative name for transitivity. + * + * \param graph The input graph, directed graphs are considered as + * undirected ones. + * \param res Pointer to a real variable, the result will be stored here. + * \param mode Defines how to treat vertices with degree less than two. + * \c IGRAPH_TRANSITIVITY_NAN leaves them out from averaging, + * \c IGRAPH_TRANSITIVITY_ZERO includes them with zero transitivity. + * The result will be \c NaN if the mode is \c IGRAPH_TRANSITIVITY_NAN + * and there are no vertices with more than one neighbor. + * + * \return Error code. + * + * \sa \ref igraph_transitivity_undirected(), \ref + * igraph_transitivity_local_undirected(). + * + * Time complexity: O(|V|*d^2), |V| is the number of vertices in the + * graph and d is the average degree. + */ + +int igraph_transitivity_avglocal_undirected(const igraph_t *graph, + igraph_real_t *res, + igraph_transitivity_mode_t mode) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_real_t sum = 0.0; + igraph_integer_t count = 0; + long int node, i, j, nn; + igraph_adjlist_t allneis; + igraph_vector_int_t *neis1, *neis2; + long int neilen1, neilen2; + long int *neis; + long int maxdegree; + + igraph_vector_t order; + igraph_vector_t rank; + igraph_vector_t degree; + igraph_vector_t triangles; + + IGRAPH_VECTOR_INIT_FINALLY(&order, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS)); + maxdegree = (long int) igraph_vector_max(°ree) + 1; + igraph_vector_order1(°ree, &order, maxdegree); + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_VECTOR_INIT_FINALLY(&rank, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(rank)[ (long int) VECTOR(order)[i] ] = no_of_nodes - i - 1; + } + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + IGRAPH_CHECK(igraph_adjlist_simplify(&allneis)); + + neis = igraph_Calloc(no_of_nodes, long int); + if (neis == 0) { + IGRAPH_ERROR("undirected average local transitivity failed", + IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, neis); + + IGRAPH_VECTOR_INIT_FINALLY(&triangles, no_of_nodes); + + for (nn = no_of_nodes - 1; nn >= 0; nn--) { + node = (long int) VECTOR(order)[nn]; + + IGRAPH_ALLOW_INTERRUPTION(); + + neis1 = igraph_adjlist_get(&allneis, node); + neilen1 = igraph_vector_int_size(neis1); + /* Mark the neighbors of 'node' */ + for (i = 0; i < neilen1; i++) { + neis[ (long int)VECTOR(*neis1)[i] ] = node + 1; + } + + for (i = 0; i < neilen1; i++) { + long int nei = (long int) VECTOR(*neis1)[i]; + if (VECTOR(rank)[nei] > VECTOR(rank)[node]) { + neis2 = igraph_adjlist_get(&allneis, nei); + neilen2 = igraph_vector_int_size(neis2); + for (j = 0; j < neilen2; j++) { + long int nei2 = (long int) VECTOR(*neis2)[j]; + if (VECTOR(rank)[nei2] < VECTOR(rank)[nei]) { + continue; + } + if (neis[nei2] == node + 1) { + VECTOR(triangles)[nei2] += 1; + VECTOR(triangles)[nei] += 1; + VECTOR(triangles)[node] += 1; + } + } + } + } + + if (neilen1 >= 2) { + sum += VECTOR(triangles)[node] / neilen1 / (neilen1 - 1) * 2.0; + count++; + } else if (mode == IGRAPH_TRANSITIVITY_ZERO) { + count++; + } + } + + *res = sum / count; + + igraph_vector_destroy(&triangles); + igraph_Free(neis); + igraph_adjlist_destroy(&allneis); + igraph_vector_destroy(&rank); + igraph_vector_destroy(&order); + IGRAPH_FINALLY_CLEAN(5); + return 0; +} + +int igraph_transitivity_local_undirected1(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode) { + +#define TRANSIT +#include "triangles_template1.h" +#undef TRANSIT + + return 0; +} + +int igraph_transitivity_local_undirected2(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_vit_t vit; + long int nodes_to_calc, affected_nodes; + long int maxdegree = 0; + long int i, j, k, nn; + igraph_lazy_adjlist_t adjlist; + igraph_vector_t indexv, avids, rank, order, triangles, degree; + long int *neis; + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_ALL, + IGRAPH_SIMPLIFY)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + + IGRAPH_VECTOR_INIT_FINALLY(&indexv, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(&avids, 0); + IGRAPH_CHECK(igraph_vector_reserve(&avids, nodes_to_calc)); + k = 0; + for (i = 0; i < nodes_to_calc; IGRAPH_VIT_NEXT(vit), i++) { + long int v = IGRAPH_VIT_GET(vit); + igraph_vector_t *neis2; + long int neilen; + if (VECTOR(indexv)[v] == 0) { + VECTOR(indexv)[v] = k + 1; k++; + IGRAPH_CHECK(igraph_vector_push_back(&avids, v)); + } + + neis2 = igraph_lazy_adjlist_get(&adjlist, (igraph_integer_t) v); + neilen = igraph_vector_size(neis2); + for (j = 0; j < neilen; j++) { + long int nei = (long int) VECTOR(*neis2)[j]; + if (VECTOR(indexv)[nei] == 0) { + VECTOR(indexv)[nei] = k + 1; k++; + IGRAPH_CHECK(igraph_vector_push_back(&avids, nei)); + } + } + } + + /* Degree, ordering, ranking */ + affected_nodes = igraph_vector_size(&avids); + IGRAPH_VECTOR_INIT_FINALLY(&order, 0); + IGRAPH_VECTOR_INIT_FINALLY(°ree, affected_nodes); + for (i = 0; i < affected_nodes; i++) { + long int v = (long int) VECTOR(avids)[i]; + igraph_vector_t *neis2; + long int deg; + neis2 = igraph_lazy_adjlist_get(&adjlist, (igraph_integer_t) v); + VECTOR(degree)[i] = deg = igraph_vector_size(neis2); + if (deg > maxdegree) { + maxdegree = deg; + } + } + igraph_vector_order1(°ree, &order, maxdegree + 1); + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_VECTOR_INIT_FINALLY(&rank, affected_nodes); + for (i = 0; i < affected_nodes; i++) { + VECTOR(rank)[ (long int) VECTOR(order)[i] ] = affected_nodes - i - 1; + } + + neis = igraph_Calloc(no_of_nodes, long int); + if (neis == 0) { + IGRAPH_ERROR("local transitivity calculation failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, neis); + + IGRAPH_VECTOR_INIT_FINALLY(&triangles, affected_nodes); + for (nn = affected_nodes - 1; nn >= 0; nn--) { + long int node = (long int) VECTOR(avids) [ (long int) VECTOR(order)[nn] ]; + igraph_vector_t *neis1, *neis2; + long int neilen1, neilen2; + long int nodeindex = (long int) VECTOR(indexv)[node]; + long int noderank = (long int) VECTOR(rank) [nodeindex - 1]; + + /* fprintf(stderr, "node %li (indexv %li, rank %li)\n", node, */ + /* (long int)VECTOR(indexv)[node]-1, noderank); */ + + IGRAPH_ALLOW_INTERRUPTION(); + + neis1 = igraph_lazy_adjlist_get(&adjlist, (igraph_integer_t) node); + neilen1 = igraph_vector_size(neis1); + for (i = 0; i < neilen1; i++) { + long int nei = (long int) VECTOR(*neis1)[i]; + neis[nei] = node + 1; + } + for (i = 0; i < neilen1; i++) { + long int nei = (long int) VECTOR(*neis1)[i]; + long int neiindex = (long int) VECTOR(indexv)[nei]; + long int neirank = (long int) VECTOR(rank)[neiindex - 1]; + + /* fprintf(stderr, " nei %li (indexv %li, rank %li)\n", nei, */ + /* neiindex, neirank); */ + if (neirank > noderank) { + neis2 = igraph_lazy_adjlist_get(&adjlist, (igraph_integer_t) nei); + neilen2 = igraph_vector_size(neis2); + for (j = 0; j < neilen2; j++) { + long int nei2 = (long int) VECTOR(*neis2)[j]; + long int nei2index = (long int) VECTOR(indexv)[nei2]; + long int nei2rank = (long int) VECTOR(rank)[nei2index - 1]; + /* fprintf(stderr, " triple %li %li %li\n", node, nei, nei2); */ + if (nei2rank < neirank) { + continue; + } + if (neis[nei2] == node + 1) { + /* fprintf(stderr, " triangle\n"); */ + VECTOR(triangles) [ nei2index - 1 ] += 1; + VECTOR(triangles) [ neiindex - 1 ] += 1; + VECTOR(triangles) [ nodeindex - 1 ] += 1; + } + } + } + } + } + + /* Ok, for all affected vertices the number of triangles were counted */ + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + IGRAPH_VIT_RESET(vit); + for (i = 0; i < nodes_to_calc; i++, IGRAPH_VIT_NEXT(vit)) { + long int node = IGRAPH_VIT_GET(vit); + long int idx = (long int) VECTOR(indexv)[node] - 1; + igraph_vector_t *neis2 = igraph_lazy_adjlist_get(&adjlist, + (igraph_integer_t) node); + long int deg = igraph_vector_size(neis2); + if (mode == IGRAPH_TRANSITIVITY_ZERO && deg < 2) { + VECTOR(*res)[i] = 0.0; + } else { + VECTOR(*res)[i] = VECTOR(triangles)[idx] / deg / (deg - 1) * 2.0; + } + /* fprintf(stderr, "%f %f\n", VECTOR(triangles)[idx], triples); */ + } + + igraph_vector_destroy(&triangles); + igraph_free(neis); + igraph_vector_destroy(&rank); + igraph_vector_destroy(&order); + igraph_vector_destroy(&avids); + igraph_vector_destroy(&indexv); + igraph_lazy_adjlist_destroy(&adjlist); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(8); + + return 0; +} + +/* We don't use this, it is theoretically good, but practically not. + */ + +/* int igraph_transitivity_local_undirected3(const igraph_t *graph, */ +/* igraph_vector_t *res, */ +/* const igraph_vs_t vids) { */ + +/* igraph_vit_t vit; */ +/* long int nodes_to_calc; */ +/* igraph_lazy_adjlist_t adjlist; */ +/* long int i, j; */ + +/* IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); */ +/* IGRAPH_FINALLY(igraph_vit_destroy, &vit); */ +/* nodes_to_calc=IGRAPH_VIT_SIZE(vit); */ + +/* IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_ALL, */ +/* IGRAPH_SIMPLIFY)); */ +/* IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); */ + +/* IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); */ +/* for (i=0, IGRAPH_VIT_RESET(vit); !IGRAPH_VIT_END(vit); */ +/* i++, IGRAPH_VIT_NEXT(vit)) { */ +/* long int node=IGRAPH_VIT_GET(vit); */ +/* igraph_vector_t *neis=igraph_lazy_adjlist_get(&adjlist, node); */ +/* long int n1=igraph_vector_size(neis); */ +/* igraph_real_t triangles=0; */ +/* igraph_real_t triples=(double)n1*(n1-1); */ +/* IGRAPH_ALLOW_INTERRUPTION(); */ +/* for (j=0; j nei2) { */ +/* l2++; */ +/* } else { */ +/* triangles+=1; */ +/* l1++; l2++; */ +/* } */ +/* } */ +/* } */ +/* /\* We're done with 'node' *\/ */ +/* VECTOR(*res)[i] = triangles / triples; */ +/* } */ + +/* igraph_lazy_adjlist_destroy(&adjlist); */ +/* igraph_vit_destroy(&vit); */ +/* IGRAPH_FINALLY_CLEAN(2); */ + +/* return 0; */ +/* } */ + +/* This removes loop, multiple edges and edges that point + "backwards" according to the rank vector. */ +/* TODO used in scan.c, add prototype to private header */ +int igraph_i_trans4_al_simplify(igraph_adjlist_t *al, + const igraph_vector_int_t *rank) { + long int i; + long int n = al->length; + igraph_vector_int_t mark; + igraph_vector_int_init(&mark, n); + IGRAPH_FINALLY(igraph_vector_int_destroy, &mark); + for (i = 0; i < n; i++) { + igraph_vector_int_t *v = &al->adjs[i]; + int j, l = igraph_vector_int_size(v); + int irank = VECTOR(*rank)[i]; + VECTOR(mark)[i] = i + 1; + for (j = 0; j < l; /* nothing */) { + long int e = (long int) VECTOR(*v)[j]; + if (VECTOR(*rank)[e] > irank && VECTOR(mark)[e] != i + 1) { + VECTOR(mark)[e] = i + 1; + j++; + } else { + VECTOR(*v)[j] = igraph_vector_int_tail(v); + igraph_vector_int_pop_back(v); + l--; + } + } + } + + igraph_vector_int_destroy(&mark); + IGRAPH_FINALLY_CLEAN(1); + return 0; + +} + +int igraph_transitivity_local_undirected4(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode) { + +#define TRANSIT 1 +#include "triangles_template.h" +#undef TRANSIT + + return 0; +} + +/** + * \function igraph_transitivity_local_undirected + * \brief Calculates the local transitivity (clustering coefficient) of a graph. + * + * The transitivity measures the probability that two neighbors of a + * vertex are connected. In case of the local transitivity, this + * probability is calculated separately for each vertex. + * + * + * Note that this measure is different from the global transitivity measure + * (see \ref igraph_transitivity_undirected() ) as it calculates a transitivity + * value for each vertex individually. See the following reference for more + * details: + * + * + * D. J. Watts and S. Strogatz: Collective dynamics of small-world networks. + * Nature 393(6684):440-442 (1998). + * + * + * Clustering coefficient is an alternative name for transitivity. + * + * \param graph The input graph, which should be undirected and simple. + * \param res Pointer to an initialized vector, the result will be + * stored here. It will be resized as needed. + * \param vids Vertex set, the vertices for which the local + * transitivity will be calculated. + * \param mode Defines how to treat vertices with degree less than two. + * \c IGRAPH_TRANSITIVITY_NAN returns \c NaN for these vertices, + * \c IGRAPH_TRANSITIVITY_ZERO returns zero. + * \return Error code. + * + * \sa \ref igraph_transitivity_undirected(), \ref + * igraph_transitivity_avglocal_undirected(). + * + * Time complexity: O(n*d^2), n is the number of vertices for which + * the transitivity is calculated, d is the average vertex degree. + */ + +int igraph_transitivity_local_undirected(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + igraph_transitivity_mode_t mode) { + + igraph_bool_t simple; + + if (igraph_is_directed(graph)) { + IGRAPH_ERROR("Transitivity works on undirected graphs only", IGRAPH_EINVAL); + } + + igraph_is_simple(graph, &simple); + if (!simple) { + IGRAPH_ERROR("Transitivity works on simple graphs only", IGRAPH_EINVAL); + } + + if (igraph_vs_is_all(&vids)) { + return igraph_transitivity_local_undirected4(graph, res, vids, mode); + } else { + igraph_vit_t vit; + long int size; + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + size = IGRAPH_VIT_SIZE(vit); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + if (size < 100) { + return igraph_transitivity_local_undirected1(graph, res, vids, mode); + } else { + return igraph_transitivity_local_undirected2(graph, res, vids, mode); + } + } + + return 0; +} + +int igraph_adjacent_triangles1(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids) { +# include "triangles_template1.h" + return 0; +} + +int igraph_adjacent_triangles4(const igraph_t *graph, + igraph_vector_t *res) { +# include "triangles_template.h" + return 0; +} + +/** + * \function igraph_adjacent_triangles + * Count the number of triangles a vertex is part of + * + * \param graph The input graph. Edge directions are ignored. + * \param res Initiliazed vector, the results are stored here. + * \param vids The vertices to perform the calculation for. + * \return Error mode. + * + * \sa \ref igraph_list_triangles() to list them. + * + * Time complexity: O(d^2 n), d is the average vertex degree of the + * queried vertices, n is their number. + */ + +int igraph_adjacent_triangles(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids) { + if (igraph_vs_is_all(&vids)) { + return igraph_adjacent_triangles4(graph, res); + } else { + return igraph_adjacent_triangles1(graph, res, vids); + } + + return 0; + +} + +/** + * \function igraph_list_triangles + * Find all triangles in a graph + * + * \param graph The input graph, edge directions are ignored. + * \param res Pointer to an initialized integer vector, the result + * is stored here, in a long list of triples of vertex ids. + * Each triple is a triangle in the graph. Each triangle is + * listed exactly once. + * \return Error code. + * + * \sa \ref igraph_transitivity_undirected() to count the triangles, + * \ref igraph_adjacent_triangles() to count the triangles a vertex + * participates in. + * + * Time complexity: O(d^2 n), d is the average degree, n is the number + * of vertices. + */ + +int igraph_list_triangles(const igraph_t *graph, + igraph_vector_int_t *res) { +# define TRIANGLES +# include "triangles_template.h" +# undef TRIANGLES + return 0; +} + +/** + * \ingroup structural + * \function igraph_transitivity_undirected + * \brief Calculates the transitivity (clustering coefficient) of a graph. + * + * + * The transitivity measures the probability that two neighbors of a + * vertex are connected. More precisely, this is the ratio of the + * triangles and connected triples in the graph, the result is a + * single real number. Directed graphs are considered as undirected ones. + * + * + * Note that this measure is different from the local transitivity measure + * (see \ref igraph_transitivity_local_undirected() ) as it calculates a single + * value for the whole graph. See the following reference for more details: + * + * + * S. Wasserman and K. Faust: Social Network Analysis: Methods and + * Applications. Cambridge: Cambridge University Press, 1994. + * + * + * Clustering coefficient is an alternative name for transitivity. + * + * \param graph The graph object. + * \param res Pointer to a real variable, the result will be stored here. + * \param mode Defines how to treat graphs with no connected triples. + * \c IGRAPH_TRANSITIVITY_NAN returns \c NaN in this case, + * \c IGRAPH_TRANSITIVITY_ZERO returns zero. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory for + * temporary data. + * + * \sa \ref igraph_transitivity_local_undirected(), + * \ref igraph_transitivity_avglocal_undirected(). + * + * Time complexity: O(|V|*d^2), |V| is the number of vertices in + * the graph, d is the average node degree. + * + * \example examples/simple/igraph_transitivity.c + */ + + +int igraph_transitivity_undirected(const igraph_t *graph, + igraph_real_t *res, + igraph_transitivity_mode_t mode) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_real_t triples = 0, triangles = 0; + long int node, nn; + long int maxdegree; + long int *neis; + igraph_vector_t order; + igraph_vector_t rank; + igraph_vector_t degree; + + igraph_adjlist_t allneis; + igraph_vector_int_t *neis1, *neis2; + long int i, j, neilen1, neilen2; + + IGRAPH_VECTOR_INIT_FINALLY(&order, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS)); + maxdegree = (long int) igraph_vector_max(°ree) + 1; + igraph_vector_order1(°ree, &order, maxdegree); + igraph_vector_destroy(°ree); + IGRAPH_FINALLY_CLEAN(1); + IGRAPH_VECTOR_INIT_FINALLY(&rank, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(rank)[ (long int) VECTOR(order)[i] ] = no_of_nodes - i - 1; + } + + IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); + IGRAPH_CHECK(igraph_adjlist_simplify(&allneis)); + + neis = igraph_Calloc(no_of_nodes, long int); + if (neis == 0) { + IGRAPH_ERROR("undirected transitivity failed", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, neis); + + for (nn = no_of_nodes - 1; nn >= 0; nn--) { + node = (long int) VECTOR(order)[nn]; + + IGRAPH_ALLOW_INTERRUPTION(); + + neis1 = igraph_adjlist_get(&allneis, node); + neilen1 = igraph_vector_int_size(neis1); + triples += (double)neilen1 * (neilen1 - 1); + /* Mark the neighbors of 'node' */ + for (i = 0; i < neilen1; i++) { + long int nei = (long int) VECTOR(*neis1)[i]; + neis[nei] = node + 1; + } + for (i = 0; i < neilen1; i++) { + long int nei = (long int) VECTOR(*neis1)[i]; + /* If 'nei' is not ready yet */ + if (VECTOR(rank)[nei] > VECTOR(rank)[node]) { + neis2 = igraph_adjlist_get(&allneis, nei); + neilen2 = igraph_vector_int_size(neis2); + for (j = 0; j < neilen2; j++) { + long int nei2 = (long int) VECTOR(*neis2)[j]; + if (neis[nei2] == node + 1) { + triangles += 1.0; + } + } + } + } + } + + igraph_Free(neis); + igraph_adjlist_destroy(&allneis); + igraph_vector_destroy(&rank); + igraph_vector_destroy(&order); + IGRAPH_FINALLY_CLEAN(4); + + if (triples == 0 && mode == IGRAPH_TRANSITIVITY_ZERO) { + *res = 0; + } else { + *res = triangles / triples * 2.0; + } + + return 0; +} + +int igraph_transitivity_barrat1(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + const igraph_vector_t *weights, + igraph_transitivity_mode_t mode); + +int igraph_transitivity_barrat4(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + const igraph_vector_t *weights, + igraph_transitivity_mode_t mode); + +int igraph_transitivity_barrat1(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + const igraph_vector_t *weights, + igraph_transitivity_mode_t mode) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_vit_t vit; + long int nodes_to_calc; + igraph_vector_t *adj1, *adj2; + igraph_vector_long_t neis; + igraph_vector_t actw; + igraph_lazy_inclist_t incident; + long int i; + igraph_vector_t strength; + + if (!weights) { + IGRAPH_WARNING("No weights given for Barrat's transitivity, unweighted version is used"); + return igraph_transitivity_local_undirected(graph, res, vids, mode); + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid edge weight vector length", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + + IGRAPH_CHECK(igraph_vector_long_init(&neis, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &neis); + + IGRAPH_VECTOR_INIT_FINALLY(&actw, no_of_nodes); + + IGRAPH_VECTOR_INIT_FINALLY(&strength, 0); + IGRAPH_CHECK(igraph_strength(graph, &strength, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS, weights)); + + igraph_lazy_inclist_init(graph, &incident, IGRAPH_ALL); + IGRAPH_FINALLY(igraph_lazy_inclist_destroy, &incident); + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + + for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + long int node = IGRAPH_VIT_GET(vit); + long int adjlen1, adjlen2, j, k; + igraph_real_t triples, triangles; + + IGRAPH_ALLOW_INTERRUPTION(); + + adj1 = igraph_lazy_inclist_get(&incident, (igraph_integer_t) node); + adjlen1 = igraph_vector_size(adj1); + /* Mark the neighbors of the node */ + for (j = 0; j < adjlen1; j++) { + long int edge = (long int) VECTOR(*adj1)[j]; + long int nei = IGRAPH_OTHER(graph, edge, node); + VECTOR(neis)[nei] = i + 1; + VECTOR(actw)[nei] = VECTOR(*weights)[edge]; + } + triples = VECTOR(strength)[node] * (adjlen1 - 1); + triangles = 0.0; + + for (j = 0; j < adjlen1; j++) { + long int edge1 = (long int) VECTOR(*adj1)[j]; + igraph_real_t weight1 = VECTOR(*weights)[edge1]; + long int v = IGRAPH_OTHER(graph, edge1, node); + adj2 = igraph_lazy_inclist_get(&incident, (igraph_integer_t) v); + adjlen2 = igraph_vector_size(adj2); + for (k = 0; k < adjlen2; k++) { + long int edge2 = (long int) VECTOR(*adj2)[k]; + long int v2 = IGRAPH_OTHER(graph, edge2, v); + if (VECTOR(neis)[v2] == i + 1) { + triangles += (VECTOR(actw)[v2] + weight1) / 2.0; + } + } + } + if (mode == IGRAPH_TRANSITIVITY_ZERO && triples == 0) { + VECTOR(*res)[i] = 0.0; + } else { + VECTOR(*res)[i] = triangles / triples; + } + } + + igraph_lazy_inclist_destroy(&incident); + igraph_vector_destroy(&strength); + igraph_vector_destroy(&actw); + igraph_vector_long_destroy(&neis); + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(5); + + return 0; +} + +int igraph_transitivity_barrat4(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + const igraph_vector_t *weights, + igraph_transitivity_mode_t mode) { + + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_vector_t order, degree, rank; + long int maxdegree; + igraph_inclist_t incident; + igraph_vector_long_t neis; + igraph_vector_int_t *adj1, *adj2; + igraph_vector_t actw; + long int i, nn; + + if (!weights) { + IGRAPH_WARNING("No weights given for Barrat's transitivity, unweighted version is used"); + return igraph_transitivity_local_undirected(graph, res, vids, mode); + } + + if (igraph_vector_size(weights) != no_of_edges) { + IGRAPH_ERROR("Invalid edge weight vector length", IGRAPH_EINVAL); + } + + IGRAPH_VECTOR_INIT_FINALLY(&order, no_of_nodes); + IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + + IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS)); + maxdegree = (long int) igraph_vector_max(°ree) + 1; + IGRAPH_CHECK(igraph_vector_order1(°ree, &order, maxdegree)); + + IGRAPH_CHECK(igraph_strength(graph, °ree, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS, weights)); + + IGRAPH_VECTOR_INIT_FINALLY(&rank, no_of_nodes); + for (i = 0; i < no_of_nodes; i++) { + VECTOR(rank)[ (long int)VECTOR(order)[i] ] = no_of_nodes - i - 1; + } + + IGRAPH_CHECK(igraph_inclist_init(graph, &incident, IGRAPH_ALL)); + IGRAPH_FINALLY(igraph_inclist_destroy, &incident); + + IGRAPH_CHECK(igraph_vector_long_init(&neis, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &neis); + + IGRAPH_VECTOR_INIT_FINALLY(&actw, no_of_nodes); + + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); + + for (nn = no_of_nodes - 1; nn >= 0; nn--) { + long int adjlen1, adjlen2; + igraph_real_t triples; + long int node = (long int) VECTOR(order)[nn]; + + IGRAPH_ALLOW_INTERRUPTION(); + + adj1 = igraph_inclist_get(&incident, node); + adjlen1 = igraph_vector_int_size(adj1); + triples = VECTOR(degree)[node] * (adjlen1 - 1) / 2.0; + /* Mark the neighbors of the node */ + for (i = 0; i < adjlen1; i++) { + long int edge = (long int) VECTOR(*adj1)[i]; + long int nei = IGRAPH_OTHER(graph, edge, node); + VECTOR(neis)[nei] = node + 1; + VECTOR(actw)[nei] = VECTOR(*weights)[edge]; + } + + for (i = 0; i < adjlen1; i++) { + long int edge1 = (long int) VECTOR(*adj1)[i]; + igraph_real_t weight1 = VECTOR(*weights)[edge1]; + long int nei = IGRAPH_OTHER(graph, edge1, node); + long int j; + if (VECTOR(rank)[nei] > VECTOR(rank)[node]) { + adj2 = igraph_inclist_get(&incident, nei); + adjlen2 = igraph_vector_int_size(adj2); + for (j = 0; j < adjlen2; j++) { + long int edge2 = (long int) VECTOR(*adj2)[j]; + igraph_real_t weight2 = VECTOR(*weights)[edge2]; + long int nei2 = IGRAPH_OTHER(graph, edge2, nei); + if (VECTOR(rank)[nei2] < VECTOR(rank)[nei]) { + continue; + } + if (VECTOR(neis)[nei2] == node + 1) { + VECTOR(*res)[nei2] += (VECTOR(actw)[nei2] + weight2) / 2.0; + VECTOR(*res)[nei] += (weight1 + weight2) / 2.0; + VECTOR(*res)[node] += (VECTOR(actw)[nei2] + weight1) / 2.0; + } + } + } + } + + if (mode == IGRAPH_TRANSITIVITY_ZERO && triples == 0) { + VECTOR(*res)[node] = 0.0; + } else { + VECTOR(*res)[node] /= triples; + } + } + + igraph_vector_destroy(&actw); + igraph_vector_long_destroy(&neis); + igraph_inclist_destroy(&incident); + igraph_vector_destroy(&rank); + igraph_vector_destroy(°ree); + igraph_vector_destroy(&order); + IGRAPH_FINALLY_CLEAN(6); + + return 0; +} + +/** + * \function igraph_transitivity_barrat + * Weighted transitivity, as defined by A. Barrat. + * + * This is a local transitivity, i.e. a vertex-level index. For a + * given vertex \c i, from all triangles in which it participates we + * consider the weight of the edges incident on \c i. The transitivity + * is the sum of these weights divided by twice the strength of the + * vertex (see \ref igraph_strength()) and the degree of the vertex + * minus one. See Alain Barrat, Marc Barthelemy, Romualdo + * Pastor-Satorras, Alessandro Vespignani: The architecture of complex + * weighted networks, Proc. Natl. Acad. Sci. USA 101, 3747 (2004) at + * http://arxiv.org/abs/cond-mat/0311416 for the exact formula. + * + * \param graph The input graph, edge directions are ignored for + * directed graphs. Note that the function does NOT work for + * non-simple graphs. + * \param res Pointer to an initialized vector, the result will be + * stored here. It will be resized as needed. + * \param vids The vertices for which the calculation is performed. + * \param weights Edge weights. If this is a null pointer, then a + * warning is given and \ref igraph_transitivity_local_undirected() + * is called. + * \param mode Defines how to treat vertices with zero strength. + * \c IGRAPH_TRANSITIVITY_NAN says that the transitivity of these + * vertices is \c NaN, \c IGRAPH_TRANSITIVITY_ZERO says it is zero. + * + * \return Error code. + * + * Time complexity: O(|V|*d^2), |V| is the number of vertices in + * the graph, d is the average node degree. + * + * \sa \ref igraph_transitivity_undirected(), \ref + * igraph_transitivity_local_undirected() and \ref + * igraph_transitivity_avglocal_undirected() for other kinds of + * (non-weighted) transitivity. + */ + +int igraph_transitivity_barrat(const igraph_t *graph, + igraph_vector_t *res, + const igraph_vs_t vids, + const igraph_vector_t *weights, + igraph_transitivity_mode_t mode) { + if (igraph_vs_is_all(&vids)) { + return igraph_transitivity_barrat4(graph, res, vids, weights, mode); + } else { + return igraph_transitivity_barrat1(graph, res, vids, weights, mode); + } + + return 0; +} diff --git a/src/triangles_template.h b/src/triangles_template.h new file mode 100644 index 0000000..c7d8ce2 --- /dev/null +++ b/src/triangles_template.h @@ -0,0 +1,118 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +long int no_of_nodes = igraph_vcount(graph); +long int node, i, j, nn; +igraph_adjlist_t allneis; +igraph_vector_int_t *neis1, *neis2; +long int neilen1, neilen2, deg1; +long int *neis; +long int maxdegree; + +igraph_vector_int_t order; +igraph_vector_int_t rank; +igraph_vector_t degree; + +igraph_vector_int_init(&order, no_of_nodes); +IGRAPH_FINALLY(igraph_vector_int_destroy, &order); +IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); + +IGRAPH_CHECK(igraph_degree(graph, °ree, igraph_vss_all(), IGRAPH_ALL, + IGRAPH_LOOPS)); +maxdegree = (long int) igraph_vector_max(°ree) + 1; +igraph_vector_order1_int(°ree, &order, maxdegree); +igraph_vector_int_init(&rank, no_of_nodes); +IGRAPH_FINALLY(igraph_vector_int_destroy, &rank); +for (i = 0; i < no_of_nodes; i++) { + VECTOR(rank)[ VECTOR(order)[i] ] = no_of_nodes - i - 1; +} + +IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, IGRAPH_ALL)); +IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis); +IGRAPH_CHECK(igraph_i_trans4_al_simplify(&allneis, &rank)); + +neis = igraph_Calloc(no_of_nodes, long int); +if (neis == 0) { + IGRAPH_ERROR("undirected local transitivity failed", IGRAPH_ENOMEM); +} +IGRAPH_FINALLY(igraph_free, neis); + +#ifndef TRIANGLES + IGRAPH_CHECK(igraph_vector_resize(res, no_of_nodes)); + igraph_vector_null(res); +#else + igraph_vector_int_clear(res); +#endif + +for (nn = no_of_nodes - 1; nn >= 0; nn--) { + node = VECTOR(order)[nn]; + + IGRAPH_ALLOW_INTERRUPTION(); + + neis1 = igraph_adjlist_get(&allneis, node); + neilen1 = igraph_vector_int_size(neis1); + deg1 = (long int) VECTOR(degree)[node]; + /* Mark the neighbors of the node */ + for (i = 0; i < neilen1; i++) { + neis[ (long int) VECTOR(*neis1)[i] ] = node + 1; + } + + for (i = 0; i < neilen1; i++) { + long int nei = (long int) VECTOR(*neis1)[i]; + neis2 = igraph_adjlist_get(&allneis, nei); + neilen2 = igraph_vector_int_size(neis2); + for (j = 0; j < neilen2; j++) { + long int nei2 = (long int) VECTOR(*neis2)[j]; + if (neis[nei2] == node + 1) { +#ifndef TRIANGLES + VECTOR(*res)[nei2] += 1; + VECTOR(*res)[nei] += 1; + VECTOR(*res)[node] += 1; +#else + IGRAPH_CHECK(igraph_vector_int_push_back(res, node)); + IGRAPH_CHECK(igraph_vector_int_push_back(res, nei)); + IGRAPH_CHECK(igraph_vector_int_push_back(res, nei2)); +#endif + } + } + } + +#ifdef TRANSIT + if (mode == IGRAPH_TRANSITIVITY_ZERO && deg1 < 2) { + VECTOR(*res)[node] = 0.0; + } else { + VECTOR(*res)[node] = VECTOR(*res)[node] / deg1 / (deg1 - 1) * 2.0; + } +#endif +#ifdef TRIEDGES + VECTOR(*res)[node] += deg1; +#endif +} + +igraph_free(neis); +igraph_adjlist_destroy(&allneis); +igraph_vector_int_destroy(&rank); +igraph_vector_destroy(°ree); +igraph_vector_int_destroy(&order); +IGRAPH_FINALLY_CLEAN(5); diff --git a/src/triangles_template1.h b/src/triangles_template1.h new file mode 100644 index 0000000..d4170b6 --- /dev/null +++ b/src/triangles_template1.h @@ -0,0 +1,88 @@ +/* -*- mode: C -*- */ +/* vim:set ts=4 sw=4 sts=4 et: */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +long int no_of_nodes = igraph_vcount(graph); +igraph_vit_t vit; +long int nodes_to_calc; +igraph_vector_t *neis1, *neis2; +igraph_real_t triangles; +long int i, j, k; +long int neilen1, neilen2; +long int *neis; +igraph_lazy_adjlist_t adjlist; + +IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); +IGRAPH_FINALLY(igraph_vit_destroy, &vit); +nodes_to_calc = IGRAPH_VIT_SIZE(vit); + +neis = igraph_Calloc(no_of_nodes, long int); +if (neis == 0) { + IGRAPH_ERROR("local undirected transitivity failed", IGRAPH_ENOMEM); +} +IGRAPH_FINALLY(igraph_free, neis); + +IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + +igraph_lazy_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_SIMPLIFY); +IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + +for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) { + long int node = IGRAPH_VIT_GET(vit); + + IGRAPH_ALLOW_INTERRUPTION(); + + neis1 = igraph_lazy_adjlist_get(&adjlist, (igraph_integer_t) node); + neilen1 = igraph_vector_size(neis1); + for (j = 0; j < neilen1; j++) { + neis[ (long int)VECTOR(*neis1)[j] ] = i + 1; + } + triangles = 0; + + for (j = 0; j < neilen1; j++) { + long int v = (long int) VECTOR(*neis1)[j]; + neis2 = igraph_lazy_adjlist_get(&adjlist, (igraph_integer_t) v); + neilen2 = igraph_vector_size(neis2); + for (k = 0; k < neilen2; k++) { + long int v2 = (long int) VECTOR(*neis2)[k]; + if (neis[v2] == i + 1) { + triangles += 1.0; + } + } + } + +#ifdef TRANSIT + if (mode == IGRAPH_TRANSITIVITY_ZERO && neilen1 < 2) { + VECTOR(*res)[i] = 0.0; + } else { + VECTOR(*res)[i] = triangles / neilen1 / (neilen1 - 1); + } +#else + VECTOR(*res)[i] = triangles / 2; +#endif +} + +igraph_lazy_adjlist_destroy(&adjlist); +igraph_Free(neis); +igraph_vit_destroy(&vit); +IGRAPH_FINALLY_CLEAN(3); diff --git a/src/type_indexededgelist.c b/src/type_indexededgelist.c new file mode 100644 index 0000000..5abc2a6 --- /dev/null +++ b/src/type_indexededgelist.c @@ -0,0 +1,1707 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2005-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_datatype.h" +#include "igraph_interface.h" +#include "igraph_attributes.h" +#include "igraph_memory.h" +#include "config.h" + +/* Internal functions */ + +static int igraph_i_create_start( + igraph_vector_t *res, igraph_vector_t *el, + igraph_vector_t *index, igraph_integer_t nodes); + +/** + * \section about_basic_interface + * + * This is the very minimal API in \a igraph. All the other + * functions use this minimal set for creating and manipulating + * graphs. + * + * This is a very important principle since it makes possible to + * implement other data representations by implementing only this + * minimal set. + */ + +/** + * \ingroup interface + * \function igraph_empty + * \brief Creates an empty graph with some vertices and no edges. + * + * + * The most basic constructor, all the other constructors should call + * this to create a minimal graph object. Our use of the term "empty graph" + * in the above description should be distinguished from the mathematical + * definition of the empty or null graph. Strictly speaking, the empty or null + * graph in graph theory is the graph with no vertices and no edges. However + * by "empty graph" as used in \c igraph we mean a graph having zero or more + * vertices, but no edges. + * \param graph Pointer to a not-yet initialized graph object. + * \param n The number of vertices in the graph, a non-negative + * integer number is expected. + * \param directed Boolean; whether the graph is directed or not. Supported + * values are: + * \clist + * \cli IGRAPH_DIRECTED + * The graph will be \em directed. + * \cli IGRAPH_UNDIRECTED + * The graph will be \em undirected. + * \endclist + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of vertices. + * + * Time complexity: O(|V|) for a graph with + * |V| vertices (and no edges). + * + * \example examples/simple/igraph_empty.c + */ +int igraph_empty(igraph_t *graph, igraph_integer_t n, igraph_bool_t directed) { + return igraph_empty_attrs(graph, n, directed, 0); +} + + +/** + * \ingroup interface + * \function igraph_empty_attrs + * \brief Creates an empty graph with some vertices, no edges and some graph attributes. + * + * + * Use this instead of \ref igraph_empty() if you wish to add some graph + * attributes right after initialization. This function is currently + * not very interesting for the ordinary user. Just supply 0 here or + * use \ref igraph_empty(). + * \param graph Pointer to a not-yet initialized graph object. + * \param n The number of vertices in the graph; a non-negative + * integer number is expected. + * \param directed Boolean; whether the graph is directed or not. Supported + * values are: + * \clist + * \cli IGRAPH_DIRECTED + * Create a \em directed graph. + * \cli IGRAPH_UNDIRECTED + * Create an \em undirected graph. + * \endclist + * \param attr The attributes. + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of vertices. + * + * Time complexity: O(|V|) for a graph with + * |V| vertices (and no edges). + */ +int igraph_empty_attrs(igraph_t *graph, igraph_integer_t n, igraph_bool_t directed, void* attr) { + + if (n < 0) { + IGRAPH_ERROR("cannot create empty graph with negative number of vertices", + IGRAPH_EINVAL); + } + + if (!IGRAPH_FINITE(n)) { + IGRAPH_ERROR("number of vertices is not finite (NA, NaN or Inf)", IGRAPH_EINVAL); + } + + graph->n = 0; + graph->directed = directed; + IGRAPH_VECTOR_INIT_FINALLY(&graph->from, 0); + IGRAPH_VECTOR_INIT_FINALLY(&graph->to, 0); + IGRAPH_VECTOR_INIT_FINALLY(&graph->oi, 0); + IGRAPH_VECTOR_INIT_FINALLY(&graph->ii, 0); + IGRAPH_VECTOR_INIT_FINALLY(&graph->os, 1); + IGRAPH_VECTOR_INIT_FINALLY(&graph->is, 1); + + VECTOR(graph->os)[0] = 0; + VECTOR(graph->is)[0] = 0; + + /* init attributes */ + graph->attr = 0; + IGRAPH_CHECK(igraph_i_attribute_init(graph, attr)); + + /* add the vertices */ + IGRAPH_CHECK(igraph_add_vertices(graph, n, 0)); + + IGRAPH_FINALLY_CLEAN(6); + return 0; +} + +/** + * \ingroup interface + * \function igraph_destroy + * \brief Frees the memory allocated for a graph object. + * + * + * This function should be called for every graph object exactly once. + * + * + * This function invalidates all iterators (of course), but the + * iterators of a graph should be destroyed before the graph itself + * anyway. + * \param graph Pointer to the graph to free. + * + * Time complexity: operating system specific. + */ +void igraph_destroy(igraph_t *graph) { + + IGRAPH_I_ATTRIBUTE_DESTROY(graph); + + igraph_vector_destroy(&graph->from); + igraph_vector_destroy(&graph->to); + igraph_vector_destroy(&graph->oi); + igraph_vector_destroy(&graph->ii); + igraph_vector_destroy(&graph->os); + igraph_vector_destroy(&graph->is); +} + +/** + * \ingroup interface + * \function igraph_copy + * \brief Creates an exact (deep) copy of a graph. + * + * + * This function deeply copies a graph object to create an exact + * replica of it. The new replica should be destroyed by calling + * \ref igraph_destroy() on it when not needed any more. + * + * + * You can also create a shallow copy of a graph by simply using the + * standard assignment operator, but be careful and do \em not + * destroy a shallow replica. To avoid this mistake, creating shallow + * copies is not recommended. + * \param to Pointer to an uninitialized graph object. + * \param from Pointer to the graph object to copy. + * \return Error code. + * + * Time complexity: O(|V|+|E|) for a + * graph with |V| vertices and + * |E| edges. + * + * \example examples/simple/igraph_copy.c + */ + +int igraph_copy(igraph_t *to, const igraph_t *from) { + to->n = from->n; + to->directed = from->directed; + IGRAPH_CHECK(igraph_vector_copy(&to->from, &from->from)); + IGRAPH_FINALLY(igraph_vector_destroy, &to->from); + IGRAPH_CHECK(igraph_vector_copy(&to->to, &from->to)); + IGRAPH_FINALLY(igraph_vector_destroy, &to->to); + IGRAPH_CHECK(igraph_vector_copy(&to->oi, &from->oi)); + IGRAPH_FINALLY(igraph_vector_destroy, &to->oi); + IGRAPH_CHECK(igraph_vector_copy(&to->ii, &from->ii)); + IGRAPH_FINALLY(igraph_vector_destroy, &to->ii); + IGRAPH_CHECK(igraph_vector_copy(&to->os, &from->os)); + IGRAPH_FINALLY(igraph_vector_destroy, &to->os); + IGRAPH_CHECK(igraph_vector_copy(&to->is, &from->is)); + IGRAPH_FINALLY(igraph_vector_destroy, &to->is); + + IGRAPH_I_ATTRIBUTE_COPY(to, from, 1, 1, 1); /* does IGRAPH_CHECK */ + + IGRAPH_FINALLY_CLEAN(6); + return 0; +} + +/** + * \ingroup interface + * \function igraph_add_edges + * \brief Adds edges to a graph object. + * + * + * The edges are given in a vector, the + * first two elements define the first edge (the order is + * from, to for directed + * graphs). The vector + * should contain even number of integer numbers between zero and the + * number of vertices in the graph minus one (inclusive). If you also + * want to add new vertices, call igraph_add_vertices() first. + * \param graph The graph to which the edges will be added. + * \param edges The edges themselves. + * \param attr The attributes of the new edges, only used by high level + * interfaces currently, you can supply 0 here. + * \return Error code: + * \c IGRAPH_EINVEVECTOR: invalid (odd) + * edges vector length, \c IGRAPH_EINVVID: + * invalid vertex id in edges vector. + * + * This function invalidates all iterators. + * + * + * Time complexity: O(|V|+|E|) where + * |V| is the number of vertices and + * |E| is the number of + * edges in the \em new, extended graph. + * + * \example examples/simple/igraph_add_edges.c + */ +int igraph_add_edges(igraph_t *graph, const igraph_vector_t *edges, + void *attr) { + long int no_of_edges = igraph_vector_size(&graph->from); + long int edges_to_add = igraph_vector_size(edges) / 2; + long int i = 0; + igraph_error_handler_t *oldhandler; + int ret1, ret2; + igraph_vector_t newoi, newii; + igraph_bool_t directed = igraph_is_directed(graph); + + if (igraph_vector_size(edges) % 2 != 0) { + IGRAPH_ERROR("invalid (odd) length of edges vector", IGRAPH_EINVEVECTOR); + } + if (!igraph_vector_isininterval(edges, 0, igraph_vcount(graph) - 1)) { + IGRAPH_ERROR("cannot add edges", IGRAPH_EINVVID); + } + + /* from & to */ + IGRAPH_CHECK(igraph_vector_reserve(&graph->from, no_of_edges + edges_to_add)); + IGRAPH_CHECK(igraph_vector_reserve(&graph->to, no_of_edges + edges_to_add)); + + while (i < edges_to_add * 2) { + if (directed || VECTOR(*edges)[i] > VECTOR(*edges)[i + 1]) { + igraph_vector_push_back(&graph->from, VECTOR(*edges)[i++]); /* reserved */ + igraph_vector_push_back(&graph->to, VECTOR(*edges)[i++]); /* reserved */ + } else { + igraph_vector_push_back(&graph->to, VECTOR(*edges)[i++]); /* reserved */ + igraph_vector_push_back(&graph->from, VECTOR(*edges)[i++]); /* reserved */ + } + } + + /* disable the error handler temporarily */ + oldhandler = igraph_set_error_handler(igraph_error_handler_ignore); + + /* oi & ii */ + ret1 = igraph_vector_init(&newoi, no_of_edges); + ret2 = igraph_vector_init(&newii, no_of_edges); + if (ret1 != 0 || ret2 != 0) { + igraph_vector_resize(&graph->from, no_of_edges); /* gets smaller */ + igraph_vector_resize(&graph->to, no_of_edges); /* gets smaller */ + igraph_set_error_handler(oldhandler); + IGRAPH_ERROR("cannot add edges", IGRAPH_ERROR_SELECT_2(ret1, ret2)); + } + ret1 = igraph_vector_order(&graph->from, &graph->to, &newoi, graph->n); + ret2 = igraph_vector_order(&graph->to, &graph->from, &newii, graph->n); + if (ret1 != 0 || ret2 != 0) { + igraph_vector_resize(&graph->from, no_of_edges); + igraph_vector_resize(&graph->to, no_of_edges); + igraph_vector_destroy(&newoi); + igraph_vector_destroy(&newii); + igraph_set_error_handler(oldhandler); + IGRAPH_ERROR("cannot add edges", IGRAPH_ERROR_SELECT_2(ret1, ret2)); + } + + /* Attributes */ + if (graph->attr) { + igraph_set_error_handler(oldhandler); + ret1 = igraph_i_attribute_add_edges(graph, edges, attr); + igraph_set_error_handler(igraph_error_handler_ignore); + if (ret1 != 0) { + igraph_vector_resize(&graph->from, no_of_edges); + igraph_vector_resize(&graph->to, no_of_edges); + igraph_vector_destroy(&newoi); + igraph_vector_destroy(&newii); + igraph_set_error_handler(oldhandler); + IGRAPH_ERROR("cannot add edges", ret1); + } + } + + /* os & is, its length does not change, error safe */ + igraph_i_create_start(&graph->os, &graph->from, &newoi, graph->n); + igraph_i_create_start(&graph->is, &graph->to, &newii, graph->n); + + /* everything went fine */ + igraph_vector_destroy(&graph->oi); + igraph_vector_destroy(&graph->ii); + graph->oi = newoi; + graph->ii = newii; + igraph_set_error_handler(oldhandler); + + return 0; +} + +/** + * \ingroup interface + * \function igraph_add_vertices + * \brief Adds vertices to a graph. + * + * + * This function invalidates all iterators. + * + * \param graph The graph object to extend. + * \param nv Non-negative integer giving the number of + * vertices to add. + * \param attr The attributes of the new vertices, only used by + * high level interfaces, you can supply 0 here. + * \return Error code: + * \c IGRAPH_EINVAL: invalid number of new + * vertices. + * + * Time complexity: O(|V|) where + * |V| is + * the number of vertices in the \em new, extended graph. + * + * \example examples/simple/igraph_add_vertices.c + */ +int igraph_add_vertices(igraph_t *graph, igraph_integer_t nv, void *attr) { + long int ec = igraph_ecount(graph); + long int i; + + if (nv < 0) { + IGRAPH_ERROR("cannot add negative number of vertices", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_reserve(&graph->os, graph->n + nv + 1)); + IGRAPH_CHECK(igraph_vector_reserve(&graph->is, graph->n + nv + 1)); + + igraph_vector_resize(&graph->os, graph->n + nv + 1); /* reserved */ + igraph_vector_resize(&graph->is, graph->n + nv + 1); /* reserved */ + for (i = graph->n + 1; i < graph->n + nv + 1; i++) { + VECTOR(graph->os)[i] = ec; + VECTOR(graph->is)[i] = ec; + } + + graph->n += nv; + + if (graph->attr) { + IGRAPH_CHECK(igraph_i_attribute_add_vertices(graph, nv, attr)); + } + + return 0; +} + +/** + * \ingroup interface + * \function igraph_delete_edges + * \brief Removes edges from a graph. + * + * + * The edges to remove are given as an edge selector. + * + * + * This function cannot remove vertices, they will be kept, even if + * they lose all their edges. + * + * + * This function invalidates all iterators. + * \param graph The graph to work on. + * \param edges The edges to remove. + * \return Error code. + * + * Time complexity: O(|V|+|E|) where + * |V| + * and |E| are the number of vertices + * and edges in the \em original graph, respectively. + * + * \example examples/simple/igraph_delete_edges.c + */ +int igraph_delete_edges(igraph_t *graph, igraph_es_t edges) { + long int no_of_edges = igraph_ecount(graph); + long int no_of_nodes = igraph_vcount(graph); + long int edges_to_remove = 0; + long int remaining_edges; + igraph_eit_t eit; + + igraph_vector_t newfrom, newto, newoi; + + int *mark; + long int i, j; + + mark = igraph_Calloc(no_of_edges, int); + if (mark == 0) { + IGRAPH_ERROR("Cannot delete edges", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, mark); + + IGRAPH_CHECK(igraph_eit_create(graph, edges, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + + for (IGRAPH_EIT_RESET(eit); !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + long int e = IGRAPH_EIT_GET(eit); + if (mark[e] == 0) { + edges_to_remove++; + mark[e]++; + } + } + remaining_edges = no_of_edges - edges_to_remove; + + /* We don't need the iterator any more */ + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + + IGRAPH_VECTOR_INIT_FINALLY(&newfrom, remaining_edges); + IGRAPH_VECTOR_INIT_FINALLY(&newto, remaining_edges); + + /* Actually remove the edges, move from pos i to pos j in newfrom/newto */ + for (i = 0, j = 0; j < remaining_edges; i++) { + if (mark[i] == 0) { + VECTOR(newfrom)[j] = VECTOR(graph->from)[i]; + VECTOR(newto)[j] = VECTOR(graph->to)[i]; + j++; + } + } + + /* Create index, this might require additional memory */ + IGRAPH_VECTOR_INIT_FINALLY(&newoi, remaining_edges); + IGRAPH_CHECK(igraph_vector_order(&newfrom, &newto, &newoi, no_of_nodes)); + IGRAPH_CHECK(igraph_vector_order(&newto, &newfrom, &graph->ii, no_of_nodes)); + + /* Edge attributes, we need an index that gives the ids of the + original edges for every new edge. + */ + if (graph->attr) { + igraph_vector_t idx; + IGRAPH_VECTOR_INIT_FINALLY(&idx, remaining_edges); + for (i = 0, j = 0; i < no_of_edges; i++) { + if (mark[i] == 0) { + VECTOR(idx)[j++] = i; + } + } + IGRAPH_CHECK(igraph_i_attribute_permute_edges(graph, graph, &idx)); + igraph_vector_destroy(&idx); + IGRAPH_FINALLY_CLEAN(1); + } + + /* Ok, we've all memory needed, free the old structure */ + igraph_vector_destroy(&graph->from); + igraph_vector_destroy(&graph->to); + igraph_vector_destroy(&graph->oi); + graph->from = newfrom; + graph->to = newto; + graph->oi = newoi; + IGRAPH_FINALLY_CLEAN(3); + + igraph_Free(mark); + IGRAPH_FINALLY_CLEAN(1); + + /* Create start vectors, no memory is needed for this */ + igraph_i_create_start(&graph->os, &graph->from, &graph->oi, + (igraph_integer_t) no_of_nodes); + igraph_i_create_start(&graph->is, &graph->to, &graph->ii, + (igraph_integer_t) no_of_nodes); + + /* Nothing to deallocate... */ + return 0; +} + +/** + * \ingroup interface + * \function igraph_delete_vertices + * \brief Removes vertices (with all their edges) from the graph. + * + * + * This function changes the ids of the vertices (except in some very + * special cases, but these should not be relied on anyway). + * + * + * This function invalidates all iterators. + * + * \param graph The graph to work on. + * \param vertices The ids of the vertices to remove in a + * vector. The vector may contain the same id more + * than once. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex id. + * + * Time complexity: O(|V|+|E|), + * |V| and + * |E| are the number of vertices and + * edges in the original graph. + * + * \example examples/simple/igraph_delete_vertices.c + */ +int igraph_delete_vertices(igraph_t *graph, const igraph_vs_t vertices) { + return igraph_delete_vertices_idx(graph, vertices, /* idx= */ 0, + /* invidx= */ 0); +} + +int igraph_delete_vertices_idx(igraph_t *graph, const igraph_vs_t vertices, + igraph_vector_t *idx, + igraph_vector_t *invidx) { + + long int no_of_edges = igraph_ecount(graph); + long int no_of_nodes = igraph_vcount(graph); + igraph_vector_t edge_recoding, vertex_recoding; + igraph_vector_t *my_vertex_recoding = &vertex_recoding; + igraph_vit_t vit; + igraph_t newgraph; + long int i, j; + long int remaining_vertices, remaining_edges; + + if (idx) { + my_vertex_recoding = idx; + IGRAPH_CHECK(igraph_vector_resize(idx, no_of_nodes)); + igraph_vector_null(idx); + } else { + IGRAPH_VECTOR_INIT_FINALLY(&vertex_recoding, no_of_nodes); + } + + IGRAPH_VECTOR_INIT_FINALLY(&edge_recoding, no_of_edges); + + IGRAPH_CHECK(igraph_vit_create(graph, vertices, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + /* mark the vertices to delete */ + for (; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit) ) { + long int vertex = IGRAPH_VIT_GET(vit); + if (vertex < 0 || vertex >= no_of_nodes) { + IGRAPH_ERROR("Cannot delete vertices", IGRAPH_EINVVID); + } + VECTOR(*my_vertex_recoding)[vertex] = 1; + } + /* create vertex recoding vector */ + for (remaining_vertices = 0, i = 0; i < no_of_nodes; i++) { + if (VECTOR(*my_vertex_recoding)[i] == 0) { + VECTOR(*my_vertex_recoding)[i] = remaining_vertices + 1; + remaining_vertices++; + } else { + VECTOR(*my_vertex_recoding)[i] = 0; + } + } + /* create edge recoding vector */ + for (remaining_edges = 0, i = 0; i < no_of_edges; i++) { + long int from = (long int) VECTOR(graph->from)[i]; + long int to = (long int) VECTOR(graph->to)[i]; + if (VECTOR(*my_vertex_recoding)[from] != 0 && + VECTOR(*my_vertex_recoding)[to ] != 0) { + VECTOR(edge_recoding)[i] = remaining_edges + 1; + remaining_edges++; + } + } + + /* start creating the graph */ + newgraph.n = (igraph_integer_t) remaining_vertices; + newgraph.directed = graph->directed; + + /* allocate vectors */ + IGRAPH_VECTOR_INIT_FINALLY(&newgraph.from, remaining_edges); + IGRAPH_VECTOR_INIT_FINALLY(&newgraph.to, remaining_edges); + IGRAPH_VECTOR_INIT_FINALLY(&newgraph.oi, remaining_edges); + IGRAPH_VECTOR_INIT_FINALLY(&newgraph.ii, remaining_edges); + IGRAPH_VECTOR_INIT_FINALLY(&newgraph.os, remaining_vertices + 1); + IGRAPH_VECTOR_INIT_FINALLY(&newgraph.is, remaining_vertices + 1); + + /* Add the edges */ + for (i = 0, j = 0; j < remaining_edges; i++) { + if (VECTOR(edge_recoding)[i] > 0) { + long int from = (long int) VECTOR(graph->from)[i]; + long int to = (long int) VECTOR(graph->to )[i]; + VECTOR(newgraph.from)[j] = VECTOR(*my_vertex_recoding)[from] - 1; + VECTOR(newgraph.to )[j] = VECTOR(*my_vertex_recoding)[to] - 1; + j++; + } + } + /* update oi & ii */ + IGRAPH_CHECK(igraph_vector_order(&newgraph.from, &newgraph.to, &newgraph.oi, + remaining_vertices)); + IGRAPH_CHECK(igraph_vector_order(&newgraph.to, &newgraph.from, &newgraph.ii, + remaining_vertices)); + + IGRAPH_CHECK(igraph_i_create_start(&newgraph.os, &newgraph.from, + &newgraph.oi, (igraph_integer_t) + remaining_vertices)); + IGRAPH_CHECK(igraph_i_create_start(&newgraph.is, &newgraph.to, + &newgraph.ii, (igraph_integer_t) + remaining_vertices)); + + /* attributes */ + IGRAPH_I_ATTRIBUTE_COPY(&newgraph, graph, + /*graph=*/ 1, /*vertex=*/0, /*edge=*/0); + IGRAPH_FINALLY_CLEAN(6); + IGRAPH_FINALLY(igraph_destroy, &newgraph); + + if (newgraph.attr) { + igraph_vector_t iidx; + IGRAPH_VECTOR_INIT_FINALLY(&iidx, remaining_vertices); + for (i = 0; i < no_of_nodes; i++) { + long int jj = (long int) VECTOR(*my_vertex_recoding)[i]; + if (jj != 0) { + VECTOR(iidx)[ jj - 1 ] = i; + } + } + IGRAPH_CHECK(igraph_i_attribute_permute_vertices(graph, + &newgraph, + &iidx)); + IGRAPH_CHECK(igraph_vector_resize(&iidx, remaining_edges)); + for (i = 0; i < no_of_edges; i++) { + long int jj = (long int) VECTOR(edge_recoding)[i]; + if (jj != 0) { + VECTOR(iidx)[ jj - 1 ] = i; + } + } + IGRAPH_CHECK(igraph_i_attribute_permute_edges(graph, &newgraph, &iidx)); + igraph_vector_destroy(&iidx); + IGRAPH_FINALLY_CLEAN(1); + } + + igraph_vit_destroy(&vit); + igraph_vector_destroy(&edge_recoding); + igraph_destroy(graph); + *graph = newgraph; + + IGRAPH_FINALLY_CLEAN(3); + + /* TODO: this is duplicate */ + if (invidx) { + IGRAPH_CHECK(igraph_vector_resize(invidx, remaining_vertices)); + for (i = 0; i < no_of_nodes; i++) { + long int newid = (long int) VECTOR(*my_vertex_recoding)[i]; + if (newid != 0) { + VECTOR(*invidx)[newid - 1] = i; + } + } + } + + if (!idx) { + igraph_vector_destroy(my_vertex_recoding); + IGRAPH_FINALLY_CLEAN(1); + } + + return 0; +} + +/** + * \ingroup interface + * \function igraph_vcount + * \brief The number of vertices in a graph. + * + * \param graph The graph. + * \return Number of vertices. + * + * Time complexity: O(1) + */ +igraph_integer_t igraph_vcount(const igraph_t *graph) { + return graph->n; +} + +/** + * \ingroup interface + * \function igraph_ecount + * \brief The number of edges in a graph. + * + * \param graph The graph. + * \return Number of edges. + * + * Time complexity: O(1) + */ +igraph_integer_t igraph_ecount(const igraph_t *graph) { + return (igraph_integer_t) igraph_vector_size(&graph->from); +} + +/** + * \ingroup interface + * \function igraph_neighbors + * \brief Adjacent vertices to a vertex. + * + * \param graph The graph to work on. + * \param neis This vector will contain the result. The vector should + * be initialized beforehand and will be resized. Starting from igraph + * version 0.4 this vector is always sorted, the vertex ids are + * in increasing order. + * \param pnode The id of the node for which the adjacent vertices are + * to be searched. + * \param mode Defines the way adjacent vertices are searched in + * directed graphs. It can have the following values: + * \c IGRAPH_OUT, vertices reachable by an + * edge from the specified vertex are searched; + * \c IGRAPH_IN, vertices from which the + * specified vertex is reachable are searched; + * \c IGRAPH_ALL, both kinds of vertices are + * searched. + * This parameter is ignored for undirected graphs. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex id. + * \c IGRAPH_EINVMODE: invalid mode argument. + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: O(d), + * d is the number + * of adjacent vertices to the queried vertex. + * + * \example examples/simple/igraph_neighbors.c + */ +int igraph_neighbors(const igraph_t *graph, igraph_vector_t *neis, igraph_integer_t pnode, + igraph_neimode_t mode) { + + long int length = 0, idx = 0; + long int i, j; + + long int node = pnode; + + if (node < 0 || node > igraph_vcount(graph) - 1) { + IGRAPH_ERROR("cannot get neighbors", IGRAPH_EINVVID); + } + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("cannot get neighbors", IGRAPH_EINVMODE); + } + + if (! graph->directed) { + mode = IGRAPH_ALL; + } + + /* Calculate needed space first & allocate it*/ + + if (mode & IGRAPH_OUT) { + length += (VECTOR(graph->os)[node + 1] - VECTOR(graph->os)[node]); + } + if (mode & IGRAPH_IN) { + length += (VECTOR(graph->is)[node + 1] - VECTOR(graph->is)[node]); + } + + IGRAPH_CHECK(igraph_vector_resize(neis, length)); + + if (!igraph_is_directed(graph) || mode != IGRAPH_ALL) { + + if (mode & IGRAPH_OUT) { + j = (long int) VECTOR(graph->os)[node + 1]; + for (i = (long int) VECTOR(graph->os)[node]; i < j; i++) { + VECTOR(*neis)[idx++] = + VECTOR(graph->to)[ (long int)VECTOR(graph->oi)[i] ]; + } + } + if (mode & IGRAPH_IN) { + j = (long int) VECTOR(graph->is)[node + 1]; + for (i = (long int) VECTOR(graph->is)[node]; i < j; i++) { + VECTOR(*neis)[idx++] = + VECTOR(graph->from)[ (long int)VECTOR(graph->ii)[i] ]; + } + } + } else { + /* both in- and out- neighbors in a directed graph, + we need to merge the two 'vectors' */ + long int jj1 = (long int) VECTOR(graph->os)[node + 1]; + long int j2 = (long int) VECTOR(graph->is)[node + 1]; + long int i1 = (long int) VECTOR(graph->os)[node]; + long int i2 = (long int) VECTOR(graph->is)[node]; + while (i1 < jj1 && i2 < j2) { + long int n1 = (long int) VECTOR(graph->to)[ + (long int)VECTOR(graph->oi)[i1] ]; + long int n2 = (long int) VECTOR(graph->from)[ + (long int)VECTOR(graph->ii)[i2] ]; + if (n1 < n2) { + VECTOR(*neis)[idx++] = n1; + i1++; + } else if (n1 > n2) { + VECTOR(*neis)[idx++] = n2; + i2++; + } else { + VECTOR(*neis)[idx++] = n1; + VECTOR(*neis)[idx++] = n2; + i1++; + i2++; + } + } + while (i1 < jj1) { + long int n1 = (long int) VECTOR(graph->to)[ + (long int)VECTOR(graph->oi)[i1] ]; + VECTOR(*neis)[idx++] = n1; + i1++; + } + while (i2 < j2) { + long int n2 = (long int) VECTOR(graph->from)[ + (long int)VECTOR(graph->ii)[i2] ]; + VECTOR(*neis)[idx++] = n2; + i2++; + } + } + + return 0; +} + +/** + * \ingroup internal + * + */ + +static int igraph_i_create_start( + igraph_vector_t *res, igraph_vector_t *el, + igraph_vector_t *iindex, igraph_integer_t nodes) { + +# define EDGE(i) (VECTOR(*el)[ (long int) VECTOR(*iindex)[(i)] ]) + + long int no_of_nodes; + long int no_of_edges; + long int i, j, idx; + + no_of_nodes = nodes; + no_of_edges = igraph_vector_size(el); + + /* result */ + + IGRAPH_CHECK(igraph_vector_resize(res, nodes + 1)); + + /* create the index */ + + if (igraph_vector_size(el) == 0) { + /* empty graph */ + igraph_vector_null(res); + } else { + idx = -1; + for (i = 0; i <= EDGE(0); i++) { + idx++; VECTOR(*res)[idx] = 0; + } + for (i = 1; i < no_of_edges; i++) { + long int n = (long int) (EDGE(i) - EDGE((long int)VECTOR(*res)[idx])); + for (j = 0; j < n; j++) { + idx++; VECTOR(*res)[idx] = i; + } + } + j = (long int) EDGE((long int)VECTOR(*res)[idx]); + for (i = 0; i < no_of_nodes - j; i++) { + idx++; VECTOR(*res)[idx] = no_of_edges; + } + } + + /* clean */ + +# undef EDGE + return 0; +} + +/** + * \ingroup interface + * \function igraph_is_directed + * \brief Is this a directed graph? + * + * \param graph The graph. + * \return Logical value, TRUE if the graph is directed, + * FALSE otherwise. + * + * Time complexity: O(1) + * + * \example examples/simple/igraph_is_directed.c + */ + +igraph_bool_t igraph_is_directed(const igraph_t *graph) { + return graph->directed; +} + +/** + * \ingroup interface + * \function igraph_degree + * \brief The degree of some vertices in a graph. + * + * + * This function calculates the in-, out- or total degree of the + * specified vertices. + * \param graph The graph. + * \param res Vector, this will contain the result. It should be + * initialized and will be resized to be the appropriate size. + * \param vids Vector, giving the vertex ids of which the degree will + * be calculated. + * \param mode Defines the type of the degree. Valid modes are: + * \c IGRAPH_OUT, out-degree; + * \c IGRAPH_IN, in-degree; + * \c IGRAPH_ALL, total degree (sum of the + * in- and out-degree). + * This parameter is ignored for undirected graphs. + * \param loops Boolean, gives whether the self-loops should be + * counted. + * \return Error code: + * \c IGRAPH_EINVVID: invalid vertex id. + * \c IGRAPH_EINVMODE: invalid mode argument. + * + * Time complexity: O(v) if + * loops is + * TRUE, and + * O(v*d) + * otherwise. v is the number of + * vertices for which the degree will be calculated, and + * d is their (average) degree. + * + * \sa \ref igraph_strength() for the version that takes into account + * edge weights. + * + * \example examples/simple/igraph_degree.c + */ +int igraph_degree(const igraph_t *graph, igraph_vector_t *res, + const igraph_vs_t vids, + igraph_neimode_t mode, igraph_bool_t loops) { + + long int nodes_to_calc; + long int i, j; + igraph_vit_t vit; + + IGRAPH_CHECK(igraph_vit_create(graph, vids, &vit)); + IGRAPH_FINALLY(igraph_vit_destroy, &vit); + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && mode != IGRAPH_ALL) { + IGRAPH_ERROR("degree calculation failed", IGRAPH_EINVMODE); + } + + nodes_to_calc = IGRAPH_VIT_SIZE(vit); + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_CHECK(igraph_vector_resize(res, nodes_to_calc)); + igraph_vector_null(res); + + if (loops) { + if (mode & IGRAPH_OUT) { + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + long int vid = IGRAPH_VIT_GET(vit); + VECTOR(*res)[i] += (VECTOR(graph->os)[vid + 1] - VECTOR(graph->os)[vid]); + } + } + if (mode & IGRAPH_IN) { + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + long int vid = IGRAPH_VIT_GET(vit); + VECTOR(*res)[i] += (VECTOR(graph->is)[vid + 1] - VECTOR(graph->is)[vid]); + } + } + } else { /* no loops */ + if (mode & IGRAPH_OUT) { + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + long int vid = IGRAPH_VIT_GET(vit); + VECTOR(*res)[i] += (VECTOR(graph->os)[vid + 1] - VECTOR(graph->os)[vid]); + for (j = (long int) VECTOR(graph->os)[vid]; + j < VECTOR(graph->os)[vid + 1]; j++) { + if (VECTOR(graph->to)[ (long int)VECTOR(graph->oi)[j] ] == vid) { + VECTOR(*res)[i] -= 1; + } + } + } + } + if (mode & IGRAPH_IN) { + for (IGRAPH_VIT_RESET(vit), i = 0; + !IGRAPH_VIT_END(vit); + IGRAPH_VIT_NEXT(vit), i++) { + long int vid = IGRAPH_VIT_GET(vit); + VECTOR(*res)[i] += (VECTOR(graph->is)[vid + 1] - VECTOR(graph->is)[vid]); + for (j = (long int) VECTOR(graph->is)[vid]; + j < VECTOR(graph->is)[vid + 1]; j++) { + if (VECTOR(graph->from)[ (long int)VECTOR(graph->ii)[j] ] == vid) { + VECTOR(*res)[i] -= 1; + } + } + } + } + } /* loops */ + + igraph_vit_destroy(&vit); + IGRAPH_FINALLY_CLEAN(1); + + return 0; +} + +/** + * \function igraph_edge + * \brief Gives the head and tail vertices of an edge. + * + * \param graph The graph object. + * \param eid The edge id. + * \param from Pointer to an \type igraph_integer_t. The tail of the edge + * will be placed here. + * \param to Pointer to an \type igraph_integer_t. The head of the edge + * will be placed here. + * \return Error code. The current implementation always returns with + * success. + * \sa \ref igraph_get_eid() for the opposite operation. + * + * Added in version 0.2. + * + * Time complexity: O(1). + */ + +int igraph_edge(const igraph_t *graph, igraph_integer_t eid, + igraph_integer_t *from, igraph_integer_t *to) { + + if (igraph_is_directed(graph)) { + *from = (igraph_integer_t) VECTOR(graph->from)[(long int)eid]; + *to = (igraph_integer_t) VECTOR(graph->to )[(long int)eid]; + } else { + *from = (igraph_integer_t) VECTOR(graph->to )[(long int)eid]; + *to = (igraph_integer_t) VECTOR(graph->from)[(long int)eid]; + } + + return 0; +} + +int igraph_edges(const igraph_t *graph, igraph_es_t eids, + igraph_vector_t *edges) { + + igraph_eit_t eit; + long int n, ptr = 0; + + IGRAPH_CHECK(igraph_eit_create(graph, eids, &eit)); + IGRAPH_FINALLY(igraph_eit_destroy, &eit); + n = IGRAPH_EIT_SIZE(eit); + IGRAPH_CHECK(igraph_vector_resize(edges, n * 2)); + if (igraph_is_directed(graph)) { + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + long int e = IGRAPH_EIT_GET(eit); + VECTOR(*edges)[ptr++] = IGRAPH_FROM(graph, e); + VECTOR(*edges)[ptr++] = IGRAPH_TO(graph, e); + } + } else { + for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { + long int e = IGRAPH_EIT_GET(eit); + VECTOR(*edges)[ptr++] = IGRAPH_TO(graph, e); + VECTOR(*edges)[ptr++] = IGRAPH_FROM(graph, e); + } + } + + igraph_eit_destroy(&eit); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/* This is an unsafe macro. Only supply variable names, i.e. no + expressions as parameters, otherwise nasty things can happen */ + +#define BINSEARCH(start,end,value,iindex,edgelist,N,pos) \ + do { \ + while ((start) < (end)) { \ + long int mid=(start)+((end)-(start))/2; \ + long int e=(long int) VECTOR((iindex))[mid]; \ + if (VECTOR((edgelist))[e] < (value)) { \ + (start)=mid+1; \ + } else { \ + (end)=mid; \ + } \ + } \ + if ((start)<(N)) { \ + long int e=(long int) VECTOR((iindex))[(start)]; \ + if (VECTOR((edgelist))[e] == (value)) { \ + *(pos)=(igraph_integer_t) e; \ + } \ + } } while(0) + +#define FIND_DIRECTED_EDGE(graph,xfrom,xto,eid) \ + do { \ + long int start=(long int) VECTOR(graph->os)[xfrom]; \ + long int end=(long int) VECTOR(graph->os)[xfrom+1]; \ + long int N=end; \ + long int start2=(long int) VECTOR(graph->is)[xto]; \ + long int end2=(long int) VECTOR(graph->is)[xto+1]; \ + long int N2=end2; \ + if (end-startoi,graph->to,N,eid); \ + } else { \ + BINSEARCH(start2,end2,xfrom,graph->ii,graph->from,N2,eid); \ + } \ + } while (0) + +#define FIND_UNDIRECTED_EDGE(graph,from,to,eid) \ + do { \ + long int xfrom1= from > to ? from : to; \ + long int xto1= from > to ? to : from; \ + FIND_DIRECTED_EDGE(graph,xfrom1,xto1,eid); \ + } while (0) + +/** + * \function igraph_get_eid + * \brief Get the edge id from the end points of an edge. + * + * For undirected graphs \c pfrom and \c pto are exchangeable. + * + * \param graph The graph object. + * \param eid Pointer to an integer, the edge id will be stored here. + * \param pfrom The starting point of the edge. + * \param pto The end point of the edge. + * \param directed Logical constant, whether to search for directed + * edges in a directed graph. Ignored for undirected graphs. + * \param error Logical scalar, whether to report an error if the edge + * was not found. If it is false, then -1 will be assigned to \p eid. + * \return Error code. + * \sa \ref igraph_edge() for the opposite operation. + * + * Time complexity: O(log (d)), where d is smaller of the out-degree + * of \c pfrom and in-degree of \c pto if \p directed is true. If \p directed + * is false, then it is O(log(d)+log(d2)), where d is the same as before and + * d2 is the minimum of the out-degree of \c pto and the in-degree of \c pfrom. + * + * \example examples/simple/igraph_get_eid.c + * + * Added in version 0.2. + */ + +int igraph_get_eid(const igraph_t *graph, igraph_integer_t *eid, + igraph_integer_t pfrom, igraph_integer_t pto, + igraph_bool_t directed, igraph_bool_t error) { + + long int from = pfrom, to = pto; + long int nov = igraph_vcount(graph); + + if (from < 0 || to < 0 || from > nov - 1 || to > nov - 1) { + IGRAPH_ERROR("cannot get edge id", IGRAPH_EINVVID); + } + + *eid = -1; + if (igraph_is_directed(graph)) { + + /* Directed graph */ + FIND_DIRECTED_EDGE(graph, from, to, eid); + if (!directed && *eid < 0) { + FIND_DIRECTED_EDGE(graph, to, from, eid); + } + + } else { + + /* Undirected graph, they only have one mode */ + FIND_UNDIRECTED_EDGE(graph, from, to, eid); + + } + + if (*eid < 0) { + if (error) { + IGRAPH_ERROR("Cannot get edge id, no such edge", IGRAPH_EINVAL); + } + } + + return IGRAPH_SUCCESS; +} + +int igraph_get_eids_pairs(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *pairs, + igraph_bool_t directed, igraph_bool_t error); + +int igraph_get_eids_path(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *path, + igraph_bool_t directed, igraph_bool_t error); + +int igraph_get_eids_pairs(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *pairs, + igraph_bool_t directed, igraph_bool_t error) { + long int n = igraph_vector_size(pairs); + long int no_of_nodes = igraph_vcount(graph); + long int i; + igraph_integer_t eid = -1; + + if (n % 2 != 0) { + IGRAPH_ERROR("Cannot get edge ids, invalid length of edge ids", + IGRAPH_EINVAL); + } + if (!igraph_vector_isininterval(pairs, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot get edge ids, invalid vertex id", IGRAPH_EINVVID); + } + + IGRAPH_CHECK(igraph_vector_resize(eids, n / 2)); + + if (igraph_is_directed(graph)) { + for (i = 0; i < n / 2; i++) { + long int from = (long int) VECTOR(*pairs)[2 * i]; + long int to = (long int) VECTOR(*pairs)[2 * i + 1]; + + eid = -1; + FIND_DIRECTED_EDGE(graph, from, to, &eid); + if (!directed && eid < 0) { + FIND_DIRECTED_EDGE(graph, to, from, &eid); + } + + VECTOR(*eids)[i] = eid; + if (eid < 0 && error) { + IGRAPH_ERROR("Cannot get edge id, no such edge", IGRAPH_EINVAL); + } + } + } else { + for (i = 0; i < n / 2; i++) { + long int from = (long int) VECTOR(*pairs)[2 * i]; + long int to = (long int) VECTOR(*pairs)[2 * i + 1]; + + eid = -1; + FIND_UNDIRECTED_EDGE(graph, from, to, &eid); + VECTOR(*eids)[i] = eid; + if (eid < 0 && error) { + IGRAPH_ERROR("Cannot get edge id, no such edge", IGRAPH_EINVAL); + } + } + } + + return 0; +} + +int igraph_get_eids_path(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *path, + igraph_bool_t directed, igraph_bool_t error) { + + long int n = igraph_vector_size(path); + long int no_of_nodes = igraph_vcount(graph); + long int i; + igraph_integer_t eid = -1; + + if (!igraph_vector_isininterval(path, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot get edge ids, invalid vertex id", IGRAPH_EINVVID); + } + + IGRAPH_CHECK(igraph_vector_resize(eids, n == 0 ? 0 : n - 1)); + + if (igraph_is_directed(graph)) { + for (i = 0; i < n - 1; i++) { + long int from = (long int) VECTOR(*path)[i]; + long int to = (long int) VECTOR(*path)[i + 1]; + + eid = -1; + FIND_DIRECTED_EDGE(graph, from, to, &eid); + if (!directed && eid < 0) { + FIND_DIRECTED_EDGE(graph, to, from, &eid); + } + + VECTOR(*eids)[i] = eid; + if (eid < 0 && error) { + IGRAPH_ERROR("Cannot get edge id, no such edge", IGRAPH_EINVAL); + } + } + } else { + for (i = 0; i < n - 1; i++) { + long int from = (long int) VECTOR(*path)[i]; + long int to = (long int) VECTOR(*path)[i + 1]; + + eid = -1; + FIND_UNDIRECTED_EDGE(graph, from, to, &eid); + VECTOR(*eids)[i] = eid; + if (eid < 0 && error) { + IGRAPH_ERROR("Cannot get edge id, no such edge", IGRAPH_EINVAL); + } + } + } + + return 0; +} + +/** + * \function igraph_get_eids + * Return edge ids based on the adjacent vertices. + * + * This function operates in two modes. If the \c pairs argument is + * not a null pointer, but the \c path argument is, then it searches + * for the edge ids of all pairs of vertices given in \c pairs. The + * pairs of vertex ids are taken consecutively from the vector, + * i.e. VECTOR(pairs)[0] and + * VECTOR(pairs)[1] give the first + * pair, VECTOR(pairs)[2] and + * VECTOR(pairs)[3] the second pair, etc. + * + * + * If the \c pairs argument is a null pointer, and \c path is not a + * null pointer, then the \c path is interpreted as a path given by + * vertex ids and the edges along the path are returned. + * + * + * If neither \c pairs nor \c path are null pointers, then both are + * considered (first \c pairs and then \c path), and the results are + * concatenated. + * + * + * If the \c error argument is true, then it is an error to give pairs + * of vertices that are not connected. Otherwise -1 is + * reported for not connected vertices. + * + * + * If there are multiple edges in the graph, then these are ignored; + * i.e. for a given pair of vertex ids, always the same edge id is + * returned, even if the pair is given multiple time in \c pairs or in + * \c path. See \ref igraph_get_eids_multi() for a similar function + * that works differently in case of multiple edges. + * + * \param graph The input graph. + * \param eids Pointer to an initialized vector, the result is stored + * here. It will be resized as needed. + * \param pairs Vector giving pairs of vertices, or a null pointer. + * \param path Vector giving vertex ids along a path, or a null + * pointer. + * \param directed Logical scalar, whether to consider edge directions + * in directed graphs. This is ignored for undirected graphs. + * \param error Logical scalar, whether it is an error to supply + * non-connected vertices. If false, then -1 is + * returned for non-connected pairs. + * \return Error code. + * + * Time complexity: O(n log(d)), where n is the number of queried + * edges and d is the average degree of the vertices. + * + * \sa \ref igraph_get_eid() for a single edge, \ref + * igraph_get_eids_multi() for a version that handles multiple edges + * better (at a cost). + * + * \example examples/simple/igraph_get_eids.c + */ + +int igraph_get_eids(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *pairs, + const igraph_vector_t *path, + igraph_bool_t directed, igraph_bool_t error) { + + if (!pairs && !path) { + igraph_vector_clear(eids); + return 0; + } else if (pairs && !path) { + return igraph_get_eids_pairs(graph, eids, pairs, directed, error); + } else if (!pairs && path) { + return igraph_get_eids_path(graph, eids, path, directed, error); + } else { + /* both */ + igraph_vector_t tmp; + IGRAPH_VECTOR_INIT_FINALLY(&tmp, 0); + IGRAPH_CHECK(igraph_get_eids_pairs(graph, eids, pairs, directed, error)); + IGRAPH_CHECK(igraph_get_eids_path(graph, &tmp, path, directed, error)); + IGRAPH_CHECK(igraph_vector_append(eids, &tmp)); + igraph_vector_destroy(&tmp); + IGRAPH_FINALLY_CLEAN(1); + return 0; + } +} + +#undef BINSEARCH +#undef FIND_DIRECTED_EDGE +#undef FIND_UNDIRECTED_EDGE + +#define BINSEARCH(start,end,value,iindex,edgelist,N,pos,seen) \ + do { \ + while ((start) < (end)) { \ + long int mid=(start)+((end)-(start))/2; \ + long int e=(long int) VECTOR((iindex))[mid]; \ + if (VECTOR((edgelist))[e] < (value)) { \ + (start)=mid+1; \ + } else { \ + (end)=mid; \ + } \ + } \ + if ((start)<(N)) { \ + long int e=(long int) VECTOR((iindex))[(start)]; \ + while ((start)<(N) && seen[e] && VECTOR(edgelist)[e] == (value)) { \ + (start)++; \ + e=(long int) VECTOR(iindex)[(start)]; \ + } \ + if ((start)<(N) && !(seen[e]) && VECTOR(edgelist)[e] == (value)) { \ + *(pos)=(igraph_integer_t) e; \ + } \ + } } while(0) + +#define FIND_DIRECTED_EDGE(graph,xfrom,xto,eid,seen) \ + do { \ + long int start=(long int) VECTOR(graph->os)[xfrom]; \ + long int end=(long int) VECTOR(graph->os)[xfrom+1]; \ + long int N=end; \ + long int start2=(long int) VECTOR(graph->is)[xto]; \ + long int end2=(long int) VECTOR(graph->is)[xto+1]; \ + long int N2=end2; \ + if (end-startoi,graph->to,N,eid,seen); \ + } else { \ + BINSEARCH(start2,end2,xfrom,graph->ii,graph->from,N2,eid,seen); \ + } \ + } while (0) + +#define FIND_UNDIRECTED_EDGE(graph,from,to,eid,seen) \ + do { \ + long int xfrom1= from > to ? from : to; \ + long int xto1= from > to ? to : from; \ + FIND_DIRECTED_EDGE(graph,xfrom1,xto1,eid,seen); \ + } while (0) + + +int igraph_get_eids_multipairs(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *pairs, + igraph_bool_t directed, igraph_bool_t error); + +int igraph_get_eids_multipath(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *path, + igraph_bool_t directed, igraph_bool_t error); + +int igraph_get_eids_multipairs(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *pairs, + igraph_bool_t directed, igraph_bool_t error) { + + long int n = igraph_vector_size(pairs); + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_bool_t *seen; + long int i; + igraph_integer_t eid = -1; + + if (n % 2 != 0) { + IGRAPH_ERROR("Cannot get edge ids, invalid length of edge ids", + IGRAPH_EINVAL); + } + if (!igraph_vector_isininterval(pairs, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot get edge ids, invalid vertex id", IGRAPH_EINVVID); + } + + seen = igraph_Calloc(no_of_edges, igraph_bool_t); + if (seen == 0) { + IGRAPH_ERROR("Cannot get edge ids", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, seen); + IGRAPH_CHECK(igraph_vector_resize(eids, n / 2)); + + if (igraph_is_directed(graph)) { + for (i = 0; i < n / 2; i++) { + long int from = (long int) VECTOR(*pairs)[2 * i]; + long int to = (long int) VECTOR(*pairs)[2 * i + 1]; + + eid = -1; + FIND_DIRECTED_EDGE(graph, from, to, &eid, seen); + if (!directed && eid < 0) { + FIND_DIRECTED_EDGE(graph, to, from, &eid, seen); + } + + VECTOR(*eids)[i] = eid; + if (eid >= 0) { + seen[(long int)(eid)] = 1; + } else if (error) { + IGRAPH_ERROR("Cannot get edge id, no such edge", IGRAPH_EINVAL); + } + } + } else { + for (i = 0; i < n / 2; i++) { + long int from = (long int) VECTOR(*pairs)[2 * i]; + long int to = (long int) VECTOR(*pairs)[2 * i + 1]; + + eid = -1; + FIND_UNDIRECTED_EDGE(graph, from, to, &eid, seen); + VECTOR(*eids)[i] = eid; + if (eid >= 0) { + seen[(long int)(eid)] = 1; + } else if (error) { + IGRAPH_ERROR("Cannot get edge id, no such edge", IGRAPH_EINVAL); + } + } + } + + igraph_Free(seen); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +int igraph_get_eids_multipath(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *path, + igraph_bool_t directed, igraph_bool_t error) { + + long int n = igraph_vector_size(path); + long int no_of_nodes = igraph_vcount(graph); + long int no_of_edges = igraph_ecount(graph); + igraph_bool_t *seen; + long int i; + igraph_integer_t eid = -1; + + if (!igraph_vector_isininterval(path, 0, no_of_nodes - 1)) { + IGRAPH_ERROR("Cannot get edge ids, invalid vertex id", IGRAPH_EINVVID); + } + + seen = igraph_Calloc(no_of_edges, igraph_bool_t); + if (!seen) { + IGRAPH_ERROR("Cannot get edge ids", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, seen); + IGRAPH_CHECK(igraph_vector_resize(eids, n == 0 ? 0 : n - 1)); + + if (igraph_is_directed(graph)) { + for (i = 0; i < n - 1; i++) { + long int from = (long int) VECTOR(*path)[i]; + long int to = (long int) VECTOR(*path)[i + 1]; + + eid = -1; + FIND_DIRECTED_EDGE(graph, from, to, &eid, seen); + if (!directed && eid < 0) { + FIND_DIRECTED_EDGE(graph, to, from, &eid, seen); + } + + VECTOR(*eids)[i] = eid; + if (eid >= 0) { + seen[(long int)(eid)] = 1; + } else if (error) { + IGRAPH_ERROR("Cannot get edge id, no such edge", IGRAPH_EINVAL); + } + } + } else { + for (i = 0; i < n - 1; i++) { + long int from = (long int) VECTOR(*path)[i]; + long int to = (long int) VECTOR(*path)[i + 1]; + + eid = -1; + FIND_UNDIRECTED_EDGE(graph, from, to, &eid, seen); + VECTOR(*eids)[i] = eid; + if (eid >= 0) { + seen[(long int)(eid)] = 1; + } else if (error) { + IGRAPH_ERROR("Cannot get edge id, no such edge", IGRAPH_EINVAL); + } + } + } + + igraph_Free(seen); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +#undef BINSEARCH +#undef FIND_DIRECTED_EDGE +#undef FIND_UNDIRECTED_EDGE + +/** + * \function igraph_get_eids_multi + * \brief Query edge ids based on their adjacent vertices, handle multiple edges. + * + * This function operates in two modes. If the \c pairs argument is + * not a null pointer, but the \c path argument is, then it searches + * for the edge ids of all pairs of vertices given in \c pairs. The + * pairs of vertex ids are taken consecutively from the vector, + * i.e. VECTOR(pairs)[0] and + * VECTOR(pairs)[1] give the first pair, + * VECTOR(pairs)[2] and VECTOR(pairs)[3] the + * second pair, etc. + * + * + * If the \c pairs argument is a null pointer, and \c path is not a + * null pointer, then the \c path is interpreted as a path given by + * vertex ids and the edges along the path are returned. + * + * + * If the \c error argument is true, then it is an error to give pairs of + * vertices that are not connected. Otherwise -1 is + * returned for not connected vertex pairs. + * + * + * An error is triggered if both \c pairs and \c path are non-null + * pointers. + * + * + * This function handles multiple edges properly, i.e. if the same + * pair is given multiple times and they are indeed connected by + * multiple edges, then each time a different edge id is reported. + * + * \param graph The input graph. + * \param eids Pointer to an initialized vector, the result is stored + * here. It will be resized as needed. + * \param pairs Vector giving pairs of vertices, or a null pointer. + * \param path Vector giving vertex ids along a path, or a null + * pointer. + * \param directed Logical scalar, whether to consider edge directions + * in directed graphs. This is ignored for undirected graphs. + * \param error Logical scalar, whether to report an error if + * non-connected vertices are specified. If false, then -1 + * is returned for non-connected vertex pairs. + * \return Error code. + * + * Time complexity: O(|E|+n log(d)), where |E| is the number of edges + * in the graph, n is the number of queried edges and d is the average + * degree of the vertices. + * + * \sa \ref igraph_get_eid() for a single edge, \ref + * igraph_get_eids() for a faster version that does not handle + * multiple edges. + */ + +int igraph_get_eids_multi(const igraph_t *graph, igraph_vector_t *eids, + const igraph_vector_t *pairs, + const igraph_vector_t *path, + igraph_bool_t directed, igraph_bool_t error) { + + if (!pairs && !path) { + igraph_vector_clear(eids); + return 0; + } else if (pairs && !path) { + return igraph_get_eids_multipairs(graph, eids, pairs, directed, error); + } else if (!pairs && path) { + return igraph_get_eids_multipath(graph, eids, path, directed, error); + } else { /* both */ + IGRAPH_ERROR("Give `pairs' or `path' but not both", IGRAPH_EINVAL); + } +} + +/** + * \function igraph_adjacent + * \brief Gives the incident edges of a vertex. + * + * This function was superseded by \ref igraph_incident() in igraph 0.6. + * Please use \ref igraph_incident() instead of this function. + * + * + * Added in version 0.2, deprecated in version 0.6. + */ +int igraph_adjacent(const igraph_t *graph, igraph_vector_t *eids, + igraph_integer_t pnode, igraph_neimode_t mode) { + IGRAPH_WARNING("igraph_adjacent is deprecated, use igraph_incident"); + return igraph_incident(graph, eids, pnode, mode); +} + +/** + * \function igraph_incident + * \brief Gives the incident edges of a vertex. + * + * \param graph The graph object. + * \param eids An initialized \type vector_t object. It will be resized + * to hold the result. + * \param pnode A vertex id. + * \param mode Specifies what kind of edges to include for directed + * graphs. \c IGRAPH_OUT means only outgoing edges, \c IGRAPH_IN only + * incoming edges, \c IGRAPH_ALL both. This parameter is ignored for + * undirected graphs. + * \return Error code. \c IGRAPH_EINVVID: invalid \p pnode argument, + * \c IGRAPH_EINVMODE: invalid \p mode argument. + * + * Added in version 0.2. + * + * Time complexity: O(d), the number of incident edges to \p pnode. + */ + +int igraph_incident(const igraph_t *graph, igraph_vector_t *eids, + igraph_integer_t pnode, igraph_neimode_t mode) { + + long int length = 0, idx = 0; + long int i, j; + + long int node = pnode; + + if (node < 0 || node > igraph_vcount(graph) - 1) { + IGRAPH_ERROR("cannot get neighbors", IGRAPH_EINVVID); + } + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("cannot get neighbors", IGRAPH_EINVMODE); + } + + if (! graph->directed) { + mode = IGRAPH_ALL; + } + + /* Calculate needed space first & allocate it*/ + + if (mode & IGRAPH_OUT) { + length += (VECTOR(graph->os)[node + 1] - VECTOR(graph->os)[node]); + } + if (mode & IGRAPH_IN) { + length += (VECTOR(graph->is)[node + 1] - VECTOR(graph->is)[node]); + } + + IGRAPH_CHECK(igraph_vector_resize(eids, length)); + + if (mode & IGRAPH_OUT) { + j = (long int) VECTOR(graph->os)[node + 1]; + for (i = (long int) VECTOR(graph->os)[node]; i < j; i++) { + VECTOR(*eids)[idx++] = VECTOR(graph->oi)[i]; + } + } + if (mode & IGRAPH_IN) { + j = (long int) VECTOR(graph->is)[node + 1]; + for (i = (long int) VECTOR(graph->is)[node]; i < j; i++) { + VECTOR(*eids)[idx++] = VECTOR(graph->ii)[i]; + } + } + + return 0; +} diff --git a/src/types.c b/src/types.c new file mode 100644 index 0000000..97d1e46 --- /dev/null +++ b/src/types.c @@ -0,0 +1,146 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include + +#ifdef _MSC_VER + #define snprintf _snprintf +#endif + +#ifdef DBL_DIG + /* Use DBL_DIG to determine the maximum precision used for %g */ + #define STRINGIFY_HELPER(x) #x + #define STRINGIFY(x) STRINGIFY_HELPER(x) + #define IGRAPH_REAL_PRINTF_PRECISE_FORMAT "%." STRINGIFY(DBL_DIG) "g" +#else + /* Assume a precision of 10 digits for %g */ + #define IGRAPH_REAL_PRINTF_PRECISE_FORMAT "%.10g" +#endif + +#ifndef USING_R +int igraph_real_printf(igraph_real_t val) { + if (igraph_finite(val)) { + return printf("%g", val); + } else if (igraph_is_nan(val)) { + return printf("NaN"); + } else if (igraph_is_inf(val)) { + if (val < 0) { + return printf("-Inf"); + } else { + return printf("Inf"); + } + } else { + /* fallback */ + return printf("%g", val); + } +} +#endif + +int igraph_real_fprintf(FILE *file, igraph_real_t val) { + if (igraph_finite(val)) { + return fprintf(file, "%g", val); + } else if (igraph_is_nan(val)) { + return fprintf(file, "NaN"); + } else if (igraph_is_inf(val)) { + if (val < 0) { + return fprintf(file, "-Inf"); + } else { + return fprintf(file, "Inf"); + } + } else { + /* fallback */ + return fprintf(file, "%g", val); + } +} + +int igraph_real_snprintf(char* str, size_t size, igraph_real_t val) { + if (igraph_finite(val)) { + return snprintf(str, size, "%g", val); + } else if (igraph_is_nan(val)) { + return snprintf(str, size, "NaN"); + } else if (igraph_is_inf(val)) { + if (val < 0) { + return snprintf(str, size, "-Inf"); + } else { + return snprintf(str, size, "Inf"); + } + } else { + /* fallback */ + return snprintf(str, size, "%g", val); + } +} + +#ifndef USING_R +int igraph_real_printf_precise(igraph_real_t val) { + if (igraph_finite(val)) { + return printf(IGRAPH_REAL_PRINTF_PRECISE_FORMAT, val); + } else if (igraph_is_nan(val)) { + return printf("NaN"); + } else if (igraph_is_inf(val)) { + if (val < 0) { + return printf("-Inf"); + } else { + return printf("Inf"); + } + } else { + /* fallback */ + return printf(IGRAPH_REAL_PRINTF_PRECISE_FORMAT, val); + } +} +#endif + +int igraph_real_fprintf_precise(FILE *file, igraph_real_t val) { + if (igraph_finite(val)) { + return fprintf(file, IGRAPH_REAL_PRINTF_PRECISE_FORMAT, val); + } else if (igraph_is_nan(val)) { + return fprintf(file, "NaN"); + } else if (igraph_is_inf(val)) { + if (val < 0) { + return fprintf(file, "-Inf"); + } else { + return fprintf(file, "Inf"); + } + } else { + /* fallback */ + return fprintf(file, IGRAPH_REAL_PRINTF_PRECISE_FORMAT, val); + } +} + +int igraph_real_snprintf_precise(char* str, size_t size, igraph_real_t val) { + if (igraph_finite(val)) { + return snprintf(str, size, IGRAPH_REAL_PRINTF_PRECISE_FORMAT, val); + } else if (igraph_is_nan(val)) { + return snprintf(str, size, "NaN"); + } else if (igraph_is_inf(val)) { + if (val < 0) { + return snprintf(str, size, "-Inf"); + } else { + return snprintf(str, size, "Inf"); + } + } else { + /* fallback */ + return snprintf(str, size, IGRAPH_REAL_PRINTF_PRECISE_FORMAT, val); + } +} + diff --git a/src/vector.c b/src/vector.c new file mode 100644 index 0000000..ab11bcc --- /dev/null +++ b/src/vector.c @@ -0,0 +1,466 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_types_internal.h" +#include "igraph_complex.h" +#include "bigint.h" +#include "config.h" +#include + +#define BASE_IGRAPH_REAL +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_IGRAPH_REAL + +#define BASE_FLOAT +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_FLOAT + +#define BASE_LONG +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_LONG + +#define BASE_CHAR +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_CHAR + +#define BASE_BOOL +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_BOOL + +#define BASE_INT +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_INT + +#define BASE_COMPLEX +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_COMPLEX + +#define BASE_LIMB +#include "igraph_pmt.h" +#include "vector.pmt" +#include "igraph_pmt_off.h" +#undef BASE_LIMB + +#include "igraph_math.h" + +int igraph_vector_floor(const igraph_vector_t *from, igraph_vector_long_t *to) { + long int i, n = igraph_vector_size(from); + + IGRAPH_CHECK(igraph_vector_long_resize(to, n)); + for (i = 0; i < n; i++) { + VECTOR(*to)[i] = (long int) floor(VECTOR(*from)[i]); + } + return 0; +} + +int igraph_vector_round(const igraph_vector_t *from, igraph_vector_long_t *to) { + long int i, n = igraph_vector_size(from); + + IGRAPH_CHECK(igraph_vector_long_resize(to, n)); + for (i = 0; i < n; i++) { + VECTOR(*to)[i] = (long int) round(VECTOR(*from)[i]); + } + return 0; +} + +int igraph_vector_order2(igraph_vector_t *v) { + + igraph_indheap_t heap; + + igraph_indheap_init_array(&heap, VECTOR(*v), igraph_vector_size(v)); + IGRAPH_FINALLY(igraph_indheap_destroy, &heap); + + igraph_vector_clear(v); + while (!igraph_indheap_empty(&heap)) { + IGRAPH_CHECK(igraph_vector_push_back(v, igraph_indheap_max_index(&heap) - 1)); + igraph_indheap_delete_max(&heap); + } + + igraph_indheap_destroy(&heap); + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_order + * \brief Calculate the order of the elements in a vector. + * + * + * The smallest element will have order zero, the second smallest + * order one, etc. + * \param v The original \type igraph_vector_t object. + * \param v2 A secondary key, another \type igraph_vector_t object. + * \param res An initialized \type igraph_vector_t object, it will be + * resized to match the size of \p v. The + * result of the computation will be stored here. + * \param nodes Hint, the largest element in \p v. + * \return Error code: + * \c IGRAPH_ENOMEM: out of memory + * + * Time complexity: O() + */ + +int igraph_vector_order(const igraph_vector_t* v, + const igraph_vector_t *v2, + igraph_vector_t* res, igraph_real_t nodes) { + long int edges = igraph_vector_size(v); + igraph_vector_t ptr; + igraph_vector_t rad; + long int i, j; + + assert(v != NULL); + assert(v->stor_begin != NULL); + + IGRAPH_VECTOR_INIT_FINALLY(&ptr, (long int) nodes + 1); + IGRAPH_VECTOR_INIT_FINALLY(&rad, edges); + IGRAPH_CHECK(igraph_vector_resize(res, edges)); + + for (i = 0; i < edges; i++) { + long int radix = (long int) v2->stor_begin[i]; + if (VECTOR(ptr)[radix] != 0) { + VECTOR(rad)[i] = VECTOR(ptr)[radix]; + } + VECTOR(ptr)[radix] = i + 1; + } + + j = 0; + for (i = 0; i < nodes + 1; i++) { + if (VECTOR(ptr)[i] != 0) { + long int next = (long int) VECTOR(ptr)[i] - 1; + res->stor_begin[j++] = next; + while (VECTOR(rad)[next] != 0) { + next = (long int) VECTOR(rad)[next] - 1; + res->stor_begin[j++] = next; + } + } + } + + igraph_vector_null(&ptr); + igraph_vector_null(&rad); + + for (i = 0; i < edges; i++) { + long int edge = (long int) VECTOR(*res)[edges - i - 1]; + long int radix = (long int) VECTOR(*v)[edge]; + if (VECTOR(ptr)[radix] != 0) { + VECTOR(rad)[edge] = VECTOR(ptr)[radix]; + } + VECTOR(ptr)[radix] = edge + 1; + } + + j = 0; + for (i = 0; i < nodes + 1; i++) { + if (VECTOR(ptr)[i] != 0) { + long int next = (long int) VECTOR(ptr)[i] - 1; + res->stor_begin[j++] = next; + while (VECTOR(rad)[next] != 0) { + next = (long int) VECTOR(rad)[next] - 1; + res->stor_begin[j++] = next; + } + } + } + + igraph_vector_destroy(&ptr); + igraph_vector_destroy(&rad); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +int igraph_vector_order1(const igraph_vector_t* v, + igraph_vector_t* res, igraph_real_t nodes) { + long int edges = igraph_vector_size(v); + igraph_vector_t ptr; + igraph_vector_t rad; + long int i, j; + + assert(v != NULL); + assert(v->stor_begin != NULL); + + IGRAPH_VECTOR_INIT_FINALLY(&ptr, (long int) nodes + 1); + IGRAPH_VECTOR_INIT_FINALLY(&rad, edges); + IGRAPH_CHECK(igraph_vector_resize(res, edges)); + + for (i = 0; i < edges; i++) { + long int radix = (long int) v->stor_begin[i]; + if (VECTOR(ptr)[radix] != 0) { + VECTOR(rad)[i] = VECTOR(ptr)[radix]; + } + VECTOR(ptr)[radix] = i + 1; + } + + j = 0; + for (i = 0; i < nodes + 1; i++) { + if (VECTOR(ptr)[i] != 0) { + long int next = (long int) VECTOR(ptr)[i] - 1; + res->stor_begin[j++] = next; + while (VECTOR(rad)[next] != 0) { + next = (long int) VECTOR(rad)[next] - 1; + res->stor_begin[j++] = next; + } + } + } + + igraph_vector_destroy(&ptr); + igraph_vector_destroy(&rad); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +int igraph_vector_order1_int(const igraph_vector_t* v, + igraph_vector_int_t* res, + igraph_real_t nodes) { + long int edges = igraph_vector_size(v); + igraph_vector_t ptr; + igraph_vector_t rad; + long int i, j; + + assert(v != NULL); + assert(v->stor_begin != NULL); + + IGRAPH_VECTOR_INIT_FINALLY(&ptr, (long int) nodes + 1); + IGRAPH_VECTOR_INIT_FINALLY(&rad, edges); + IGRAPH_CHECK(igraph_vector_int_resize(res, edges)); + + for (i = 0; i < edges; i++) { + long int radix = (long int) v->stor_begin[i]; + if (VECTOR(ptr)[radix] != 0) { + VECTOR(rad)[i] = VECTOR(ptr)[radix]; + } + VECTOR(ptr)[radix] = i + 1; + } + + j = 0; + for (i = 0; i < nodes + 1; i++) { + if (VECTOR(ptr)[i] != 0) { + long int next = (long int) VECTOR(ptr)[i] - 1; + res->stor_begin[j++] = next; + while (VECTOR(rad)[next] != 0) { + next = (long int) VECTOR(rad)[next] - 1; + res->stor_begin[j++] = next; + } + } + } + + igraph_vector_destroy(&ptr); + igraph_vector_destroy(&rad); + IGRAPH_FINALLY_CLEAN(2); + + return 0; +} + +int igraph_vector_rank(const igraph_vector_t *v, igraph_vector_t *res, + long int nodes) { + + igraph_vector_t rad; + igraph_vector_t ptr; + long int edges = igraph_vector_size(v); + long int i, c = 0; + + IGRAPH_VECTOR_INIT_FINALLY(&rad, nodes); + IGRAPH_VECTOR_INIT_FINALLY(&ptr, edges); + IGRAPH_CHECK(igraph_vector_resize(res, edges)); + + for (i = 0; i < edges; i++) { + long int elem = (long int) VECTOR(*v)[i]; + VECTOR(ptr)[i] = VECTOR(rad)[elem]; + VECTOR(rad)[elem] = i + 1; + } + + for (i = 0; i < nodes; i++) { + long int p = (long int) VECTOR(rad)[i]; + while (p != 0) { + VECTOR(*res)[p - 1] = c++; + p = (long int) VECTOR(ptr)[p - 1]; + } + } + + igraph_vector_destroy(&ptr); + igraph_vector_destroy(&rad); + IGRAPH_FINALLY_CLEAN(2); + return 0; +} + +#ifndef USING_R +int igraph_vector_complex_print(const igraph_vector_complex_t *v) { + long int i, n = igraph_vector_complex_size(v); + if (n != 0) { + igraph_complex_t z = VECTOR(*v)[0]; + printf("%g%+gi", IGRAPH_REAL(z), IGRAPH_IMAG(z)); + } + for (i = 1; i < n; i++) { + igraph_complex_t z = VECTOR(*v)[i]; + printf(" %g%+gi", IGRAPH_REAL(z), IGRAPH_IMAG(z)); + } + printf("\n"); + return 0; +} +#endif + +int igraph_vector_complex_fprint(const igraph_vector_complex_t *v, + FILE *file) { + long int i, n = igraph_vector_complex_size(v); + if (n != 0) { + igraph_complex_t z = VECTOR(*v)[0]; + fprintf(file, "%g%+g", IGRAPH_REAL(z), IGRAPH_IMAG(z)); + } + for (i = 1; i < n; i++) { + igraph_complex_t z = VECTOR(*v)[i]; + fprintf(file, " %g%+g", IGRAPH_REAL(z), IGRAPH_IMAG(z)); + } + fprintf(file, "\n"); + return 0; +} + +int igraph_vector_complex_real(const igraph_vector_complex_t *v, + igraph_vector_t *real) { + int i, n = (int) igraph_vector_complex_size(v); + IGRAPH_CHECK(igraph_vector_resize(real, n)); + for (i = 0; i < n; i++) { + VECTOR(*real)[i] = IGRAPH_REAL(VECTOR(*v)[i]); + } + + return 0; +} + +int igraph_vector_complex_imag(const igraph_vector_complex_t *v, + igraph_vector_t *imag) { + int i, n = (int) igraph_vector_complex_size(v); + IGRAPH_CHECK(igraph_vector_resize(imag, n)); + for (i = 0; i < n; i++) { + VECTOR(*imag)[i] = IGRAPH_IMAG(VECTOR(*v)[i]); + } + + return 0; +} + +int igraph_vector_complex_realimag(const igraph_vector_complex_t *v, + igraph_vector_t *real, + igraph_vector_t *imag) { + int i, n = (int) igraph_vector_complex_size(v); + IGRAPH_CHECK(igraph_vector_resize(real, n)); + IGRAPH_CHECK(igraph_vector_resize(imag, n)); + for (i = 0; i < n; i++) { + igraph_complex_t z = VECTOR(*v)[i]; + VECTOR(*real)[i] = IGRAPH_REAL(z); + VECTOR(*imag)[i] = IGRAPH_IMAG(z); + } + + return 0; +} + +int igraph_vector_complex_create(igraph_vector_complex_t *v, + const igraph_vector_t *real, + const igraph_vector_t *imag) { + int i, n = (int) igraph_vector_size(real); + if (n != igraph_vector_size(imag)) { + IGRAPH_ERROR("Real and imag vector sizes don't match", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_complex_init(v, n)); + /* FINALLY not needed */ + + for (i = 0; i < n; i++) { + VECTOR(*v)[i] = igraph_complex(VECTOR(*real)[i], VECTOR(*imag)[i]); + } + + return 0; +} + +int igraph_vector_complex_create_polar(igraph_vector_complex_t *v, + const igraph_vector_t *r, + const igraph_vector_t *theta) { + int i, n = (int) igraph_vector_size(r); + if (n != igraph_vector_size(theta)) { + IGRAPH_ERROR("'r' and 'theta' vector sizes don't match", IGRAPH_EINVAL); + } + + IGRAPH_CHECK(igraph_vector_complex_init(v, n)); + /* FINALLY not needed */ + + for (i = 0; i < n; i++) { + VECTOR(*v)[i] = igraph_complex_polar(VECTOR(*r)[i], VECTOR(*theta)[i]); + } + + return 0; +} + +igraph_bool_t igraph_vector_e_tol(const igraph_vector_t *lhs, + const igraph_vector_t *rhs, + igraph_real_t tol) { + long int i, s; + assert(lhs != 0); + assert(rhs != 0); + assert(lhs->stor_begin != 0); + assert(rhs->stor_begin != 0); + + s = igraph_vector_size(lhs); + if (s != igraph_vector_size(rhs)) { + return 0; + } else { + if (tol == 0) { + tol = DBL_EPSILON; + } + for (i = 0; i < s; i++) { + igraph_real_t l = VECTOR(*lhs)[i]; + igraph_real_t r = VECTOR(*rhs)[i]; + if (l < r - tol || l > r + tol) { + return 0; + } + } + return 1; + } +} + +int igraph_vector_zapsmall(igraph_vector_t *v, igraph_real_t tol) { + int i, n = igraph_vector_size(v); + if (tol < 0.0) { + IGRAPH_ERROR("`tol' tolerance must be non-negative", IGRAPH_EINVAL); + } + if (tol == 0.0) { + tol = sqrt(DBL_EPSILON); + } + for (i = 0; i < n; i++) { + igraph_real_t val = VECTOR(*v)[i]; + if (val < tol && val > -tol) { + VECTOR(*v)[i] = 0.0; + } + } + return 0; +} diff --git a/src/vector.pmt b/src/vector.pmt new file mode 100644 index 0000000..82217b6 --- /dev/null +++ b/src/vector.pmt @@ -0,0 +1,2684 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_memory.h" +#include "igraph_error.h" +#include "igraph_random.h" +#include "igraph_qsort.h" + +#include +#include /* memcpy & co. */ +#include +#include /* va_start & co */ +#include + +/** + * \ingroup vector + * \section about_igraph_vector_t_objects About \type igraph_vector_t objects + * + * The \type igraph_vector_t data type is a simple and efficient + * interface to arrays containing numbers. It is something + * similar as (but much simpler than) the \type vector template + * in the C++ standard library. + * + * Vectors are used extensively in \a igraph, all + * functions which expect or return a list of numbers use + * igraph_vector_t to achieve this. + * + * The \type igraph_vector_t type usually uses + * O(n) space + * to store n elements. Sometimes it + * uses more, this is because vectors can shrink, but even if they + * shrink, the current implementation does not free a single bit of + * memory. + * + * The elements in an \type igraph_vector_t + * object are indexed from zero, we follow the usual C convention + * here. + * + * The elements of a vector always occupy a single block of + * memory, the starting address of this memory block can be queried + * with the \ref VECTOR macro. This way, vector objects can be used + * with standard mathematical libraries, like the GNU Scientific + * Library. + */ + +/** + * \ingroup vector + * \section igraph_vector_constructors_and_destructors Constructors and + * Destructors + * + * \type igraph_vector_t objects have to be initialized before using + * them, this is analogous to calling a constructor on them. There are a + * number of \type igraph_vector_t constructors, for your + * convenience. \ref igraph_vector_init() is the basic constructor, it + * creates a vector of the given length, filled with zeros. + * \ref igraph_vector_copy() creates a new identical copy + * of an already existing and initialized vector. \ref + * igraph_vector_init_copy() creates a vector by copying a regular C array. + * \ref igraph_vector_init_seq() creates a vector containing a regular + * sequence with increment one. + * + * \ref igraph_vector_view() is a special constructor, it allows you to + * handle a regular C array as a \type vector without copying + * its elements. + * + * + * If a \type igraph_vector_t object is not needed any more, it + * should be destroyed to free its allocated memory by calling the + * \type igraph_vector_t destructor, \ref igraph_vector_destroy(). + * + * Note that vectors created by \ref igraph_vector_view() are special, + * you mustn't call \ref igraph_vector_destroy() on these. + */ + +/** + * \ingroup vector + * \function igraph_vector_init + * \brief Initializes a vector object (constructor). + * + * + * Every vector needs to be initialized before it can be used, and + * there are a number of initialization functions or otherwise called + * constructors. This function constructs a vector of the given size and + * initializes each entry to 0. Note that \ref igraph_vector_null() can be + * used to set each element of a vector to zero. However, if you want a + * vector of zeros, it is much faster to use this function than to create a + * vector and then invoke \ref igraph_vector_null(). + * + * + * Every vector object initialized by this function should be + * destroyed (ie. the memory allocated for it should be freed) when it + * is not needed anymore, the \ref igraph_vector_destroy() function is + * responsible for this. + * \param v Pointer to a not yet initialized vector object. + * \param size The size of the vector. + * \return error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, the amount of + * \quote time \endquote required to allocate + * O(n) elements, + * n is the number of elements. + */ + +int FUNCTION(igraph_vector, init) (TYPE(igraph_vector)* v, int long size) { + long int alloc_size = size > 0 ? size : 1; + if (size < 0) { + size = 0; + } + v->stor_begin = igraph_Calloc(alloc_size, BASE); + if (v->stor_begin == 0) { + IGRAPH_ERROR("cannot init vector", IGRAPH_ENOMEM); + } + v->stor_end = v->stor_begin + alloc_size; + v->end = v->stor_begin + size; + + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_view + * \brief Handle a regular C array as a \type igraph_vector_t. + * + * + * This is a special \type igraph_vector_t constructor. It allows to + * handle a regular C array as a \type igraph_vector_t temporarily. + * Be sure that you \em don't ever call the destructor (\ref + * igraph_vector_destroy()) on objects created by this constructor. + * \param v Pointer to an uninitialized \type igraph_vector_t object. + * \param data Pointer, the C array. It may not be \c NULL. + * \param length The length of the C array. + * \return Pointer to the vector object, the same as the + * \p v parameter, for convenience. + * + * Time complexity: O(1) + */ + +const TYPE(igraph_vector)*FUNCTION(igraph_vector, view) (const TYPE(igraph_vector) *v, + const BASE *data, + long int length) { + TYPE(igraph_vector) *v2 = (TYPE(igraph_vector)*)v; + + assert(data != 0); + + v2->stor_begin = (BASE*)data; + v2->stor_end = (BASE*)data + length; + v2->end = v2->stor_end; + return v; +} + +#ifndef BASE_COMPLEX + +/** + * \ingroup vector + * \function igraph_vector_init_real + * \brief Create an \type igraph_vector_t from the parameters. + * + * + * Because of how C and the C library handles variable length argument + * lists, it is required that you supply real constants to this + * function. This means that + * \verbatim igraph_vector_t v; + * igraph_vector_init_real(&v, 5, 1,2,3,4,5); \endverbatim + * is an error at runtime and the results are undefined. This is + * the proper way: + * \verbatim igraph_vector_t v; + * igraph_vector_init_real(&v, 5, 1.0,2.0,3.0,4.0,5.0); \endverbatim + * \param v Pointer to an uninitialized \type igraph_vector_t object. + * \param no Positive integer, the number of \type igraph_real_t + * parameters to follow. + * \param ... The elements of the vector. + * \return Error code, this can be \c IGRAPH_ENOMEM + * if there isn't enough memory to allocate the vector. + * + * \sa \ref igraph_vector_init_real_end(), \ref igraph_vector_init_int() for similar + * functions. + * + * Time complexity: depends on the time required to allocate memory, + * but at least O(n), the number of + * elements in the vector. + */ + +int FUNCTION(igraph_vector, init_real)(TYPE(igraph_vector) *v, int no, ...) { + int i = 0; + va_list ap; + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(v, no)); + + va_start(ap, no); + for (i = 0; i < no; i++) { + VECTOR(*v)[i] = (BASE) va_arg(ap, double); + } + va_end(ap); + + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_init_real_end + * \brief Create an \type igraph_vector_t from the parameters. + * + * + * This constructor is similar to \ref igraph_vector_init_real(), the only + * difference is that instead of giving the number of elements in the + * vector, a special marker element follows the last real vector + * element. + * \param v Pointer to an uninitialized \type igraph_vector_t object. + * \param endmark This element will signal the end of the vector. It + * will \em not be part of the vector. + * \param ... The elements of the vector. + * \return Error code, \c IGRAPH_ENOMEM if there + * isn't enough memory. + * + * \sa \ref igraph_vector_init_real() and \ref igraph_vector_init_int_end() for + * similar functions. + * + * Time complexity: at least O(n) for + * n elements plus the time + * complexity of the memory allocation. + */ + +int FUNCTION(igraph_vector, init_real_end)(TYPE(igraph_vector) *v, + BASE endmark, ...) { + int i = 0, n = 0; + va_list ap; + + va_start(ap, endmark); + while (1) { + BASE num = (BASE) va_arg(ap, double); + if (num == endmark) { + break; + } + n++; + } + va_end(ap); + + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(v, n)); + IGRAPH_FINALLY(FUNCTION(igraph_vector, destroy), v); + + va_start(ap, endmark); + for (i = 0; i < n; i++) { + VECTOR(*v)[i] = (BASE) va_arg(ap, double); + } + va_end(ap); + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_init_int + * \brief Create an \type igraph_vector_t containing the parameters. + * + * + * This function is similar to \ref igraph_vector_init_real(), but it expects + * \type int parameters. It is important that all parameters + * should be of this type, otherwise the result of the function call + * is undefined. + * \param v Pointer to an uninitialized \type igraph_vector_t object. + * \param no The number of \type int parameters to follow. + * \param ... The elements of the vector. + * \return Error code, \c IGRAPH_ENOMEM if there is + * not enough memory. + * \sa \ref igraph_vector_init_real() and igraph_vector_init_int_end(), these are + * similar functions. + * + * Time complexity: at least O(n) for + * n elements plus the time + * complexity of the memory allocation. + */ + +int FUNCTION(igraph_vector, init_int)(TYPE(igraph_vector) *v, int no, ...) { + int i = 0; + va_list ap; + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(v, no)); + + va_start(ap, no); + for (i = 0; i < no; i++) { + VECTOR(*v)[i] = (BASE) va_arg(ap, int); + } + va_end(ap); + + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_init_int_end + * \brief Create an \type igraph_vector_t from the parameters. + * + * + * This constructor is similar to \ref igraph_vector_init_int(), the only + * difference is that instead of giving the number of elements in the + * vector, a special marker element follows the last real vector + * element. + * \param v Pointer to an uninitialized \type igraph_vector_t object. + * \param endmark This element will signal the end of the vector. It + * will \em not be part of the vector. + * \param ... The elements of the vector. + * \return Error code, \c IGRAPH_ENOMEM if there + * isn't enough memory. + * + * \sa \ref igraph_vector_init_int() and \ref igraph_vector_init_real_end() for + * similar functions. + * + * Time complexity: at least O(n) for + * n elements plus the time + * complexity of the memory allocation. + */ + +int FUNCTION(igraph_vector_init, int_end)(TYPE(igraph_vector) *v, int endmark, ...) { + int i = 0, n = 0; + va_list ap; + + va_start(ap, endmark); + while (1) { + int num = va_arg(ap, int); + if (num == endmark) { + break; + } + n++; + } + va_end(ap); + + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(v, n)); + IGRAPH_FINALLY(FUNCTION(igraph_vector, destroy), v); + + va_start(ap, endmark); + for (i = 0; i < n; i++) { + VECTOR(*v)[i] = (BASE) va_arg(ap, int); + } + va_end(ap); + + IGRAPH_FINALLY_CLEAN(1); + return 0; +} + +#endif /* ifndef BASE_COMPLEX */ + +/** + * \ingroup vector + * \function igraph_vector_destroy + * \brief Destroys a vector object. + * + * + * All vectors initialized by \ref igraph_vector_init() should be properly + * destroyed by this function. A destroyed vector needs to be + * reinitialized by \ref igraph_vector_init(), \ref igraph_vector_init_copy() or + * another constructor. + * \param v Pointer to the (previously initialized) vector object to + * destroy. + * + * Time complexity: operating system dependent. + */ + +void FUNCTION(igraph_vector, destroy) (TYPE(igraph_vector)* v) { + assert(v != 0); + if (v->stor_begin != 0) { + igraph_Free(v->stor_begin); + v->stor_begin = NULL; + } +} + +/** + * \ingroup vector + * \function igraph_vector_capacity + * \brief Returns the allocated capacity of the vector + * + * Note that this might be different from the size of the vector (as + * queried by \ref igraph_vector_size(), and specifies how many elements + * the vector can hold, without reallocation. + * \param v Pointer to the (previously initialized) vector object + * to query. + * \return The allocated capacity. + * + * \sa \ref igraph_vector_size(). + * + * Time complexity: O(1). + */ + +long int FUNCTION(igraph_vector, capacity)(const TYPE(igraph_vector)*v) { + return v->stor_end - v->stor_begin; +} + +/** + * \ingroup vector + * \function igraph_vector_reserve + * \brief Reserves memory for a vector. + * + * + * \a igraph vectors are flexible, they can grow and + * shrink. Growing + * however occasionally needs the data in the vector to be copied. + * In order to avoid this, you can call this function to reserve space for + * future growth of the vector. + * + * + * Note that this function does \em not change the size of the + * vector. Let us see a small example to clarify things: if you + * reserve space for 100 elements and the size of your + * vector was (and still is) 60, then you can surely add additional 40 + * elements to your vector before it will be copied. + * \param v The vector object. + * \param size The new \em allocated size of the vector. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, should be around + * O(n), n + * is the new allocated size of the vector. + */ + +int FUNCTION(igraph_vector, reserve) (TYPE(igraph_vector)* v, long int size) { + long int actual_size = FUNCTION(igraph_vector, size)(v); + BASE *tmp; + assert(v != NULL); + assert(v->stor_begin != NULL); + if (size <= FUNCTION(igraph_vector, size)(v)) { + return 0; + } + + tmp = igraph_Realloc(v->stor_begin, (size_t) size, BASE); + if (tmp == 0) { + IGRAPH_ERROR("cannot reserve space for vector", IGRAPH_ENOMEM); + } + v->stor_begin = tmp; + v->stor_end = v->stor_begin + size; + v->end = v->stor_begin + actual_size; + + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_empty + * \brief Decides whether the size of the vector is zero. + * + * \param v The vector object. + * \return Non-zero number (true) if the size of the vector is zero and + * zero (false) otherwise. + * + * Time complexity: O(1). + */ + +igraph_bool_t FUNCTION(igraph_vector, empty) (const TYPE(igraph_vector)* v) { + assert(v != NULL); + assert(v->stor_begin != NULL); + return v->stor_begin == v->end; +} + +/** + * \ingroup vector + * \function igraph_vector_size + * \brief Gives the size (=length) of the vector. + * + * \param v The vector object + * \return The size of the vector. + * + * Time complexity: O(1). + */ + +long int FUNCTION(igraph_vector, size) (const TYPE(igraph_vector)* v) { + assert(v != NULL); + assert(v->stor_begin != NULL); + return v->end - v->stor_begin; +} + +/** + * \ingroup vector + * \function igraph_vector_clear + * \brief Removes all elements from a vector. + * + * + * This function simply sets the size of the vector to zero, it does + * not free any allocated memory. For that you have to call + * \ref igraph_vector_destroy(). + * \param v The vector object. + * + * Time complexity: O(1). + */ + +void FUNCTION(igraph_vector, clear) (TYPE(igraph_vector)* v) { + assert(v != NULL); + assert(v->stor_begin != NULL); + v->end = v->stor_begin; +} + +/** + * \ingroup vector + * \function igraph_vector_push_back + * \brief Appends one element to a vector. + * + * + * This function resizes the vector to be one element longer and + * sets the very last element in the vector to \p e. + * \param v The vector object. + * \param e The element to append to the vector. + * \return Error code: + * \c IGRAPH_ENOMEM: not enough memory. + * + * Time complexity: operating system dependent. What is important is that + * a sequence of n + * subsequent calls to this function has time complexity + * O(n), even if there + * hadn't been any space reserved for the new elements by + * \ref igraph_vector_reserve(). This is implemented by a trick similar to the C++ + * \type vector class: each time more memory is allocated for a + * vector, the size of the additionally allocated memory is the same + * as the vector's current length. (We assume here that the time + * complexity of memory allocation is at most linear.) + */ + +int FUNCTION(igraph_vector, push_back) (TYPE(igraph_vector)* v, BASE e) { + assert(v != NULL); + assert(v->stor_begin != NULL); + + /* full, allocate more storage */ + if (v->stor_end == v->end) { + long int new_size = FUNCTION(igraph_vector, size)(v) * 2; + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(FUNCTION(igraph_vector, reserve)(v, new_size)); + } + + *(v->end) = e; + v->end += 1; + + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_insert + * \brief Inserts a single element into a vector. + * + * Note that this function does not do range checking. Insertion will shift the + * elements from the position given to the end of the vector one position to the + * right, and the new element will be inserted in the empty space created at + * the given position. The size of the vector will increase by one. + * + * \param v The vector object. + * \param pos The position where the new element is to be inserted. + * \param value The new element to be inserted. + */ +int FUNCTION(igraph_vector, insert)(TYPE(igraph_vector) *v, long int pos, + BASE value) { + size_t size = (size_t) FUNCTION(igraph_vector, size)(v); + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(v, (long) size + 1)); + if (pos < size) { + memmove(v->stor_begin + pos + 1, v->stor_begin + pos, + sizeof(BASE) * (size - (size_t) pos)); + } + v->stor_begin[pos] = value; + return 0; +} + +/** + * \ingroup vector + * \section igraph_vector_accessing_elements Accessing elements + * + * The simplest way to access an element of a vector is to use the + * \ref VECTOR macro. This macro can be used both for querying and setting + * \type igraph_vector_t elements. If you need a function, \ref + * igraph_vector_e() queries and \ref igraph_vector_set() sets an element of a + * vector. \ref igraph_vector_e_ptr() returns the address of an element. + * + * \ref igraph_vector_tail() returns the last element of a non-empty + * vector. There is no igraph_vector_head() function + * however, as it is easy to write VECTOR(v)[0] + * instead. + */ + +/** + * \ingroup vector + * \function igraph_vector_e + * \brief Access an element of a vector. + * \param v The \type igraph_vector_t object. + * \param pos The position of the element, the index of the first + * element is zero. + * \return The desired element. + * \sa \ref igraph_vector_e_ptr() and the \ref VECTOR macro. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_vector, e) (const TYPE(igraph_vector)* v, long int pos) { + assert(v != NULL); + assert(v->stor_begin != NULL); + return * (v->stor_begin + pos); +} + +/** + * \ingroup vector + * \function igraph_vector_e_ptr + * \brief Get the address of an element of a vector + * \param v The \type igraph_vector_t object. + * \param pos The position of the element, the position of the first + * element is zero. + * \return Pointer to the desired element. + * \sa \ref igraph_vector_e() and the \ref VECTOR macro. + * + * Time complexity: O(1). + */ + +BASE* FUNCTION(igraph_vector, e_ptr) (const TYPE(igraph_vector)* v, long int pos) { + assert(v != NULL); + assert(v->stor_begin != NULL); + return v->stor_begin + pos; +} + +/** + * \ingroup vector + * \function igraph_vector_set + * \brief Assignment to an element of a vector. + * \param v The \type igraph_vector_t element. + * \param pos Position of the element to set. + * \param value New value of the element. + * \sa \ref igraph_vector_e(). + */ + +void FUNCTION(igraph_vector, set) (TYPE(igraph_vector)* v, + long int pos, BASE value) { + assert(v != NULL); + assert(v->stor_begin != NULL); + *(v->stor_begin + pos) = value; +} + +/** + * \ingroup vector + * \function igraph_vector_null + * \brief Sets each element in the vector to zero. + * + * + * Note that \ref igraph_vector_init() sets the elements to zero as well, so + * it makes no sense to call this function on a just initialized + * vector. Thus if you want to construct a vector of zeros, then you should + * use \ref igraph_vector_init(). + * \param v The vector object. + * + * Time complexity: O(n), the size of + * the vector. + */ + +void FUNCTION(igraph_vector, null) (TYPE(igraph_vector)* v) { + assert(v != NULL); + assert(v->stor_begin != NULL); + if (FUNCTION(igraph_vector, size)(v) > 0) { + memset(v->stor_begin, 0, + sizeof(BASE) * (size_t) FUNCTION(igraph_vector, size)(v)); + } +} + +/** + * \function igraph_vector_fill + * \brief Fill a vector with a constant element + * + * Sets each element of the vector to the supplied constant. + * \param vector The vector to work on. + * \param e The element to fill with. + * + * Time complexity: O(n), the size of the vector. + */ + +void FUNCTION(igraph_vector, fill) (TYPE(igraph_vector)* v, BASE e) { + BASE *ptr; + assert(v != NULL); + assert(v->stor_begin != NULL); + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + *ptr = e; + } +} + +/** + * \ingroup vector + * \function igraph_vector_tail + * \brief Returns the last element in a vector. + * + * + * It is an error to call this function on an empty vector, the result + * is undefined. + * \param v The vector object. + * \return The last element. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_vector, tail)(const TYPE(igraph_vector) *v) { + assert(v != NULL); + assert(v->stor_begin != NULL); + return *((v->end) - 1); +} + +/** + * \ingroup vector + * \function igraph_vector_pop_back + * \brief Removes and returns the last element of a vector. + * + * + * It is an error to call this function with an empty vector. + * \param v The vector object. + * \return The removed last element. + * + * Time complexity: O(1). + */ + +BASE FUNCTION(igraph_vector, pop_back)(TYPE(igraph_vector)* v) { + BASE tmp; + assert(v != NULL); + assert(v->stor_begin != NULL); + assert(v->end != v->stor_begin); + tmp = FUNCTION(igraph_vector, e)(v, FUNCTION(igraph_vector, size)(v) - 1); + v->end -= 1; + return tmp; +} + +#ifndef NOTORDERED + +/** + * \ingroup vector + * \function igraph_vector_sort_cmp + * \brief Internal comparison function of vector elements, used by + * \ref igraph_vector_sort(). + */ + +int FUNCTION(igraph_vector, sort_cmp)(const void *a, const void *b) { + const BASE *da = (const BASE *) a; + const BASE *db = (const BASE *) b; + + return (*da > *db) - (*da < *db); +} + +/** + * \ingroup vector + * \function igraph_vector_sort + * \brief Sorts the elements of the vector into ascending order. + * + * + * This function uses the built-in sort function of the C library. + * \param v Pointer to an initialized vector object. + * + * Time complexity: should be + * O(nlogn) for + * n + * elements. + */ + +void FUNCTION(igraph_vector, sort)(TYPE(igraph_vector) *v) { + assert(v != NULL); + assert(v->stor_begin != NULL); + igraph_qsort(v->stor_begin, (size_t) FUNCTION(igraph_vector, size)(v), + sizeof(BASE), FUNCTION(igraph_vector, sort_cmp)); +} + +/** + * Ascending comparison function passed to qsort from igraph_vector_qsort_ind + */ +int FUNCTION(igraph_vector, i_qsort_ind_cmp_asc)(const void *p1, const void *p2) { + BASE **pa = (BASE **) p1; + BASE **pb = (BASE **) p2; + if ( **pa < **pb ) { + return -1; + } + if ( **pa > **pb) { + return 1; + } + return 0; +} + +/** + * Descending comparison function passed to qsort from igraph_vector_qsort_ind + */ +int FUNCTION(igraph_vector, i_qsort_ind_cmp_desc)(const void *p1, const void *p2) { + BASE **pa = (BASE **) p1; + BASE **pb = (BASE **) p2; + if ( **pa < **pb ) { + return 1; + } + if ( **pa > **pb) { + return -1; + } + return 0; +} + +/** + * \function igraph_vector_qsort_ind + * \brief Return a permutation of indices that sorts a vector + * + * Takes an unsorted array \c v as input and computes an array of + * indices inds such that v[ inds[i] ], with i increasing from 0, is + * an ordered array (either ascending or descending, depending on + * \v order). The order of indices for identical elements is not + * defined. + * + * \param v the array to be sorted + * \param inds the output array of indices. this must be initialized, + * but will be resized + * \param descending whether the output array should be sorted in descending + * order. + * \return Error code. + * + * This routine uses the C library qsort routine. + * Algorithm: 1) create an array of pointers to the elements of v. 2) + * Pass this array to qsort. 3) after sorting the difference between + * the pointer value and the first pointer value gives its original + * position in the array. Use this to set the values of inds. + * + * Some tests show that this routine is faster than + * igraph_vector_heapsort_ind by about 10 percent + * for small vectors to a factor of two for large vectors. + */ + +long int FUNCTION(igraph_vector, qsort_ind)(TYPE(igraph_vector) *v, + igraph_vector_t *inds, igraph_bool_t descending) { + long int i; + BASE **vind, *first; + size_t n = (size_t) FUNCTION(igraph_vector, size)(v); + IGRAPH_CHECK(igraph_vector_resize(inds, (long) n)); + if (n == 0) { + return 0; + } + vind = igraph_Calloc(n, BASE*); + if (vind == 0) { + IGRAPH_ERROR("igraph_vector_qsort_ind failed", IGRAPH_ENOMEM); + } + for (i = 0; i < n; i++) { + vind[i] = &VECTOR(*v)[i]; + } + first = vind[0]; + if (descending) { + igraph_qsort(vind, n, sizeof(BASE**), FUNCTION(igraph_vector, i_qsort_ind_cmp_desc)); + } else { + igraph_qsort(vind, n, sizeof(BASE**), FUNCTION(igraph_vector, i_qsort_ind_cmp_asc)); + } + for (i = 0; i < n; i++) { + VECTOR(*inds)[i] = vind[i] - first; + } + igraph_Free(vind); + return 0; +} + +#endif + +/** + * \ingroup vector + * \function igraph_vector_resize + * \brief Resize the vector. + * + * + * Note that this function does not free any memory, just sets the + * size of the vector to the given one. It can on the other hand + * allocate more memory if the new size is larger than the previous + * one. In this case the newly appeared elements in the vector are + * \em not set to zero, they are uninitialized. + * \param v The vector object + * \param newsize The new size of the vector. + * \return Error code, + * \c IGRAPH_ENOMEM if there is not enough + * memory. Note that this function \em never returns an error + * if the vector is made smaller. + * \sa \ref igraph_vector_reserve() for allocating memory for future + * extensions of a vector. \ref igraph_vector_resize_min() for + * deallocating the unnneded memory for a vector. + * + * Time complexity: O(1) if the new + * size is smaller, operating system dependent if it is larger. In the + * latter case it is usually around + * O(n), + * n is the new size of the vector. + */ + +int FUNCTION(igraph_vector, resize)(TYPE(igraph_vector)* v, long int newsize) { + assert(v != NULL); + assert(v->stor_begin != NULL); + IGRAPH_CHECK(FUNCTION(igraph_vector, reserve)(v, newsize)); + v->end = v->stor_begin + newsize; + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_resize_min + * \brief Deallocate the unused memory of a vector. + * + * + * Note that this function involves additional memory allocation and + * may result an out-of-memory error. + * \param v Pointer to an initialized vector. + * \return Error code. + * + * \sa \ref igraph_vector_resize(), \ref igraph_vector_reserve(). + * + * Time complexity: operating system dependent. + */ + +int FUNCTION(igraph_vector, resize_min)(TYPE(igraph_vector)*v) { + size_t size; + BASE *tmp; + if (v->stor_end == v->end) { + return 0; + } + + size = (size_t) (v->end - v->stor_begin); + tmp = igraph_Realloc(v->stor_begin, size, BASE); + if (tmp == 0) { + IGRAPH_ERROR("cannot resize vector", IGRAPH_ENOMEM); + } else { + v->stor_begin = tmp; + v->stor_end = v->end = v->stor_begin + size; + } + + return 0; +} + +#ifndef NOTORDERED + +/** + * \ingroup vector + * \function igraph_vector_max + * \brief Gives the maximum element of the vector. + * + * + * If the size of the vector is zero, an arbitrary number is + * returned. + * \param v The vector object. + * \return The maximum element. + * + * Time complexity: O(n), + * n is the size of the vector. + */ + +BASE FUNCTION(igraph_vector, max)(const TYPE(igraph_vector)* v) { + BASE max; + BASE *ptr; + assert(v != NULL); + assert(v->stor_begin != NULL); + max = *(v->stor_begin); + ptr = v->stor_begin + 1; + while (ptr < v->end) { + if ((*ptr) > max) { + max = *ptr; + } + ptr++; + } + return max; +} + +/** + * \ingroup vector + * \function igraph_vector_which_max + * \brief Gives the position of the maximum element of the vector. + * + * + * If the size of the vector is zero, -1 is + * returned. + * \param v The vector object. + * \return The position of the first maximum element. + * + * Time complexity: O(n), + * n is the size of the vector. + */ + +long int FUNCTION(igraph_vector, which_max)(const TYPE(igraph_vector)* v) { + long int which = -1; + if (!FUNCTION(igraph_vector, empty)(v)) { + BASE max; + BASE *ptr; + long int pos; + assert(v != NULL); + assert(v->stor_begin != NULL); + max = *(v->stor_begin); which = 0; + ptr = v->stor_begin + 1; pos = 1; + while (ptr < v->end) { + if ((*ptr) > max) { + max = *ptr; + which = pos; + } + ptr++; pos++; + } + } + return which; +} + +/** + * \function igraph_vector_min + * \brief Smallest element of a vector. + * + * The vector must be non-empty. + * \param v The input vector. + * \return The smallest element of \p v. + * + * Time complexity: O(n), the number of elements. + */ + +BASE FUNCTION(igraph_vector, min)(const TYPE(igraph_vector)* v) { + BASE min; + BASE *ptr; + assert(v != NULL); + assert(v->stor_begin != NULL); + min = *(v->stor_begin); + ptr = v->stor_begin + 1; + while (ptr < v->end) { + if ((*ptr) < min) { + min = *ptr; + } + ptr++; + } + return min; +} + +/** + * \function igraph_vector_which_min + * \brief Index of the smallest element. + * + * The vector must be non-empty. + * If the smallest element is not unique, then the index of the first + * is returned. + * \param v The input vector. + * \return Index of the smallest element. + * + * Time complexity: O(n), the number of elements. + */ + +long int FUNCTION(igraph_vector, which_min)(const TYPE(igraph_vector)* v) { + long int which = -1; + if (!FUNCTION(igraph_vector, empty)(v)) { + BASE min; + BASE *ptr; + long int pos; + assert(v != NULL); + assert(v->stor_begin != NULL); + min = *(v->stor_begin); which = 0; + ptr = v->stor_begin + 1; pos = 1; + while (ptr < v->end) { + if ((*ptr) < min) { + min = *ptr; + which = pos; + } + ptr++; pos++; + } + } + return which; +} + +#endif + +/** + * \ingroup vector + * \function igraph_vector_init_copy + * \brief Initializes a vector from an ordinary C array (constructor). + * + * \param v Pointer to an uninitialized vector object. + * \param data A regular C array. + * \param length The length of the C array. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system specific, usually + * O(\p length). + */ + +int FUNCTION(igraph_vector, init_copy)(TYPE(igraph_vector) *v, + const BASE *data, long int length) { + v->stor_begin = igraph_Calloc(length, BASE); + if (v->stor_begin == 0) { + IGRAPH_ERROR("cannot init vector from array", IGRAPH_ENOMEM); + } + v->stor_end = v->stor_begin + length; + v->end = v->stor_end; + memcpy(v->stor_begin, data, (size_t) length * sizeof(BASE)); + + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_copy_to + * \brief Copies the contents of a vector to a C array. + * + * + * The C array should have sufficient length. + * \param v The vector object. + * \param to The C array. + * + * Time complexity: O(n), + * n is the size of the vector. + */ + +void FUNCTION(igraph_vector, copy_to)(const TYPE(igraph_vector) *v, BASE *to) { + + assert(v != NULL); + assert(v->stor_begin != NULL); + if (v->end != v->stor_begin) { + memcpy(to, v->stor_begin, sizeof(BASE) * (size_t) (v->end - v->stor_begin)); + } +} + +/** + * \ingroup vector + * \function igraph_vector_copy + * \brief Initializes a vector from another vector object (constructor). + * + * + * The contents of the existing vector object will be copied to + * the new one. + * \param to Pointer to a not yet initialized vector object. + * \param from The original vector object to copy. + * \return Error code: + * \c IGRAPH_ENOMEM if there is not enough memory. + * + * Time complexity: operating system dependent, usually + * O(n), + * n is the size of the vector. + */ + +int FUNCTION(igraph_vector, copy)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from) { + assert(from != NULL); + assert(from->stor_begin != NULL); + to->stor_begin = igraph_Calloc(FUNCTION(igraph_vector, size)(from), BASE); + if (to->stor_begin == 0) { + IGRAPH_ERROR("cannot copy vector", IGRAPH_ENOMEM); + } + to->stor_end = to->stor_begin + FUNCTION(igraph_vector, size)(from); + to->end = to->stor_end; + memcpy(to->stor_begin, from->stor_begin, + (size_t) FUNCTION(igraph_vector, size)(from) * sizeof(BASE)); + + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_sum + * \brief Calculates the sum of the elements in the vector. + * + * + * For the empty vector 0.0 is returned. + * \param v The vector object. + * \return The sum of the elements. + * + * Time complexity: O(n), the size of + * the vector. + */ + +BASE FUNCTION(igraph_vector, sum)(const TYPE(igraph_vector) *v) { + BASE res = ZERO; + BASE *p; + assert(v != NULL); + assert(v->stor_begin != NULL); + for (p = v->stor_begin; p < v->end; p++) { +#ifdef SUM + SUM(res, res, *p); +#else + res += *p; +#endif + } + return res; +} + +igraph_real_t FUNCTION(igraph_vector, sumsq)(const TYPE(igraph_vector) *v) { + igraph_real_t res = 0.0; + BASE *p; + assert(v != NULL); + assert(v->stor_begin != NULL); + for (p = v->stor_begin; p < v->end; p++) { +#ifdef SQ + res += SQ(*p); +#else + res += (*p) * (*p); +#endif + } + return res; +} + +/** + * \ingroup vector + * \function igraph_vector_prod + * \brief Calculates the product of the elements in the vector. + * + * + * For the empty vector one (1) is returned. + * \param v The vector object. + * \return The product of the elements. + * + * Time complexity: O(n), the size of + * the vector. + */ + +BASE FUNCTION(igraph_vector, prod)(const TYPE(igraph_vector) *v) { + BASE res = ONE; + BASE *p; + assert(v != NULL); + assert(v->stor_begin != NULL); + for (p = v->stor_begin; p < v->end; p++) { +#ifdef PROD + PROD(res, res, *p); +#else + res *= *p; +#endif + } + return res; +} + +/** + * \ingroup vector + * \function igraph_vector_cumsum + * \brief Calculates the cumulative sum of the elements in the vector. + * + * + * \param to An initialized vector object that will store the cumulative + * sums. Element i of this vector will store the sum of the elements + * of the 'from' vector, up to and including element i. + * \param from The input vector. + * \return Error code. + * + * Time complexity: O(n), the size of the vector. + */ + +int FUNCTION(igraph_vector, cumsum)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from) { + BASE res = ZERO; + BASE *p, *p2; + + assert(from != NULL); + assert(from->stor_begin != NULL); + assert(to != NULL); + assert(to->stor_begin != NULL); + + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(to, FUNCTION(igraph_vector, size)(from))); + + for (p = from->stor_begin, p2 = to->stor_begin; p < from->end; p++, p2++) { +#ifdef SUM + SUM(res, res, *p); +#else + res += *p; +#endif + *p2 = res; + } + + return 0; +} + +#ifndef NOTORDERED + +/** + * \ingroup vector + * \function igraph_vector_init_seq + * \brief Initializes a vector with a sequence. + * + * + * The vector will contain the numbers \p from, + * \p from+1, ..., \p to. + * \param v Pointer to an uninitialized vector object. + * \param from The lower limit in the sequence (inclusive). + * \param to The upper limit in the sequence (inclusive). + * \return Error code: + * \c IGRAPH_ENOMEM: out of memory. + * + * Time complexity: O(n), the number + * of elements in the vector. + */ + +int FUNCTION(igraph_vector, init_seq)(TYPE(igraph_vector) *v, + BASE from, BASE to) { + BASE *p; + IGRAPH_CHECK(FUNCTION(igraph_vector, init)(v, (long int) (to - from + 1))); + + for (p = v->stor_begin; p < v->end; p++) { + *p = from++; + } + + return 0; +} + +#endif + +/** + * \ingroup vector + * \function igraph_vector_remove_section + * \brief Deletes a section from a vector. + * + * + * Note that this function does not do range checking. The result is + * undefined if you supply invalid limits. + * \param v The vector object. + * \param from The position of the first element to remove. + * \param to The position of the first element \em not to remove. + * + * Time complexity: O(n-from), + * n is the number of elements in the + * vector. + */ + +void FUNCTION(igraph_vector, remove_section)(TYPE(igraph_vector) *v, + long int from, long int to) { + assert(v != NULL); + assert(v->stor_begin != NULL); + /* Not removing from the end? */ + if (to < FUNCTION(igraph_vector, size)(v)) { + memmove(v->stor_begin + from, v->stor_begin + to, + sizeof(BASE) * (size_t) (v->end - v->stor_begin - to)); + } + v->end -= (to - from); +} + +/** + * \ingroup vector + * \function igraph_vector_remove + * \brief Removes a single element from a vector. + * + * Note that this function does not do range checking. + * \param v The vector object. + * \param elem The position of the element to remove. + * + * Time complexity: O(n-elem), + * n is the number of elements in the + * vector. + */ + +void FUNCTION(igraph_vector, remove)(TYPE(igraph_vector) *v, long int elem) { + assert(v != NULL); + assert(v->stor_begin != NULL); + FUNCTION(igraph_vector, remove_section)(v, elem, elem + 1); +} + +/** + * \ingroup vector + * \function igraph_vector_move_interval + * \brief Copies a section of a vector. + * + * + * The result of this function is undefined if the source and target + * intervals overlap. + * \param v The vector object. + * \param begin The position of the first element to move. + * \param end The position of the first element \em not to move. + * \param to The target position. + * \return Error code, the current implementation always returns with + * success. + * + * Time complexity: O(end-begin). + */ + +int FUNCTION(igraph_vector, move_interval)(TYPE(igraph_vector) *v, + long int begin, long int end, + long int to) { + assert(v != NULL); + assert(v->stor_begin != NULL); + memcpy(v->stor_begin + to, v->stor_begin + begin, + sizeof(BASE) * (size_t) (end - begin)); + + return 0; +} + +int FUNCTION(igraph_vector, move_interval2)(TYPE(igraph_vector) *v, + long int begin, long int end, + long int to) { + assert(v != NULL); + assert(v->stor_begin != NULL); + memmove(v->stor_begin + to, v->stor_begin + begin, + sizeof(BASE) * (size_t) (end - begin)); + + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_permdelete + * \brief Remove elements of a vector (for internal use). + */ + +void FUNCTION(igraph_vector, permdelete)(TYPE(igraph_vector) *v, + const igraph_vector_t *index, long int nremove) { + long int i, n; + assert(v != NULL); + assert(v->stor_begin != NULL); + n = FUNCTION(igraph_vector, size)(v); + for (i = 0; i < n; i++) { + if (VECTOR(*index)[i] != 0) { + VECTOR(*v)[ (long int)VECTOR(*index)[i] - 1 ] = VECTOR(*v)[i]; + } + } + v->end -= nremove; +} + +#ifndef NOTORDERED + +/** + * \ingroup vector + * \function igraph_vector_isininterval + * \brief Checks if all elements of a vector are in the given + * interval. + * + * \param v The vector object. + * \param low The lower limit of the interval (inclusive). + * \param high The higher limit of the interval (inclusive). + * \return True (positive integer) if all vector elements are in the + * interval, false (zero) otherwise. + * + * Time complexity: O(n), the number + * of elements in the vector. + */ + +igraph_bool_t FUNCTION(igraph_vector, isininterval)(const TYPE(igraph_vector) *v, + BASE low, + BASE high) { + BASE *ptr; + assert(v != NULL); + assert(v->stor_begin != NULL); + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + if (*ptr < low || *ptr > high) { + return 0; + } + } + return 1; +} + +/** + * \ingroup vector + * \function igraph_vector_any_smaller + * \brief Checks if any element of a vector is smaller than a limit. + * + * \param v The \type igraph_vector_t object. + * \param limit The limit. + * \return True (positive integer) if the vector contains at least one + * smaller element than \p limit, false (zero) + * otherwise. + * + * Time complexity: O(n), the number + * of elements in the vector. + */ + +igraph_bool_t FUNCTION(igraph_vector, any_smaller)(const TYPE(igraph_vector) *v, + BASE limit) { + BASE *ptr; + assert(v != NULL); + assert(v->stor_begin != NULL); + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + if (*ptr < limit) { + return 1; + } + } + return 0; +} + +#endif + +/** + * \ingroup vector + * \function igraph_vector_all_e + * \brief Are all elements equal? + * + * \param lhs The first vector. + * \param rhs The second vector. + * \return Positive integer (=true) if the elements in the \p lhs are all + * equal to the corresponding elements in \p rhs. Returns \c 0 + * (=false) if the lengths of the vectors don't match. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_bool_t FUNCTION(igraph_vector, all_e)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + long int i, s; + assert(lhs != 0); + assert(rhs != 0); + assert(lhs->stor_begin != 0); + assert(rhs->stor_begin != 0); + + s = FUNCTION(igraph_vector, size)(lhs); + if (s != FUNCTION(igraph_vector, size)(rhs)) { + return 0; + } else { + for (i = 0; i < s; i++) { + BASE l = VECTOR(*lhs)[i]; + BASE r = VECTOR(*rhs)[i]; +#ifdef EQ + if (!EQ(l, r)) { +#else + if (l != r) { +#endif + return 0; + } + } + return 1; + } +} + +igraph_bool_t +FUNCTION(igraph_vector, is_equal)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + return FUNCTION(igraph_vector, all_e)(lhs, rhs); +} + +#ifndef NOTORDERED + +/** + * \ingroup vector + * \function igraph_vector_all_l + * \brief Are all elements less? + * + * \param lhs The first vector. + * \param rhs The second vector. + * \return Positive integer (=true) if the elements in the \p lhs are all + * less than the corresponding elements in \p rhs. Returns \c 0 + * (=false) if the lengths of the vectors don't match. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_bool_t FUNCTION(igraph_vector, all_l)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + long int i, s; + assert(lhs != 0); + assert(rhs != 0); + assert(lhs->stor_begin != 0); + assert(rhs->stor_begin != 0); + + s = FUNCTION(igraph_vector, size)(lhs); + if (s != FUNCTION(igraph_vector, size)(rhs)) { + return 0; + } else { + for (i = 0; i < s; i++) { + BASE l = VECTOR(*lhs)[i]; + BASE r = VECTOR(*rhs)[i]; + if (l >= r) { + return 0; + } + } + return 1; + } +} + +/** + * \ingroup vector + * \function igraph_vector_all_g + * \brief Are all elements greater? + * + * \param lhs The first vector. + * \param rhs The second vector. + * \return Positive integer (=true) if the elements in the \p lhs are all + * greater than the corresponding elements in \p rhs. Returns \c 0 + * (=false) if the lengths of the vectors don't match. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_bool_t FUNCTION(igraph_vector, all_g)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + + long int i, s; + assert(lhs != 0); + assert(rhs != 0); + assert(lhs->stor_begin != 0); + assert(rhs->stor_begin != 0); + + s = FUNCTION(igraph_vector, size)(lhs); + if (s != FUNCTION(igraph_vector, size)(rhs)) { + return 0; + } else { + for (i = 0; i < s; i++) { + BASE l = VECTOR(*lhs)[i]; + BASE r = VECTOR(*rhs)[i]; + if (l <= r) { + return 0; + } + } + return 1; + } +} + +/** + * \ingroup vector + * \function igraph_vector_all_le + * \brief Are all elements less or equal? + * + * \param lhs The first vector. + * \param rhs The second vector. + * \return Positive integer (=true) if the elements in the \p lhs are all + * less than or equal to the corresponding elements in \p + * rhs. Returns \c 0 (=false) if the lengths of the vectors don't + * match. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_bool_t +FUNCTION(igraph_vector, all_le)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + long int i, s; + assert(lhs != 0); + assert(rhs != 0); + assert(lhs->stor_begin != 0); + assert(rhs->stor_begin != 0); + + s = FUNCTION(igraph_vector, size)(lhs); + if (s != FUNCTION(igraph_vector, size)(rhs)) { + return 0; + } else { + for (i = 0; i < s; i++) { + BASE l = VECTOR(*lhs)[i]; + BASE r = VECTOR(*rhs)[i]; + if (l > r) { + return 0; + } + } + return 1; + } +} + +/** + * \ingroup vector + * \function igraph_vector_all_ge + * \brief Are all elements greater or equal? + * + * \param lhs The first vector. + * \param rhs The second vector. + * \return Positive integer (=true) if the elements in the \p lhs are all + * greater than or equal to the corresponding elements in \p + * rhs. Returns \c 0 (=false) if the lengths of the vectors don't + * match. + * + * Time complexity: O(n), the length of the vectors. + */ + +igraph_bool_t +FUNCTION(igraph_vector, all_ge)(const TYPE(igraph_vector) *lhs, + const TYPE(igraph_vector) *rhs) { + long int i, s; + assert(lhs != 0); + assert(rhs != 0); + assert(lhs->stor_begin != 0); + assert(rhs->stor_begin != 0); + + s = FUNCTION(igraph_vector, size)(lhs); + if (s != FUNCTION(igraph_vector, size)(rhs)) { + return 0; + } else { + for (i = 0; i < s; i++) { + BASE l = VECTOR(*lhs)[i]; + BASE r = VECTOR(*rhs)[i]; + if (l < r) { + return 0; + } + } + return 1; + } +} + +#endif + +igraph_bool_t FUNCTION(igraph_i_vector, binsearch_slice)(const TYPE(igraph_vector) *v, + BASE what, long int *pos, + long int start, long int end); + +#ifndef NOTORDERED + +/** + * \ingroup vector + * \function igraph_vector_binsearch + * \brief Finds an element by binary searching a sorted vector. + * + * + * It is assumed that the vector is sorted. If the specified element + * (\p what) is not in the vector, then the + * position of where it should be inserted (to keep the vector sorted) + * is returned. + * \param v The \type igraph_vector_t object. + * \param what The element to search for. + * \param pos Pointer to a \type long int. This is set to the + * position of an instance of \p what in the + * vector if it is present. If \p v does not + * contain \p what then + * \p pos is set to the position to which it + * should be inserted (to keep the the vector sorted of course). + * \return Positive integer (true) if \p what is + * found in the vector, zero (false) otherwise. + * + * Time complexity: O(log(n)), + * n is the number of elements in + * \p v. + */ + +igraph_bool_t FUNCTION(igraph_vector, binsearch)(const TYPE(igraph_vector) *v, + BASE what, long int *pos) { + return FUNCTION(igraph_i_vector, binsearch_slice)(v, what, pos, + 0, FUNCTION(igraph_vector, size)(v)); +} + +igraph_bool_t FUNCTION(igraph_i_vector, binsearch_slice)(const TYPE(igraph_vector) *v, + BASE what, long int *pos, + long int start, long int end) { + long int left = start; + long int right = end - 1; + + while (left <= right) { + /* (right + left) / 2 could theoretically overflow for long vectors */ + long int middle = left + ((right - left) >> 1); + if (VECTOR(*v)[middle] > what) { + right = middle - 1; + } else if (VECTOR(*v)[middle] < what) { + left = middle + 1; + } else { + if (pos != 0) { + *pos = middle; + } + return 1; + } + } + + /* if we are here, the element was not found */ + if (pos != 0) { + *pos = left; + } + + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_binsearch2 + * \brief Binary search, without returning the index. + * + * + * It is assumed that the vector is sorted. + * \param v The \type igraph_vector_t object. + * \param what The element to search for. + * \return Positive integer (true) if \p what is + * found in the vector, zero (false) otherwise. + * + * Time complexity: O(log(n)), + * n is the number of elements in + * \p v. + */ + +igraph_bool_t FUNCTION(igraph_vector, binsearch2)(const TYPE(igraph_vector) *v, + BASE what) { + long int left = 0; + long int right = FUNCTION(igraph_vector, size)(v) - 1; + + while (left <= right) { + /* (right + left) / 2 could theoretically overflow for long vectors */ + long int middle = left + ((right - left) >> 1); + if (what < VECTOR(*v)[middle]) { + right = middle - 1; + } else if (what > VECTOR(*v)[middle]) { + left = middle + 1; + } else { + return 1; + } + } + + return 0; +} + +#endif + +/** + * \function igraph_vector_scale + * \brief Multiply all elements of a vector by a constant + * + * \param v The vector. + * \param by The constant. + * \return Error code. The current implementation always returns with success. + * + * Added in version 0.2. + * + * Time complexity: O(n), the number of elements in a vector. + */ + +void FUNCTION(igraph_vector, scale)(TYPE(igraph_vector) *v, BASE by) { + long int i; + for (i = 0; i < FUNCTION(igraph_vector, size)(v); i++) { +#ifdef PROD + PROD(VECTOR(*v)[i], VECTOR(*v)[i], by); +#else + VECTOR(*v)[i] *= by; +#endif + } +} + +/** + * \function igraph_vector_add_constant + * \brief Add a constant to the vector. + * + * \p plus is added to every element of \p v. Note that overflow + * might happen. + * \param v The input vector. + * \param plus The constant to add. + * + * Time complexity: O(n), the number of elements. + */ + +void FUNCTION(igraph_vector, add_constant)(TYPE(igraph_vector) *v, BASE plus) { + long int i, n = FUNCTION(igraph_vector, size)(v); + for (i = 0; i < n; i++) { +#ifdef SUM + SUM(VECTOR(*v)[i], VECTOR(*v)[i], plus); +#else + VECTOR(*v)[i] += plus; +#endif + } +} + +/** + * \function igraph_vector_contains + * \brief Linear search in a vector. + * + * Check whether the supplied element is included in the vector, by + * linear search. + * \param v The input vector. + * \param e The element to look for. + * \return \c TRUE if the element is found and \c FALSE otherwise. + * + * Time complexity: O(n), the length of the vector. + */ + +igraph_bool_t FUNCTION(igraph_vector, contains)(const TYPE(igraph_vector) *v, + BASE e) { + BASE *p = v->stor_begin; + while (p < v->end) { +#ifdef EQ + if (EQ(*p, e)) { +#else + if (*p == e) { +#endif + return 1; + } + p++; + } + return 0; +} + +/** + * \function igraph_vector_search + * \brief Search from a given position + * + * The supplied element \p what is searched in vector \p v, starting + * from element index \p from. If found then the index of the first + * instance (after \p from) is stored in \p pos. + * \param v The input vector. + * \param from The index to start searching from. No range checking is + * performed. + * \param what The element to find. + * \param pos If not \c NULL then the index of the found element is + * stored here. + * \return Boolean, \c TRUE if the element was found, \c FALSE + * otherwise. + * + * Time complexity: O(m), the number of elements to search, the length + * of the vector minus the \p from argument. + */ + +igraph_bool_t FUNCTION(igraph_vector, search)(const TYPE(igraph_vector) *v, + long int from, BASE what, + long int *pos) { + long int i, n = FUNCTION(igraph_vector, size)(v); + for (i = from; i < n; i++) { +#ifdef EQ + if (EQ(VECTOR(*v)[i], what)) { + break; + } +#else + if (VECTOR(*v)[i] == what) { + break; + } +#endif + } + + if (i < n) { + if (pos != 0) { + *pos = i; + } + return 1; + } else { + return 0; + } +} + +#ifndef NOTORDERED + +/** + * \function igraph_vector_filter_smaller + * \ingroup internal + */ + +int FUNCTION(igraph_vector, filter_smaller)(TYPE(igraph_vector) *v, + BASE elem) { + long int i = 0, n = FUNCTION(igraph_vector, size)(v); + long int s; + while (i < n && VECTOR(*v)[i] < elem) { + i++; + } + s = i; + + while (s < n && VECTOR(*v)[s] == elem) { + s++; + } + + FUNCTION(igraph_vector, remove_section)(v, 0, i + (s - i) / 2); + return 0; +} + +#endif + +/** + * \function igraph_vector_append + * \brief Append a vector to another one. + * + * The target vector will be resized (except when \p from is empty). + * \param to The vector to append to. + * \param from The vector to append, it is kept unchanged. + * \return Error code. + * + * Time complexity: O(n), the number of elements in the new vector. + */ + +int FUNCTION(igraph_vector, append)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from) { + long tosize, fromsize; + + tosize = FUNCTION(igraph_vector, size)(to); + fromsize = FUNCTION(igraph_vector, size)(from); + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(to, tosize + fromsize)); + memcpy(to->stor_begin + tosize, from->stor_begin, + sizeof(BASE) * (size_t) fromsize); + to->end = to->stor_begin + tosize + fromsize; + + return 0; +} + +/** + * \function igraph_vector_get_interval + */ + +int FUNCTION(igraph_vector, get_interval)(const TYPE(igraph_vector) *v, + TYPE(igraph_vector) *res, + long int from, long int to) { + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(res, to - from)); + memcpy(res->stor_begin, v->stor_begin + from, + (size_t) (to - from) * sizeof(BASE)); + return 0; +} + +#ifndef NOTORDERED + +/** + * \function igraph_vector_maxdifference + * \brief The maximum absolute difference of \p m1 and \p m2 + * + * The element with the largest absolute value in \p m1 - \p m2 is + * returned. Both vectors must be non-empty, but they not need to have + * the same length, the extra elements in the longer vector are ignored. + * \param m1 The first vector. + * \param m2 The second vector. + * \return The maximum absolute difference of \p m1 and \p m2. + * + * Time complexity: O(n), the number of elements in the shorter + * vector. + */ + +igraph_real_t FUNCTION(igraph_vector, maxdifference)(const TYPE(igraph_vector) *m1, + const TYPE(igraph_vector) *m2) { + long int n1 = FUNCTION(igraph_vector, size)(m1); + long int n2 = FUNCTION(igraph_vector, size)(m2); + long int n = n1 < n2 ? n1 : n2; + long int i; + igraph_real_t diff = 0.0; + + for (i = 0; i < n; i++) { + igraph_real_t d = fabs((igraph_real_t)(VECTOR(*m1)[i]) - + (igraph_real_t)(VECTOR(*m2)[i])); + if (d > diff) { + diff = d; + } + } + + return diff; +} + +#endif + +/** + * \function igraph_vector_update + * \brief Update a vector from another one. + * + * After this operation the contents of \p to will be exactly the same + * as that of \p from. The vector \p to will be resized if it was originally + * shorter or longer than \p from. + * \param to The vector to update. + * \param from The vector to update from. + * \return Error code. + * + * Time complexity: O(n), the number of elements in \p from. + */ + +int FUNCTION(igraph_vector, update)(TYPE(igraph_vector) *to, + const TYPE(igraph_vector) *from) { + size_t n = (size_t) FUNCTION(igraph_vector, size)(from); + FUNCTION(igraph_vector, resize)(to, (long) n); + memcpy(to->stor_begin, from->stor_begin, sizeof(BASE)*n); + return 0; +} + +/** + * \function igraph_vector_swap + * \brief Swap elements of two vectors. + * + * The two vectors must have the same length, otherwise an error + * happens. + * \param v1 The first vector. + * \param v2 The second vector. + * \return Error code. + * + * Time complexity: O(n), the length of the vectors. + */ + +int FUNCTION(igraph_vector, swap)(TYPE(igraph_vector) *v1, TYPE(igraph_vector) *v2) { + + long int i, n1 = FUNCTION(igraph_vector, size)(v1); + long int n2 = FUNCTION(igraph_vector, size)(v2); + if (n1 != n2) { + IGRAPH_ERROR("Vectors must have the same number of elements for swapping", + IGRAPH_EINVAL); + } + + for (i = 0; i < n1; i++) { + BASE tmp; + tmp = VECTOR(*v1)[i]; + VECTOR(*v1)[i] = VECTOR(*v2)[i]; + VECTOR(*v2)[i] = tmp; + } + return 0; +} + +/** + * \function igraph_vector_swap_elements + * \brief Swap two elements in a vector. + * + * Note that currently no range checking is performed. + * \param v The input vector. + * \param i Index of the first element. + * \param j Index of the second element (may be the same as the + * first one). + * \return Error code, currently always \c IGRAPH_SUCCESS. + * + * Time complexity: O(1). + */ + +int FUNCTION(igraph_vector, swap_elements)(TYPE(igraph_vector) *v, + long int i, long int j) { + BASE tmp = VECTOR(*v)[i]; + VECTOR(*v)[i] = VECTOR(*v)[j]; + VECTOR(*v)[j] = tmp; + + return 0; +} + +/** + * \function igraph_vector_reverse + * \brief Reverse the elements of a vector. + * + * The first element will be last, the last element will be + * first, etc. + * \param v The input vector. + * \return Error code, currently always \c IGRAPH_SUCCESS. + * + * Time complexity: O(n), the number of elements. + */ + +int FUNCTION(igraph_vector, reverse)(TYPE(igraph_vector) *v) { + + long int n = FUNCTION(igraph_vector, size)(v), n2 = n / 2; + long int i, j; + for (i = 0, j = n - 1; i < n2; i++, j--) { + BASE tmp; + tmp = VECTOR(*v)[i]; + VECTOR(*v)[i] = VECTOR(*v)[j]; + VECTOR(*v)[j] = tmp; + } + return 0; +} + +/** + * \ingroup vector + * \function igraph_vector_shuffle + * \brief Shuffles a vector in-place using the Fisher-Yates method + * + * + * The Fisher-Yates shuffle ensures that every permutation is + * equally probable when using a proper randomness source. Of course + * this does not apply to pseudo-random generators as the cycle of + * these generators is less than the number of possible permutations + * of the vector if the vector is long enough. + * \param v The vector object. + * \return Error code, currently always \c IGRAPH_SUCCESS. + * + * Time complexity: O(n), + * n is the number of elements in the + * vector. + * + * + * References: + * \clist + * \cli (Fisher & Yates 1963) + * R. A. Fisher and F. Yates. \emb Statistical Tables for Biological, + * Agricultural and Medical Research. \eme Oliver and Boyd, 6th edition, + * 1963, page 37. + * \cli (Knuth 1998) + * D. E. Knuth. \emb Seminumerical Algorithms, \eme volume 2 of \emb The Art + * of Computer Programming. \eme Addison-Wesley, 3rd edition, 1998, page 145. + * \endclist + * + * \example examples/simple/igraph_fisher_yates_shuffle.c + */ + +int FUNCTION(igraph_vector, shuffle)(TYPE(igraph_vector) *v) { + long int n = FUNCTION(igraph_vector, size)(v); + long int k; + BASE dummy; + + RNG_BEGIN(); + while (n > 1) { + k = RNG_INTEGER(0, n - 1); + n--; + dummy = VECTOR(*v)[n]; + VECTOR(*v)[n] = VECTOR(*v)[k]; + VECTOR(*v)[k] = dummy; + } + RNG_END(); + + return IGRAPH_SUCCESS; +} + +/** + * \function igraph_vector_add + * \brief Add two vectors. + * + * Add the elements of \p v2 to \p v1, the result is stored in \p + * v1. The two vectors must have the same length. + * \param v1 The first vector, the result will be stored here. + * \param v2 The second vector, its contents will be unchanged. + * \return Error code. + * + * Time complexity: O(n), the number of elements. + */ + +int FUNCTION(igraph_vector, add)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2) { + + long int n1 = FUNCTION(igraph_vector, size)(v1); + long int n2 = FUNCTION(igraph_vector, size)(v2); + long int i; + if (n1 != n2) { + IGRAPH_ERROR("Vectors must have the same number of elements for swapping", + IGRAPH_EINVAL); + } + + for (i = 0; i < n1; i++) { +#ifdef SUM + SUM(VECTOR(*v1)[i], VECTOR(*v1)[i], VECTOR(*v2)[i]); +#else + VECTOR(*v1)[i] += VECTOR(*v2)[i]; +#endif + } + + return 0; +} + +/** + * \function igraph_vector_sub + * \brief Subtract a vector from another one. + * + * Subtract the elements of \p v2 from \p v1, the result is stored in + * \p v1. The two vectors must have the same length. + * \param v1 The first vector, to subtract from. The result is stored + * here. + * \param v2 The vector to subtract, it will be unchanged. + * \return Error code. + * + * Time complexity: O(n), the length of the vectors. + */ + +int FUNCTION(igraph_vector, sub)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2) { + + long int n1 = FUNCTION(igraph_vector, size)(v1); + long int n2 = FUNCTION(igraph_vector, size)(v2); + long int i; + if (n1 != n2) { + IGRAPH_ERROR("Vectors must have the same number of elements for swapping", + IGRAPH_EINVAL); + } + + for (i = 0; i < n1; i++) { +#ifdef DIFF + DIFF(VECTOR(*v1)[i], VECTOR(*v1)[i], VECTOR(*v2)[i]); +#else + VECTOR(*v1)[i] -= VECTOR(*v2)[i]; +#endif + } + + return 0; +} + +/** + * \function igraph_vector_mul + * \brief Multiply two vectors. + * + * \p v1 will be multiplied by \p v2, elementwise. The two vectors + * must have the same length. + * \param v1 The first vector, the result will be stored here. + * \param v2 The second vector, it is left unchanged. + * \return Error code. + * + * Time complexity: O(n), the number of elements. + */ + +int FUNCTION(igraph_vector, mul)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2) { + + long int n1 = FUNCTION(igraph_vector, size)(v1); + long int n2 = FUNCTION(igraph_vector, size)(v2); + long int i; + if (n1 != n2) { + IGRAPH_ERROR("Vectors must have the same number of elements for swapping", + IGRAPH_EINVAL); + } + + for (i = 0; i < n1; i++) { +#ifdef PROD + PROD(VECTOR(*v1)[i], VECTOR(*v1)[i], VECTOR(*v2)[i]); +#else + VECTOR(*v1)[i] *= VECTOR(*v2)[i]; +#endif + } + + return 0; +} + +/** + * \function igraph_vector_div + * \brief Divide a vector by another one. + * + * \p v1 is divided by \p v2, elementwise. They must have the same length. If the + * base type of the vector can generate divide by zero errors then + * please make sure that \p v2 contains no zero if you want to avoid + * trouble. + * \param v1 The dividend. The result is also stored here. + * \param v2 The divisor, it is left unchanged. + * \return Error code. + * + * Time complexity: O(n), the length of the vectors. + */ + +int FUNCTION(igraph_vector, div)(TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2) { + + long int n1 = FUNCTION(igraph_vector, size)(v1); + long int n2 = FUNCTION(igraph_vector, size)(v2); + long int i; + if (n1 != n2) { + IGRAPH_ERROR("Vectors must have the same number of elements for swapping", + IGRAPH_EINVAL); + } + + for (i = 0; i < n1; i++) { +#ifdef DIV + DIV(VECTOR(*v1)[i], VECTOR(*v1)[i], VECTOR(*v2)[i]); +#else + VECTOR(*v1)[i] /= VECTOR(*v2)[i]; +#endif + } + + return 0; +} + +#ifndef NOABS + +int FUNCTION(igraph_vector, abs)(TYPE(igraph_vector) *v) { +#ifdef UNSIGNED + /* Nothing do to, unsigned type */ +#else + long int i, n = FUNCTION(igraph_vector, size)(v); + for (i = 0; i < n; i++) { + VECTOR(*v)[i] = VECTOR(*v)[i] >= 0 ? VECTOR(*v)[i] : -VECTOR(*v)[i]; + } +#endif + + return 0; +} + +#endif + +#ifndef NOTORDERED + +/** + * \function igraph_vector_minmax + * \brief Minimum and maximum elements of a vector. + * + * Handy if you want to have both the smallest and largest element of + * a vector. The vector is only traversed once. The vector must by non-empty. + * \param v The input vector. It must contain at least one element. + * \param min Pointer to a base type variable, the minimum is stored + * here. + * \param max Pointer to a base type variable, the maximum is stored + * here. + * \return Error code. + * + * Time complexity: O(n), the number of elements. + */ + +int FUNCTION(igraph_vector, minmax)(const TYPE(igraph_vector) *v, + BASE *min, BASE *max) { + long int n = FUNCTION(igraph_vector, size)(v); + long int i; + *min = *max = VECTOR(*v)[0]; + for (i = 1; i < n; i++) { + BASE tmp = VECTOR(*v)[i]; + if (tmp > *max) { + *max = tmp; + } else if (tmp < *min) { + *min = tmp; + } + } + return 0; +} + +/** + * \function igraph_vector_which_minmax + * \brief Index of the minimum and maximum elements + * + * Handy if you need the indices of the smallest and largest + * elements. The vector is traversed only once. The vector must to + * non-empty. + * \param v The input vector. It must contain at least one element. + * \param which_min The index of the minimum element will be stored + * here. + * \param which_max The index of the maximum element will be stored + * here. + * \return Error code. + * + * Time complexity: O(n), the number of elements. + */ + +int FUNCTION(igraph_vector, which_minmax)(const TYPE(igraph_vector) *v, + long int *which_min, long int *which_max) { + + long int n = FUNCTION(igraph_vector, size)(v); + long int i; + BASE min, max; + *which_min = *which_max = 0; + min = max = VECTOR(*v)[0]; + for (i = 1; i < n; i++) { + BASE tmp = VECTOR(*v)[i]; + if (tmp > max) { + max = tmp; + *which_max = i; + } else if (tmp < min) { + min = tmp; + *which_min = i; + } + } + return 0; +} + +#endif + +/** + * \function igraph_vector_isnull + * \brief Are all elements zero? + * + * Checks whether all elements of a vector are zero. + * \param v The input vector + * \return Boolean, \c TRUE if the vector contains only zeros, \c + * FALSE otherwise. + * + * Time complexity: O(n), the number of elements. + */ + +igraph_bool_t FUNCTION(igraph_vector, isnull)(const TYPE(igraph_vector) *v) { + + long int n = FUNCTION(igraph_vector, size)(v); + long int i = 0; + +#ifdef EQ + while (i < n && EQ(VECTOR(*v)[i], ZERO)) { +#else + while (i < n && VECTOR(*v)[i] == ZERO) { +#endif + i++; + } + + return i == n; +} + +#ifndef NOTORDERED + +int FUNCTION(igraph_i_vector, intersect_sorted)( + const TYPE(igraph_vector) *v1, long int begin1, long int end1, + const TYPE(igraph_vector) *v2, long int begin2, long int end2, + TYPE(igraph_vector) *result); + +/** + * \function igraph_vector_intersect_sorted + * \brief Calculates the intersection of two sorted vectors + * + * The elements that are contained in both vectors are stored in the result + * vector. All three vectors must be initialized. + * + * + * Instead of the naive intersection which takes O(n), this function uses + * the set intersection method of Ricardo Baeza-Yates, which is more efficient + * when one of the vectors is significantly smaller than the other, and + * gives similar performance on average when the two vectors are equal. + * + * + * The algorithm keeps the multiplicities of the elements: if an element appears + * k1 times in the first vector and k2 times in the second, the result + * will include that element min(k1, k2) times. + * + * + * Reference: Baeza-Yates R: A fast set intersection algorithm for sorted + * sequences. In: Lecture Notes in Computer Science, vol. 3109/2004, pp. + * 400--408, 2004. Springer Berlin/Heidelberg. ISBN: 978-3-540-22341-2. + * + * \param v1 the first vector + * \param v2 the second vector + * \param result the result vector, which will also be sorted. + * + * Time complexity: O(m log(n)) where m is the size of the smaller vector + * and n is the size of the larger one. + */ +int FUNCTION(igraph_vector, intersect_sorted)(const TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2, TYPE(igraph_vector) *result) { + long int size1, size2; + + size1 = FUNCTION(igraph_vector, size)(v1); + size2 = FUNCTION(igraph_vector, size)(v2); + + FUNCTION(igraph_vector, clear)(result); + + if (size1 == 0 || size2 == 0) { + return 0; + } + + IGRAPH_CHECK(FUNCTION(igraph_i_vector, intersect_sorted)( + v1, 0, size1, v2, 0, size2, result)); + return 0; +} + +int FUNCTION(igraph_i_vector, intersect_sorted)( + const TYPE(igraph_vector) *v1, long int begin1, long int end1, + const TYPE(igraph_vector) *v2, long int begin2, long int end2, + TYPE(igraph_vector) *result) { + long int size1, size2, probe1, probe2; + + if (begin1 == end1 || begin2 == end2) { + return 0; + } + + size1 = end1 - begin1; + size2 = end2 - begin2; + + if (size1 < size2) { + probe1 = begin1 + (size1 >> 1); /* pick the median element */ + FUNCTION(igraph_i_vector, binsearch_slice)(v2, VECTOR(*v1)[probe1], &probe2, begin2, end2); + IGRAPH_CHECK(FUNCTION(igraph_i_vector, intersect_sorted)( + v1, begin1, probe1, v2, begin2, probe2, result + )); + if (!(probe2 == end2 || VECTOR(*v1)[probe1] < VECTOR(*v2)[probe2])) { + IGRAPH_CHECK(FUNCTION(igraph_vector, push_back)(result, VECTOR(*v2)[probe2])); + probe2++; + } + IGRAPH_CHECK(FUNCTION(igraph_i_vector, intersect_sorted)( + v1, probe1 + 1, end1, v2, probe2, end2, result + )); + } else { + probe2 = begin2 + (size2 >> 1); /* pick the median element */ + FUNCTION(igraph_i_vector, binsearch_slice)(v1, VECTOR(*v2)[probe2], &probe1, begin1, end1); + IGRAPH_CHECK(FUNCTION(igraph_i_vector, intersect_sorted)( + v1, begin1, probe1, v2, begin2, probe2, result + )); + if (!(probe1 == end1 || VECTOR(*v2)[probe2] < VECTOR(*v1)[probe1])) { + IGRAPH_CHECK(FUNCTION(igraph_vector, push_back)(result, VECTOR(*v2)[probe2])); + probe1++; + } + IGRAPH_CHECK(FUNCTION(igraph_i_vector, intersect_sorted)( + v1, probe1, end1, v2, probe2 + 1, end2, result + )); + } + + return 0; +} + +/** + * \function igraph_vector_difference_sorted + * \brief Calculates the difference between two sorted vectors (considered as sets) + * + * The elements that are contained in only the first vector but not the second are + * stored in the result vector. All three vectors must be initialized. + * + * \param v1 the first vector + * \param v2 the second vector + * \param result the result vector + */ +int FUNCTION(igraph_vector, difference_sorted)(const TYPE(igraph_vector) *v1, + const TYPE(igraph_vector) *v2, TYPE(igraph_vector) *result) { + long int i, j, i0, j0; + i0 = FUNCTION(igraph_vector, size)(v1); + j0 = FUNCTION(igraph_vector, size)(v2); + i = j = 0; + + if (i0 == 0) { + /* v1 is empty, this is easy */ + FUNCTION(igraph_vector, clear)(result); + return IGRAPH_SUCCESS; + } + + if (j0 == 0) { + /* v2 is empty, this is easy */ + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(result, i0)); + memcpy(result->stor_begin, v1->stor_begin, sizeof(BASE) * (size_t) i0); + return IGRAPH_SUCCESS; + } + + FUNCTION(igraph_vector, clear)(result); + + /* Copy the part of v1 that is less than the first element of v2 */ + while (i < i0 && VECTOR(*v1)[i] < VECTOR(*v2)[j]) { + i++; + } + if (i > 0) { + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(result, i)); + memcpy(result->stor_begin, v1->stor_begin, sizeof(BASE) * (size_t) i); + } + + while (i < i0 && j < j0) { + BASE element = VECTOR(*v1)[i]; + if (element == VECTOR(*v2)[j]) { + i++; j++; + while (i < i0 && VECTOR(*v1)[i] == element) { + i++; + } + while (j < j0 && VECTOR(*v2)[j] == element) { + j++; + } + } else if (element < VECTOR(*v2)[j]) { + IGRAPH_CHECK(FUNCTION(igraph_vector, push_back)(result, element)); + i++; + } else { + j++; + } + } + if (i < i0) { + long int oldsize = FUNCTION(igraph_vector, size)(result); + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(result, oldsize + i0 - i)); + memcpy(result->stor_begin + oldsize, v1->stor_begin + i, + sizeof(BASE) * (size_t) (i0 - i)); + } + + return 0; +} + +#endif + +#if defined(OUT_FORMAT) + +#ifndef USING_R +int FUNCTION(igraph_vector, print)(const TYPE(igraph_vector) *v) { + long int i, n = FUNCTION(igraph_vector, size)(v); + if (n != 0) { +#ifdef PRINTFUNC + PRINTFUNC(VECTOR(*v)[0]); +#else + printf(OUT_FORMAT, VECTOR(*v)[0]); +#endif + } + for (i = 1; i < n; i++) { +#ifdef PRINTFUNC + putchar(' '); PRINTFUNC(VECTOR(*v)[i]); +#else + printf(" " OUT_FORMAT, VECTOR(*v)[i]); +#endif + } + printf("\n"); + return 0; +} + +int FUNCTION(igraph_vector, printf)(const TYPE(igraph_vector) *v, + const char *format) { + long int i, n = FUNCTION(igraph_vector, size)(v); + if (n != 0) { + printf(format, VECTOR(*v)[0]); + } + for (i = 1; i < n; i++) { + putchar(' '); printf(format, VECTOR(*v)[i]); + } + printf("\n"); + return 0; +} + +#endif + +int FUNCTION(igraph_vector, fprint)(const TYPE(igraph_vector) *v, FILE *file) { + long int i, n = FUNCTION(igraph_vector, size)(v); + if (n != 0) { +#ifdef FPRINTFUNC + FPRINTFUNC(file, VECTOR(*v)[0]); +#else + fprintf(file, OUT_FORMAT, VECTOR(*v)[0]); +#endif + } + for (i = 1; i < n; i++) { +#ifdef FPRINTFUNC + fputc(' ', file); FPRINTFUNC(file, VECTOR(*v)[i]); +#else + fprintf(file, " " OUT_FORMAT, VECTOR(*v)[i]); +#endif + } + fprintf(file, "\n"); + return 0; +} + +#endif + +int FUNCTION(igraph_vector, index)(const TYPE(igraph_vector) *v, + TYPE(igraph_vector) *newv, + const igraph_vector_t *idx) { + + long int i, newlen = igraph_vector_size(idx); + IGRAPH_CHECK(FUNCTION(igraph_vector, resize)(newv, newlen)); + + for (i = 0; i < newlen; i++) { + long int j = (long int) VECTOR(*idx)[i]; + VECTOR(*newv)[i] = VECTOR(*v)[j]; + } + + return 0; +} + +int FUNCTION(igraph_vector, index_int)(TYPE(igraph_vector) *v, + const igraph_vector_int_t *idx) { + BASE *tmp; + int i, n = igraph_vector_int_size(idx); + + tmp = igraph_Calloc(n, BASE); + if (!tmp) { + IGRAPH_ERROR("Cannot index vector", IGRAPH_ENOMEM); + } + + for (i = 0; i < n; i++) { + tmp[i] = VECTOR(*v)[ VECTOR(*idx)[i] ]; + } + + igraph_Free(v->stor_begin); + v->stor_begin = tmp; + v->stor_end = v->end = tmp + n; + + return 0; +} diff --git a/src/vector_ptr.c b/src/vector_ptr.c new file mode 100644 index 0000000..523c43a --- /dev/null +++ b/src/vector_ptr.c @@ -0,0 +1,628 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2003-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_types.h" +#include "igraph_vector_ptr.h" +#include "igraph_memory.h" +#include "igraph_random.h" +#include "igraph_error.h" +#include "config.h" + +#include +#include /* memcpy & co. */ +#include + +/** + * \section about_igraph_vector_ptr_objects Pointer vectors + * (igraph_vector_ptr_t) + * + * The \type igraph_vector_ptr_t data type is very similar to + * the \type igraph_vector_t type, but it stores generic pointers instead of + * real numbers. + * + * This type has the same space complexity as \type + * igraph_vector_t, and most implemented operations work the same way + * as for \type igraph_vector_t. + * + * This type is mostly used to pass to or receive from a set of + * graphs to some \a igraph functions, such as \ref + * igraph_decompose(), which decomposes a graph to connected + * components. + * + * The same \ref VECTOR macro used for ordinary vectors can be + * used for pointer vectors as well, please note that a typeless + * generic pointer will be provided by this macro and you may need to + * cast it to a specific pointer before starting to work with it. + * + * Pointer vectors may have an associated item destructor function + * which takes a pointer and returns nothing. The item destructor will + * be called on each item in the pointer vector when it is destroyed by + * \ref igraph_vector_ptr_destroy() or \ref igraph_vector_ptr_destroy_all(), + * or when its elements are freed by \ref igraph_vector_ptr_free_all(). + * Note that the semantics of an item destructor does not coincide with + * C++ destructors; for instance, when a pointer vector is resized to a + * smaller size, the extra items will \em not be destroyed automatically! + * Nevertheless, item destructors may become handy in many cases; for + * instance, a vector of graphs generated by \ref igraph_decompose() can + * be destroyed with a single call to \ref igraph_vector_ptr_destroy_all() + * if the item destructor is set to \ref igraph_destroy(). + */ + + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_init + * \brief Initialize a pointer vector (constructor). + * + * + * This is the constructor of the pointer vector data type. All + * pointer vectors constructed this way should be destroyed via + * calling \ref igraph_vector_ptr_destroy(). + * \param v Pointer to an uninitialized + * igraph_vector_ptr_t object, to be created. + * \param size Integer, the size of the pointer vector. + * \return Error code: + * \c IGRAPH_ENOMEM if out of memory + * + * Time complexity: operating system dependent, the amount of \quote + * time \endquote required to allocate \p size elements. + */ + +int igraph_vector_ptr_init (igraph_vector_ptr_t* v, int long size) { + long int alloc_size = size > 0 ? size : 1; + assert(v != NULL); + if (size < 0) { + size = 0; + } + v->stor_begin = igraph_Calloc(alloc_size, void*); + if (v->stor_begin == 0) { + IGRAPH_ERROR("vector ptr init failed", IGRAPH_ENOMEM); + } + v->stor_end = v->stor_begin + alloc_size; + v->end = v->stor_begin + size; + v->item_destructor = 0; + + return 0; +} + +/** + */ + +const igraph_vector_ptr_t *igraph_vector_ptr_view (const igraph_vector_ptr_t *v, void *const *data, + long int length) { + igraph_vector_ptr_t *v2 = (igraph_vector_ptr_t*) v; + v2->stor_begin = (void **)data; + v2->stor_end = (void**)data + length; + v2->end = v2->stor_end; + v2->item_destructor = 0; + return v; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_destroy + * \brief Destroys a pointer vector. + * + * + * The destructor for pointer vectors. + * \param v Pointer to the pointer vector to destroy. + * + * Time complexity: operating system dependent, the \quote time + * \endquote required to deallocate O(n) bytes, n is the number of + * elements allocated for the pointer vector (not necessarily the + * number of elements in the vector). + */ + +void igraph_vector_ptr_destroy (igraph_vector_ptr_t* v) { + assert(v != 0); + if (v->stor_begin != 0) { + igraph_Free(v->stor_begin); + v->stor_begin = NULL; + } +} + +void igraph_i_vector_ptr_call_item_destructor_all(igraph_vector_ptr_t* v) { + void **ptr; + + if (v->item_destructor != 0) { + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + if (*ptr != 0) { + v->item_destructor(*ptr); + } + } + } +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_free_all + * \brief Frees all the elements of a pointer vector. + * + * If an item destructor is set for this pointer vector, this function will + * first call the destructor on all elements of the vector and then + * free all the elements using free(). If an item destructor is not set, + * the elements will simply be freed. + * + * \param v Pointer to the pointer vector whose elements will be freed. + * + * Time complexity: operating system dependent, the \quote time + * \endquote required to call the destructor n times and then + * deallocate O(n) pointers, each pointing to a memory area of + * arbitrary size. n is the number of elements in the pointer vector. + */ + +void igraph_vector_ptr_free_all (igraph_vector_ptr_t* v) { + void **ptr; + assert(v != 0); + assert(v->stor_begin != 0); + + igraph_i_vector_ptr_call_item_destructor_all(v); + for (ptr = v->stor_begin; ptr < v->end; ptr++) { + igraph_Free(*ptr); + } +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_destroy_all + * \brief Frees all the elements and destroys the pointer vector. + * + * This function is equivalent to \ref igraph_vector_ptr_free_all() + * followed by \ref igraph_vector_ptr_destroy(). + * + * \param v Pointer to the pointer vector to destroy. + * + * Time complexity: operating system dependent, the \quote time + * \endquote required to deallocate O(n) pointers, each pointing to + * a memory area of arbitrary size, plus the \quote time \endquote + * required to deallocate O(n) bytes, n being the number of elements + * allocated for the pointer vector (not necessarily the number of + * elements in the vector). + */ + +void igraph_vector_ptr_destroy_all (igraph_vector_ptr_t* v) { + assert(v != 0); + assert(v->stor_begin != 0); + igraph_vector_ptr_free_all(v); + igraph_vector_ptr_set_item_destructor(v, 0); + igraph_vector_ptr_destroy(v); +} + +/** + * \ingroup vectorptr + * \brief Reserves memory for a pointer vector for later use. + * + * @return Error code: + * - IGRAPH_ENOMEM: out of memory + */ + +int igraph_vector_ptr_reserve (igraph_vector_ptr_t* v, long int size) { + long int actual_size = igraph_vector_ptr_size(v); + void **tmp; + assert(v != NULL); + assert(v->stor_begin != NULL); + + if (size <= igraph_vector_ptr_size(v)) { + return 0; + } + + tmp = igraph_Realloc(v->stor_begin, (size_t) size, void*); + if (tmp == 0) { + IGRAPH_ERROR("vector ptr reserve failed", IGRAPH_ENOMEM); + } + v->stor_begin = tmp; + v->stor_end = v->stor_begin + size; + v->end = v->stor_begin + actual_size; + + return 0; +} + +/** + * \ingroup vectorptr + * \brief Decides whether the pointer vector is empty. + */ + +igraph_bool_t igraph_vector_ptr_empty (const igraph_vector_ptr_t* v) { + assert(v != NULL); + assert(v->stor_begin != NULL); + return v->stor_begin == v->end; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_size + * \brief Gives the number of elements in the pointer vector. + * + * \param v The pointer vector object. + * \return The size of the object, ie. the number of pointers stored. + * + * Time complexity: O(1). + */ + +long int igraph_vector_ptr_size (const igraph_vector_ptr_t* v) { + assert(v != NULL); + /* assert(v->stor_begin != NULL); */ /* TODO */ + return v->end - v->stor_begin; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_clear + * \brief Removes all elements from a pointer vector. + * + * + * This function resizes a pointer to vector to zero length. Note that + * the pointed objects are \em not deallocated, you should call + * free() on them, or make sure that their allocated memory is freed + * in some other way, you'll get memory leaks otherwise. If you have + * set up an item destructor earlier, the destructor will be called + * on every element. + * + * + * Note that the current implementation of this function does + * \em not deallocate the memory required for storing the + * pointers, so making a pointer vector smaller this way does not give + * back any memory. This behavior might change in the future. + * \param v The pointer vector to clear. + * + * Time complexity: O(1). + */ + +void igraph_vector_ptr_clear (igraph_vector_ptr_t* v) { + assert(v != NULL); + assert(v->stor_begin != NULL); + igraph_i_vector_ptr_call_item_destructor_all(v); + v->end = v->stor_begin; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_push_back + * \brief Appends an element to the back of a pointer vector. + * + * \param v The pointer vector. + * \param e The new element to include in the pointer vector. + * \return Error code. + * \sa igraph_vector_push_back() for the corresponding operation of + * the ordinary vector type. + * + * Time complexity: O(1) or O(n), n is the number of elements in the + * vector. The pointer vector implementation ensures that n subsequent + * push_back operations need O(n) time to complete. + */ + +int igraph_vector_ptr_push_back (igraph_vector_ptr_t* v, void* e) { + assert(v != NULL); + assert(v->stor_begin != NULL); + + /* full, allocate more storage */ + if (v->stor_end == v->end) { + long int new_size = igraph_vector_ptr_size(v) * 2; + if (new_size == 0) { + new_size = 1; + } + IGRAPH_CHECK(igraph_vector_ptr_reserve(v, new_size)); + } + + *(v->end) = e; + v->end += 1; + + return 0; +} + +void *igraph_vector_ptr_pop_back (igraph_vector_ptr_t *v) { + assert(v != NULL); + assert(v->stor_begin != NULL); + assert(v->stor_begin != v->end); + v->end -= 1; + return *(v->end); +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_insert + * \brief Inserts a single element into a pointer vector. + * + * Note that this function does not do range checking. Insertion will shift the + * elements from the position given to the end of the vector one position to the + * right, and the new element will be inserted in the empty space created at + * the given position. The size of the vector will increase by one. + * + * \param v The pointer vector object. + * \param pos The position where the new element is inserted. + * \param e The inserted element + */ +int igraph_vector_ptr_insert(igraph_vector_ptr_t* v, long int pos, void* e) { + long int size = igraph_vector_ptr_size(v); + IGRAPH_CHECK(igraph_vector_ptr_resize(v, size + 1)); + if (pos < size) { + memmove(v->stor_begin + pos + 1, v->stor_begin + pos, + sizeof(void*) * (size_t) (size - pos)); + } + v->stor_begin[pos] = e; + return 0; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_e + * \brief Access an element of a pointer vector. + * + * \param v Pointer to a pointer vector. + * \param pos The index of the pointer to return. + * \return The pointer at \p pos position. + * + * Time complexity: O(1). + */ + +void* igraph_vector_ptr_e (const igraph_vector_ptr_t* v, long int pos) { + assert(v != NULL); + assert(v->stor_begin != NULL); + return * (v->stor_begin + pos); +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_set + * \brief Assign to an element of a pointer vector. + * + * \param v Pointer to a pointer vector. + * \param pos The index of the pointer to update. + * \param value The new pointer to set in the vector. + * + * Time complexity: O(1). + */ + +void igraph_vector_ptr_set (igraph_vector_ptr_t* v, long int pos, void* value) { + assert(v != NULL); + assert(v->stor_begin != NULL); + *(v->stor_begin + pos) = value; +} + +/** + * \ingroup vectorptr + * \brief Set all elements of a pointer vector to the NULL pointer. + */ + +void igraph_vector_ptr_null (igraph_vector_ptr_t* v) { + assert(v != NULL); + assert(v->stor_begin != NULL); + if (igraph_vector_ptr_size(v) > 0) { + memset(v->stor_begin, 0, sizeof(void*) * + (size_t) igraph_vector_ptr_size(v)); + } +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_resize + * \brief Resizes a pointer vector. + * + * + * Note that if a vector is made smaller the pointed object are not + * deallocated by this function and the item destructor is not called + * on the extra elements. + * + * \param v A pointer vector. + * \param newsize The new size of the pointer vector. + * \return Error code. + * + * Time complexity: O(1) if the vector if made smaller. Operating + * system dependent otherwise, the amount of \quote time \endquote + * needed to allocate the memory for the vector elements. + */ + +int igraph_vector_ptr_resize(igraph_vector_ptr_t* v, long int newsize) { + IGRAPH_CHECK(igraph_vector_ptr_reserve(v, newsize)); + v->end = v->stor_begin + newsize; + return 0; +} + +/** + * \ingroup vectorptr + * \brief Initializes a pointer vector from an array (constructor). + * + * \return Error code: + * \c IGRAPH_ENOMEM if out of memory + */ + +int igraph_vector_ptr_init_copy(igraph_vector_ptr_t *v, void * *data, long int length) { + v->stor_begin = igraph_Calloc(length, void*); + if (v->stor_begin == 0) { + IGRAPH_ERROR("cannot init ptr vector from array", IGRAPH_ENOMEM); + } + v->stor_end = v->stor_begin + length; + v->end = v->stor_end; + v->item_destructor = 0; + memcpy(v->stor_begin, data, (size_t) length * sizeof(void*)); + + return 0; +} + +/** + * \ingroup vectorptr + * \brief Copy the contents of a pointer vector to a regular C array. + */ + +void igraph_vector_ptr_copy_to(const igraph_vector_ptr_t *v, void** to) { + assert(v != NULL); + assert(v->stor_begin != NULL); + if (v->end != v->stor_begin) { + memcpy(to, v->stor_begin, sizeof(void*) * + (size_t) (v->end - v->stor_begin)); + } +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_copy + * \brief Copy a pointer vector (constructor). + * + * + * This function creates a pointer vector by copying another one. This + * is shallow copy, only the pointers in the vector will be copied. + * + * + * It is potentially dangerous to copy a pointer vector with an associated + * item destructor. The copied vector will inherit the item destructor, + * which may cause problems when both vectors are destroyed as the items + * might get destroyed twice. Make sure you know what you are doing when + * copying a pointer vector with an item destructor, or unset the item + * destructor on one of the vectors later. + * + * \param to Pointer to an uninitialized pointer vector object. + * \param from A pointer vector object. + * \return Error code: + * \c IGRAPH_ENOMEM if out of memory + * + * Time complexity: O(n) if allocating memory for n elements can be + * done in O(n) time. + */ + +int igraph_vector_ptr_copy(igraph_vector_ptr_t *to, const igraph_vector_ptr_t *from) { + assert(from != NULL); + /* assert(from->stor_begin != NULL); */ /* TODO */ + to->stor_begin = igraph_Calloc(igraph_vector_ptr_size(from), void*); + if (to->stor_begin == 0) { + IGRAPH_ERROR("cannot copy ptr vector", IGRAPH_ENOMEM); + } + to->stor_end = to->stor_begin + igraph_vector_ptr_size(from); + to->end = to->stor_end; + to->item_destructor = from->item_destructor; + memcpy(to->stor_begin, from->stor_begin, + (size_t) igraph_vector_ptr_size(from)*sizeof(void*)); + + return 0; +} + +/** + * \ingroup vectorptr + * \brief Remove an element from a pointer vector. + */ + +void igraph_vector_ptr_remove(igraph_vector_ptr_t *v, long int pos) { + assert(v != NULL); + assert(v->stor_begin != NULL); + if (pos + 1 < igraph_vector_ptr_size(v)) { /* TOOD: why is this needed */ + memmove(v->stor_begin + pos, v->stor_begin + pos + 1, + sizeof(void*) * (size_t) (igraph_vector_ptr_size(v) - pos - 1)); + } + v->end--; +} + +/** + * \ingroup vectorptr + * \brief Sort the pointer vector based on an external comparison function + * + * Sometimes it is necessary to sort the pointers in the vector based on + * the property of the element being referenced by the pointer. This + * function allows us to sort the vector based on an arbitrary external + * comparison function which accepts two \c void* pointers \c p1 and \c p2 + * and returns an integer less than, equal to or greater than zero if the + * first argument is considered to be respectively less than, equal to, or + * greater than the second. \c p1 and \c p2 will point to the pointer in the + * vector, so they have to be double-dereferenced if one wants to get access + * to the underlying object the address of which is stored in \c v . + */ +void igraph_vector_ptr_sort(igraph_vector_ptr_t *v, int (*compar)(const void*, const void*)) { + qsort(v->stor_begin, (size_t) igraph_vector_ptr_size(v), sizeof(void*), + compar); +} + +int igraph_vector_ptr_index_int(igraph_vector_ptr_t *v, + const igraph_vector_int_t *idx) { + void **tmp; + int i, n = igraph_vector_int_size(idx); + + tmp = igraph_Calloc(n, void*); + if (!tmp) { + IGRAPH_ERROR("Cannot index pointer vector", IGRAPH_ENOMEM); + } + + for (i = 0; i < n; i++) { + tmp[i] = VECTOR(*v)[ VECTOR(*idx)[i] ]; + } + + igraph_Free(v->stor_begin); + v->stor_begin = tmp; + v->stor_end = v->end = tmp + n; + + return 0; +} + +int igraph_vector_ptr_append (igraph_vector_ptr_t *to, + const igraph_vector_ptr_t *from) { + long int origsize = igraph_vector_ptr_size(to); + long int othersize = igraph_vector_ptr_size(from); + long int i; + + IGRAPH_CHECK(igraph_vector_ptr_resize(to, origsize + othersize)); + for (i = 0; i < othersize; i++, origsize++) { + to->stor_begin[origsize] = from->stor_begin[i]; + } + + return 0; +} + + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_set_item_destructor + * \brief Sets the item destructor for this pointer vector. + * + * The item destructor is a function which will be called on every non-null + * pointer stored in this vector when \ref igraph_vector_ptr_destroy(), + * igraph_vector_ptr_destroy_all() or \ref igraph_vector_ptr_free_all() + * is called. + * + * \return The old item destructor. + * + * Time complexity: O(1). + */ +igraph_finally_func_t* igraph_vector_ptr_set_item_destructor( + igraph_vector_ptr_t *v, igraph_finally_func_t *func) { + igraph_finally_func_t* result = v->item_destructor; + + v->item_destructor = func; + + return result; +} + +/** + * \ingroup vectorptr + * \function igraph_vector_ptr_get_item_destructor + * \brief Gets the current item destructor for this pointer vector. + * + * The item destructor is a function which will be called on every non-null + * pointer stored in this vector when \ref igraph_vector_ptr_destroy(), + * igraph_vector_ptr_destroy_all() or \ref igraph_vector_ptr_free_all() + * is called. + * + * \return The current item destructor. + * + * Time complexity: O(1). + */ +igraph_finally_func_t* igraph_vector_ptr_get_item_destructor(const igraph_vector_ptr_t *v) { + assert(v != 0); + return v->item_destructor; +} diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..72af7ca --- /dev/null +++ b/src/version.c @@ -0,0 +1,67 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2008-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_version.h" + +#include + +static const char *igraph_version_string = IGRAPH_VERSION; + +/** + * \function igraph_version + * Return the version of the igraph C library + * + * \param version_string Pointer to a string pointer. If not null, it + * is set to the igraph version string, e.g. "0.6" or "0.5.3". This + * string should not be modified or deallocated. + * \param major If not a null pointer, then it is set to the major + * igraph version. E.g. for version "0.5.3" this is 0. + * \param minor If not a null pointer, then it is set to the minor + * igraph version. E.g. for version "0.5.3" this is 5. + * \param subminor If not a null pointer, then it is set to the + * subminor igraph version. E.g. for version "0.5.3" this is 3. + * \return Error code. + * + * Time complexity: O(1). + * + * \example examples/simple/igraph_version.c + */ + +int igraph_version(const char **version_string, + int *major, + int *minor, + int *subminor) { + int i1, i2, i3; + int *p1 = major ? major : &i1, + *p2 = minor ? minor : &i2, + *p3 = subminor ? subminor : &i3; + + if (version_string) { + *version_string = igraph_version_string; + } + + *p1 = *p2 = *p3 = 0; + sscanf(IGRAPH_VERSION, "%i.%i.%i", p1, p2, p3); + + return 0; +} diff --git a/src/visitors.c b/src/visitors.c new file mode 100644 index 0000000..c60a093 --- /dev/null +++ b/src/visitors.c @@ -0,0 +1,593 @@ +/* -*- mode: C -*- */ +/* + IGraph R package. + Copyright (C) 2006-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +#include "igraph_visitor.h" +#include "igraph_memory.h" +#include "igraph_adjlist.h" +#include "igraph_interface.h" +#include "igraph_dqueue.h" +#include "igraph_stack.h" +#include "config.h" + +/** + * \function igraph_bfs + * Breadth-first search + * + * A simple breadth-first search, with a lot of different results and + * the possibility to call a callback whenever a vertex is visited. + * It is allowed to supply null pointers as the output arguments the + * user is not interested in, in this case they will be ignored. + * + * + * If not all vertices can be reached from the supplied root vertex, + * then additional root vertices will be used, in the order of their + * vertex ids. + * \param graph The input graph. + * \param root The id of the root vertex. It is ignored if the \c + * roots argument is not a null pointer. + * \param roots Pointer to an initialized vector, or a null + * pointer. If not a null pointer, then it is a vector + * containing root vertices to start the BFS from. The vertices + * are considered in the order they appear. If a root vertex + * was already found while searching from another one, then no + * search is conducted from it. + * \param mode For directed graphs, it defines which edges to follow. + * \c IGRAPH_OUT means following the direction of the edges, + * \c IGRAPH_IN means the opposite, and + * \c IGRAPH_ALL ignores the direction of the edges. + * This parameter is ignored for undirected graphs. + * \param unreachable Logical scalar, whether the search should visit + * the vertices that are unreachable from the given root + * node(s). If true, then additional searches are performed + * until all vertices are visited. + * \param restricted If not a null pointer, then it must be a pointer + * to a vector containing vertex ids. The BFS is carried out + * only on these vertices. + * \param order If not null pointer, then the vertex ids of the graph are + * stored here, in the same order as they were visited. + * \param rank If not a null pointer, then the rank of each vertex is + * stored here. + * \param father If not a null pointer, then the id of the father of + * each vertex is stored here. + * \param pred If not a null pointer, then the id of vertex that was + * visited before the current one is stored here. If there is + * no such vertex (the current vertex is the root of a search + * tree), then -1 is stored. + * \param succ If not a null pointer, then the id of the vertex that + * was visited after the current one is stored here. If there + * is no such vertex (the current one is the last in a search + * tree), then -1 is stored. + * \param dist If not a null pointer, then the distance from the root of + * the current search tree is stored here. + * \param callback If not null, then it should be a pointer to a + * function of type \ref igraph_bfshandler_t. This function + * will be called, whenever a new vertex is visited. + * \param extra Extra argument to pass to the callback function. + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + * + * \example examples/simple/igraph_bfs.c + * \example examples/simple/igraph_bfs2.c + */ + +int igraph_bfs(const igraph_t *graph, + igraph_integer_t root, const igraph_vector_t *roots, + igraph_neimode_t mode, igraph_bool_t unreachable, + const igraph_vector_t *restricted, + igraph_vector_t *order, igraph_vector_t *rank, + igraph_vector_t *father, + igraph_vector_t *pred, igraph_vector_t *succ, + igraph_vector_t *dist, igraph_bfshandler_t *callback, + void *extra) { + + igraph_dqueue_t Q; + long int no_of_nodes = igraph_vcount(graph); + long int actroot = 0; + igraph_vector_char_t added; + + igraph_lazy_adjlist_t adjlist; + + long int act_rank = 0; + long int pred_vec = -1; + + long int rootpos = 0; + long int noroots = roots ? igraph_vector_size(roots) : 1; + + if (!roots && (root < 0 || root >= no_of_nodes)) { + IGRAPH_ERROR("Invalid root vertex in BFS", IGRAPH_EINVAL); + } + + if (roots) { + igraph_real_t min, max; + igraph_vector_minmax(roots, &min, &max); + if (min < 0 || max >= no_of_nodes) { + IGRAPH_ERROR("Invalid root vertex in BFS", IGRAPH_EINVAL); + } + } + + if (restricted) { + igraph_real_t min, max; + igraph_vector_minmax(restricted, &min, &max); + if (min < 0 || max >= no_of_nodes) { + IGRAPH_ERROR("Invalid vertex id in restricted set", IGRAPH_EINVAL); + } + } + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_CHECK(igraph_vector_char_init(&added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_char_destroy, &added); + IGRAPH_CHECK(igraph_dqueue_init(&Q, 100)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &Q); + + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, mode, /*simplify=*/ 0)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + + /* Mark the vertices that are not in the restricted set, as already + found. Special care must be taken for vertices that are not in + the restricted set, but are to be used as 'root' vertices. */ + if (restricted) { + long int i, n = igraph_vector_size(restricted); + igraph_vector_char_fill(&added, 1); + for (i = 0; i < n; i++) { + long int v = (long int) VECTOR(*restricted)[i]; + VECTOR(added)[v] = 0; + } + } + + /* Resize result vectors, and fill them with IGRAPH_NAN */ + +# define VINIT(v) if (v) { \ + igraph_vector_resize((v), no_of_nodes); \ + igraph_vector_fill((v), IGRAPH_NAN); } + + VINIT(order); + VINIT(rank); + VINIT(father); + VINIT(pred); + VINIT(succ); + VINIT(dist); +# undef VINIT + + while (1) { + + /* Get the next root vertex, if any */ + + if (roots && rootpos < noroots) { + /* We are still going through the 'roots' vector */ + actroot = (long int) VECTOR(*roots)[rootpos++]; + } else if (!roots && rootpos == 0) { + /* We have a single root vertex given, and start now */ + actroot = root; + rootpos++; + } else if (rootpos == noroots && unreachable) { + /* We finished the given root(s), but other vertices are also + tried as root */ + actroot = 0; + rootpos++; + } else if (unreachable && actroot + 1 < no_of_nodes) { + /* We are already doing the other vertices, take the next one */ + actroot++; + } else { + /* No more root nodes to do */ + break; + } + + /* OK, we have a new root, start BFS */ + if (VECTOR(added)[actroot]) { + continue; + } + IGRAPH_CHECK(igraph_dqueue_push(&Q, actroot)); + IGRAPH_CHECK(igraph_dqueue_push(&Q, 0)); + VECTOR(added)[actroot] = 1; + if (father) { + VECTOR(*father)[actroot] = -1; + } + + pred_vec = -1; + + while (!igraph_dqueue_empty(&Q)) { + long int actvect = (long int) igraph_dqueue_pop(&Q); + long int actdist = (long int) igraph_dqueue_pop(&Q); + long int succ_vec; + igraph_vector_t *neis = igraph_lazy_adjlist_get(&adjlist, + (igraph_integer_t) actvect); + long int i, n = igraph_vector_size(neis); + + if (pred) { + VECTOR(*pred)[actvect] = pred_vec; + } + if (rank) { + VECTOR(*rank) [actvect] = act_rank; + } + if (order) { + VECTOR(*order)[act_rank++] = actvect; + } + if (dist) { + VECTOR(*dist)[actvect] = actdist; + } + + for (i = 0; i < n; i++) { + long int nei = (long int) VECTOR(*neis)[i]; + if (! VECTOR(added)[nei]) { + VECTOR(added)[nei] = 1; + IGRAPH_CHECK(igraph_dqueue_push(&Q, nei)); + IGRAPH_CHECK(igraph_dqueue_push(&Q, actdist + 1)); + if (father) { + VECTOR(*father)[nei] = actvect; + } + } + } + + succ_vec = igraph_dqueue_empty(&Q) ? -1L : + (long int) igraph_dqueue_head(&Q); + if (callback) { + igraph_bool_t terminate = + callback(graph, (igraph_integer_t) actvect, (igraph_integer_t) + pred_vec, (igraph_integer_t) succ_vec, + (igraph_integer_t) act_rank - 1, (igraph_integer_t) actdist, + extra); + if (terminate) { + igraph_lazy_adjlist_destroy(&adjlist); + igraph_dqueue_destroy(&Q); + igraph_vector_char_destroy(&added); + IGRAPH_FINALLY_CLEAN(3); + return 0; + } + } + + if (succ) { + VECTOR(*succ)[actvect] = succ_vec; + } + pred_vec = actvect; + + } /* while Q !empty */ + + } /* for actroot < no_of_nodes */ + + igraph_lazy_adjlist_destroy(&adjlist); + igraph_dqueue_destroy(&Q); + igraph_vector_char_destroy(&added); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_i_bfs + * \ingroup internal + * + * Added in version 0.2. + * + * TODO + */ + +int igraph_i_bfs(igraph_t *graph, igraph_integer_t vid, igraph_neimode_t mode, + igraph_vector_t *vids, igraph_vector_t *layers, + igraph_vector_t *parents) { + + igraph_dqueue_t q; + long int vidspos = 0; + igraph_vector_t neis; + long int no_of_nodes = igraph_vcount(graph); + long int i; + char *added; + long int lastlayer = -1; + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument", IGRAPH_EINVMODE); + } + + /* temporary storage */ + added = igraph_Calloc(no_of_nodes, char); + if (added == 0) { + IGRAPH_ERROR("Cannot calculate BFS", IGRAPH_ENOMEM); + } + IGRAPH_FINALLY(igraph_free, added); + IGRAPH_VECTOR_INIT_FINALLY(&neis, 0); + IGRAPH_CHECK(igraph_dqueue_init(&q, 100)); + IGRAPH_FINALLY(igraph_dqueue_destroy, &q); + + /* results */ + IGRAPH_CHECK(igraph_vector_resize(vids, no_of_nodes)); + igraph_vector_clear(layers); + IGRAPH_CHECK(igraph_vector_resize(parents, no_of_nodes)); + + /* ok start with vid */ + IGRAPH_CHECK(igraph_dqueue_push(&q, vid)); + IGRAPH_CHECK(igraph_dqueue_push(&q, 0)); + IGRAPH_CHECK(igraph_vector_push_back(layers, vidspos)); + VECTOR(*vids)[vidspos++] = vid; + VECTOR(*parents)[(long int)vid] = vid; + added[(long int)vid] = 1; + + while (!igraph_dqueue_empty(&q)) { + long int actvect = (long int) igraph_dqueue_pop(&q); + long int actdist = (long int) igraph_dqueue_pop(&q); + IGRAPH_CHECK(igraph_neighbors(graph, &neis, (igraph_integer_t) actvect, + mode)); + for (i = 0; i < igraph_vector_size(&neis); i++) { + long int neighbor = (long int) VECTOR(neis)[i]; + if (added[neighbor] == 0) { + added[neighbor] = 1; + VECTOR(*parents)[neighbor] = actvect; + IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor)); + IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1)); + if (lastlayer != actdist + 1) { + IGRAPH_CHECK(igraph_vector_push_back(layers, vidspos)); + } + VECTOR(*vids)[vidspos++] = neighbor; + lastlayer = actdist + 1; + } + } /* for i in neis */ + } /* while ! dqueue_empty */ + IGRAPH_CHECK(igraph_vector_push_back(layers, vidspos)); + + igraph_vector_destroy(&neis); + igraph_dqueue_destroy(&q); + igraph_Free(added); + IGRAPH_FINALLY_CLEAN(3); + + return 0; +} + +/** + * \function igraph_dfs + * Depth-first search + * + * A simple depth-first search, with + * the possibility to call a callback whenever a vertex is discovered + * and/or whenever a subtree is finished. + * It is allowed to supply null pointers as the output arguments the + * user is not interested in, in this case they will be ignored. + * + * + * If not all vertices can be reached from the supplied root vertex, + * then additional root vertices will be used, in the order of their + * vertex ids. + * \param graph The input graph. + * \param root The id of the root vertex. + * \param mode For directed graphs, it defines which edges to follow. + * \c IGRAPH_OUT means following the direction of the edges, + * \c IGRAPH_IN means the opposite, and + * \c IGRAPH_ALL ignores the direction of the edges. + * This parameter is ignored for undirected graphs. + * \param unreachable Logical scalar, whether the search should visit + * the vertices that are unreachable from the given root + * node(s). If true, then additional searches are performed + * until all vertices are visited. + * \param order If not null pointer, then the vertex ids of the graph are + * stored here, in the same order as they were discovered. + * \param order_out If not a null pointer, then the vertex ids of the + * graphs are stored here, in the order of the completion of + * their subtree. + * \param father If not a null pointer, then the id of the father of + * each vertex is stored here. + * \param dist If not a null pointer, then the distance from the root of + * the current search tree is stored here. + * \param in_callback If not null, then it should be a pointer to a + * function of type \ref igraph_dfshandler_t. This function + * will be called, whenever a new vertex is discovered. + * \param out_callback If not null, then it should be a pointer to a + * function of type \ref igraph_dfshandler_t. This function + * will be called, whenever the subtree of a vertex is completed. + * \param extra Extra argument to pass to the callback function(s). + * \return Error code. + * + * Time complexity: O(|V|+|E|), linear in the number of vertices and + * edges. + */ + +int igraph_dfs(const igraph_t *graph, igraph_integer_t root, + igraph_neimode_t mode, igraph_bool_t unreachable, + igraph_vector_t *order, + igraph_vector_t *order_out, igraph_vector_t *father, + igraph_vector_t *dist, igraph_dfshandler_t *in_callback, + igraph_dfshandler_t *out_callback, + void *extra) { + + long int no_of_nodes = igraph_vcount(graph); + igraph_lazy_adjlist_t adjlist; + igraph_stack_t stack; + igraph_vector_char_t added; + igraph_vector_long_t nptr; + long int actroot; + long int act_rank = 0; + long int rank_out = 0; + long int act_dist = 0; + + if (root < 0 || root >= no_of_nodes) { + IGRAPH_ERROR("Invalid root vertex for DFS", IGRAPH_EINVAL); + } + + if (mode != IGRAPH_OUT && mode != IGRAPH_IN && + mode != IGRAPH_ALL) { + IGRAPH_ERROR("Invalid mode argument", IGRAPH_EINVMODE); + } + + if (!igraph_is_directed(graph)) { + mode = IGRAPH_ALL; + } + + IGRAPH_CHECK(igraph_vector_char_init(&added, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_char_destroy, &added); + IGRAPH_CHECK(igraph_stack_init(&stack, 100)); + IGRAPH_FINALLY(igraph_stack_destroy, &stack); + IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &adjlist, mode, /*simplify=*/ 0)); + IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &adjlist); + IGRAPH_CHECK(igraph_vector_long_init(&nptr, no_of_nodes)); + IGRAPH_FINALLY(igraph_vector_long_destroy, &nptr); + +# define FREE_ALL() do { \ + igraph_vector_long_destroy(&nptr); \ + igraph_lazy_adjlist_destroy(&adjlist); \ + igraph_stack_destroy(&stack); \ + igraph_vector_char_destroy(&added); \ + IGRAPH_FINALLY_CLEAN(4); } while (0) + + /* Resize result vectors and fill them with IGRAPH_NAN */ + +# define VINIT(v) if (v) { \ + igraph_vector_resize(v, no_of_nodes); \ + igraph_vector_fill(v, IGRAPH_NAN); } + + VINIT(order); + VINIT(order_out); + VINIT(father); + VINIT(dist); + +# undef VINIT + + IGRAPH_CHECK(igraph_stack_push(&stack, root)); + VECTOR(added)[(long int)root] = 1; + if (father) { + VECTOR(*father)[(long int)root] = -1; + } + if (order) { + VECTOR(*order)[act_rank++] = root; + } + if (dist) { + VECTOR(*dist)[(long int)root] = 0; + } + if (in_callback) { + igraph_bool_t terminate = in_callback(graph, root, 0, extra); + if (terminate) { + FREE_ALL(); + return 0; + } + } + + for (actroot = 0; actroot < no_of_nodes; ) { + + /* 'root' first, then all other vertices */ + if (igraph_stack_empty(&stack)) { + if (!unreachable) { + break; + } + if (VECTOR(added)[actroot]) { + actroot++; + continue; + } + IGRAPH_CHECK(igraph_stack_push(&stack, actroot)); + VECTOR(added)[actroot] = 1; + if (father) { + VECTOR(*father)[actroot] = -1; + } + if (order) { + VECTOR(*order)[act_rank++] = actroot; + } + if (dist) { + VECTOR(*dist)[actroot] = 0; + } + + if (in_callback) { + igraph_bool_t terminate = in_callback(graph, (igraph_integer_t) actroot, + 0, extra); + if (terminate) { + FREE_ALL(); + return 0; + } + } + actroot++; + } + + while (!igraph_stack_empty(&stack)) { + long int actvect = (long int) igraph_stack_top(&stack); + igraph_vector_t *neis = igraph_lazy_adjlist_get(&adjlist, + (igraph_integer_t) actvect); + long int n = igraph_vector_size(neis); + long int *ptr = igraph_vector_long_e_ptr(&nptr, actvect); + + /* Search for a neighbor that was not yet visited */ + igraph_bool_t any = 0; + long int nei; + while (!any && (*ptr) < n) { + nei = (long int) VECTOR(*neis)[(*ptr)]; + any = !VECTOR(added)[nei]; + (*ptr) ++; + } + if (any) { + /* There is such a neighbor, add it */ + IGRAPH_CHECK(igraph_stack_push(&stack, nei)); + VECTOR(added)[nei] = 1; + if (father) { + VECTOR(*father)[ nei ] = actvect; + } + if (order) { + VECTOR(*order)[act_rank++] = nei; + } + act_dist++; + if (dist) { + VECTOR(*dist)[nei] = act_dist; + } + + if (in_callback) { + igraph_bool_t terminate = in_callback(graph, (igraph_integer_t) nei, + (igraph_integer_t) act_dist, + extra); + if (terminate) { + FREE_ALL(); + return 0; + } + } + + } else { + /* There is no such neighbor, finished with the subtree */ + igraph_stack_pop(&stack); + if (order_out) { + VECTOR(*order_out)[rank_out++] = actvect; + } + act_dist--; + + if (out_callback) { + igraph_bool_t terminate = out_callback(graph, (igraph_integer_t) + actvect, (igraph_integer_t) + act_dist, extra); + if (terminate) { + FREE_ALL(); + return 0; + } + } + } + } + } + + FREE_ALL(); +# undef FREE_ALL + + return 0; +} diff --git a/src/walktrap.cpp b/src/walktrap.cpp new file mode 100644 index 0000000..9bf18b9 --- /dev/null +++ b/src/walktrap.cpp @@ -0,0 +1,168 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: walktrap.cpp +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +#include "walktrap_graph.h" +#include "walktrap_communities.h" + +#include "igraph_community.h" +#include "igraph_components.h" +#include "igraph_interface.h" +#include "igraph_interrupt_internal.h" + +using namespace igraph::walktrap; + +/** + * \function igraph_community_walktrap + * + * This function is the implementation of the Walktrap community + * finding algorithm, see Pascal Pons, Matthieu Latapy: Computing + * communities in large networks using random walks, + * https://arxiv.org/abs/physics/0512106 + * + * + * Currently the original C++ implementation is used in igraph, + * see https://www-complexnetworks.lip6.fr/~latapy/PP/walktrap.html + * We are grateful to Matthieu Latapy and Pascal Pons for providing this + * source code. + * + * + * In contrast to the original implementation, isolated vertices are allowed + * in the graph and they are assumed to have a single incident loop edge with + * weight 1. + * + * \param graph The input graph, edge directions are ignored. + * \param weights Numeric vector giving the weights of the edges. + * If it is a NULL pointer then all edges will have equal + * weights. The weights are expected to be positive. + * \param steps Integer constant, the length of the random walks. + * \param merges Pointer to a matrix, the merges performed by the + * algorithm will be stored here (if not NULL). Each merge is a + * row in a two-column matrix and contains the ids of the merged + * clusters. Clusters are numbered from zero and cluster numbers + * smaller than the number of nodes in the network belong to the + * individual vertices as singleton clusters. In each step a new + * cluster is created from two other clusters and its id will be + * one larger than the largest cluster id so far. This means that + * before the first merge we have \c n clusters (the number of + * vertices in the graph) numbered from zero to \c n-1. The first + * merge creates cluster \c n, the second cluster \c n+1, etc. + * \param modularity Pointer to a vector. If not NULL then the + * modularity score of the current clustering is stored here after + * each merge operation. + * \param membership Pointer to a vector. If not a NULL pointer, then + * the membership vector corresponding to the maximal modularity + * score is stored here. If it is not a NULL pointer, then neither + * \p modularity nor \p merges may be NULL. + * \return Error code. + * + * \sa \ref igraph_community_spinglass(), \ref + * igraph_community_edge_betweenness(). + * + * Time complexity: O(|E||V|^2) in the worst case, O(|V|^2 log|V|) typically, + * |V| is the number of vertices, |E| is the number of edges. + * + * \example examples/simple/walktrap.c + */ + +int igraph_community_walktrap(const igraph_t *graph, + const igraph_vector_t *weights, + int steps, + igraph_matrix_t *merges, + igraph_vector_t *modularity, + igraph_vector_t *membership) { + + long int no_of_nodes = (long int)igraph_vcount(graph); + int length = steps; + long max_memory = -1; + + if (membership && !(modularity && merges)) { + IGRAPH_ERROR("Cannot calculate membership without modularity or merges", + IGRAPH_EINVAL); + } + + Graph* G = new Graph; + if (G->convert_from_igraph(graph, weights)) { + IGRAPH_ERROR("Cannot convert igraph graph into walktrap format", IGRAPH_EINVAL); + } + + if (merges) { + igraph_integer_t no; + IGRAPH_CHECK(igraph_clusters(graph, /*membership=*/ 0, /*csize=*/ 0, + &no, IGRAPH_WEAK)); + IGRAPH_CHECK(igraph_matrix_resize(merges, no_of_nodes - no, 2)); + } + if (modularity) { + IGRAPH_CHECK(igraph_vector_resize(modularity, no_of_nodes)); + igraph_vector_null(modularity); + } + Communities C(G, length, max_memory, merges, modularity); + + while (!C.H->is_empty()) { + IGRAPH_ALLOW_INTERRUPTION(); + C.merge_nearest_communities(); + } + + delete G; + + if (membership) { + long int m = igraph_vector_which_max(modularity); + IGRAPH_CHECK(igraph_community_to_membership(merges, no_of_nodes, + /*steps=*/ m, + membership, + /*csize=*/ 0)); + } + + return 0; +} diff --git a/src/walktrap_communities.cpp b/src/walktrap_communities.cpp new file mode 100644 index 0000000..9cb474c --- /dev/null +++ b/src/walktrap_communities.cpp @@ -0,0 +1,936 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: communities.cpp +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +#include "walktrap_communities.h" +#include "config.h" +#include +#include + +using namespace std; + +namespace igraph { + +namespace walktrap { + +IGRAPH_THREAD_LOCAL int Probabilities::length = 0; +IGRAPH_THREAD_LOCAL Communities* Probabilities::C = 0; +IGRAPH_THREAD_LOCAL float* Probabilities::tmp_vector1 = 0; +IGRAPH_THREAD_LOCAL float* Probabilities::tmp_vector2 = 0; +IGRAPH_THREAD_LOCAL int* Probabilities::id = 0; +IGRAPH_THREAD_LOCAL int* Probabilities::vertices1 = 0; +IGRAPH_THREAD_LOCAL int* Probabilities::vertices2 = 0; +IGRAPH_THREAD_LOCAL int Probabilities::current_id = 0; + + +Neighbor::Neighbor() { + next_community1 = 0; + previous_community1 = 0; + next_community2 = 0; + previous_community2 = 0; + heap_index = -1; +} + +Probabilities::~Probabilities() { + C->memory_used -= memory(); + if (P) { + delete[] P; + } + if (vertices) { + delete[] vertices; + } +} + +Probabilities::Probabilities(int community) { + Graph* G = C->G; + int nb_vertices1 = 0; + int nb_vertices2 = 0; + + float initial_proba = 1. / float(C->communities[community].size); + int last = C->members[C->communities[community].last_member]; + for (int m = C->communities[community].first_member; m != last; m = C->members[m]) { + tmp_vector1[m] = initial_proba; + vertices1[nb_vertices1++] = m; + } + + for (int t = 0; t < length; t++) { + current_id++; + if (nb_vertices1 > (G->nb_vertices / 2)) { + nb_vertices2 = G->nb_vertices; + for (int i = 0; i < G->nb_vertices; i++) { + tmp_vector2[i] = 0.; + } + if (nb_vertices1 == G->nb_vertices) { + for (int i = 0; i < G->nb_vertices; i++) { + float proba = tmp_vector1[i] / G->vertices[i].total_weight; + for (int j = 0; j < G->vertices[i].degree; j++) { + tmp_vector2[G->vertices[i].edges[j].neighbor] += proba * G->vertices[i].edges[j].weight; + } + } + } else { + for (int i = 0; i < nb_vertices1; i++) { + int v1 = vertices1[i]; + float proba = tmp_vector1[v1] / G->vertices[v1].total_weight; + for (int j = 0; j < G->vertices[v1].degree; j++) { + tmp_vector2[G->vertices[v1].edges[j].neighbor] += proba * G->vertices[v1].edges[j].weight; + } + } + } + } else { + nb_vertices2 = 0; + for (int i = 0; i < nb_vertices1; i++) { + int v1 = vertices1[i]; + float proba = tmp_vector1[v1] / G->vertices[v1].total_weight; + for (int j = 0; j < G->vertices[v1].degree; j++) { + int v2 = G->vertices[v1].edges[j].neighbor; + if (id[v2] == current_id) { + tmp_vector2[v2] += proba * G->vertices[v1].edges[j].weight; + } else { + tmp_vector2[v2] = proba * G->vertices[v1].edges[j].weight; + id[v2] = current_id; + vertices2[nb_vertices2++] = v2; + } + } + } + } + float* tmp = tmp_vector2; + tmp_vector2 = tmp_vector1; + tmp_vector1 = tmp; + + int* tmp2 = vertices2; + vertices2 = vertices1; + vertices1 = tmp2; + + nb_vertices1 = nb_vertices2; + } + + if (nb_vertices1 > (G->nb_vertices / 2)) { + P = new float[G->nb_vertices]; + size = G->nb_vertices; + vertices = 0; + if (nb_vertices1 == G->nb_vertices) { + for (int i = 0; i < G->nb_vertices; i++) { + P[i] = tmp_vector1[i] / sqrt(G->vertices[i].total_weight); + } + } else { + for (int i = 0; i < G->nb_vertices; i++) { + P[i] = 0.; + } + for (int i = 0; i < nb_vertices1; i++) { + P[vertices1[i]] = tmp_vector1[vertices1[i]] / sqrt(G->vertices[vertices1[i]].total_weight); + } + } + } else { + P = new float[nb_vertices1]; + size = nb_vertices1; + vertices = new int[nb_vertices1]; + int j = 0; + for (int i = 0; i < G->nb_vertices; i++) { + if (id[i] == current_id) { + P[j] = tmp_vector1[i] / sqrt(G->vertices[i].total_weight); + vertices[j] = i; + j++; + } + } + } + C->memory_used += memory(); +} + +Probabilities::Probabilities(int community1, int community2) { + // The two following probability vectors must exist. + // Do not call this function if it is not the case. + Probabilities* P1 = C->communities[community1].P; + Probabilities* P2 = C->communities[community2].P; + + float w1 = float(C->communities[community1].size) / float(C->communities[community1].size + C->communities[community2].size); + float w2 = float(C->communities[community2].size) / float(C->communities[community1].size + C->communities[community2].size); + + + if (P1->size == C->G->nb_vertices) { + P = new float[C->G->nb_vertices]; + size = C->G->nb_vertices; + vertices = 0; + + if (P2->size == C->G->nb_vertices) { // two full vectors + for (int i = 0; i < C->G->nb_vertices; i++) { + P[i] = P1->P[i] * w1 + P2->P[i] * w2; + } + } else { // P1 full vector, P2 partial vector + int j = 0; + for (int i = 0; i < P2->size; i++) { + for (; j < P2->vertices[i]; j++) { + P[j] = P1->P[j] * w1; + } + P[j] = P1->P[j] * w1 + P2->P[i] * w2; + j++; + } + for (; j < C->G->nb_vertices; j++) { + P[j] = P1->P[j] * w1; + } + } + } else { + if (P2->size == C->G->nb_vertices) { // P1 partial vector, P2 full vector + P = new float[C->G->nb_vertices]; + size = C->G->nb_vertices; + vertices = 0; + + int j = 0; + for (int i = 0; i < P1->size; i++) { + for (; j < P1->vertices[i]; j++) { + P[j] = P2->P[j] * w2; + } + P[j] = P1->P[i] * w1 + P2->P[j] * w2; + j++; + } + for (; j < C->G->nb_vertices; j++) { + P[j] = P2->P[j] * w2; + } + } else { // two partial vectors + int i = 0; + int j = 0; + int nb_vertices1 = 0; + while ((i < P1->size) && (j < P2->size)) { + if (P1->vertices[i] < P2->vertices[j]) { + tmp_vector1[P1->vertices[i]] = P1->P[i] * w1; + vertices1[nb_vertices1++] = P1->vertices[i]; + i++; + continue; + } + if (P1->vertices[i] > P2->vertices[j]) { + tmp_vector1[P2->vertices[j]] = P2->P[j] * w2; + vertices1[nb_vertices1++] = P2->vertices[j]; + j++; + continue; + } + tmp_vector1[P1->vertices[i]] = P1->P[i] * w1 + P2->P[j] * w2; + vertices1[nb_vertices1++] = P1->vertices[i]; + i++; + j++; + } + if (i == P1->size) { + for (; j < P2->size; j++) { + tmp_vector1[P2->vertices[j]] = P2->P[j] * w2; + vertices1[nb_vertices1++] = P2->vertices[j]; + } + } else { + for (; i < P1->size; i++) { + tmp_vector1[P1->vertices[i]] = P1->P[i] * w1; + vertices1[nb_vertices1++] = P1->vertices[i]; + } + } + + if (nb_vertices1 > (C->G->nb_vertices / 2)) { + P = new float[C->G->nb_vertices]; + size = C->G->nb_vertices; + vertices = 0; + for (int i = 0; i < C->G->nb_vertices; i++) { + P[i] = 0.; + } + for (int i = 0; i < nb_vertices1; i++) { + P[vertices1[i]] = tmp_vector1[vertices1[i]]; + } + } else { + P = new float[nb_vertices1]; + size = nb_vertices1; + vertices = new int[nb_vertices1]; + for (int i = 0; i < nb_vertices1; i++) { + vertices[i] = vertices1[i]; + P[i] = tmp_vector1[vertices1[i]]; + } + } + } + } + + C->memory_used += memory(); +} + +double Probabilities::compute_distance(const Probabilities* P2) const { + double r = 0.; + if (vertices) { + if (P2->vertices) { // two partial vectors + int i = 0; + int j = 0; + while ((i < size) && (j < P2->size)) { + if (vertices[i] < P2->vertices[j]) { + r += P[i] * P[i]; + i++; + continue; + } + if (vertices[i] > P2->vertices[j]) { + r += P2->P[j] * P2->P[j]; + j++; + continue; + } + r += (P[i] - P2->P[j]) * (P[i] - P2->P[j]); + i++; + j++; + } + if (i == size) { + for (; j < P2->size; j++) { + r += P2->P[j] * P2->P[j]; + } + } else { + for (; i < size; i++) { + r += P[i] * P[i]; + } + } + } else { // P1 partial vector, P2 full vector + + int i = 0; + for (int j = 0; j < size; j++) { + for (; i < vertices[j]; i++) { + r += P2->P[i] * P2->P[i]; + } + r += (P[j] - P2->P[i]) * (P[j] - P2->P[i]); + i++; + } + for (; i < P2->size; i++) { + r += P2->P[i] * P2->P[i]; + } + } + } else { + if (P2->vertices) { // P1 full vector, P2 partial vector + int i = 0; + for (int j = 0; j < P2->size; j++) { + for (; i < P2->vertices[j]; i++) { + r += P[i] * P[i]; + } + r += (P[i] - P2->P[j]) * (P[i] - P2->P[j]); + i++; + } + for (; i < size; i++) { + r += P[i] * P[i]; + } + } else { // two full vectors + for (int i = 0; i < size; i++) { + r += (P[i] - P2->P[i]) * (P[i] - P2->P[i]); + } + } + } + return r; +} + +long Probabilities::memory() { + if (vertices) { + return (sizeof(Probabilities) + long(size) * (sizeof(float) + sizeof(int))); + } else { + return (sizeof(Probabilities) + long(size) * sizeof(float)); + } +} + +Community::Community() { + P = 0; + first_neighbor = 0; + last_neighbor = 0; + sub_community_of = -1; + sub_communities[0] = -1; + sub_communities[1] = -1; + sigma = 0.; + internal_weight = 0.; + total_weight = 0.; +} + +Community::~Community() { + if (P) { + delete P; + } +} + + +Communities::Communities(Graph* graph, int random_walks_length, + long m, igraph_matrix_t *pmerges, + igraph_vector_t *pmodularity) { + max_memory = m; + memory_used = 0; + G = graph; + merges = pmerges; + mergeidx = 0; + modularity = pmodularity; + + Probabilities::C = this; + Probabilities::length = random_walks_length; + Probabilities::tmp_vector1 = new float[G->nb_vertices]; + Probabilities::tmp_vector2 = new float[G->nb_vertices]; + Probabilities::id = new int[G->nb_vertices]; + for (int i = 0; i < G->nb_vertices; i++) { + Probabilities::id[i] = 0; + } + Probabilities::vertices1 = new int[G->nb_vertices]; + Probabilities::vertices2 = new int[G->nb_vertices]; + Probabilities::current_id = 0; + + + members = new int[G->nb_vertices]; + for (int i = 0; i < G->nb_vertices; i++) { + members[i] = -1; + } + + H = new Neighbor_heap(G->nb_edges); + communities = new Community[2 * G->nb_vertices]; + +// init the n single vertex communities + + if (max_memory != -1) { + min_delta_sigma = new Min_delta_sigma_heap(G->nb_vertices * 2); + } else { + min_delta_sigma = 0; + } + + for (int i = 0; i < G->nb_vertices; i++) { + communities[i].this_community = i; + communities[i].first_member = i; + communities[i].last_member = i; + communities[i].size = 1; + communities[i].sub_community_of = 0; + } + + nb_communities = G->nb_vertices; + nb_active_communities = G->nb_vertices; + + for (int i = 0; i < G->nb_vertices; i++) + for (int j = 0; j < G->vertices[i].degree; j++) + if (i < G->vertices[i].edges[j].neighbor) { + communities[i].total_weight += G->vertices[i].edges[j].weight / 2.; + communities[G->vertices[i].edges[j].neighbor].total_weight += G->vertices[i].edges[j].weight / 2.; + Neighbor* N = new Neighbor; + N->community1 = i; + N->community2 = G->vertices[i].edges[j].neighbor; + N->delta_sigma = -1. / double(min(G->vertices[i].degree, G->vertices[G->vertices[i].edges[j].neighbor].degree)); + N->weight = G->vertices[i].edges[j].weight; + N->exact = false; + add_neighbor(N); + } + + if (max_memory != -1) { + memory_used += min_delta_sigma->memory(); + memory_used += 2 * long(G->nb_vertices) * sizeof(Community); + memory_used += long(G->nb_vertices) * (2 * sizeof(float) + 3 * sizeof(int)); // the static data of Probabilities class + memory_used += H->memory() + long(G->nb_edges) * sizeof(Neighbor); + memory_used += G->memory(); + } + + /* int c = 0; */ + Neighbor* N = H->get_first(); + if (N == 0) { + return; /* this can happen if there are no edges */ + } + while (!N->exact) { + update_neighbor(N, compute_delta_sigma(N->community1, N->community2)); + N->exact = true; + N = H->get_first(); + if (max_memory != -1) { + manage_memory(); + } + /* TODO: this could use igraph_progress */ + /* if(!silent) { */ + /* c++; */ + /* for(int k = (500*(c-1))/G->nb_edges + 1; k <= (500*c)/G->nb_edges; k++) { */ + /* if(k % 50 == 1) {cerr.width(2); cerr << endl << k/ 5 << "% ";} */ + /* cerr << "."; */ + /* } */ + /* } */ + } + +} + +Communities::~Communities() { + delete[] members; + delete[] communities; + delete H; + if (min_delta_sigma) { + delete min_delta_sigma; + } + + delete[] Probabilities::tmp_vector1; + delete[] Probabilities::tmp_vector2; + delete[] Probabilities::id; + delete[] Probabilities::vertices1; + delete[] Probabilities::vertices2; +} + +float Community::min_delta_sigma() { + float r = 1.; + for (Neighbor* N = first_neighbor; N != 0;) { + if (N->delta_sigma < r) { + r = N->delta_sigma; + } + if (N->community1 == this_community) { + N = N->next_community1; + } else { + N = N->next_community2; + } + } + return r; +} + + +void Community::add_neighbor(Neighbor* N) { // add a new neighbor at the end of the list + if (last_neighbor) { + if (last_neighbor->community1 == this_community) { + last_neighbor->next_community1 = N; + } else { + last_neighbor->next_community2 = N; + } + + if (N->community1 == this_community) { + N->previous_community1 = last_neighbor; + } else { + N->previous_community2 = last_neighbor; + } + } else { + first_neighbor = N; + if (N->community1 == this_community) { + N->previous_community1 = 0; + } else { + N->previous_community2 = 0; + } + } + last_neighbor = N; +} + +void Community::remove_neighbor(Neighbor* N) { // remove a neighbor from the list + if (N->community1 == this_community) { + if (N->next_community1) { +// if (N->next_community1->community1 == this_community) + N->next_community1->previous_community1 = N->previous_community1; +// else +// N->next_community1->previous_community2 = N->previous_community1; + } else { + last_neighbor = N->previous_community1; + } + if (N->previous_community1) { + if (N->previous_community1->community1 == this_community) { + N->previous_community1->next_community1 = N->next_community1; + } else { + N->previous_community1->next_community2 = N->next_community1; + } + } else { + first_neighbor = N->next_community1; + } + } else { + if (N->next_community2) { + if (N->next_community2->community1 == this_community) { + N->next_community2->previous_community1 = N->previous_community2; + } else { + N->next_community2->previous_community2 = N->previous_community2; + } + } else { + last_neighbor = N->previous_community2; + } + if (N->previous_community2) { +// if (N->previous_community2->community1 == this_community) +// N->previous_community2->next_community1 = N->next_community2; +// else + N->previous_community2->next_community2 = N->next_community2; + } else { + first_neighbor = N->next_community2; + } + } +} + +void Communities::remove_neighbor(Neighbor* N) { + communities[N->community1].remove_neighbor(N); + communities[N->community2].remove_neighbor(N); + H->remove(N); + + if (max_memory != -1) { + if (N->delta_sigma == min_delta_sigma->delta_sigma[N->community1]) { + min_delta_sigma->delta_sigma[N->community1] = communities[N->community1].min_delta_sigma(); + if (communities[N->community1].P) { + min_delta_sigma->update(N->community1); + } + } + + if (N->delta_sigma == min_delta_sigma->delta_sigma[N->community2]) { + min_delta_sigma->delta_sigma[N->community2] = communities[N->community2].min_delta_sigma(); + if (communities[N->community2].P) { + min_delta_sigma->update(N->community2); + } + } + } +} + +void Communities::add_neighbor(Neighbor* N) { + communities[N->community1].add_neighbor(N); + communities[N->community2].add_neighbor(N); + H->add(N); + + if (max_memory != -1) { + if (N->delta_sigma < min_delta_sigma->delta_sigma[N->community1]) { + min_delta_sigma->delta_sigma[N->community1] = N->delta_sigma; + if (communities[N->community1].P) { + min_delta_sigma->update(N->community1); + } + } + + if (N->delta_sigma < min_delta_sigma->delta_sigma[N->community2]) { + min_delta_sigma->delta_sigma[N->community2] = N->delta_sigma; + if (communities[N->community2].P) { + min_delta_sigma->update(N->community2); + } + } + } +} + +void Communities::update_neighbor(Neighbor* N, float new_delta_sigma) { + if (max_memory != -1) { + if (new_delta_sigma < min_delta_sigma->delta_sigma[N->community1]) { + min_delta_sigma->delta_sigma[N->community1] = new_delta_sigma; + if (communities[N->community1].P) { + min_delta_sigma->update(N->community1); + } + } + + if (new_delta_sigma < min_delta_sigma->delta_sigma[N->community2]) { + min_delta_sigma->delta_sigma[N->community2] = new_delta_sigma; + if (communities[N->community2].P) { + min_delta_sigma->update(N->community2); + } + } + + float old_delta_sigma = N->delta_sigma; + N->delta_sigma = new_delta_sigma; + H->update(N); + + if (old_delta_sigma == min_delta_sigma->delta_sigma[N->community1]) { + min_delta_sigma->delta_sigma[N->community1] = communities[N->community1].min_delta_sigma(); + if (communities[N->community1].P) { + min_delta_sigma->update(N->community1); + } + } + + if (old_delta_sigma == min_delta_sigma->delta_sigma[N->community2]) { + min_delta_sigma->delta_sigma[N->community2] = communities[N->community2].min_delta_sigma(); + if (communities[N->community2].P) { + min_delta_sigma->update(N->community2); + } + } + } else { + N->delta_sigma = new_delta_sigma; + H->update(N); + } +} + +void Communities::manage_memory() { + while ((memory_used > max_memory) && !min_delta_sigma->is_empty()) { + int c = min_delta_sigma->get_max_community(); + delete communities[c].P; + communities[c].P = 0; + min_delta_sigma->remove_community(c); + } +} + + + +void Communities::merge_communities(Neighbor* merge_N) { + int c1 = merge_N->community1; + int c2 = merge_N->community2; + + communities[nb_communities].first_member = communities[c1].first_member; // merge the + communities[nb_communities].last_member = communities[c2].last_member; // two lists + members[communities[c1].last_member] = communities[c2].first_member; // of members + + communities[nb_communities].size = communities[c1].size + communities[c2].size; + communities[nb_communities].this_community = nb_communities; + communities[nb_communities].sub_community_of = 0; + communities[nb_communities].sub_communities[0] = c1; + communities[nb_communities].sub_communities[1] = c2; + communities[nb_communities].total_weight = communities[c1].total_weight + communities[c2].total_weight; + communities[nb_communities].internal_weight = communities[c1].internal_weight + communities[c2].internal_weight + merge_N->weight; + communities[nb_communities].sigma = communities[c1].sigma + communities[c2].sigma + merge_N->delta_sigma; + + communities[c1].sub_community_of = nb_communities; + communities[c2].sub_community_of = nb_communities; + +// update the new probability vector... + + if (communities[c1].P && communities[c2].P) { + communities[nb_communities].P = new Probabilities(c1, c2); + } + + if (communities[c1].P) { + delete communities[c1].P; + communities[c1].P = 0; + if (max_memory != -1) { + min_delta_sigma->remove_community(c1); + } + } + if (communities[c2].P) { + delete communities[c2].P; + communities[c2].P = 0; + if (max_memory != -1) { + min_delta_sigma->remove_community(c2); + } + } + + if (max_memory != -1) { + min_delta_sigma->delta_sigma[c1] = -1.; // to avoid to update the min_delta_sigma for these communities + min_delta_sigma->delta_sigma[c2] = -1.; // + min_delta_sigma->delta_sigma[nb_communities] = -1.; + } + +// update the new neighbors +// by enumerating all the neighbors of c1 and c2 + + Neighbor* N1 = communities[c1].first_neighbor; + Neighbor* N2 = communities[c2].first_neighbor; + + while (N1 && N2) { + int neighbor_community1; + int neighbor_community2; + + if (N1->community1 == c1) { + neighbor_community1 = N1->community2; + } else { + neighbor_community1 = N1->community1; + } + if (N2->community1 == c2) { + neighbor_community2 = N2->community2; + } else { + neighbor_community2 = N2->community1; + } + + if (neighbor_community1 < neighbor_community2) { + Neighbor* tmp = N1; + if (N1->community1 == c1) { + N1 = N1->next_community1; + } else { + N1 = N1->next_community2; + } + remove_neighbor(tmp); + Neighbor* N = new Neighbor; + N->weight = tmp->weight; + N->community1 = neighbor_community1; + N->community2 = nb_communities; + N->delta_sigma = (double(communities[c1].size + communities[neighbor_community1].size) * tmp->delta_sigma + double(communities[c2].size) * merge_N->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community1].size)); //compute_delta_sigma(neighbor_community1, nb_communities); + N->exact = false; + delete tmp; + add_neighbor(N); + } + + if (neighbor_community2 < neighbor_community1) { + Neighbor* tmp = N2; + if (N2->community1 == c2) { + N2 = N2->next_community1; + } else { + N2 = N2->next_community2; + } + remove_neighbor(tmp); + Neighbor* N = new Neighbor; + N->weight = tmp->weight; + N->community1 = neighbor_community2; + N->community2 = nb_communities; + N->delta_sigma = (double(communities[c1].size) * merge_N->delta_sigma + double(communities[c2].size + communities[neighbor_community2].size) * tmp->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community2].size)); //compute_delta_sigma(neighbor_community2, nb_communities); + N->exact = false; + delete tmp; + add_neighbor(N); + } + + if (neighbor_community1 == neighbor_community2) { + Neighbor* tmp1 = N1; + Neighbor* tmp2 = N2; + bool exact = N1->exact && N2->exact; + if (N1->community1 == c1) { + N1 = N1->next_community1; + } else { + N1 = N1->next_community2; + } + if (N2->community1 == c2) { + N2 = N2->next_community1; + } else { + N2 = N2->next_community2; + } + remove_neighbor(tmp1); + remove_neighbor(tmp2); + Neighbor* N = new Neighbor; + N->weight = tmp1->weight + tmp2->weight; + N->community1 = neighbor_community1; + N->community2 = nb_communities; + N->delta_sigma = (double(communities[c1].size + communities[neighbor_community1].size) * tmp1->delta_sigma + double(communities[c2].size + communities[neighbor_community1].size) * tmp2->delta_sigma - double(communities[neighbor_community1].size) * merge_N->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community1].size)); + N->exact = exact; + delete tmp1; + delete tmp2; + add_neighbor(N); + } + } + + + if (!N1) { + while (N2) { +// double delta_sigma2 = N2->delta_sigma; + int neighbor_community; + if (N2->community1 == c2) { + neighbor_community = N2->community2; + } else { + neighbor_community = N2->community1; + } + Neighbor* tmp = N2; + if (N2->community1 == c2) { + N2 = N2->next_community1; + } else { + N2 = N2->next_community2; + } + remove_neighbor(tmp); + Neighbor* N = new Neighbor; + N->weight = tmp->weight; + N->community1 = neighbor_community; + N->community2 = nb_communities; + N->delta_sigma = (double(communities[c1].size) * merge_N->delta_sigma + double(communities[c2].size + communities[neighbor_community].size) * tmp->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community].size)); //compute_delta_sigma(neighbor_community, nb_communities); + N->exact = false; + delete tmp; + add_neighbor(N); + } + } + if (!N2) { + while (N1) { +// double delta_sigma1 = N1->delta_sigma; + int neighbor_community; + if (N1->community1 == c1) { + neighbor_community = N1->community2; + } else { + neighbor_community = N1->community1; + } + Neighbor* tmp = N1; + if (N1->community1 == c1) { + N1 = N1->next_community1; + } else { + N1 = N1->next_community2; + } + remove_neighbor(tmp); + Neighbor* N = new Neighbor; + N->weight = tmp->weight; + N->community1 = neighbor_community; + N->community2 = nb_communities; + N->delta_sigma = (double(communities[c1].size + communities[neighbor_community].size) * tmp->delta_sigma + double(communities[c2].size) * merge_N->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community].size)); //compute_delta_sigma(neighbor_community, nb_communities); + N->exact = false; + delete tmp; + add_neighbor(N); + } + } + + if (max_memory != -1) { + min_delta_sigma->delta_sigma[nb_communities] = communities[nb_communities].min_delta_sigma(); + min_delta_sigma->update(nb_communities); + } + + nb_communities++; + nb_active_communities--; +} + +double Communities::merge_nearest_communities() { + Neighbor* N = H->get_first(); + while (!N->exact) { + update_neighbor(N, compute_delta_sigma(N->community1, N->community2)); + N->exact = true; + N = H->get_first(); + if (max_memory != -1) { + manage_memory(); + } + } + + double d = N->delta_sigma; + remove_neighbor(N); + + merge_communities(N); + if (max_memory != -1) { + manage_memory(); + } + + if (merges) { + MATRIX(*merges, mergeidx, 0) = N->community1; + MATRIX(*merges, mergeidx, 1) = N->community2; + mergeidx++; + } + + if (modularity) { + float Q = 0.; + for (int i = 0; i < nb_communities; i++) { + if (communities[i].sub_community_of == 0) { + Q += (communities[i].internal_weight - communities[i].total_weight * communities[i].total_weight / G->total_weight) / G->total_weight; + } + } + VECTOR(*modularity)[mergeidx] = Q; + } + + delete N; + + /* This could use igraph_progress */ + /* if(!silent) { */ + /* for(int k = (500*(G->nb_vertices - nb_active_communities - 1))/(G->nb_vertices-1) + 1; k <= (500*(G->nb_vertices - nb_active_communities))/(G->nb_vertices-1); k++) { */ + /* if(k % 50 == 1) {cerr.width(2); cerr << endl << k/ 5 << "% ";} */ + /* cerr << "."; */ + /* } */ + /* } */ + return d; +} + +double Communities::compute_delta_sigma(int community1, int community2) { + if (!communities[community1].P) { + communities[community1].P = new Probabilities(community1); + if (max_memory != -1) { + min_delta_sigma->update(community1); + } + } + if (!communities[community2].P) { + communities[community2].P = new Probabilities(community2); + if (max_memory != -1) { + min_delta_sigma->update(community2); + } + } + + return communities[community1].P->compute_distance(communities[community2].P) * double(communities[community1].size) * double(communities[community2].size) / double(communities[community1].size + communities[community2].size); +} + +} +} /* end of namespaces */ diff --git a/src/walktrap_communities.h b/src/walktrap_communities.h new file mode 100644 index 0000000..690d7aa --- /dev/null +++ b/src/walktrap_communities.h @@ -0,0 +1,175 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: communities.h +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + + +#ifndef WALKTRAP_COMMUNITIES_H +#define WALKTRAP_COMMUNITIES_H + +#include "walktrap_graph.h" +#include "walktrap_heap.h" +#include "igraph_community.h" +#include "config.h" + +namespace igraph { + +namespace walktrap { + +class Communities; +class Probabilities { +public: + static IGRAPH_THREAD_LOCAL float* tmp_vector1; // + static IGRAPH_THREAD_LOCAL float* tmp_vector2; // + static IGRAPH_THREAD_LOCAL int* id; // + static IGRAPH_THREAD_LOCAL int* vertices1; // + static IGRAPH_THREAD_LOCAL int* vertices2; // + static IGRAPH_THREAD_LOCAL int current_id; // + + static IGRAPH_THREAD_LOCAL Communities* C; // pointer to all the communities + static IGRAPH_THREAD_LOCAL int length; // length of the random walks + + + int size; // number of probabilities stored + int* vertices; // the vertices corresponding to the stored probabilities, 0 if all the probabilities are stored + float* P; // the probabilities + + long memory(); // the memory (in Bytes) used by the object + double compute_distance(const Probabilities* P2) const; // compute the squared distance r^2 between this probability vector and P2 + Probabilities(int community); // compute the probability vector of a community + Probabilities(int community1, int community2); // merge the probability vectors of two communities in a new one + // the two communities must have their probability vectors stored + + ~Probabilities(); // destructor +}; + +class Community { +public: + + Neighbor* first_neighbor; // first item of the list of adjacent communities + Neighbor* last_neighbor; // last item of the list of adjacent communities + + int this_community; // number of this community + int first_member; // number of the first vertex of the community + int last_member; // number of the last vertex of the community + int size; // number of members of the community + + Probabilities* P; // the probability vector, 0 if not stored. + + + float sigma; // sigma(C) of the community + float internal_weight; // sum of the weight of the internal edges + float total_weight; // sum of the weight of all the edges of the community (an edge between two communities is a half-edge for each community) + + int sub_communities[2]; // the two sub sommunities, -1 if no sub communities; + int sub_community_of; // number of the community in which this community has been merged + // 0 if the community is active + // -1 if the community is not used + + void merge(Community &C1, Community &C2); // create a new community by merging C1 an C2 + void add_neighbor(Neighbor* N); + void remove_neighbor(Neighbor* N); + float min_delta_sigma(); // compute the minimal delta sigma among all the neighbors of this community + + Community(); // create an empty community + ~Community(); // destructor +}; + +class Communities { +private: + long max_memory; // size in Byte of maximal memory usage, -1 for no limit + igraph_matrix_t *merges; + long int mergeidx; + igraph_vector_t *modularity; + +public: + + long memory_used; // in bytes + Min_delta_sigma_heap* min_delta_sigma; // the min delta_sigma of the community with a saved probability vector (for memory management) + + Graph* G; // the graph + int* members; // the members of each community represented as a chained list. + // a community points to the first_member the array which contains + // the next member (-1 = end of the community) + Neighbor_heap* H; // the distances between adjacent communities. + + + Community* communities; // array of the communities + + int nb_communities; // number of valid communities + int nb_active_communities; // number of active communities + + Communities(Graph* G, int random_walks_length = 3, + long max_memory = -1, igraph_matrix_t *merges = 0, + igraph_vector_t *modularity = 0); // Constructor + ~Communities(); // Destructor + + + void merge_communities(Neighbor* N); // create a community by merging two existing communities + double merge_nearest_communities(); + + + double compute_delta_sigma(int c1, int c2); // compute delta_sigma(c1,c2) + + void remove_neighbor(Neighbor* N); + void add_neighbor(Neighbor* N); + void update_neighbor(Neighbor* N, float new_delta_sigma); + + void manage_memory(); + +}; + +} +} /* end of namespaces */ + +#endif // WALKTRAP_COMMUNITIES_H diff --git a/src/walktrap_graph.cpp b/src/walktrap_graph.cpp new file mode 100644 index 0000000..6931d58 --- /dev/null +++ b/src/walktrap_graph.cpp @@ -0,0 +1,250 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: graph.cpp +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +#include "walktrap_graph.h" +#include "igraph_interface.h" +#include +#include // strlen + +using namespace std; + +namespace igraph { + +namespace walktrap { + +bool operator<(const Edge& E1, const Edge& E2) { + return (E1.neighbor < E2.neighbor); +} + + +Vertex::Vertex() { + degree = 0; + edges = 0; + total_weight = 0.; +} + +Vertex::~Vertex() { + if (edges) { + delete[] edges; + } +} + +Graph::Graph() { + nb_vertices = 0; + nb_edges = 0; + vertices = 0; + index = 0; + total_weight = 0.; +} + +Graph::~Graph () { + if (vertices) { + delete[] vertices; + } +} + +class Edge_list { +public: + int* V1; + int* V2; + float* W; + + int size; + int size_max; + + void add(int v1, int v2, float w); + Edge_list() { + size = 0; + size_max = 1024; + V1 = new int[1024]; + V2 = new int[1024]; + W = new float[1024]; + } + ~Edge_list() { + if (V1) { + delete[] V1; + } + if (V2) { + delete[] V2; + } + if (W) { + delete[] W; + } + } +}; + +void Edge_list::add(int v1, int v2, float w) { + if (size == size_max) { + int* tmp1 = new int[2 * size_max]; + int* tmp2 = new int[2 * size_max]; + float* tmp3 = new float[2 * size_max]; + for (int i = 0; i < size_max; i++) { + tmp1[i] = V1[i]; + tmp2[i] = V2[i]; + tmp3[i] = W[i]; + } + delete[] V1; + delete[] V2; + delete[] W; + V1 = tmp1; + V2 = tmp2; + W = tmp3; + size_max *= 2; + } + V1[size] = v1; + V2[size] = v2; + W[size] = w; + size++; +} + +int Graph::convert_from_igraph(const igraph_t *graph, + const igraph_vector_t *weights) { + Graph &G = *this; + + int max_vertex = (int)igraph_vcount(graph) - 1; + long int no_of_edges = (long int)igraph_ecount(graph); + long int i; + long int deg; + double w; + + Edge_list EL; + + for (i = 0; i < no_of_edges; i++) { + igraph_integer_t from, to; + int v1, v2; + w = weights ? VECTOR(*weights)[i] : 1.0; + igraph_edge(graph, i, &from, &to); + v1 = (int)from; v2 = (int)to; + EL.add(v1, v2, w); + } + + G.nb_vertices = max_vertex + 1; + G.vertices = new Vertex[G.nb_vertices]; + G.nb_edges = 0; + G.total_weight = 0.0; + + for (int i = 0; i < EL.size; i++) { + G.vertices[EL.V1[i]].degree++; + G.vertices[EL.V2[i]].degree++; + G.vertices[EL.V1[i]].total_weight += EL.W[i]; + G.vertices[EL.V2[i]].total_weight += EL.W[i]; + G.nb_edges++; + G.total_weight += EL.W[i]; + } + + for (int i = 0; i < G.nb_vertices; i++) { + deg = G.vertices[i].degree; + w = (deg == 0) ? 1.0 : (G.vertices[i].total_weight / double(deg)); + G.vertices[i].edges = new Edge[deg + 1]; + G.vertices[i].edges[0].neighbor = i; + G.vertices[i].edges[0].weight = w; + G.vertices[i].total_weight += w; + G.vertices[i].degree = 1; + } + + for (int i = 0; i < EL.size; i++) { + G.vertices[EL.V1[i]].edges[G.vertices[EL.V1[i]].degree].neighbor = EL.V2[i]; + G.vertices[EL.V1[i]].edges[G.vertices[EL.V1[i]].degree].weight = EL.W[i]; + G.vertices[EL.V1[i]].degree++; + G.vertices[EL.V2[i]].edges[G.vertices[EL.V2[i]].degree].neighbor = EL.V1[i]; + G.vertices[EL.V2[i]].edges[G.vertices[EL.V2[i]].degree].weight = EL.W[i]; + G.vertices[EL.V2[i]].degree++; + } + + for (int i = 0; i < G.nb_vertices; i++) { + sort(G.vertices[i].edges, G.vertices[i].edges + G.vertices[i].degree); + } + + for (int i = 0; i < G.nb_vertices; i++) { // merge multi edges + int a = 0; + for (int b = 1; b < G.vertices[i].degree; b++) { + if (G.vertices[i].edges[b].neighbor == G.vertices[i].edges[a].neighbor) { + G.vertices[i].edges[a].weight += G.vertices[i].edges[b].weight; + } else { + G.vertices[i].edges[++a] = G.vertices[i].edges[b]; + } + } + G.vertices[i].degree = a + 1; + } + + return 0; +} + +long Graph::memory() { + size_t m = 0; + m += size_t(nb_vertices) * sizeof(Vertex); + m += 2 * size_t(nb_edges) * sizeof(Edge); + m += sizeof(Graph); + if (index != 0) { + m += size_t(nb_vertices) * sizeof(char*); + for (int i = 0; i < nb_vertices; i++) { + m += strlen(index[i]) + 1; + } + } + return m; +} + +} +} + + + + + + + + + + diff --git a/src/walktrap_graph.h b/src/walktrap_graph.h new file mode 100644 index 0000000..f82313a --- /dev/null +++ b/src/walktrap_graph.h @@ -0,0 +1,105 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here */ + +// File: graph.h +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +/* FSF address above was fixed by Tamas Nepusz */ + + +#ifndef WALKTRAP_GRAPH_H +#define WALKTRAP_GRAPH_H + +#include "igraph_community.h" + +namespace igraph { + +namespace walktrap { + +class Edge { // code an edge of a given vertex +public: + int neighbor; // the number of the neighbor vertex + float weight; // the weight of the edge +}; +bool operator<(const Edge& E1, const Edge& E2); + + +class Vertex { +public: + Edge* edges; // the edges of the vertex + int degree; // number of neighbors + float total_weight; // the total weight of the vertex + + Vertex(); // creates empty vertex + ~Vertex(); // destructor +}; + +class Graph { +public: + int nb_vertices; // number of vertices + int nb_edges; // number of edges + float total_weight; // total weight of the edges + Vertex* vertices; // array of the vertices + + long memory(); // the total memory used in Bytes + Graph(); // create an empty graph + ~Graph(); // destructor + char** index; // to keep the real name of the vertices + + int convert_from_igraph(const igraph_t * igraph, + const igraph_vector_t *weights); +}; + +} +} /* end of namespaces */ + +#endif // WALKTRAP_GRAPH_H + diff --git a/src/walktrap_heap.cpp b/src/walktrap_heap.cpp new file mode 100644 index 0000000..983cb7b --- /dev/null +++ b/src/walktrap_heap.cpp @@ -0,0 +1,241 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: heap.cpp +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pascal.pons@gmail.com +// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +#include "walktrap_heap.h" + +using namespace igraph::walktrap; + +void Neighbor_heap::move_up(int index) { + while (H[index / 2]->delta_sigma > H[index]->delta_sigma) { + Neighbor* tmp = H[index / 2]; + H[index]->heap_index = index / 2; + H[index / 2] = H[index]; + tmp->heap_index = index; + H[index] = tmp; + index = index / 2; + } +} + +void Neighbor_heap::move_down(int index) { + while (true) { + int min = index; + if ((2 * index < size) && (H[2 * index]->delta_sigma < H[min]->delta_sigma)) { + min = 2 * index; + } + if (2 * index + 1 < size && H[2 * index + 1]->delta_sigma < H[min]->delta_sigma) { + min = 2 * index + 1; + } + if (min != index) { + Neighbor* tmp = H[min]; + H[index]->heap_index = min; + H[min] = H[index]; + tmp->heap_index = index; + H[index] = tmp; + index = min; + } else { + break; + } + } +} + +Neighbor* Neighbor_heap::get_first() { + if (size == 0) { + return 0; + } else { + return H[0]; + } +} + +void Neighbor_heap::remove(Neighbor* N) { + if (N->heap_index == -1 || size == 0) { + return; + } + Neighbor* last_N = H[--size]; + H[N->heap_index] = last_N; + last_N->heap_index = N->heap_index; + move_up(last_N->heap_index); + move_down(last_N->heap_index); + N->heap_index = -1; +} + +void Neighbor_heap::add(Neighbor* N) { + if (size >= max_size) { + return; + } + N->heap_index = size++; + H[N->heap_index] = N; + move_up(N->heap_index); +} + +void Neighbor_heap::update(Neighbor* N) { + if (N->heap_index == -1) { + return; + } + move_up(N->heap_index); + move_down(N->heap_index); +} + +long Neighbor_heap::memory() { + return (sizeof(Neighbor_heap) + long(max_size) * sizeof(Neighbor*)); +} + +Neighbor_heap::Neighbor_heap(int max_s) { + max_size = max_s; + size = 0; + H = new Neighbor*[max_s]; +} + +Neighbor_heap::~Neighbor_heap() { + delete[] H; +} + +bool Neighbor_heap::is_empty() { + return (size == 0); +} + + + +//################################################################# + +void Min_delta_sigma_heap::move_up(int index) { + while (delta_sigma[H[index / 2]] < delta_sigma[H[index]]) { + int tmp = H[index / 2]; + I[H[index]] = index / 2; + H[index / 2] = H[index]; + I[tmp] = index; + H[index] = tmp; + index = index / 2; + } +} + +void Min_delta_sigma_heap::move_down(int index) { + while (true) { + int max = index; + if (2 * index < size && delta_sigma[H[2 * index]] > delta_sigma[H[max]]) { + max = 2 * index; + } + if (2 * index + 1 < size && delta_sigma[H[2 * index + 1]] > delta_sigma[H[max]]) { + max = 2 * index + 1; + } + if (max != index) { + int tmp = H[max]; + I[H[index]] = max; + H[max] = H[index]; + I[tmp] = index; + H[index] = tmp; + index = max; + } else { + break; + } + } +} + +int Min_delta_sigma_heap::get_max_community() { + if (size == 0) { + return -1; + } else { + return H[0]; + } +} + +void Min_delta_sigma_heap::remove_community(int community) { + if (I[community] == -1 || size == 0) { + return; + } + int last_community = H[--size]; + H[I[community]] = last_community; + I[last_community] = I[community]; + move_up(I[last_community]); + move_down(I[last_community]); + I[community] = -1; +} + +void Min_delta_sigma_heap::update(int community) { + if (community < 0 || community >= max_size) { + return; + } + if (I[community] == -1) { + I[community] = size++; + H[I[community]] = community; + } + move_up(I[community]); + move_down(I[community]); +} + +long Min_delta_sigma_heap::memory() { + return (sizeof(Min_delta_sigma_heap) + long(max_size) * (2 * sizeof(int) + sizeof(float))); +} + +Min_delta_sigma_heap::Min_delta_sigma_heap(int max_s) { + max_size = max_s; + size = 0; + H = new int[max_s]; + I = new int[max_s]; + delta_sigma = new float[max_s]; + for (int i = 0; i < max_size; i++) { + I[i] = -1; + delta_sigma[i] = 1.; + } +} + +Min_delta_sigma_heap::~Min_delta_sigma_heap() { + delete[] H; + delete[] I; + delete[] delta_sigma; +} + +bool Min_delta_sigma_heap::is_empty() { + return (size == 0); +} diff --git a/src/walktrap_heap.h b/src/walktrap_heap.h new file mode 100644 index 0000000..e149bf5 --- /dev/null +++ b/src/walktrap_heap.h @@ -0,0 +1,134 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard st, Cambridge, MA, 02138 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* The original version of this file was written by Pascal Pons + The original copyright notice follows here. The FSF address was + fixed by Tamas Nepusz */ + +// File: heap.h +//----------------------------------------------------------------------------- +// Walktrap v0.2 -- Finds community structure of networks using random walks +// Copyright (C) 2004-2005 Pascal Pons +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +// 02110-1301 USA +//----------------------------------------------------------------------------- +// Author : Pascal Pons +// Email : pons@liafa.jussieu.fr +// Web page : http://www.liafa.jussieu.fr/~pons/ +// Location : Paris, France +// Time : June 2005 +//----------------------------------------------------------------------------- +// see readme.txt for more details + +#ifndef WALKTRAP_HEAP_H +#define WALKTRAP_HEAP_H + +namespace igraph { + +namespace walktrap { + +class Neighbor { +public: + int community1; // the two adjacent communities + int community2; // community1 < community2 + + float delta_sigma; // the delta sigma between the two communities + float weight; // the total weight of the edges between the two communities + bool exact; // true if delta_sigma is exact, false if it is only a lower bound + + Neighbor* next_community1; // pointers of two double + Neighbor* previous_community1; // chained lists containing + Neighbor* next_community2; // all the neighbors of + Neighbor* previous_community2; // each communities. + + int heap_index; // + + Neighbor(); +}; + + +class Neighbor_heap { +private: + int size; + int max_size; + + Neighbor** H; // the heap that contains a pointer to each Neighbor object stored + + void move_up(int index); + void move_down(int index); + +public: + void add(Neighbor* N); // add a new distance + void update(Neighbor* N); // update a distance + void remove(Neighbor* N); // remove a distance + Neighbor* get_first(); // get the first item + long memory(); + bool is_empty(); + + Neighbor_heap(int max_size); + ~Neighbor_heap(); +}; + + +class Min_delta_sigma_heap { +private: + int size; + int max_size; + + int* H; // the heap that contains the number of each community + int* I; // the index of each community in the heap (-1 = not stored) + + void move_up(int index); + void move_down(int index); + +public: + int get_max_community(); // return the community with the maximal delta_sigma + void remove_community(int community); // remove a community; + void update(int community); // update (or insert if necessary) the community + long memory(); // the memory used in Bytes. + bool is_empty(); + + float* delta_sigma; // the delta_sigma of the stored communities + + Min_delta_sigma_heap(int max_size); + ~Min_delta_sigma_heap(); +}; + +} +} /* end of namespaces */ + +#endif // WALKTRAP_HEAP_H + diff --git a/src/zeroin.c b/src/zeroin.c new file mode 100644 index 0000000..e248f54 --- /dev/null +++ b/src/zeroin.c @@ -0,0 +1,203 @@ +/* -*- mode: C -*- */ +/* + IGraph library. + Copyright (C) 2007-2012 Gabor Csardi + 334 Harvard street, Cambridge, MA 02139 USA + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + +*/ + +/* from GNU R's zeroin.c, minor modifications by Gabor Csardi */ + +/* from NETLIB c/brent.shar with max.iter, add'l info and convergence + details hacked in by Peter Dalgaard */ + +/************************************************************************* + * C math library + * function ZEROIN - obtain a function zero within the given range + * + * Input + * double zeroin(ax,bx,f,info,Tol,Maxit) + * double ax; Root will be seeked for within + * double bx; a range [ax,bx] + * double (*f)(double x, void *info); Name of the function whose zero + * will be seeked for + * void *info; Add'l info passed to f + * double *Tol; Acceptable tolerance for the root + * value. + * May be specified as 0.0 to cause + * the program to find the root as + * accurate as possible + * + * int *Maxit; Max. iterations + * + * + * Output + * Zeroin returns an estimate for the root with accuracy + * 4*EPSILON*abs(x) + tol + * *Tol returns estimated precision + * *Maxit returns actual # of iterations, or -1 if maxit was + * reached without convergence. + * + * Algorithm + * G.Forsythe, M.Malcolm, C.Moler, Computer methods for mathematical + * computations. M., Mir, 1980, p.180 of the Russian edition + * + * The function makes use of the bisection procedure combined with + * the linear or quadric inverse interpolation. + * At every step program operates on three abscissae - a, b, and c. + * b - the last and the best approximation to the root + * a - the last but one approximation + * c - the last but one or even earlier approximation than a that + * 1) |f(b)| <= |f(c)| + * 2) f(b) and f(c) have opposite signs, i.e. b and c confine + * the root + * At every step Zeroin selects one of the two new approximations, the + * former being obtained by the bisection procedure and the latter + * resulting in the interpolation (if a,b, and c are all different + * the quadric interpolation is utilized, otherwise the linear one). + * If the latter (i.e. obtained by the interpolation) point is + * reasonable (i.e. lies within the current interval [b,c] not being + * too close to the boundaries) it is accepted. The bisection result + * is used in the other case. Therefore, the range of uncertainty is + * ensured to be reduced at least by the factor 1.6 + * + ************************************************************************ + */ + +#include "igraph_types.h" +#include "igraph_interrupt_internal.h" + +#include +#include + +#define EPSILON DBL_EPSILON + +int igraph_zeroin( /* An estimate of the root */ + igraph_real_t *ax, /* Left border | of the range */ + igraph_real_t *bx, /* Right border| the root is seeked*/ + igraph_real_t (*f)(igraph_real_t x, void *info), /* Function under investigation */ + void *info, /* Add'l info passed on to f */ + igraph_real_t *Tol, /* Acceptable tolerance */ + int *Maxit, /* Max # of iterations */ + igraph_real_t *res) { /* Result is stored here */ + igraph_real_t a, b, c, /* Abscissae, descr. see above */ + fa, fb, fc; /* f(a), f(b), f(c) */ + igraph_real_t tol; + int maxit; + + a = *ax; b = *bx; fa = (*f)(a, info); fb = (*f)(b, info); + c = a; fc = fa; + maxit = *Maxit + 1; tol = * Tol; + + /* First test if we have found a root at an endpoint */ + if (fa == 0.0) { + *Tol = 0.0; + *Maxit = 0; + *res = a; + return 0; + } + if (fb == 0.0) { + *Tol = 0.0; + *Maxit = 0; + *res = b; + return 0; + } + + while (maxit--) { /* Main iteration loop */ + igraph_real_t prev_step = b - a; /* Distance from the last but one + to the last approximation */ + igraph_real_t tol_act; /* Actual tolerance */ + igraph_real_t p; /* Interpolation step is calcu- */ + igraph_real_t q; /* lated in the form p/q; divi- + * sion operations is delayed + * until the last moment */ + igraph_real_t new_step; /* Step at this iteration */ + + IGRAPH_ALLOW_INTERRUPTION(); + + if ( fabs(fc) < fabs(fb) ) { + /* Swap data for b to be the */ + a = b; b = c; c = a; /* best approximation */ + fa = fb; fb = fc; fc = fa; + } + tol_act = 2 * EPSILON * fabs(b) + tol / 2; + new_step = (c - b) / 2; + + if ( fabs(new_step) <= tol_act || fb == (igraph_real_t)0 ) { + *Maxit -= maxit; + *Tol = fabs(c - b); + *res = b; + return 0; /* Acceptable approx. is found */ + } + + /* Decide if the interpolation can be tried */ + if ( fabs(prev_step) >= tol_act /* If prev_step was large enough*/ + && fabs(fa) > fabs(fb) ) { + /* and was in true direction, + * Interpolation may be tried */ + register igraph_real_t t1, cb, t2; + cb = c - b; + if ( a == c ) { /* If we have only two distinct */ + /* points linear interpolation */ + t1 = fb / fa; /* can only be applied */ + p = cb * t1; + q = 1.0 - t1; + } else { /* Quadric inverse interpolation*/ + + q = fa / fc; t1 = fb / fc; t2 = fb / fa; + p = t2 * ( cb * q * (q - t1) - (b - a) * (t1 - 1.0) ); + q = (q - 1.0) * (t1 - 1.0) * (t2 - 1.0); + } + if ( p > (igraph_real_t)0 ) { /* p was calculated with the */ + q = -q; /* opposite sign; make p positive */ + } else { /* and assign possible minus to */ + p = -p; /* q */ + } + + if ( p < (0.75 * cb * q - fabs(tol_act * q) / 2) /* If b+p/q falls in [b,c]*/ + && p < fabs(prev_step * q / 2) ) { /* and isn't too large */ + new_step = p / q; + } /* it is accepted + * If p/q is too large then the + * bisection procedure can + * reduce [b,c] range to more + * extent */ + } + + if ( fabs(new_step) < tol_act) { /* Adjust the step to be not less*/ + if ( new_step > (igraph_real_t)0 ) { /* than tolerance */ + new_step = tol_act; + } else { + new_step = -tol_act; + } + } + a = b; fa = fb; /* Save the previous approx. */ + b += new_step; fb = (*f)(b, info); /* Do step to a new approxim. */ + if ( (fb > 0 && fc > 0) || (fb < 0 && fc < 0) ) { + /* Adjust c for it to have a sign opposite to that of b */ + c = a; fc = fa; + } + + } + /* failed! */ + *Tol = fabs(c - b); + *Maxit = -1; + *res = b; + return IGRAPH_DIVERGED; +} + diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..d350f11 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,44 @@ + +EXTRA_DIST = $(TESTSUITE_AT) $(top_builddir)/tests/testsuite + +MAINTAINERCLEANFILES = Makefile.in $(TESTSUITE) package.m4 atconfig + +$(srcdir)/package.m4: $(top_srcdir)/configure.ac + { \ + echo '# Signature of the current package.'; \ + echo 'm4_define([AT_PACKAGE_NAME], [@PACKAGE_NAME@])'; \ + echo 'm4_define([AT_PACKAGE_TARNAME], [@PACKAGE_TARNAME@])'; \ + echo 'm4_define([AT_PACKAGE_VERSION], [@PACKAGE_VERSION@])'; \ + echo 'm4_define([AT_PACKAGE_STRING], [@PACKAGE_STRING@])'; \ + echo 'm4_define([AT_PACKAGE_BUGREPORT], [@PACKAGE_BUGREPORT@])'; \ + } >$(srcdir)/package.m4 + +EXTRA_DIST += package.m4 + +TESTSUITE_AT = \ + testsuite.at \ + types.at basic.at structure_generators.at \ + structural_properties.at iterators.at components.at \ + visitors.at layout.at motifs.at topology.at foreign.at operators.at \ + other.at foreign.at conversion.at flow.at community.at eigen.at \ + cliques.at attributes.at arpack.at bipartite.at centralization.at \ + version.at separators.at hrg.at microscopic.at mt.at random.at scg.at \ + matching.at qsort.at coloring.at embedding.at + +TESTSUITE = testsuite + +AUTOTEST = $(AUTOM4TE) --language=autotest +$(TESTSUITE): $(srcdir)/package.m4 $(TESTSUITE_AT) + $(AUTOTEST) -I $(top_srcdir)/tests $(top_srcdir)/tests/testsuite.at -o $@.tmp + mv $@.tmp $@ + +clean-local: $(TESTSUITE) + $(SHELL) $(TESTSUITE) --clean + +check-local: atconfig atlocal $(TESTSUITE) + if [ ! -f $(TESTSUITE) ]; then cp $(top_srcdir)/tests/testsuite .; fi + $(SHELL) $(TESTSUITE) + +# Run the test suite on the *installed* tree. +installcheck-local: + $(SHELL) $(TESTSUITE) AUTOTEST_PATH=$(exec_prefix)/bin diff --git a/tests/arpack.at b/tests/arpack.at new file mode 100644 index 0000000..640df23 --- /dev/null +++ b/tests/arpack.at @@ -0,0 +1,68 @@ +# Check ARPACK based functins + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[BLAS, LAPACK and ARPACK based functions]]) + +AT_SETUP([Basic BLAS functions (igraph_blas_*):]) +AT_KEYWORDS([blas BLAS matrix vector dgemv igraph_blas_dgemv]) +AT_COMPILE_CHECK([simple/blas.c], + [simple/blas.out]) +AT_CLEANUP + +AT_SETUP([Dense symmetric eigenvalues and eigenvectors (igraph_lapack_dsyevr):]) +AT_KEYWORDS([lapack LAPACK dsyevr eigenvalue eigenvector dense]) +AT_COMPILE_CHECK([simple/igraph_lapack_dsyevr.c]) +AT_CLEANUP + +AT_SETUP([Dense non-symmetric eigenvalues and eigenvectors (igraph_lapack_dgeev):]) +AT_KEYWORDS([lapack LAPACK dgeev eigenvalue eigenvector dense]) +AT_COMPILE_CHECK([simple/igraph_lapack_dgeev.c]) +AT_CLEANUP + +AT_SETUP([Dense non-symmetric eigenvalues and eigenvectors (igraph_lapack_dgeevx):]) +AT_KEYWORDS([lapack LAPACK dgeevx eigenvalue eigenvector dense]) +AT_COMPILE_CHECK([simple/igraph_lapack_dgeevx.c]) +AT_CLEANUP + +AT_SETUP([Solving linear systems with LU factorization (igraph_lapack_dgesv):]) +AT_KEYWORDS([lapack LAPACK dgesv solve LU factorization]) +AT_COMPILE_CHECK([simple/igraph_lapack_dgesv.c], + [simple/igraph_lapack_dgesv.out]) +AT_CLEANUP + +AT_SETUP([Upper Hessenberg transformation (igraph_lapack_dgehrd):]) +AT_KEYWORDS([lapack dgehrd Hessenberg]) +AT_COMPILE_CHECK([simple/igraph_lapack_dgehrd.c], + [simple/igraph_lapack_dgehrd.out]) +AT_CLEANUP + +AT_SETUP([Eigenvector centrality (igraph_eigenvector_centrality):]) +AT_KEYWORDS([eigenvector centrality arpack ARPACK]) +AT_COMPILE_CHECK([simple/eigenvector_centrality.c], + [simple/eigenvector_centrality.out]) +AT_CLEANUP + +AT_SETUP([Non-symmetric ARPACK solver (igraph_arpack_rnsolve):]) +AT_KEYWORDS([ARPACK eigenvalue eigenvector eigen eigenproblem + non-symmetric]) +AT_COMPILE_CHECK([simple/igraph_arpack_rnsolve.c], + [simple/igraph_arpack_rnsolve.out]) +AT_CLEANUP diff --git a/tests/atlocal.in b/tests/atlocal.in new file mode 100644 index 0000000..7afe765 --- /dev/null +++ b/tests/atlocal.in @@ -0,0 +1,11 @@ +# @configure_input@ +# Configurable variable values for igraph test suite. Taken from bison source. +# Copyright 2000, 2001, 2002 Free Software Foundation, Inc. + +# We need a C compiler. +CC='@CC@' +CFLAGS='@CFLAGS@ @WARNING_CFLAGS@ @WERROR_CFLAGS@' + +# We need `config.h'. +CPPFLAGS="-I$abs_top_builddir @CPPFLAGS@" + diff --git a/tests/attributes.at b/tests/attributes.at new file mode 100644 index 0000000..dba45e2 --- /dev/null +++ b/tests/attributes.at @@ -0,0 +1,58 @@ +# Check functions for graph, vertex and edge attributes + +# Test suite for the IGraph library. +# Copyright (C) 2007-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Attributes from C]]) + +AT_SETUP([Reading a Pajek file with attributes:]) +AT_KEYWORDS([pajek attributes]) +AT_COMPILE_CHECK([simple/cattributes.c], [simple/cattributes.out], + [simple/LINKS.NET]) +AT_CLEANUP + +AT_SETUP([Writing an attributed graph in GML and GraphML:]) +AT_KEYWORDS([gml GML graphml GraphML attributes]) +AT_COMPILE_CHECK([simple/cattributes2.c], [simple/cattributes2.out]) +AT_CLEANUP + +AT_SETUP([Combining numeric attributes:]) +AT_KEYWORDS([attributes combination combining]) +AT_COMPILE_CHECK([simple/cattributes3.c], [simple/cattributes3.out]) +AT_CLEANUP + +AT_SETUP([Combining string attributes:]) +AT_KEYWORDS([attributes combination combining]) +AT_COMPILE_CHECK([simple/cattributes4.c], [simple/cattributes4.out]) +AT_CLEANUP + +AT_SETUP([Combining Boolean attributes:]) +AT_KEYWORDS([attributes combination combining]) +AT_COMPILE_CHECK([simple/cattributes5.c], [simple/cattributes5.out]) +AT_CLEANUP + +AT_SETUP([Boolean graph attribute bug:]) +AT_KEYWORDS([attributes bool boolean logical bug]) +AT_COMPILE_CHECK([simple/cattr_bool_bug.c], [], [simple/cattr_bool_bug.graphml]) +AT_CLEANUP + +AT_SETUP([Boolean graph attribute bug 2:]) +AT_KEYWORDS([attributes bool boolean logical bug]) +AT_COMPILE_CHECK([tests/cattr_bool_bug2.c], [tests/cattr_bool_bug2.out], [tests/cattr_bool_bug2.graphml]) +AT_CLEANUP diff --git a/tests/basic.at b/tests/basic.at new file mode 100644 index 0000000..76e479c --- /dev/null +++ b/tests/basic.at @@ -0,0 +1,81 @@ +# Check the basic (interface) functions and implicitly also compilation + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +# Macros + +AT_BANNER([[Basic (interface) functions.]]) + +AT_SETUP([Creating an empty graph (igraph_empty): ]) +AT_KEYWORDS([igraph_empty]) +AT_COMPILE_CHECK([simple/igraph_empty.c]) +AT_CLEANUP + +AT_SETUP([Copying a graph (igraph_copy): ]) +AT_KEYWORDS([igraph_copy igraph_create]) +AT_COMPILE_CHECK([simple/igraph_copy.c]) +AT_CLEANUP + +AT_SETUP([Adding edges to a graph (igraph_add_edges): ]) +AT_KEYWORDS([igraph_add_edges]) +AT_COMPILE_CHECK([simple/igraph_add_edges.c], + [simple/igraph_add_edges.out]) +AT_CLEANUP + +AT_SETUP([Adding vertices (igraph_add_vertices): ]) +AT_KEYWORDS([igraph_add_vertices]) +AT_COMPILE_CHECK([simple/igraph_add_vertices.c]) +AT_CLEANUP + +AT_SETUP([Deleting edges (igraph_delete_edges): ]) +AT_KEYWORDS([igraph_delete_vertices]) +AT_COMPILE_CHECK([simple/igraph_delete_edges.c]) +AT_CLEANUP + +AT_SETUP([Deleting vertices (igraph_delete_vertices): ]) +AT_KEYWORDS([igraph_delete_vertices]) +AT_COMPILE_CHECK([simple/igraph_delete_vertices.c]) +AT_CLEANUP + +AT_SETUP([Neighbors (igraph_neighbors): ]) +AT_KEYWORDS([igraph_neighbors]) +AT_COMPILE_CHECK([simple/igraph_neighbors.c], [simple/igraph_neighbors.out]) +AT_CLEANUP + +AT_SETUP([Is the graph directed? (igraph_is_directed): ]) +AT_KEYWORDS([igraph_is_directed]) +AT_COMPILE_CHECK([simple/igraph_is_directed.c]) +AT_CLEANUP + +AT_SETUP([Degree of the vertices (igraph_degree): ]) +AT_KEYWORDS([igraph_degree]) +AT_COMPILE_CHECK([simple/igraph_degree.c], [simple/igraph_degree.out]) +AT_CLEANUP + +AT_SETUP([Query edge ids (igraph_get_eid): ]) +AT_KEYWORDS([igraph_get_eid edge id]) +AT_COMPILE_CHECK([simple/igraph_get_eid.c], [simple/igraph_get_eid.out]) +AT_CLEANUP + +AT_SETUP([Query many edge ids (igraph_get_eids): ]) +AT_KEYWORDS([igraph_get_eids edge id]) +AT_COMPILE_CHECK([simple/igraph_get_eids.c], [simple/igraph_get_eids.out]) +AT_CLEANUP + diff --git a/tests/bipartite.at b/tests/bipartite.at new file mode 100644 index 0000000..2e419b0 --- /dev/null +++ b/tests/bipartite.at @@ -0,0 +1,33 @@ +# Check functions for bipartite graphs + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Bipartite graphs]]) + +AT_SETUP([Create bipartite graphs (igraph_create_bipartite):]) +AT_KEYWORDS([bipartite two mode igraph_create_bipartite]) +AT_COMPILE_CHECK([simple/igraph_bipartite_create.c], + [simple/igraph_bipartite_create.out]) +AT_CLEANUP + +AT_SETUP([Projection of bipartite graphs (igraph_bipartite_projection):]) +AT_KEYWORDS([bipartite two mode projection igraph_bipartite_projection]) +AT_COMPILE_CHECK([simple/igraph_bipartite_projection.c]) +AT_CLEANUP diff --git a/tests/centralization.at b/tests/centralization.at new file mode 100644 index 0000000..dc4446c --- /dev/null +++ b/tests/centralization.at @@ -0,0 +1,27 @@ +# Check functions for centralization + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Centralization]]) + +AT_SETUP([Centralization (igraph_centralization_*):]) +AT_KEYWORDS([centralization]) +AT_COMPILE_CHECK([simple/centralization.c]) +AT_CLEANUP diff --git a/tests/cliques.at b/tests/cliques.at new file mode 100644 index 0000000..ca0526c --- /dev/null +++ b/tests/cliques.at @@ -0,0 +1,77 @@ +# Check the functions related to clique and independent set calculations + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +# Macros + +AT_BANNER([[Cliques and independent vertex sets.]]) + +AT_SETUP([Calculating cliques (igraph_cliques): ]) +AT_KEYWORDS([igraph_cliques, igraph_maximal_cliques, igraph_clique_number]) +AT_COMPILE_CHECK([simple/igraph_cliques.c], [simple/igraph_cliques.out]) +AT_CLEANUP + +AT_SETUP([Additional test for maximal cliques (igraph_maximal_cliques):]) +AT_KEYWORDS([igraph_maximal_cliques cliques maximal cliques]) +AT_COMPILE_CHECK([simple/igraph_maximal_cliques.c], + [simple/igraph_maximal_cliques.out]) +AT_CLEANUP + +AT_SETUP([More maximal cliques (igraph_maximal_cliques):]) +AT_KEYWORDS([igraph_maximal_cliques cliques maximal cliques]) +AT_COMPILE_CHECK([simple/igraph_maximal_cliques2.c], + [simple/igraph_maximal_cliques2.out]) +AT_CLEANUP + +AT_SETUP([Maximal cliques 3 (igraph_maximal_cliques):]) +AT_KEYWORDS([igraph_maximal_cliques cliques maximal cliques]) +AT_COMPILE_CHECK([simple/igraph_maximal_cliques3.c], + [simple/igraph_maximal_cliques3.out]) +AT_CLEANUP + +AT_SETUP([Maximal cliques for a subset (igraph_maximal_cliques):]) +AT_KEYWORDS([igraph_maximal_cliques cliques maximal cliques]) +AT_COMPILE_CHECK([simple/igraph_maximal_cliques4.c], + [simple/igraph_maximal_cliques4.out]) +AT_CLEANUP + +AT_SETUP([Maximal cliques callback (igraph_maximal_cliques_callback):]) +AT_KEYWORDS([igraph_maximal_cliques cliques maximal cliques]) +AT_COMPILE_CHECK([tests/maximal_cliques_callback.c]) +AT_CLEANUP + +AT_SETUP([Maximal cliques histogram (igraph_maximal_cliques_hist):]) +AT_KEYWORDS([igraph_maximal_cliques cliques maximal cliques]) +AT_COMPILE_CHECK([tests/maximal_cliques_hist.c], + [tests/maximal_cliques_hist.out]) +AT_CLEANUP + +AT_SETUP([Weighted cliques (igraph_weighted_cliques):]) +AT_KEYWORDS([igraph_weighted_cliques cliques]) +AT_COMPILE_CHECK([simple/igraph_weighted_cliques.c], + [simple/igraph_weighted_cliques.out]) +AT_CLEANUP + +AT_SETUP([Calculating independent vertex sets (igraph_independent_vertex_sets): ]) +AT_KEYWORDS([igraph_independent_vertex_sets, + igraph_maximal_independent_vertex_sets, + igraph_independence_number]) +AT_COMPILE_CHECK([simple/igraph_independent_sets.c], [simple/igraph_independent_sets.out]) +AT_CLEANUP diff --git a/tests/coloring.at b/tests/coloring.at new file mode 100644 index 0000000..b21457f --- /dev/null +++ b/tests/coloring.at @@ -0,0 +1,29 @@ +# Check the functions related to graph coloring + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +# Macros + +AT_BANNER([[Graph coloring]]) + +AT_SETUP([Greedy vertex coloring (igraph_vertex_coloring_greedy): ]) +#AT_KEYWORDS([igraph_cliques, igraph_maximal_cliques, igraph_clique_number]) +AT_COMPILE_CHECK([simple/igraph_coloring.c]) +AT_CLEANUP diff --git a/tests/community.at b/tests/community.at new file mode 100644 index 0000000..9969c75 --- /dev/null +++ b/tests/community.at @@ -0,0 +1,102 @@ +# Community structure + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Community structure]]) + +AT_SETUP([Spinglass clustering (igraph_spinglass_community): ]) +AT_KEYWORDS([spin glass spinglass community clustering]) +AT_COMPILE_CHECK([simple/spinglass.c]) +AT_CLEANUP + +AT_SETUP([Walktrap community structure (igraph_walktrap_community): ]) +AT_KEYWORDS([random walk community structure clustering walktrap]) +AT_COMPILE_CHECK([simple/walktrap.c], [simple/walktrap.out]) +AT_CLEANUP + +AT_SETUP([Edge betweenness community structure (igraph_community_edge_betweenness): ]) +AT_KEYWORDS([community structure edge betweenness Newman Girvan]) +AT_COMPILE_CHECK([simple/igraph_community_edge_betweenness.c], + [simple/igraph_community_edge_betweenness.out]) +AT_CLEANUP + +AT_SETUP([Modularity optimization (igraph_community_fastgreedy): ]) +AT_KEYWORDS([community structure Clauset Newman Moore modularity greedy]) +AT_COMPILE_CHECK([simple/igraph_community_fastgreedy.c], + [simple/igraph_community_fastgreedy.out]) +AT_CLEANUP + +AT_SETUP([Leading eigenvector community structure (igraph_community_leading_eigenvector) :]) +AT_KEYWORDS([community structure leading eigenvector Newman]) +AT_COMPILE_CHECK([simple/igraph_community_leading_eigenvector.c], + [simple/igraph_community_leading_eigenvector.out]) +AT_CLEANUP + +AT_SETUP([Weighted leading eigenvector community structure (igraph_community_leading_eigenvector) :]) +AT_KEYWORDS([community structure leading eigenvector Newman weighted]) +AT_COMPILE_CHECK([simple/igraph_community_leading_eigenvector2.c], + [simple/igraph_community_leading_eigenvector2.out]) +AT_CLEANUP + +AT_SETUP([Leading eigenvector bug 1002140 test (igraph_community_leading_eigenvector) :]) +AT_KEYWORDS([community structure leading eigenvector Newman]) +AT_COMPILE_CHECK([simple/levc-stress.c], [], [simple/input.dl]) +AT_CLEANUP + +AT_SETUP([Fluid communities algorithm (igraph_community_fluid_communities) :]) +AT_KEYWORDS([community structure fluidc fluid communities Pares Garcia-Gasulla]) +AT_COMPILE_CHECK([tests/igraph_community_fluid_communities.c], + [tests/igraph_community_fluid_communities.out]) +AT_CLEANUP + +AT_SETUP([Label propagation algorithm (igraph_community_label_propagation) :]) +AT_KEYWORDS([community structure label propagation Raghavan Albert Kumara]) +AT_COMPILE_CHECK([tests/igraph_community_label_propagation.c], + [tests/igraph_community_label_propagation.out]) +AT_CLEANUP + +AT_SETUP([Multilevel community detection (igraph_community_multilevel) :]) +AT_KEYWORDS([community structure multilevel Blondel Guillaume Lambiotte Lefebvre]) +AT_COMPILE_CHECK([simple/igraph_community_multilevel.c], + [simple/igraph_community_multilevel.out]) +AT_CLEANUP + +AT_SETUP([Multilevel community detection, isolates (igraph_community_multilevel) :]) +AT_KEYWORDS([community structure multilevel Blondel Guillaume Lambiotte Lefebvre]) +AT_COMPILE_CHECK([simple/bug-1149658.c]) +AT_CLEANUP + +AT_SETUP([Leiden community detection (igraph_community_leiden) :]) +AT_KEYWORDS([community structure using Leiden algorithm]) +AT_COMPILE_CHECK([tests/igraph_community_leiden.c], + [tests/igraph_community_leiden.out]) +AT_CLEANUP + +AT_SETUP([Modularity optimization, integer programming (igraph_community_optimal_modularity) :]) +AT_KEYWORDS([community structure optimal modularity integer programming]) +AT_COMPILE_CHECK([simple/igraph_community_optimal_modularity.c]) +AT_CLEANUP + +AT_SETUP([Infomap community structure (igraph_community_infomap) :]) +AT_KEYWORDS([community structure infomap Rosvall Bergstrom]) +AT_COMPILE_CHECK([simple/igraph_community_infomap.c], + [simple/igraph_community_infomap.out], + [simple/wikti_en_V_syn.elist]) +AT_CLEANUP diff --git a/tests/components.at b/tests/components.at new file mode 100644 index 0000000..ca4e069 --- /dev/null +++ b/tests/components.at @@ -0,0 +1,44 @@ +# Check functions for working with connected components + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Components]]) + +AT_SETUP([Decompose a graph (igraph_decompose):]) +AT_KEYWORDS([igraph_decompose decompose component]) +AT_COMPILE_CHECK([simple/igraph_decompose.c], [simple/igraph_decompose.out]) +AT_CLEANUP + +AT_SETUP([Decompose a graph into strongly connected components (igraph_decompose_strong):]) +AT_KEYWORDS([igraph_decompose_strong decompose_strong component_strong]) +AT_COMPILE_CHECK([tests/igraph_decompose_strong.c], [tests/igraph_decompose_strong.out]) +AT_CLEANUP + +AT_SETUP([Biconnected components (igraph_biconnected_components):]) +AT_KEYWORDS([igraph_biconnected_components biconnected component]) +AT_COMPILE_CHECK([simple/igraph_biconnected_components.c], + [simple/igraph_biconnected_components.out]) +AT_CLEANUP + +AT_SETUP([Bridges (igraph_bridges):]) +AT_KEYWORDS([igraph_bridges bridges]) +AT_COMPILE_CHECK([simple/igraph_bridges.c], [simple/igraph_bridges.out]) +AT_CLEANUP + diff --git a/tests/conversion.at b/tests/conversion.at new file mode 100644 index 0000000..c372e4b --- /dev/null +++ b/tests/conversion.at @@ -0,0 +1,44 @@ +# Check various conversion functions + +# Test suite for the IGraph library. +# Copyright (C) 2006-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Conversion functions]]) + +AT_SETUP([Directed to undirected (igraph_to_undirected):]) +AT_KEYWORDS([igraph_to_undirected directedness undirected directed]) +AT_COMPILE_CHECK([simple/igraph_to_undirected.c], + [simple/igraph_to_undirected.out]) +AT_CLEANUP + +AT_SETUP([Graphs from adjacency list (igraph_adjlist):]) +AT_KEYWORDS([igraph_adjlist adjacency list adjlist]) +AT_COMPILE_CHECK([simple/adjlist.c]) +AT_CLEANUP + +AT_SETUP([Graph to Laplacian matrix (igraph_laplacian):]) +AT_KEYWORDS([igraph_laplacian laplacian matrix]) +AT_COMPILE_CHECK([simple/igraph_laplacian.c], + [simple/igraph_laplacian.out]) +AT_CLEANUP + +AT_SETUP([Tree to prufer sequence (igraph_to_prufer):]) +AT_KEYWORDS([igraph_to_prufer]) +AT_COMPILE_CHECK([simple/igraph_to_prufer.c]) +AT_CLEANUP diff --git a/tests/eigen.at b/tests/eigen.at new file mode 100644 index 0000000..e90e9b5 --- /dev/null +++ b/tests/eigen.at @@ -0,0 +1,59 @@ +# Eigenvalues, eigenvectors + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Eigenvalues, eigenvectors]]) + +AT_SETUP([Symmetric matrix, LAPACK (igraph_eigen_matrix_symmetric):]) +AT_KEYWORDS([eigenvalue lapack LAPACK]) +AT_COMPILE_CHECK([simple/igraph_eigen_matrix_symmetric.c], + [simple/igraph_eigen_matrix_symmetric.out]) +AT_CLEANUP + +AT_SETUP([Symmetric matrix, ARPACK (igraph_eigen_matrix_symmetric):]) +AT_KEYWORDS([eigenvalue lapack LAPACK]) +AT_COMPILE_CHECK([simple/igraph_eigen_matrix_symmetric_arpack.c], + [simple/igraph_eigen_matrix_symmetric_arpack.out]) +AT_CLEANUP + +AT_SETUP([General matrix, LAPACK, LM, SM (igraph_eigen_matrix):]) +AT_KEYWORDS([eigenvalue lapack LAPACK]) +AT_COMPILE_CHECK([simple/igraph_eigen_matrix.c], + [simple/igraph_eigen_matrix.out]) +AT_CLEANUP + +AT_SETUP([General matrix, LAPACK, LR, SR (igraph_eigen_matrix):]) +AT_KEYWORDS([eigenvalue lapack LAPACK]) +AT_COMPILE_CHECK([simple/igraph_eigen_matrix2.c], + [simple/igraph_eigen_matrix2.out]) +AT_CLEANUP + +AT_SETUP([General matrix, LAPACK, LI, SI (igraph_eigen_matrix):]) +AT_KEYWORDS([eigenvalue lapack LAPACK]) +AT_COMPILE_CHECK([simple/igraph_eigen_matrix4.c], + [simple/igraph_eigen_matrix4.out]) +AT_CLEANUP + +AT_SETUP([General matrix, LAPACK, SELECT (igraph_eigen_matrix):]) +AT_KEYWORDS([eigenvalue lapack LAPACK]) +AT_COMPILE_CHECK([simple/igraph_eigen_matrix3.c], + [simple/igraph_eigen_matrix3.out]) +AT_CLEANUP + diff --git a/tests/embedding.at b/tests/embedding.at new file mode 100644 index 0000000..a5cc088 --- /dev/null +++ b/tests/embedding.at @@ -0,0 +1,27 @@ +# Embeddings + +# Test suite for the igraph library. +# Copyright (C) 2013 Gabor Csardi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Embeddings]]) + +AT_SETUP([Adjacency spectral embedding (igraph_adjacency_spectral_embedding): ]) +AT_KEYWORDS([adjacency spectral embedding]) +AT_COMPILE_CHECK([simple/igraph_adjacency_spectral_embedding.c], + [simple/igraph_adjacency_spectral_embedding.out]) +AT_CLEANUP diff --git a/tests/flow.at b/tests/flow.at new file mode 100644 index 0000000..ac945ff --- /dev/null +++ b/tests/flow.at @@ -0,0 +1,66 @@ +# Check maximum flow and related functions + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Maximum flows and such]]) + +AT_SETUP([Maximum flow value (igraph_maxflow_value): ]) +AT_KEYWORDS([maximum flow maxflow minimum cut]) +AT_COMPILE_CHECK([simple/flow.c], [], [simple/ak-4102.max]) +AT_CLEANUP + +AT_SETUP([Maximum flow (igraph_maxflow): ]) +AT_KEYWORDS([maximum flow maxflow minimum cut]) +AT_COMPILE_CHECK([simple/flow2.c], [simple/flow2.out], [simple/ak-4102.max]) +AT_CLEANUP + +AT_SETUP([Minimum cut (igraph_mincut): ]) +AT_KEYWORDS([minimum cut Stoer-Wagner]) +AT_COMPILE_CHECK([simple/igraph_mincut.c], [simple/igraph_mincut.out]) +AT_CLEANUP + +AT_SETUP([Even-Tarjan reduction (igraph_even_tarjan_reduction): ]) +AT_KEYWORDS([Even Tarjan reduction vertex cut separator]) +AT_COMPILE_CHECK([simple/even_tarjan.c]) +AT_CLEANUP + +AT_SETUP([Dominator tree of a flow graph (igraph_dominator_tree): ]) +AT_KEYWORDS([dominator tree]) +AT_COMPILE_CHECK([simple/dominator_tree.c], + [simple/dominator_tree.out]) +AT_CLEANUP + +AT_SETUP([All s-t cuts of a graph (igraph_all_st_cuts): ]) +AT_KEYWORDS([s-t cut]) +AT_COMPILE_CHECK([simple/igraph_all_st_cuts.c], + [simple/igraph_all_st_cuts.out], [], [INTERNAL]) +AT_CLEANUP + +AT_SETUP([All minimal s-t cuts of a graph (igraph_all_st_mincuts): ]) +AT_KEYWORDS([minimal s-t cut]) +AT_COMPILE_CHECK([simple/igraph_all_st_mincuts.c], + [simple/igraph_all_st_mincuts.out]) +AT_CLEANUP + +AT_SETUP([Gomory-Hu tree (igraph_gomory_hu_tree): ]) +AT_KEYWORDS([Gomory-Hu tree]) +AT_COMPILE_CHECK([simple/igraph_gomory_hu_tree.c]) +AT_CLEANUP + diff --git a/tests/foreign.at b/tests/foreign.at new file mode 100644 index 0000000..af6b6d4 --- /dev/null +++ b/tests/foreign.at @@ -0,0 +1,114 @@ +# Check functions for importing and exporting various formats + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Foreign formats]]) + +AT_SETUP([Reading Pajek (igraph_read_graph_pajek):]) +AT_KEYWORDS([igraph_read_graph_pajek foreign pajek]) +AT_COMPILE_CHECK([simple/foreign.c], [simple/foreign.out], [simple/LINKS.NET]) +AT_CLEANUP + +AT_SETUP([GraphML (igraph_{read,write}_graph_graphml):]) +AT_KEYWORDS([igraph_read_graph_graphml igraph_write_graph_graphml foreign graphml]) +AT_COMPILE_CHECK([simple/graphml.c], [simple/graphml.out], + [simple/{test.gxl,graphml-hsa05010.xml,graphml-default-attrs.xml,graphml-namespace.xml,graphml-lenient.xml,graphml-malformed.xml}]) +AT_CLEANUP + +AT_SETUP([Writing Pajek (igraph_write_graph_pajek):]) +AT_KEYWORDS([igraph_write_graph_pajek foreign pajek]) +AT_COMPILE_CHECK([simple/igraph_write_graph_pajek.c], + [simple/igraph_write_graph_pajek.out]) +AT_CLEANUP + +AT_SETUP([Pajek with number of edges present (igraph_read_graph_pajek):]) +AT_KEYWORDS([igraph_read_graph_pajek pajek foreign]) +AT_COMPILE_CHECK([simple/pajek.c], [], [simple/pajek{5,6}.net]) +AT_CLEANUP + +AT_SETUP([Pajek, bipartite (igraph_read_graph_pajek):]) +AT_KEYWORDS([igraph_read_graph_pajek pajek foreign bipartite]) +AT_COMPILE_CHECK([simple/pajek2.c], [simple/pajek2.out], + [simple/bipartite.net]) +AT_CLEANUP + +AT_SETUP([Pajek, bipartite incidence matrix (igraph_read_graph_pajek):]) +AT_KEYWORDS([igraph_read_graph_pajek pajek foreign bipartite incidence]) +AT_COMPILE_CHECK([simple/pajek_bipartite2.c], [simple/pajek_bipartite2.out], + [simple/pajek_{bip,bip2}.net]) +AT_CLEANUP + +AT_SETUP([Pajek, signed (igraph_read_graph_pajek):]) +AT_KEYWORDS([igraph_read_graph_pajek pajek foreign signed]) +AT_COMPILE_CHECK([simple/pajek_signed.c], [simple/pajek_signed.out], + [simple/pajek_signed.net]) +AT_CLEANUP + +AT_SETUP([Pajek, writing bipartite graph (igraph_write_graph_pajek):]) +AT_KEYWORDS([igraph_write_graph_pajek pajek foreign bipartite]) +AT_COMPILE_CHECK([simple/pajek_bipartite.c], [simple/pajek_bipartite.out]) +AT_CLEANUP + +AT_SETUP([Reading an LGL file (igraph_read_graph_lgl):]) +AT_KEYWORDS([igraph_read_graph_lgl LGL foreign]) +AT_COMPILE_CHECK([simple/igraph_read_graph_lgl.c], + [simple/igraph_read_graph_lgl.out], + [{simple/igraph_read_graph_lgl-1.lgl,simple/igraph_read_graph_lgl-2.lgl,simple/igraph_read_graph_lgl-3.lgl}]) +AT_CLEANUP + +AT_SETUP([Writing LGL (igraph_write_graph_lgl):]) +AT_KEYWORDS([igraph_write_graph_lgl foreign LGL]) +AT_COMPILE_CHECK([simple/igraph_write_graph_lgl.c]) +AT_CLEANUP + +AT_SETUP([Reading a graph from the graph database (igraph_read_graph_graphdb):]) +AT_KEYWORDS([igraph_read_graph_graphdb foreign graphdb database isomorphism]) +AT_COMPILE_CHECK([simple/igraph_read_graph_graphdb.c], + [simple/igraph_read_graph_graphdb.out], + [simple/iso_b03_m1000.A00]) +AT_CLEANUP + +AT_SETUP([Reading a GML file (igraph_read_graph_gml):]) +AT_KEYWORDS([igraph_read_graph_gml foreign GML]) +AT_COMPILE_CHECK([simple/gml.c], [simple/gml.out], [simple/karate.gml]) +AT_CLEANUP + +AT_SETUP([Writing a DOT file (igraph_write_graph_dot):]) +AT_KEYWORDS([igraph_write_graph_dot foreign DOT GraphViz]) +AT_COMPILE_CHECK([simple/dot.c], [simple/dot.out], [simple/karate.gml]) +AT_CLEANUP + +AT_SETUP([Different line endings:]) +AT_KEYWORDS([igraph_read_graph_pajek igraph_write_graph_pajek + foreign line ending lineending]) +AT_COMPILE_CHECK([simple/lineendings.c], [simple/lineendings.out], + [{simple/pajek1.net,simple/pajek2.net,simple/pajek3.net,simple/pajek4.net}]) +AT_CLEANUP + +AT_SETUP([UNICET DL format:]) +AT_KEYWORDS([igraph_read_graph_dl DL UCINET]) +AT_COMPILE_CHECK([simple/igraph_read_graph_dl.c], [simple/igraph_read_graph_dl.out], + [simple/{edgelist1,edgelist2,edgelist3,edgelist4,edgelist5,edgelist6,edgelist7,fullmatrix1,fullmatrix2,fullmatrix3,fullmatrix4,nodelist1,nodelist2}.dl]) +AT_CLEANUP + +AT_SETUP([LEDA format:]) +AT_KEYWORDS([igraph_write_graph_leda LEDA]) +AT_COMPILE_CHECK([simple/igraph_write_graph_leda.c], [simple/igraph_write_graph_leda.out], []) +AT_CLEANUP diff --git a/tests/hrg.at b/tests/hrg.at new file mode 100644 index 0000000..317fc0c --- /dev/null +++ b/tests/hrg.at @@ -0,0 +1,37 @@ +# Hierarchical random graphs + +# Test suite for the IGraph library. +# Copyright (C) 2011-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Hierarchical random graphs]]) + +AT_SETUP([Fitting a hierarchical model (igraph_hrg_fit) :]) +AT_KEYWORDS([hierarchical random graph]) +AT_COMPILE_CHECK([simple/igraph_hrg.c]) +AT_CLEANUP + +AT_SETUP([Consensus tree (igraph_hrg_consensus) :]) +AT_KEYWORDS([hierarchical random graph consensus tree]) +AT_COMPILE_CHECK([simple/igraph_hrg2.c], [simple/igraph_hrg2.out]) +AT_CLEANUP + +AT_SETUP([Missing edge prediction (igraph_hrg_predict) :]) +AT_KEYWORDS([hierarchical random graph missing edge prediction]) +AT_COMPILE_CHECK([simple/igraph_hrg3.c], [simple/igraph_hrg3.out]) +AT_CLEANUP diff --git a/tests/iterators.at b/tests/iterators.at new file mode 100644 index 0000000..d8237f5 --- /dev/null +++ b/tests/iterators.at @@ -0,0 +1,57 @@ +# Check vertex and edge sequences (=iterators) + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Iterators aka vertex and edge sequences]]) + +AT_SETUP([Vertices in a vector (igraph_vs_vector): ]) +AT_KEYWORDS([iterator vector igraph_vs_vector igraph_vs_vectorview]) +AT_COMPILE_CHECK([simple/igraph_vs_vector.c], [simple/igraph_vs_vector.out]) +AT_CLEANUP + +AT_SETUP([Non-adjacent vertices (igraph_vs_nonadj): ]) +AT_KEYWORDS([iterator igraph_vs_nonadj]) +AT_COMPILE_CHECK([simple/igraph_vs_nonadj.c], [simple/igraph_vs_nonadj.out]) +AT_CLEANUP + +AT_SETUP([Sequence (igraph_vs_seq): ]) +AT_KEYWORDS([iterator igraph_vs_seq seq sequence]) +AT_COMPILE_CHECK([simple/igraph_vs_seq.c], [simple/igraph_vs_seq.out]) +AT_CLEANUP + +#AT_SETUP([Adjacent edges (igraph_es_adj): ]) +#AT_KEYWORDS([iterator adjacent igraph_es_adj]) +#AT_COMPILE_CHECK([simple/igraph_es_adj.c], [simple/igraph_es_adj.out]) +#AT_CLEANUP + +#AT_SETUP([Edges connecting two vertex sets (igraph_es_fromto): ]) +#AT_KEYWORDS([iterator igraph_es_fromto]) +#AT_COMPILE_CHECK([simple/igraph_es_fromto.c], [simple/igraph_es_fromto.out]) +#AT_CLEANUP + +AT_SETUP([Edges given by end points (igraph_es_pairs): ]) +AT_KEYWORDS([iterator igraph_es_pairs]) +AT_COMPILE_CHECK([simple/igraph_es_pairs.c]) +AT_CLEANUP + +AT_SETUP([Edges in a path (igraph_es_path): ]) +AT_KEYWORDS([iterator, igraph_es_path]) +AT_COMPILE_CHECK([simple/igraph_es_path.c]) +AT_CLEANUP diff --git a/tests/layout.at b/tests/layout.at new file mode 100644 index 0000000..b5c2d25 --- /dev/null +++ b/tests/layout.at @@ -0,0 +1,80 @@ +# Check functions for generating layouts + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Layouts]]) + +AT_SETUP([Grid layout (igraph_layout_grid, igraph_layout_grid_3d):]) +AT_KEYWORDS([igraph_layout_grid igraph_layout_grid_3d grid layout]) +AT_COMPILE_CHECK([simple/igraph_layout_grid.c], [simple/igraph_layout_grid.out]) +AT_CLEANUP + +AT_SETUP([Large Graph Layout (igraph_layout_lgl):]) +AT_KEYWORDS([igraph_layout_lgl LGL]) +AT_COMPILE_CHECK([simple/igraph_layout_lgl.c]) +AT_CLEANUP + +AT_SETUP([Reingold-Tilford tree layout (igraph_layout_reingold_tilford):]) +AT_KEYWORDS([reingold tilford tree layout igraph_layout_reingold_tilford]) +AT_COMPILE_CHECK([simple/igraph_layout_reingold_tilford.c], [], + [simple/igraph_layout_reingold_tilford.in]) +AT_CLEANUP + +AT_SETUP([Reingold-Tilford tree layout extended (igraph_layout_reingold_tilford):]) +AT_KEYWORDS([reingold tilford tree layout igraph_layout_reingold_tilford]) +AT_COMPILE_CHECK([tests/igraph_layout_reingold_tilford_extended.c], [], + [tests/igraph_layout_reingold_tilford_extended.in]) +AT_CLEANUP + +AT_SETUP([Sugiyama layout (igraph_layout_sugiyama):]) +AT_KEYWORDS([sugiyama layout igraph_layout_sugiyama]) +AT_COMPILE_CHECK([simple/igraph_layout_sugiyama.c], [simple/igraph_layout_sugiyama.out]) +AT_CLEANUP + +AT_SETUP([Multidimensional scaling (igraph_layout_mds):]) +AT_KEYWORDS([multidimensional scaling layout igraph_layout_mds]) +AT_COMPILE_CHECK([simple/igraph_layout_mds.c], [simple/igraph_layout_mds.out]) +AT_CLEANUP + +AT_SETUP([Covering circle and sphere (igraph_i_layout_sphere_{2,3}d):]) +AT_KEYWORDS([covering circle sphere layout]) +AT_COMPILE_CHECK([simple/igraph_i_layout_sphere.c]) +AT_CLEANUP + +AT_SETUP([Merging layouts (igraph_i_layout_merge):]) +AT_KEYWORDS([layout merge dla]) +AT_COMPILE_CHECK([simple/igraph_layout_merge.c], [], [], [INTERNAL]) +AT_CLEANUP + +AT_SETUP([Merging layouts 2 (igraph_i_layout_merge):]) +AT_KEYWORDS([layout merge dla]) +AT_COMPILE_CHECK([simple/igraph_layout_merge2.c], + [simple/igraph_layout_merge2.out]) +AT_CLEANUP + +AT_SETUP([Merging layouts 3 (igraph_i_layout_merge):]) +AT_KEYWORDS([layout merge dla]) +AT_COMPILE_CHECK([simple/igraph_layout_merge3.c]) +AT_CLEANUP + +AT_SETUP([Davidson-Harel layout (igraph_layout_davidson_harel):]) +AT_KEYWORDS([layout Davidson-Harel]) +AT_COMPILE_CHECK([simple/igraph_layout_davidson_harel.c]) +AT_CLEANUP diff --git a/tests/matching.at b/tests/matching.at new file mode 100644 index 0000000..f62c8a3 --- /dev/null +++ b/tests/matching.at @@ -0,0 +1,26 @@ +# Maximum bipartite and non-bipartite matchings + +# Test suite for the IGraph library. +# Copyright (C) 2012 Tamas Nepusz +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Maximum matchings]]) + +AT_SETUP([Maximum bipartite matching (igraph_maximum_bipartite_matching): ]) +AT_KEYWORDS([bipartite matching]) +AT_COMPILE_CHECK([simple/igraph_maximum_bipartite_matching.c]) +AT_CLEANUP diff --git a/tests/microscopic.at b/tests/microscopic.at new file mode 100644 index 0000000..1151373 --- /dev/null +++ b/tests/microscopic.at @@ -0,0 +1,41 @@ +# Check functions for microscopic updates at the agent level + +# Test suite for the IGraph library. +# Copyright (C) 2011 Minh Van Nguyen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Microscopic updates]]) + +AT_SETUP([Deterministic optimal imitation:]) +AT_KEYWORDS([deterministic imitation strategy]) +AT_COMPILE_CHECK([simple/igraph_deterministic_optimal_imitation.c]) +AT_CLEANUP + +AT_SETUP([Stochastic imitation via uniform selection:]) +AT_KEYWORDS([stochastic imitation strategy uniform selection]) +AT_COMPILE_CHECK([simple/igraph_stochastic_imitation.c]) +AT_CLEANUP + +AT_SETUP([Stochastic imitation via roulette selection:]) +AT_KEYWORDS([stochastic imitation strategy roulette wheel]) +AT_COMPILE_CHECK([simple/igraph_roulette_wheel_imitation.c]) +AT_CLEANUP + +AT_SETUP([Moran process:]) +AT_KEYWORDS([Moran process haploid reproduction]) +AT_COMPILE_CHECK([simple/igraph_moran_process.c]) +AT_CLEANUP diff --git a/tests/motifs.at b/tests/motifs.at new file mode 100644 index 0000000..517fcd9 --- /dev/null +++ b/tests/motifs.at @@ -0,0 +1,32 @@ +# Check functions for motif detectors + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Motifs]]) + +AT_SETUP([RAND-ESU algorithm (igraph_motifs_randesu)]) +AT_KEYWORDS([motif RAND-ESU]) +AT_COMPILE_CHECK([simple/igraph_motifs_randesu.c], [simple/igraph_motifs_randesu.out]) +AT_CLEANUP + +AT_SETUP([Triad counts (igraph_triad_census):]) +AT_KEYWORDS([motif RAND-ESU]) +AT_COMPILE_CHECK([simple/triad_census.c], [simple/triad_census.out]) +AT_CLEANUP diff --git a/tests/mt.at b/tests/mt.at new file mode 100644 index 0000000..050974a --- /dev/null +++ b/tests/mt.at @@ -0,0 +1,33 @@ +# Thread-safety tests + +# Test suite for the IGraph library. +# Copyright (C) 2011-2012 Gabor Csardi +# 334 Harvard street, Cambridge MA, 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Thread-safety tests]]) + +AT_SETUP([Simple error handling test :]) +AT_KEYWORDS([thread-safe]) +AT_COMPILE_CHECK([simple/tls1.c], [], [], [], [-lpthread]) +AT_CLEANUP + +AT_SETUP([Thread-safe ARPACK:]) +AT_KEYWORDS([thread-safe ARPACK]) +AT_COMPILE_CHECK([simple/tls2.c], [simple/tls2.out], [], [internal], + [-lpthread]) +AT_CLEANUP diff --git a/tests/operators.at b/tests/operators.at new file mode 100644 index 0000000..93c8ae0 --- /dev/null +++ b/tests/operators.at @@ -0,0 +1,65 @@ +# Check functions for graph operators + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Graphs operators]]) + +AT_SETUP([Disjoint union (igraph_disjoint_union, igraph_dosjoint_union_many):]) +AT_KEYWORDS([igraph_disjoint_union, igraph_disjoint_union_many, + disjoint_union, union]) +AT_COMPILE_CHECK([simple/igraph_disjoint_union.c], + [simple/igraph_disjoint_union.out]) +AT_CLEANUP + +AT_SETUP([Union (igraph_union, igraph_union_many):]) +AT_KEYWORDS([igraph_union, igraph_union_many, union]) +AT_COMPILE_CHECK([simple/igraph_union.c], + [simple/igraph_union.out]) +AT_CLEANUP + +AT_SETUP([Intersection (igraph_intersection, igraph_intersection_many):]) +AT_KEYWORDS([igraph_intersection, igraph_intersection_many, intersection]) +AT_COMPILE_CHECK([simple/igraph_intersection.c], + [simple/igraph_intersection.out]) +AT_CLEANUP + +AT_SETUP([Intersection 2 (igraph_intersection, igraph_intersection_many):]) +AT_KEYWORDS([igraph_intersection, igraph_intersection_many, intersection]) +AT_COMPILE_CHECK([simple/igraph_intersection2.c], + [simple/igraph_intersection2.out]) +AT_CLEANUP + +AT_SETUP([Difference (igraph_difference):]) +AT_KEYWORDS([igraph_difference, difference]) +AT_COMPILE_CHECK([simple/igraph_difference.c], + [simple/igraph_difference.out]) +AT_CLEANUP + +AT_SETUP([Complementer (igraph_complementer):]) +AT_KEYWORDS([igraph_complementer, complementer]) +AT_COMPILE_CHECK([simple/igraph_complementer.c], + [simple/igraph_complementer.out]) +AT_CLEANUP + +AT_SETUP([Composition (igraph_compose):]) +AT_KEYWORDS([igraph_composition, composition, compose]) +AT_COMPILE_CHECK([simple/igraph_compose.c], + [simple/igraph_compose.out]) +AT_CLEANUP diff --git a/tests/other.at b/tests/other.at new file mode 100644 index 0000000..3345f44 --- /dev/null +++ b/tests/other.at @@ -0,0 +1,32 @@ +# Check functions for other miscellaneous functions + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Miscellaneous functions]]) + +AT_SETUP([Convex hull calculation (igraph_convex_hull):]) +AT_KEYWORDS([igraph_convex_hull other]) +AT_COMPILE_CHECK([simple/igraph_convex_hull.c], [simple/igraph_convex_hull.out]) +AT_CLEANUP + +AT_SETUP([Fitting power-law distributions (igraph_power_law_fit):]) +AT_KEYWORDS([igraph_power_law_fit other power law fitting]) +AT_COMPILE_CHECK([simple/igraph_power_law_fit.c], [simple/igraph_power_law_fit.out]) +AT_CLEANUP diff --git a/tests/qsort.at b/tests/qsort.at new file mode 100644 index 0000000..57ddadc --- /dev/null +++ b/tests/qsort.at @@ -0,0 +1,32 @@ +# qsort test + +# Test suite for the IGraph library. +# Copyright (C) 2011-2012 Gabor Csardi +# 334 Harvard st, Cambridge, MA 02139, USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Quick sort]]) + +AT_SETUP([Regular qsort (igraph_qsort):]) +AT_KEYWORDS([qsort quick sort igraph_qsort]) +AT_COMPILE_CHECK([simple/igraph_qsort.c], [simple/igraph_qsort.out]) +AT_CLEANUP + +AT_SETUP([qsort with extra argument (igraph_qsort_r):]) +AT_KEYWORDS([qsort quick sort igraph_qsort_r]) +AT_COMPILE_CHECK([simple/igraph_qsort_r.c], [simple/igraph_qsort_r.out]) +AT_CLEANUP diff --git a/tests/random.at b/tests/random.at new file mode 100644 index 0000000..772fd60 --- /dev/null +++ b/tests/random.at @@ -0,0 +1,54 @@ +# Check functions for other miscellaneous functions + +# Test suite for the IGraph library. +# Copyright (C) 2011-2012 Gabor Csardi +# 334 Harvard st, Cambridge MA, 02139, USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([Random number generators]) + +AT_SETUP([Random seed:]) +AT_KEYWORDS([RNG seed random]) +AT_COMPILE_CHECK([simple/random_seed.c]) +AT_CLEANUP + +AT_SETUP([RNG reproducibility:]) +AT_KEYWORDS([RNG seed random]) +AT_COMPILE_CHECK([tests/rng_reproducibility.c], + [tests/rng_reproducibility.out]) +AT_CLEANUP + +AT_SETUP([MT19937 RNG on 64 bit machines:]) +AT_KEYWORDS([RNG MT19937]) +AT_COMPILE_CHECK([simple/mt.c]) +AT_CLEANUP + +AT_SETUP([Exponentially distributed random numbers:]) +AT_KEYWORDS([exponential random numbers]) +AT_COMPILE_CHECK([simple/igraph_rng_get_exp.c], + [simple/igraph_rng_get_exp.out]) +AT_CLEANUP + +AT_SETUP([Random sampling from consecutive sequence:]) +AT_KEYWORDS([random sampling]) +AT_COMPILE_CHECK([simple/igraph_random_sample.c]) +AT_CLEANUP + +AT_SETUP([Fisher-Yates shuffle:]) +AT_KEYWORDS([Fisher-Yates shuffle random permutation]) +AT_COMPILE_CHECK([simple/igraph_fisher_yates_shuffle.c]) +AT_CLEANUP diff --git a/tests/scg.at b/tests/scg.at new file mode 100644 index 0000000..08888e3 --- /dev/null +++ b/tests/scg.at @@ -0,0 +1,79 @@ +# Spectral coarse graining + +# Test suite for the IGraph library. +# Copyright (C) 2011-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Spectral coarse graining]]) + +AT_SETUP([Solving the SCG problem (igraph_scg_grouping) :]) +AT_KEYWORDS([SCG spectral coarse graining grouping]) +AT_COMPILE_CHECK([simple/igraph_scg_grouping.c], + [simple/igraph_scg_grouping.out]) +AT_CLEANUP + +AT_SETUP([Solving the SCG problem, adjacency matrix (igraph_scg_grouping) :]) +AT_KEYWORDS([SCG spectral coarse graining grouping adjacency]) +AT_COMPILE_CHECK([simple/igraph_scg_grouping2.c], + [simple/igraph_scg_grouping2.out]) +AT_CLEANUP + +AT_SETUP([Solving the SCG problem, stochastic matrix (igraph_scg_grouping) :]) +AT_KEYWORDS([SCG spectral coarse graining grouping stochastic]) +AT_COMPILE_CHECK([simple/igraph_scg_grouping3.c], + [simple/igraph_scg_grouping3.out]) +AT_CLEANUP + +AT_SETUP([Solving the SCG problem, laplacian matrix (igraph_scg_grouping) :]) +AT_KEYWORDS([SCG spectral coarse graining grouping laplacian]) +AT_COMPILE_CHECK([simple/igraph_scg_grouping4.c], + [simple/igraph_scg_grouping4.out]) +AT_CLEANUP + +AT_SETUP([SCG semi-projectors, symmetric (igraph_scg_semiprojectors) :]) +AT_KEYWORDS([SCG spectral coarse graining semi-projectors adjancency]) +AT_COMPILE_CHECK([simple/igraph_scg_semiprojectors.c], + [simple/igraph_scg_semiprojectors.out]) +AT_CLEANUP + +AT_SETUP([SCG semi-projectors, stochastic (igraph_scg_semiprojectors) :]) +AT_KEYWORDS([SCG spectral coarse graining semi-projectors stochastic]) +AT_COMPILE_CHECK([simple/igraph_scg_semiprojectors2.c], + [simple/igraph_scg_semiprojectors2.out]) +AT_CLEANUP + +AT_SETUP([SCG semi-projectors, laplacian (igraph_scg_semiprojectors) :]) +AT_KEYWORDS([SCG spectral coarse graining semi-projectors laplacian]) +AT_COMPILE_CHECK([simple/igraph_scg_semiprojectors3.c], + [simple/igraph_scg_semiprojectors3.out]) +AT_CLEANUP + +AT_SETUP([SCG of a graph, adjacency matrix (igraph_scg) :]) +AT_KEYWORDS([SCG spectral coarse graining]) +AT_COMPILE_CHECK([simple/scg.c], [simple/scg.out]) +AT_CLEANUP + +AT_SETUP([SCG of a graph, stochastic matrix (igraph_scg) :]) +AT_KEYWORDS([SCG spectral coarse graining]) +AT_COMPILE_CHECK([simple/scg2.c], [simple/scg2.out]) +AT_CLEANUP + +AT_SETUP([SCG of a graph, laplacian matrix (igraph_scg) :]) +AT_KEYWORDS([SCG spectral coarse graining]) +AT_COMPILE_CHECK([simple/scg3.c], [simple/scg3.out]) +AT_CLEANUP diff --git a/tests/separators.at b/tests/separators.at new file mode 100644 index 0000000..7c01a60 --- /dev/null +++ b/tests/separators.at @@ -0,0 +1,53 @@ +# Minimal separators + +# Test suite for the IGraph library. +# Copyright (C) 2010-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Minimal separators]]) + +AT_SETUP([Decision problem (igraph_is_separator): ]) +AT_KEYWORDS([vertex separator]) +AT_COMPILE_CHECK([simple/igraph_is_separator.c]) +AT_CLEANUP + +AT_SETUP([Decision problem, minimal separator (igraph_is_minimal separator): ]) +AT_KEYWORDS([minimal vertex separator]) +AT_COMPILE_CHECK([simple/igraph_is_minimal_separator.c]) +AT_CLEANUP + +AT_SETUP([Minimal separators (igraph_all_minimal_ab_separators): ]) +AT_KEYWORDS([minimal separator]) +AT_COMPILE_CHECK([simple/igraph_minimal_separators.c]) +AT_CLEANUP + +AT_SETUP([Minimal separators, bug 1033045 (igraph_all_minimal_st_separators): ]) +AT_KEYWORDS([minimal separator]) +AT_COMPILE_CHECK([simple/bug-1033045.c], [simple/bug-1033045.out]) +AT_CLEANUP + +AT_SETUP([Minimum size separators (igraph_minimum_size_separators): ]) +AT_KEYWORDS([minimum size separators]) +AT_COMPILE_CHECK([simple/igraph_minimum_size_separators.c], + [simple/igraph_minimum_size_separators.out]) +AT_CLEANUP + +AT_SETUP([Cohesive blocking (igraph_cohesive_blocks): ]) +AT_KEYWORDS([structurally cohesive blocks]) +AT_COMPILE_CHECK([simple/cohesive_blocks.c], [simple/cohesive_blocks.out]) +AT_CLEANUP diff --git a/tests/structural_properties.at b/tests/structural_properties.at new file mode 100644 index 0000000..d52eb37 --- /dev/null +++ b/tests/structural_properties.at @@ -0,0 +1,230 @@ +# Check functions calculating structural properties + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Structural properties]]) + +AT_SETUP([Two vertices connected by an edge:]) +AT_KEYWORDS([igraph_are_connected]) +AT_COMPILE_CHECK([simple/igraph_are_connected.c]) +AT_CLEANUP + +AT_SETUP([Density of a graph (igraph_density):]) +AT_KEYWORDS([igraph_density density]) +AT_COMPILE_CHECK([simple/igraph_density.c], [simple/igraph_density.out]) +AT_CLEANUP + +AT_SETUP([Diameter of a graph (igraph_diameter):]) +AT_KEYWORDS([igraph_diameter]) +AT_COMPILE_CHECK([simple/igraph_diameter.c], [simple/igraph_diameter.out]) +AT_CLEANUP + +AT_SETUP([Average geodesic length (igraph_average_path_length): ]) +AT_KEYWORDS([igraph_average_path_length]) +AT_COMPILE_CHECK([simple/igraph_average_path_length.c]) +AT_CLEANUP + +AT_SETUP([Google PageRank (igraph_pagerank): ]) +AT_KEYWORDS([igraph_pagerank]) +AT_COMPILE_CHECK([simple/igraph_pagerank.c], [simple/igraph_pagerank.out]) +AT_CLEANUP + +AT_SETUP([Random rewiring (igraph_rewire): ]) +AT_KEYWORDS([igraph_rewire]) +AT_COMPILE_CHECK([simple/igraph_rewire.c]) +AT_CLEANUP + +AT_SETUP([Get the shortest paths (igraph_get_shortest_paths): ]) +AT_KEYWORDS([igraph_get_shortest_paths shortest paths geodesic]) +AT_COMPILE_CHECK([simple/igraph_get_shortest_paths.c], + [simple/igraph_get_shortest_paths.out]) +AT_CLEANUP + +AT_SETUP([Get the shortest paths 2 (igraph_get_shortest_paths): ]) +AT_KEYWORDS([igraph_get_shortest_paths shortest paths geodesic]) +AT_COMPILE_CHECK([simple/igraph_get_shortest_paths2.c], + [simple/igraph_get_shortest_paths2.out]) +AT_CLEANUP + +AT_SETUP([Weighted shortest paths (Dijkstra): ]) +AT_KEYWORDS([igraph_shortest_paths_dijkstra Dijkstra shortest paths geodesic]) +AT_COMPILE_CHECK([simple/dijkstra.c], [simple/dijkstra.out]) +AT_CLEANUP + +AT_SETUP([Weighted shortest paths (Bellman-Ford): ]) +AT_KEYWORDS([igraph_shortest_paths_bellman_ford Bellman-Ford shortest paths geodesic]) +AT_COMPILE_CHECK([simple/bellman_ford.c], [simple/bellman_ford.out]) +AT_CLEANUP + +AT_SETUP([Get the weighted shortest paths (Dijkstra): ]) +AT_KEYWORDS([igraph_get_shortest_paths_dijkstra Dijkstra shortest paths geodesic]) +AT_COMPILE_CHECK([simple/igraph_get_shortest_paths_dijkstra.c], + [simple/igraph_get_shortest_paths_dijkstra.out]) +AT_CLEANUP + +AT_SETUP([Get all weighted shortest paths (Dijkstra): ]) +AT_KEYWORDS([igraph_get_all_shortest_paths_dijkstra Dijkstra shortest paths geodesic]) +AT_COMPILE_CHECK([simple/igraph_get_all_shortest_paths_dijkstra.c], + [simple/igraph_get_all_shortest_paths_dijkstra.out]) +AT_CLEANUP + +AT_SETUP([Get all simple paths: ]) +AT_KEYWORDS([igraph_get_all_simple_paths simple paths]) +AT_COMPILE_CHECK([simple/igraph_get_all_simple_paths.c], + [simple/igraph_get_all_simple_paths.out]) +AT_CLEANUP + +AT_SETUP([Shortest path wrappers for single target node: ]) +AT_KEYWORDS([igraph_get_shortest_path igraph_get_shortest_path_dijkstra]) +AT_COMPILE_CHECK([simple/single_target_shortest_path.c], + [simple/single_target_shortest_path.out]) +AT_CLEANUP + +AT_SETUP([Betweenness (igraph_betweenness): ]) +AT_KEYWORDS([igraph_betweenness betweenness]) +AT_COMPILE_CHECK([simple/igraph_betweenness.c]) +AT_CLEANUP + +AT_SETUP([Betweenness, big integers (igraph_betweenness): ]) +AT_KEYWORDS([igraph_betweenness betweenness arbitrarily large integers biguint bigint]) +AT_COMPILE_CHECK([simple/biguint_betweenness.c]) +AT_CLEANUP + +AT_SETUP([Edge betweenness (igraph_edge_betweenness): ]) +AT_KEYWORDS([igraph_edge_betweenness betwenness]) +AT_COMPILE_CHECK([simple/igraph_edge_betweenness.c], + [simple/igraph_edge_betweenness.out]) +AT_CLEANUP + +AT_SETUP([Vertex closeness estimate (igraph_closeness): ]) +AT_KEYWORDS([igraph_closeness closeness]) +AT_COMPILE_CHECK([tests/igraph_closeness.c], + [tests/igraph_closeness.out]) +AT_CLEANUP + +AT_SETUP([Transitivity (igraph_transitivity): ]) +AT_KEYWORDS([igraph_transitivity transitivity igraph_transitivity_undirected]) +AT_COMPILE_CHECK([simple/igraph_transitivity.c]) +AT_CLEANUP + +AT_SETUP([Local transitivity (igraph_local_transitivity): ]) +AT_KEYWORDS([transitivity igraph_transitivity_local_undirected]) +AT_COMPILE_CHECK([simple/igraph_local_transitivity.c]) +AT_CLEANUP + +AT_SETUP([Reciprocity (igraph_reciprocity): ]) +AT_KEYWORDS([igraph_reciprocity reciprocity]) +AT_COMPILE_CHECK([simple/igraph_reciprocity.c]) +AT_CLEANUP + +AT_SETUP([Minimum spanning tree (igraph_minimum_spanning_tree_*): ]) +AT_KEYWORDS([igraph_minimum_spanning_tree_prim Prim minimum spanning tree]) +AT_COMPILE_CHECK([simple/igraph_minimum_spanning_tree.c], + [simple/igraph_minimum_spanning_tree.out]) +AT_CLEANUP + +AT_SETUP([Cocitation and bibcoupling (igraph_cocitation,igraph_bibcoupling):]) +AT_KEYWORDS([cocitation bibliographic coupling]) +AT_COMPILE_CHECK([simple/igraph_cocitation.c], [simple/igraph_cocitation.out]) +AT_CLEANUP + +AT_SETUP([Similarity coefficients (igraph_similarity_*):]) +AT_KEYWORDS([similarity jaccard dice]) +AT_COMPILE_CHECK([simple/igraph_similarity.c], [simple/igraph_similarity.out]) +AT_CLEANUP + +AT_SETUP([Simplification of non-simple graphs (igraph_simplify): ]) +AT_KEYWORDS([simplify multiple edge loop edges non-simple graphs simple graphs]) +AT_COMPILE_CHECK([simple/igraph_simplify.c], [simple/igraph_simplify.out]) +AT_CLEANUP + +AT_SETUP([Topological sorting (igraph_topological_sorting, igraph_is_dag): ]) +AT_KEYWORDS([topological sorting directed acyclic graphs]) +AT_COMPILE_CHECK([simple/igraph_topological_sorting.c], [simple/igraph_topological_sorting.out]) +AT_CLEANUP + +AT_SETUP([Feedback arc sets, Eades heuristics (igraph_feedback_arc_set): ]) +AT_KEYWORDS([feedback arc set directed graphs]) +AT_COMPILE_CHECK([simple/igraph_feedback_arc_set.c], [simple/igraph_feedback_arc_set.out]) +AT_CLEANUP + +AT_SETUP([Feedback arc sets, integer programming (igraph_feedback_arc_set): ]) +AT_KEYWORDS([feedback arc set directed graphs]) +AT_COMPILE_CHECK([simple/igraph_feedback_arc_set_ip.c], [simple/igraph_feedback_arc_set_ip.out]) +AT_CLEANUP + +AT_SETUP([Loop edges test (igraph_is_loop): ]) +AT_KEYWORDS([loop edge igraph_is_loop]) +AT_COMPILE_CHECK([simple/igraph_is_loop.c], [simple/igraph_is_loop.out]) +AT_CLEANUP + +AT_SETUP([Multiple edges test (igraph_is_multiple): ]) +AT_KEYWORDS([multiple edge parallel edge igraph_is_multiple]) +AT_COMPILE_CHECK([simple/igraph_is_multiple.c], [simple/igraph_is_multiple.out]) +AT_CLEANUP + +AT_SETUP([Multiple edges test (igraph_has_multiple): ]) +AT_KEYWORDS([multiple edge parallel edge igraph_has_multiple]) +AT_COMPILE_CHECK([simple/igraph_has_multiple.c]) +AT_CLEANUP + +AT_SETUP([Tree test (igraph_is_tree): ]) +AT_KEYWORDS([tree igraph_is_tree]) +AT_COMPILE_CHECK([simple/igraph_is_tree.c]) +AT_CLEANUP + +AT_SETUP([Girth (igraph_girth): ]) +AT_KEYWORDS([girth igraph_girth]) +AT_COMPILE_CHECK([simple/igraph_girth.c]) +AT_CLEANUP + +AT_SETUP([Convergence degree (igraph_convergence_degree): ]) +AT_KEYWORDS([edge convergence degree igraph_convergence_degree]) +AT_COMPILE_CHECK([simple/igraph_convergence_degree.c], [simple/igraph_convergence_degree.out]) +AT_CLEANUP + +AT_SETUP([Assortativity coefficient (igraph_assortativity): ]) +AT_KEYWORDS([assortativity mixing igraph_assortativity]) +AT_COMPILE_CHECK([simple/assortativity.c], [simple/assortativity.out], + [simple/{karate,celegansneural}.gml]) +AT_CLEANUP + +AT_SETUP([Average nearest neighbor degree (igraph_avg_nearest_neighbor_degree): ]) +AT_KEYWORDS([nearest neighbor degree degree correlations]) +AT_COMPILE_CHECK([simple/igraph_knn.c]) +AT_CLEANUP + +AT_SETUP([Transitive closure of a DAG (igraph_transitive_closure_dag): ]) +AT_KEYWORDS([transitive closure DAG]) +AT_COMPILE_CHECK([simple/igraph_transitive_closure_dag.c], + [simple/igraph_transitive_closure_dag.out]) +AT_CLEANUP + +AT_SETUP([Eccentricity (igraph_eccentricity): ]) +AT_KEYWORDS([eccentricity]) +AT_COMPILE_CHECK([simple/igraph_eccentricity.c], + [simple/igraph_eccentricity.out]) +AT_CLEANUP + +AT_SETUP([Radius (igraph_radius): ]) +AT_KEYWORDS([radius eccentricity]) +AT_COMPILE_CHECK([simple/igraph_radius.c]) +AT_CLEANUP + diff --git a/tests/structure_generators.at b/tests/structure_generators.at new file mode 100644 index 0000000..0875069 --- /dev/null +++ b/tests/structure_generators.at @@ -0,0 +1,142 @@ +# Check graph generators + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Structure generators]]) + +AT_SETUP([Simple graph creation (igraph_create): ]) +AT_KEYWORDS([igraph_create]) +AT_COMPILE_CHECK([simple/igraph_create.c]) +AT_CLEANUP + +AT_SETUP([Barabasi-Albert model (igraph_barabasi_game):]) +AT_KEYWORDS([barabasi barabasi-albert igraph_barabasi_game]) +AT_COMPILE_CHECK([simple/igraph_barabasi_game.c]) +AT_CLEANUP + +AT_SETUP([More Barabasi-Albert model (igraph_barabasi_game):]) +AT_KEYWORDS([barabasi barabasi-albert igraph_barabasi_game]) +AT_COMPILE_CHECK([simple/igraph_barabasi_game2.c]) +AT_CLEANUP + +AT_SETUP([Erdos-Renyi model (igraph_erdos_renyi_game):]) +AT_KEYWORDS([erdos renyi erdos-renyi igraph_erdos_renyi_game]) +AT_COMPILE_CHECK([simple/igraph_erdos_renyi_game.c]) +AT_CLEANUP + +AT_SETUP([Degree sequence (igraph_degree_sequence_game):]) +AT_KEYWORDS([degree sequence igraph_degree_sequence_game]) +AT_COMPILE_CHECK([simple/igraph_degree_sequence_game.c], [simple/igraph_degree_sequence_game.out]) +AT_CLEANUP + +AT_SETUP([k-regular graphs (igraph_k_regular_game):]) +AT_KEYWORDS([regular k-regular igraph_k_regular_game]) +AT_COMPILE_CHECK([simple/igraph_k_regular_game.c], [simple/igraph_k_regular_game.out]) +AT_CLEANUP + +AT_SETUP([Growing random (igraph_growing_random_game):]) +AT_KEYWORDS([growing random game igraph_growing_random_game]) +AT_COMPILE_CHECK([simple/igraph_growing_random_game.c]) +AT_CLEANUP + +AT_SETUP([Preference model (igraph_preference_game):]) +AT_KEYWORDS([preference game igraph_preference_game igraph_asymmetric_preference_game]) +AT_COMPILE_CHECK([simple/igraph_preference_game.c]) +AT_CLEANUP + +AT_SETUP([From adjacency matrix (igraph_adjacency):]) +AT_KEYWORDS([adjacency matrix igraph_adjacency]) +AT_COMPILE_CHECK([simple/igraph_adjacency.c]) +AT_CLEANUP + +AT_SETUP([From weighted adjacency matrix (igraph_weighted_adjacency):]) +AT_KEYWORDS([weighted adjacency matrix igraph_weighted_adjacency]) +AT_COMPILE_CHECK([simple/igraph_weighted_adjacency.c], [simple/igraph_weighted_adjacency.out]) +AT_CLEANUP + +AT_SETUP([Star graph (igraph_star):]) +AT_KEYWORDS([star igraph_star]) +AT_COMPILE_CHECK([simple/igraph_star.c]) +AT_CLEANUP + +AT_SETUP([Lattice graph (igraph_lattice):]) +AT_KEYWORDS([lattice igraph_lattice]) +AT_COMPILE_CHECK([simple/igraph_lattice.c]) +AT_CLEANUP + +AT_SETUP([Ring graph (igraph_ring):]) +AT_KEYWORDS([ring igraph_ring]) +AT_COMPILE_CHECK([simple/igraph_ring.c]) +AT_CLEANUP + +AT_SETUP([Tree graph (igraph_tree):]) +AT_KEYWORDS([tree igraph_tree]) +AT_COMPILE_CHECK([simple/igraph_tree.c], [simple/igraph_tree.out]) +AT_CLEANUP + +AT_SETUP([Tree graph 2 (igraph_tree):]) +AT_KEYWORDS([tree igraph_tree]) +AT_COMPILE_CHECK([tests/tree.c], [tests/tree.out]) +AT_CLEANUP + +AT_SETUP([Tree graph from Prufer sequence (igraph_from_prufer):]) +AT_KEYWORDS([tree igraph_from_prufer]) +AT_COMPILE_CHECK([simple/igraph_from_prufer.c], [simple/igraph_from_prufer.out]) +AT_CLEANUP + +AT_SETUP([Full graph (igraph_full):]) +AT_KEYWORDS([full igraph_full]) +AT_COMPILE_CHECK([simple/igraph_full.c]) +AT_CLEANUP + +AT_SETUP([Graph atlas (igraph_atlas):]) +AT_KEYWORDS([atlas igraph_atlas]) +AT_COMPILE_CHECK([simple/igraph_atlas.c], [simple/igraph_atlas.out]) +AT_CLEANUP + +AT_SETUP([Small graph (igraph_small):]) +AT_KEYWORDS([graph constructor small igraph_small]) +AT_COMPILE_CHECK([simple/igraph_small.c], [simple/igraph_small.out]) +AT_CLEANUP + +AT_SETUP([Geomeric random graphs (igraph_grg_game):]) +AT_KEYWORDS([graph GRG grg geometric random graph igraph_grg_game]) +AT_COMPILE_CHECK([simple/igraph_grg_game.c]) +AT_CLEANUP + +AT_SETUP([Graphs in LCF notation (igraph_lcf{,_vector}):]) +AT_KEYWORDS([LCF graph constructor]) +AT_COMPILE_CHECK([simple/igraph_lcf.c]) +AT_CLEANUP + +AT_SETUP([Watts-Strogatz graphs (igraph_watts_strogatz_game):]) +AT_KEYWORDS([small world small-world Watts Strogratz]) +AT_COMPILE_CHECK([simple/watts_strogatz_game.c]) +AT_CLEANUP + +AT_SETUP([Correlated random graphs (igraph_correlated_game):]) +AT_KEYWORDS([correlated random graph]) +AT_COMPILE_CHECK([simple/igraph_correlated_game.c]) +AT_CLEANUP + +AT_SETUP([Realize a degree sequence (igraph_realize_degree_sequence):]) +AT_KEYWORDS([degree sequence]) +AT_COMPILE_CHECK([simple/igraph_realize_degree_sequence.c], [simple/igraph_realize_degree_sequence.out]) +AT_CLEANUP diff --git a/tests/testsuite.at b/tests/testsuite.at new file mode 100644 index 0000000..45fcf4a --- /dev/null +++ b/tests/testsuite.at @@ -0,0 +1,64 @@ +# Process this file with autom4te to create testsuite. -*- Autotest -*- + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_INIT([igraph]) + +m4_define([AT_COMPILE_CHECK], [ +AT_CHECK([m4_if([[$4]],[[]], + [[${CC} ${CFLAGS} ${abs_top_srcdir}/examples/$1 -I${abs_top_srcdir}/include -I${abs_top_builddir}/include -L${abs_top_builddir}/src/.libs -ligraph -lm $5 -o itest]], + [[${CC} ${CFLAGS} ${abs_top_srcdir}/examples/$1 -I${abs_top_srcdir}/include -I${abs_top_srcdir}/src -I${abs_top_builddir}/include -I${abs_top_builddir} -L${abs_top_builddir}/src/.libs -ligraph -lm $5 -o itest]])]) +AT_CHECK([m4_if([[$2]],[[]],[[>expout]],[[cat ${abs_top_srcdir}/examples/'$2' | sed "s/@VERSION@/$(cat ${abs_top_srcdir}/IGRAPH_VERSION)/g" > expout]])]) +AT_CHECK([m4_if([[$3]],[[]],[[]],[[cp ${abs_top_srcdir}/examples/$3 .]])]) +AT_CHECK([DYLD_LIBRARY_PATH=${abs_top_builddir}/src/.libs${DYLD_LIBRARY_PATH+:$DYLD_LIBRARY_PATH} LD_LIBRARY_PATH=${abs_top_builddir}/src/.libs${LD_LIBRARY_PATH+:$LD_LIBRARY_PATH} ./itest], [], [expout])]) + +m4_include([version.at]) +m4_include([types.at]) +m4_include([basic.at]) +m4_include([iterators.at]) +m4_include([structure_generators.at]) +m4_include([structural_properties.at]) +m4_include([components.at]) +m4_include([layout.at]) +m4_include([visitors.at]) +m4_include([topology.at]) +m4_include([coloring.at]) +m4_include([motifs.at]) +m4_include([foreign.at]) +m4_include([other.at]) +m4_include([operators.at]) +m4_include([conversion.at]) +m4_include([flow.at]) +m4_include([community.at]) +m4_include([cliques.at]) +m4_include([eigen.at]) +m4_include([attributes.at]) +m4_include([arpack.at]) +m4_include([bipartite.at]) +m4_include([centralization.at]) +m4_include([separators.at]) +m4_include([hrg.at]) +m4_include([microscopic.at]) +m4_include([mt.at]) +m4_include([scg.at]) +m4_include([random.at]) +m4_include([qsort.at]) +m4_include([matching.at]) +m4_include([embedding.at]) diff --git a/tests/topology.at b/tests/topology.at new file mode 100644 index 0000000..094c576 --- /dev/null +++ b/tests/topology.at @@ -0,0 +1,65 @@ +# Check graph topology related functions + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Graph topology]]) + +AT_SETUP([The isomorphism class of a subgraph (igraph_isolass_subgraph)]) +AT_KEYWORDS([isomorph isomorphism class motif]) +AT_COMPILE_CHECK([simple/topology.c], [simple/topology.out]) +AT_CLEANUP + +AT_SETUP([The VF2 isomorphism algorithm]) +AT_KEYWORDS([isomorph isomorphic VF2]) +AT_COMPILE_CHECK([simple/igraph_isomorphic_vf2.c]) +AT_CLEANUP + +AT_SETUP([The BLISS isomorphism algorithm]) +AT_KEYWORDS([isomorph isomorphic BLISS]) +AT_COMPILE_CHECK([simple/igraph_isomorphic_bliss.c]) +AT_CLEANUP + + +AT_SETUP([VF algorithm with compatibility functions]) +AT_KEYWORDS([isomorph isomorphic VF2 compatibility]) +AT_COMPILE_CHECK([simple/VF2-compat.c]) +AT_CLEANUP + +AT_SETUP([LAD subgraph isomorphism algorithm]) +AT_KEYWORDS([isomorph isomorphic subgraph isomorphism LAD]) +AT_COMPILE_CHECK([simple/igraph_subisomorphic_lad.c], + [simple/igraph_subisomorphic_lad.out]) +AT_CLEANUP + +AT_SETUP([Additional isomorphism tests]) +AT_KEYWORDS([isomorph isomorphic isomorphism BLISS VF2]) +AT_COMPILE_CHECK([simple/isomorphism_test.c], + [simple/isomorphism_test.out]) +AT_CLEANUP + +AT_SETUP([Simplify and colorize]) +AT_KEYWORDS([simplify multigraph colorize isomorphism]) +AT_COMPILE_CHECK([tests/simplify_and_colorize.c], [tests/simplify_and_colorize.out]) +AT_CLEANUP + +AT_SETUP([Graphical degree sequences]) +AT_KEYWORDS([degree sequence graphical]) +AT_COMPILE_CHECK([simple/igraph_is_degree_sequence.c]) +AT_CLEANUP diff --git a/tests/types.at b/tests/types.at new file mode 100644 index 0000000..6f82046 --- /dev/null +++ b/tests/types.at @@ -0,0 +1,211 @@ +# Check the utility types (vector_t, etc.) + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Utility types (vector_t, etc.)]]) + +AT_SETUP([Vector (vector_t): ]) +AT_KEYWORDS([vector vector_t]) +AT_COMPILE_CHECK([simple/vector.c], [simple/vector.out]) +AT_CLEANUP + +AT_SETUP([Vector (more) (vector_t): ]) +AT_KEYWORDS([vector vector_t]) +AT_COMPILE_CHECK([simple/vector2.c], [simple/vector2.out]) +AT_CLEANUP + +AT_SETUP([Vector (even more) (vector_t): ]) +AT_KEYWORDS([vector vector_t]) +AT_COMPILE_CHECK([simple/vector3.c]) +AT_CLEANUP + +AT_SETUP([Matrix (matrix_t): ]) +AT_KEYWORDS([matrix matrix_t]) +AT_COMPILE_CHECK([simple/matrix.c], [simple/matrix.out]) +AT_CLEANUP + +AT_SETUP([Matrix (more) (matrix_t): ]) +AT_KEYWORDS([matrix matrix_t]) +AT_COMPILE_CHECK([simple/matrix2.c], [simple/matrix2.out]) +AT_CLEANUP + +AT_SETUP([Matrix (even more) (matrix_t): ]) +AT_KEYWORDS([matrix matrix_t]) +AT_COMPILE_CHECK([simple/matrix3.c]) +AT_CLEANUP + +AT_SETUP([Double ended queue (dqueue_t): ]) +AT_KEYWORDS([dqueue double queue dqueue_t]) +AT_COMPILE_CHECK([simple/dqueue.c], [simple/dqueue.out]) +AT_CLEANUP + +AT_SETUP([Vector of pointers (vector_ptr_t): ]) +AT_KEYWORDS([vector pointers vector_ptr_t]) +AT_COMPILE_CHECK([simple/vector_ptr.c]) +AT_CLEANUP + +AT_SETUP([Stack (stack_t): ]) +AT_KEYWORDS([stack stack_t]) +AT_COMPILE_CHECK([simple/stack.c]) +AT_CLEANUP + +AT_SETUP([Heap (heap_t): ]) +AT_KEYWORDS([heap heap_t]) +AT_COMPILE_CHECK([simple/heap.c]) +AT_CLEANUP + +AT_SETUP([Indexed heap (indheap_t): ]) +AT_KEYWORDS([indexed heap indheap_t]) +AT_COMPILE_CHECK([simple/indheap.c], [], [], [INTERNAL]) +AT_CLEANUP + +AT_SETUP([Doubly indexed heap (d_indheap_t): ]) +AT_KEYWORDS([doubly indexed heap d_indheap_t]) +AT_COMPILE_CHECK([simple/d_indheap.c], [simple/d_indheap.out], [], [INTERNAL]) +AT_CLEANUP + +AT_SETUP([String vector (igraph_strvector_t): ]) +AT_KEYWORDS([string vector igraph_strvector_t]) +AT_COMPILE_CHECK([simple/igraph_strvector.c], [simple/igraph_strvector.out]) +AT_CLEANUP + +AT_SETUP([Trie (igraph_trie_t): ]) +AT_KEYWORDS([trie igraph_trie_t]) +AT_COMPILE_CHECK([simple/igraph_trie.c], [simple/igraph_trie.out], [], + [INTERNAL]) +AT_CLEANUP + +AT_SETUP([Partial Sum-Tree (igraph_psumtree_t): ]) +AT_KEYWORDS([partial sumtree igraph_psumtree_t]) +AT_COMPILE_CHECK([simple/igraph_psumtree.c]) +AT_CLEANUP + +AT_SETUP([Three dimensional array (igraph_array3_t): ]) +AT_KEYWORDS([array array3 three dimensional array]) +AT_COMPILE_CHECK([simple/igraph_array.c], [simple/igraph_array.out]) +AT_CLEANUP + +AT_SETUP([Hash table (string->string) (igraph_hashtable_t): ]) +AT_KEYWORDS([igraph_hashtable_t hash table]) +AT_COMPILE_CHECK([simple/igraph_hashtable.c], [simple/igraph_hashtable.out], + [], [INTERNAL]) +AT_CLEANUP + +AT_SETUP([Special heap for minimum cuts (igraph_i_cutheap_t): ]) +AT_KEYWORDS([heap minimum cut]) +AT_COMPILE_CHECK([simple/igraph_i_cutheap.c], [simple/igraph_i_cutheap.out], + [], [INTERNAL]) +AT_CLEANUP + +AT_SETUP([Set (igraph_set_t): ]) +AT_KEYWORDS([set igraph_set_t]) +AT_COMPILE_CHECK([simple/igraph_set.c], [simple/igraph_set.out], + [], [INTERNAL]) +AT_CLEANUP + +AT_SETUP([2-way heap (igraph_2wheap_t): ]) +AT_KEYWORDS([heap two-way 2-way igraph_2wheap_t]) +AT_COMPILE_CHECK([simple/2wheap.c], [], [], [INTERNAL]) +AT_CLEANUP + +AT_SETUP([Sparse matrix (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t]) +AT_COMPILE_CHECK([simple/igraph_sparsemat.c], [simple/igraph_sparsemat.out]) +AT_CLEANUP + +AT_SETUP([Sparse matrix, multiplications (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t]) +AT_COMPILE_CHECK([simple/igraph_sparsemat2.c], [simple/igraph_sparsemat2.out], + [], [INTERNAL], [-lblas]) +AT_CLEANUP + +AT_SETUP([Sparse matrix, indexing (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t]) +AT_COMPILE_CHECK([simple/igraph_sparsemat3.c], [simple/igraph_sparsemat3.out], + [], [INTERNAL]) +AT_CLEANUP + +AT_SETUP([Sparse matrix, solvers (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t]) +AT_COMPILE_CHECK([simple/igraph_sparsemat4.c], [simple/igraph_sparsemat4.out], + [], [INTERNAL]) +AT_CLEANUP + +AT_SETUP([Sparse matrix, ARPACK eigensolver (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t ARPACK]) +AT_COMPILE_CHECK([simple/igraph_sparsemat5.c], [simple/igraph_sparsemat5.out]) +AT_CLEANUP + +AT_SETUP([Sparse matrix, conversion to dense (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t]) +AT_COMPILE_CHECK([simple/igraph_sparsemat6.c]) +AT_CLEANUP + +AT_SETUP([Sparse matrix, min & max (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t]) +AT_COMPILE_CHECK([simple/igraph_sparsemat7.c]) +AT_CLEANUP + +AT_SETUP([Sparse matrix, other operations (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t]) +AT_COMPILE_CHECK([simple/igraph_sparsemat8.c]) +AT_CLEANUP + +AT_SETUP([Sparse matrix, multiplications with dense (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t sparse-dense dense-sparse]) +AT_COMPILE_CHECK([simple/igraph_sparsemat9.c]) +AT_CLEANUP + +AT_SETUP([Sparse matrix, is symmetric? (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t symmetric is_symmetric]) +AT_COMPILE_CHECK([simple/igraph_sparsemat_is_symmetric.c]) +AT_CLEANUP + +AT_SETUP([Sparse matrix col/row min/max (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t]) +AT_COMPILE_CHECK([simple/igraph_sparsemat_minmax.c], + [simple/igraph_sparsemat_minmax.out]) +AT_CLEANUP + +AT_SETUP([Sparse matrix which col/row min/max (igraph_sparsemat_t): ]) +AT_KEYWORDS([sparse matrix igraph_sparsemat_t minimum maximum]) +AT_COMPILE_CHECK([simple/igraph_sparsemat_which_minmax.c], + [simple/igraph_sparsemat_which_minmax.out]) +AT_CLEANUP + +AT_SETUP([Another sparse matrix (igraph_spmatrix_t): ]) +AT_KEYWORDS([sparse matrix igraph_spmatrix_t]) +AT_COMPILE_CHECK([simple/spmatrix.c], [simple/spmatrix.out]) +AT_CLEANUP + +AT_SETUP([Arbitrarily big integers (igraph_biguint_t): ]) +AT_KEYWORDS([bignum bigint big integer arbitrarily]) +AT_COMPILE_CHECK([simple/biguint.c],[simple/biguint.out],[],[INTERNAL]) +AT_CLEANUP + +AT_SETUP([Marked double ended queue (igraph_marked_queue_t): ]) +AT_KEYWORDS([dqueue queue igraph_marked_queue_t]) +AT_COMPILE_CHECK([simple/igraph_marked_queue.c], [], [], [INTERNAL]) +AT_CLEANUP + +AT_SETUP([Complex numbers (igraph_complex_t): ]) +AT_KEYWORDS([complex]) +AT_COMPILE_CHECK([simple/igraph_complex.c]) +AT_CLEANUP diff --git a/tests/version.at b/tests/version.at new file mode 100644 index 0000000..cdd533d --- /dev/null +++ b/tests/version.at @@ -0,0 +1,29 @@ +# Query version number + +# Test suite for the IGraph library. +# Copyright (C) 2010-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +# Macros + +AT_BANNER([[igraph version number.]]) + +AT_SETUP([Simple version query (igraph_version): ]) +AT_KEYWORDS([version igraph_version]) +AT_COMPILE_CHECK([simple/igraph_version.c]) +AT_CLEANUP diff --git a/tests/visitors.at b/tests/visitors.at new file mode 100644 index 0000000..8590a8b --- /dev/null +++ b/tests/visitors.at @@ -0,0 +1,37 @@ +# Check functions for different visitor-like functions + +# Test suite for the IGraph library. +# Copyright (C) 2005-2012 Gabor Csardi +# 334 Harvard street, Cambridge, MA 02139 USA +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +AT_BANNER([[Visitors]]) + +AT_SETUP([Internal breadth-first search (igraph_i_bfs):]) +AT_KEYWORDS([igraph_i_bfs bfs breadth-first visitor]) +AT_COMPILE_CHECK([simple/igraph_bfs.c], [simple/igraph_bfs.out]) +AT_CLEANUP + +AT_SETUP([Breadth-first search (igraph_bfs):]) +AT_KEYWORDS([igraph_bfs bfs breadth-first visitor]) +AT_COMPILE_CHECK([simple/igraph_bfs2.c], [simple/igraph_bfs2.out]) +AT_CLEANUP + +AT_SETUP([Random walk (igraph_random_edge_walk):]) +AT_KEYWORDS([igraph_random_edge_walk random_walk]) +AT_COMPILE_CHECK([simple/igraph_random_walk.c]) +AT_CLEANUP \ No newline at end of file diff --git a/tools/NEXT_VERSION b/tools/NEXT_VERSION new file mode 100644 index 0000000..ac39a10 --- /dev/null +++ b/tools/NEXT_VERSION @@ -0,0 +1 @@ +0.9.0 diff --git a/tools/arpack-sed.txt b/tools/arpack-sed.txt new file mode 100644 index 0000000..9c5e5ac --- /dev/null +++ b/tools/arpack-sed.txt @@ -0,0 +1,93 @@ +s/dsaupd_/igraphdsaupd_/g +s/dseupd_/igraphdseupd_/g +s/dsaup2_/igraphdsaup2_/g +s/dstats_/igraphdstats_/g +s/dsesrt_/igraphdsesrt_/g +s/dsortr_/igraphdsortr_/g +s/dgetv0_/igraphdgetv0_/g +s/dsaitr_/igraphdsaitr_/g +s/dsapps_/igraphdsapps_/g +s/dsconv_/igraphdsconv_/g +s/dseigt_/igraphdseigt_/g +s/dsgets_/igraphdsgets_/g +s/dstqrb_/igraphdstqrb_/g +s/dmout_/igraphdmout_/g +s/ivout_/igraphivout_/g +s/second_/igraphsecond_/g +s/dvout_/igraphdvout_/g + +s/dlarnv_/igraphdlarnv_/g +s/dlascl_/igraphdlascl_/g +s/dlartg_/igraphdlartg_/g +s/dlaset_/igraphdlaset_/g +s/dlaev2_/igraphdlaev2_/g +s/dlasr_/igraphdlasr_/g +s/dlasrt_/igraphdlasrt_/g +s/dgeqr2_/igraphdgeqr2_/g +s/dlacpy_/igraphdlacpy_/g +s/dorm2r_/igraphdorm2r_/g +s/dsteqr_/igraphdsteqr_/g +s/dlanst_/igraphdlanst_/g +s/dlapy2_/igraphdlapy2_/g +s/dlamch_/igraphdlamch_/g +s/dlaruv_/igraphdlaruv_/g +s/dlarfg_/igraphdlarfg_/g +s/dlarf_/igraphdlarf_/g +s/dlae2_/igraphdlae2_/g +s/dlassq_/igraphdlassq_/g +s/dlamc1_/igraphdlamc1_/g +s/dlamc2_/igraphdlamc2_/g +s/dlamc3_/igraphdlamc3_/g +s/dlamc4_/igraphdlamc4_/g +s/dlamc5_/igraphdlamc5_/g +s/xerbla_/igraphxerbla_/g + +s/daxpy_/igraphdaxpy_/g +s/dger_/igraphdger_/g +s/dcopy_/igraphdcopy_/g +s/dscal_/igraphdscal_/g +s/dswap_/igraphdswap_/g +s/dgemv_/igraphdgemv_/g +s/ddot_/igraphddot_/g +s/dnrm2_/igraphdnrm2_/g +s/lsame_/igraphlsame_/g + +s/d_sign/igraphd_sign/g +s/etime_/igraphetime_/g +s/pow_dd/igraphpow_dd/g +s/pow_di/igraphpow_di/g +s/s_cmp/igraphs_cmp/g +s/s_copy/igraphs_copy/g + +s/dnaitr/igraphdnaitr/g +s/dnapps/igraphdnapps/g +s/dnaup2/igraphdnaup2/g +s/dnaupd/igraphdnaupd/g +s/dnconv/igraphdnconv/g +s/dlabad/igraphdlabad/g +s/dlanhs/igraphdlanhs/g +s/dsortc/igraphdsortc/g +s/dneigh/igraphdneigh/g +s/dngets/igraphdngets/g +s/dstatn/igraphdstatn/g +s/dtrevc/igraphdtrevc/g +s/dlaqrb/igraphdlaqrb/g +s/d_lg10/igraphd_lg10/g +s/dlanv2/igraphdlanv2/g +s/drot/igraphdrot/g +s/idamax/igraphidamax/g +s/dlaln2/igraphdlaln2/g +s/dladiv/igraphdladiv/g +s/dneupd/igraphdneupd/g +s/dtrmm/igraphdtrmm/g +s/dtrsen/igraphdtrsen/g +s/dlahqr/igraphdlahqr/g +s/dlacon/igraphdlacon/g +s/dtrsyl/igraphdtrsyl/g +s/dtrexc/igraphdtrexc/g +s/dlange/igraphdlange/g +s/dlaexc/igraphdlaexc/g +s/dlasy2/igraphdlasy2/g +s/dasum/igraphdasum/g +s/i_dnnt/igraphi_dnnt/g +s/dlarfx/igraphdlarfx/g diff --git a/tools/autoconf/as-version.m4 b/tools/autoconf/as-version.m4 new file mode 100644 index 0000000..c2c89b1 --- /dev/null +++ b/tools/autoconf/as-version.m4 @@ -0,0 +1,74 @@ +dnl as-version.m4 0.2.1 + +dnl autostars m4 macro for versioning + +dnl Thomas Vander Stichele +dnl Gabor Csardi + +dnl $Id: as-version.m4,v 1.4 2004/06/01 09:40:05 thomasvs Exp $ + +dnl AS_VERSION + +dnl example +dnl AS_VERSION + +dnl this macro +dnl - AC_SUBST's PACKAGE_VERSION_MAJOR, _MINOR, _PATCH, _PRERELEASE +dnl - AC_SUBST's PACKAGE_VERSION_RELEASE, +dnl which can be used for rpm release fields +dnl - doesn't call AM_INIT_AUTOMAKE anymore because it prevents +dnl maintainer mode from running correctly +dnl +dnl don't forget to put #undef PACKAGE_VERSION_RELEASE in acconfig.h +dnl if you use acconfig.h + +AC_DEFUN([AS_VERSION], +[ + PACKAGE_VERSION_MAJOR=$(echo AC_PACKAGE_VERSION | cut -d'.' -f1) + PACKAGE_VERSION_MINOR=$(echo AC_PACKAGE_VERSION | cut -d'.' -f2) + PACKAGE_VERSION_PATCH=$(echo AC_PACKAGE_VERSION | cut -d'.' -f3 | cut -d'-' -f1) + PACKAGE_VERSION_PRERELEASE=$(echo AC_PACKAGE_VERSION | cut -d'.' -f3- | cut -s -d'-' -f2-) + + AC_SUBST(PACKAGE_VERSION_MAJOR) + AC_SUBST(PACKAGE_VERSION_MINOR) + AC_SUBST(PACKAGE_VERSION_PATCH) + AC_SUBST(PACKAGE_VERSION_PRERELEASE) +]) + +dnl AS_NANO(ACTION-IF-NO-NANO, [ACTION-IF-NANO]) + +dnl requires AC_INIT to be called before +dnl For projects using a fourth or nano number in your versioning to indicate +dnl development or prerelease snapshots, this macro allows the build to be +dnl set up differently accordingly. + +dnl this macro: +dnl - parses AC_PACKAGE_VERSION, set by AC_INIT, and extracts the nano number +dnl - sets the variable PACKAGE_VERSION_NANO +dnl - sets the variable PACKAGE_VERSION_RELEASE, which can be used +dnl for rpm release fields +dnl - executes ACTION-IF-NO-NANO or ACTION-IF-NANO + +dnl example: +dnl AS_NANO(RELEASE="yes", RELEASE="no") + +AC_DEFUN([AS_NANO], +[ + AC_MSG_CHECKING(nano version) + + NANO=$(echo AC_PACKAGE_VERSION | cut -d'.' -f4) + + if test x"$NANO" = x || test "x$NANO" = "x0" ; then + AC_MSG_RESULT([0 (release)]) + NANO=0 + PACKAGE_VERSION_RELEASE=1 + ifelse([$1], , :, [$1]) + else + AC_MSG_RESULT($NANO) + PACKAGE_VERSION_RELEASE=0.`date +%Y%m%d.%H%M%S` + ifelse([$2], , :, [$2]) + fi + PACKAGE_VERSION_NANO=$NANO + AC_SUBST(PACKAGE_VERSION_NANO) + AC_SUBST(PACKAGE_VERSION_RELEASE) +]) diff --git a/tools/autoconf/ax_tls.m4 b/tools/autoconf/ax_tls.m4 new file mode 100644 index 0000000..e3119ea --- /dev/null +++ b/tools/autoconf/ax_tls.m4 @@ -0,0 +1,76 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_tls.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_TLS([action-if-found], [action-if-not-found]) +# +# DESCRIPTION +# +# Provides a test for the compiler support of thread local storage (TLS) +# extensions. Defines TLS if it is found. Currently knows about GCC/ICC +# and MSVC. I think SunPro uses the same as GCC, and Borland apparently +# supports either. +# +# LICENSE +# +# Copyright (c) 2008 Alan Woodland +# Copyright (c) 2010 Diego Elio Petteno` +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 10 + +AC_DEFUN([AX_TLS], [ + AC_MSG_CHECKING(for thread local storage (TLS) class) + AC_CACHE_VAL(ac_cv_tls, [ + ax_tls_keywords="__thread __declspec(thread) none" + for ax_tls_keyword in $ax_tls_keywords; do + AS_CASE([$ax_tls_keyword], + [none], [ac_cv_tls=none ; break], + [AC_TRY_COMPILE( + [#include + static void + foo(void) { + static ] $ax_tls_keyword [ int bar; + exit(1); + }], + [], + [ac_cv_tls=$ax_tls_keyword ; break], + ac_cv_tls=none + )]) + done + ]) + AC_MSG_RESULT($ac_cv_tls) + + AS_IF([test "$ac_cv_tls" != "none"], + AC_DEFINE_UNQUOTED([TLS], $ac_cv_tls, [If the compiler supports a TLS storage class define it to that here]) + m4_ifval([$1], [$1], [true]), + m4_ifval([$2], [$2], [true]) + ) +]) diff --git a/tools/bump_version.sh b/tools/bump_version.sh new file mode 100755 index 0000000..aa0147c --- /dev/null +++ b/tools/bump_version.sh @@ -0,0 +1,74 @@ +#!/bin/sh +# +# Script that should be run whenever we bump the version number of +# igraph. +# +# This script adjusts the version numbers in the following files: +# +# - configure.in +# - interfaces/java/build.xml +# - interfaces/R/configure.in +# - examples/simple/gml.out +# - examples/simple/cattributes2.out +# - msvc/igraphtest/igraphtest.vcproj +# - tools/launchpad_nightly.recipe +# - debian/changelog + +set -e +set -u + +if [ $# -lt 1 ]; then + echo "Usage: $0 version" + exit 1 +fi + +VERSION="$1" + +# Step to the root of the source tree +cd `dirname $0`/.. + +# Adjust configure.in +sed -e "s/AC_INIT(igraph, [^,]*,/AC_INIT(igraph, ${VERSION},/" \ + -e "s/AM_INIT_AUTOMAKE(igraph, [^)]*)/AM_INIT_AUTOMAKE(igraph, ${VERSION})/" \ + -i configure.in + +# Adjust interfaces/java/build.xml +sed -e "s/property name=\"package\.version\" value=\"[^\"]*\"/property name=\"package.version\" value=\"${VERSION}\"/" \ + -i interfaces/java/build.xml + +# Adjust interfaces/R/configure.in +sed -e "s/AC_INIT(igraph, [^,]*,/AC_INIT(igraph, ${VERSION},/" \ + -i configure.in + +# Adjust examples/simple/gml.out +sed -e "s/igraph version [^ ]*/igraph version ${VERSION}/" \ + -i examples/simple/gml.out + +# Adjust examples/simple/cattributes2.out +sed -e "s/igraph version [^ ]*/igraph version ${VERSION}/" \ + -i examples/simple/cattributes2.out + +# Adjust msvc/igraphtest/igraphtest.vcproj +sed -e "s/igraph-[^-]*-msvc/igraph-${VERSION}-msvc/g" \ + -i msvc/igraphtest/igraphtest.vcproj + +# Adjust tools/launchpad_nightly.recipe +sed -e "s/deb-version [^~]*/deb-version ${VERSION}/" \ + -e "s|lp:igraph/[^-]*-main|lp:igraph/${VERSION}-main|" \ + -i tools/launchpad_nightly.recipe + +# Adjust debian/changelog +DATE="`date -R`" +cat >debian/changelog.new < ${DATE} + +EOF +cat debian/changelog >>debian/changelog.new +mv debian/changelog.new debian/changelog + +# Done. +echo "Successfully bumped version number to ${VERSION}." diff --git a/tools/create-msvc-projectfile.py b/tools/create-msvc-projectfile.py new file mode 100755 index 0000000..79915b2 --- /dev/null +++ b/tools/create-msvc-projectfile.py @@ -0,0 +1,71 @@ +#! /usr/bin/env python + +import sys +import os.path +import re + +from subprocess import check_output + +# Some notes: +# - we have some sources with .cc extensions, these are marked as type 0 +# - we have some non-standard header files, e.g. .hh, .pmt, these are type 2 + +srctext = """ + """ + +headtext = """ + """ +headptext = """ + """ + +def runmake(makefile, target): + out = check_output("make -q -C " + os.path.dirname(makefile) + + " -f " + os.path.basename(makefile) + + " " + target, shell=True) + out = out.decode(sys.stdout.encoding or "utf8") + out = out.replace("/", "\\") + out = re.sub("make.*'.*'", "", out) # msys2 make adds "make[x]: entering ''"" + return out + +def rreplace(s, old, new, occurrence): + li = s.rsplit(old, occurrence) + return new.join(li) + +def main(): + if len(sys.argv) != 4: + print( "Error: need three arguments") + sys.exit(1) + package = sys.argv[1] + projectfile = sys.argv[2] + makefile = sys.argv[3] + + proj = open(projectfile).read() + sources = runmake(makefile, "echosources").split() + headers = runmake(makefile, "echoheaders").split() + headersprivate = runmake(makefile, "echoheadersprivate").split() + + # lex and bison stuff + headers2 = [ rreplace(s, ".y", ".h",1) for s in sources if s[-2:]==".y" ] + sources = [ rreplace(s, ".l", ".c", 1) for s in sources ] + sources = [ rreplace(s, ".y", ".c", 1) for s in sources ] + + stext = "\n".join([ srctext % s for s in sources ]) + htext = "\n".join([ headtext % s for s in headers ]) + hptext = "\n".join([ headptext % s for s in headersprivate + headers2 ]) + + proj = proj.replace("", stext) + proj = proj.replace("", htext + "\n" + hptext) + + out_file = open(package + "/igraph.vcproj", "w") + out_file.write(proj) + out_file.close() + +if __name__ == "__main__": + main() + diff --git a/tools/exclude.txt b/tools/exclude.txt new file mode 100644 index 0000000..b0285aa --- /dev/null +++ b/tools/exclude.txt @@ -0,0 +1,2 @@ +toc.html +toc-*.html diff --git a/tools/extract_body.sh b/tools/extract_body.sh new file mode 100755 index 0000000..2e6e017 --- /dev/null +++ b/tools/extract_body.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +sed -n '1,/^]/!{ /<\/body>/,/^]/!p; }' + diff --git a/tools/getglpk.sh b/tools/getglpk.sh new file mode 100755 index 0000000..210ba32 --- /dev/null +++ b/tools/getglpk.sh @@ -0,0 +1,281 @@ +#! /bin/sh + +if [ -d ../optional/glpk ]; then + echo "GLPK directory '../optional/glpk' already exists, remove it first" +# exit 1 +fi + +THIS=`pwd` +IDIR=${THIS}/../optional/glpk/ +mkdir $IDIR + +GLPK="http://ftp.gnu.org/gnu/glpk/glpk-4.45.tar.gz" +TARGZ=`echo $GLPK | sed 's/^.*\///'` +DIR=`echo $TARGZ | sed 's/\.tar\.gz$//'` + +cd /tmp +if [ ! -f $TARGZ ]; then curl -O $GLPK; fi +tar xzf $TARGZ + +#cp -R $DIR/include/*.h $DIR/src/*.{c,h} $DIR/src/amd $DIR/src/colamd \ +# $DIR/{README,COPYING} $IDIR + +cd $THIS + +SRC=`ls ../optional/glpk/*.h ../optional/glpk/*.c` +SRC2=`ls ../optional/glpk/amd/*.h ../optional/glpk/amd/*.c` +SRC3=`ls ../optional/glpk/colamd/*.h ../optional/glpk/colamd/*.c` + +INC=$IDIR/glpk.inc + +/bin/echo -n "GLPK = " > $INC +for i in $SRC; do /bin/echo -n "$i " >>$INC; done +for i in $SRC2; do /bin/echo -n "$i " >>$INC; done +for i in $SRC3; do /bin/echo -n "$i " >>$INC; done + +# Need a patch to get rid of an abort() call. We call igraph_error() +# instead. + +patch -p1 -d ../optional/glpk <<-EOF +diff -ru glpk.old/glpenv01.c glpk/glpenv01.c +--- glpk.old/glpenv01.c 2012-03-30 11:30:58.000000000 -0400 ++++ glpk/glpenv01.c 2012-03-30 12:03:54.000000000 -0400 +@@ -23,6 +23,7 @@ + ***********************************************************************/ + + #include "glpapi.h" ++#include "igraph_error.h" + + /*********************************************************************** + * NAME +@@ -126,19 +127,15 @@ + { /* not initialized yet; perform initialization */ + if (glp_init_env() != 0) + { /* initialization failed; display an error message */ +- fprintf(stderr, "GLPK initialization failed\n"); +- fflush(stderr); +- /* and abnormally terminate the program */ +- abort(); ++ IGRAPH_ERROR("GLPK initialization failed", IGRAPH_EGLP); + } + /* initialization successful; retrieve the pointer */ + env = tls_get_ptr(); + } + /* check if the environment block is valid */ + if (env->magic != ENV_MAGIC) +- { fprintf(stderr, "Invalid GLPK environment\n"); +- fflush(stderr); +- abort(); ++ { ++ IGRAPH_ERROR("Invalid GLPK environment", IGRAPH_EGLP); + } + return env; + } +@@ -200,9 +197,8 @@ + if (env == NULL) return 1; + /* check if the environment block is valid */ + if (env->magic != ENV_MAGIC) +- { fprintf(stderr, "Invalid GLPK environment\n"); +- fflush(stderr); +- abort(); ++ { ++ IGRAPH_ERROR("Invalid GLPK environment", IGRAPH_EGLP); + } + /* close handles to shared libraries */ + if (env->h_odbc != NULL) +diff -ru glpk.old/glpenv03.c glpk/glpenv03.c +--- glpk.old/glpenv03.c 2012-03-30 11:30:58.000000000 -0400 ++++ glpk/glpenv03.c 2012-04-02 11:18:42.000000000 -0400 +@@ -40,9 +40,9 @@ + + void glp_printf(const char *fmt, ...) + { va_list arg; +- va_start(arg, fmt); +- xvprintf(fmt, arg); +- va_end(arg); ++ /* va_start(arg, fmt); */ ++ /* xvprintf(fmt, arg); */ ++ /* va_end(arg); */ + return; + } + +@@ -64,22 +64,22 @@ + void glp_vprintf(const char *fmt, va_list arg) + { ENV *env = get_env_ptr(); + /* if terminal output is disabled, do nothing */ +- if (!env->term_out) goto skip; +- /* format the output */ +- vsprintf(env->term_buf, fmt, arg); +- /* pass the output to the user-defined routine */ +- if (env->term_hook != NULL) +- { if (env->term_hook(env->term_info, env->term_buf) != 0) +- goto skip; +- } +- /* send the output to the terminal */ +- fputs(env->term_buf, stdout); +- fflush(stdout); +- /* copy the output to the text file */ +- if (env->tee_file != NULL) +- { fputs(env->term_buf, env->tee_file); +- fflush(env->tee_file); +- } ++ /* if (!env->term_out) goto skip; */ ++ /* /\* format the output *\/ */ ++ /* vsprintf(env->term_buf, fmt, arg); */ ++ /* /\* pass the output to the user-defined routine *\/ */ ++ /* if (env->term_hook != NULL) */ ++ /* { if (env->term_hook(env->term_info, env->term_buf) != 0) */ ++ /* goto skip; */ ++ /* } */ ++ /* /\* send the output to the terminal *\/ */ ++ /* fputs(env->term_buf, stdout); */ ++ /* fflush(stdout); */ ++ /* /\* copy the output to the text file *\/ */ ++ /* if (env->tee_file != NULL) */ ++ /* { fputs(env->term_buf, env->tee_file); */ ++ /* fflush(env->tee_file); */ ++ /* } */ + skip: return; + } + +diff -ru glpk.old/glpenv04.c glpk/glpenv04.c +--- glpk.old/glpenv04.c 2012-03-30 11:30:58.000000000 -0400 ++++ glpk/glpenv04.c 2012-03-30 11:56:41.000000000 -0400 +@@ -23,6 +23,7 @@ + ***********************************************************************/ + + #include "glpapi.h" ++#include "igraph_error.h" + + /*********************************************************************** + * NAME +@@ -44,14 +45,7 @@ + va_list arg; + env->term_out = GLP_ON; + va_start(arg, fmt); +- xvprintf(fmt, arg); +- va_end(arg); +- xprintf("Error detected in file %s at line %d\n", env->err_file, +- env->err_line); +- if (env->err_hook != NULL) +- env->err_hook(env->err_info); +- abort(); +- exit(EXIT_FAILURE); ++ igraph_errorvf(fmt, env->err_file, env->err_line, IGRAPH_EGLP, arg); + /* no return */ + } + +diff -ru glpk.old/glpenv07.c glpk/glpenv07.c +--- glpk.old/glpenv07.c 2012-03-30 11:30:58.000000000 -0400 ++++ glpk/glpenv07.c 2012-03-31 13:21:03.000000000 -0400 +@@ -413,13 +413,13 @@ + + static void *c_fopen(const char *fname, const char *mode) + { FILE *fh; +- if (strcmp(fname, "/dev/stdin") == 0) +- fh = stdin; +- else if (strcmp(fname, "/dev/stdout") == 0) +- fh = stdout; +- else if (strcmp(fname, "/dev/stderr") == 0) +- fh = stderr; +- else ++ /* if (strcmp(fname, "/dev/stdin") == 0) */ ++ /* fh = stdin; */ ++ /* else if (strcmp(fname, "/dev/stdout") == 0) */ ++ /* fh = stdout; */ ++ /* else if (strcmp(fname, "/dev/stderr") == 0) */ ++ /* fh = stderr; */ ++ /* else */ + fh = fopen(fname, mode); + if (fh == NULL) + lib_err_msg(strerror(errno)); +@@ -484,11 +484,11 @@ + static int c_fclose(void *_fh) + { FILE *fh = _fh; + int ret; +- if (fh == stdin) +- ret = 0; +- else if (fh == stdout || fh == stderr) +- fflush(fh), ret = 0; +- else ++ /* if (fh == stdin) */ ++ /* ret = 0; */ ++ /* else if (fh == stdout || fh == stderr) */ ++ /* fflush(fh), ret = 0; */ ++ /* else */ + ret = fclose(fh); + if (ret != 0) + { lib_err_msg(strerror(errno)); +diff -ru glpk.old/glpgmp.c glpk/glpgmp.c +--- glpk.old/glpgmp.c 2012-03-30 11:30:58.000000000 -0400 ++++ glpk/glpgmp.c 2012-04-01 00:05:13.000000000 -0400 +@@ -860,7 +860,7 @@ + d[j] = (unsigned char)r->val; + } + /* output the integer to the stream */ +- if (fp == NULL) fp = stdout; ++ /* if (fp == NULL) fp = stdout; */ + if (mpz_sgn(x) < 0) + fputc('-', fp), nwr++; + for (j = n-1; j >= 0; j--) +@@ -1091,7 +1091,7 @@ + int nwr; + if (!(2 <= base && base <= 36)) + xfault("mpq_out_str: base = %d; invalid base\n", base); +- if (fp == NULL) fp = stdout; ++ /* if (fp == NULL) fp = stdout; */ + nwr = mpz_out_str(fp, base, &x->p); + if (x->q.val == 1 && x->q.ptr == NULL) + ; +diff -ru glpk.old/glpmpl04.c glpk/glpmpl04.c +--- glpk.old/glpmpl04.c 2012-03-30 11:30:58.000000000 -0400 ++++ glpk/glpmpl04.c 2012-04-01 00:07:09.000000000 -0400 +@@ -341,11 +341,11 @@ + + void open_output(MPL *mpl, char *file) + { xassert(mpl->out_fp == NULL); +- if (file == NULL) +- { file = ""; +- mpl->out_fp = (void *)stdout; +- } +- else ++ /* if (file == NULL) */ ++ /* { file = ""; */ ++ /* mpl->out_fp = (void *)stdout; */ ++ /* } */ ++ /* else */ + { mpl->out_fp = xfopen(file, "w"); + if (mpl->out_fp == NULL) + error(mpl, "unable to create %s - %s", file, xerrmsg()); +@@ -362,9 +362,9 @@ + + void write_char(MPL *mpl, int c) + { xassert(mpl->out_fp != NULL); +- if (mpl->out_fp == (void *)stdout) +- xprintf("%c", c); +- else ++ /* if (mpl->out_fp == (void *)stdout) */ ++ /* xprintf("%c", c); */ ++ /* else */ + xfprintf(mpl->out_fp, "%c", c); + return; + } +@@ -393,7 +393,7 @@ + + void flush_output(MPL *mpl) + { xassert(mpl->out_fp != NULL); +- if (mpl->out_fp != (void *)stdout) ++ /* if (mpl->out_fp != (void *)stdout) */ + { xfflush(mpl->out_fp); + if (xferror(mpl->out_fp)) + error(mpl, "write error on %s - %s", mpl->out_file, +@@ -1410,7 +1410,7 @@ + if (mpl->row != NULL) xfree(mpl->row); + if (mpl->col != NULL) xfree(mpl->col); + if (mpl->in_fp != NULL) xfclose(mpl->in_fp); +- if (mpl->out_fp != NULL && mpl->out_fp != (void *)stdout) ++ if (mpl->out_fp != NULL /* && mpl->out_fp != (void *)stdout */) + xfclose(mpl->out_fp); + if (mpl->out_file != NULL) xfree(mpl->out_file); + if (mpl->prt_fp != NULL) xfclose(mpl->prt_fp); +EOF diff --git a/tools/getversion.sh b/tools/getversion.sh new file mode 100755 index 0000000..52d042a --- /dev/null +++ b/tools/getversion.sh @@ -0,0 +1,13 @@ +#! /bin/bash + +thistag=$(git describe --exact-match --tags HEAD 2>/dev/null || true) + +if [ -z "${thistag}" ]; then + # taghash=$(git rev-list --tags --max-count=1) + # tag=$(git describe --tags "$taghash") + next_version=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cat NEXT_VERSION ) + current=$(git rev-parse --short HEAD) + echo "${next_version}-pre+${current}" +else + echo "${thistag}" +fi diff --git a/tools/insert-banner.sh b/tools/insert-banner.sh new file mode 100755 index 0000000..ec34508 --- /dev/null +++ b/tools/insert-banner.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +## Insert a banner into a html file, right at the start of + +if [ $# != "2" -a $# != "3" ] || [ ! -d $1 ] || [ ! -f $2 ]; then + printf "Usage: $0 []\n" + exit 1 +fi + +banner=$2 +exclude=/dev/null +if [ -n "$3" ]; then exclude=$3; fi + +tmpfile=`mktemp -t XXXXXX` + +function insert { + printf "%b" "Doing $1..." + + if [ -n "$exclude" ] && (echo $1 | grep -q -f $exclude); then + printf "%b" " excluded\n" + else + insert2 $1 > "$tmpfile" + cp $tmpfile $1 + printf "%b" " DONE\n" + fi +} + +function insert2 { + cat $1 | + sed -n '1h;1!H;${;g;s/\(]*>\)/\1\n/g;p;}' | + sed "// { + r $banner + N + }" +} + +find $1 -name "*.html" | +while read; do + insert $REPLY +done + +rm "$tmpfile" + diff --git a/tools/jekyll_header.sh b/tools/jekyll_header.sh new file mode 100755 index 0000000..d12a3c5 --- /dev/null +++ b/tools/jekyll_header.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +cat < ${base}.c + # run_macro (better vector and array indexing; from NAG) +# ${trans_dir}/substitute_locals.exe < ${base}.c > ${base}.u +# ${trans_dir}/test_tool.exe ${base}.u > ${base}.c +# rm -f ${base}.u + # run_comment + sed -f ${trans_dir}/delete.sed ${base}.c > ${base}.t + mv -f ${base}.t ${base}.c + ${trans_dir}/comment < ${base}.c > ${base}.t + mv -f ${base}.t ${base}.c + # run_splitter +# sed -n -f ${trans_dir}/split.sed ${base}.c +# mv -f ${base}.c ${base}.t +# cat temp/header1 temp/header3 temp/comment temp/header2 temp/prologue \ +# temp/code > ${base}.c +# rm -f ${base}.t +done +rm -f -r temp diff --git a/tools/lapack/Makefile b/tools/lapack/Makefile new file mode 100644 index 0000000..8b09ce1 --- /dev/null +++ b/tools/lapack/Makefile @@ -0,0 +1,15 @@ +LOADLIBS = -ly -lfl -lm +LIBS = -lfl -lm +CFLAGS = -O + +all: lenscrub comment + +lenscrub: lenscrub.l + lex lenscrub.l + mv -f lex.yy.c lex_for_lenscrub.c + cc -o lenscrub -O lex_for_lenscrub.c -ll + +comment: comment.l + lex comment.l + mv -f lex.yy.c lex_for_comment.c + cc -o comment -O lex_for_comment.c -ll diff --git a/tools/lapack/comment.l b/tools/lapack/comment.l new file mode 100644 index 0000000..0b05581 --- /dev/null +++ b/tools/lapack/comment.l @@ -0,0 +1,14 @@ +%{ +#include + +/* extern FILE *commentFile, *localVarFile, *codeFile; */ + +%} + +whitespace [\n\t ]* +any .* + +%% +"*/"{whitespace}"/*" {yytext[0]=yytext[1]=yytext[yyleng-1]=yytext[yyleng-2]=' ';printf("%s",yytext);} +"\n" {printf("%s", yytext);} +. {printf("%s", yytext);} diff --git a/tools/lapack/delete.sed b/tools/lapack/delete.sed new file mode 100644 index 0000000..8122b2b --- /dev/null +++ b/tools/lapack/delete.sed @@ -0,0 +1,4 @@ +# delete the line of the form .. Scalar arguments .. +/\/\* *\.\. .*\*\//{ +d +} diff --git a/tools/lapack/extra/len_trim.f b/tools/lapack/extra/len_trim.f new file mode 100644 index 0000000..cb821eb --- /dev/null +++ b/tools/lapack/extra/len_trim.f @@ -0,0 +1,14 @@ +* +* -- LEN_TRIM is Fortran 95, so we use a replacement here +* + FUNCTION LEN_TRIM(S) +* + CHARACTER*(*) S + INTEGER LEN_TRIM +* + INTRINSIC LEN +* + DO LEN_TRIM = LEN(S), 1, -1 + IF (s(LEN_TRIM:LEN_TRIM) .NE. ' ') RETURN + END DO + END diff --git a/tools/lapack/getlapack.sh b/tools/lapack/getlapack.sh new file mode 100755 index 0000000..997816e --- /dev/null +++ b/tools/lapack/getlapack.sh @@ -0,0 +1,191 @@ +#! /bin/sh +# +# ./getlapack.sh dgeev dsyevr dnaupd dneupd dsaupd dseupd dgemv dgeevx \ +# dgetrf dgetrs dgesv dlapy2 dpotrf dsyrk dtrsv +# + +make + +origdir=`pwd` +destdir=lapack-new + +cd /tmp +rm -rf $destdir +mkdir $destdir + +## Download and unpack BLAS + +if test ! -f blas.tgz; then + curl -O http://www.netlib.org/blas/blas.tgz +fi +blasdir=`tar tzf blas.tgz | head -1 | cut -f1 -d"/"` +rm -rf ${blasdir} +tar xzf blas.tgz + +## Download, unpack and patch LAPACK + +if test ! -f lapack.tgz; then + curl -O http://www.netlib.org/lapack/lapack.tgz +fi +lapackdir=`tar tzf lapack.tgz | head -1 | cut -f1 -d"/"` +rm -rf ${lapackdir} +tar xzf lapack.tgz + +cd /tmp/${lapackdir} +patch -p 1 <${origdir}/lapack.patch +cd /tmp + +## Download and unpack ARPACK + +if test ! -f arpack96.tar.gz; then + curl -O http://www.caam.rice.edu/software/ARPACK/SRC/arpack96.tar.gz +fi +arpackdir=`tar tzf arpack96.tar.gz | head -1 | cut -f1 -d"/"` +rm -rf ${arpackdir} +tar xzf arpack96.tar.gz + +alreadydone=() +lapack=() +arpack=() +blas=() + +known() { + needle=$1 + res=0 + for i in ${alreadydone[@]}; do + if [[ $i == ${needle} ]]; then + return 0 + fi + done + return 1 +} + +getdeps() { + name=$1; + f2c -a ${name}.f >/dev/null 2>/dev/null && + gcc -c ${name}.c >/dev/null && + nm ${name}.o | grep " U " | awk ' { print $2 }' | + sed 's/_$//g' | sed 's/^_//g' +} + +dofunction() { + name=$1; + + if known $name; then return 0; fi + + if test -f /tmp/${arpackdir}/SRC/${name}.f; then + cd /tmp/${arpackdir}/SRC + arpack[$[${#arpack[@]}+1]]=$name + elif test -f /tmp/${lapackdir}/SRC/${name}.f; then + cd /tmp/${lapackdir}/SRC + lapack[$[${#lapack[@]}+1]]=$name + elif test -f /tmp/${blasdir}/${name}.f; then + cd /tmp/${blasdir} + blas[$[${#blas[@]}+1]]=$name + elif test -f /tmp/${arpackdir}/UTIL/${name}.f; then + cd /tmp/${arpackdir}/UTIL + arpack[$[${#arpack[@]}+1]]=$name + elif test -f /tmp/${lapackdir}/INSTALL/${name}.f; then + cd /tmp/${lapackdir}/INSTALL + lapack[$[${#lapack[@]}+1]]=$name + elif test -f ${origdir}/extra/${name}.f; then + cd ${origdir}/extra + lapack[$[${#lapack[@]}+1]]=$name + else + return + fi + + cp ${name}.f /tmp/${destdir} + + alreadydone[$[${#alreadydone[@]}+1]]=$name + + deps=`getdeps $name` + for i in $deps; do + dofunction $i + done +} + +if test "$#" -eq "0"; then + exit 0 +fi + +## Collect and copy the needed files + +for i in "$@"; do + dofunction $i +done + +## Some more required files + +dofunction second +dofunction dvout +dofunction ivout +dofunction dmout +dofunction dlamch +dofunction len_trim + +## Polish them + +cd /tmp/${destdir} +touch debug.h +touch stat.h +trans_dir=${origdir} ${origdir}/CompletePolish *.f + +## Remove the .f files. + +cd /tmp/${destdir} +rm -f *.f + +## Prefix the function calls with 'igraph', this is needed +## if the user wants to link igraph including internal BLAS/LAPACK/ARPACK +## and BLAS/LAPACK/ARPACK for some reason + +extrafunctions=(dlamc1 dlamc2 dlamc3 dlamc4 dlamc5) + +for name in ${alreadydone[@]} ${extrafunctions[@]}; do + echo "s/${name}_/igraph${name}_/g" +done > /tmp/lapack-sed.txt + +for name in ${alreadydone[@]}; do + sed -f /tmp/lapack-sed.txt < ${name}.c >/tmp/arpackfun.c + cp /tmp/arpackfun.c ${name}.c +done + +## Update the file that is included into the main Makefile, +## this contains the ARPACK/LAPACK/BLAS source files + +blasinc=/tmp/${destdir}/blas.inc +/bin/echo -n "BLAS = " > ${blasinc} +for name in ${blas[@]}; do + /bin/echo -n "lapack/${name}.c " +done >> ${blasinc} +/bin/echo >> ${blasinc} + +lapackinc=/tmp/${destdir}/lapack.inc +/bin/echo -n "LAPACK = " > ${lapackinc} +for name in ${lapack[@]}; do + /bin/echo -n "lapack/${name}.c " +done | sed 's/lapack\/dlamch\.c//' >> ${lapackinc} +/bin/echo >> ${lapackinc} + +arpackinc=/tmp/${destdir}/arpack.inc +/bin/echo -n "ARPACK = " > ${arpackinc} +for name in ${arpack[@]}; do + /bin/echo -n "lapack/${name}.c " +done >> ${arpackinc} +/bin/echo >> ${arpackinc} + +## This is a patch to make ARPACK thread-safe + +cd /tmp/${destdir} +patch -p2 < ${origdir}/mt.patch + +## We are done + +echo "Sources are ready, to update your tree please run: + + bzr rm ${origdir}/../../src/lapack + mv /tmp/${destdir} ${origdir}/../../src/lapack + bzr add ${origdir}/../../src/lapack + +" diff --git a/tools/lapack/lapack.patch b/tools/lapack/lapack.patch new file mode 100644 index 0000000..ff44dc4 --- /dev/null +++ b/tools/lapack/lapack.patch @@ -0,0 +1,156 @@ +diff -ru lapack-3.2.2/SRC/dlarft.f lapack-3.2.2-new/SRC/dlarft.f +--- lapack-3.2.2/SRC/dlarft.f 2009-04-16 20:10:16.000000000 +0200 ++++ lapack-3.2.2-new/SRC/dlarft.f 2010-10-06 21:47:53.000000000 +0200 +@@ -145,9 +145,15 @@ + V( I, I ) = ONE + IF( LSAME( STOREV, 'C' ) ) THEN + ! Skip any trailing zeros. +- DO LASTV = N, I+1, -1 +- IF( V( LASTV, I ).NE.ZERO ) EXIT +- END DO ++ LASTV = N ++ 14 IF (V(LASTV, I ) .NE. ZERO) GOTO 15 ++ IF (LASTV .EQ. I+1) GOTO 15 ++ LASTV = LASTV - 1 ++ GOTO 14 ++ 15 CONTINUE ++* DO LASTV = N, I+1, -1 ++* IF( V( LASTV, I ).NE.ZERO ) EXIT ++* END DO + J = MIN( LASTV, PREVLASTV ) + * + * T(1:i-1,i) := - tau(i) * V(i:j,1:i-1)' * V(i:j,i) +@@ -157,9 +163,16 @@ + $ T( 1, I ), 1 ) + ELSE + ! Skip any trailing zeros. +- DO LASTV = N, I+1, -1 +- IF( V( I, LASTV ).NE.ZERO ) EXIT +- END DO ++ LASTV = N ++ 16 IF (V(I, LASTV) .NE. ZERO) GOTO 17 ++ IF (LASTV .EQ. I+1) GOTO 17 ++ LASTV = LASTV - 1 ++ GOTO 16 ++ 17 CONTINUE ++* DO LASTV = N, I+1, -1 ++* IF( V( I, LASTV ).NE.ZERO ) EXIT ++* END DO ++ + J = MIN( LASTV, PREVLASTV ) + * + * T(1:i-1,i) := - tau(i) * V(1:i-1,i:j) * V(i,i:j)' +@@ -201,9 +214,16 @@ + VII = V( N-K+I, I ) + V( N-K+I, I ) = ONE + ! Skip any leading zeros. +- DO LASTV = 1, I-1 +- IF( V( LASTV, I ).NE.ZERO ) EXIT +- END DO ++ LASTV = 1 ++ 34 IF (V(LASTV, I) .NE. ZERO) GOTO 35 ++ IF (LASTV .EQ. I-1) GOTO 35 ++ LASTV = LASTV + 1 ++ GOTO 34 ++ 35 CONTINUE ++* DO LASTV = 1, I-1 ++* IF( V( LASTV, I ).NE.ZERO ) EXIT ++* END DO ++ + J = MAX( LASTV, PREVLASTV ) + * + * T(i+1:k,i) := +@@ -217,9 +237,14 @@ + VII = V( I, N-K+I ) + V( I, N-K+I ) = ONE + ! Skip any leading zeros. +- DO LASTV = 1, I-1 +- IF( V( I, LASTV ).NE.ZERO ) EXIT +- END DO ++ LASTV = 1 ++ 36 IF (V(I, LASTV) .NE. ZERO) GOTO 37 ++ IF (LASTV .EQ. I-1) GOTO 37 ++ LASTV = LASTV + 1 ++ 37 CONTINUE ++* DO LASTV = 1, I-1 ++* IF( V( I, LASTV ).NE.ZERO ) EXIT ++* END DO + J = MAX( LASTV, PREVLASTV ) + * + * T(i+1:k,i) := +diff -ru lapack-3.2.2/SRC/xerbla.f lapack-3.2.2-new/SRC/xerbla.f +--- lapack-3.2.2/SRC/xerbla.f 2009-04-16 20:10:16.000000000 +0200 ++++ lapack-3.2.2-new/SRC/xerbla.f 2010-10-08 17:53:21.000000000 +0200 +@@ -33,7 +33,7 @@ + * ===================================================================== + * + * .. Intrinsic Functions .. +- INTRINSIC LEN_TRIM ++ EXTERNAL LEN_TRIM + * .. + * .. Executable Statements .. + * +diff -ru lapack-3.3.1/INSTALL/dlamch.f lapack-3.3.1-new/INSTALL/dlamch.f +--- lapack-3.3.1/INSTALL/dlamch.f 2011-04-26 12:41:18.000000000 -0400 ++++ lapack-3.3.1-new/INSTALL/dlamch.f 2011-04-26 12:41:22.000000000 -0400 +@@ -60,8 +60,8 @@ + EXTERNAL LSAME + * .. + * .. Intrinsic Functions .. +- INTRINSIC DIGITS, EPSILON, HUGE, MAXEXPONENT, +- $ MINEXPONENT, RADIX, TINY ++ EXTERNAL DIGITSDBL, EPSILONDBL, HUGEDBL, MAXEXPONENTDBL, ++ $ MINEXPONENTDBL, RADIXDBL, TINYDBL + * .. + * .. Executable Statements .. + * +@@ -71,16 +71,16 @@ + RND = ONE + * + IF( ONE.EQ.RND ) THEN +- EPS = EPSILON(ZERO) * 0.5 ++ EPS = EPSILONDBL(ZERO) * 0.5 + ELSE +- EPS = EPSILON(ZERO) ++ EPS = EPSILONDBL(ZERO) + END IF + * + IF( LSAME( CMACH, 'E' ) ) THEN + RMACH = EPS + ELSE IF( LSAME( CMACH, 'S' ) ) THEN +- SFMIN = TINY(ZERO) +- SMALL = ONE / HUGE(ZERO) ++ SFMIN = TINYDBL(ZERO) ++ SMALL = ONE / HUGEDBL(ZERO) + IF( SMALL.GE.SFMIN ) THEN + * + * Use SMALL plus a bit, to avoid the possibility of rounding +@@ -90,21 +90,21 @@ + END IF + RMACH = SFMIN + ELSE IF( LSAME( CMACH, 'B' ) ) THEN +- RMACH = RADIX(ZERO) ++ RMACH = RADIXDBL(ZERO) + ELSE IF( LSAME( CMACH, 'P' ) ) THEN +- RMACH = EPS * RADIX(ZERO) ++ RMACH = EPS * RADIXDBL(ZERO) + ELSE IF( LSAME( CMACH, 'N' ) ) THEN +- RMACH = DIGITS(ZERO) ++ RMACH = DIGITSDBL(ZERO) + ELSE IF( LSAME( CMACH, 'R' ) ) THEN + RMACH = RND + ELSE IF( LSAME( CMACH, 'M' ) ) THEN +- RMACH = MINEXPONENT(ZERO) ++ RMACH = MINEXPONENTDBL(ZERO) + ELSE IF( LSAME( CMACH, 'U' ) ) THEN +- RMACH = tiny(zero) ++ RMACH = TINYDBL(zero) + ELSE IF( LSAME( CMACH, 'L' ) ) THEN +- RMACH = MAXEXPONENT(ZERO) ++ RMACH = MAXEXPONENTDBL(ZERO) + ELSE IF( LSAME( CMACH, 'O' ) ) THEN +- RMACH = HUGE(ZERO) ++ RMACH = HUGEDBL(ZERO) + ELSE + RMACH = ZERO + END IF diff --git a/tools/lapack/lenscrub.l b/tools/lapack/lenscrub.l new file mode 100644 index 0000000..c3c8631 --- /dev/null +++ b/tools/lapack/lenscrub.l @@ -0,0 +1,42 @@ +/* {definitions} */ +iofun "("[^;\{]*[;\{] +decl "("[^)]*")"[,;] +any [.]* +S [ \t\n]* +cS ","{S} +len [a-z][a-z0-9]*_len + +%% +"s_stop"{decl} | +"do_fio"{decl} | +"s_cat"{iofun} | +"s_copy"{iofun} | +"s_stop"{iofun} | +"s_cmp"{iofun} | +"i_len"{iofun} | +"len_trim__"{iofun} | +"do_fio"{iofun} | +"do_lio"{iofun} { printf("%s", yytext); /* unchanged */ } +{any}"ilaenv_(" | +"dvout_(" | +"dmout_(" | +"ivout_(" | +"xerbla_(" | +[a-z]"tim"[a-z0-9]*"_(" | +[a-z]"prtb"[a-z0-9]"_(" { + register int c, paran_count = 1; + printf("%s", yytext); /* unchanged */ + /* Loop until the correct closing paranthesis */ + while (paran_count != 0) { + c = input(); + if (c == '(') ++paran_count; + else if (c == ')') --paran_count; + putchar(c); + } + } +{cS}"("{S}ftnlen{S}")"{S}[1-9][0-9]* { ; /* omit -- f2c -A */ } +{cS}[1-9]([0-9])*L { ; /* omit */ } +{cS}ftnlen({S}{len})? { ; /* omit -- f2c -A */ } +^ftnlen" "{len}";\n" { ; /* omit -- f2c without -A or -C++ */ } +{cS}{len} { ; } +. { printf("%s", yytext); /* unchanged */ } diff --git a/tools/lapack/mt.patch b/tools/lapack/mt.patch new file mode 100644 index 0000000..10cf2ea --- /dev/null +++ b/tools/lapack/mt.patch @@ -0,0 +1,498 @@ +=== modified file 'src/lapack/dgetv0.c' +--- src/lapack/dgetv0.c 2011-11-02 20:55:12 +0000 ++++ src/lapack/dgetv0.c 2011-11-03 13:12:52 +0000 +@@ -144,7 +144,7 @@ + { + /* Initialized data */ + +- static logical inits = TRUE_; ++ IGRAPH_F77_SAVE logical inits = TRUE_; + + /* System generated locals */ + integer v_dim1, v_offset, i__1; +@@ -157,29 +157,29 @@ + integer jj, nbx; + extern doublereal igraphddot_(integer *, doublereal *, integer *, doublereal *, + integer *); +- static integer iter; +- static logical orth; ++ IGRAPH_F77_SAVE integer iter; ++ IGRAPH_F77_SAVE logical orth; + integer nopx; + extern doublereal igraphdnrm2_(integer *, doublereal *, integer *); +- static integer iseed[4]; ++ IGRAPH_F77_SAVE integer iseed[4]; + extern /* Subroutine */ int igraphdgemv_(char *, integer *, integer *, + doublereal *, doublereal *, integer *, doublereal *, integer *, + doublereal *, doublereal *, integer *); + integer idist; + extern /* Subroutine */ int igraphdcopy_(integer *, doublereal *, integer *, + doublereal *, integer *); +- static logical first; ++ IGRAPH_F77_SAVE logical first; + real tmvbx; + extern /* Subroutine */ int igraphdvout_(integer *, integer *, doublereal *, + integer *, char *, ftnlen); + integer mgetv0=0; + real tgetv0; +- static doublereal rnorm0; ++ IGRAPH_F77_SAVE doublereal rnorm0; + extern /* Subroutine */ int igraphsecond_(real *); + integer logfil=0, ndigit; + extern /* Subroutine */ int igraphdlarnv_(integer *, integer *, integer *, + doublereal *); +- static integer msglvl; ++ IGRAPH_F77_SAVE integer msglvl; + real tmvopx; + + + +=== modified file 'src/lapack/dlaln2.c' +--- src/lapack/dlaln2.c 2011-11-02 20:55:12 +0000 ++++ src/lapack/dlaln2.c 2011-11-03 13:47:19 +0000 +@@ -28,7 +28,7 @@ + /* System generated locals */ + integer a_dim1, a_offset, b_dim1, b_offset, x_dim1, x_offset; + doublereal d__1, d__2, d__3, d__4, d__5, d__6; +- static doublereal equiv_0[4], equiv_1[4]; ++ IGRAPH_F77_SAVE doublereal equiv_0[4], equiv_1[4]; + + /* Local variables */ + integer j; + +=== modified file 'src/lapack/dnaitr.c' +--- src/lapack/dnaitr.c 2011-11-02 20:55:12 +0000 ++++ src/lapack/dnaitr.c 2011-11-03 13:12:52 +0000 +@@ -236,7 +236,7 @@ + { + /* Initialized data */ + +- static logical first = TRUE_; ++ IGRAPH_F77_SAVE logical first = TRUE_; + + /* System generated locals */ + integer h_dim1, h_offset, v_dim1, v_offset, i__1, i__2; +@@ -247,24 +247,24 @@ + + /* Local variables */ + integer i__; +- static integer j; ++ IGRAPH_F77_SAVE integer j; + real t0, t1, t2, t3, t4, t5; + integer jj; +- static integer ipj, irj; ++ IGRAPH_F77_SAVE integer ipj, irj; + integer nbx; +- static integer ivj; +- static doublereal ulp; ++ IGRAPH_F77_SAVE integer ivj; ++ IGRAPH_F77_SAVE doublereal ulp; + doublereal tst1; + extern doublereal igraphddot_(integer *, doublereal *, integer *, doublereal *, + integer *); +- static integer ierr, iter; +- static doublereal unfl, ovfl; ++ IGRAPH_F77_SAVE integer ierr, iter; ++ IGRAPH_F77_SAVE doublereal unfl, ovfl; + integer nopx; +- static integer itry; ++ IGRAPH_F77_SAVE integer itry; + extern doublereal igraphdnrm2_(integer *, doublereal *, integer *); + doublereal temp1; +- static logical orth1, orth2, step3, step4; +- static doublereal betaj; ++ IGRAPH_F77_SAVE logical orth1, orth2, step3, step4; ++ IGRAPH_F77_SAVE doublereal betaj; + extern /* Subroutine */ int igraphdscal_(integer *, doublereal *, doublereal *, + integer *), igraphdgemv_(char *, integer *, integer *, doublereal *, + doublereal *, integer *, doublereal *, integer *, doublereal *, +@@ -279,12 +279,12 @@ + real tmvbx; + extern /* Subroutine */ int igraphdvout_(integer *, integer *, doublereal *, + integer *, char *, ftnlen); +- static doublereal wnorm; ++ IGRAPH_F77_SAVE doublereal wnorm; + extern /* Subroutine */ int igraphivout_(integer *, integer *, integer *, + integer *, char *, ftnlen), igraphdgetv0_(integer *, char *, integer *, + logical *, integer *, integer *, doublereal *, integer *, + doublereal *, doublereal *, integer *, doublereal *, integer *), igraphdlabad_(doublereal *, doublereal *); +- static doublereal rnorm1; ++ IGRAPH_F77_SAVE doublereal rnorm1; + extern doublereal igraphdlamch_(char *); + extern /* Subroutine */ int igraphdlascl_(char *, integer *, integer *, + doublereal *, doublereal *, integer *, integer *, doublereal *, +@@ -294,10 +294,10 @@ + extern /* Subroutine */ int igraphsecond_(real *); + integer logfil=0, ndigit, nitref, mnaitr=0; + real titref, tnaitr; +- static integer msglvl; +- static doublereal smlnum; ++ IGRAPH_F77_SAVE integer msglvl; ++ IGRAPH_F77_SAVE doublereal smlnum; + integer nrorth; +- static logical rstart; ++ IGRAPH_F77_SAVE logical rstart; + integer nrstrt; + real tmvopx; + + +=== modified file 'src/lapack/dnapps.c' +--- src/lapack/dnapps.c 2011-11-02 20:55:12 +0000 ++++ src/lapack/dnapps.c 2011-11-03 13:12:52 +0000 +@@ -168,7 +168,7 @@ + { + /* Initialized data */ + +- static logical first = TRUE_; ++ IGRAPH_F77_SAVE logical first = TRUE_; + + /* System generated locals */ + integer h_dim1, h_offset, v_dim1, v_offset, q_dim1, q_offset, i__1, i__2, +@@ -183,10 +183,10 @@ + doublereal h11, h12, h21, h22, h32; + integer jj, ir, nr; + doublereal tau; +- static doublereal ulp; ++ IGRAPH_F77_SAVE doublereal ulp; + doublereal tst1; + integer iend; +- static doublereal unfl, ovfl; ++ IGRAPH_F77_SAVE doublereal unfl, ovfl; + extern /* Subroutine */ int igraphdscal_(integer *, doublereal *, doublereal *, + integer *), igraphdlarf_(char *, integer *, integer *, doublereal *, + integer *, doublereal *, doublereal *, integer *, doublereal *); +@@ -218,7 +218,7 @@ + integer mnapps=0, msglvl; + real tnapps; + integer istart; +- static doublereal smlnum; ++ IGRAPH_F77_SAVE doublereal smlnum; + integer kplusp; + + + +=== modified file 'src/lapack/dnaup2.c' +--- src/lapack/dnaup2.c 2011-11-02 20:55:12 +0000 ++++ src/lapack/dnaup2.c 2011-11-03 13:12:52 +0000 +@@ -213,44 +213,44 @@ + double sqrt(doublereal); + + /* Local variables */ +- static integer j; +- static real t0, t1, t2, t3; +- static integer kp[4], np0, nbx, nev0; ++ IGRAPH_F77_SAVE integer j; ++ IGRAPH_F77_SAVE real t0, t1, t2, t3; ++ IGRAPH_F77_SAVE integer kp[4], np0, nbx, nev0; + extern doublereal igraphddot_(integer *, doublereal *, integer *, doublereal *, + integer *); +- static doublereal eps23; +- static integer ierr, iter; +- static doublereal temp; ++ IGRAPH_F77_SAVE doublereal eps23; ++ IGRAPH_F77_SAVE integer ierr, iter; ++ IGRAPH_F77_SAVE doublereal temp; + extern doublereal igraphdnrm2_(integer *, doublereal *, integer *); +- static logical getv0, cnorm; ++ IGRAPH_F77_SAVE logical getv0, cnorm; + extern /* Subroutine */ int igraphdcopy_(integer *, doublereal *, integer *, + doublereal *, integer *); +- static integer nconv; ++ IGRAPH_F77_SAVE integer nconv; + extern /* Subroutine */ int igraphdmout_(integer *, integer *, integer *, + doublereal *, integer *, integer *, char *, ftnlen); +- static logical initv; +- static doublereal rnorm; +- static real tmvbx; ++ IGRAPH_F77_SAVE logical initv; ++ IGRAPH_F77_SAVE doublereal rnorm; ++ IGRAPH_F77_SAVE real tmvbx; + extern /* Subroutine */ int igraphdvout_(integer *, integer *, doublereal *, + integer *, char *, ftnlen), igraphivout_(integer *, integer *, integer * + , integer *, char *, ftnlen), igraphdgetv0_(integer *, char *, integer * + , logical *, integer *, integer *, doublereal *, integer *, + doublereal *, doublereal *, integer *, doublereal *, integer *); + extern doublereal igraphdlapy2_(doublereal *, doublereal *); +- static integer mnaup2=0; +- static real tnaup2; ++ IGRAPH_F77_SAVE integer mnaup2=0; ++ IGRAPH_F77_SAVE real tnaup2; + extern doublereal igraphdlamch_(char *); + extern /* Subroutine */ int igraphdneigh_(doublereal *, integer *, doublereal *, + integer *, doublereal *, doublereal *, doublereal *, doublereal * + , integer *, doublereal *, integer *); +- static integer nevbef; ++ IGRAPH_F77_SAVE integer nevbef; + extern /* Subroutine */ int igraphsecond_(real *); +- static integer logfil=0, ndigit; ++ IGRAPH_F77_SAVE integer logfil=0, ndigit; + extern /* Subroutine */ int igraphdnaitr_(integer *, char *, integer *, integer + *, integer *, integer *, doublereal *, doublereal *, doublereal *, + integer *, doublereal *, integer *, integer *, doublereal *, + integer *); +- static logical update; ++ IGRAPH_F77_SAVE logical update; + extern /* Subroutine */ int igraphdngets_(integer *, char *, integer *, integer + *, doublereal *, doublereal *, doublereal *, doublereal *, + doublereal *), igraphdnapps_(integer *, integer *, integer *, +@@ -259,9 +259,9 @@ + doublereal *), igraphdnconv_(integer *, doublereal *, doublereal *, + doublereal *, doublereal *, integer *), igraphdsortc_(char *, logical *, + integer *, doublereal *, doublereal *, doublereal *); +- static logical ushift; +- static char wprime[2]; +- static integer msglvl, nptemp, numcnv, kplusp; ++ IGRAPH_F77_SAVE logical ushift; ++ IGRAPH_F77_SAVE char wprime[2]; ++ IGRAPH_F77_SAVE integer msglvl, nptemp, numcnv, kplusp; + + + /* %----------------------------------------------------% + +=== modified file 'src/lapack/dnaupd.c' +--- src/lapack/dnaupd.c 2011-11-02 20:55:12 +0000 ++++ src/lapack/dnaupd.c 2011-11-03 13:12:52 +0000 +@@ -464,19 +464,19 @@ + /* Local variables */ + integer j; + real t0, t1; +- static integer nb, ih, iq, np, iw, ldh, ldq; ++ IGRAPH_F77_SAVE integer nb, ih, iq, np, iw, ldh, ldq; + integer nbx; +- static integer nev0, mode; ++ IGRAPH_F77_SAVE integer nev0, mode; + integer ierr; +- static integer iupd, next; ++ IGRAPH_F77_SAVE integer iupd, next; + integer nopx; +- static integer levec; ++ IGRAPH_F77_SAVE integer levec; + real trvec, tmvbx; +- static integer ritzi; ++ IGRAPH_F77_SAVE integer ritzi; + extern /* Subroutine */ int igraphdvout_(integer *, integer *, doublereal *, + integer *, char *, ftnlen), igraphivout_(integer *, integer *, integer * + , integer *, char *, ftnlen); +- static integer ritzr; ++ IGRAPH_F77_SAVE integer ritzr; + extern /* Subroutine */ int igraphdnaup2_(integer *, char *, integer *, char *, + integer *, integer *, doublereal *, doublereal *, integer *, + integer *, integer *, integer *, doublereal *, integer *, +@@ -489,15 +489,15 @@ + integer logfil=0, ndigit; + real tneigh; + integer mnaupd=0; +- static integer ishift; ++ IGRAPH_F77_SAVE integer ishift; + integer nitref; +- static integer bounds; ++ IGRAPH_F77_SAVE integer bounds; + real tnaupd; + extern /* Subroutine */ int igraphdstatn_(void); + real titref, tnaitr; +- static integer msglvl; ++ IGRAPH_F77_SAVE integer msglvl; + real tngets, tnapps, tnconv; +- static integer mxiter; ++ IGRAPH_F77_SAVE integer mxiter; + integer nrorth, nrstrt; + real tmvopx; + + +=== modified file 'src/lapack/dsaitr.c' +--- src/lapack/dsaitr.c 2011-11-02 20:55:12 +0000 ++++ src/lapack/dsaitr.c 2011-11-03 13:12:53 +0000 +@@ -231,7 +231,7 @@ + { + /* Initialized data */ + +- static logical first = TRUE_; ++ IGRAPH_F77_SAVE logical first = TRUE_; + + /* System generated locals */ + integer h_dim1, h_offset, v_dim1, v_offset, i__1; +@@ -241,20 +241,20 @@ + + /* Local variables */ + integer i__; +- static integer j; ++ IGRAPH_F77_SAVE integer j; + real t0, t1, t2, t3, t4, t5; + integer jj; +- static integer ipj, irj; ++ IGRAPH_F77_SAVE integer ipj, irj; + integer nbx; +- static integer ivj; ++ IGRAPH_F77_SAVE integer ivj; + extern doublereal igraphddot_(integer *, doublereal *, integer *, doublereal *, + integer *); +- static integer ierr, iter; ++ IGRAPH_F77_SAVE integer ierr, iter; + integer nopx; +- static integer itry; ++ IGRAPH_F77_SAVE integer itry; + extern doublereal igraphdnrm2_(integer *, doublereal *, integer *); + doublereal temp1; +- static logical orth1, orth2, step3, step4; ++ IGRAPH_F77_SAVE logical orth1, orth2, step3, step4; + extern /* Subroutine */ int igraphdscal_(integer *, doublereal *, doublereal *, + integer *), igraphdgemv_(char *, integer *, integer *, doublereal *, + doublereal *, integer *, doublereal *, integer *, doublereal *, +@@ -266,25 +266,25 @@ + real tmvbx; + extern /* Subroutine */ int igraphdvout_(integer *, integer *, doublereal *, + integer *, char *, ftnlen); +- static doublereal wnorm; ++ IGRAPH_F77_SAVE doublereal wnorm; + extern /* Subroutine */ int igraphivout_(integer *, integer *, integer *, + integer *, char *, ftnlen), igraphdgetv0_(integer *, char *, integer *, + logical *, integer *, integer *, doublereal *, integer *, + doublereal *, doublereal *, integer *, doublereal *, integer *); +- static doublereal rnorm1; ++ IGRAPH_F77_SAVE doublereal rnorm1; + extern doublereal igraphdlamch_(char *); + extern /* Subroutine */ int igraphdlascl_(char *, integer *, integer *, + doublereal *, doublereal *, integer *, integer *, doublereal *, + integer *, integer *), igraphsecond_(real *); + integer logfil=0; +- static doublereal safmin; ++ IGRAPH_F77_SAVE doublereal safmin; + integer ndigit, nitref; + real titref; + integer msaitr=0; +- static integer msglvl; ++ IGRAPH_F77_SAVE integer msglvl; + real tsaitr; + integer nrorth; +- static logical rstart; ++ IGRAPH_F77_SAVE logical rstart; + integer nrstrt; + real tmvopx; + + +=== modified file 'src/lapack/dsapps.c' +--- src/lapack/dsapps.c 2011-11-02 20:55:12 +0000 ++++ src/lapack/dsapps.c 2011-11-03 13:12:53 +0000 +@@ -156,7 +156,7 @@ + { + /* Initialized data */ + +- static logical first = TRUE_; ++ IGRAPH_F77_SAVE logical first = TRUE_; + + /* System generated locals */ + integer h_dim1, h_offset, q_dim1, q_offset, v_dim1, v_offset, i__1, i__2, +@@ -185,7 +185,7 @@ + integer *, doublereal *, integer *, doublereal *, integer *), igraphdlartg_(doublereal *, doublereal *, doublereal *, + doublereal *, doublereal *), igraphdlaset_(char *, integer *, integer *, + doublereal *, doublereal *, doublereal *, integer *); +- static doublereal epsmch; ++ IGRAPH_F77_SAVE doublereal epsmch; + integer logfil=0, ndigit, msapps=0, msglvl, istart; + real tsapps; + integer kplusp; + +=== modified file 'src/lapack/dsaup2.c' +--- src/lapack/dsaup2.c 2011-11-02 20:55:12 +0000 ++++ src/lapack/dsaup2.c 2011-11-03 13:12:53 +0000 +@@ -221,26 +221,26 @@ + integer j; + real t0, t1, t2, t3; + integer kp[3]; +- static integer np0; ++ IGRAPH_F77_SAVE integer np0; + integer nbx; +- static integer nev0; ++ IGRAPH_F77_SAVE integer nev0; + extern doublereal igraphddot_(integer *, doublereal *, integer *, doublereal *, + integer *); +- static doublereal eps23; ++ IGRAPH_F77_SAVE doublereal eps23; + integer ierr; +- static integer iter; ++ IGRAPH_F77_SAVE integer iter; + doublereal temp; + integer nevd2; + extern doublereal igraphdnrm2_(integer *, doublereal *, integer *); +- static logical getv0; ++ IGRAPH_F77_SAVE logical getv0; + integer nevm2; +- static logical cnorm; ++ IGRAPH_F77_SAVE logical cnorm; + extern /* Subroutine */ int igraphdcopy_(integer *, doublereal *, integer *, + doublereal *, integer *), igraphdswap_(integer *, doublereal *, integer + *, doublereal *, integer *); +- static integer nconv; +- static logical initv; +- static doublereal rnorm; ++ IGRAPH_F77_SAVE integer nconv; ++ IGRAPH_F77_SAVE logical initv; ++ IGRAPH_F77_SAVE doublereal rnorm; + real tmvbx; + extern /* Subroutine */ int igraphdvout_(integer *, integer *, doublereal *, + integer *, char *, ftnlen), igraphivout_(integer *, integer *, integer * +@@ -255,7 +255,7 @@ + integer logfil=0, ndigit; + extern /* Subroutine */ int igraphdseigt_(doublereal *, integer *, doublereal *, + integer *, doublereal *, doublereal *, doublereal *, integer *); +- static logical update; ++ IGRAPH_F77_SAVE logical update; + extern /* Subroutine */ int igraphdsaitr_(integer *, char *, integer *, integer + *, integer *, integer *, doublereal *, doublereal *, doublereal *, + integer *, doublereal *, integer *, integer *, doublereal *, +@@ -265,13 +265,13 @@ + integer *, doublereal *, integer *, doublereal *, doublereal *, + integer *, doublereal *), igraphdsconv_(integer *, doublereal *, + doublereal *, doublereal *, integer *); +- static logical ushift; ++ IGRAPH_F77_SAVE logical ushift; + char wprime[2]; +- static integer msglvl; ++ IGRAPH_F77_SAVE integer msglvl; + integer nptemp; + extern /* Subroutine */ int igraphdsortr_(char *, logical *, integer *, + doublereal *, doublereal *); +- static integer kplusp; ++ IGRAPH_F77_SAVE integer kplusp; + + + /* %----------------------------------------------------% + +=== modified file 'src/lapack/dsaupd.c' +--- src/lapack/dsaupd.c 2011-11-02 20:55:12 +0000 ++++ src/lapack/dsaupd.c 2011-11-03 13:12:53 +0000 +@@ -465,11 +465,11 @@ + /* Local variables */ + integer j; + real t0, t1; +- static integer nb, ih, iq, np, iw, ldh, ldq; ++ IGRAPH_F77_SAVE integer nb, ih, iq, np, iw, ldh, ldq; + integer nbx; +- static integer nev0, mode, ierr, iupd, next; ++ IGRAPH_F77_SAVE integer nev0, mode, ierr, iupd, next; + integer nopx; +- static integer ritz; ++ IGRAPH_F77_SAVE integer ritz; + real tmvbx; + extern /* Subroutine */ int igraphdvout_(integer *, integer *, doublereal *, + integer *, char *, ftnlen), igraphivout_(integer *, integer *, integer * +@@ -483,14 +483,14 @@ + extern doublereal igraphdlamch_(char *); + extern /* Subroutine */ int igraphsecond_(real *); + integer logfil=0, ndigit; +- static integer ishift; ++ IGRAPH_F77_SAVE integer ishift; + integer nitref, msaupd=0; +- static integer bounds; ++ IGRAPH_F77_SAVE integer bounds; + real titref, tseigt, tsaupd; + extern /* Subroutine */ int igraphdstats_(void); +- static integer msglvl; ++ IGRAPH_F77_SAVE integer msglvl; + real tsaitr; +- static integer mxiter; ++ IGRAPH_F77_SAVE integer mxiter; + real tsgets, tsapps; + integer nrorth; + real tsconv; + diff --git a/tools/lapack/split.sed b/tools/lapack/split.sed new file mode 100644 index 0000000..ab2c17b --- /dev/null +++ b/tools/lapack/split.sed @@ -0,0 +1,55 @@ +# delete the header produced by f2c +/\/\* -- trans.*/,/\*\//{ +d +} + +# extract the first line of including f2c.h +/#include/,/^$/{ +w temp/header1 +d +} + +# possible local constants produced by f2c +/\/\* Table of constant values \*\//{ +s/^/ / +w temp/header2 +d +} +/^static.*=.*/,/^$/{ +s/^/ / +w temp/header2 +d +} + +# matches /* Subroutine */..._( or /* Complex */..._( +/^\/\* .*_(/,/^\{/{ +w temp/header3 +/^\{/!{ +d +} +} +# matches any function declaration line +/^[a-zA-Z].*_(/,/^\{/{ +w temp/header3 +/^\{/!{ +d +} +} + +/^\{/,/\/\*.*LAPACK/{ +/\/\*.*LAPACK/!{ +/^$/d +/^\{/d +w temp/prologue +d +} +} + +/\/\*.*LAPACK/,/\*\//{ +w temp/comment +d +} + + +w temp/code + diff --git a/tools/leakcheck b/tools/leakcheck new file mode 100755 index 0000000..e4a60a4 --- /dev/null +++ b/tools/leakcheck @@ -0,0 +1,133 @@ +#!/bin/bash +# +# Valgrind leak check for the given subdirectory. +# The given subdirectory must be an igraph root subdir +function usage { + echo Usage: $0 [directory] + echo directory must be the root of an igraph tree. + echo If omitted, assumes that the script itself is in the igraph tree +} + +VALGRIND=`which valgrind` + +if [ x$VALGRIND == x ]; then + echo Error: Valgrind is not installed + exit 3 +fi + +if [ x$1 == -h -o x$1 == --help ]; then + usage + exit 1 +fi + +ORIGDIR=`pwd` +DIR=$1 +if [ x$DIR == x ]; then + # No directory was given, start backtracking from the current + OK=0 + PREVDIR="/" + DIR=`pwd` + while [ $OK -eq 0 -a ${PREVDIR} != ${DIR} ]; do + if [ -f include/igraph.h ]; then + OK=1 + else + cd .. + DIR=`pwd` + fi + done + cd ${ORIGDIR} + if [ $OK -eq 0 ]; then + echo Error: no igraph tree was given and not in an igraph tree + usage + exit 4 + fi +fi + +if [ ! -d $DIR -o ! -f $DIR/include/igraph.h ]; then + echo $DIR is not an igraph root subdirectory + exit 2 +fi + +TESTBED="valgrind-testbed" +FULLDIR=`cd $DIR && pwd && cd ..` +TESTBEDDIR=${FULLDIR}/${TESTBED} + +if [ ! -f $DIR/configure ]; then + cd $DIR + ./bootstrap.sh + cd $ORIGDIR +fi + +if [ ! -d $TESTBED ]; then + mkdir $TESTBED +fi + +# run make distclean on the original tree if necessary +if [ -f ${FULLDIR}/Makefile ]; then + cd ${FULLDIR} && make distclean && cd ${ORIGDIR} +fi + +cd $TESTBED || exit 3 +${FULLDIR}/configure --enable-debug || ( cd $ORIGDIR; exit 4 ) +make || (cd $ORIGDIR; exit 5 ) +rm -f a.out + +if [ `grep -c "HAVE_TLS 1" config.h` -gt 0 ]; then + PTHREADS_LIBS=-lpthread +else + PTHREADS_LIBS= +fi + +mkdir -p examples/simple +rm -rf examples/simple/* +cp ${FULLDIR}/examples/simple/* examples/simple + +mkdir -p valgrind-logs +rm -rf valgrind-logs/* + +cd examples/simple + +SKIPS="igraph_layout_merge.c igraph_es_adj.c igraph_es_fromto.c" +for i in *.c; do + current=$i + OK=1 + for skip in $SKIPS; do + if [ $skip == $current ]; then OK=0; fi + done + echo -n "${current}... " + if [ $OK -eq 0 ]; then + echo "skipped." + else + gcc -g -o a.out $i -I${ORIGDIR}/include -I${TESTBEDDIR}/include -I${ORIGDIR}/src -I${TESTBEDDIR} -L../../src/.libs -ligraph ${PTHREADS_LIBS} + if [ -x a.out ]; then + echo -n "compiled... " + LOG=../../valgrind-logs/`basename ${current} .c`.log + LD_LIBRARY_PATH=../../src/.libs valgrind --tool=memcheck --error-exitcode=63 --leak-check=yes --show-reachable=yes --log-file=${LOG} --suppressions=${ORIGDIR}/tools/leakcheck.supp ./a.out >/dev/null + ERRCODE=$? + if [ $ERRCODE -eq 63 ]; then + echo "executed, memory access problems found!" + elif [ $ERRCODE -eq 77 ]; then + echo "skipped, OK" + rm ${LOG} + elif [ $ERRCODE -ne 0 ]; then + echo "test case failed, error code: $ERRCODE!" + elif [ `cat $LOG | grep -c 'no leaks are possible'` -ne 1 ]; then + if [ `cat $LOG | grep -c 'lost: 0 bytes'` -lt 2 ]; then + echo "executed, leaks found!" + else + echo "executed, OK" + rm ${LOG} + fi + else + echo "executed, OK" + rm ${LOG} + fi + else + echo "compilation FAILED!" + fi + rm -f a.out + fi +done +cd ../.. + +cd $ORIGDIR diff --git a/tools/leakcheck.supp b/tools/leakcheck.supp new file mode 100644 index 0000000..712bb85 --- /dev/null +++ b/tools/leakcheck.supp @@ -0,0 +1,6 @@ +{ + malloc/glp_init_env + Memcheck:Leak + fun:malloc + fun:glp_init_env +} diff --git a/tools/ltmain.patch b/tools/ltmain.patch new file mode 100644 index 0000000..126cc34 --- /dev/null +++ b/tools/ltmain.patch @@ -0,0 +1,15 @@ +--- ltmain.sh.old 2017-09-12 11:03:05.000000000 +0200 ++++ ltmain.sh 2017-09-12 11:04:08.000000000 +0200 +@@ -7273,9 +7273,11 @@ + # --sysroot=* for sysroot support + # -O*, -g*, -flto*, -fwhopr*, -fuse-linker-plugin GCC link-time optimization + # -stdlib=* select c++ std lib with clang ++ # -fsanitize=* memory and address sanitizers + -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \ + -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*|-tp=*|--sysroot=*| \ +- -O*|-g*|-flto*|-fwhopr*|-fuse-linker-plugin|-fstack-protector*|-stdlib=*) ++ -O*|-g*|-flto*|-fwhopr*|-fuse-linker-plugin|-fstack-protector*|-stdlib=*| \ ++ -fsanitize=*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + func_append compile_command " $arg" diff --git a/tools/protect_braces.sh b/tools/protect_braces.sh new file mode 100755 index 0000000..9b61713 --- /dev/null +++ b/tools/protect_braces.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +echo '{% raw %}' +cat - +echo '{% endraw %}' + diff --git a/tools/removeexamples.py b/tools/removeexamples.py new file mode 100755 index 0000000..a535cee --- /dev/null +++ b/tools/removeexamples.py @@ -0,0 +1,28 @@ +#! /usr/bin/env python + +import sys +from xml.etree.ElementTree import ElementTree + +def usage(): + print sys.argv[0], " " + +def main(): + if len(sys.argv) != 3: + usage() + sys.exit(2) + + # Read in + tree=ElementTree() + tree.parse(sys.argv[1]) + + # Remove examples + examples=tree.findall(".//example") + for ex in examples: + prog=ex.find("programlisting") + ex.remove(prog) + + # Write result + tree.write(sys.argv[2]) + +if __name__ == "__main__": + main() diff --git a/tools/seqdict/__init__.py b/tools/seqdict/__init__.py new file mode 100644 index 0000000..0841177 --- /dev/null +++ b/tools/seqdict/__init__.py @@ -0,0 +1,2 @@ +from ndict import seqdict +from mdict import mseqdict diff --git a/tools/seqdict/mdict.py b/tools/seqdict/mdict.py new file mode 100644 index 0000000..bcfb27d --- /dev/null +++ b/tools/seqdict/mdict.py @@ -0,0 +1,195 @@ +################################################################################ +# Sequential Dictionary Class # +# # +# by Wolfgang Grafen # +# # +# Version 0.2 11. February 2004 +# # +# email to: WolfgangGrafen@gmx.de # +# # +################################################################################ + +from ndict import seqdict #Sequential Single Value Dictionary +from UserList import UserList + +class MyUserList(UserList): + from UserList import UserList + def __init__(self,parent,liste=None): + UserList.__init__(self,liste) + self.parent = parent #remember parent for call-back + def __delitem__(self, i): + del self.data[i] + if self.data==[]: #call-back, deletes item of parent + index = self.parent.values().index([]) + del self.parent[index:index+1] + +class mseqdict(seqdict): #Sequential Multiple Value Dictionary + def __init__(self,List=[],Dict={}): + self.list = [] + self.dict = {} + if not List: + pass + elif type(List)==type({}): + for key,value in List.items(): + self.__setitem__(key,value) + elif List and not Dict: #dict.items() + for key,value in List: + if isinstance(value,MyUserList): + for v in value: + self.__setitem__(key,v) + else: + self.__setitem__(key,value) + elif type(List)==type(Dict)==type([]): + for key,value in map(None,List,Dict): + self.__setitem__(key,value) + else: + if isinstance(Dict.values()[0],MyUserList): + self.dict = Dict + self.list = List + else: + for key in List: + value = Dict[key] + if type(value)==type([]): + for v in value: + self.__setitem__(key,v) + else: + self.__setitem__(key,value) + + self_list = self.list + self_dict = self.dict + for k in self_list: + assert self_dict.has_key(k),"key %r not in self.dict" % k + + for k in self_dict.keys(): + if k not in self_list: + self_list.append(k) + + def __setitem__(self,key,value): + if not self.dict.has_key(key): + self.list.append(key) + if isinstance(value,MyUserList): + self.dict[key] = value + else: + self.dict[key]=MyUserList(self,[value]) + else: + values = self.dict[key] + if isinstance(value,MyUserList): + for v in value: + if not v in values: + values.extend(MyUserList(self,[v])) + else: + #if not value in values: + for v in values: + if v is value: + break + values.extend(MyUserList(self,[value])) + def __delitem__(self, key): + del self.dict[key] + self.list.remove(key) + + def append(self,key,value): + self.__setitem__(key,value) + def __setslice__(self,start,stop,newdict): + start = max(start,0); stop = max(stop,0) + delindexes = [] + for key in newdict.keys(): + if self.dict.has_key(key): + index = self.list.index(key) + delindexes.append(index) + if index < start: + start = start - 1 + stop = stop - 1 + elif index >= stop: + pass + else: + stop = stop - 1 + else: + self.dict[key]=UserList(self) + delindexes.sort() + delindexes.reverse() + for index in delindexes: + key = self.list[index] + #del self.dict[key] + del self.list[index] + self.list[start:stop] = newdict.list[:] + self.dict.update(newdict.dict) + def copy(self): + values = map(lambda x:x[:],self.values()) + return self.__class__(self.list,values) + def count(self,value): + vallist = self.dict.values() + return map(lambda x,y=value:x.count(y),vallist).count(1) + def filter(self,function,filtervalues=0): + if filtervalues == 1: #consider key and all keyvalues at once + dict = self.__class__() + for key,values in self.items(): + if function(key,values): + dict[key]=values + return dict + elif filtervalues == 2: #consider key and every keyvalue for itself + dict = self.__class__() + for key,values in self.items(): + for value in values: + if function(key,value): + dict[key]=value + return dict + else: #consider key only + liste=filter(function,self.list) + dict = {} + for i in liste: + dict[i]=self.dict[i] + return self.__class__(liste,dict) + def map(self,function,mapvalues=2): + if mapvalues == 1: #consider key and all keyvalues at once + dict = self.__class__() + for key,values in self.items(): + k,v = function(key,values) + dict[k]=v + return dict + else: #if mapvalues!=1: #consider key and every keyvalue for itself + dict = self.__class__() + for key,values in self.items(): + for value in values: + k,v = function(key,value) + dict[k]=v + return dict + def pop(self,key='...None',value='...None'): + if value=='...None': + if key=='...None': + pos = -1 + key = self.list[pos] + else: + pos = self.list.index(key) + tmp = self.dict[key] + del self.dict[key] + return {self.list.pop(pos):tmp} + else: + val = self.dict[key] + index = val.index(value) + tmp = val[index] + del val[index] + return {key:tmp} + def remove(self,key,value='...None'): + if value=='...None': + del self[key] + else: + index = self[key].index(value) + del self[key][index] + def sort(self,func1=None,func2=None): + if not func1: + self.list.sort() + else: + apply(self.list.sort,[func1]) + if func2: + for value in self.values(): + apply(value.sort,[func2]) + + def swap(self): + tmp = self.__class__() + for key,values in self.items(): + for value in values: + tmp[value]=key + self.list,self.dict = tmp.list,tmp.dict + del tmp + + def __repr__(self):return 'mseqdict(\n%s,\n%s)'%(self.list,self.dict) diff --git a/tools/seqdict/ndict.py b/tools/seqdict/ndict.py new file mode 100644 index 0000000..8d2d73e --- /dev/null +++ b/tools/seqdict/ndict.py @@ -0,0 +1,233 @@ +################################################################################ +# Sequential Dictionary Class # +# # +# by Wolfgang Grafen # +# # +# Version 0.2 11. February 2004 +# # +# email to: WolfgangGrafen@gmx.de # +# # +################################################################################ +# History +# Version 0.2 11. February 2004 # +# - Fixed slicing problem: +# +#>>> s = seqdict.seqdict(['b'], {'b': 'b'}) +#>>> s[0:0] = seqdict.seqdict(['a'], {'a': 'a'}) +# >>> s +#seqdict( +#['a', 'b'], #was ['a', 'b', 'a'], +#{'a': 'a', 'b': 'b'}) +# +# - Initialisation is now correct for: +# a) Not all keys of dict given in list: +#>>> seqdict(["a","b","c","d","b","a",],{"a":1,"b":2,"d":4,"c":3,"h":66,"j":77}) +#seqdict( +#['c', 'd', 'b', 'a', 'h', 'j'], +#{'a': 1, 'c': 3, 'b': 2, 'd': 4, 'h': 66, 'j': 77}) +# +# b) exceeding key "p" in list: +#>>> seqdict(["a","b","c","d","b","a","p"],{"a":1,"b":2,"d":4,"c":3}) +#Traceback (most recent call last): +# File "", line 1, in ? +# File "seqdict/hide/ndict.py", line 53, in __init__ +# assert self.dict.has_key(k),"key %r not in self.dict" % k +#AssertionError: key 'p' not in self.dict +# +# Version 0.1 24. Oct 2002 +# - Bugfix seqdict(["a","b","c"],[1,2,3]) evaluated into +# seqdict( ['a', 'b', 'd', 'c', 'd'], {'d': 4, 'b': 2, 'c': 3, 'a': 1}) +# +# Version 0.0 29. June 1999 + +def is_dict(whatever): + try: + whatever.keys() + return 1 + except: + return 0 + +class seqdict: + def __init__(self,List=[],Dict={}): + if is_dict(List): + self.list = List.keys() + self.dict = List.copy() + elif List and not Dict: + self.list=[] + self.dict={} + for i,j in List: + self.list.append(i) + self.dict[i]=j + elif type(List)==type(Dict)==type([]): + self.list = List + self.dict = {} + for key,value in map(None,List,Dict): + self.dict[key] = value + else: + lcopy = List[:] + lcopy.reverse() + lnew = [] + for l in lcopy: + if not l in lnew: + lnew.append(l) + + lnew.reverse() + self.list,self.dict = lnew,Dict.copy() + + self_list = self.list + self_dict = self.dict + for k in self_list: + assert self_dict.has_key(k),"key %r not in self.dict" % k + + for k in self_dict.keys(): + if k not in self_list: + self_list.append(k) + + def append(self,key,value): + if self.dict.has_key(key): + self.list.remove(key) + self.list.append(key) + self.dict[key]=value + def check(self): + if len(self.dict)==len(self.list): + l1=self.list[:];l1.sort() + l2=self.dict.keys();l2.sort() + return l1==l2 + return -1 + def clear(self): + self.list=[];self.dict={} + def copy(self): + if self.__class__ is seqdict: + return self.__class__(self.list,self.dict) + import copy + return copy.copy(self) + def __cmp__(self,other): + return cmp(self.dict,other.dict) or cmp(self.list,other.list) + def __getitem__(self,key): + if type(key)==type([]): + newdict={} + for i in key: + newdict[i]=self.dict[i] + return self.__class__(key,newdict) + return self.dict[key] + def __setitem__(self,key,value): + if not self.dict.has_key(key): + self.list.append(key) + self.dict[key]=value + def __delitem__(self, key): + del self.dict[key] + self.list.remove(key) + def __getslice__(self,start,stop): + start = max(start,0); stop = max(stop,0) + newdict = self.__class__() + for key in self.list[start:stop]: + newdict.dict[key]=self.dict[key] + newdict.list[:]=self.list[start:stop] + return newdict + def __setslice__(self,start,stop,newdict): + start = max(start,0); stop = max(stop,0) + delindexes = [] + for key in newdict.keys(): + if self.dict.has_key(key): + index = self.list.index(key) + delindexes.append(index) + if index < start: + start = start - 1 + stop = stop - 1 + elif index >= stop: + pass + else: + stop = stop - 1 + delindexes.sort() + delindexes.reverse() + for index in delindexes: + key = self.list[index] + del self.dict[key] + del self.list[index] + for key in self.list[start:stop]: + del self.dict[key] + self.list[start:stop] = newdict.list[:] + self.dict.update(newdict.dict) + def __delslice__(self, start, stop): + start = max(start, 0); stop = max(stop, 0) + for key in self.list[start:stop]: + del self.dict[key] + del self.list[start:stop] + def __add__(self,other): + newdict = self.__class__() + for key,value in self.items()+other.items(): + newdict.append(key,value) + return newdict + def __radd__(self,other): + newdict = self.__class__() + for key,value in other.items()+self.items(): + newdict.append(key,value) + return newdict + def count(self,value): + vallist = self.dict.values() + return vallist.count(value) + def extend(self,other): + self.update(other) + def filter(self,function): + liste=filter(function,self.list) + dict = {} + for i in liste: + dict[i]=self.dict[i] + return self.__class__(liste,dict) + def get(self, key, failobj=None): + return self.dict.get(key, failobj) + def index(self,key):return self.list.index(key) + def insert(self,i,x):self.__setslice__(i,i,x) + def items(self):return map(None,self.list,self.values()) + def has_key(self,key):return self.dict.has_key(key) + def keys(self):return self.list + def map(self,function): + return self.__class__(map(function,self.items())) + def values(self): + nlist = [] + for key in self.list: + nlist.append(self.dict[key]) + return nlist + def __len__(self):return len(self.list) + def pop(self,key=None): + if key==None: + pos = -1 + key = self.list[pos] + else: + pos = self.list.index(key) + tmp = self.dict[key] + del self.dict[key] + return {self.list.pop(pos):tmp} + def push(self,key,value): + self.append(key,value) + def reduce(self,function,start=None): + return reduce(function,self.items(),start) + def remove(self,key): + del self.dict[key] + self.list.remove(key) + def reverse(self):self.list.reverse() + def sort(self,*args):apply(self.list.sort,args) + def split(self,function,Ignore=None): + splitdict = seqdict() #self.__class__() + for key in self.list: + skey = function(key) + if skey != Ignore: + if not splitdict.has_key(skey): + splitdict[skey] = self.__class__() + splitdict[skey][key] = self.dict[key] + return splitdict + def swap(self): + tmp = self.__class__(map(lambda (x,y):(y,x),self.items())) + self.list,self.dict = tmp.list,tmp.dict + def update(self,newdict): + for key,value in newdict.items(): + self.__setitem__(key,value) + def slice(self,From,To=None,Step=1): + From = self.list.index(From) + if To:To = self.list.index(To) + else : + To = From + 1 + List = range(From,To,Step) + def getitem(pos,self=self):return self.list[pos] + return self.__getitem__(map(getitem,List)) + def __repr__(self):return 'seqdict(\n%s,\n%s)'%(self.list,self.dict) diff --git a/tools/stimulus.py b/tools/stimulus.py new file mode 100755 index 0000000..ee42c58 --- /dev/null +++ b/tools/stimulus.py @@ -0,0 +1,1488 @@ +#! /usr/bin/env python + +import re +import seqdict +import sys +import getopt +import os + +version="0.1" +date="Jul 29 2007" + +def usage(): + print "Stimulus version", version, date + print sys.argv[0], "-f -t -l language " + print ' ' * len(sys.argv[0]), "-i -o " + print ' ' * len(sys.argv[0]), "-h --help -v" + +################################################################################ +class StimulusError(Exception): + def __init__(self, message): + self.msg = message + def __str__(self): + return str(self.msg) + +################################################################################ +class PLexer: + def __init__(self, stream): + self.stream=stream + self.ws_stack=[0] + self.tokens=[] + self.lineno=0 + + def lineno(self): + return self.lineno + + def token(self): + keys=[] + + if (len(self.tokens)>0): + return self.tokens.pop(0) + + # Read a line, skip empty lines and comments + while True: + line=self.stream.readline(); self.lineno = self.lineno+1 + if line=="": + for k in keys: + self.tokens.append( ("key", k) ) + keys=[] + while len(self.ws_stack)>0: + self.tokens.append( ("dedent", "") ) + self.ws_stack.pop() + self.tokens.append( ("eof", "") ) + return self.tokens.pop(0) + if re.match("^[ \t]*$", line): continue + if re.match("^[ \t]*#", line): continue + break + + if line[-1]=="\n": line=line[:(len(line)-1)] + ws=re.match(r"^[ \t]*", line).span()[1] + line=line.strip() + if ws > self.ws_stack[-1]: + self.tokens.append( ("indent", "") ) + self.ws_stack.append(ws) + else: + for k in keys: + self.tokens.append( ("key", k) ) + keys=[] + while ws < self.ws_stack[-1]: + self.ws_stack.pop() + self.tokens.append( ("dedent", "") ) + if ws != self.ws_stack[-1]: + print "Bad indentation in line", self.lineno + exit + + # Ok, we're done with the white space, now let's see + # whether this line is continued + while line[-1]=="\\": + line=line[:(len(line)-1)] + line=line+"\n " + self.stream.readline().strip() ; self.lineno=self.lineno+1 + + # We have the line now, check whether there is a ':' in it + line=line.split(":", 1) + if len(line)>1: + line[0]=line[0].strip() + line[1]=line[1].strip() + if line[0]=="": + print "Missing keyword in line", self.lineno + exit + keys=line[0].split(",") + keys=[ k.strip() for k in keys ] + if line[1] == "": + self.tokens.append( ("key", keys.pop(0)) ) + else: + for k in keys: + self.tokens.append( ("key", k)) + self.tokens.append( ("indent", "") ) + self.tokens.append( ("text", line[1]) ) + self.tokens.append( ("dedent", "") ) + else: + self.tokens.append( ("text", line[0].strip()) ) + for k in keys: + self.tokens.append( ("dedent", "") ) + self.tokens.append( ("key", k) ) + self.tokens.append( ("indent", "") ) + keys=[] + + if self.tokens: + return self.tokens.pop(0) + +################################################################################ +class PParser: + def parse(self, stream): + lex=PLexer(stream) + val=seqdict.seqdict() + val_stack=[val, None] + nam_stack=[None, None] + + tok=lex.token() + while not tok[0]=="eof": + if tok[0]=="indent": + val_stack.append(None) + nam_stack.append(None) + elif tok[0]=="dedent": + v=val_stack.pop() + n=nam_stack.pop() + if n is None: + val_stack[-1]=v + else: + val_stack[-1][n]=v + elif tok[0]=="key": + if not nam_stack[-1] is None: + val_stack[-2][nam_stack[-1]]=val_stack[-1] + if tok[1][-5:]=="-list": + val_stack[-1]=seqdict.seqdict() + nam_stack[-1]=tok[1][:-5] + else: + val_stack[-1]={} + nam_stack[-1]=tok[1] + elif tok[0]=="text": + val_stack[-1]=tok[1] + tok=lex.token() + + return val + +################################################################################ + +def main(): + # Command line arguments + try: + optlist, args = getopt.getopt(sys.argv[1:], 't:f:l:i:o:hv', ['help']) + except getopt.GetoptError: + usage() + sys.exit(2) + + types=[]; functions=[]; inputs=[]; languages=[]; outputs=[]; verbose=False + + for o,a in optlist: + if o in ("-h", "--help"): + usage() + sys.exit() + elif o == "-o": + outputs.append(a) + elif o == "-t": + types.append(a) + elif o == "-f": + functions.append(a) + elif o == "-l": + languages.append(a) + elif o == "-i": + inputs.append(a) + elif o =="-v": + verbose=True + + # Parameter checks + # Note: the lists might be empty, but languages and outputs must + # have the same length. + if len(languages) != len(outputs): + print "Error: number of languages and output files must match" + sys.exit(4) + for l in languages: + if not l+"CodeGenerator" in globals(): + print "Error: unknown language:", l + sys.exit(6) + for f in types: + if not os.access(f, os.R_OK): + print "Error: cannot open type file:", f + sys.exit(5) + for f in functions: + if not os.access(f, os.R_OK): + print "Error: cannot open function file:", f + sys.exit(5) + for f in inputs: + if not os.access(f, os.R_OK): + print "Error: cannot open input file:", f + sys.exit(5) + # TODO: output files are not checked now + + # OK, do the trick: + for l in range(len(languages)): + cl=globals()[languages[l]+"CodeGenerator"] + cg=cl(functions, types) + cg.generate(inputs, outputs[l]) + +################################################################################ +class CodeGenerator: + def __init__(self, func, types): + # Set name + self.name=str(self.__class__).split(".")[-1] + self.name=self.name[0:len(self.name)-len("CodeGenerator")] + + # Parse function and type files + parser=PParser() + self.func=seqdict.seqdict() + for f in func: + ff=open(f) + newfunc=parser.parse(ff) + self.func.extend(newfunc) + ff.close() + + self.types=seqdict.seqdict() + for t in types: + ff=open(t) + newtypes=parser.parse(ff) + self.types.extend(newtypes) + ff.close() + + # The default return type is 'ERROR' + for f in self.func.keys(): + if 'RETURN' not in self.func[f]: + self.func[f]['RETURN']='ERROR' + + def generate(self, inputs, output): + out=open(output, "w") + self.append_inputs(inputs, out) + for f in self.func.keys(): + if 'FLAGS' in self.func[f]: + flags=self.func[f]['FLAGS'] + flags=flags.split(",") + flags=[ flag.strip() for flag in flags ] + else: + self.func[f]['FLAGS']=[] + self.generate_function(f, out) + out.close() + + def generate_function(self, f, out): + print "Error: invalid code generator, this method should be overridden" + sys.exit(1) + + def parse_params(self, function): + if "PARAMS" not in self.func[function]: + return seqdict.seqdict() + + params=self.func[function]["PARAMS"] + params=params.split(",") + params=[ p.strip() for p in params ] + params=[ p.split(" ", 1) for p in params ] + for p in range(len(params)): + if params[p][0] in ['OUT', 'IN', 'INOUT']: + params[p]=[params[p][0]] + params[p][1].split(" ", 1) + else: + params[p]=['IN', params[p][0]]+ params[p][1].split(" ", 1) + if '=' in params[p][2]: + params[p]=params[p][:2] + params[p][2].split("=", 1) + params=[ [ p.strip() for p in pp ] for pp in params ] + res=seqdict.seqdict() + for p in params: + if len(p)==3: + res[ p[2] ] = { 'mode': p[0], 'type': p[1] } + else: + res[ p[2] ] = { 'mode': p[0], 'type': p[1], 'default': p[3] } + return res + + def parse_deps(self, function): + if 'DEPS' not in self.func[function]: + return seqdict.seqdict() + + deps=self.func[function]["DEPS"] + deps=deps.split(",") + deps=[ d.strip() for d in deps ] + deps=[ d.split("ON", 1) for d in deps ] + deps=[ [ dd.strip() for dd in d ] for d in deps ] + deps=[ [d[0]] + d[1].split(" ",1) for d in deps ] + deps=[ [ dd.strip() for dd in d ] for d in deps ] + res=seqdict.seqdict() + for d in deps: + res[ d[0] ] = d[1:] + return res + + def append_inputs(self, inputs, output): + for i in inputs: + ii=open(i) + str=ii.read() + while str != "": + output.write(str) + str=ii.read() + ii.close() + pass + + def ignore(self, function): + if 'IGNORE' in self.func[function]: + ign=self.func[function]['IGNORE'] + ign=ign.split(",") + ign=[i.strip() for i in ign] + if self.name in ign: return True + return False + +################################################################################ +# GNU R, see http://www.r-project.org +# TODO: free memory when CTRL+C pressed, even on windows +################################################################################ + +class RNamespaceCodeGenerator(CodeGenerator): + def __init__(self, func, types): + CodeGenerator.__init__(self, func, types) + + def generate(self, inputs, output): + """This is very simple, we include an 'export' line for every + function which it not to be ignored by the RNamespace language. + Function names are taken from NAME-R if present, otherwise + underscores are converted to dots and the leading 'i' (from + 'igraph') is stripped to create the function name, + ie. igraph_clusters is mapped to graph.clusters.""" + out=open(output, "w") + self.append_inputs(inputs, out) + for f in self.func.keys(): + if (self.ignore(f)): + continue + name=self.func[f].get("NAME-R", f[1:].replace("_", ".")) + out.write("export(" + name + ")\n") + out.close() + + +class RRCodeGenerator(CodeGenerator): + def __init__(self, func, types): + CodeGenerator.__init__(self, func, types) + + def generate_function(self, function, out): + + # Ignore? + if self.ignore(function): + return + + name=self.func[function].get("NAME-R", function[1:].replace("_", ".")) + params=self.parse_params(function) + self.deps=self.parse_deps(function) + + # Check types + for p in params.keys(): + tname=params[p]['type'] + if not tname in self.types.keys(): + print "Error: Unknown type encountered:", tname + sys.exit(7) + params[p].setdefault('mode', 'IN') + + ## Roxygen to export the function + internal = self.func[function].get("INTERNAL") + print internal + if internal is None or internal == 'False': + out.write("#' @export\n") + + ## Header + ## do_par handles the translation of a single argument in the + ## header. Pretty simple, the only difficulty is that we + ## might need to add default values. Default values are taken + ## from a language specific dictionary, this is compiled from + ## the type file(s). + + ## So we take all arguments with mode 'IN' or 'INOUT' and + ## check whether they have a default value. If yes then we + ## check if the default value is given in the type file. If + ## yes then we use the value given there, otherwise the + ## default value is ignored silently. (Not very nice.) + + out.write(name) + out.write(" <- function(") + def do_par(pname): + tname=params[pname]['type'] + t=self.types[tname] + default="" + header=pname.replace("_", ".") + if 'HEADER' in t: + header=t['HEADER'] + if header: + header=header.replace("%I%", pname.replace("_", ".")) + else: + header="" + if 'default' in params[pname]: + if 'DEFAULT' in t and params[pname]['default'] in t['DEFAULT']: + default="=" + t['DEFAULT'][ params[pname]['default'] ] + else: + default="=" + params[pname]['default'] + header = header + default + + if pname in self.deps.keys(): + deps = self.deps[pname] + for i in range(len(deps)): + header=header.replace("%I"+str(i+1)+"%", deps[i]) + + return header + + head=[ do_par(n) for n,p in params.items() + if p['mode'] in ['IN','INOUT'] ] + head=[ h for h in head if h != "" ] + out.write(", ".join(head)) + out.write(") {\n") + + ## Argument checks, INCONV + ## We take 'IN' and 'INOUT' mode arguments and if they have an + ## INCONV field then we use that. This is typically for + ## argument checks, like we check here that the argument + ## supplied for a graph is indeed an igraph graph object. We + ## also covert numeric vectors to 'double' here. + + ## The INCONV fields are simply concatenated by newline + ## characters. + out.write(" # Argument checks\n") + def do_par(pname): + t=self.types[params[pname]['type']] + m=params[pname]['mode'] + if m in ['IN', 'INOUT'] and 'INCONV' in t: + if m in t['INCONV']: + res= " " + t['INCONV'][m] + else: + res=" " + t['INCONV'] + else: + res="" + res=res.replace("%I%", pname.replace("_", ".")) + + if pname in self.deps.keys(): + deps = self.deps[pname] + for i in range(len(deps)): + res=res.replace("%I"+str(i+1)+"%", deps[i]) + return res + + inconv=[ do_par(n) for n in params.keys() ] + inconv=[ i for i in inconv if i != "" ] + out.write("\n".join(inconv)+"\n\n") + + ## Function call + ## This is a bit more difficult than INCONV. Here we supply + ## each argument to the .Call function, if the argument has a + ## 'CALL' field then it is used, otherwise we simply use its + ## name. + ## argument. Note that arguments with empty CALL fields are + ## completely ignored, so giving an empty CALL field is + ## different than not giving it at all. + + ## Function call + def do_par(pname): + t=self.types[params[pname]['type']] + call=pname.replace("_", ".") + if 'CALL' in t: + call=t['CALL'] + if call: + call=call.replace('%I%', pname.replace("_", ".")) + else: + call="" + return call + + out.write(" on.exit( .Call(C_R_igraph_finalizer) )\n") + out.write(" # Function call\n") + out.write(" res <- .Call(C_R_" + function + ", ") + call=[ do_par(n) for n,p in params.items() if p['mode'] in ['IN', 'INOUT'] ] + call=[ c for c in call if c != "" ] + out.write(", ".join(call)) + out.write(")\n") + + ## Output conversions + def do_opar(pname, realname=None, iprefix=""): + if realname is None: realname=pname + t=self.types[params[pname]['type']] + mode=params[pname]['mode'] + if 'OUTCONV' in t and mode in t['OUTCONV']: + outconv=" " + t['OUTCONV'][mode] + else: + outconv="" + outconv=outconv.replace("%I%", iprefix+realname) + + if pname in self.deps.keys(): + deps = self.deps[pname] + for i in range(len(deps)): + outconv=outconv.replace("%I"+str(i+1)+"%", deps[i]) + return re.sub("%I[0-9]+%", "", outconv) + + retpars=[ n for n,p in params.items() if p['mode'] in + ['OUT', 'INOUT'] ] + + if len(retpars) <= 1: + outconv=[ do_opar(n, "res") for n in params.keys() ] + else: + outconv=[ do_opar(n, iprefix="res$") for n in params.keys() ] + + outconv=[ o for o in outconv if o != "" ] + + if len(retpars)==0: + # returning the return value of the function + rt=self.types[self.func[function]['RETURN']] + if 'OUTCONV' in rt: + retconv=" " + rt['OUTCONV']['OUT'] + else: + retconv="" + retconv=retconv.replace("%I%", "res") + # TODO: %I1% etc, is not handled here! + ret="\n".join(outconv) + "\n" + retconv + "\n" + elif len(retpars)==1: + # returning a single output value + ret="\n".join(outconv) + "\n" + else: + # returning a list of output values + None + ret="\n".join(outconv) + "\n" + out.write(ret) + + ## Some graph attributes to add + if 'GATTR-R' in self.func[function].keys(): + gattrs=self.func[function]['GATTR-R'].split(',') + gattrs=[ ga.split(' IS ', 1) for ga in gattrs ] + sstr=" res <- set.graph.attribute(res, '%s', '%s')\n" + for ga in gattrs: + aname=ga[0].strip() + aval=ga[1].strip().replace("'", "\\'") + out.write(sstr % (aname, aval)) + + ## Add some parameters as graph attributes + if 'GATTR-PARAM-R' in self.func[function].keys(): + pars=self.func[function]['GATTR-PARAM-R'].split(',') + pars=[ p.strip().replace("_", ".") for p in pars ] + sstr=" res <- set.graph.attribute(res, '%s', %s)\n" + for p in pars: + out.write(sstr % (p, p)) + + ## Set the class if requested + if 'CLASS-R' in self.func[function].keys(): + myclass=self.func[function]['CLASS-R'] + out.write(" class(res) <- \"" + myclass + "\"\n") + + ## See if there is a postprocessor + if 'PP-R' in self.func[function].keys(): + pp=self.func[function]['PP-R'] + out.write(" res <- " + pp + "(res)\n") + + out.write(" res\n}\n\n") + +class RCCodeGenerator(CodeGenerator): + def __init__(self, func, types): + CodeGenerator.__init__(self, func, types) + + def generate_function(self, function, out): + + # Ignore? + if self.ignore(function): + return + + params=self.parse_params(function) + self.deps = self.parse_deps(function) + + # Check types + for p in params.keys(): + tname=params[p]['type'] + if not tname in self.types.keys(): + print "Error: Unknown type encountered:", tname + sys.exit(7) + params[p].setdefault('mode', 'IN') + + ## Compile the output + ## This code generator is quite difficult, so we use different + ## functions to generate the approprite chunks and then + ## compile them together using a simple template. + ## See the documentation of each chunk below. + res={} + res['func']=function + res['header']=self.chunk_header(function, params) + res['decl']=self.chunk_declaration(function, params) + res['inconv']=self.chunk_inconv(function, params) + res['call']=self.chunk_call(function, params) + res['outconv']=self.chunk_outconv(function, params) + + # Replace into the template + text=""" +/*-------------------------------------------/ +/ %(func)-42s / +/-------------------------------------------*/ +%(header)s { + /* Declarations */ +%(decl)s + /* Convert input */ +%(inconv)s + /* Call igraph */ +%(call)s + /* Convert output */ +%(outconv)s + + UNPROTECT(1); + return(result); +}\n""" % res + + out.write(text) + + def chunk_header(self, function, params): + """The header. All functions return with a 'SEXP', so this is + easy. We just take the 'IN' and 'INOUT' arguments, all will + have type SEXP, and concatenate them by commas. The function name + is created by prefixing the original name with 'R_'.""" + def do_par(pname): + t=self.types[params[pname]['type']] + if 'HEADER' in t: + if t['HEADER']: + return t['HEADER'].replace("%I%", pname) + else: + return "" + else: + return pname + + inout=[ do_par(n) for n,p in params.items() + if p['mode'] in ['IN','INOUT'] ] + inout=[ "SEXP " + n for n in inout if n != "" ] + return "SEXP R_" + function + "(" + ", ".join(inout) + ")" + + def chunk_declaration(self, function, params): + """There are a couple of things to declare. First a C type is + needed for every argument, these will be supplied in the C + igraph call. Then, all 'OUT' arguments need a SEXP variable as + well, the result will be stored here. The return type + of the C function also needs to be declared, that comes + next. The result and names SEXP variables will contain the + final result, these are last. ('names' is not always used, but + it is easier to always declare it.) + """ + def do_par(pname): + cname="c_"+pname + t=self.types[params[pname]['type']] + if 'DECL' in t: + decl=" " + t['DECL'] + elif 'CTYPE' in t: + ctype = t['CTYPE'] + if type(ctype)==dict: + mode=params[pname]['mode'] + decl=" " + ctype[mode] + " " + cname + ";" + else: + decl=" " + ctype + " " + cname + ";" + else: + decl="" + return decl.replace("%C%", cname).replace("%I%", pname) + + inout=[ do_par(n) for n in params.keys() ] + out=[ " SEXP "+n+";" for n,p in params.items() + if p['mode']=='OUT' ] + + retpars=[ n for n,p in params.items() if p['mode'] in + ['OUT', 'INOUT'] ] + + rt=self.types[self.func[function]['RETURN']] + if 'DECL' in rt: + retdecl=" " + rt['DECL'] + elif 'CTYPE' in rt and len(retpars)==0: + ctype=rt['CTYPE'] + if type(ctype)==dict: + mode=params[pname]['mode'] + retdecl=" " + ctype[mode] + " " + "c_result;" + else: + retdecl=" " + rt['CTYPE'] + " c_result;" + else: + retdecl="" + + if len(retpars)<=1: + res = "\n".join(inout + out + [retdecl] + [" SEXP result;"]) + else: + res = "\n".join(inout + out + [retdecl] + + [" SEXP result, names;"]) + return res + + def chunk_inconv(self, function, params): + """Input conversions. Not only for types with mode 'IN' and + 'INOUT', eg. for 'OUT' vector types we need to allocate the + required memory here, do all the initializations, etc. Types + without INCONV fields are ignored. The usual %C%, %I% is + performed at the end. + """ + def do_par(pname): + cname="c_"+pname + t=self.types[params[pname]['type']] + mode=params[pname]['mode'] + if 'INCONV' in t and mode in t['INCONV']: + inconv=" " + t['INCONV'][mode] + else: + inconv="" + + if pname in self.deps.keys(): + deps = self.deps[pname] + for i in range(len(deps)): + inconv=inconv.replace("%C"+str(i+1)+"%", "c_"+deps[i]) + + return inconv.replace("%C%", cname).replace("%I%", pname) + + inconv=[ do_par(n) for n in params.keys() ] + inconv=[ i for i in inconv if i != "" ] + + return "\n".join(inconv) + + def chunk_call(self, function, params): + """Every single argument is included, independently of their + mode. If a type has a 'CALL' field then that is used after the + usual %C% and %I% substitutions, otherwise the standard 'c_' + prefixed C argument name is used. + """ + def docall(t, n): + if type(t)==dict: + mode=params[n]['mode'] + if mode in t: + return t[mode] + else: + return "" + else: + return t + + types=[ self.types[params[n]['type']] for n in params.keys() ] + call=map( lambda t, n: docall(t.get('CALL', "c_"+n), n), types, + params.keys() ) + call=map( lambda c, n: c.replace("%C%", "c_"+n).replace("%I%", n), + call, params.keys() ) + retpars=[ n for n,p in params.items() if p['mode'] in + ['OUT', 'INOUT'] ] + call=[ c for c in call if c != "" ] + res=" " + function + "(" + ", ".join(call) + ");\n" + if len(retpars)==0: + res=" c_result=" + res + return res + + def chunk_outconv(self, function, params): + """The output conversions, this is quite difficult. A function + may report its results in two ways: by returning it directly + or by setting a variable to which a pointer was passed. igraph + usually uses the latter and returns error codes, except for + some simple functions like 'igraph_vcount()' which cannot + fail. + + First we add the output conversion for all types. This is + easy. Note that even 'IN' arguments may have output + conversion, eg. this is the place to free memory allocated to + them in the 'INCONV' part. + + Then we check how many 'OUT' or 'INOUT' arguments we + have. There are three cases. If there is a single such + argument then that is already converted and we need to return + that. If there is no such argument then the output of the + function was returned, so we perform the output conversion for + the returned type and this will be the result. If there are + more than one 'OUT' and 'INOUT' arguments then they are + collected in a named list. The names come from the argument + names. + """ + def do_par(pname): + cname="c_"+pname + t=self.types[params[pname]['type']] + mode=params[pname]['mode'] + if 'OUTCONV' in t and mode in t['OUTCONV']: + outconv=" " + t['OUTCONV'][mode] + else: + outconv="" + + if pname in self.deps.keys(): + deps = self.deps[pname] + for i in range(len(deps)): + outconv=outconv.replace("%C"+str(i+1)+"%", "c_"+deps[i]) + return outconv.replace("%C%", cname).replace("%I%", pname) + + outconv=[ do_par(n) for n in params.keys() ] + outconv=[ o for o in outconv if o != "" ] + + retpars=[ n for n,p in params.items() if p['mode'] in ['OUT', 'INOUT'] ] + if len(retpars)==0: + # return the return value of the function + rt=self.types[self.func[function]['RETURN']] + if 'OUTCONV' in rt: + retconv=" " + rt['OUTCONV']['OUT'] + else: + retconv="" + retconv=retconv.replace("%C%", "c_result").replace("%I%", "result") + ret="\n".join(outconv) + "\n" + retconv + elif len(retpars)==1: + # return the single output value + retconv=" result=" + retpars[0] + ";" + ret="\n".join(outconv) + "\n" + retconv + else: + # create a list of output values + sets=map ( lambda c, n: " SET_VECTOR_ELT(result, "+str(c)+", "+n+");", + range(len(retpars)), retpars ) + names=map ( lambda c, n: " SET_STRING_ELT(names, "+str(c)+ + ", CREATE_STRING_VECTOR(\""+n+"\"));", + range(len(retpars)), retpars ) + ret="\n".join([" PROTECT(result=NEW_LIST(" + str(len(retpars)) + "));", + " PROTECT(names=NEW_CHARACTER(" + str(len(retpars)) + "));"]+ + outconv + sets + names + + [" SET_NAMES(result, names);" ] + + [" UNPROTECT("+str(len(sets)+1)+");" ]) + + return ret + +################################################################################ +# Java interface, experimental version using JNI (Java Native Interface) +# TODO: - everything :) This is just a PoC implementation. +################################################################################ + +class JavaCodeGenerator(CodeGenerator): + """Class containing the common parts of JavaJavaCodeGenerator and + JavaCCodeGenerator""" + package = "net.sf.igraph" + + def __init__(self, func, types): + CodeGenerator.__init__(self, func, types) + + def camelcase(s): + """Returns a camelCase version of the given string (as used in Java + libraries""" + parts = s.split("_") + result = [parts.pop(0)] + for part in parts: result.append(part.capitalize()) + return "".join(result) + camelcase=staticmethod(camelcase) + + def get_function_metadata(self, f, type_param="JAVATYPE"): + """Returns metadata for the given function based on the parameters. + f is the name of the function. The result is a dict with the following + keys: + + - java_modifiers: Java modifiers to be used in the .java file + - return_type: return type of the function + - name: name of the function + - argument_types: list of argument types + - self_name: name of the "self" argument + - is_static: whether the function is static + - is_constructor: whether the function is a constructor + """ + params = self.parse_params(f) + is_static, is_constructor = False, False + + # We will collect data related to the current function in a dict + data = {} + data["name"]=self.func[f].get("NAME-JAVA", \ + JavaCodeGenerator.camelcase(f[7:])) + data["java_modifiers"]=["public"] + + # Check parameter types to determine Java calling semantics + types = {"IN": [], "OUT": [], "INOUT": []} + for p in params.keys(): + types[params[p]["mode"]].append(params[p]) + + if len(types["OUT"])+len(types["INOUT"]) == 1: + # If a single one is OUT or INOUT and all others are + # INs, then this is our lucky day - the method fits the Java + # semantics + if len(types["OUT"]) > 0: + return_type_name = types["OUT"][0]["type"] + else: + return_type_name = types["INOUT"][0]["type"] + elif len(types["OUT"])+len(types["INOUT"]) == 0 and \ + self.func[f].has_key("RETURN"): + # There are only input parameters and the return type is specified, + # this also fits the Java semantics + return_type_name = self.func[f]["RETURN"] + else: + raise StimulusError, "%s: calling convention unsupported yet" % \ + data["name"] + + # Loop through the input parameters + method_arguments = [] + found_self = False + for p in params.keys(): + if params[p]["mode"] != "IN": continue + type_name = params[p]["type"] + if not found_self and type_name == "GRAPH": + # this will be the 'self' argument + found_self = True + data["self_name"] = p + continue + tdesc = self.types.get(type_name, {}) + if not tdesc.has_key(type_param): + raise StimulusError, "%s: unknown input type %s (needs %s), skipping" % \ + (data["name"], type_name, type_param) + method_arguments.append(" ".join([tdesc[type_param], p])) + data["argument_types"] = method_arguments + + if not found_self: + # Loop through INOUT arguments if we found no "self" yet + for p in params.keys(): + if params[p]["mode"] == "INOUT" and params[p]["type"] == "GRAPH": + found_self = True + data["self_name"] = p + break + + tdesc = self.types.get(return_type_name, {}) + if not tdesc.has_key(type_param): + raise StimulusError, "%s: unknown return type %s, skipping" % \ + (data["name"], return_type_name) + data["return_type"] = tdesc[type_param] + + if not found_self: + data["java_modifiers"].append("static") + data["name"] = data["name"][0].upper()+data["name"][1:] + + data["java_modifiers"] = " ".join(data["java_modifiers"]) + data["is_static"] = not found_self + data["is_constructor"] = is_constructor + + return data + + +class JavaJavaCodeGenerator(JavaCodeGenerator): + def __init__(self, func, types): + JavaCodeGenerator.__init__(self, func, types) + + def generate(self, inputs, output): + out=open(output, "w") + + if len(inputs)>1: + raise StimulusError, "Java code generator supports only a single input" + + input = open(inputs[0]) + for line in input: + if "%STIMULUS%" not in line: + out.write(line) + continue + + for f in self.func.keys(): + if (self.ignore(f)): continue + try: + func_metadata = self.get_function_metadata(f) + func_metadata["arguments"] = ", ".join(func_metadata["argument_types"]) + out.write(" %(java_modifiers)s native %(return_type)s %(name)s(%(arguments)s);\n" % func_metadata) + except StimulusError, e: + out.write(" // %s\n" % str(e)) + + out.close() + + +class JavaCCodeGenerator(JavaCodeGenerator): + def __init__(self, func, types): + JavaCodeGenerator.__init__(self, func, types) + + def generate_function(self, function, out): + # Ignore? + if self.ignore(function): return + + try: + self.metadata=self.get_function_metadata(function, "CTYPE") + except StimulusError, e: + out.write("/* %s */\n" % str(e)) + return + + params=self.parse_params(function) + self.deps = self.parse_deps(function) + + # Check types + for p in params.keys(): + tname=params[p]['type'] + if not tname in self.types.keys(): + print "W: Unknown type encountered:", tname + return + params[p].setdefault('mode', 'IN') + + ## Compile the output + ## This code generator is quite difficult, so we use different + ## functions to generate the approprite chunks and then + ## compile them together using a simple template. + ## See the documentation of each chunk below. + try: + res={} + res['func']=function + res['header']=self.chunk_header(function, params) + res['decl']=self.chunk_declaration(function, params) + res['before']=self.chunk_before(function, params) + res['inconv']=self.chunk_inconv(function, params) + res['call']=self.chunk_call(function, params) + res['outconv']=self.chunk_outconv(function, params) + res['after']=self.chunk_after(function, params) + except StimulusError, e: + out.write("/* %s */\n" % str(e)) + return + + # Replace into the template + text=""" +/*-------------------------------------------/ +/ %(func)-42s / +/-------------------------------------------*/ +%(header)s { + /* Declarations */ +%(decl)s + +%(before)s + /* Convert input */ +%(inconv)s + /* Call igraph */ +%(call)s + /* Convert output */ +%(outconv)s + +%(after)s + + return result; +}\n""" % res + + out.write(text) + + def chunk_header(self, function, params): + """The header. + + The name of the function is the igraph function name minus the + igraph_ prefix, camelcased and prefixed with the underscored + Java classname: net_sf_igraph_Graph_. The arguments + are mapped from the JAVATYPE key of the type dict. Static + methods also need a 'jclass cls' argument, ordinary methods + need 'jobject jobj'. Besides that, the Java environment pointer + is also passed. + """ + data = self.get_function_metadata(function, "JAVATYPE") + types = [] + + data["funcname"] = "Java_%s_Graph_%s" % \ + (self.package.replace(".", "_"), data["name"]) + + if data["is_static"]: + data["argument_types"].insert(0, "jclass cls") + else: + data["argument_types"].insert(0, "jobject "+data["self_name"]) + data["argument_types"].insert(0, "JNIEnv *env") + + data["types"] = ", ".join(data["argument_types"]) + + res="JNIEXPORT %(return_type)s JNICALL %(funcname)s(%(types)s)" % data + return res + + def chunk_declaration(self, function, params): + """The declaration part of the function body + + There are a couple of things to declare. First a C type is + needed for every argument, these will be supplied in the C + igraph call. Then, all 'OUT' arguments need an appropriate variable as + well, the result will be stored here. The return type + of the C function also needs to be declared, that comes + next. The result variable will contain the final result. Finally, + if the method is not static but we are returning a new Graph object + (e.g. in the case of igraph_linegraph), we need a jclass variable + to store the Java class object.""" + def do_cpar(pname): + cname="c_"+pname + t=self.types[params[pname]['type']] + if 'CDECL' in t: + decl=" " + t['CDECL'] + elif 'CTYPE' in t: + decl=" " + t['CTYPE'] + " " + cname + ";" + else: + decl="" + return decl.replace("%C%", cname).replace("%I%", pname) + def do_jpar(pname): + jname="j_"+pname + t=self.types[params[pname]['type']] + if 'JAVADECL' in t: + decl=" " + t['JAVADECL'] + elif 'JAVATYPE' in t: + decl=" " + t['JAVATYPE'] + " " + jname + ";" + else: + decl="" + return decl.replace("%J%", jname).replace("%I%", pname) + + inout=[ do_cpar(n) for n in params.keys() ] + out=[ do_jpar(n) for n,p in params.items() if p['mode']=='OUT'] + + rt=self.types[self.func[function]['RETURN']] + if 'CDECL' in rt: + retdecl=" " + rt['CDECL'] + elif 'CTYPE' in rt: + retdecl=" " + rt['CTYPE'] + " c__result;" + else: + retdecl="" + + rnames = [n for n,p in params.items() if p['mode'] in ['OUT','INOUT']] + jretdecl = "" + if len(rnames)>0: + n = rnames[0] + rtname = params[n]['type'] + else: + rtname = self.func[function]["RETURN"] + rt = self.types[rtname] + if 'JAVADECL' in rt: + jretdecl=" " + rt['JAVADECL'] + elif 'JAVATYPE' in rt: + jretdecl=" " + rt['JAVATYPE'] + " result;" + + decls = inout + out + [retdecl, jretdecl] + if not self.metadata["is_static"] and rtname == "GRAPH": + self.metadata["need_class_decl"] = True + decls.append(" jclass cls = (*env)->GetObjectClass(env, %s);" % self.metadata["self_name"]) + else: + self.metadata["need_class_decl"] = False + return "\n".join([i for i in decls if i!=""]) + + def chunk_before(self, function, params): + """We simply call Java_igraph_before""" + return ' Java_igraph_before();' + + def chunk_inconv(self, function, params): + """Input conversions. Not only for types with mode 'IN' and + 'INOUT', eg. for 'OUT' vector types we need to allocate the + required memory here, do all the initializations, etc. Types + without INCONV fields are ignored. The usual %C%, %I% is + performed at the end. + """ + def do_par(pname): + cname="c_"+pname + t=self.types[params[pname]['type']] + mode=params[pname]['mode'] + if 'INCONV' in t and mode in t['INCONV']: + inconv=" " + t['INCONV'][mode] + else: + inconv="" + + if pname in self.deps.keys(): + deps = self.deps[pname] + for i in range(len(deps)): + inconv=inconv.replace("%C"+str(i+1)+"%", "c_"+deps[i]) + + return inconv.replace("%C%", cname).replace("%I%", pname) + + inconv=[ do_par(n) for n in params.keys() ] + inconv=[ i for i in inconv if i != "" ] + + return "\n".join(inconv) + + def chunk_call(self, function, params): + """Every single argument is included, independently of their + mode. If a type has a 'CALL' field then that is used after the + usual %C% and %I% substitutions, otherwise the standard 'c_' + prefixed C argument name is used. + """ + types=[ self.types[params[n]['type']] for n in params.keys() ] + call=map( lambda t, n: t.get('CALL', "c_"+n), types, params.keys() ) + call=map( lambda c, n: c.replace("%C%", "c_"+n).replace("%I%", n), + call, params.keys() ) + lines = [" if ((*env)->ExceptionCheck(env)) {", \ + " c__result = IGRAPH_EINVAL;", \ + " } else {", \ + " c__result = " + function + "(" + ", ".join(call) + ");", \ + " }"] + return "\n".join(lines) + + def chunk_outconv(self, function, params): + """The output conversions, this is quite difficult. A function + may report its results in two ways: by returning it directly + or by setting a variable to which a pointer was passed. igraph + usually uses the latter and returns error codes, except for + some simple functions like 'igraph_vcount()' which cannot + fail. + + First we add the output conversion for all types. This is + easy. Note that even 'IN' arguments may have output + conversion, eg. this is the place to free memory allocated to + them in the 'INCONV' part. + + Then we check how many 'OUT' or 'INOUT' arguments we + have. There are three cases. If there is a single such + argument then that is already converted and we need to return + that. If there is no such argument then the output of the + function was returned, so we perform the output conversion for + the returned type and this will be the result. The case of + more than one 'OUT' and 'INOUT' arguments is not yet supported by + the Java interface. + """ + def do_par(pname): + cname="c_"+pname + jname="j_"+pname + t=self.types[params[pname]['type']] + mode=params[pname]['mode'] + if 'OUTCONV' in t and mode in t['OUTCONV']: + outconv=" " + t['OUTCONV'][mode] + else: + outconv="" + return outconv.replace("%C%", cname).replace("%I%", jname) + + outconv=[ do_par(n) for n in params.keys() ] + outconv=[ o for o in outconv if o != "" ] + + retpars=[ (n,p) for n,p in params.items() if p['mode'] in ['OUT', 'INOUT'] ] + if len(retpars)==0: + # return the return value of the function + rt=self.types[self.func[function]['RETURN']] + if 'OUTCONV' in rt: + retconv=" " + rt['OUTCONV']['OUT'] + else: + retconv="" + retconv=retconv.replace("%C%", "c__result").replace("%I%", "result") + if len(retconv)>0: outconv.append(retconv) + ret="\n".join(outconv) + elif len(retpars)==1: + # return the single output value + if retpars[0][1]['mode'] == "OUT": + # OUT parameter + retconv=" result = j_" + retpars[0][0] + ";" + else: + # INOUT parameter + retconv=" result = " + retpars[0][0] + ";" + outconv.append(retconv) + + outconv.insert(0, "if (c__result == 0) {") + outconv.extend(["} else {", " result = 0;", "}"]) + outconv = [" %s" % line for line in outconv] + ret="\n".join(outconv) + else: + raise StimulusError, "%s: the case of multiple outputs not supported yet" % function + + return ret + + def chunk_after(self, function, params): + """We simply call Java_igraph_after""" + return ' Java_igraph_after();' + + + +################################################################################ +# Shell interface, igraph functions directly from the command line +# TODO: - read/write default input/output from/to stdin/stdout +# - short options +# - prefixed output (?) +# - default values depending on other parameters +# - other input/output graph formats, to be controlled by +# environment variables (?): IGRAPH_INGRAPH, IGRAPH_OUTGRAPH +################################################################################ + +class ShellLnCodeGenerator(CodeGenerator): + def __init__(self, func, types): + CodeGenerator.__init__(self, func, types) + + def generate(self, inputs, output): + out=open(output, "w") + self.append_inputs(inputs, out) + for f in self.func.keys(): + if (self.ignore(f)): + continue + out.write(f+"\n") + out.close() + +class ShellCodeGenerator(CodeGenerator): + def __init__(self, func, types): + CodeGenerator.__init__(self, func, types) + + def generate(self, inputs, output): + out=open(output, "w") + self.append_inputs(inputs, out) + out.write("\n/* Function prototypes first */\n\n") + + for f in self.func.keys(): + if self.ignore(f): continue + if 'FLAGS' in self.func[f]: + flags=self.func[f]['FLAGS'] + flags=flags.split(",") + flags=[ flag.strip() for flag in flags ] + else: + self.func[f]['FLAGS']=[] + self.generate_prototype(f, out) + + out.write("\n/* The main function */\n\n") + out.write("int main(int argc, char **argv) {\n\n") + out.write(" const char *base=basename(argv[0]);\n\n ") + for f in self.func.keys(): + if self.ignore(f): continue + out.write("if (!strcasecmp(base, \""+f+ + "\")) {\n return shell_"+f+"(argc, argv);\n } else ") + out.write("{\n printf(\"Unknown function, exiting\\n\");\n") + out.write(" }\n\n shell_igraph_usage(argc, argv);\n return 0;\n\n}\n"); + + out.write("\n/* The functions themselves at last */\n") + for f in self.func.keys(): + if self.ignore(f): continue + self.generate_function(f, out) + + out.close() + + def generate_prototype(self, function, out): + out.write("int shell_"+function+"(int argc, char **argv);\n") + + def generate_function(self, function, out): + params=self.parse_params(function) + + # Check types, also enumerate them + args=seqdict.seqdict() + for p in params.keys(): + tname=params[p]['type'] + if not tname in self.types.keys(): + print "Error: Unknown type encountered:", tname + sys.exit(7) + params[p].setdefault('mode', 'IN') + t=self.types[tname] + mode=params[p]['mode'] + if 'INCONV' in t or 'OUTCONV' in t: + args[p]=params[p].copy() + args[p]['shell_no']=len(args)-1 + if mode=="INOUT": + args[p]['mode']='IN' + args[p+'-out']=params[p].copy() + args[p+'-out']['mode']='OUT' + args[p+'-out']['shell_no']=len(args)-1 + if 'INCONV' not in t or 'IN' not in t['INCONV']: + print "Warning: no INCONV for type", tname, ", mode IN" + if 'OUTCONV' not in t or 'OUT' not in t['OUTCONV']: + print "Warning: no OUTCONV for type", tname, ", mode OUT" + if mode =='IN' and ('INCONV' not in t or mode not in t['INCONV']): + print "Warning: no INCONV for type", tname, ", mode", mode + if mode == 'OUT' and ('OUTCONV' not in t or mode not in t['OUTCONV']): + print "Warning: no OUTCONV for type", tname, ", mode", mode + + res={'nargs': len(args)} + res['func']=function + res['args']=self.chunk_args(function, args) + res['decl']=self.chunk_decl(function, params) + res['inconv']=self.chunk_inconv(function, args) + res['call']=self.chunk_call(function, params) + res['outconv']=self.chunk_outconv(function, args) + res['default']=self.chunk_default(function, args) + res['usage']=self.chunk_usage(function, args) + text=""" +/*-------------------------------------------/ +/ %(func)-42s / +/-------------------------------------------*/ +void shell_%(func)s_usage(char **argv) { +%(usage)s + exit(1); +} + +int shell_%(func)s(int argc, char **argv) { + +%(decl)s + + int shell_seen[%(nargs)s]; + int shell_index=-1; + struct option shell_options[]= { %(args)s + { "help",no_argument,0,%(nargs)s }, + { 0,0,0,0 } + }; + + /* 0 - not seen, 1 - seen as argument, 2 - seen as default */ + memset(shell_seen, 0, %(nargs)s*sizeof(int)); +%(default)s + + /* Parse arguments and read input */ + while (getopt_long(argc, argv, "", shell_options, &shell_index) != -1) { + + if (shell_index==-1) { + exit(1); + } + + if (shell_seen[shell_index]==1) { + fprintf(stderr, "Error, `--%%s' argument given twice.\\n", + shell_options[shell_index].name); + exit(1); + } + shell_seen[shell_index]=1; +%(inconv)s + shell_index=-1; + } + + /* Check that we have all arguments */ + for (shell_index=0; shell_index<%(nargs)s; shell_index++) { + if (!shell_seen[shell_index]) { + fprintf(stderr, "Error, argument missing: `--%%s'.\\n", + shell_options[shell_index].name); + exit(1); + } + } + + /* Do the operation */ +%(call)s + + /* Write the result */ +%(outconv)s + + return 0; +}\n""" % res + out.write(text) + + def chunk_args(self, function, params): + res=[ ['"'+n+'"',"required_argument","0", str(p['shell_no']) ] + for n,p in params.items() ] + res=[ "{ "+",".join(e)+" }," for e in res ] + return "\n ".join(res) + + def chunk_decl(self, function, params): + def do_par(pname): + t=self.types[params[pname]['type']] + if 'DECL' in t: + decl=" " + t['DECL'].replace("%C%", pname) + elif 'CTYPE' in t: + decl=" " + t['CTYPE'] + " " + pname + else: + decl="" + if 'default' in params[pname]: + if 'DEFAULT' in t and params[pname]['default'] in t['DEFAULT']: + default="="+t['DEFAULT'][params[pname]['default']] + else: + default="="+params[pname]['default'] + else: + default="" + if decl: return decl+default+";" + else: return "" + + decl=[ do_par(n) for n in params.keys() ] + inout=[ " char* shell_arg_"+n+"=0;" for n,p in params.items() + if p['mode'] in ['INOUT','OUT'] ] + rt=self.types[self.func[function]['RETURN']] + if 'DECL' in rt: + retdecl=" " + rt['DECL'] + elif 'CTYPE' in rt: + retdecl=" " + rt['CTYPE'] + " shell_result;" + else: + retdecl="" + + if self.func[function]['RETURN'] != 'ERROR': + retchar=" char *shell_arg_shell_result=\"-\";" + else: + retchar="" + return "\n".join(decl+inout+[retdecl, retchar]) + + def chunk_default(self, function, params): + def do_par(pname): + t=self.types[params[pname]['type']] + if 'default' in params[pname]: + res=" shell_seen["+str(params[pname]['shell_no'])+"]=2;" + else: + res="" + return res + + res= [ do_par(n) for n in params.keys() ] + res= [ n for n in res if n != "" ] + return "\n".join(res) + + def chunk_inconv(self, function, params): + def do_par(pname): + t=self.types[params[pname]['type']] + mode=params[pname]['mode'] + if 'INCONV' in t and mode in t['INCONV']: + inconv="" + t['INCONV'][mode] + else: + inconv="" + if pname.endswith('-out'): pname=pname[0:-4] + return inconv.replace("%C%", pname) + + inconv=[ " case "+str(p['shell_no'])+": /* "+n+" */\n "+ do_par(n) + for n,p in params.items() ] + inconv=[ n+"\n break;" for n in inconv ] + inconv=[ "".join(n) for n in inconv ] + text="\n switch (shell_index) {\n"+"\n".join(inconv)+ \ + "\n case "+str(len(inconv))+":\n shell_"+function+"_usage(argv);\n break;"+ \ + "\n default:\n break;\n }\n" + return text + + def chunk_call(self, function, params): + types=[ self.types[params[n]['type']] for n in params.keys() ] + call=map( lambda t,n: t.get('CALL', n), types, params.keys() ) + call=map( lambda c,n: c.replace("%C%", n), call, params.keys() ) + return " shell_result=" + function + "(" + ", ".join(call) + ");" + + def chunk_outconv(self, function, params): + def do_par(pname): + t=self.types[params[pname]['type']] + mode=params[pname]['mode'] + if 'OUTCONV' in t and mode in t['OUTCONV']: + outconv=" " + t['OUTCONV'][mode] + else: + outconv="" + if pname.endswith('-out'): pname=pname[0:-4] + return outconv.replace("%C%", pname) + + outconv=[ do_par(n) for n in params.keys() ] + rt=self.types[self.func[function]['RETURN']] + if 'OUTCONV' in rt and 'OUT' in rt['OUTCONV']: + rtout=" " + rt['OUTCONV']['OUT'] + else: + rtout="" + outconv.append(rtout.replace("%C%", "shell_result")) + outconv=[ o for o in outconv if o != "" ] + return "\n".join(outconv) + + def chunk_usage(self, function, params): + res=[ "--"+n+"=<"+n+">" for n in params.keys() ] + return " printf(\"%s "+" ".join(res)+"\\n\", basename(argv[0]));" + +################################################################################ +if __name__ == "__main__": + main() + diff --git a/tools/test-icc-compiler.sh b/tools/test-icc-compiler.sh new file mode 100755 index 0000000..c9109b7 --- /dev/null +++ b/tools/test-icc-compiler.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Test igraph compilation with Intel's C compiler + +set -e + +ICC_DIR=/opt/intel +source ${ICC_DIR}/bin/compilervars.sh intel64 + +CC=icc +CXX=icpc +LD=xild +AR=xiar +LANG=en +LANGUAGE=en +LC_ALL=C + +export CC CXX LD AR LANG LANGUAGE LC_ALL + +IGRAPH_ROOT=`dirname $0`/.. +cd ${IGRAPH_ROOT} + +rm -rf build-icc +mkdir build-icc +cd build-icc +../configure +make 2>stderr.log +cd .. \ No newline at end of file diff --git a/tools/virtual/packer/OSX-10.8.4/template.json b/tools/virtual/packer/OSX-10.8.4/template.json new file mode 100644 index 0000000..8973e12 --- /dev/null +++ b/tools/virtual/packer/OSX-10.8.4/template.json @@ -0,0 +1,66 @@ +{ + "builders": [ + { + "type": "vmware", + "boot_wait": "2s", + "disk_size": 20480, + "guest_os_type": "darwin12-64", + "iso_checksum": "db6b44cd118e1a8bfcaaf329ad378f12", + "iso_checksum_type": "md5", + "iso_url": "file:///Users/csardi/ISO/OSX_InstallESD_10.8.4_12E55.dmg", + "shutdown_command": "echo 'vagrant'|sudo -S shutdown -h now", + "skip_compaction": true, + "ssh_password": "vagrant", + "ssh_port": 22, + "ssh_username": "vagrant", + "ssh_wait_timeout": "10000s", + "tools_upload_flavor": "darwin", + "headless": true, + "vmx_data": { + "cpuid.coresPerSocket": "1", + "memsize": "4096", + "numvcpus": "2", + "firmware": "efi", + "keyboardAndMouseProfile": "macProfile", + "smc.present": "TRUE", + "hpet0.present": "TRUE", + "ich7m.present": "TRUE", + "ehci.present": "TRUE", + "usb.present": "TRUE" + } + } + ], + "provisioners": [ + { + "destination": "/private/tmp/kcpassword", + "source": "../scripts/support/kcpassword", + "type": "file" + }, + { + "execute_command": "chmod +x {{ .Path }}; sudo {{ .Vars }} {{ .Path }}", + "scripts": [ + "../scripts/osx-vagrant.sh", + "../scripts/vmware.sh", + "../scripts/xcode-cli-tools.sh" + ], + "type": "shell" + }, + { + "type": "shell", + "script": "../scripts/install-python.sh" + }, + { + "execute_command": "chmod +x {{ .Path }}; sudo {{ .Vars }} {{ .Path }}", + "inline": [ + "[ -z \"{{user `autologin_vagrant_user`}}\" ] && exit", + "echo \"Enabling automatic GUI login for the 'vagrant' user..\"", + "cp /private/tmp/kcpassword /private/etc/kcpassword", + "/usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser vagrant" + ], + "type": "shell" + } + ], + "variables": { + "autologin_vagrant_user": "" + } +} diff --git a/tools/virtual/packer/floppy/Autounattend.xml b/tools/virtual/packer/floppy/Autounattend.xml new file mode 100755 index 0000000..d1207a4 --- /dev/null +++ b/tools/virtual/packer/floppy/Autounattend.xml @@ -0,0 +1,161 @@ + + + + + en-US + en-US + en-US + en-US + + + + + + + /IMAGE/NAME + Windows 7 ENTERPRISE + + + + 0 + 1 + + OnError + + + + true + + + OnError + + + + 1 + 10000 + Primary + + + + + true + true + NTFS + C + 1 + 1 + + + 0 + true + + + + + + + + + + + dgBhAGcAcgBhAG4AdABQAGEAcwBzAHcAbwByAGQA + false</PlainText> + </Password> + <Description>Vagrant User</Description> + <DisplayName>vagrant</DisplayName> + <Group>Administrators</Group> + <Name>vagrant</Name> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <AutoLogon> + <Password> + <Value>dgBhAGcAcgBhAG4AdABQAGEAcwBzAHcAbwByAGQA</Value> + <PlainText>false</PlainText> + </Password> + <Enabled>true</Enabled> + <Username>vagrant</Username> + </AutoLogon> + <OOBE> + <NetworkLocation>Work</NetworkLocation> + <ProtectYourPC>3</ProtectYourPC> + </OOBE> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <CommandLine>REG ADD &quot;HKLM\System\CurrentControlSet\Control\Network\NewNetworkWindowOff&quot;</CommandLine> + <Description>Disable Set Network Location</Description> + <Order>1</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>REG ADD &quot;HKLM\System\CurrentControlSet\Services\Netlogon\Parameters&quot; /v DisablePasswordChange /t REG_DWORD /d 1 /f</CommandLine> + <Description>Disable computer password change</Description> + <Order>2</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c a:set-power-config.bat</CommandLine> + <Description>Turn off all power saving and timeouts</Description> + <Order>3</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <RequiresUserInput>true</RequiresUserInput> + <Order>4</Order> + <Description>Install Cygwin SSHD</Description> + <CommandLine>cmd /c a:install-cygwin-sshd.bat</CommandLine> + </SynchronousCommand> + </FirstLogonCommands> + </component> + </settings> + <settings pass="specialize"> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <ComputerName>*</ComputerName> + <TimeZone>Pacific Standard Time</TimeZone> + </component> + <component name="Microsoft-Windows-IE-InternetExplorer" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SearchScopes> + <Scope wcm:action="add"> + <ScopeDefault>true</ScopeDefault> + <ScopeDisplayName>Google</ScopeDisplayName> + <ScopeKey>Google</ScopeKey> + <ScopeUrl>http://www.google.com/search?q={searchTerms}</ScopeUrl> + </Scope> + </SearchScopes> + <DisableAccelerators>true</DisableAccelerators> + <DisableFirstRunWizard>true</DisableFirstRunWizard> + <Help_Page>about:blank</Help_Page> + </component> + <component name="Microsoft-Windows-IE-InternetExplorer" processorArchitecture="wow64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SearchScopes> + <Scope wcm:action="add"> + <ScopeDefault>true</ScopeDefault> + <ScopeDisplayName>Google</ScopeDisplayName> + <ScopeKey>Google</ScopeKey> + <ScopeUrl>http://www.google.com/search?q={searchTerms}</ScopeUrl> + </Scope> + </SearchScopes> + <DisableAccelerators>true</DisableAccelerators> + <DisableFirstRunWizard>true</DisableFirstRunWizard> + <Home_Page>about:blank</Home_Page> + </component> + <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <fDenyTSConnections>false</fDenyTSConnections> + </component> + <component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <FirewallGroups> + <FirewallGroup wcm:action="add" wcm:keyValue="RemoteDesktop"> + <Group>Remote Desktop</Group> + <Profile>all</Profile> + <Active>true</Active> + </FirewallGroup> + </FirewallGroups> + </component> + </settings> + <settings pass="offlineServicing"> + <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <EnableLUA>false</EnableLUA> + </component> + </settings> + <cpi:offlineImage cpi:source="wim:c:/users/misheska/sources/win7enterprise/x64/install.wim#Windows 7 ENTERPRISE" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> +</unattend> diff --git a/tools/virtual/packer/floppy/install-cygwin-sshd.bat b/tools/virtual/packer/floppy/install-cygwin-sshd.bat new file mode 100644 index 0000000..c65b9fd --- /dev/null +++ b/tools/virtual/packer/floppy/install-cygwin-sshd.bat @@ -0,0 +1,88 @@ +REM http://webcache.googleusercontent.com/search?q=cache:SjoPPpuQxuoJ:www.tcm.phy.cam.ac.uk/~mr349/cygwin_install.html+install+cygwin+ssh+commandline&cd=2&hl=nl&ct=clnk&gl=be&source=www.google.be + +REM create the cygwin directory +cmd /c mkdir %SystemDrive%\cygwin + +if "%PROCESSOR_ARCHITECTURE%" == "AMD64" (set ARCH=x86_64) else (set ARCH=x86) +set URL=http://cygwin.com/setup-%ARCH%.exe + +cmd /c bitsadmin /transfer CygwinSetupExe /download /priority normal %URL% %SystemDrive%\cygwin\cygwin-setup.exe + +REM goto a temp directory +cd /D %SystemDrive%\windows\temp + +set PACKAGES= alternatives +set PACKAGES=%PACKAGES%,csih +set PACKAGES=%PACKAGES%,cygrunsrv +set PACKAGES=%PACKAGES%,crypt +set PACKAGES=%PACKAGES%,diffutils +set PACKAGES=%PACKAGES%,libasn1_8 +set PACKAGES=%PACKAGES%,libattr1 +set PACKAGES=%PACKAGES%,libcom_err2 +set PACKAGES=%PACKAGES%,libcrypt0 +set PACKAGES=%PACKAGES%,libffi6 +set PACKAGES=%PACKAGES%,libgcc1 +set PACKAGES=%PACKAGES%,libgcrypt11 +set PACKAGES=%PACKAGES%,libgmp10 +set PACKAGES=%PACKAGES%,libgmp3 +set PACKAGES=%PACKAGES%,libgnutls26 +set PACKAGES=%PACKAGES%,libgpg-error0 +set PACKAGES=%PACKAGES%,libgssapi3 +set PACKAGES=%PACKAGES%,libheimbase1 +set PACKAGES=%PACKAGES%,libheimntlm0 +set PACKAGES=%PACKAGES%,libhx509_5 +set PACKAGES=%PACKAGES%,libiconv2 +set PACKAGES=%PACKAGES%,libidn11 +set PACKAGES=%PACKAGES%,libintl8 +set PACKAGES=%PACKAGES%,libkafs0 +set PACKAGES=%PACKAGES%,libkrb5_26 +set PACKAGES=%PACKAGES%,libmpfr4 +set PACKAGES=%PACKAGES%,libncursesw10 +set PACKAGES=%PACKAGES%,libopenssl100 +set PACKAGES=%PACKAGES%,libp11-kit0 +set PACKAGES=%PACKAGES%,libpcre0 +set PACKAGES=%PACKAGES%,libpcre1 +set PACKAGES=%PACKAGES%,libreadline7 +set PACKAGES=%PACKAGES%,libroken18 +set PACKAGES=%PACKAGES%,libsqlite3_0 +set PACKAGES=%PACKAGES%,libssp0 +set PACKAGES=%PACKAGES%,libtasn1_3 +set PACKAGES=%PACKAGES%,libwind0 +set PACKAGES=%PACKAGES%,libwrap0 +set PACKAGES=%PACKAGES%,openssh +set PACKAGES=%PACKAGES%,openssl +set PACKAGES=%PACKAGES%,rebase +set PACKAGES=%PACKAGES%,termcap +set PACKAGES=%PACKAGES%,terminfo +set PACKAGES=%PACKAGES%,wget +set PACKAGES=%PACKAGES%,zlib0 + +REM run the installation +%SystemDrive%\cygwin\cygwin-setup.exe -a %ARCH% -q -R %SystemDrive%\cygwin -P %PACKAGES% -s http://cygwin.mirrors.pair.com + +REM stop the service, instead of attempting to remove it +%SystemDrive%\cygwin\bin\bash -c 'PATH=/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin cygrunsrv -E sshd' + +REM /bin/ash is the right shell for this command +cmd /c %SystemDrive%\cygwin\bin\ash -c /bin/rebaseall + +cmd /c %SystemDrive%\cygwin\bin\bash -c 'PATH=/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin mkgroup -l'>%SystemDrive%\cygwin\etc\group + +cmd /c %SystemDrive%\cygwin\bin\bash -c 'PATH=/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin mkpasswd -l'>%SystemDrive%\cygwin\etc\passwd + +%SystemDrive%\cygwin\bin\bash -c 'PATH=/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin /usr/bin/ssh-host-config -y -c "ntsecbinmode mintty" -w "abc&&123!!" ' + +cmd /c if exist %Systemroot%\system32\netsh.exe netsh advfirewall firewall add rule name="SSHD" dir=in action=allow program="%SystemDrive%\cygwin\usr\sbin\sshd.exe" enable=yes + +cmd /c if exist %Systemroot%\system32\netsh.exe netsh advfirewall firewall add rule name="ssh" dir=in action=allow protocol=TCP localport=22 + +%SystemDrive%\cygwin\bin\bash -c 'PATH=/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin ln -s "$(/bin/dirname $(/bin/cygpath -D))" /home/$USERNAME' + +net start sshd + +REM Put local users home directories in the Windows Profiles directory +%SystemDrive%\cygwin\bin\bash -c 'PATH=/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin mkpasswd -l -p "$(/bin/cygpath -H)"'>%SystemDrive%\cygwin\etc\passwd + +REM Fix corrupt recycle bin +REM http://www.winhelponline.com/blog/fix-corrupted-recycle-bin-windows-7-vista/ +cmd /c rd /s /q %SystemDrive%\$Recycle.bin diff --git a/tools/virtual/packer/floppy/oracle-cert.cer b/tools/virtual/packer/floppy/oracle-cert.cer new file mode 100644 index 0000000000000000000000000000000000000000..6f3380d48553956036650bc711ca5aed73a1224d GIT binary patch literal 1398 zcmXqLVl6UgVu@P7%*4pVBoH|JX8KPK#!$sYJbaVIzH}P!vT<s)d9;1!Wn^S!WiV*m zV#sa4$;KSY!Y0fV8f++TAPVAe3G;=e7G(x!rswG>c;+SR8Oj^Tf+V<w#b6Q&Aw{Ld zB?^A2CFS`=*@o5zmJkI-A*n^V#R~ap3Z=!V3W+5O86_nJ#a8<I<>lpiWk6Glfu`yu z=jZAd6(lNXBx@QP80s15fHX4;tHMop&PgmTRxnm@&QD2I068QxFI~aNz|cU!+0j5w zoY&CMz{tSD01TplToWMI+{oC}($q4_pm8gzw}cG@LFRJ^b2%sGWTxd8<z*(q{K_uO z7nGV(o}Zth5S)=;lv->kZ6FC!%_Sn_UzC`flL~Z0Q9*uDVo7Fxo}rzA4aA?OE}7|> zC5br-o-PV7$LcEhW+oTq7w4yyC<FtE^2DN4g)pF%DPYYCWkxW=nXwse(8Q>O97Bw( z49rc8{0u;GE~X|%MuyHw?*%`8FjtXzIendtSKY3w*O-60EAjFQ2OV~w_ftDDLUzu_ z85<Wa=?S-*nejb)|0Bzi=ZAKyJXs+1{&U}<E!<r7eBtNQVs{iy`CpdM8+pV3kFKYd z;{O}R&%U~HRFb)|&*|X#58c1RGz)!dcTGuVbeeB)eCp1-hX0aX)OY`{ZrXppaE9c( z=3cQz#vO?t;*(yVb7)<pC3A9tz>SryzkP0TS}(uOwDSCq)A{l5xun*{K0kH+^Qm$< z{v9i2H}l%A36FnQYWvH^<3!H>xUK(c<{T^S(EK-X{(E*tk9{wuILuppXYONpzdIrm z6;8kD=Vfr5%TV)i?iWRwFs0(DVJoe?Co?fKGB7S~Vyp&+NI5v&$_leE888^|0pmoL zpONuD3kx$7dxL=kh%e7#X<%+(y1;mWVVgcE6#^4sa&fY;E+`r4CKu(PCP(LBXNZ&@ zP|CmsWP$>VtpO-ub0@TUFt+`0Wn>p;HqbKAVB^qc1LhxgCPpzCe97BD7-Whn3y%R8 zObau!K_QII$jFjvkZhm=;~OxxNkHA1pIlsk;yeRuxGE+rzDdkXL{YAf<eStY19^}; zlvyMU#2Q4-&s_PcTJ4MX$-e&UA6V7iKfbhlE--&^Lj33ibQCZQSp*Ek*tmecWMOPF zXJV9Q;V@tWCO}{aBPUQ`5duu0j10%my}A5s>tn4l8@-n`97fxDH}mY7!8P?YpYXXe zANBj<Zy#8B<=plhr2w0$&kh}Y;3_J_rq%O(!d;Kdh!69Qrx$0(Z#pBlLibZ#!*^ku zETe+LaLfPw_xTrys&70sPe)j1+iVq`aMSBM(lw_ziD+!<3Y)OpMqT!-=r7Lxi?JI@ z<mcTks*7urddn8t7Ob=7j{GmlCwc6O!YLD(?sT2IaC=(b)&muZ35!DO?Rt6F3ICk! zzWcisYuQ$<<p-zv%;P9Gxv*7GE-~|}O~UcDuPhm!mwENJ*Y;evpI^83&Bmf4#}q;F kZ>ww{IP}*EUbv-wBB=FaQSwz;n~wTPeHRx#@~!?40OZx*k^lez literal 0 HcmV?d00001 diff --git a/tools/virtual/packer/floppy/set-power-config.bat b/tools/virtual/packer/floppy/set-power-config.bat new file mode 100644 index 0000000..35b584e --- /dev/null +++ b/tools/virtual/packer/floppy/set-power-config.bat @@ -0,0 +1,5 @@ +REM Set power configuration to High Performance +powercfg -setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c +REM Monitor timeout +powercfg -Change -monitor-timeout-ac 0 +powercfg -Change -monitor-timeout-dc 0 diff --git a/tools/virtual/packer/http/preseed.cfg b/tools/virtual/packer/http/preseed.cfg new file mode 100644 index 0000000..4710a54 --- /dev/null +++ b/tools/virtual/packer/http/preseed.cfg @@ -0,0 +1,68 @@ +## Options to set on the command line +d-i debian-installer/locale string en_US.utf8 +d-i console-setup/ask_detect boolean false +d-i console-setup/layout string us + +d-i netcfg/get_hostname string unassigned-hostname +d-i netcfg/get_domain string unassigned-domain + +d-i time/zone string UTC +d-i clock-setup/utc-auto boolean true +d-i clock-setup/utc boolean true + +d-i kbd-chooser/method select American English + +d-i netcfg/wireless_wep string + +d-i base-installer/kernel/override-image string linux-server + +d-i debconf debconf/frontend select Noninteractive + +d-i pkgsel/install-language-support boolean false +tasksel tasksel/first multiselect standard, ubuntu-server + +d-i partman-auto/method string lvm + +d-i partman-lvm/confirm boolean true +d-i partman-lvm/device_remove_lvm boolean true +d-i partman-auto/choose_recipe select atomic + +d-i partman/confirm_write_new_label boolean true +d-i partman/confirm_nooverwrite boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true + +# Write the changes to disks and configure LVM? +d-i partman-lvm/confirm boolean true +d-i partman-lvm/confirm_nooverwrite boolean true +d-i partman-auto-lvm/guided_size string max + +# Default user +d-i passwd/user-fullname string vagrant +d-i passwd/username string vagrant +d-i passwd/user-password password vagrant +d-i passwd/user-password-again password vagrant +d-i user-setup/encrypt-home boolean false +d-i user-setup/allow-password-weak boolean true + +# Minimum packages (see postinstall.sh) +d-i pkgsel/include string openssh-server ntp + +# Upgrade packages after debootstrap? (none, safe-upgrade, full-upgrade) +# (note: set to none for speed) +d-i pkgsel/upgrade select none + +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i finish-install/reboot_in_progress note + +d-i pkgsel/update-policy select none + +choose-mirror-bin mirror/http/proxy string + +# really, really dist-upgrade +d-i preseed/late_command string in-target apt-get update ; \ + in-target apt-get -y dist-upgrade ; \ + in-target apt-get -y autoremove ; \ + in-target apt-get autoclean ; \ + in-target apt-get clean diff --git a/tools/virtual/packer/scripts/apt.sh b/tools/virtual/packer/scripts/apt.sh new file mode 100644 index 0000000..60560cb --- /dev/null +++ b/tools/virtual/packer/scripts/apt.sh @@ -0,0 +1,4 @@ +#!/bin/sh -x +apt-get -y install linux-headers-$(uname -r) build-essential +apt-get -y install zlib1g-dev libssl-dev libreadline-gplv2-dev libyaml-dev +apt-get -y install nfs-common diff --git a/tools/virtual/packer/scripts/base.sh b/tools/virtual/packer/scripts/base.sh new file mode 100644 index 0000000..42c4ce7 --- /dev/null +++ b/tools/virtual/packer/scripts/base.sh @@ -0,0 +1,7 @@ +#!/bin/sh -x + +#Turn off and disable ufw +echo "Stopping ufw..." +service ufw stop +echo "Disabling ufw..." +ufw disable \ No newline at end of file diff --git a/tools/virtual/packer/scripts/build_time.sh b/tools/virtual/packer/scripts/build_time.sh new file mode 100644 index 0000000..8a12b83 --- /dev/null +++ b/tools/virtual/packer/scripts/build_time.sh @@ -0,0 +1,2 @@ +#!/bin/sh -x +date > /etc/vagrant_box_build_time diff --git a/tools/virtual/packer/scripts/cleanup.sh b/tools/virtual/packer/scripts/cleanup.sh new file mode 100644 index 0000000..5f11d17 --- /dev/null +++ b/tools/virtual/packer/scripts/cleanup.sh @@ -0,0 +1,32 @@ +#!/bin/bash -x +echo "Cleaning up dhcp leases..." +rm /var/lib/dhcp/* + +echo "Cleaning up udev rules..." +rm /etc/udev/rules.d/70-persistent-net.rules +mkdir /etc/udev/rules.d/70-persistent-net.rules +rm -rf /dev/.udev/ +rm /lib/udev/rules.d/75-persistent-net-generator.rules + +#apt cleanup +echo "Running apt-get remove kernel headers..." +apt-get -y remove linux-headers-$(uname -r) +echo "Running remove older kernel headers" +dpkg -l 'linux-*' | sed '/^ii/!d;/'"$(uname -r | sed "s/\(.*\)-\([^0-9]\+\)/\1/")"'/d;s/^[^ ]* [^ ]* \([^ ]*\).*/\1/;/[0-9]/!d' | xargs sudo apt-get -y purge +echo "Running apt-get clean..." +apt-get -y clean +echo "Running apt-get autoclean..." +apt-get -y autoclean +echo "Running apt-get remove..." +apt-get -y remove +echo "Running apt-get auto-remove..." +apt-get -y autoremove + +echo "pre-up sleep 2" >> /etc/network/interfaces + +#zero out disk space. Replacing free space with 0s makes the drive more easily compressed +echo "Zeroing out disk..." +dd if=/dev/zero of=/EMPTY bs=1M || true +rm -f /EMPTY +exit + diff --git a/tools/virtual/packer/scripts/compilers.sh b/tools/virtual/packer/scripts/compilers.sh new file mode 100644 index 0000000..d281e88 --- /dev/null +++ b/tools/virtual/packer/scripts/compilers.sh @@ -0,0 +1,6 @@ +#!/bin/sh -x + +apt-get -y install gcc-4.7 g++-4.7 gfortran-4.7 \ + gcc-4.8 g++-4.8 gfortran-4.8 \ + gfortran \ + clang-3.4 diff --git a/tools/virtual/packer/scripts/git.sh b/tools/virtual/packer/scripts/git.sh new file mode 100644 index 0000000..4481be5 --- /dev/null +++ b/tools/virtual/packer/scripts/git.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +apt-get -y install git diff --git a/tools/virtual/packer/scripts/igraphdeps.sh b/tools/virtual/packer/scripts/igraphdeps.sh new file mode 100644 index 0000000..3cb3b3e --- /dev/null +++ b/tools/virtual/packer/scripts/igraphdeps.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +apt-get -y install autoconf automake bison flex libtool \ + libxml2-dev libgmp-dev docbook2x source-highlight libxml2-utils \ + mesa-common-dev libglu1-mesa-dev libpng-dev curl libreadline6-dev \ + libsqlite3-dev make libssl-dev zlib1g-dev libbz2-dev diff --git a/tools/virtual/packer/scripts/install-python.sh b/tools/virtual/packer/scripts/install-python.sh new file mode 100755 index 0000000..94b3191 --- /dev/null +++ b/tools/virtual/packer/scripts/install-python.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Compiles and installs all the supported Python distributions using pyenv + +VERSIONS="2.6.9 2.7.5 3.1.5 3.2.5 3.3.2" + +cd ~ +if [ ! -d python ]; then + mkdir python +fi + +if [ ! -d pyenv ]; then + git clone git://github.com/yyuu/pyenv.git pyenv +else + cd pyenv + git pull + cd .. +fi + +echo 'export PYENV_ROOT="$HOME/pyenv"' > ~/pyenv/env.sh +echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/pyenv/env.sh +echo 'eval "$(pyenv init -)"' >> ~/pyenv/env.sh + +if grep -q "pyenv/env.sh" ~/.bashrc; then + # Nothing to do + true +else + echo "" >>~/.bashrc + echo "# Initialize PyEnv" >>~/.bashrc + echo "source pyenv/env.sh" >>~/.bashrc +fi + +source pyenv/env.sh + +for VER in ${VERSIONS}; do + if pyenv versions | grep -F -q "${VER}" ; then + # Version already installed + true + else + pyenv install ${VER} + fi + BASEVER="`echo ${VER} | cut -d '.' -f 1-2`" + rm -f "python/${BASEVER}" + ln -s "../pyenv/versions/${VER}" "python/${BASEVER}" +done +pyenv rehash diff --git a/tools/virtual/packer/scripts/installr-svn.sh b/tools/virtual/packer/scripts/installr-svn.sh new file mode 100644 index 0000000..8ec1993 --- /dev/null +++ b/tools/virtual/packer/scripts/installr-svn.sh @@ -0,0 +1,15 @@ +#! /bin/sh -ex + +## Quit immediately on error +set -e + +echo -n "Installing R-devel..." + +mkdir -p ~vagrant/src/ +cd ~vagrant/src/ +svn checkout https://svn.r-project.org/R/trunk/ R-devel +cd R-devel +./tools/rsync-recommended +./configure --prefix=$HOME/R/R-devel +make +make install diff --git a/tools/virtual/packer/scripts/installr.sh b/tools/virtual/packer/scripts/installr.sh new file mode 100644 index 0000000..b7760cc --- /dev/null +++ b/tools/virtual/packer/scripts/installr.sh @@ -0,0 +1,41 @@ +#! /bin/sh -ex + +# Exit if error +set -e + +echo $RVERSION + +## Check R version to build +if [ -z "$RVERSION" ]; then exit 2; fi +version=$RVERSION +majorversion=`echo $version | cut -f1 -d.` +markerfile=~vagrant/R/R-$version/DONE + +## Target directory +rdir=~vagrant/R/R-$version +mkdir -p ${rdir} + +echo -n "Installing R version $version.... " + +## Check if we have anything to do +if [ -e ${markerfile} ]; then echo "Already installed" ; exit 0; fi + +## Temporary build directory +tmp=`mktemp -d` +cd ${tmp} + +## Download, extract and build +wget http://cran.rstudio.com/src/base/R-${majorversion}/R-${version}.tar.gz +tar xzf R-${version}.tar.gz +cd R-${version} +./configure --prefix=${rdir} +make +make install + +## Clean up +cd +rm -rf ${tmp} + +## Mark this as done +echo DONE. +touch ${markerfile} diff --git a/tools/virtual/packer/scripts/jenkins.sh b/tools/virtual/packer/scripts/jenkins.sh new file mode 100644 index 0000000..c22502c --- /dev/null +++ b/tools/virtual/packer/scripts/jenkins.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +mkdir /home/vagrant/jenkins +chown vagrant:vagrant /home/vagrant/jenkins diff --git a/tools/virtual/packer/scripts/osx-vagrant.sh b/tools/virtual/packer/scripts/osx-vagrant.sh new file mode 100644 index 0000000..55b4379 --- /dev/null +++ b/tools/virtual/packer/scripts/osx-vagrant.sh @@ -0,0 +1,15 @@ +#!/bin/sh +date > /etc/vagrant_box_build_time +OSX_VERS=$(sw_vers -productVersion | awk -F "." '{print $2}') + +# Set computer/hostname +COMPNAME=vagrant-osx-10-${OSX_VERS} +scutil --set ComputerName ${COMPNAME} +scutil --set HostName ${COMPNAME}.vagrantup.com + +# Installing vagrant keys +mkdir /Users/vagrant/.ssh +chmod 700 /Users/vagrant/.ssh +curl -k 'https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub' > /Users/vagrant/.ssh/authorized_keys +chmod 600 /Users/vagrant/.ssh/authorized_keys +chown -R vagrant /Users/vagrant/.ssh diff --git a/tools/virtual/packer/scripts/otherdeb.sh b/tools/virtual/packer/scripts/otherdeb.sh new file mode 100644 index 0000000..0246557 --- /dev/null +++ b/tools/virtual/packer/scripts/otherdeb.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +apt-get -y install --no-install-recommends w3c-linkchecker diff --git a/tools/virtual/packer/scripts/rdeps.sh b/tools/virtual/packer/scripts/rdeps.sh new file mode 100644 index 0000000..cb5ab56 --- /dev/null +++ b/tools/virtual/packer/scripts/rdeps.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +apt-get -y install tcl8.5-dev tk8.5-dev libxt-dev \ + mesa-common-dev libglu1-mesa-dev texlive-base \ + texlive-latex-recommended texlive-fonts-extra texlive-latex-extra \ + texlive-fonts-recommended subversion default-jre diff --git a/tools/virtual/packer/scripts/sudo.sh b/tools/virtual/packer/scripts/sudo.sh new file mode 100644 index 0000000..6f5c44d --- /dev/null +++ b/tools/virtual/packer/scripts/sudo.sh @@ -0,0 +1,6 @@ +#!/bin/sh -x +groupadd -r admin +usermod -a -G admin vagrant +cp /etc/sudoers /etc/sudoers.orig +sed -i -e '/Defaults\s\+env_reset/a Defaults\texempt_group=admin' /etc/sudoers +sed -i -e 's/%admin ALL=(ALL) ALL/%admin ALL=NOPASSWD:ALL/g' /etc/sudoers diff --git a/tools/virtual/packer/scripts/support/kcpassword b/tools/virtual/packer/scripts/support/kcpassword new file mode 100644 index 0000000..c818c0c --- /dev/null +++ b/tools/virtual/packer/scripts/support/kcpassword @@ -0,0 +1 @@ + è5Q³Ò©êG« \ No newline at end of file diff --git a/tools/virtual/packer/scripts/vagrant.sh b/tools/virtual/packer/scripts/vagrant.sh new file mode 100644 index 0000000..609c908 --- /dev/null +++ b/tools/virtual/packer/scripts/vagrant.sh @@ -0,0 +1,7 @@ +#!/bin/sh -x +mkdir /home/vagrant/.ssh +chmod 700 /home/vagrant/.ssh +cd /home/vagrant/.ssh +wget --no-check-certificate 'https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub' -O authorized_keys +chmod 600 /home/vagrant/.ssh/authorized_keys +chown -R vagrant /home/vagrant/.ssh diff --git a/tools/virtual/packer/scripts/vbox.sh b/tools/virtual/packer/scripts/vbox.sh new file mode 100644 index 0000000..fbfda5b --- /dev/null +++ b/tools/virtual/packer/scripts/vbox.sh @@ -0,0 +1,14 @@ +#!/bin/sh -x +# Without libdbus virtualbox would not start automatically after compile +apt-get -y install --no-install-recommends libdbus-1-3 +aptitude -y install dkms + +# Install the VirtualBox guest additions +VBOX_VERSION=$(cat /home/vagrant/.vbox_version) +VBOX_ISO=VBoxGuestAdditions_$VBOX_VERSION.iso +mount -o loop $VBOX_ISO /mnt +yes|sh /mnt/VBoxLinuxAdditions.run +umount /mnt + +# Cleanup +rm $VBOX_ISO diff --git a/tools/virtual/packer/scripts/vmware.sh b/tools/virtual/packer/scripts/vmware.sh new file mode 100644 index 0000000..42e84ba --- /dev/null +++ b/tools/virtual/packer/scripts/vmware.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# VMware Fusion specific items +if [ -e .vmfusion_version ] || [ "$PACKER_BUILDER_TYPE" = "vmware" ]; then + TMPMOUNT=`/usr/bin/mktemp -d /tmp/vmware-tools.XXXX` + hdiutil attach darwin.iso -mountpoint "$TMPMOUNT" + installer -pkg "$TMPMOUNT/Install VMware Tools.app/Contents/Resources/VMware Tools.pkg" -target / + # This usually fails + hdiutil detach "$TMPMOUNT" + rm -rf "$TMPMOUNT" + rm -f darwin.iso + + # Point Linux shared folder root to that used by OS X guests, + # useful for the Hashicorp vmware_fusion Vagrant provider plugin + mkdir /mnt + ln -sf /Volumes/VMware\ Shared\ Folders /mnt/hgfs +fi diff --git a/tools/virtual/packer/scripts/win-change-home-dirs.sh b/tools/virtual/packer/scripts/win-change-home-dirs.sh new file mode 100644 index 0000000..5eb927a --- /dev/null +++ b/tools/virtual/packer/scripts/win-change-home-dirs.sh @@ -0,0 +1,3 @@ +set -x + +mkpasswd -l -p "$(cygpath $(cygpath -dH))" > /etc/passwd diff --git a/tools/virtual/packer/scripts/win-postinstall64.sh b/tools/virtual/packer/scripts/win-postinstall64.sh new file mode 100644 index 0000000..1fdb42b --- /dev/null +++ b/tools/virtual/packer/scripts/win-postinstall64.sh @@ -0,0 +1,48 @@ +#set -x + +VAGRANT_HOME=/cygdrive/c/Users/vagrant +# Install ssh certificates +mkdir $VAGRANT_HOME/.ssh +chmod 700 $VAGRANT_HOME/.ssh +cd $VAGRANT_HOME/.ssh +wget --no-check-certificate 'https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub' -O authorized_keys +chown -R vagrant $VAGRANT_HOME/.ssh +cd .. + +cd $VAGRANT_HOME + +ZIP_INSTALL=7z922-x64.msi +VBOX_INSTALL=VBoxWindowsAdditions-amd64.exe +VMWARE_INSTALL=setup64.exe + +# 7zip will allow us to extract a file from an ISO +wget http://downloads.sourceforge.net/sevenzip/$ZIP_INSTALL +msiexec /qb /i $ZIP_INSTALL + +if [ -f VBoxGuestAdditions.iso ]; then + # Extract the installer from the ISO + /cygdrive/c/Program\ Files/7-Zip/7z.exe x VBoxGuestAdditions.iso $VBOX_INSTALL + + # Mark Oracle as a trusted installer + certutil -addstore -f "TrustedPublisher" $(cygpath -d /cygdrive/a/oracle-cert.cer) + + # Install the Virtualbox Additions + ./$VBOX_INSTALL /S + + # Cleanup + rm -f VBoxGuestAdditions.iso + rm -f $VBOX_INSTALL.exe +elif [ -f windows.iso ]; then + # Extract the installer from the ISO + /cygdrive/c/Program\ Files/7-Zip/7z.exe x windows.iso $VMWARE_INSTALL + + # Install VMware tools + ./$VMWARE_INSTALL /S /v "/qn REBOOT=R ADDLOCAL=ALL" || true + + # Cleanup + rm -f windows.iso + rm -f $VMWARE_INSTALL +fi + +# Cleanup +rm -f $ZIP_INSTALL diff --git a/tools/virtual/packer/scripts/xcode-cli-tools.sh b/tools/virtual/packer/scripts/xcode-cli-tools.sh new file mode 100644 index 0000000..6a46b57 --- /dev/null +++ b/tools/virtual/packer/scripts/xcode-cli-tools.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +# Get and install Xcode CLI tools +OSX_VERS=$(sw_vers -productVersion | awk -F "." '{print $2}') + +# on 10.9, we can leverage SUS to get the latest CLI tools +if [ "$OSX_VERS" -ge 9 ]; then + + # create the placeholder file that's checked by CLI updates' .dist code + # in Apple's SUS catalog + touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress + + # find the update with "Developer" in the name + PROD=$(softwareupdate -l | grep -B 1 "Developer" | head -n 1 | awk -F"*" '{print $2}') + + # install it + # amazingly, it won't find the update if we put the update ID in double-quotes + softwareupdate -i $PROD -v + +# on 10.7/10.8, we instead download from public download URLs, which can be found in +# the dvtdownloadableindex: +# https://devimages.apple.com.edgekey.net/downloads/xcode/simulators/index-3905972D-B609-49CE-8D06-51ADC78E07BC.dvtdownloadableindex +else + [ "$OSX_VERS" -eq 7 ] && DMGURL=http://devimages.apple.com/downloads/xcode/command_line_tools_for_xcode_os_x_lion_april_2013.dmg + [ "$OSX_VERS" -eq 8 ] && DMGURL=http://devimages.apple.com/downloads/xcode/command_line_tools_os_x_mountain_lion_for_xcode_october_2013.dmg + + TOOLS=clitools.dmg + curl "$DMGURL" -o "$TOOLS" + TMPMOUNT=`/usr/bin/mktemp -d /tmp/clitools.XXXX` + hdiutil attach "$TOOLS" -mountpoint "$TMPMOUNT" + installer -pkg "$(find $TMPMOUNT -name '*.mpkg')" -target / + hdiutil detach "$TMPMOUNT" + rm -rf "$TMPMOUNT" + rm "$TOOLS" + exit +fi diff --git a/tools/virtual/packer/ubuntu-13.10-32/template.json b/tools/virtual/packer/ubuntu-13.10-32/template.json new file mode 100644 index 0000000..8bf7a96 --- /dev/null +++ b/tools/virtual/packer/ubuntu-13.10-32/template.json @@ -0,0 +1,94 @@ +{ + "builders": [ + { + "type": "virtualbox", + "name": "ubuntu-13.10-32", + "boot_command": [ + "<esc><esc><enter><wait>", + "/install/vmlinuz noapic preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg <wait>", + "debian-installer=en_US auto locale=en_US kbd-chooser/method=us <wait>", + "hostname={{ .Name }} <wait>", + "fb=false debconf/frontend=noninteractive <wait>", + "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=us keyboard-configuration/variant=us console-setup/ask_detect=false <wait>", + "initrd=/install/initrd.gz -- <enter><wait>" + ], + "boot_wait": "4s", + "disk_size": 65536, + "guest_os_type": "Ubuntu", + "http_directory": "../http", + "iso_checksum": "77043904185d7efa0966b1c2c153805b", + "iso_checksum_type": "md5", + "iso_url": "http://releases.ubuntu.com/13.10/ubuntu-13.10-server-i386.iso", + "ssh_username": "vagrant", + "ssh_password": "vagrant", + "ssh_port": 22, + "ssh_wait_timeout": "10000s", + "shutdown_command": "echo 'shutdown -P now' > shutdown.sh; echo 'vagrant'|sudo -S sh 'shutdown.sh'", + "guest_additions_path": "VBoxGuestAdditions_{{.Version}}.iso", + "virtualbox_version_file": ".vbox_version", + "vboxmanage": [ + ["modifyvm", "{{.Name}}", "--memory", "4096"], + ["modifyvm", "{{.Name}}", "--cpus", "1"], + ["modifyvm", "{{.Name}}", "--nictype1", "virtio"], + ["modifyvm", "{{.Name}}", "--natpf1", "guestssh,tcp,,3331,,22"] + ], + "headless": true, + "format": "ova" + } + ], + "provisioners": [ + { + "type": "shell", + "scripts": [ + "../scripts/build_time.sh", + "../scripts/git.sh", + "../scripts/apt.sh", + "../scripts/vbox.sh", + "../scripts/sudo.sh", + "../scripts/vagrant.sh", + "../scripts/base.sh", + "../scripts/compilers.sh", + "../scripts/git.sh", + "../scripts/igraphdeps.sh", + "../scripts/rdeps.sh", + "../scripts/jenkins.sh" + ], + "override": { + "ubuntu-13.10-32": { + "execute_command": "echo 'vagrant'|sudo -S sh '{{.Path}}'" + } + } + }, + { + "type": "shell", + "script": "../scripts/installr.sh", + "environment_vars": [ + "RVERSION=2.15.3" + ] + }, + { + "type": "shell", + "script": "../scripts/installr.sh", + "environment_vars": [ + "RVERSION=3.0.2" + ] + }, + { + "type": "shell", + "script": "../scripts/installr-svn.sh" + }, + { + "type": "shell", + "script": "../scripts/install-python.sh" + }, + { + "type": "shell", + "script": "../scripts/cleanup.sh", + "override": { + "ubuntu-13.10-32": { + "execute_command": "echo 'vagrant'|sudo -S sh '{{.Path}}'" + } + } + } + ] +} diff --git a/tools/virtual/packer/ubuntu-13.10-64/template.json b/tools/virtual/packer/ubuntu-13.10-64/template.json new file mode 100644 index 0000000..33a952c --- /dev/null +++ b/tools/virtual/packer/ubuntu-13.10-64/template.json @@ -0,0 +1,95 @@ +{ + "builders": [ + { + "type": "virtualbox", + "name": "ubuntu-13.10-64", + "boot_command": [ + "<esc><esc><enter><wait>", + "/install/vmlinuz noapic preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg <wait>", + "debian-installer=en_US auto locale=en_US kbd-chooser/method=us <wait>", + "hostname={{ .Name }} <wait>", + "fb=false debconf/frontend=noninteractive <wait>", + "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=us keyboard-configuration/variant=us console-setup/ask_detect=false <wait>", + "initrd=/install/initrd.gz -- <enter><wait>" + ], + "boot_wait": "4s", + "disk_size": 65536, + "guest_os_type": "Ubuntu_64", + "http_directory": "../http", + "iso_checksum": "4d1a8b720cdd14b76ed9410c63a00d0e", + "iso_checksum_type": "md5", + "iso_url": "http://releases.ubuntu.com/13.10/ubuntu-13.10-server-amd64.iso", + "ssh_username": "vagrant", + "ssh_password": "vagrant", + "ssh_port": 22, + "ssh_wait_timeout": "10000s", + "shutdown_command": "echo 'shutdown -P now' > shutdown.sh; echo 'vagrant'|sudo -S sh 'shutdown.sh'", + "guest_additions_path": "VBoxGuestAdditions_{{.Version}}.iso", + "virtualbox_version_file": ".vbox_version", + "vboxmanage": [ + ["modifyvm", "{{.Name}}", "--memory", "4096"], + ["modifyvm", "{{.Name}}", "--cpus", "1"], + ["modifyvm", "{{.Name}}", "--nictype1", "virtio"], + ["modifyvm", "{{.Name}}", "--natpf1", "guestssh,tcp,,3332,,22"] + ], + "headless": true, + "format": "ova" + } + ], + "provisioners": [ + { + "type": "shell", + "scripts": [ + "../scripts/build_time.sh", + "../scripts/git.sh", + "../scripts/apt.sh", + "../scripts/vbox.sh", + "../scripts/sudo.sh", + "../scripts/vagrant.sh", + "../scripts/base.sh", + "../scripts/compilers.sh", + "../scripts/git.sh", + "../scripts/igraphdeps.sh", + "../scripts/rdeps.sh", + "../scripts/otherdeb.sh", + "../scripts/jenkins.sh" + ], + "override": { + "ubuntu-13.10-64": { + "execute_command": "echo 'vagrant'|sudo -S sh '{{.Path}}'" + } + } + }, + { + "type": "shell", + "script": "../scripts/installr.sh", + "environment_vars": [ + "RVERSION=2.15.3" + ] + }, + { + "type": "shell", + "script": "../scripts/installr.sh", + "environment_vars": [ + "RVERSION=3.0.2" + ] + }, + { + "type": "shell", + "script": "../scripts/installr-svn.sh" + }, + { + "type": "shell", + "script": "../scripts/install-python.sh" + }, + { + "type": "shell", + "script": "../scripts/cleanup.sh", + "override": { + "ubuntu-13.10-64": { + "execute_command": "echo 'vagrant'|sudo -S sh '{{.Path}}'" + } + } + } + ] +} diff --git a/tools/virtual/packer/windows7/template.json b/tools/virtual/packer/windows7/template.json new file mode 100644 index 0000000..68ad7ca --- /dev/null +++ b/tools/virtual/packer/windows7/template.json @@ -0,0 +1,35 @@ +{ + "builders": [ + { + "vm_name": "win7x64-enterprise", + "type": "virtualbox", + "guest_os_type": "Windows7_64", + "iso_url": "file:///Users/csardi/ISO/7600.16385.090713-1255_x64fre_enterprise_en-us_EVAL_Eval_Enterprise-GRMCENXEVAL_EN_DVD.iso", + "iso_checksum": "1d0d239a252cb53e466d39e752b17c28", + "iso_checksum_type": "md5", + "guest_additions_path": "/cygdrive/c/Users/vagrant/VBoxGuestAdditions.iso", + "ssh_username": "vagrant", + "ssh_password": "vagrant", + "ssh_wait_timeout": "10000s", + "disk_size": 40960, + "floppy_files": ["../floppy/Autounattend.xml", + "../floppy/set-power-config.bat", + "../floppy/install-cygwin-sshd.bat", + "../floppy/oracle-cert.cer"], + "shutdown_command": "shutdown /s /t 10 /f /d p:4:1 /c \"Packer Shutdown\"", + "vboxmanage": [ + ["modifyvm", "{{.Name}}", "--memory", "2048"], + ["modifyvm", "{{.Name}}", "--cpus", "1"], + ["modifyvm", "{{.Name}}", "--natpf1", "guestssh,tcp,,3333,,22"] + ], + "headless": true + } + ], + "provisioners": [{ + "type": "shell", + "scripts": [ + "../scripts/win-change-home-dirs.sh", + "../scripts/win-postinstall64.sh" + ] + }] +} diff --git a/tools/virtual/vagrant/agents/org.igraph.tekton.build-c-0.5-main.plist b/tools/virtual/vagrant/agents/org.igraph.tekton.build-c-0.5-main.plist new file mode 100644 index 0000000..f235cd0 --- /dev/null +++ b/tools/virtual/vagrant/agents/org.igraph.tekton.build-c-0.5-main.plist @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN + http://www.apple.com/DTDs/PropertyList-1.0.dtd > +<plist version="1.0"> + <dict> + <key>Label</key> + <string>org.igraph.tekton.build-c-0.5-main</string> + <key>WorkingDirectory</key> + <string>$PWD/ubuntu-13.04-x86_64</string> + <key>ProgramArguments</key> + <array> + <string>../run-script.sh</string> + <string>build-c.sh</string> + <string>0.5-main</string> + </array> + <key>StartCalendarInterval</key> + <dict> + <key>Hour</key> + <integer>0</integer> + <key>Minute</key> + <integer>10</integer> + </dict> + <key>StandardOutPath</key> + <string>output/build-c-develop.txt</string> + <key>StandardErrorPath</key> + <string>error/build-c-develop.txt</string> + <key>KeepAlive</key> + <false/> + </dict> +</plist> diff --git a/tools/virtual/vagrant/agents/org.igraph.tekton.build-c-develop.plist b/tools/virtual/vagrant/agents/org.igraph.tekton.build-c-develop.plist new file mode 100644 index 0000000..0005f58 --- /dev/null +++ b/tools/virtual/vagrant/agents/org.igraph.tekton.build-c-develop.plist @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN + http://www.apple.com/DTDs/PropertyList-1.0.dtd > +<plist version="1.0"> + <dict> + <key>Label</key> + <string>org.igraph.tekton.build-c-develop</string> + <key>WorkingDirectory</key> + <string>$PWD/ubuntu-13.04-x86_64</string> + <key>ProgramArguments</key> + <array> + <string>../run-script.sh</string> + <string>build-c.sh</string> + <string>develop</string> + </array> + <key>StartCalendarInterval</key> + <dict> + <key>Hour</key> + <integer>0</integer> + <key>Minute</key> + <integer>0</integer> + </dict> + <key>StandardOutPath</key> + <string>output/build-c-develop.txt</string> + <key>StandardErrorPath</key> + <string>error/build-c-develop.txt</string> + <key>KeepAlive</key> + <false/> + </dict> +</plist> diff --git a/tools/virtual/vagrant/agents/org.igraph.tekton.build-r-0.5-main.plist b/tools/virtual/vagrant/agents/org.igraph.tekton.build-r-0.5-main.plist new file mode 100644 index 0000000..4e196e1 --- /dev/null +++ b/tools/virtual/vagrant/agents/org.igraph.tekton.build-r-0.5-main.plist @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN + http://www.apple.com/DTDs/PropertyList-1.0.dtd > +<plist version="1.0"> + <dict> + <key>Label</key> + <string>org.igraph.tekton.build-r-0.5-main</string> + <key>WorkingDirectory</key> + <string>$PWD/ubuntu-13.04-x86_64</string> + <key>ProgramArguments</key> + <array> + <string>../run-script.sh</string> + <string>build-r.sh</string> + <string>0.5-main</string> + </array> + <key>StartCalendarInterval</key> + <dict> + <key>Hour</key> + <integer>0</integer> + <key>Minute</key> + <integer>30</integer> + </dict> + <key>StandardOutPath</key> + <string>output/build-r-0.5-main.txt</string> + <key>StandardErrorPath</key> + <string>error/build-r-0.5-main.txt</string> + <key>KeepAlive</key> + <false/> + </dict> +</plist> diff --git a/tools/virtual/vagrant/agents/org.igraph.tekton.build-r-0.7-graphlets.plist b/tools/virtual/vagrant/agents/org.igraph.tekton.build-r-0.7-graphlets.plist new file mode 100644 index 0000000..9557504 --- /dev/null +++ b/tools/virtual/vagrant/agents/org.igraph.tekton.build-r-0.7-graphlets.plist @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN + http://www.apple.com/DTDs/PropertyList-1.0.dtd > +<plist version="1.0"> + <dict> + <key>Label</key> + <string>org.igraph.tekton.build-r-0.7-graphlets</string> + <key>WorkingDirectory</key> + <string>$PWD/ubuntu-13.04-x86_64</string> + <key>ProgramArguments</key> + <array> + <string>../run-script.sh</string> + <string>build-r.sh</string> + <string>0.7-graphlets</string> + </array> + <key>StartCalendarInterval</key> + <dict> + <key>Hour</key> + <integer>0</integer> + <key>Minute</key> + <integer>40</integer> + </dict> + <key>StandardOutPath</key> + <string>output/build-r-0.7-graphlets.txt</string> + <key>StandardErrorPath</key> + <string>error/build-r-0.7-graphlets.txt</string> + <key>KeepAlive</key> + <false/> + </dict> +</plist> diff --git a/tools/virtual/vagrant/agents/org.igraph.tekton.build-r-develop.plist b/tools/virtual/vagrant/agents/org.igraph.tekton.build-r-develop.plist new file mode 100644 index 0000000..cceddd9 --- /dev/null +++ b/tools/virtual/vagrant/agents/org.igraph.tekton.build-r-develop.plist @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN + http://www.apple.com/DTDs/PropertyList-1.0.dtd > +<plist version="1.0"> + <dict> + <key>Label</key> + <string>org.igraph.tekton.build-r-develop</string> + <key>WorkingDirectory</key> + <string>$PWD/ubuntu-13.04-x86_64</string> + <key>ProgramArguments</key> + <array> + <string>../run-script.sh</string> + <string>build-r.sh</string> + <string>develop</string> + </array> + <key>StartCalendarInterval</key> + <dict> + <key>Hour</key> + <integer>0</integer> + <key>Minute</key> + <integer>20</integer> + </dict> + <key>StandardOutPath</key> + <string>output/build-r-develop.txt</string> + <key>StandardErrorPath</key> + <string>error/build-r-develop.txt</string> + <key>KeepAlive</key> + <false/> + </dict> +</plist> diff --git a/tools/virtual/vagrant/agents/org.igraph.tekton.check-r-0.5-main.plist b/tools/virtual/vagrant/agents/org.igraph.tekton.check-r-0.5-main.plist new file mode 100644 index 0000000..ccfb3a0 --- /dev/null +++ b/tools/virtual/vagrant/agents/org.igraph.tekton.check-r-0.5-main.plist @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN + http://www.apple.com/DTDs/PropertyList-1.0.dtd > +<plist version="1.0"> + <dict> + <key>Label</key> + <string>org.igraph.tekton.check-r-0.5-main</string> + <key>WorkingDirectory</key> + <string>$PWD/ubuntu-13.04-x86_64</string> + <key>ProgramArguments</key> + <array> + <string>../run-script.sh</string> + <string>check-r.sh</string> + <string>0.5-main</string> + <string>~vagrant/R/R-3.0.1/bin/R</string> + <string>R-3.0.1</string> + </array> + <key>StartCalendarInterval</key> + <dict> + <key>Hour</key> + <integer>1</integer> + <key>Minute</key> + <integer>10</integer> + </dict> + <key>StandardOutPath</key> + <string>output/check-r-0.5-main.txt</string> + <key>StandardErrorPath</key> + <string>error/check-r-0.5-main.txt</string> + <key>KeepAlive</key> + <false/> + </dict> +</plist> diff --git a/tools/virtual/vagrant/agents/org.igraph.tekton.check-r-develop.plist b/tools/virtual/vagrant/agents/org.igraph.tekton.check-r-develop.plist new file mode 100644 index 0000000..ef8c7a4 --- /dev/null +++ b/tools/virtual/vagrant/agents/org.igraph.tekton.check-r-develop.plist @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN + http://www.apple.com/DTDs/PropertyList-1.0.dtd > +<plist version="1.0"> + <dict> + <key>Label</key> + <string>org.igraph.tekton.check-r-develop</string> + <key>WorkingDirectory</key> + <string>$PWD/ubuntu-13.04-x86_64</string> + <key>ProgramArguments</key> + <array> + <string>../run-script.sh</string> + <string>check-r.sh</string> + <string>develop</string> + <string>~vagrant/R/R-3.0.1/bin/R</string> + <string>R-3.0.1</string> + </array> + <key>StartCalendarInterval</key> + <dict> + <key>Hour</key> + <integer>1</integer> + <key>Minute</key> + <integer>00</integer> + </dict> + <key>StandardOutPath</key> + <string>output/check-r-develop.txt</string> + <key>StandardErrorPath</key> + <string>error/check-r-develop.txt</string> + <key>KeepAlive</key> + <false/> + </dict> +</plist> diff --git a/tools/virtual/vagrant/agents/org.igraph.tekton.check-rdevel-develop.plist b/tools/virtual/vagrant/agents/org.igraph.tekton.check-rdevel-develop.plist new file mode 100644 index 0000000..44767e5 --- /dev/null +++ b/tools/virtual/vagrant/agents/org.igraph.tekton.check-rdevel-develop.plist @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN + http://www.apple.com/DTDs/PropertyList-1.0.dtd > +<plist version="1.0"> + <dict> + <key>Label</key> + <string>org.igraph.tekton.check-rdevel-develop</string> + <key>WorkingDirectory</key> + <string>$PWD/ubuntu-13.04-x86_64</string> + <key>ProgramArguments</key> + <array> + <string>../run-script.sh</string> + <string>check-r.sh</string> + <string>develop</string> + <string>~vagrant/R/R-devel/bin/R</string> + <string>R-devel</string> + </array> + <key>StartCalendarInterval</key> + <dict> + <key>Hour</key> + <integer>1</integer> + <key>Minute</key> + <integer>20</integer> + </dict> + <key>StandardOutPath</key> + <string>output/check-rdevel-develop.txt</string> + <key>StandardErrorPath</key> + <string>error/check-rdevel-develop.txt</string> + <key>KeepAlive</key> + <false/> + </dict> +</plist> diff --git a/tools/virtual/vagrant/agents/org.igraph.tekton.update-r-svn.plist b/tools/virtual/vagrant/agents/org.igraph.tekton.update-r-svn.plist new file mode 100644 index 0000000..1455501 --- /dev/null +++ b/tools/virtual/vagrant/agents/org.igraph.tekton.update-r-svn.plist @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN + http://www.apple.com/DTDs/PropertyList-1.0.dtd > +<plist version="1.0"> + <dict> + <key>Label</key> + <string>org.igraph.tekton.update-r-svn</string> + <key>WorkingDirectory</key> + <string>$PWD/ubuntu-13.04-x86_64</string> + <key>ProgramArguments</key> + <array> + <string>../run-script.sh</string> + <string>update-r-svn.sh</string> + </array> + <key>StartCalendarInterval</key> + <dict> + <key>Hour</key> + <integer>23</integer> + <key>Minute</key> + <integer>00</integer> + </dict> + <key>StandardOutPath</key> + <string>output/update-r-svn.txt</string> + <key>StandardErrorPath</key> + <string>error/update-r-svn.txt</string> + <key>KeepAlive</key> + <false/> + </dict> +</plist> diff --git a/tools/virtual/vagrant/load-all.sh b/tools/virtual/vagrant/load-all.sh new file mode 100755 index 0000000..33d7b6c --- /dev/null +++ b/tools/virtual/vagrant/load-all.sh @@ -0,0 +1,17 @@ +#! /bin/sh + +agentdir=`dirname $0`/agents +agents=`ls $agentdir/*.plist` +tmpdir=`mktemp -d -t tekton` +mywd=$(pwd | sed -e 's/[\/&]/\\&/g') +trap "rm -rf $tmpdir" EXIT + +for agent in $agents; do + agfile=$tmpdir/`basename $agent` + basename $agent + cat "$agent" | sed 's/\$PWD/'$mywd'/g' >> "$agfile" + launchctl load "$agfile" +done + +rm -rf $tmpdir + diff --git a/tools/virtual/vagrant/provisioners/apt-get-install.sh b/tools/virtual/vagrant/provisioners/apt-get-install.sh new file mode 100755 index 0000000..1d78d0e --- /dev/null +++ b/tools/virtual/vagrant/provisioners/apt-get-install.sh @@ -0,0 +1,4 @@ +#! /bin/sh + +apt-get -y install $@ +apt-get clean diff --git a/tools/virtual/vagrant/provisioners/apt-update.sh b/tools/virtual/vagrant/provisioners/apt-update.sh new file mode 100755 index 0000000..0094ff3 --- /dev/null +++ b/tools/virtual/vagrant/provisioners/apt-update.sh @@ -0,0 +1,5 @@ +#! /bin/sh + +apt-get update +apt-get -y autoremove +apt-get clean diff --git a/tools/virtual/vagrant/provisioners/compilers.sh b/tools/virtual/vagrant/provisioners/compilers.sh new file mode 100755 index 0000000..fe69038 --- /dev/null +++ b/tools/virtual/vagrant/provisioners/compilers.sh @@ -0,0 +1,5 @@ +#! /bin/sh + +apt-get -y install gcc-4.7 g++-4.7 gfortran-4.7 +apt-get -y install clang-3.2 +apt-get clean diff --git a/tools/virtual/vagrant/provisioners/createdir.sh b/tools/virtual/vagrant/provisioners/createdir.sh new file mode 100755 index 0000000..8871d1d --- /dev/null +++ b/tools/virtual/vagrant/provisioners/createdir.sh @@ -0,0 +1,7 @@ +#! /bin/sh + +su vagrant <<EOF + +mkdir -p $@ + +EOF diff --git a/tools/virtual/vagrant/provisioners/igraphdeps.sh b/tools/virtual/vagrant/provisioners/igraphdeps.sh new file mode 100755 index 0000000..35e9ed4 --- /dev/null +++ b/tools/virtual/vagrant/provisioners/igraphdeps.sh @@ -0,0 +1,9 @@ +#! /bin/bash + +apt-get -y install git autoconf automake bison flex libtool \ + libxml2-dev libgmp-dev docbook2x source-highlight libxml2-utils \ + r-base-core tcl8.5-dev tk8.5-dev \ + mesa-common-dev libglu1-mesa-dev texlive-base \ + texlive-latex-recommended texlive-fonts-extra texlive-latex-extra \ + texlive-fonts-recommended +apt-get clean diff --git a/tools/virtual/vagrant/provisioners/install-service.sh b/tools/virtual/vagrant/provisioners/install-service.sh new file mode 100755 index 0000000..83e7e20 --- /dev/null +++ b/tools/virtual/vagrant/provisioners/install-service.sh @@ -0,0 +1,22 @@ +#! /bin/bash + +## Quit immediately on error +set -e + +## Want to run this as 'vagrant', so rerun if root +if [ "$(id -u)" = "0" ]; then + sudo -u vagrant bash $0 $@ + exit 0 +fi + +scriptdir="/tekton" + +## Check arguments, we need two of them, +## first specifies the script to run, the second +## is the crontab specification for when to run it. +if [ ! "x"$# = "x2" ]; then exit 2; fi +script=$1 +if [ ! -e ${scriptdir}/${script} ]; then exit 3; fi + + + diff --git a/tools/virtual/vagrant/provisioners/installR-asan.sh b/tools/virtual/vagrant/provisioners/installR-asan.sh new file mode 100755 index 0000000..3490e3f --- /dev/null +++ b/tools/virtual/vagrant/provisioners/installR-asan.sh @@ -0,0 +1,47 @@ +#! /bin/sh + +## Quit immediately on error +set -e + +## Want to run this as 'vagrant', so rerun if root +if [ "$(id -u)" = "0" ]; then + sudo -u vagrant bash $0 $@ + exit 0 +fi + +## Check arguments +if [ ! "x"$# = "x1" ]; then exit 2; fi +version=$@ +majorversion=`echo $version | cut -f1 -d.` +markerfile=~vagrant/vagrant/provisions/installR-asan-${version} + +echo -n "Installing R version $version (with asan) .... " + +## Check if we have anything to do +if [ -e ${markerfile} ]; then echo "Already installed" ; exit 0; fi + +## Target directory +rdir=~vagrant/R/R-$version-asan +mkdir -p ${rdir} + +## Temporary build directory +tmp=`mktemp -d` +trap "rm -rf ${tmp}" EXIT +cd ${tmp} + +## Download, extract and build +wget http://cran.rstudio.com/src/base/R-${majorversion}/R-${version}.tar.gz +tar xzf R-${version}.tar.gz +cd R-${version} +CC=clang CXX=clang++ MAIN_LD="clang -fsanitize=address" \ + ./configure --prefix=$rdir +make +make install + +## Clean up +cd +rm -rf ${tmp} + +## Mark this as done +echo DONE. +touch ${markerfile} diff --git a/tools/virtual/vagrant/provisioners/installR-svn.sh b/tools/virtual/vagrant/provisioners/installR-svn.sh new file mode 100755 index 0000000..ac18352 --- /dev/null +++ b/tools/virtual/vagrant/provisioners/installR-svn.sh @@ -0,0 +1,25 @@ +#! /bin/sh + +## Quit immediately on error +set -e + +## Want to run this as 'vagrant', so rerun if root +if [ "$(id -u)" = "0" ]; then + sudo -u vagrant bash $0 $@ + exit 0 +fi + +echo -n "Installing R-devel..." + +if [ -e ~vagrant/src/R-devel ]; then echo "already installed" ; exit 0 ; fi + +mkdir -p ~vagrant/src/ +cd ~vagrant/src/ +svn checkout https://svn.r-project.org/R/trunk/ R-devel +cd R-devel +./tools/rsync-recommended +./configure --prefix=$HOME/R/R-devel +make +make install + + diff --git a/tools/virtual/vagrant/provisioners/installR.sh b/tools/virtual/vagrant/provisioners/installR.sh new file mode 100755 index 0000000..edff6e0 --- /dev/null +++ b/tools/virtual/vagrant/provisioners/installR.sh @@ -0,0 +1,45 @@ +#! /bin/sh + +## Quit immediately on error +set -e + +## Want to run this as 'vagrant', so rerun if root +if [ "$(id -u)" = "0" ]; then + sudo -u vagrant bash $0 $@ + exit 0 +fi + +## Check arguments +if [ ! "x"$# = "x1" ]; then exit 2; fi +version=$@ +majorversion=`echo $version | cut -f1 -d.` +markerfile=~vagrant/vagrant/provisions/installR-${version} + +echo -n "Installing R version $version.... " + +## Check if we have anything to do +if [ -e ${markerfile} ]; then echo "Already installed" ; exit 0; fi + +## Target directory +rdir=~vagrant/R/R-$version +mkdir -p ${rdir} + +## Temporary build directory +tmp=`mktemp -d` +cd ${tmp} + +## Download, extract and build +wget http://cran.rstudio.com/src/base/R-${majorversion}/R-${version}.tar.gz +tar xzf R-${version}.tar.gz +cd R-${version} +./configure --prefix=${rdir} +make +make install + +## Clean up +cd +rm -rf ${tmp} + +## Mark this as done +echo DONE. +touch ${markerfile} diff --git a/tools/virtual/vagrant/provisioners/sshkey.sh b/tools/virtual/vagrant/provisioners/sshkey.sh new file mode 100755 index 0000000..0a5f673 --- /dev/null +++ b/tools/virtual/vagrant/provisioners/sshkey.sh @@ -0,0 +1,16 @@ +#! /bin/sh + +#! /bin/sh + +## Quit immediately on error +set -e + +## Want to run this as 'vagrant', so rerun if root +if [ "$(id -u)" = "0" ]; then + sudo -u vagrant bash $0 $@ + exit 0 +fi + +mkdir -p ~/.ssh +cp /tekton/key/id_rsa ~/.ssh/id_rsa +cp /tekton/key/known_hosts ~/.ssh/ diff --git a/tools/virtual/vagrant/run-script.sh b/tools/virtual/vagrant/run-script.sh new file mode 100755 index 0000000..877d955 --- /dev/null +++ b/tools/virtual/vagrant/run-script.sh @@ -0,0 +1,26 @@ +#! /bin/sh + +## Quit immediately on error +set -e + +## Check arguments, at least the script to run is needed. +## Additional arguments will be passed to the script +if [ $# -lt 1 ]; then + echo "Error: not enough arguments, need script to run at least" + exit 1 +fi +script=$1 +shift + +if [ ! -f "../scripts/$script" ]; then + echo "Script '$script' does not exist" + exit 3 +fi + +if [ ! -x "../scripts/$script" ]; then + echo "Script '$script' is not executable" + exit 4 +fi + +vagrant up +vagrant ssh -- /tekton/$script $@ diff --git a/tools/virtual/vagrant/scripts/build-c.sh b/tools/virtual/vagrant/scripts/build-c.sh new file mode 100755 index 0000000..ac50291 --- /dev/null +++ b/tools/virtual/vagrant/scripts/build-c.sh @@ -0,0 +1,44 @@ +# /bin/sh + +## Quit immediately on error +set -e + +## Build the C library from a github branch. +## We assume that all the build tools, and the +## igraph dependencies are already installed. + +## If not specified, we build the master branch +branch=${1:-master} + +## We freshly clone the repo from github and build igraph from scratch. +builddir=`mktemp -d` +trap "rm -rf $builddir" EXIT +cd $builddir + +git clone -b $branch https://github.com/igraph/igraph.git +cd igraph +./bootstrap.sh +./configure +make +cd tests ; make testsuite ; cd .. +if [ "$branch" = "0.5-main" ]; then +## Need the info file for the 0.5 tree + cd doc ; make info ; cd .. +fi +make dist + +## Canonical filename +version=`grep " VERSION " config.h | cut -f3 -d" " | tr -d '"'` +commit=`git rev-parse --short HEAD` +filename=igraph-${version}-${branch}-$commit.tar.gz +mv igraph-$version.tar.gz $filename + +## Upload file to igraph.org +eval `ssh-agent -s` +trap "kill $SSH_AGENT_PID" EXIT +ssh-add +scp -P 2222 ${filename} csardi@igraph.org:www/nightly/files/c/ + +## Clean up +rm -rf $builddir +kill $SSH_AGENT_PID diff --git a/tools/virtual/vagrant/scripts/build-r.sh b/tools/virtual/vagrant/scripts/build-r.sh new file mode 100755 index 0000000..ddf25ba --- /dev/null +++ b/tools/virtual/vagrant/scripts/build-r.sh @@ -0,0 +1,49 @@ +#! /bin/sh + +## Quit immediately an error +set -e + +## Build the R package from a github branch. +## We assume that all the build tools, and the +## igraph dependencies are already installed, +## but the R packages we depend on are not. + +## If not specified, we build the master branch +branch=${1-master} + +## If not specified, we use the system R version +R=${2-R} + +## We freshly clone the repo from github and build igraph from scratch. +builddir=`mktemp -d` +trap "rm -rf $builddir" EXIT +cd $builddir + +git clone -b $branch https://github.com/igraph/igraph.git +cd igraph +./bootstrap.sh +./configure +./bootstrap.sh +make parsersources + +version=`grep " VERSION " config.h | cut -f3 -d" " | tr -d '"'` +commit=`git rev-parse --short HEAD` + +cd interfaces/R +make + +## Canonical filename +package=`cat igraph/DESCRIPTION | grep ^Package: | cut -f2 -d" "` +filename=${package}_${version}-${branch}-$commit.tar.gz +mv ${package}_${version}.tar.gz $filename + +## Upload file to igraph.org +eval `ssh-agent -s` +trap "kill $SSH_AGENT_PID" EXIT +ssh-add +scp -P 2222 ${filename} csardi@igraph.org:www/nightly/files/r/ + +## Clean up +rm -rf $builddir +kill $SSH_AGENT_PID + diff --git a/tools/virtual/vagrant/scripts/check-r-asan.sh b/tools/virtual/vagrant/scripts/check-r-asan.sh new file mode 100755 index 0000000..82fd501 --- /dev/null +++ b/tools/virtual/vagrant/scripts/check-r-asan.sh @@ -0,0 +1,67 @@ +#! /bin/sh + +## If not specified, we build the master branch +branch=${1-master} + +## R version with AddressSanitizer support +R=~vagrant/R/R-3.0.1-asan/bin/R + +## We freshly clone the repo from github and build igraph from scratch. +builddir=`mktemp -d` +trap "rm -rf $builddir" EXIT +cd $builddir + +git clone -b $branch https://github.com/igraph/igraph.git +cd igraph +./bootstrap.sh +./configure +./bootstrap.sh +make +cd interfaces/R +make + +## A temporary directory for R packages +libdir=`mktemp -d` +trap "rm -rf $libdir" EXIT + +## Install dependent packages +${R} -e " + options(repos=structure(c(CRAN='http://cran.rstudio.com/'))); \ + desc <- read.dcf('igraph/DESCRIPTION'); \ + depkeys <- c('Depends', 'Imports', 'Suggests', 'LinkingTo'); \ + cn <- intersect(colnames(desc), depkeys); \ + pkg <- gsub(' ', '', unlist(strsplit(desc[,cn], ','))); \ + install.packages(pkg, lib='$libdir', dependencies=NA); \ +" + +${R} -e " + .libPaths('$libdir'); \ + source('http://bioconductor.org/biocLite.R'); \ + biocLite('graph', suppressUpdates=TRUE, suppressAutoUpdate=TRUE); \ +" + +package=`cat igraph/DESCRIPTION | grep ^Package: | cut -f2 -d" "` +version=`cat igraph/DESCRIPTION | grep ^Version: | cut -f2 -d" "` + +echo " +CC = clang -fsanitize=address -fno-omit-frame-pointer +CXX = clang++ -fsanitize=address -fno-omit-frame-pointer +" >${builddir}/Makevars + +R_MAKEVARS_USER=${builddir}/Makevars R_LIBS=${libdir} \ + ${R} CMD INSTALL -l ${libdir} ${package}_${version}.tar.gz + +## Extract examples and run them +${R} -e " + library(tools); \ + rdfiles <- list.files('igraph/man', pattern='.*\\\\.Rd$', full.names=TRUE); \ + out <- file('igraph-Ex.R', open='w'); \ + cat('### Load the package\\n.libPaths(\'${libdir}\');library(graph);library(\'${package}\')\\n\\n', \ + file=out); \ + sapply(rdfiles, Rd2ex, out=out); \ + close(out) \ +" +${R} --no-save < igraph-Ex.R + +rm -rf $builddir +rm -rf $libdir diff --git a/tools/virtual/vagrant/scripts/check-r.sh b/tools/virtual/vagrant/scripts/check-r.sh new file mode 100755 index 0000000..d5a22af --- /dev/null +++ b/tools/virtual/vagrant/scripts/check-r.sh @@ -0,0 +1,73 @@ +#! /bin/sh + +## Quit immediately an error +set -e + +## Build the R package from a github branch. +## We assume that all the build tools, and the +## igraph dependencies are already installed, +## but the R packages we depend on are not. + +## If not specified, we build the master branch +branch=${1-master} + +## If not specified, we use the system R version +R=${2-R} + +## If not specified, no R version is used to determine output location +Rversion=${3-} + +## We freshly clone the repo from github and build igraph from scratch. +builddir=`mktemp -d` +trap "rm -rf $builddir" EXIT +cd $builddir + +git clone -b $branch https://github.com/igraph/igraph.git +cd igraph +./bootstrap.sh +./configure +./bootstrap.sh +make parsersources +cd interfaces/R +make + +## A temporary directory for R packages +libdir=`mktemp -d` +trap "rm -rf $libdir" EXIT + +## Install dependent packages +${R} -e " + options(repos=structure(c(CRAN='http://cran.rstudio.com/'))); \ + desc <- read.dcf('igraph/DESCRIPTION'); \ + depkeys <- c('Depends', 'Imports', 'Suggests', 'LinkingTo'); \ + cn <- intersect(colnames(desc), depkeys); \ + pkg <- gsub(' ', '', unlist(strsplit(desc[,cn], ','))); \ + install.packages(pkg, lib='$libdir', dependencies=NA); \ +" + +${R} -e " + .libPaths('$libdir'); \ + source('http://bioconductor.org/biocLite.R'); \ + biocLite('graph', suppressUpdates=TRUE, suppressAutoUpdate=TRUE); \ +" + +package=`cat igraph/DESCRIPTION | grep ^Package: | cut -f2 -d" "` +version=`cat igraph/DESCRIPTION | grep ^Version | cut -f2 -d" "` +commit=`git rev-parse --short HEAD` + +## Check R package +R_LIBS=${libdir} ${R} CMD check --as-cran ${package}_${version}.tar.gz || true + +## Upload the output +eval `ssh-agent -s` +trap "kill $SSH_AGENT_PID" EXIT +ssh-add +ssh -p 2222 csardi@igraph.org mkdir -p www/nightly/check/r/${Rversion}/${branch}/${commit} +scp -P 2222 ${package}.Rcheck/00check.log ${package}.Rcheck/00install.out \ + csardi@igraph.org:www/nightly/check/r/$Rversion/${branch}/${commit}/ + +## Clean up +rm -rf $builddir +rm -rf $libdir +kill $SSH_AGENT_PID + diff --git a/tools/virtual/vagrant/scripts/key/dummy b/tools/virtual/vagrant/scripts/key/dummy new file mode 100644 index 0000000..e69de29 diff --git a/tools/virtual/vagrant/scripts/update-r-svn.sh b/tools/virtual/vagrant/scripts/update-r-svn.sh new file mode 100755 index 0000000..099df82 --- /dev/null +++ b/tools/virtual/vagrant/scripts/update-r-svn.sh @@ -0,0 +1,14 @@ +#! /bin/sh + +## Quit immediately an error +set -e + +if [ ! -e ~vagrant/src/R-devel ]; then echo "No R SVN folder"; exit 0; fi + +cd ~vagrant/src/R-devel +svn update +./tools/rsync-recommended +./configure --prefix=$HOME/R/R-devel +make +make install + diff --git a/tools/virtual/vagrant/ubuntu-13.04-x86_64/Vagrantfile b/tools/virtual/vagrant/ubuntu-13.04-x86_64/Vagrantfile new file mode 100644 index 0000000..dc7c698 --- /dev/null +++ b/tools/virtual/vagrant/ubuntu-13.04-x86_64/Vagrantfile @@ -0,0 +1,28 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + + # Every Vagrant virtual environment requires a box to build off of. + config.vm.box = "ubuntu-x86_64" + config.vm.box_url = "file:///Users/csardi/BOX/ubuntu-x86_64/package.box" + + config.vm.synced_folder "../scripts/", "/tekton" + + config.vm.provision :shell, :path => "../provisioners/apt-update.sh" + config.vm.provision :shell, :path => "../provisioners/sshkey.sh" + config.vm.provision :shell, :path => "../provisioners/createdir.sh", :args => "~vagrant/vagrant/provisions" + config.vm.provision :shell, :path => "../provisioners/createdir.sh", :args => "~vagrant/vagrant/scripts" + config.vm.provision :shell, :path => "../provisioners/apt-get-install.sh", :args => "task-spooler" + config.vm.provision :shell, :path => "../provisioners/apt-get-install.sh", :args => "python" + config.vm.provision :shell, :path => "../provisioners/compilers.sh" + config.vm.provision :shell, :path => "../provisioners/igraphdeps.sh" + config.vm.provision :shell, :path => "../provisioners/installR.sh", :args => "2.15.3" + config.vm.provision :shell, :path => "../provisioners/installR.sh", :args => "3.0.1" + config.vm.provision :shell, :path => "../provisioners/installR-asan.sh", :args => "3.0.1" + config.vm.provision :shell, :path => "../provisioners/installR-svn.sh" + +end diff --git a/tools/virtual/vagrant/ubuntu-13.04-x86_64/error/dummy b/tools/virtual/vagrant/ubuntu-13.04-x86_64/error/dummy new file mode 100644 index 0000000..e69de29 diff --git a/tools/virtual/vagrant/ubuntu-13.04-x86_64/output/dummy b/tools/virtual/vagrant/ubuntu-13.04-x86_64/output/dummy new file mode 100644 index 0000000..e69de29 diff --git a/tools/virtual/vagrant/unload-all.sh b/tools/virtual/vagrant/unload-all.sh new file mode 100755 index 0000000..509f18c --- /dev/null +++ b/tools/virtual/vagrant/unload-all.sh @@ -0,0 +1,5 @@ +#! /bin/bash + +for a in `launchctl list | cut -f3 | grep "^org\.igraph\.tekton\."`; do + launchctl remove $a +done -- 2.30.2

    -C+Ca%QBiW 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 z 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 V_tDi18EIh{*4uvVo`3qa|Jm(79EJb@ literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/mixed.svg.gz b/doc/presentations/user2008/mixed.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..c5c80f65a7b2216889e0621164033567ab75ad9d GIT binary patch literal 1436 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 B3~T@Z literal 0 HcmV?d00001 diff --git a/doc/presentations/user2008/outdd.svg.gz b/doc/presentations/user2008/outdd.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..1489b07f895cf619e4e39c8cfb54c168b40c403e GIT binary patch literal 8837 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

  2. x^H?@}ht4(MpTwN^_Iz&7n^Bw*R2GRM^Qr z5IQt}r6g-zYQxG;BNQjg>lLUmU!Y~6<6>njreS2|qN~z7h}qAW%!A1CDVzq(xt%2R zQ1O-}ID!=V0E^89hya7elI1&O<&uV}Bmj^1R_vM%*dg$DJXviaC{ss6$HhFaG@#Nq zIkXyWXnwwXdU&~QY_4l)X=!OF8cZFt9Y+R>51Ws_i%N?2*3Q<}-r6B*L4{lw&;ylK zuYc=7oc!o=7c&JdsG_;L!Ee<-g!oTH(aHB%4e!26ed%}v2qNdpzA9Z#G|hZaR~ zAbA-I3X0-5ed*(G5eo~tlOMl*$nr^(%95uU9~qhH8R(cA(>$AeLl%khKLs*As4lvx z($UyW(ZoWp=eoH~qoadBzf=Ac67Z0fu7)l)x^Cun@~ee{fHCw0^0D+NjW71Ll zrc~SD;NoKYcz^%=55u9xI(Xk<2%lf@KDd3t`P#Dl{7%NdT0n`BoW!=DDF5xLK1asK zsm!xFLw99ilq}6oj!ji&mX@X-it#F09Rn05C8uEH;Vg@U4j7>CE8_3y<7`o+! z>#n}ZPcsg#OIiM0{!u`|UQLyqt)U)7Zs!Oj!z}X)$;87E&m4et!%VAxPf+5>@zh^0 zu)0F`WTDAh8XND;=EyvNYM3n5)uKKMF;iSPCIj{sU!Tfv|ME?f*Y5E0^?m@MiH4KI@qsa0NI-_)rTI^q!%(fzCwkU{CSLZc(AlQHAi}?u}}k~@ffB{ zsRzVDkBL=s9$vL;$|#;wO4xO+Ty5KB2>+<+595szCNKk`)=<{$Kx2OCgyS% za0p|w8#~`{TH>M(+bjC*56nyzOs7i=DaZ#UgW%49q*7bQP~qf@HIagjpC)HvV=YXx zlQ}BQF!eMA%tc~`j*R<@vL#bQNhfP&XpyswP639BF*twF@S-hMl`apT?<*`m08&Ts z;tzRvEef=h6l9WOQ{JtetaWltmoGjbg!~Tx@$tfxik$@cxvX}w=yAOwxWAyFTK}2Y z?rM8@rNPzE#?DyCNNpmgKH=(M8qQ5cK|#P-ghx`9mp?5wg!AH>I?HE$tY@TsK84LA zomOOT&KX*utBVM}t4jddd2UaHHr=CN=BHeJk>U&N+)IL|D~@f@61LM-oo=7k>YW7B zcuE-+=!^+NqdiyH0C+r{A`GBxmTxK-Y=LOD=f!&lRu6?IQq!*UfNq6<@xuX0@D2>- z1%*@tVj+mv4sB-jp=cGb5PDvJgP-hQed0qD7MoO>uXJeP@&SNT=L~uNEU>#jl-vTq zAaf-xql4}F>MbGx=({^et=PoKIt72+(pRVI zy|p3&;&40_taLWc1|vgVC!wC4{zX6OdM;~ZAN=>Pe?bB*iOh<&I5svduceV7(SoTs zJcaX$;NRArG_KE3u?DF`)1z%orra7XC@4roKNY3k#dhZifH5CumusBeopmGyRYfaW zV~L1~J9`9${9ZdXu+Ej!yZnL|ZKcQ%#^J_5PW4(HJ?+iQGmg&7>pdM^g#c2yFlyws zn^=DZP68T;ers%u9Ic+yx~jRq{49B*l0Vu}<#PN>1ywrRSpI4WU?74IZ{JbhKh@<) zL9p?W!QS&pLI=1ufS9a``lW&{caFonP`>>O4}G|-t&jJwPWBCH0BL=b7B)7H^E&l> z|A&GU+?6R%ad`~~HvuUXkdanjJ>ccz^MQJD<50&rDvtliDGZ&h9iAFpyx2F@G0-!z z!u>6@r6n~yHlQ#vDNLZuYnSZB&T(e>1kaxwZ*3fqbrUuJ2Dog z$q7X*g}8`!Xjq#kJLH${%gdU$$5fIw00uGS0$?(!ez10Dx8(8e;rjk|N!!p=*PM){ zQD&fkKCI5|K_`17&6q#zSk-{X0hli;k)w%yDY399)?-J#w{))&izV1!C5zeoM zU$WMhvrXBo?3pkCEHAbs4kuqHN?2mQSh9c)wo5<4aUmZBYwCc-K@Sk zDLc)KtX5F`hZgmY-bP@%FqfXReQ+Jq^amNX3bl&UJUM zs74QtN_T3yEee$gnX!0}i-5{+x`~Z3fb+Q$p)w%cPXm?fK2mM+zjEVZ4>(qrg5>Bz zMz4NRoTYD-iv5_~eh;q6epDI15yV^hy!K$ng&Lai?j_47iD+jOf};#7%Tk6bC(iNN z8J48mpn=Ypd6hD3!~V&vjXI0Tv@L9@lHSEm7}|i&`J1kcwP9mJP;m1@q@6Wu0Er=b zG)7L(n}#@w`SJIT8u1%Kc;D!4ZTB%vGr%PDY^alDAEM$}?gg_kFtP=l7AS8@Cl_hs zIcTy8nTIuOQ~BM(47btIQ&W^=HGiSt9UCQi_LMPNjRiXKyf3WKWK9s!h?_s{wzD`D=mx!gV_ABT7iVmx3_~0S|8;Fo`Qnqm4Y6l}SNkdul zcO;P4@0NLpExS)-3owW>6zn4cBM}hA0g(>#%!gb7(u0Z;DBQ~cY(BXPcv13T8~Szk zqL?dfy4a9kRpW=4J6mi?;@(aHomtz_fNvJKt;>rI3_>Dm(*6{)Y`w=SfR+tCm#-tK zo;W&#{Jxr7QfBXNOW3Jut4xqWSn5 zzotFl_UgLOh@e$SKbz(roG&8v7P)VPz`uWzsH0-YRVNc(8s*oI%@LY4c3g_4+37&6EXIJXEtNOKJhpfe`cA%c~?fV6c6K{)}&TwJZO< z*2=77zA@`NH#10u+1?t3YB^u}<{~AHVOo~VKW7(!sius$uD0O1G~JkToUstbH-=5F#8_Jv`9;Z$ zyPxE`EJr!gpDtHg+=MT*J#N|vT0Uv)G^Np*KA~n~Lpjei2Q;&k%!X4_jjpw45*1z! zPn5VGHsLXdJsq@gyB+N<6hiK(Icjw8=pRRS#B#YwUqGH;q_-@)bl2#cHK~8gs)nxF zay@ao$upG-bHANst;Q`t=P5g+l&z8Uwl(`-oa=E~%_xZQsBqcZ_zRh-OFYp; zO{vj$(MX!-y@^!$b?nsfO9=-$^|xd|M~VS$aZCNh&@;Bu!sJZpvb|}tt9B?(%5xeR?zai0}C;I_wA?T~?)x)?TVf)nk7%EQ%fcvZ{#8q^VPnP;d{A3Hv#}aY;Ra%#`EwJ47md6W5)aqtEUfK?eFONb`|rPq9W`u+raHs($+T!S8rBXjh{rOJSg(Ue zVAXE-rpU1t?b&^M{hP}?Zo`^-uM(xe2EAh9a{5ho!+0!Q9+&Bot`;Q_vIQZUYBGG; zKE!oK97AK7`Z$dqAss%$K7PFGCng~lP`Ay(g*7sl9+5ZB9+_gJ)!&Wlw~He~nA;WC zc040>(%De6aj;S4+T_dhU@yH6M>$ zg!pQl5y-3h=3N;!`dCk11T@>Hamy;sRy=VXmVF0x2#-1YDJ5_m{*38Qo%*EV z>Am2S7q(FcOF7)>6%@@Xd`RQrV#GxJS%yW*D_?k2hiv}@D zqq?Q)r~${pTO$1HDwYYb!``wMv19Uq+|TagBRSZf6)SS(ANFM>e>**>XP-%QrJtTLUSTA^M{c8p{0EkBWDpk136{(gf9 z31T5T(NNbZX7l<@HlXm>vZ3ZV;ol|gOg`gP)IOGYQB1-3{(X9=m=vgSWL*RL_EZIY zc&3pcT4Y9cOXFGRNhJS}9iw*4*tqf5*0yC4G^~2j_zrw#;io!LwYwQ{(_C*{NICd` z8t+(1k zuyB;v&i0$X9u4F7TY`DDfN|Nyu1VA%(y7K^))h|Vw&p?}7o6C|ZM9O4I6eI_ElC{E(%r-|H!YQbY45ICCG#D0%wa{etX9F?j)j8@!^Kot^LA2mAliD#@F)hl3o144Dpj0Px(z5c|B$aBu5zjWg zT-M<^zD5Q)e--K2{XSWf4bcrAHk-CCZ(q5ZqiMHiD6r+23cwQvSJlrbGO@}+T1}Q; zM|g6JSRWj-V4RG(pYl5dE_zJPDu13%%Wkr^k2J5vJlh;{=OSAzMVn;CmzcWMUDA)a z{33X-|Ky|>EM59%sNE$Wu}@H-{4eK}=>ZjUvRZ8t!W^F|>h}*NR#USDJA(6<#1;;X zyo-M0$V%NbYIIudDdg!z;PkuQytriArTc`{(nqo6;tY=X6N|NnhK=^b*s_a?SrS(8 zzJ`#H5KJ9sH6J>5d7AA-b!V(L)%D2JtNEYmOT4%J_y|LH1pU6`WzqXQ%}{hdl7QbrjUrg;6qx+bhl{#cNgZhff_Q+kh~&D zbiEbpM+F$Uhoih9o1aj9;||;+Zo$qG8#kp&U`Wy)uBg-K88fatipVLq)-$CU>6Q*2 zOCwig>zfS55ki@V8bb-(8KowoCX-EGHXmJ$@-#o##7N|3La=KKyR^0i5pAwv97^+L zzJIX|)y&npOpZcLWp&PmDO9&l&ZpxwDNrqr>sAGNc~|i-kI-G~in_X)ZjPeP9Td~y z)#qVtWvnweGe@9_KVgfnG2KwI&8iOGmX z71}~jKiZYQTO)?L$YCw=yDjK)jAY$rt6C?XQm+#PbJl+2Oh2qI5%TBwh-A7(em z$UBZE6td?Tnw%IBP-|V% zI6_(O4(SP}Rx6q&`%U(Q>86WSQcMvWrR>eqRtZ8joT-Yxk>PKI{NV9slDO7 zE0GD^msn<(RKmzHsDR$WisG3xEJrk+}8?6*I+Qa)O zZtu;0Gp6#szuAC03)x2FN55%2m2{ihYt`gI-|^uAAut=jAk2k_kP!geRu+^Ww1IY; z(576;c0E~XXUD&9j(<}NcCC5ORXRL;>EFQ5s|92N1FD zM>ltwEGCPTTg%G1cQ=tAWz1oOEsZSle{uk6%Ve(Y$K41*!`Qdqf0qmAukHm)LfVqX z_R-a*BC%;q;mE5yvVtnBZYl-B15GY48Z;V=oHa(?u}qQ;xUC1NZ)cjdA>D_*i;e!yHkIoLX0*ETBlAUNc_S1NBYIP=iL%fqgF@A_H< zWH}Zrn}#rO?iN8ljNQ_yq0;K}fKRk!!zDHtx6^mfn7rv=n%Uk?lt#pt2-Kt?BbIxHk#r%l~x5#xBjtI!>h z1c#@*1O-G+>u5q>e?2E@bQX^B^_Og$(Ihpgp&SRQXv$lCH3J~~H-4HW*>3eAb=mm7 zEh1j(a}K)7C2EOuqvYe~E=l`WC~EtDw*pV_x~6qn>S}ha6G4{2=v&mG8G;g?%LjCx z(C==cvsUxQWsah7n7@%1?8uLv>3XxeBRo3Jis-wUo)OB?&T=BZ_cj3MM|W&k6+BQM=O zv9ND9DV6JuYyujQUa7i3PzPT>(Z@qo$62{dVsYe! z=XsQ)Zo+pNet}EduO^1F6PR56|M2vdVQqFz8}3t~cqyeoaSFxVo#5{7F2&ugP^7rK zyF+me?(R--w*m<+`|i8Hz5nEQl4I7aSu@vp&4X;u+tSrgyf9B#SQy;d=pMISX&8ixER~Y9NFS>0uS;{kaRB_sZ%a5Ia zK5}f_pPvs+K;2djsL-PtMfL4SB4DqV*m;iM8QbSNxRhM;cuc>0IsTQBayayd81!$B zdn1%=f_;(RU^C6XO#KbGd@(xC zxJ=7s&MJ5=EMq2_Ls*6;XZyPsM%KpuE6v#nnwi0K0a5NVAywP0RIHnbl%2J(>CiK2 zQS>obs(*62IOsd?&CXxbJzP%1?aqR!8i9P{qpy&N=Zs0<#Mws7##Uy|d~4E^X2Ooy zFcV-1U^)AmMdTx8H;0GTA0XxTMadcEhv$^$d=9>fpKeQTt4n7cy9XsZmEXp~FRsxh zyKCo&om2$@Z@lbqPTz*?{4&BzTbU*mMydI6g#_=<7uk@Wr_y(RAt{*Moi%T6EG^|* z)f5sVjRSpFjJJFpednAd9;mvwU0bqsRzwJE*3GVU+arDG=bzEgZf;$jYR$8e=7gy^ z`=5tTczJoTpdhH1Ch6JDvNeT&>1b$s?xlx{TgRtr*1_AKG4`d3S7_J-L`8m@sfO z`6N{@vLR(K@qd}pAc7K8<|%xr>ib?dF*kNSrsq!2TlVD8jexX`#}-Ci7@2!sB%rNk zVVuhLb)NI+7U7n=;wg9@(4Q~}P)p})_jAbba7@f%GAjNGH|=;pdXF9YEH37$&%J~37O6zL%czNTcmK34xS-fM+S%jBoCd__Q>N{GCe4e( zl63Jx^ zOUTQHg`?cKn4jM*Z@4!ltnWD&%&I!qOFn;8qIQSB=1Cb`EWaDXyU6ns_f{zZSzyu~ zPcAVV5(Qx=?+v#Ui9N@3$uXIQElR<}NQQrlD^$Gm59l1RR(VMG*yoGS<>C=PEROpp z)6X>3GHtOL7;N{0y>;P?-^mw#=F{C}Pcp-I@^&#Mcj)+4MD+GK^=3&g2TK%G*(=tO z?qbY9Od?)w5_o6&f@G9QWvID%yA3|lFU3_W3`CUCP`)+zZ`BnEmLlQB-=xWuxG~Lt zV0@XLG!=Htf(iB08lk6t<~`d#E#aq$HwVd1koZk9sLq9oGI$D9e;8jR;hVUhZW8|W zWt`SeUz&#mttt{$jS6X4DpA#e8FtV-b_H==P`3YzS4js~Zpz~Al)n?6_qRQuy_Z6a zC%iW8=_l@`I8SIQe)bsZm%2;qty`wQk9q1^-rs9iPd#Z~^;Jy@3cn8o!rwP@t!PoT zzH%1@+^@0SNB$+=Um09*>!B*6+qTtb+>re7>VI2JZ`k(hn=j$SP1$))>%Wv5-+I^B z`WWz*)k`!$+oeT1{Vbz>XjZiOUX6J{Te|e%H~9%zNq@59+@}n*S2{hbaNc&vQX~8{ zLp~UsQrdmm$f*0jA~N->JhcYJ3k)HFLfD5-7yDKxRH>jtU-;Pg+@(CbgvYj z=I75{xTa)F43esy=0mQ}QFEOHUy|`N4cI3qc6QpuG1u&o3NEu%-NLay7kU_dNObO@ga&=b5mH|PVA7x{8KYh$}h ze}1}2c)^5`9(7e3>OXTq`9@%T@J2_i=vaMYOq*V1dhdaLY7@2ekfv?#bGgxL(JGcN z|Lm{SOwub$5h*x*BiAI$L|sqc?@yTP)aFHf!S`OA%b|Qq+oF;~jhs(kdowf%>HBhB zX2C0yP6q9%`CJLqpDHo7vF9=SYQhx`@x4CDHxM1mx>poP_PN0+r+*<#B>Nz4!!+k1 zHmUq0wiQx7q^1&GYB1XL*-i1NL!m!~a!ZOTK6UBSYGX^wubNz3y3d2R&JMnRMc%Du z!Of0W{Hjz?mq)tlj#R_fp_qg(NJwVaMQ+0@v=$tX=2@-)Y^L=FRT$Ol(hW&iT2gQA z7HQ;7wv9a}|Jt20&Occ~54k%1cXGf)qc_FRysD2acTNk*79iu_mon`I?D8s2T(PWe zHpY^QzOGtACNWLnYhsEQF4Ay(FXQ(uc2dU1CGXHKfx)hd3!+kB|N-;rE=+uC&H z?#j!h!jK(V;_`sW6oKlxAe*!os7>+MDu#{s6JGxfUVoL4-h|I)Ao$ZkH$TbuZGS`3 z|K;2FWtpV7wV;KgTW^W#h29%h);1&~`s)ZbaU<65ZYDwS0xGIHo*9}rgF*gSBubAK|Z7UNn5IZT)%rJXf$qSNy? z-(dvmR{?baeQTi6GW?SbXRwuN?}~nKf}Wg{o9m|r{7YOw$uA}5DFrNbD*?TZ)=^f^ zgw%0Tu(9_ICVCM|VP`(YlZo6jWl~@K{sd^;dLD|tebzS|*gCY0jR#v4+=+U?U6MDb z>%8F0zDo5!2FjR(em2+E{Upd{a&33I*Y*?>kWbi(6OM>x$)ZA2)}%6Zsx<6XawQnt zWzz%CDwnoX15+T`EXIf z@1iumC8YuoHY&b%6l7{lu~Cei+Bxe;F4>9iU9VmjF$%{bVw?Z|#}GMQe%?03BM-Wj zI`c+z;JvKdFA8pTDQp>5^v@cr|MF12Q~$t(OmOna>%D28b`!ZLyRqd{0(-P!HV)S2 z+4*;D_wa}k=W@ZG6{iZnf?)WEW*!`qmhbZ&R`Ez>s>Djvochl??q%7(9argGiipn! z7|S-X__*5+|HLvEZZM+Vk&}P+CEnXEqD!}=L1x(oRKj~B8zspq_UFL*K?ZdSCZ}>B zFAGDh2OMY6;r@6RLJ9coo&go@i&$)0pC67uhme z=~$|^4FjdbV<~B7eAX9&4_lN6){nu!U4W2K%9O7g7s$er@JY$doNrUt)Q zteFLR;|JEU0al_Wg>n~v{OncLvgS{-*G*iXNu`R}bu&e)Zf`$jaF(wbqp$COzC!r} zDGGjo?E$<KlI3s zB3fQ2_P1C#82Um3*voR=97=PH4nl^6F<*}%3lNpgDo#@RK5e$s;%x1vmV7!Wy7OoL zfL-k-dLY~(gq4J*d^If0fj%FUPaHHwTN}h7Vx0W=vmemtSb1K{0o`+Rc0=U?o$8hYM^QbTuouX_Y z(l1Mh^<5K1{TGz5{9_UHCR~N8o#Oj0_%g${i&tFsL|ZH_$9kE^5;8@#ab|6@evN3S zLM#5`!CQx*j_L`{_|f=0QUrg_Kawngp4K@Z2_Ar$Ka(sIV!Yzo?y5Nlrxs5Y%TiJ{ zV$MrbmsmA$>kN-2ZW0_6DhoPEC*KZlTxpuhS?Z*89#Ar+pA4|}pGB#|i zZz#yA#e}(G!2fQkSATIDPx?240^tFhTLs-2q|nScd~#*+4HeOX>)IEYcV;#0`nI5(l}Mt@wJ zgR*LC3`ht<-k)u-`3C;EIt)!J8h4pD^`M#-#5L~SR_g8@^NvUNSJ~fq=d;?xZbLa~ z7y_4K_&GJpc6~p}`jgxH;U-&QSya45v>#kmFynX6^%XP@t-&eIouLU!jGkhV z`#p>gCPONJ3QPT~A3|l|9tZ1rS)y(SgW!?IYQLFFby}?`=_^y!M{G4bgLQ4v%K?c| z0B@!>EFEcL9ghIFtNpFy<~JmO`jc_mDO#cSSFk@{_9s|B)3%hV!ytwK?dOi?BQl$k zNYx)PT4?Vx$YWEfGM^*W#K`h3Ku=Y2bx8_Tx@mm+?nN_n%eTx#y}_T8PHfFXhp4Y& zg@X5!s1)3W5xL8Y7BQ<=IClm?WP|B4VlF>^bnywX%2jMHilZ?EId*3hY}1AD3je33I%i% zl#Bzw8L5pEbEno25Py2>=@(P-KZtp$4>siZYiNP5qEpQBXqMfn9$Tkbwjdb@^;b9h8LHoP7R~y@Fvvfj2T=jb|4q$h8Z1 z$`YKM7}!~gRnt;bhrlmYCkwu7V={V4$d&3gflWzDljxBYh|GmmL|t3gJffEHdKHXoO%+w^9r?a+< z&)0d*o~E?(;Xk1IzW@as;v_f)fT<^ukMmn#cB0Vr8px&RatpQ*@>M+j2 zBZblbF-s-?p;0g?KiO_QL`i!#&Z-*EKGd%5N<4%oabOl4NZKwdXvSa>h=AI1nN%{K z5((LJoUNLFW8%Omb-T4SHL!g|1ZN*+Ze>2N16-8{zz&a?vq4GJZYfre`lqBWHt5wc! zC-3`$d|k zB#&OmmLn-}d)uUbQ=K*HcUtVF5H8B#-?0W91S;|QzHQSd6QeiMb`XW}rK%hOj2eo-4TLr^#7#hq$w|Rve-mVI#HN^D{-)O(q zCI?>K#p5#5SE>8p%k}rVZK3weGuGfR4A|fAO=&!NOMAa1X_%2R;^0d&*Xi%R47XwA z3xffWm?~?2N?Rf~I|jNWU4!XEt8);?@HFjJqPXeJF}`PN&UYQ zAT%o4(YdMs$TA5xuTxx!I-6;u^?vHK7+gHL=QZNh*NR;rUe9V*B z-4sSYh$xdOGX{$^9{j8PkiKJc4GDv4P0}C7W@U%&vh9~X|4bF9g~i$B6@}i9xeU70 z5=3k4ka=df-{)GaN5*U$7(AHwwUJu1B~Ov9g#oj3%geP6zO8AcrKxlm29Jght;$*H zU#h$>(;bQSZx6h$_FqIMKhTUDt*-B)yU6)V2`UkEkC{mpUp$P|V-DAEdu z1}E)wPx}smqkI#5+%renp&)K>5HyH^_J54~msvESV-O}UBrEnS4nPV>=p2T5pSV6$ zb>lfNhp;#)83}o>->XZb%Qe;Ge=eG6JkG1=`z-+Mst7LW1_sWE^YS{>cRadk^leeT z*~QuLuiIX`m!}(OXtLfk&9NKuk!PocTH0n`e*==}2Why2tfAaRA`>IQo@oj|IiXx! z^EZFZFBKP%%iBo17;4EJe%RLMUj<05G69pcK@abXuS9t3Y!V=4{nX^Kh+WHUq`0UY z4TgwQAi=AZ1YfD-vv+7!j*Ht%Ryc}^35`hel5G@!q&R59WozePV_Mr-{>PDwY-HZ{ zkfvtyn;}h%6X4;jOfT2g#NnhbF7XsuaLO{^rMi^_YBpx1iWIH48J5xv$yJ)y>8fd_ zpcDCmld^rQi+CB>00d!PkhEP<8#`t#Oplw_eT%sw*oRGJmtI$+_Ycgt9L^7+&W~rFY+B%an0{e8J3_UPZCB;jgGH!-ztQC$;ur|h=%rWpR| z)_#&1y)VtVu|vP>5Nr{`xNNiyS-{gUVBnjUZkY-a+}A??)eu^f=`$t*cob~$e#L{q zw6VFE0ON`3|5-tZ86SO)E#2%}>hE#m4KXhs_;9pI`D^_H%O-~WL*Mj82RHOSSzM&! z;DT$PkA%Of&-^n`x0^?0(*Jc7slDJh0E{ePtKu~~wTlySmOS5OS`2I))P&yiNtE7_ z#V`4A7*I>7cKT)=Q&3~BN$ZR%4Na~?Y4P^oyhqOJ6~mqEu<_p2-HKe}=3`nLM0r;e z8fQR#H~RYGb$2bi@kD9+(m#9-R1M@<-%;QFGpvz|2y>TvE=&cUv)p1*;fScjdiuU! zk4@BMP^WbQBIDT>=V~tiKwLKB{P@IZfT_o4yxd^R>-hg9$ry zdxteWRid*zMN^(6dC5R2U-$He6nec_;`86>%Chxar1`hoofl@!z&x zQf-;zAvjx$iWUYmoz(ua^j%4a?V=AWMSvd4*!xBwVI!|_c_C%;n^!RmOCekY@bU%7Qcf39d$Q$1{rx&$>}MvG*MD z4A_2${-u(hxT)KS-J+2Yy-cK^qfVs&uhouWj;W1x$bNw}?U=y?*hMr|cJT7(-KsFg zc?BQ%-N?#L_lq-3wA7Rd%!QxV3&_C{5fGs>y`FESh8i}ugd1Gr^heJQ;5r{B)dyF)l$Sq9qC9`bF%@GO`A#dhjJfh-&FdOaKuK7;bLr4 zMg4AJMoeDe$Cj{Y<~rA3?Q<0K|762mahfkGfAuQc;+&k+txJaFBN53v>G`!w^j~*q z{2rb$4_u;kie}y^ z+269F%x>0ymTr#iBIv^~5|Q`|ZE|Zq)}j&9YIw{0aAvY*`3!#XAd_9Nsf#ClUd2rb z=^ZZJoR-&XeI1aVP+554!(Mw@YU`=HbAz^zH_gJ(Hbzlt1G`Lu=#99W`V(3Ox~&9i_tbCIb$ zo@>14;MPvKHqLJTJf^Lwj73PHi%77hZ`G0jl2uvkxl;AF1xPe%_*EbiJsh4u;6&6ZB;| zZ`b!be=m)tvS75i*8f9NLJrfARvbG$Uj6R?m3D5CYDMqtPOr~M z;-gis?Ef{1a=@nYa17I?lDQnXR-;Zw9KxTt;~lTs z^yNBWkF&RmH|K?RZ>q@T4VrTkS|T0ECS~UU^Aoe;fkPEhoKy#bh{vGO)AV8n0JsM| z|0>mgN^jfXr+-2p2wgs^$>giV8)e=JU`?h+Peht{QS4*?I9r{1YEelu9)Y+m59fD( zS9&71F%GF!G&_3q9_W`xigeh})s4#(>3ki?dQBC%s$AvbrW29GV;GgqRhL%m^!dU^ z?{!A#ua1MBd0JjgEyFRjGi`;Ej�X?P~-y1YC4mPjw#NyeA)T*p+e`-dT1#!(bWQ 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 z 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